From 0a3d4abe9f45667e788a13a7de67f838ebe67c12 Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Sun, 16 Jun 2024 13:01:30 +1000 Subject: [PATCH] cleanups wrt modern tsc --- src/array.ts | 2 +- src/generator.ts | 9 +++------ src/intermediate.ts | 4 ++-- src/memoize.ts | 7 +++++-- src/primitives.ts | 12 ++++++++++++ src/signal.ts | 10 +++++----- src/task.ts | 31 +++++++++++++++---------------- test/array.ts | 9 +++++++++ test/primitives.ts | 16 ++++++++++++++++ 9 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/array.ts b/src/array.ts index 57969c2..77784df 100644 --- a/src/array.ts +++ b/src/array.ts @@ -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(arr: Iterable, predicate: (x: X) => boolean): number[] { const out: number[] = []; diff --git a/src/generator.ts b/src/generator.ts index f314d3f..ab4a210 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -32,18 +32,15 @@ export async function* combineAsyncGenerators( 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 }; } } diff --git a/src/intermediate.ts b/src/intermediate.ts index 1f57b4e..658c827 100644 --- a/src/intermediate.ts +++ b/src/intermediate.ts @@ -32,7 +32,7 @@ export function buildAsyncIntermediate(): AsyncIntermediateReturn { for (;;) { const next = pending.shift(); if (next === undefined) { @@ -48,7 +48,7 @@ export function buildAsyncIntermediate(): AsyncIntermediateReturn AsyncGenerator)(); + })(); const send = (value: T | PromiseLike) => { if (done) { diff --git a/src/memoize.ts b/src/memoize.ts index b4b23a2..30d4146 100644 --- a/src/memoize.ts +++ b/src/memoize.ts @@ -104,8 +104,11 @@ export function clearMemoize(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(fn: (...args: T[]) => R, ...args: T[]): R { if (memoizeMap === undefined) { diff --git a/src/primitives.ts b/src/primitives.ts index 2e8b84c..2a07a44 100644 --- a/src/primitives.ts +++ b/src/primitives.ts @@ -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 { @@ -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; +} \ No newline at end of file diff --git a/src/signal.ts b/src/signal.ts index acd822d..9ab5136 100644 --- a/src/signal.ts +++ b/src/signal.ts @@ -21,11 +21,10 @@ export function promiseForSignal( ): Promise { 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)); + }); } /** @@ -33,7 +32,8 @@ export function promiseForSignal( * 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); diff --git a/src/task.ts b/src/task.ts index c3cdf1f..55db261 100644 --- a/src/task.ts +++ b/src/task.ts @@ -2,9 +2,7 @@ import { promiseForSignal, symbolAbortSignal } from './internal.js'; import { timeout } from './promise.js'; import { WorkQueue } from './queue.js'; - export type TaskType = { - /** * Resolves when the passed {@link AbortSignal} is aborted, or rejects if the task runner throws. */ @@ -14,29 +12,32 @@ export type TaskType = { * 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 @@ -47,26 +48,24 @@ export type TaskOptions = { * * Returns a function which triggers the task for new items. */ -export function workTask(task: (...args: T[]) => void | Promise, options: Partial = {}): TaskType { +export function workTask( + task: (...args: T[]) => void | Promise, + options: Partial = {}, +): TaskType { const wq = new WorkQueue(); - 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; } diff --git a/test/array.ts b/test/array.ts index c503361..70b558c 100644 --- a/test/array.ts +++ b/test/array.ts @@ -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); }); diff --git a/test/primitives.ts b/test/primitives.ts index 7c3e4c3..139151f 100644 --- a/test/primitives.ts +++ b/test/primitives.ts @@ -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); + } });