Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[idea] add pending to store.unstable_derive #2854

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 73 additions & 41 deletions src/vanilla/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@
dependents: Map<AnyAtom, Set<AnyAtom>>,
atomStates: Map<AnyAtom, AtomState>,
functions: Set<() => void>,
...unknown[],

Check failure on line 168 in src/vanilla/store.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.1.6)

Tuple members must all have names or all not have names.

Check failure on line 168 in src/vanilla/store.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.0.4)

Tuple members must all have names or all not have names.
]

const createPending = (): Pending => [new Map(), new Map(), new Set()]

const addPendingAtom = (
pending: Pending,
atom: AnyAtom,
Expand Down Expand Up @@ -227,19 +226,27 @@

// internal & unstable type
type StoreArgs = readonly [
getAtomState: <Value>(atom: Atom<Value>) => AtomState<Value>,
getAtomState: <Value>(
pending: Pending | undefined,
atom: Atom<Value>,
) => AtomState<Value>,
atomRead: <Value>(
pending: Pending | undefined,
atom: Atom<Value>,
...params: Parameters<Atom<Value>['read']>
) => Value,
atomWrite: <Value, Args extends unknown[], Result>(
pending: Pending,
atom: WritableAtom<Value, Args, Result>,
...params: Parameters<WritableAtom<Value, Args, Result>['write']>
) => Result,
atomOnMount: <Value, Args extends unknown[], Result>(
pending: Pending,
atom: WritableAtom<Value, Args, Result>,
setAtom: (...args: Args) => Result,
) => OnUnmount | void,
createPending: (prevPending?: Pending | undefined) => Pending,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prevPending is the in-context upstream pending. For instance, unsubscribe's invocation of createPending receives subscribe's pending.

flushPending: (pending: Pending) => void,
]

// for debugging purpose only
Expand Down Expand Up @@ -267,7 +274,14 @@
export type INTERNAL_PrdStore = PrdStore

const buildStore = (
...[getAtomState, atomRead, atomWrite, atomOnMount]: StoreArgs
...[
getAtomState,
atomRead,
atomWrite,
atomOnMount,
createPending,
flushPending,
]: StoreArgs
): Store => {
// for debugging purpose only
let debugMountedAtoms: Set<AnyAtom>
Expand All @@ -277,6 +291,7 @@
}

const setAtomStateValueOrPromise = (
pending: Pending | undefined,
atom: AnyAtom,
atomState: AtomState,
valueOrPromise: unknown,
Expand All @@ -287,7 +302,11 @@
if (isPromiseLike(valueOrPromise)) {
patchPromiseForCancelability(valueOrPromise)
for (const a of atomState.d.keys()) {
addPendingPromiseToDependency(atom, valueOrPromise, getAtomState(a))
addPendingPromiseToDependency(
atom,
valueOrPromise,
getAtomState(pending, a),
)
}
atomState.v = valueOrPromise
delete atomState.e
Expand All @@ -308,7 +327,7 @@
atom: Atom<Value>,
dirtyAtoms?: Set<AnyAtom>,
): AtomState<Value> => {
const atomState = getAtomState(atom)
const atomState = getAtomState(pending, atom)
// See if we can skip recomputing this atom.
if (isAtomStateInitialized(atomState)) {
// If the atom is mounted, we can use cached atom state.
Expand All @@ -335,10 +354,10 @@
let isSync = true
const getter: Getter = <V>(a: Atom<V>) => {
if (isSelfAtom(atom, a)) {
const aState = getAtomState(a)
const aState = getAtomState(pending, a)
if (!isAtomStateInitialized(aState)) {
if (hasInitialValue(a)) {
setAtomStateValueOrPromise(a, aState, a.init)
setAtomStateValueOrPromise(pending, a, aState, a.init)
} else {
// NOTE invalid derived atoms can reach here
throw new Error('no atom init')
Expand All @@ -354,10 +373,10 @@
if (isSync) {
addDependency(pending, atom, atomState, a, aState)
} else {
const pending = createPending()
addDependency(pending, atom, atomState, a, aState)
mountDependencies(pending, atom, atomState)
flushPending(pending)
const pending2 = createPending(pending)
addDependency(pending2, atom, atomState, a, aState)
mountDependencies(pending2, atom, atomState)
flushPending(pending2)
}
}
}
Expand Down Expand Up @@ -391,15 +410,15 @@
},
}
try {
const valueOrPromise = atomRead(atom, getter, options as never)
setAtomStateValueOrPromise(atom, atomState, valueOrPromise)
const valueOrPromise = atomRead(pending, atom, getter, options as never)
setAtomStateValueOrPromise(pending, atom, atomState, valueOrPromise)
if (isPromiseLike(valueOrPromise)) {
valueOrPromise.onCancel?.(() => controller?.abort())
const complete = () => {
if (atomState.m) {
const pending = createPending()
mountDependencies(pending, atom, atomState)
flushPending(pending)
const pending2 = createPending(pending)
mountDependencies(pending2, atom, atomState)
flushPending(pending2)
}
}
valueOrPromise.then(complete, complete)
Expand All @@ -425,16 +444,16 @@
): Map<AnyAtom, AtomState> => {
const dependents = new Map<AnyAtom, AtomState>()
for (const a of atomState.m?.t || []) {
dependents.set(a, getAtomState(a))
dependents.set(a, getAtomState(pending, a))
}
for (const atomWithPendingPromise of atomState.p) {
dependents.set(
atomWithPendingPromise,
getAtomState(atomWithPendingPromise),
getAtomState(pending, atomWithPendingPromise),
)
}
getPendingDependents(pending, atom)?.forEach((dependent) => {
dependents.set(dependent, getAtomState(dependent))
dependents.set(dependent, getAtomState(pending, dependent))
})
return dependents
}
Expand Down Expand Up @@ -531,7 +550,7 @@
a: WritableAtom<V, As, R>,
...args: As
) => {
const aState = getAtomState(a)
const aState = getAtomState(pending, a)
try {
if (isSelfAtom(atom, a)) {
if (!hasInitialValue(a)) {
Expand All @@ -540,7 +559,7 @@
}
const prevEpochNumber = aState.n
const v = args[0] as V
setAtomStateValueOrPromise(a, aState, v)
setAtomStateValueOrPromise(pending, a, aState, v)
mountDependencies(pending, a, aState)
if (prevEpochNumber !== aState.n) {
addPendingAtom(pending, a, aState)
Expand All @@ -557,7 +576,7 @@
}
}
try {
return atomWrite(atom, getter, setter, ...args)
return atomWrite(pending, atom, getter, setter, ...args)
} finally {
isSync = false
}
Expand All @@ -583,15 +602,15 @@
if (atomState.m && !isPendingPromise(atomState.v)) {
for (const a of atomState.d.keys()) {
if (!atomState.m.d.has(a)) {
const aMounted = mountAtom(pending, a, getAtomState(a))
const aMounted = mountAtom(pending, a, getAtomState(pending, a))
aMounted.t.add(atom)
atomState.m.d.add(a)
}
}
for (const a of atomState.m.d || []) {
if (!atomState.d.has(a)) {
atomState.m.d.delete(a)
const aMounted = unmountAtom(pending, a, getAtomState(a))
const aMounted = unmountAtom(pending, a, getAtomState(pending, a))
aMounted?.t.delete(atom)
}
}
Expand All @@ -608,7 +627,7 @@
readAtomState(pending, atom)
// mount dependencies first
for (const a of atomState.d.keys()) {
const aMounted = mountAtom(pending, a, getAtomState(a))
const aMounted = mountAtom(pending, a, getAtomState(pending, a))
aMounted.t.add(atom)
}
// mount self
Expand Down Expand Up @@ -642,7 +661,7 @@
}
addPendingFunction(pending, () => {
const onUnmount = createInvocationContext(pending, () =>
atomOnMount(atom, (...args) => setAtom(...args)),
atomOnMount(pending, atom, (...args) => setAtom(...args)),
)
if (onUnmount) {
mounted.u = (pending) => createInvocationContext(pending, onUnmount)
Expand All @@ -661,7 +680,9 @@
if (
atomState.m &&
!atomState.m.l.size &&
!Array.from(atomState.m.t).some((a) => getAtomState(a).m?.d.has(atom))
!Array.from(atomState.m.t).some((a) =>
getAtomState(pending, a).m?.d.has(atom),
)
) {
// unmount self
const onUnmount = atomState.m.u
Expand All @@ -674,7 +695,7 @@
}
// unmount dependencies
for (const a of atomState.d.keys()) {
const aMounted = unmountAtom(pending, a, getAtomState(a))
const aMounted = unmountAtom(pending, a, getAtomState(pending, a))
aMounted?.t.delete(atom)
}
return undefined
Expand All @@ -684,21 +705,30 @@

const subscribeAtom = (atom: AnyAtom, listener: () => void) => {
const pending = createPending()
const atomState = getAtomState(atom)
const atomState = getAtomState(pending, atom)
const mounted = mountAtom(pending, atom, atomState)
const listeners = mounted.l
listeners.add(listener)
flushPending(pending)
return () => {
listeners.delete(listener)
const pending = createPending()
unmountAtom(pending, atom, atomState)
flushPending(pending)
const pending2 = createPending(pending)
unmountAtom(pending2, atom, atomState)
flushPending(pending2)
}
}

const unstable_derive = (fn: (...args: StoreArgs) => StoreArgs) =>
buildStore(...fn(getAtomState, atomRead, atomWrite, atomOnMount))
buildStore(
...fn(
getAtomState,
atomRead,
atomWrite,
atomOnMount,
createPending,
flushPending,
),
)

const store: Store = {
get: readAtom,
Expand All @@ -711,7 +741,7 @@
// store dev methods (these are tentative and subject to change without notice)
dev4_get_internal_weak_map: () => ({
get: (atom) => {
const atomState = getAtomState(atom)
const atomState = getAtomState(undefined, atom)
if (atomState.n === 0) {
// for backward compatibility
return undefined
Expand All @@ -724,9 +754,9 @@
const pending = createPending()
for (const [atom, value] of values) {
if (hasInitialValue(atom)) {
const atomState = getAtomState(atom)
const atomState = getAtomState(pending, atom)
const prevEpochNumber = atomState.n
setAtomStateValueOrPromise(atom, atomState, value)
setAtomStateValueOrPromise(pending, atom, atomState, value)
mountDependencies(pending, atom, atomState)
if (prevEpochNumber !== atomState.n) {
addPendingAtom(pending, atom, atomState)
Expand All @@ -744,7 +774,7 @@

export const createStore = (): Store => {
const atomStateMap = new WeakMap()
const getAtomState = <Value>(atom: Atom<Value>) => {
const getAtomState = <Value>(_: Pending | undefined, atom: Atom<Value>) => {
if (import.meta.env?.MODE !== 'production' && !atom) {
throw new Error('Atom is undefined or null')
}
Expand All @@ -757,9 +787,11 @@
}
return buildStore(
getAtomState,
(atom, ...params) => atom.read(...params),
(atom, ...params) => atom.write(...params),
(atom, ...params) => atom.onMount?.(...params),
(_, atom, ...params) => atom.read(...params),
(_, atom, ...params) => atom.write(...params),
(_, atom, ...params) => atom.onMount?.(...params),
(_) => [new Map(), new Map(), new Set()],
flushPending,
)
}

Expand Down
Loading