diff --git a/packages/state/api-report.md b/packages/state/api-report.md index c211c96f6..f58a65e49 100644 --- a/packages/state/api-report.md +++ b/packages/state/api-report.md @@ -33,8 +33,6 @@ export interface Computed extends Signal { readonly parentEpochs: number[]; // @internal (undocumented) readonly parents: Signal[]; - // @internal (undocumented) - readonly parentSet: ArraySet>; } // @public diff --git a/packages/state/src/lib/core/ArraySet.ts b/packages/state/src/lib/core/ArraySet.ts index 97a49c2ac..8de6b089a 100644 --- a/packages/state/src/lib/core/ArraySet.ts +++ b/packages/state/src/lib/core/ArraySet.ts @@ -144,29 +144,4 @@ export class ArraySet { throw new Error('no set or array') } - - has(elem: T) { - if (this.array) { - return this.array.indexOf(elem) !== -1 - } else { - return this.set!.has(elem) - } - } - - clear() { - if (this.set) { - this.set.clear() - } else { - this.arraySize = 0 - this.array?.fill(undefined) - } - } - - size() { - if (this.set) { - return this.set.size - } else { - return this.arraySize - } - } } diff --git a/packages/state/src/lib/core/Computed.ts b/packages/state/src/lib/core/Computed.ts index 290f8c97d..a5e2a2e81 100644 --- a/packages/state/src/lib/core/Computed.ts +++ b/packages/state/src/lib/core/Computed.ts @@ -123,8 +123,6 @@ export interface Computed extends Signal { */ readonly isActivelyListening: boolean - /** @internal */ - readonly parentSet: ArraySet> /** @internal */ readonly parents: Signal[] /** @internal */ @@ -143,7 +141,6 @@ class __UNSAFE__Computed implements Computed */ private lastCheckedEpoch = GLOBAL_START_EPOCH - parentSet = new ArraySet>() parents: Signal[] = [] parentEpochs: number[] = [] diff --git a/packages/state/src/lib/core/EffectScheduler.ts b/packages/state/src/lib/core/EffectScheduler.ts index 830359983..bf99d5d80 100644 --- a/packages/state/src/lib/core/EffectScheduler.ts +++ b/packages/state/src/lib/core/EffectScheduler.ts @@ -1,4 +1,3 @@ -import { ArraySet } from './ArraySet' import { startCapturingParents, stopCapturingParents } from './capture' import { GLOBAL_START_EPOCH } from './constants' import { attach, detach, haveParentsChanged, singleton } from './helpers' @@ -64,11 +63,9 @@ class __EffectScheduler__ { } /** @internal */ - readonly parentSet = new ArraySet>() + parentEpochs: number[] = [] /** @internal */ - readonly parentEpochs: number[] = [] - /** @internal */ - readonly parents: Signal[] = [] + parents: Signal[] = [] private readonly _scheduleEffect?: (execute: () => void) => void constructor( public readonly name: string, diff --git a/packages/state/src/lib/core/__tests__/arraySet.test.ts b/packages/state/src/lib/core/__tests__/arraySet.test.ts index 6655733f7..fee4b082c 100644 --- a/packages/state/src/lib/core/__tests__/arraySet.test.ts +++ b/packages/state/src/lib/core/__tests__/arraySet.test.ts @@ -94,16 +94,12 @@ function runTest(seed: number) { for (let i = 0; i < 1000; i++) { const num = nums[Math.floor(r() * nums.length)] - const choice = r() - if (choice < 0.45) { + if (r() > 0.5) { as.add(num) s.add(num) - } else if (choice < 0.9) { + } else { as.remove(num) s.delete(num) - } else { - as.clear() - s.clear() } try { diff --git a/packages/state/src/lib/core/__tests__/capture.test.ts b/packages/state/src/lib/core/__tests__/capture.test.ts index fbc757ee8..18e935699 100644 --- a/packages/state/src/lib/core/__tests__/capture.test.ts +++ b/packages/state/src/lib/core/__tests__/capture.test.ts @@ -1,4 +1,3 @@ -import { ArraySet } from '../ArraySet' import { atom } from '../Atom' import { computed } from '../Computed' import { react } from '../EffectScheduler' @@ -15,7 +14,6 @@ const emptyChild = (props: Partial = {}) => ({ parentEpochs: [], parents: [], - parentSet: new ArraySet(), isActivelyListening: false, lastTraversedEpoch: 0, ...props, diff --git a/packages/state/src/lib/core/capture.ts b/packages/state/src/lib/core/capture.ts index ee8d38476..cfe38894c 100644 --- a/packages/state/src/lib/core/capture.ts +++ b/packages/state/src/lib/core/capture.ts @@ -3,6 +3,7 @@ import type { Child, Signal } from './types' class CaptureStackFrame { offset = 0 + numNewParents = 0 maybeRemoved?: Signal[] @@ -49,29 +50,33 @@ export function unsafe__withoutCapture(fn: () => T): T { export function startCapturingParents(child: Child) { inst.stack = new CaptureStackFrame(inst.stack, child) - child.parentSet.clear() } export function stopCapturingParents() { const frame = inst.stack! inst.stack = frame.below - if (frame.offset < frame.child.parents.length) { - for (let i = frame.offset; i < frame.child.parents.length; i++) { - const maybeRemovedParent = frame.child.parents[i] - if (!frame.child.parentSet.has(maybeRemovedParent)) { - detach(maybeRemovedParent, frame.child) - } - } + const didParentsChange = frame.numNewParents > 0 || frame.offset !== frame.child.parents.length - frame.child.parents.length = frame.offset - frame.child.parentEpochs.length = frame.offset + if (!didParentsChange) { + return } + for (let i = frame.offset; i < frame.child.parents.length; i++) { + const p = frame.child.parents[i] + const parentWasRemoved = frame.child.parents.indexOf(p) >= frame.offset + if (parentWasRemoved) { + detach(p, frame.child) + } + } + + frame.child.parents.length = frame.offset + frame.child.parentEpochs.length = frame.offset + if (inst.stack?.maybeRemoved) { for (let i = 0; i < inst.stack.maybeRemoved.length; i++) { const maybeRemovedParent = inst.stack.maybeRemoved[i] - if (!inst.stack.child.parentSet.has(maybeRemovedParent)) { + if (frame.child.parents.indexOf(maybeRemovedParent) === -1) { detach(maybeRemovedParent, frame.child) } } @@ -81,33 +86,34 @@ export function stopCapturingParents() { // this must be called after the parent is up to date export function maybeCaptureParent(p: Signal) { if (inst.stack) { - const wasCapturedAlready = inst.stack.child.parentSet.has(p) + const idx = inst.stack.child.parents.indexOf(p) // if the child didn't deref this parent last time it executed, then idx will be -1 // if the child did deref this parent last time but in a different order relative to other parents, then idx will be greater than stack.offset // if the child did deref this parent last time in the same order, then idx will be the same as stack.offset // if the child did deref this parent already during this capture session then 0 <= idx < stack.offset - if (wasCapturedAlready) { - return - } - - inst.stack.child.parentSet.add(p) - if (inst.stack.child.isActivelyListening) { - attach(p, inst.stack.child) - } - - if (inst.stack.offset < inst.stack.child.parents.length) { - const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset] - if (!inst.stack.maybeRemoved) { - inst.stack.maybeRemoved = [maybeRemovedParent] - } else { - inst.stack.maybeRemoved.push(maybeRemovedParent) + if (idx < 0) { + inst.stack.numNewParents++ + if (inst.stack.child.isActivelyListening) { + attach(p, inst.stack.child) } } - inst.stack.child.parents[inst.stack.offset] = p - inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch - inst.stack.offset++ + if (idx < 0 || idx >= inst.stack.offset) { + if (idx !== inst.stack.offset && idx > 0) { + const maybeRemovedParent = inst.stack.child.parents[inst.stack.offset] + + if (!inst.stack.maybeRemoved) { + inst.stack.maybeRemoved = [maybeRemovedParent] + } else if (inst.stack.maybeRemoved.indexOf(maybeRemovedParent) === -1) { + inst.stack.maybeRemoved.push(maybeRemovedParent) + } + } + + inst.stack.child.parents[inst.stack.offset] = p + inst.stack.child.parentEpochs[inst.stack.offset] = p.lastChangedEpoch + inst.stack.offset++ + } } } diff --git a/packages/state/src/lib/core/types.ts b/packages/state/src/lib/core/types.ts index 31d276ca1..0ba5c7fb0 100644 --- a/packages/state/src/lib/core/types.ts +++ b/packages/state/src/lib/core/types.ts @@ -51,9 +51,8 @@ export interface Signal { /** @internal */ export type Child = { lastTraversedEpoch: number - readonly parentSet: ArraySet> - readonly parents: Signal[] - readonly parentEpochs: number[] + parents: Signal[] + parentEpochs: number[] isActivelyListening: boolean }