Skip to content

Commit

Permalink
add single recomputeDependents to batch
Browse files Browse the repository at this point in the history
  • Loading branch information
dmaskasky committed Jan 8, 2025
1 parent 023480c commit 8ebb8f0
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 35 deletions.
67 changes: 34 additions & 33 deletions src/vanilla/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@ const addDependency = <Value>(
type BatchPriority = 0 | 1 | 2

type Batch = [
/** high priority listeners */
priority0: Set<() => void>,
/** finish recompute */
priority0: Set<(batch: Batch) => void>,
/** atom listeners */
priority1: Set<() => void>,
priority1: Set<(batch: Batch) => void>,
/** atom mount hooks */
priority2: Set<() => void>,
priority2: Set<(batch: Batch) => void>,
] & {
/** changed Atoms */
C: Set<AnyAtom>
Expand All @@ -189,7 +189,7 @@ const createBatch = (): Batch =>
const addBatchFunc = (
batch: Batch,
priority: BatchPriority,
fn: () => void,
fn: (batch: Batch) => void,
) => {
batch[priority].add(fn)
}
Expand All @@ -203,21 +203,20 @@ const registerBatchAtom = (
batch.C.add(atom)
atomState.u?.(batch)
const scheduleListeners = () => {
atomState.m?.l.forEach((listener) => addBatchFunc(batch, 1, listener))
atomState.m?.l.forEach((listener) =>
addBatchFunc(batch, 1, () => listener()),
)
}
addBatchFunc(batch, 1, scheduleListeners)
}
}

const flushBatch = (
batch: Batch,
recomputeDependents: (batch: Batch) => void,
) => {
const flushBatch = (batch: Batch) => {
let error: AnyError
let hasError = false
const call = (fn: () => void) => {
const call = (fn: (batch: Batch) => void) => {
try {
fn()
fn(batch)
} catch (e) {
if (!hasError) {
error = e
Expand All @@ -226,8 +225,6 @@ const flushBatch = (
}
}
while (batch.C.size || batch.some((channel) => channel.size)) {
recomputeDependents(batch)
batch.C.clear()
for (const channel of batch) {
channel.forEach(call)
channel.clear()
Expand Down Expand Up @@ -380,7 +377,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
const batch = createBatch()
addDependency(atom, atomState, a, aState)
mountDependencies(batch, atom, atomState)
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}
}
Expand Down Expand Up @@ -422,7 +419,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
if (atomState.m) {
const batch = createBatch()
mountDependencies(batch, atom, atomState)
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}
valueOrPromise.then(complete, complete)
Expand All @@ -441,18 +438,23 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
const readAtom = <Value>(atom: Atom<Value>): Value =>
returnAtomValue(readAtomState(undefined, atom))

const getMountedDependents = <Value>(
const getMountedOrBatchDependents = <Value>(
atomState: AtomState<Value>,
): Map<AnyAtom, AtomState> => {
const mountedDependents = new Map<AnyAtom, AtomState>()
const dependents = new Set([...(atomState.m?.t || []), ...atomState.p])
for (const a of dependents) {
const dependents = new Map<AnyAtom, AtomState>()
for (const a of atomState.m?.t || []) {
const aState = ensureAtomState(a)
if (aState.m) {
mountedDependents.set(a, aState)
dependents.set(a, aState)
}
}
return mountedDependents
for (const atomWithPendingPromise of atomState.p) {
dependents.set(
atomWithPendingPromise,
ensureAtomState(atomWithPendingPromise),
)
}
return dependents
}

const dirtyDependents = <Value>(atomState: AtomState<Value>) => {
Expand All @@ -465,7 +467,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
continue
}
aState.x = true
for (const [, s] of getMountedDependents(aState)) {
for (const [, s] of getMountedOrBatchDependents(aState)) {
if (!dependents.has(s)) {
dependents.add(s)
stack.push(s)
Expand Down Expand Up @@ -512,7 +514,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
}
visiting.add(a)
// Push unvisited dependents onto the stack
for (const [d, s] of getMountedDependents(aState)) {
for (const [d, s] of getMountedOrBatchDependents(aState)) {
if (a !== d && !visiting.has(d)) {
stack.push([d, s])
}
Expand All @@ -539,11 +541,9 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
}
delete aState.x
}
batch.C.clear()
}

const recomputeAndFlushBatch = (batch: Batch) =>
flushBatch(batch, recomputeDependents)

const writeAtomState = <Value, Args extends unknown[], Result>(
batch: Batch,
atom: WritableAtom<Value, Args, Result>,
Expand All @@ -570,14 +570,15 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
if (prevEpochNumber !== aState.n) {
dirtyDependents(aState)
registerBatchAtom(batch, a, aState)
addBatchFunc(batch, 0, recomputeDependents)
}
return undefined as R
} else {
return writeAtomState(batch, a, ...args)
}
} finally {
if (!isSync) {
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}
}
Expand All @@ -596,7 +597,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
try {
return writeAtomState(batch, atom, ...args)
} finally {
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}

Expand Down Expand Up @@ -653,7 +654,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
return writeAtomState(batch, atom, ...args)
} finally {
if (!isSync) {
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}
}
Expand Down Expand Up @@ -690,7 +691,7 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
// unmount self
const onUnmount = atomState.m.u
if (onUnmount) {
addBatchFunc(batch, 2, () => onUnmount(batch))
addBatchFunc(batch, 2, onUnmount)
}
delete atomState.m
atomState.h?.(batch)
Expand All @@ -710,12 +711,12 @@ const buildStore = (...storeArgs: StoreArgs): Store => {
const mounted = mountAtom(batch, atom, atomState)
const listeners = mounted.l
listeners.add(listener)
recomputeAndFlushBatch(batch)
flushBatch(batch)
return () => {
listeners.delete(listener)
const batch = createBatch()
unmountAtom(batch, atom, atomState)
recomputeAndFlushBatch(batch)
flushBatch(batch)
}
}

Expand Down
20 changes: 18 additions & 2 deletions tests/vanilla/effect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Store = ReturnType<typeof createStore>
type GetAtomState = Parameters<Parameters<Store['unstable_derive']>[0]>[0]
type AtomState = NonNullable<ReturnType<GetAtomState>>
type AnyAtom = Atom<unknown>
type Batch = Parameters<NonNullable<AtomState['u']>>[0]

type Cleanup = () => void
type Effect = (get: Getter, set: Setter) => Cleanup | void
Expand Down Expand Up @@ -59,7 +60,7 @@ function syncEffect(effect: Effect): Atom<void> {
store.set(refreshAtom, (v) => v + 1)
} else {
// unmount
batch[0].add(() => {
scheduleListenerAfterRecompute(batch, () => {
ref.cleanup?.()
delete ref.cleanup
})
Expand All @@ -69,7 +70,22 @@ function syncEffect(effect: Effect): Atom<void> {
internalAtomState.u = (batch) => {
originalUpdateHook?.(batch)
// update
batch[0].add(runEffect)
scheduleListenerAfterRecompute(batch, runEffect)
}
function scheduleListenerAfterRecompute(
batch: Batch,
listener: () => void,
) {
const scheduleListener = () => {
batch[0].add(listener)
}
if (batch[0].size === 0) {
// no other listeners
// schedule after recomputeDependents
batch[0].add(scheduleListener)
} else {
scheduleListener()
}
}
}
return atom((get) => {
Expand Down

0 comments on commit 8ebb8f0

Please sign in to comment.