Skip to content

Commit

Permalink
cleanups wrt modern tsc
Browse files Browse the repository at this point in the history
  • Loading branch information
samthor committed Jun 16, 2024
1 parent 7dea965 commit 0a3d4ab
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/array.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

/**
* Finds all indexes of a given array which match the given predicate.
* Finds all indexes of a given iterable which match the given predicate.
*/
export function findAllIndex<X>(arr: Iterable<X>, predicate: (x: X) => boolean): number[] {
const out: number[] = [];
Expand Down
9 changes: 3 additions & 6 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,15 @@ export async function* combineAsyncGenerators<T, Y = void>(
const next = await Promise.race(nexts);

if (next.res.done) {
// We put an unresolvable promise here so it'll never resolve again.
// Put an unresolvable promise here so it'll never resolve again and `Promise.race` will
// ignore it.
nexts[next.index] = unresolvedPromise;
doneValues[next.index] = next.res.value;
++doneCount;
} else {
// TODO: tsc is confused
const value = next.res.value as T;

// We got a value on this generator! Reset it for next time.
nexts[next.index] = buildNext(next.index);

yield { index: next.index, value };
yield { index: next.index, value: next.res.value };
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/intermediate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function buildAsyncIntermediate<T, Y = void>(): AsyncIntermediateReturn<T
)[] = [];
let done = false;

const gen = (async function* () {
const gen = (async function* (): AsyncGenerator<T, Y, void> {
for (;;) {
const next = pending.shift();
if (next === undefined) {
Expand All @@ -48,7 +48,7 @@ export function buildAsyncIntermediate<T, Y = void>(): AsyncIntermediateReturn<T
yield next.value;
next.resolve(); // resolve after processed
}
} as () => AsyncGenerator<T, Y, void>)();
})();

const send = (value: T | PromiseLike<T>) => {
if (done) {
Expand Down
7 changes: 5 additions & 2 deletions src/memoize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ export function clearMemoize<T, R>(fn: (...args: T[]) => R, ...args: T[]) {


/**
* Calls the passed {@link Function} but memoize the result. Matching calls to this helper will
* return the same result. This holds the object parameters weakly.
* Calls the passed {@link Function} but memoize the result based on the function arguments.
* The memoize state is stored globally based on the function ref.
*
* Matching calls to this helper will return the same result.
* Holds the object parameters (i.e., non-nullable typeof 'object') weakly.
*/
export function callMemoize<T, R>(fn: (...args: T[]) => R, ...args: T[]): R {
if (memoizeMap === undefined) {
Expand Down
12 changes: 12 additions & 0 deletions src/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export function randomRangeInt(a: number, b: number = 0) {
/**
* Generates seeded, random 32-bit numbers between `[-2147483648,2147483647]` (i.e., `[-2^31,(2^31)-1]`).
*
* The seed must be an integer, if a float is passed, only the integer part is used (e.g., `0.5`
* becomes `0`).
*
* The best use is to clip these numbers with a mask, e.g., `gen() & 0xffff` for 16-bit.
*/
export function seeded32(s: number): () => number {
Expand All @@ -134,3 +137,12 @@ export function seeded32(s: number): () => number {
(t = t ^ (t >>> 15))
);
}

/**
* Generates seeded `Math.random()` behavior (i.e., >=0 and <1). Requires integer seed, just like
* {@link seeded32}.
*/
export function seededRand(s: number): () => number {
const actual = seeded32(s);
return () => ((actual() / 2147483648) + 1.0) / 2.0;
}
10 changes: 5 additions & 5 deletions src/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ export function promiseForSignal<T = never>(
): Promise<T> {
if (signal.aborted) {
return Promise.resolve(resolveWith);
} else {
return new Promise((resolve) => {
signal.addEventListener('abort', () => resolve(resolveWith));
});
}
return new Promise((resolve) => {
signal.addEventListener('abort', () => resolve(resolveWith));
});
}

/**
* Returns a new {@link AbortSignal} that can be individually aborted, but which is also tied to
* the lifetimes of the passed signals. If any passed signals are aborted, the derived symbol also
* aborts.
*
* If any passed signal is already aborted, returns one of them directly (not derived).
* If any passed signal is already aborted, returns one of them directly (not derived), with a no-op
* abort function.
*/
export function derivedSignal(...previous: AbortSignal[]) {
const previouslyAborted = previous.find((x) => x.aborted);
Expand Down
31 changes: 15 additions & 16 deletions src/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { promiseForSignal, symbolAbortSignal } from './internal.js';
import { timeout } from './promise.js';
import { WorkQueue } from './queue.js';


export type TaskType<T> = {

/**
* Resolves when the passed {@link AbortSignal} is aborted, or rejects if the task runner throws.
*/
Expand All @@ -14,29 +12,32 @@ export type TaskType<T> = {
* Helper to queue items into the {@link workTask}.
*/
queue: (arg: T, ...rest: T[]) => void;

}

};

export type TaskOptions = {
signal: AbortSignal;

/**
* Whether only to pass unique items (as per {@link Set} equality) to the task runner.
*
* @default false
*/
unique: boolean;

/**
* The minimum time to wait before running a task.
*
* @default 0
*/
min: number;

/**
* The time to wait for items to run the task on, after the first is seen.
*
* @default 0
*/
delay: number;
}

};

/**
* Runs a task forever (unless it crashes). This enables a "single-threaded" task to run over items
Expand All @@ -47,26 +48,24 @@ export type TaskOptions = {
*
* Returns a function which triggers the task for new items.
*/
export function workTask<T = void>(task: (...args: T[]) => void | Promise<void>, options: Partial<TaskOptions> = {}): TaskType<T> {
export function workTask<T = void>(
task: (...args: T[]) => void | Promise<void>,
options: Partial<TaskOptions> = {},
): TaskType<T> {
const wq = new WorkQueue<T>();

const {
min = 0,
delay = 0,
signal,
unique = false,
} = options;
const { min = 0, delay = 0, signal, unique = false } = options;
const signalPromise = promiseForSignal(signal);

const done = (async () => {
for (; ;) {
for (;;) {
try {
await Promise.race([signalPromise, timeout(min)]);
await Promise.race([signalPromise, wq.wait()]);
await Promise.race([signalPromise, timeout(delay)]);
} catch (e) {
if (e === symbolAbortSignal) {
return; // aborted, drop tasks
return; // aborted, drop tasks
}
throw e;
}
Expand Down
9 changes: 9 additions & 0 deletions test/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ test('sub', () => {
assert.strictEqual(array.findSubArray(arr, ['b', 'c']), 1);
assert.strictEqual(array.findSubArray(arr, ['b', 'c', 'a']), -1);
assert.strictEqual(array.arrayContainsSub(arr, ['b', 'c']), true);

// empty array should be at index zero
assert.strictEqual(array.findSubArray(arr, []), 0);

// empty array is in empty array at zero
assert.strictEqual(array.findSubArray([], []), 0);

// can't find in empty array
assert.strictEqual(array.findSubArray([], ['cannot be found']), -1);
});
16 changes: 16 additions & 0 deletions test/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,20 @@ test('seed', () => {
assert.strictEqual(-1182780713 & 0xff, 215); // can mask
assert.strictEqual(s(), -1646890852);
assert.strictEqual(s(), 428646200);

const s2 = primitives.seededRand(1000);
assert.strictEqual(s2(), 0.7110681121703237);
assert.strictEqual(s2(), 0.9401418915949762);
assert.strictEqual(s2(), 0.6993810315616429);
assert.strictEqual(s2(), 0.9919486327562481);
assert.strictEqual(s2(), 0.24484887369908392);
assert.strictEqual(s2(), 0.2944149309769273);

// seed with actual rand
const s3 = primitives.seededRand(primitives.randomRangeInt(0, 10_000));
for (let i = 0; i < 10_000; ++i) {
const v = s3();
assert.ok(v >= 0.0);
assert.ok(v < 1.0);
}
});

0 comments on commit 0a3d4ab

Please sign in to comment.