From 3805421d44d64e995a5cb1dddc4cc653cb61ddc5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 4 Jun 2019 14:05:41 -0400 Subject: [PATCH 1/3] solve diamond dependencies (#2660) --- src/runtime/store/index.ts | 62 ++++++++++++++++++++++++++++++-------- test/store/index.ts | 28 +++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index e7db228401..f03a550c5f 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -112,12 +112,12 @@ type StoresValues = T extends Readable ? U : * applying an aggregation function over its input values. * @param {Stores} stores input stores * @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values - * @param {*=}initial_value when used asynchronously + * @param {*=}value initial value, when used asynchronously */ export function derived( stores: S, fn: (values: StoresValues, set?: Subscriber) => T | Unsubscriber | void, - initial_value?: T, + value?: T, ): Readable { const single = !Array.isArray(stores); @@ -127,12 +127,28 @@ export function derived( const auto = fn.length < 2; - return readable(initial_value, (set) => { - let inited = false; + const subscribers: Array> = []; + let unsubscribers; + let cleanup = noop; + let running = false; + + function invalidate() { + subscribers.forEach(subscriber => subscriber[1]()); + } + + function set(current_value) { + value = current_value; + if (running) { + invalidate(); + subscribers.forEach(subscriber => subscriber[0](value)); + } + } + + function start() { const values: StoresValues = [] as StoresValues; let pending = 0; - let cleanup = noop; + running = false; const sync = () => { if (pending) { @@ -147,27 +163,47 @@ export function derived( } }; - const unsubscribers = stores_array.map((store, i) => store.subscribe( + unsubscribers = stores_array.map((store, i) => store.subscribe( (value) => { values[i] = value; pending &= ~(1 << i); - if (inited) { + if (running) { sync(); } }, () => { + invalidate(); pending |= (1 << i); }), ); - inited = true; sync(); + running = true; + } - return function stop() { - run_all(unsubscribers); - cleanup(); - }; - }); + function stop() { + run_all(unsubscribers); + cleanup(); + } + + return { + subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { + const subscriber: SubscribeInvalidateTuple = [run, invalidate]; + subscribers.push(subscriber); + if (subscribers.length === 1) start(); + run(value); + + return () => { + const index = subscribers.indexOf(subscriber); + if (index !== -1) { + subscribers.splice(index, 1); + } + if (subscribers.length === 0) { + stop(); + } + }; + } + }; } /** diff --git a/test/store/index.ts b/test/store/index.ts index 13b8e1f869..77ef9f9549 100644 --- a/test/store/index.ts +++ b/test/store/index.ts @@ -189,6 +189,34 @@ describe('store', () => { unsubscribe(); }); + it('prevents diamond dependency problem', () => { + const count = writable(0); + const values = []; + + const a = derived(count, $count => { + return 'a' + $count; + }); + + const b = derived(count, $count => { + return 'b' + $count; + }); + + const combined = derived([a, b], ([a, b]) => { + return a + b; + }); + + const unsubscribe = combined.subscribe(v => { + values.push(v); + }); + + assert.deepEqual(values, ['a0b0']); + + count.set(1); + assert.deepEqual(values, ['a0b0', 'a1b1']); + + unsubscribe(); + }); + it('is updated with safe_not_equal logic', () => { const arr = [0]; From ed2a19aa67e511cab9f7db0229215adaff134ba5 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Tue, 4 Jun 2019 22:14:36 -0400 Subject: [PATCH 2/3] DRY out --- src/runtime/store/index.ts | 63 ++++++++++++++------------------------ 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index f03a550c5f..6f14162115 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -112,12 +112,12 @@ type StoresValues = T extends Readable ? U : * applying an aggregation function over its input values. * @param {Stores} stores input stores * @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values - * @param {*=}value initial value, when used asynchronously + * @param {*=}initial_value when used asynchronously */ export function derived( stores: S, fn: (values: StoresValues, set?: Subscriber) => T | Unsubscriber | void, - value?: T, + initial_value?: T, ): Readable { const single = !Array.isArray(stores); @@ -127,28 +127,14 @@ export function derived( const auto = fn.length < 2; - const subscribers: Array> = []; - let unsubscribers; - let cleanup = noop; - let running = false; - - function invalidate() { - subscribers.forEach(subscriber => subscriber[1]()); - } - - function set(current_value) { - value = current_value; - if (running) { - invalidate(); - subscribers.forEach(subscriber => subscriber[0](value)); - } - } + const invalidators: Array> = []; - function start() { + const store = readable(initial_value, (set) => { + let inited = false; const values: StoresValues = [] as StoresValues; let pending = 0; - running = false; + let cleanup = noop; const sync = () => { if (pending) { @@ -163,47 +149,44 @@ export function derived( } }; - unsubscribers = stores_array.map((store, i) => store.subscribe( + const unsubscribers = stores_array.map((store, i) => store.subscribe( (value) => { values[i] = value; pending &= ~(1 << i); - if (running) { + if (inited) { sync(); } }, () => { - invalidate(); + run_all(invalidators); pending |= (1 << i); }), ); + inited = true; sync(); - running = true; - } - function stop() { - run_all(unsubscribers); - cleanup(); - } + return function stop() { + run_all(unsubscribers); + cleanup(); + }; + }); return { subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { - const subscriber: SubscribeInvalidateTuple = [run, invalidate]; - subscribers.push(subscriber); - if (subscribers.length === 1) start(); - run(value); + invalidators.push(invalidate); + + const unsubscribe = store.subscribe(run, invalidate); return () => { - const index = subscribers.indexOf(subscriber); + const index = invalidators.indexOf(invalidate); if (index !== -1) { - subscribers.splice(index, 1); - } - if (subscribers.length === 0) { - stop(); + invalidators.splice(index, 1); } + unsubscribe(); }; } - }; + } } /** @@ -214,4 +197,4 @@ export function get(store: Readable): T { let value: T | undefined; store.subscribe((_: T) => value = _)(); return value as T; -} +} \ No newline at end of file From 0d31e5c046106b4ee7c2c699b9d04c71bb75611a Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Tue, 4 Jun 2019 22:15:58 -0400 Subject: [PATCH 3/3] fix typo --- src/runtime/store/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 6f14162115..5ab6cfa26b 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -10,7 +10,7 @@ type Unsubscriber = () => void; type Updater = (value: T) => T; /** Cleanup logic callback. */ -type Invalidater = (value?: T) => void; +type Invalidator = (value?: T) => void; /** Start and stop notification callbacks. */ type StartStopNotifier = (set: Subscriber) => Unsubscriber | void; @@ -22,7 +22,7 @@ export interface Readable { * @param run subscription callback * @param invalidate cleanup callback */ - subscribe(run: Subscriber, invalidate?: Invalidater): Unsubscriber; + subscribe(run: Subscriber, invalidate?: Invalidator): Unsubscriber; } /** Writable interface for both updating and subscribing. */ @@ -41,7 +41,7 @@ export interface Writable extends Readable { } /** Pair of subscriber and invalidator. */ -type SubscribeInvalidateTuple = [Subscriber, Invalidater]; +type SubscribeInvalidateTuple = [Subscriber, Invalidator]; /** * Creates a `Readable` store that allows reading by subscription. @@ -78,7 +78,7 @@ export function writable(value: T, start: StartStopNotifier = noop): Writa set(fn(value)); } - function subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { + function subscribe(run: Subscriber, invalidate: Invalidator = noop): Unsubscriber { const subscriber: SubscribeInvalidateTuple = [run, invalidate]; subscribers.push(subscriber); if (subscribers.length === 1) { @@ -127,7 +127,7 @@ export function derived( const auto = fn.length < 2; - const invalidators: Array> = []; + const invalidators: Array> = []; const store = readable(initial_value, (set) => { let inited = false; @@ -173,7 +173,7 @@ export function derived( }); return { - subscribe(run: Subscriber, invalidate: Invalidater = noop): Unsubscriber { + subscribe(run: Subscriber, invalidate: Invalidator = noop): Unsubscriber { invalidators.push(invalidate); const unsubscribe = store.subscribe(run, invalidate);