From de3d610d4b33e7271445586e571422b9b439b1c6 Mon Sep 17 00:00:00 2001 From: Mikhail Nasyrov Date: Mon, 6 Mar 2023 02:32:59 +0300 Subject: [PATCH 1/4] refactor: Optimised `store` --- packages/rx-effects/src/store.ts | 36 +++++++++++++++++++-------- packages/rx-effects/src/utils.test.ts | 20 ++++++++++++++- packages/rx-effects/src/utils.ts | 10 +++----- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/rx-effects/src/store.ts b/packages/rx-effects/src/store.ts index d7bb6c7..9b3ac6b 100644 --- a/packages/rx-effects/src/store.ts +++ b/packages/rx-effects/src/store.ts @@ -4,7 +4,7 @@ import { Controller } from './controller'; import { Query } from './query'; import { STORE_EVENT_BUS } from './storeEvents'; import { setInternalStoreFlag, setStateMutationName } from './storeMetadata'; -import { DEFAULT_COMPARATOR, isReadonlyArray } from './utils'; +import { DEFAULT_COMPARATOR, isReadonlyArray, removeFromArray } from './utils'; let STORE_SEQ_NUMBER = 0; @@ -175,7 +175,8 @@ export type InternalStoreOptions = Readonly< StoreOptions & { internal?: boolean } >; -const NOTIFY_QUEUE: Set> = new Set(); +let STORE_NOTIFY_QUEUE: Store[] = []; +const STORE_NOTIFY_PRESENCE: Set = new Set(); /** * Creates the state store. @@ -190,17 +191,21 @@ export function createStore( const stateComparator = options?.comparator ?? DEFAULT_COMPARATOR; let currentState = initialState; - const subscribers: Set> = new Set(); + let subscribers: Subscriber[] | undefined; const store: Store = { id: ++STORE_SEQ_NUMBER, name: options?.name, value$: new Observable((subscriber) => { - subscribers.add(subscriber); + if (!subscribers) subscribers = []; + subscribers.push(subscriber); + subscriber.next(currentState); - return () => subscribers.delete(subscriber); + return () => { + subscribers = removeFromArray(subscribers, subscriber); + }; }), get(): State { @@ -221,12 +226,12 @@ export function createStore( notify() { const pinnedState = currentState; - subscribers.forEach((subscriber) => subscriber.next(pinnedState)); + subscribers?.forEach((subscriber) => subscriber.next(pinnedState)); }, destroy() { - subscribers.forEach((subscriber) => subscriber.complete()); - subscribers.clear(); + subscribers?.forEach((subscriber) => subscriber.complete()); + subscribers = []; STORE_EVENT_BUS.next({ type: 'destroyed', store }); @@ -301,14 +306,23 @@ export function createStore( } function scheduleNotify(store: Store) { - if (NOTIFY_QUEUE.add(store).size === 1) { + const prevSize = STORE_NOTIFY_PRESENCE.size; + STORE_NOTIFY_PRESENCE.add(store.id); + + if (STORE_NOTIFY_PRESENCE.size === prevSize) { + return; + } + + if (STORE_NOTIFY_QUEUE.push(store) === 1) { Promise.resolve().then(executeNotifyQueue); } } function executeNotifyQueue() { - const queue = [...NOTIFY_QUEUE]; - NOTIFY_QUEUE.clear(); + const queue = STORE_NOTIFY_QUEUE; + STORE_NOTIFY_QUEUE = []; + STORE_NOTIFY_PRESENCE.clear(); + queue.forEach((store) => store.notify()); } diff --git a/packages/rx-effects/src/utils.test.ts b/packages/rx-effects/src/utils.test.ts index b19afa4..22eeb6b 100644 --- a/packages/rx-effects/src/utils.test.ts +++ b/packages/rx-effects/src/utils.test.ts @@ -1,4 +1,4 @@ -import { isReadonlyArray, OBJECT_COMPARATOR } from './utils'; +import { isReadonlyArray, OBJECT_COMPARATOR, removeFromArray } from './utils'; describe('isReadonlyArray()', () => { it('should return true for the array', () => { @@ -25,3 +25,21 @@ describe('OBJECT_COMPARATOR', () => { expect(OBJECT_COMPARATOR({ a: 1 }, { a: 1, b: undefined })).toBe(false); }); }); + +describe('removeFromArray()', () => { + it('should return the same value if source is "undefined"', () => { + expect(removeFromArray(undefined, 1)).toBeUndefined(); + }); + + it('should return the same array if the item is not found', () => { + const source = [1, 2, 3]; + expect(removeFromArray(source, 4)).toBe(source); + }); + + it('should return a copy without the item if the item is found', () => { + const source = [1, 2, 3]; + const result = removeFromArray(source, 2); + expect(result).not.toBe(source); + expect(result).toEqual([1, 3]); + }); +}); diff --git a/packages/rx-effects/src/utils.ts b/packages/rx-effects/src/utils.ts index 37cc024..fabea06 100644 --- a/packages/rx-effects/src/utils.ts +++ b/packages/rx-effects/src/utils.ts @@ -49,12 +49,10 @@ export function removeFromArray( ): T[] | undefined { if (!source) return undefined; - const clone = Array.from(source); - - const index = clone.indexOf(item); - if (index >= 0) { - clone.splice(index, 1); - } + const index = source.indexOf(item); + if (index < 0) return source; + const clone = [...source]; + clone.splice(index, 1); return clone; } From d270dfb0b1bda749174eee49313dcb863307053a Mon Sep 17 00:00:00 2001 From: Mikhail Nasyrov Date: Tue, 14 Mar 2023 00:53:36 +0300 Subject: [PATCH 2/4] refactor: Optimised compute() --- packages/rx-effects/src/compute.test.ts | 132 ++--------- packages/rx-effects/src/compute.ts | 280 ++++++++++-------------- 2 files changed, 135 insertions(+), 277 deletions(-) diff --git a/packages/rx-effects/src/compute.test.ts b/packages/rx-effects/src/compute.test.ts index 6ec36e2..07831dd 100644 --- a/packages/rx-effects/src/compute.test.ts +++ b/packages/rx-effects/src/compute.test.ts @@ -8,11 +8,9 @@ import { Subject, Subscription, } from 'rxjs'; -import { collectChanges, mockObserver } from '../test/testUtils'; +import { collectChanges } from '../test/testUtils'; import { - addChildNode, addValueObserver, - calculateValue, compute, createComputationNode, createComputationQuery, @@ -261,9 +259,6 @@ describe('compute()', () => { expect(node1.hot).toBe(false); expect(node2.hot).toBe(false); - - expect(node1.treeObserverCount).toBe(0); - expect(node2.treeObserverCount).toBe(0); }); it('should propagate "complete" event from a source to observers', async () => { @@ -308,9 +303,6 @@ describe('compute()', () => { expect(node1.hot).toBe(false); expect(node2.hot).toBe(false); - - expect(node1.treeObserverCount).toBe(0); - expect(node2.treeObserverCount).toBe(0); }); it('should throw an error on subscription to an incorrect dependency', async () => { @@ -457,8 +449,7 @@ describe('createComputationNode()', () => { it('should create a default node', () => { const node = createComputationNode(() => 1); - expect(node.dependencies).toBe(undefined); - expect(node.treeObserverCount).toBe(0); + expect(node.customDeps).toBe(undefined); }); it('should create a node by options', () => { @@ -472,8 +463,7 @@ describe('createComputationNode()', () => { }); expect(node.comparator).toBe(comparator); - expect(node.dependencies).toEqual([s1, s2]); - expect(node.treeObserverCount).toBe(0); + expect(node.customDeps).toEqual([s1, s2, s1]); }); }); @@ -553,7 +543,6 @@ describe('addValueObserver()', () => { expect(node.hot).toBe(true); expect(node.observers?.length).toBe(1); - expect(node.treeObserverCount).toBe(1); expect(changes).toEqual([1]); }); @@ -567,14 +556,10 @@ describe('addValueObserver()', () => { const node2 = getNode(query2); expect(node1.observers).toBeUndefined(); - expect(node1.children?.length).toBeUndefined(); - expect(node1.parents?.length).toBeUndefined(); - expect(node1.depsSubscriptions?.length).toBeUndefined(); + expect(node1.subscriptions?.length).toBeUndefined(); expect(node2.observers).toBeUndefined(); - expect(node2.children?.length).toBeUndefined(); - expect(node2.parents?.length).toBeUndefined(); - expect(node1.depsSubscriptions?.length).toBeUndefined(); + expect(node1.subscriptions?.length).toBeUndefined(); const subject = new Subject(); const changes = await collectChanges(subject, () => { @@ -582,18 +567,12 @@ describe('addValueObserver()', () => { }); expect(node1.hot).toBe(true); - expect(node1.treeObserverCount).toBe(1); - expect(node1.observers?.length).toBeUndefined(); - expect(node1.children?.length).toBe(1); - expect(node1.parents?.length).toBeUndefined(); - expect(node1.depsSubscriptions?.length).toBe(1); + expect(node2.observers?.length).toBe(1); + expect(node1.subscriptions?.length).toBe(1); expect(node2.hot).toBe(true); - expect(node2.treeObserverCount).toBe(1); expect(node2.observers?.length).toBe(1); - expect(node2.parents?.length).toBe(1); - expect(node2.children?.length).toBeUndefined(); - expect(node2.depsSubscriptions?.length).toBe(0); + expect(node2.subscriptions?.length).toBe(1); expect(changes).toEqual([1]); }); @@ -610,7 +589,6 @@ describe('removeValueObserver()', () => { removeValueObserver(node, subject); }).not.toThrow(); expect(node.hot).toBe(false); - expect(node.treeObserverCount).toBe(0); }); it('should remove an observer and make a node be cold', async () => { @@ -624,7 +602,6 @@ describe('removeValueObserver()', () => { removeValueObserver(node, subject); expect(node.hot).toBe(false); expect(node.observers?.length).toBeUndefined(); - expect(node.treeObserverCount).toBe(0); }); it('should remove an observer, make a node be cold if treeObserverCount = 0 and update parents', async () => { @@ -640,25 +617,20 @@ describe('removeValueObserver()', () => { const subject2 = new Subject(); addValueObserver(node1, subject1); addValueObserver(node2, subject2); + expect(node1.hot).toBe(true); - expect(node1.treeObserverCount).toBe(2); + expect(node1.observers?.length).toBe(2); expect(node2.hot).toBe(true); - expect(node2.treeObserverCount).toBe(1); + expect(node2.observers?.length).toBe(1); removeValueObserver(node2, subject2); expect(node1.hot).toBe(true); - expect(node1.treeObserverCount).toBe(1); expect(node1.observers?.length).toBe(1); - expect(node1.children?.length).toBe(0); - expect(node1.parents?.length).toBeUndefined(); - expect(node1.depsSubscriptions?.length).toBe(1); + expect(node1.subscriptions?.length).toBe(1); expect(node2.hot).toBe(false); - expect(node2.treeObserverCount).toBe(0); expect(node2.observers?.length).toBeUndefined(); - expect(node2.parents?.length).toBeUndefined(); - expect(node2.children?.length).toBeUndefined(); - expect(node2.depsSubscriptions?.length).toBeUndefined(); + expect(node2.subscriptions?.length).toBeUndefined(); }); }); @@ -710,12 +682,8 @@ describe('onSourceError()', () => { { error: 'Test error 4', hasValue: false, kind: 'E', value: undefined }, ]); - // Nodes will be made cold by unsubscribing an observable by a subscriber - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); - - expect(node1.treeObserverCount).toBe(3); - expect(node2.treeObserverCount).toBe(2); + expect(node1.hot).toBe(false); + expect(node2.hot).toBe(false); }); }); @@ -732,11 +700,7 @@ describe('makeColdNode()', () => { it('should not fail if a parent node has incorrect state', () => { const query = compute(() => 1); - const parent = getNode(query); - const node = getNode(query); - node.parents = []; - node.parents.push(parent); expect(() => { makeColdNode(node); @@ -792,12 +756,8 @@ describe('onSourceComplete()', () => { { error: undefined, hasValue: false, kind: 'C', value: undefined }, ]); - // Nodes will be made cold by unsubscribing an observable by a subscriber - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); - - expect(node1.treeObserverCount).toBe(3); - expect(node2.treeObserverCount).toBe(2); + expect(node1.hot).toBe(false); + expect(node2.hot).toBe(false); }); }); @@ -846,8 +806,6 @@ describe('recompute()', () => { const query2 = compute(calc2); const node2 = getNode(query2); - addChildNode(node1, node2); - calc1.mockClear(); calc2.mockClear(); nextNodeVersion(); @@ -855,15 +813,16 @@ describe('recompute()', () => { expect(calc1).toHaveBeenCalledTimes(0); expect(calc2).toHaveBeenCalledTimes(0); + addValueObserver(node1, new Subject()); addValueObserver(node2, new Subject()); calc1.mockClear(); calc2.mockClear(); nextNodeVersion(); recompute(node1); expect(calc1).toHaveBeenCalledTimes(1); - expect(calc2).toHaveBeenCalledTimes(1); + expect(calc2).toHaveBeenCalledTimes(0); - expect(node1.valueRef).toBeUndefined(); + expect(node1.valueRef).not.toBeUndefined(); expect(node2.valueRef).not.toBeUndefined(); }); @@ -875,8 +834,6 @@ describe('recompute()', () => { const node2 = createComputationNode(calc2); addValueObserver(node2, new Subject()); - addChildNode(node1, node2); - calc1.mockClear(); calc2.mockClear(); nextNodeVersion(); @@ -886,55 +843,6 @@ describe('recompute()', () => { }); }); -describe('calculateValue()', () => { - it('should notify subscribers with a new value', () => { - const observer = mockObserver(); - let value = 1; - - calculateValue({ - computation: () => ++value, - hot: false, - treeObserverCount: 1, - observers: [observer], - }); - - expect(value).toBe(2); - expect(observer.next).toHaveBeenCalledOnceWith(2); - }); - - it('should cache value in the node in case valueRef is undefined', () => { - const observer = mockObserver(); - const source = 1; - - const comparator = () => true; - const node = { - ...createComputationNode(() => source, { comparator }), - treeObserverCount: 1, - observers: [observer], - }; - - calculateValue(node); - expect(observer.next).toHaveBeenCalledOnceWith(1); - expect(node.valueRef).toEqual(expect.objectContaining({ value: 1 })); - }); - - it('should not cache value in the node in case valueRef is undefined', () => { - const observer = mockObserver(); - const source = 2; - - const comparator = () => true; - const node = { - ...createComputationNode(() => source, { comparator }), - treeObserverCount: 1, - observers: [observer], - }; - node.valueRef = { value: 1, params: [] }; - - calculateValue(node); - expect(observer.next).toHaveBeenCalledTimes(0); - }); -}); - function getNode(query: Query): Node { const node = getComputationNode(query); diff --git a/packages/rx-effects/src/compute.ts b/packages/rx-effects/src/compute.ts index 9b055d9..de5d604 100644 --- a/packages/rx-effects/src/compute.ts +++ b/packages/rx-effects/src/compute.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-var */ import { Observable, Observer } from 'rxjs'; import { Query } from './query'; import { DEFAULT_COMPARATOR, removeFromArray } from './utils'; @@ -90,12 +91,11 @@ export type Node = { hot: boolean; version?: number; valueRef?: ValueRef; - dependencies?: Query[]; - depsSubscriptions?: (() => void)[]; + customDeps?: ReadonlyArray>; + resolvedDeps?: ReadonlyArray>; + subscriptions?: (() => void)[]; observers?: Observer[]; - treeObserverCount: number; - parents?: Node[]; - children?: Node[]; + depObserver?: Observer; }; const NODES = new WeakMap, Node>(); @@ -105,14 +105,11 @@ export function createComputationNode( computation: Computation, options?: ComputationOptions, ): Node { - const dependencies = options?.dependencies; - return { + hot: false, computation, comparator: options?.comparator, - hot: false, - dependencies: dependencies ? [...new Set(dependencies)] : undefined, - treeObserverCount: 0, + customDeps: options?.dependencies, }; } @@ -140,8 +137,11 @@ export function getComputationNode(query: Query): Node | undefined { /// COMPUTATION ENGINE -let NODE_VERSION = 0; -let STORE_VERSION = 0; +var NODE_VERSION = 0; +var STORE_VERSION = 0; + +var DEPS_COLLECTOR: undefined | ((query: Query) => void); +var RECOMPUTE = false; const FAST_QUERY_GETTER: ComputationResolver = ( query: Query, @@ -158,7 +158,6 @@ export function nextVersion(currentValue: number): number { /** @internal */ export function nextNodeVersion() { - STORE_VERSION = nextVersion(STORE_VERSION); NODE_VERSION = nextVersion(NODE_VERSION); } @@ -168,157 +167,124 @@ export function nextStoreVersion() { } export function getQueryValue(node: Node): T { + if (DEPS_COLLECTOR) { + if (node.version === NODE_VERSION && node.valueRef) { + return node.valueRef.value; + } + node.version = NODE_VERSION; + + const valueRef = calculate(node.computation); + + // if (isNodeValueChanged(node, valueRef)) { + node.valueRef = valueRef; + // } + + // return (node.valueRef ?? valueRef).value; + return node.valueRef.value; + } + if (node.valueRef?.version === STORE_VERSION) { return node.valueRef.value; } + if (RECOMPUTE) { + if (node.version === NODE_VERSION && node.valueRef) { + return node.valueRef.value; + } + + node.version = NODE_VERSION; + node.valueRef = calculate(node.computation); + return node.valueRef.value; + } + return node.computation(FAST_QUERY_GETTER); } export function addValueObserver(node: Node, observer: Observer) { - if (!node.observers) { - node.observers = []; - } + if (!node.observers) node.observers = []; node.observers.push(observer); + nextNodeVersion(); makeHotNode(node, observer); - - updateTreeObserverCount(node); } export function removeValueObserver(node: Node, observer: Observer) { node.observers = removeFromArray(node.observers, observer); - updateTreeObserverCount(node); -} - -function getTreeObserverCount(node: Node): number { - const { children, observers } = node; - - let subtreeCount = 0; - - if (children) { - for (let i = 0; i < children.length; i++) { - const childNode = children[i]; - subtreeCount += childNode.treeObserverCount; - } - } - - return subtreeCount + (observers?.length ?? 0); -} - -function updateTreeObserverCount(node: Node) { - node.treeObserverCount = getTreeObserverCount(node); - - const parents = node.parents; - if (parents) { - for (let i = 0; i < parents.length; i++) { - const parentNode = parents[i]; - updateTreeObserverCount(parentNode); - } - } - - if (node.treeObserverCount === 0) { + if (!node.observers || node.observers.length === 0) { makeColdNode(node); } } -function makeHotNode(node: Node, observer?: Observer) { - let dependencies = node.dependencies; - let valueRef = - node.valueRef?.version === STORE_VERSION ? node.valueRef : undefined; +function makeHotNode(node: Node, observer: Observer) { + if (node.hot && node.valueRef) { + observer.next(node.valueRef.value); + return; + } - if (dependencies) { - if (observer && !valueRef) { + let valueRef = node.valueRef; + if (node.resolvedDeps) { + if (!valueRef) { valueRef = node.valueRef = calculate(node.computation); } } else { - const visitedDeps: Set> = new Set(); - - const next = calculate(node.computation, (query) => visitedDeps.add(query)); + const visitedDeps: Set> = new Set(node.customDeps); - dependencies = node.dependencies = [...visitedDeps]; + DEPS_COLLECTOR = (query) => { + if (!NODES.has(query)) visitedDeps.add(query); + }; + const next = calculate(node.computation); + DEPS_COLLECTOR = undefined; - if (observer && !valueRef) { + if (!valueRef || isNodeValueChanged(node, next)) { valueRef = node.valueRef = next; } - } - if (dependencies.length > 0 && !node.hot) { - let depObserver; - - let depsSubscriptions = node.depsSubscriptions; - if (!depsSubscriptions) { - depsSubscriptions = node.depsSubscriptions = []; - } - - for (let i = 0; i < dependencies.length; i++) { - const parentQuery = dependencies[i]; + node.resolvedDeps = [...visitedDeps]; + } + if (node.resolvedDeps && node.resolvedDeps.length > 0) { + node.resolvedDeps.forEach((parentQuery) => { if (!parentQuery) { throw new TypeError('Incorrect dependency'); } - const parentNode = NODES.get(parentQuery); - if (parentNode) { - addChildNode(parentNode, node); - } else { - if (!depObserver) { - depObserver = { - next: () => onSourceChanged(node), - error: (error: any) => onSourceError(node, error), - complete: () => onSourceComplete(node), - }; - } - - const subscription = parentQuery.value$.subscribe(depObserver); - - depsSubscriptions.push(() => subscription.unsubscribe()); + if (!node.depObserver) { + node.depObserver = { + next: () => onSourceChanged(node), + error: (error: any) => onSourceError(node, error), + complete: () => onSourceComplete(node), + }; } - } - } - - node.hot = true; + const subscription = parentQuery.value$.subscribe( + node.depObserver as any, + ); - if (observer && valueRef) { - observer.next(valueRef.value); + if (!node.subscriptions) node.subscriptions = []; + node.subscriptions.push(() => subscription.unsubscribe()); + }); } -} - -export function addChildNode(parent: Node, child: Node) { - if (!parent.children) parent.children = []; - parent.children.push(child); - if (!child.parents) child.parents = []; - child.parents.push(parent); + node.hot = true; - makeHotNode(parent); + observer.next(valueRef.value); } export function makeColdNode(node: Node) { node.hot = false; - node.treeObserverCount = 0; - const { depsSubscriptions, parents } = node; - if (depsSubscriptions) { - for (let i = 0; i < depsSubscriptions.length; i++) { - const unsubscribe = depsSubscriptions[i]; + const { subscriptions } = node; + if (subscriptions) { + for (let i = 0; i < subscriptions.length; i++) { + const unsubscribe = subscriptions[i]; unsubscribe(); } } - if (parents) { - for (let i = 0; i < parents.length; i++) { - const parent = parents[i]; - parent.children = removeFromArray(parent.children, node); - } - } - + node.version = undefined; node.valueRef = undefined; - node.depsSubscriptions = undefined; + node.subscriptions = undefined; node.observers = undefined; - node.parents = undefined; - node.children = undefined; } function onSourceChanged(node: Node) { @@ -327,7 +293,7 @@ function onSourceChanged(node: Node) { } export function onSourceError(node: Node, error: any) { - const { children, observers } = node; + const { observers } = node; if (observers) { for (let i = 0; i < observers.length; i++) { @@ -336,16 +302,12 @@ export function onSourceError(node: Node, error: any) { } } - if (children) { - for (let i = 0; i < children.length; i++) { - const child = children[i]; - onSourceError(child, error); - } - } + node.observers = undefined; + makeColdNode(node); } export function onSourceComplete(node: Node) { - const { children, observers } = node; + const { observers } = node; if (observers) { for (let i = 0; i < observers.length; i++) { @@ -354,65 +316,43 @@ export function onSourceComplete(node: Node) { } } - if (children) { - for (let i = 0; i < children.length; i++) { - const child = children[i]; - onSourceComplete(child); - } - } + node.observers = undefined; + makeColdNode(node); } export function recompute(node: Node) { - if (node.version === NODE_VERSION) { - return; - } + if (!node.hot || node.version === NODE_VERSION) return; node.version = NODE_VERSION; - if (node.observers && node.observers.length > 0) { - calculateValue(node); - } else { + if (!node.observers || node.observers.length === 0) { node.valueRef = undefined; + return; } - if (node.treeObserverCount > 0 && node.children) { - const children = node.children; - for (let i = 0; i < children.length; i++) { - const child = children[i]; - recompute(child); - } + RECOMPUTE = true; + let next; + try { + next = calculate(node.computation); + } finally { + RECOMPUTE = false; } -} - -export function calculateValue(node: Node) { - const comparator = node.comparator ?? DEFAULT_COMPARATOR; - - const next = calculate(node.computation); - - const isChanged = node.valueRef - ? isCalculationChanged(comparator, node.valueRef, next) - : true; + const isChanged = isNodeValueChanged(node, next); if (isChanged) { node.valueRef = next; + const value = next.value; + const observers = node.observers; - if (node.observers) { - const observers = node.observers; - for (let i = 0; i < observers.length; i++) { - const observer = observers[i]; - observer.next(next.value); - } + for (let i = 0; i < observers.length; i++) { + const observer = observers[i]; + observer.next(value); } - } - - if (!isChanged && node.valueRef) { + } else if (node.valueRef) { node.valueRef.version = STORE_VERSION; } } -function calculate( - computation: Computation, - visitor?: (query: Query) => void, -): ValueRef { +function calculate(computation: Computation): ValueRef { let params: Array | undefined; const value = computation( @@ -421,11 +361,11 @@ function calculate( if (selector) param = selector(param); + DEPS_COLLECTOR?.(query); + if (!params) params = []; params.push(param); - visitor?.(query); - return param; }, ); @@ -433,6 +373,16 @@ function calculate( return { value, params, version: STORE_VERSION }; } +function isNodeValueChanged(node: Node, next: ValueRef): boolean { + return node.valueRef + ? isCalculationChanged( + node.comparator ?? DEFAULT_COMPARATOR, + node.valueRef, + next, + ) + : true; +} + function isCalculationChanged( comparator: Comparator, a: ValueRef, From f9e14cc85a9fe24358a8cb482930556e3d35f3f5 Mon Sep 17 00:00:00 2001 From: Mikhail Nasyrov Date: Tue, 14 Mar 2023 02:14:36 +0300 Subject: [PATCH 3/4] refactor: Optimised compute() --- packages/rx-effects/src/compute.test.ts | 166 +++++------------------- packages/rx-effects/src/compute.ts | 44 ++----- 2 files changed, 46 insertions(+), 164 deletions(-) diff --git a/packages/rx-effects/src/compute.test.ts b/packages/rx-effects/src/compute.test.ts index 07831dd..e292bbe 100644 --- a/packages/rx-effects/src/compute.test.ts +++ b/packages/rx-effects/src/compute.test.ts @@ -14,14 +14,9 @@ import { compute, createComputationNode, createComputationQuery, - getComputationNode, getQueryValue, - makeColdNode, nextNodeVersion, nextVersion, - Node, - onSourceComplete, - onSourceError, recompute, removeValueObserver, } from './compute'; @@ -223,8 +218,6 @@ describe('compute()', () => { const query1 = compute(() => source.get() + 1, [source]); const query2 = compute(() => query1.get() * 2, [query1]); - const node1 = getNode(query1); - const node2 = getNode(query2); const subject1 = new Subject(); const subject2 = new Subject(); @@ -256,9 +249,6 @@ describe('compute()', () => { expect(changes3).toEqual([ { error: 'Test error 1', hasValue: false, kind: 'E', value: undefined }, ]); - - expect(node1.hot).toBe(false); - expect(node2.hot).toBe(false); }); it('should propagate "complete" event from a source to observers', async () => { @@ -267,8 +257,6 @@ describe('compute()', () => { const query1 = compute(() => source.get() + 1, [source]); const query2 = compute(() => query1.get() * 2, [query1]); - const node1 = getNode(query1); - const node2 = getNode(query2); const subject1 = new Subject(); const subject2 = new Subject(); @@ -300,9 +288,6 @@ describe('compute()', () => { expect(changes3).toEqual([ { error: undefined, hasValue: false, kind: 'C', value: undefined }, ]); - - expect(node1.hot).toBe(false); - expect(node2.hot).toBe(false); }); it('should throw an error on subscription to an incorrect dependency', async () => { @@ -472,7 +457,7 @@ describe('createComputationQuery()', () => { const node = createComputationNode(() => 1); const query = createComputationQuery(node); - expect(getComputationNode(query)).toBe(node); + expect((query as any)._computed).toBe(true); expect(query.get()).toBe(1); expect(await firstValueFrom(query.value$)).toBe(1); }); @@ -550,30 +535,14 @@ describe('addValueObserver()', () => { const source = createStore(1); const query1 = compute(() => 1, [source]); - const node1 = getNode(query1); const query2 = compute(() => 1, [query1]); - const node2 = getNode(query2); - - expect(node1.observers).toBeUndefined(); - expect(node1.subscriptions?.length).toBeUndefined(); - - expect(node2.observers).toBeUndefined(); - expect(node1.subscriptions?.length).toBeUndefined(); const subject = new Subject(); const changes = await collectChanges(subject, () => { - addValueObserver(node2, subject); + query2.value$.subscribe(subject); }); - expect(node1.hot).toBe(true); - expect(node2.observers?.length).toBe(1); - expect(node1.subscriptions?.length).toBe(1); - - expect(node2.hot).toBe(true); - expect(node2.observers?.length).toBe(1); - expect(node2.subscriptions?.length).toBe(1); - expect(changes).toEqual([1]); }); }); @@ -608,53 +577,33 @@ describe('removeValueObserver()', () => { const source = createStore(1); const query1 = compute(() => 1, [source]); - const node1 = getNode(query1); const query2 = compute(() => 1, [query1]); - const node2 = getNode(query2); const subject1 = new Subject(); const subject2 = new Subject(); - addValueObserver(node1, subject1); - addValueObserver(node2, subject2); - - expect(node1.hot).toBe(true); - expect(node1.observers?.length).toBe(2); - expect(node2.hot).toBe(true); - expect(node2.observers?.length).toBe(1); - - removeValueObserver(node2, subject2); - expect(node1.hot).toBe(true); - expect(node1.observers?.length).toBe(1); - expect(node1.subscriptions?.length).toBe(1); - - expect(node2.hot).toBe(false); - expect(node2.observers?.length).toBeUndefined(); - expect(node2.subscriptions?.length).toBeUndefined(); + query1.value$.subscribe(subject1); + query2.value$.subscribe(subject2); }); }); describe('onSourceError()', () => { it('should propagate an error to an observer from a computation node', async () => { - const source = createStore(1); + const bs = new BehaviorSubject(1); + const source = queryBehaviourSubject(bs); const query1 = compute(() => source.get() + 1, [source]); - const node1 = getNode(query1); const query2 = compute(() => query1.get() * 2, [query1]); - const node2 = getNode(query2); const subject1 = new Subject(); const subject2 = new Subject(); const changes = await collectChanges(subject2.pipe(materialize()), () => { - addValueObserver(node1, subject1); - addValueObserver(node2, subject2); - - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); + query1.value$.subscribe(subject1); + query2.value$.subscribe(subject2); - onSourceError(node1, 'Test error 1'); - onSourceError(node1, 'Test error 1 second try'); + bs.error('Test error 1'); + bs.error('Test error 1 second try'); }); expect(changes).toEqual([ { error: undefined, hasValue: true, kind: 'N', value: 4 }, @@ -662,7 +611,7 @@ describe('onSourceError()', () => { ]); const changes2 = await collectChanges(subject2.pipe(materialize()), () => { - onSourceError(node1, 'Test error 3'); + bs.error('Test error 3'); }); expect(changes2).toEqual([ { error: 'Test error 1', hasValue: false, kind: 'E', value: undefined }, @@ -670,65 +619,33 @@ describe('onSourceError()', () => { const subject3 = new Subject(); const changes3 = await collectChanges(subject3.pipe(materialize()), () => { - addValueObserver(node2, subject3); - - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); + query2.value$.subscribe(subject3); - onSourceError(node1, 'Test error 4'); + bs.error('Test error 4'); }); expect(changes3).toEqual([ - { error: undefined, hasValue: true, kind: 'N', value: 4 }, - { error: 'Test error 4', hasValue: false, kind: 'E', value: undefined }, + { error: 'Test error 1', hasValue: false, kind: 'E', value: undefined }, ]); - - expect(node1.hot).toBe(false); - expect(node2.hot).toBe(false); - }); -}); - -describe('makeColdNode()', () => { - it('should not fail if a node has initial state', () => { - const query = compute(() => 1); - const node = getNode(query); - - expect(() => { - makeColdNode(node); - }).not.toThrow(); - }); - - it('should not fail if a parent node has incorrect state', () => { - const query = compute(() => 1); - - const node = getNode(query); - - expect(() => { - makeColdNode(node); - }).not.toThrow(); }); }); describe('onSourceComplete()', () => { it('should propagate "complete" to an observer from a computation node', async () => { - const source = createStore(1); + const bs = new BehaviorSubject(1); + const source = queryBehaviourSubject(bs); const query1 = compute(() => source.get() + 1, [source]); - const node1 = getNode(query1); const query2 = compute(() => query1.get() * 2, [query1]); - const node2 = getNode(query2); const subject1 = new Subject(); const subject2 = new Subject(); const changes = await collectChanges(subject2.pipe(materialize()), () => { - addValueObserver(node1, subject1); - addValueObserver(node2, subject2); - - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); + query1.value$.subscribe(subject1); + query2.value$.subscribe(subject2); - onSourceComplete(node1); - onSourceComplete(node1); + bs.complete(); + bs.complete(); }); expect(changes).toEqual([ { error: undefined, hasValue: true, kind: 'N', value: 4 }, @@ -736,7 +653,7 @@ describe('onSourceComplete()', () => { ]); const changes2 = await collectChanges(subject2.pipe(materialize()), () => { - onSourceComplete(node1); + bs.complete(); }); expect(changes2).toEqual([ { error: undefined, hasValue: false, kind: 'C', value: undefined }, @@ -744,20 +661,13 @@ describe('onSourceComplete()', () => { const subject3 = new Subject(); const changes3 = await collectChanges(subject3.pipe(materialize()), () => { - addValueObserver(node2, subject3); - - expect(node1.hot).toBe(true); - expect(node2.hot).toBe(true); + query2.value$.subscribe(subject3); - onSourceComplete(node1); + bs.complete(); }); expect(changes3).toEqual([ - { error: undefined, hasValue: true, kind: 'N', value: 4 }, { error: undefined, hasValue: false, kind: 'C', value: undefined }, ]); - - expect(node1.hot).toBe(false); - expect(node2.hot).toBe(false); }); }); @@ -798,32 +708,30 @@ describe('recompute()', () => { }); it('should compute if only a subtree has an indirect observer', () => { - const calc1 = jest.fn(() => 1); + const bs = new BehaviorSubject(1); + const source = queryBehaviourSubject(bs); + + const calc1 = jest.fn((get) => get(source) + 1); const query1 = compute(calc1); - const node1 = getNode(query1); const calc2 = jest.fn((get) => get(query1) + 1); const query2 = compute(calc2); - const node2 = getNode(query2); calc1.mockClear(); calc2.mockClear(); nextNodeVersion(); - recompute(node1); + bs.next(1); expect(calc1).toHaveBeenCalledTimes(0); expect(calc2).toHaveBeenCalledTimes(0); - addValueObserver(node1, new Subject()); - addValueObserver(node2, new Subject()); + query1.value$.subscribe(); + query2.value$.subscribe(); calc1.mockClear(); calc2.mockClear(); nextNodeVersion(); - recompute(node1); - expect(calc1).toHaveBeenCalledTimes(1); - expect(calc2).toHaveBeenCalledTimes(0); - - expect(node1.valueRef).not.toBeUndefined(); - expect(node2.valueRef).not.toBeUndefined(); + bs.next(2); + expect(calc1).toHaveBeenCalledTimes(2); + expect(calc2).toHaveBeenCalledTimes(1); }); it('should not trigger recomputing for cold nodes', () => { @@ -842,11 +750,3 @@ describe('recompute()', () => { expect(calc2).toHaveBeenCalledTimes(0); }); }); - -function getNode(query: Query): Node { - const node = getComputationNode(query); - - if (node) return node; - - throw new Error('Not ComputationQuery'); -} diff --git a/packages/rx-effects/src/compute.ts b/packages/rx-effects/src/compute.ts index de5d604..c88ee92 100644 --- a/packages/rx-effects/src/compute.ts +++ b/packages/rx-effects/src/compute.ts @@ -92,14 +92,12 @@ export type Node = { version?: number; valueRef?: ValueRef; customDeps?: ReadonlyArray>; - resolvedDeps?: ReadonlyArray>; + resolvedDeps?: ReadonlySet>; subscriptions?: (() => void)[]; observers?: Observer[]; depObserver?: Observer; }; -const NODES = new WeakMap, Node>(); - /** @internal */ export function createComputationNode( computation: Computation, @@ -116,6 +114,8 @@ export function createComputationNode( /** @internal */ export function createComputationQuery(node: Node): Query { const query = { + _computed: true, + get: () => getQueryValue(node), value$: new Observable((observer) => { @@ -125,16 +125,9 @@ export function createComputationQuery(node: Node): Query { }), }; - NODES.set(query, node); - return query; } -/** @internal */ -export function getComputationNode(query: Query): Node | undefined { - return NODES.get(query); -} - /// COMPUTATION ENGINE var NODE_VERSION = 0; @@ -171,19 +164,9 @@ export function getQueryValue(node: Node): T { if (node.version === NODE_VERSION && node.valueRef) { return node.valueRef.value; } - node.version = NODE_VERSION; - - const valueRef = calculate(node.computation); - - // if (isNodeValueChanged(node, valueRef)) { - node.valueRef = valueRef; - // } - - // return (node.valueRef ?? valueRef).value; - return node.valueRef.value; - } - if (node.valueRef?.version === STORE_VERSION) { + node.version = NODE_VERSION; + node.valueRef = calculate(node.computation); return node.valueRef.value; } @@ -197,6 +180,10 @@ export function getQueryValue(node: Node): T { return node.valueRef.value; } + if (node.valueRef?.version === STORE_VERSION) { + return node.valueRef.value; + } + return node.computation(FAST_QUERY_GETTER); } @@ -231,7 +218,7 @@ function makeHotNode(node: Node, observer: Observer) { const visitedDeps: Set> = new Set(node.customDeps); DEPS_COLLECTOR = (query) => { - if (!NODES.has(query)) visitedDeps.add(query); + if (!(query as any)._computed) visitedDeps.add(query); }; const next = calculate(node.computation); DEPS_COLLECTOR = undefined; @@ -240,10 +227,10 @@ function makeHotNode(node: Node, observer: Observer) { valueRef = node.valueRef = next; } - node.resolvedDeps = [...visitedDeps]; + node.resolvedDeps = visitedDeps; } - if (node.resolvedDeps && node.resolvedDeps.length > 0) { + if (node.resolvedDeps.size > 0) { node.resolvedDeps.forEach((parentQuery) => { if (!parentQuery) { throw new TypeError('Incorrect dependency'); @@ -321,14 +308,9 @@ export function onSourceComplete(node: Node) { } export function recompute(node: Node) { - if (!node.hot || node.version === NODE_VERSION) return; + if (!node.hot || !node.observers || node.version === NODE_VERSION) return; node.version = NODE_VERSION; - if (!node.observers || node.observers.length === 0) { - node.valueRef = undefined; - return; - } - RECOMPUTE = true; let next; try { From 4e3b2eb3e78809c44cf4b7a59b821937b70b2750 Mon Sep 17 00:00:00 2001 From: Mikhail Nasyrov Date: Tue, 14 Mar 2023 02:33:06 +0300 Subject: [PATCH 4/4] refactor: Removed explicit 'dependencies' option from `compute()` --- packages/rx-effects/benchmarks/index.ts | 22 +---- packages/rx-effects/src/compute.test.ts | 104 ++++++------------------ packages/rx-effects/src/compute.ts | 42 ++-------- packages/rx-effects/src/index.ts | 6 +- packages/rx-effects/src/queryMappers.ts | 4 +- packages/rx-effects/src/store.ts | 4 +- 6 files changed, 40 insertions(+), 142 deletions(-) diff --git a/packages/rx-effects/benchmarks/index.ts b/packages/rx-effects/benchmarks/index.ts index f40e019..8a50581 100644 --- a/packages/rx-effects/benchmarks/index.ts +++ b/packages/rx-effects/benchmarks/index.ts @@ -5,7 +5,7 @@ const ITERATION_COUNT = 100; const bench = new Bench(); -bench.add('reactive-computed-bench (implicit)', () => { +bench.add('reactive-computed-bench', () => { const entry = createStore(0); const a = compute((get) => get(entry)); @@ -25,26 +25,6 @@ bench.add('reactive-computed-bench (implicit)', () => { } }); -bench.add('reactive-computed-bench (explicit)', () => { - const entry = createStore(0); - - const a = compute(() => entry.get(), [entry]); - const b = compute(() => a.get() + 1, [a]); - const c = compute(() => a.get() + 1, [a]); - const d = compute(() => b.get() + c.get(), [b, c]); - const e = compute(() => d.get() + 1, [d]); - const f = compute(() => d.get() + e.get(), [d, e]); - const g = compute(() => d.get() + e.get(), [d, e]); - const h = compute(() => f.get() + g.get(), [f, g]); - - h.value$.subscribe(); - - for (let i = 0; i < ITERATION_COUNT; i++) { - entry.set(i); - entry.notify(); - } -}); - async function main() { await bench.run(); diff --git a/packages/rx-effects/src/compute.test.ts b/packages/rx-effects/src/compute.test.ts index e292bbe..8edee72 100644 --- a/packages/rx-effects/src/compute.test.ts +++ b/packages/rx-effects/src/compute.test.ts @@ -25,7 +25,7 @@ import { queryBehaviourSubject } from './queryUtils'; import { createStore } from './store'; describe('compute()', () => { - it('should work #1', async () => { + it('should calculate the benchmark', async () => { const entry = createStore(0); const a = compute((get) => get(entry)); @@ -54,36 +54,9 @@ describe('compute()', () => { expect(changes).toEqual([10, 18, 26]); }); - it('should work #2', async () => { - const entry = createStore(0); - - const a = compute(() => entry.get(), [entry]); - const b = compute(() => a.get() + 1, [a]); - const c = compute(() => a.get() + 1, [a]); - const d = compute(() => b.get() + c.get(), [b, c]); - const e = compute(() => d.get() + 1, [d]); - const f = compute(() => d.get() + e.get(), [d, e]); - const g = compute(() => d.get() + e.get(), [d, e]); - const h = compute(() => f.get() + g.get(), [f, g]); - - expect(h.get()).toEqual(10); - - const changes = await collectChanges(h.value$, async () => { - entry.set(1); - expect(h.get()).toEqual(18); - - await 0; - - entry.set(2); - expect(h.get()).toEqual(26); - }); - - expect(changes).toEqual([10, 18, 26]); - }); - it('should have typings to return another type', () => { const source = createStore(1); - const query: Query = compute(() => source.get() + '!', [source]); + const query: Query = compute((get) => get(source) + '!'); expect(query.get()).toBe('1!'); }); @@ -133,9 +106,10 @@ describe('compute()', () => { const s2 = createStore(0); const a = compute((get) => get(s1) + 1); - const b = compute((get) => ({ a: get(a), b: get(s2) }), { - comparator: (a, b) => a.b === b.b, - }); + const b = compute( + (get) => ({ a: get(a), b: get(s2) }), + (a, b) => a.b === b.b, + ); expect(b.get()).toEqual({ a: 1, b: 0 }); @@ -216,8 +190,8 @@ describe('compute()', () => { const bs = new BehaviorSubject(1); const source = queryBehaviourSubject(bs); - const query1 = compute(() => source.get() + 1, [source]); - const query2 = compute(() => query1.get() * 2, [query1]); + const query1 = compute((get) => get(source) + 1); + const query2 = compute((get) => get(query1) * 2); const subject1 = new Subject(); const subject2 = new Subject(); @@ -255,8 +229,8 @@ describe('compute()', () => { const bs = new BehaviorSubject(1); const source = queryBehaviourSubject(bs); - const query1 = compute(() => source.get() + 1, [source]); - const query2 = compute(() => query1.get() * 2, [query1]); + const query1 = compute((get) => get(source) + 1); + const query2 = compute((get) => get(query1) * 2); const subject1 = new Subject(); const subject2 = new Subject(); @@ -291,12 +265,7 @@ describe('compute()', () => { }); it('should throw an error on subscription to an incorrect dependency', async () => { - const source = createStore(1); - - const query1 = compute( - () => 1, - [source, undefined as unknown as Query], - ); + const query1 = compute((get) => get(undefined as any)); const subject = new Subject(); const changes = await collectChanges(subject.pipe(materialize()), () => { @@ -304,7 +273,9 @@ describe('compute()', () => { }); expect(changes).toEqual([ { - error: new TypeError('Incorrect dependency'), + error: new TypeError( + "Cannot read properties of undefined (reading 'get')", + ), hasValue: false, kind: 'E', value: undefined, @@ -399,9 +370,10 @@ describe('compute()', () => { it('should use a custom comparator', async () => { const source = createStore({ key: 1, val: 'a' }); - const query = compute((get) => get(source), { - comparator: (a, b) => a.key === b.key, - }); + const query = compute( + (get) => get(source), + (a, b) => a.key === b.key, + ); const changes = await collectChanges(query.value$, () => { source.set({ key: 1, val: 'a' }); @@ -430,28 +402,6 @@ describe('compute()', () => { }); }); -describe('createComputationNode()', () => { - it('should create a default node', () => { - const node = createComputationNode(() => 1); - - expect(node.customDeps).toBe(undefined); - }); - - it('should create a node by options', () => { - const s1 = createStore(1); - const s2 = createStore(2); - - const comparator = jest.fn(); - const node = createComputationNode(() => 1, { - comparator, - dependencies: [s1, s2, s1], - }); - - expect(node.comparator).toBe(comparator); - expect(node.customDeps).toEqual([s1, s2, s1]); - }); -}); - describe('createComputationQuery()', () => { it('should return ComputationQuery', async () => { const node = createComputationNode(() => 1); @@ -534,16 +484,16 @@ describe('addValueObserver()', () => { it('should add an observer and push a current value into it and make the node be hot', async () => { const source = createStore(1); - const query1 = compute(() => 1, [source]); + const query1 = compute((get) => get(source) + 1); - const query2 = compute(() => 1, [query1]); + const query2 = compute((get) => get(query1) + 1); const subject = new Subject(); const changes = await collectChanges(subject, () => { query2.value$.subscribe(subject); }); - expect(changes).toEqual([1]); + expect(changes).toEqual([3]); }); }); @@ -576,9 +526,9 @@ describe('removeValueObserver()', () => { it('should remove an observer, make a node be cold if treeObserverCount = 0 and update parents', async () => { const source = createStore(1); - const query1 = compute(() => 1, [source]); + const query1 = compute((get) => get(source) + 1); - const query2 = compute(() => 1, [query1]); + const query2 = compute((get) => get(query1) + 1); const subject1 = new Subject(); const subject2 = new Subject(); @@ -592,9 +542,9 @@ describe('onSourceError()', () => { const bs = new BehaviorSubject(1); const source = queryBehaviourSubject(bs); - const query1 = compute(() => source.get() + 1, [source]); + const query1 = compute((get) => get(source) + 1); - const query2 = compute(() => query1.get() * 2, [query1]); + const query2 = compute((get) => get(query1) * 2); const subject1 = new Subject(); const subject2 = new Subject(); @@ -634,9 +584,9 @@ describe('onSourceComplete()', () => { const bs = new BehaviorSubject(1); const source = queryBehaviourSubject(bs); - const query1 = compute(() => source.get() + 1, [source]); + const query1 = compute((get) => get(source) + 1); - const query2 = compute(() => query1.get() * 2, [query1]); + const query2 = compute((get) => get(query1) * 2); const subject1 = new Subject(); const subject2 = new Subject(); diff --git a/packages/rx-effects/src/compute.ts b/packages/rx-effects/src/compute.ts index c88ee92..5f5e12f 100644 --- a/packages/rx-effects/src/compute.ts +++ b/packages/rx-effects/src/compute.ts @@ -20,17 +20,6 @@ export type ComputationResolver = { */ export type Computation = (resolver: ComputationResolver) => T; -/** - * Options for "compute()" function - */ -export type ComputationOptions = { - /** A custom comparator to differ complex values */ - comparator?: Comparator; - - /** Explicitly dependencies for refreshing calculations */ - dependencies?: Query[]; -}; - /** * Creates a computable query which calculates its values by provided "computation" function and dependencies. * @@ -63,22 +52,13 @@ export type ComputationOptions = { * expect(messageUppercase.get()).toBe('HELLO WORLD!'); * ``` */ -export const compute: { - (computation: Computation, dependencies?: Query[]): Query; - (computation: Computation, options?: ComputationOptions): Query; -} = ( +export function compute( computation: Computation, - dependenciesOrOptions?: Query[] | ComputationOptions, -) => { - const options: ComputationOptions | undefined = dependenciesOrOptions - ? Array.isArray(dependenciesOrOptions) - ? { dependencies: dependenciesOrOptions } - : dependenciesOrOptions - : undefined; - - const node = createComputationNode(computation, options); + comparator?: Comparator, +): Query { + const node = createComputationNode(computation, comparator); return createComputationQuery(node); -}; +} /// INTERNAL @@ -91,7 +71,6 @@ export type Node = { hot: boolean; version?: number; valueRef?: ValueRef; - customDeps?: ReadonlyArray>; resolvedDeps?: ReadonlySet>; subscriptions?: (() => void)[]; observers?: Observer[]; @@ -101,13 +80,12 @@ export type Node = { /** @internal */ export function createComputationNode( computation: Computation, - options?: ComputationOptions, + comparator?: Comparator, ): Node { return { hot: false, computation, - comparator: options?.comparator, - customDeps: options?.dependencies, + comparator, }; } @@ -215,7 +193,7 @@ function makeHotNode(node: Node, observer: Observer) { valueRef = node.valueRef = calculate(node.computation); } } else { - const visitedDeps: Set> = new Set(node.customDeps); + const visitedDeps: Set> = new Set(); DEPS_COLLECTOR = (query) => { if (!(query as any)._computed) visitedDeps.add(query); @@ -232,10 +210,6 @@ function makeHotNode(node: Node, observer: Observer) { if (node.resolvedDeps.size > 0) { node.resolvedDeps.forEach((parentQuery) => { - if (!parentQuery) { - throw new TypeError('Incorrect dependency'); - } - if (!node.depObserver) { node.depObserver = { next: () => onSourceChanged(node), diff --git a/packages/rx-effects/src/index.ts b/packages/rx-effects/src/index.ts index 2110363..fd38e59 100644 --- a/packages/rx-effects/src/index.ts +++ b/packages/rx-effects/src/index.ts @@ -50,9 +50,5 @@ export { OBJECT_COMPARATOR } from './utils'; export type { StoreDeclaration, DeclaredStoreFactory } from './declareStore'; export { declareStore } from './declareStore'; -export type { - ComputationOptions, - Computation, - ComputationResolver, -} from './compute'; +export type { Computation, ComputationResolver } from './compute'; export { compute } from './compute'; diff --git a/packages/rx-effects/src/queryMappers.ts b/packages/rx-effects/src/queryMappers.ts index 39c33af..ad08c46 100644 --- a/packages/rx-effects/src/queryMappers.ts +++ b/packages/rx-effects/src/queryMappers.ts @@ -12,7 +12,7 @@ export function mapQuery( query: Query, mapper: (value: T) => R, ): Query { - return compute((get) => mapper(get(query)), [query]); + return compute((get) => mapper(get(query))); } /** @@ -33,5 +33,5 @@ export function mergeQueries( return compute((get) => { const values = queries.map((query) => get(query)); return merger(...(values as Values)); - }, queries); + }); } diff --git a/packages/rx-effects/src/store.ts b/packages/rx-effects/src/store.ts index 9b3ac6b..82f845c 100644 --- a/packages/rx-effects/src/store.ts +++ b/packages/rx-effects/src/store.ts @@ -213,9 +213,7 @@ export function createStore( }, query(selector?: (state: State) => T): Query | Query { - return selector - ? compute((get) => selector(get(store)), [store]) - : store; + return selector ? compute((get) => selector(get(store))) : store; }, set(nextState: State) {