Implement prism.source()

This commit is contained in:
Aria Minaei 2022-12-01 12:58:59 +01:00
parent b117ee0aff
commit ee68112867
3 changed files with 86 additions and 53 deletions

View file

@ -253,7 +253,7 @@ export default class Atom<State extends {}>
const getValue = () => this.getIn(path) const getValue = () => this.getIn(path)
return prism(() => { return prism(() => {
return prism.source('value', subscribe, getValue) return prism.source(subscribe, getValue)
}) })
} }
} }

View file

@ -55,7 +55,7 @@ export default class Box<V> implements IBox<V> {
this._emitter.tappable.tap(listener) this._emitter.tappable.tap(listener)
const getValue = () => this._value const getValue = () => this._value
this._publicDerivation = prism(() => { this._publicDerivation = prism(() => {
return prism.source('value', subscribe, getValue) return prism.source(subscribe, getValue)
}) })
} }

View file

@ -21,15 +21,6 @@ type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
const voidFn = () => {} const voidFn = () => {}
class HotHandle<V> { class HotHandle<V> {
get hasDependents(): boolean {
return this._dependents.size > 0
}
removeDependent(d: IDependent) {
this._dependents.delete(d)
}
addDependent(d: IDependent) {
this._dependents.add(d)
}
private _didMarkDependentsAsStale: boolean = false private _didMarkDependentsAsStale: boolean = false
private _isFresh: boolean = false private _isFresh: boolean = false
protected _cacheOfDendencyValues: Map<IDerivation<unknown>, unknown> = protected _cacheOfDendencyValues: Map<IDerivation<unknown>, unknown> =
@ -46,30 +37,49 @@ class HotHandle<V> {
protected _dependencies: Set<IDerivation<$IntentionalAny>> = new Set() protected _dependencies: Set<IDerivation<$IntentionalAny>> = new Set()
protected _possiblyStaleDeps = new Set<IDerivation<unknown>>() protected _possiblyStaleDeps = new Set<IDerivation<unknown>>()
private _scope = new HotScope()
private _scope: HotScope = new HotScope(
this as $IntentionalAny as HotHandle<unknown>,
)
/** /**
* @internal * @internal
*/ */
protected _lastValue: undefined | V = undefined protected _lastValue: undefined | V = undefined
/**
* If true, the derivation is stale even though its dependencies aren't
* marked as such. This is used by `prism.source()` and `prism.state()`
* to mark the prism as stale.
*/
private _forciblySetToStale: boolean = false
constructor( constructor(
private readonly _fn: () => V, private readonly _fn: () => V,
private readonly _prismInstance: PrismDerivation<V>, private readonly _prismInstance: PrismDerivation<V>,
) { ) {
for (const d of this._dependencies) { for (const d of this._dependencies) {
d.addDependent(this._markAsStale) d.addDependent(this._reactToDependencyGoingStale)
} }
this._scope = new HotScope()
startIgnoringDependencies() startIgnoringDependencies()
this.getValue() this.getValue()
stopIgnoringDependencies() stopIgnoringDependencies()
} }
get hasDependents(): boolean {
return this._dependents.size > 0
}
removeDependent(d: IDependent) {
this._dependents.delete(d)
}
addDependent(d: IDependent) {
this._dependents.add(d)
}
destroy() { destroy() {
for (const d of this._dependencies) { for (const d of this._dependencies) {
d.removeDependent(this._markAsStale) d.removeDependent(this._reactToDependencyGoingStale)
} }
cleanupScopeStack(this._scope) cleanupScopeStack(this._scope)
} }
@ -80,6 +90,7 @@ class HotHandle<V> {
this._lastValue = newValue this._lastValue = newValue
this._isFresh = true this._isFresh = true
this._didMarkDependentsAsStale = false this._didMarkDependentsAsStale = false
this._forciblySetToStale = false
} }
return this._lastValue! return this._lastValue!
} }
@ -87,19 +98,21 @@ class HotHandle<V> {
_recalculate() { _recalculate() {
let value: V let value: V
if (this._possiblyStaleDeps.size > 0) { if (!this._forciblySetToStale) {
let anActuallyStaleDepWasFound = false if (this._possiblyStaleDeps.size > 0) {
startIgnoringDependencies() let anActuallyStaleDepWasFound = false
for (const dep of this._possiblyStaleDeps) { startIgnoringDependencies()
if (this._cacheOfDendencyValues.get(dep) !== dep.getValue()) { for (const dep of this._possiblyStaleDeps) {
anActuallyStaleDepWasFound = true if (this._cacheOfDendencyValues.get(dep) !== dep.getValue()) {
break anActuallyStaleDepWasFound = true
break
}
}
stopIgnoringDependencies()
this._possiblyStaleDeps.clear()
if (!anActuallyStaleDepWasFound) {
return this._lastValue!
} }
}
stopIgnoringDependencies()
this._possiblyStaleDeps.clear()
if (!anActuallyStaleDepWasFound) {
return this._lastValue!
} }
} }
@ -147,9 +160,20 @@ class HotHandle<V> {
return value! return value!
} }
protected _markAsStale = (which: IDerivation<$IntentionalAny>) => { forceStale() {
this._reactToDependencyBecomingStale(which) this._forciblySetToStale = true
this._markAsStale()
}
protected _reactToDependencyGoingStale = (
which: IDerivation<$IntentionalAny>,
) => {
this._possiblyStaleDeps.add(which)
this._markAsStale()
}
private _markAsStale() {
if (this._didMarkDependentsAsStale) return if (this._didMarkDependentsAsStale) return
this._didMarkDependentsAsStale = true this._didMarkDependentsAsStale = true
@ -160,17 +184,13 @@ class HotHandle<V> {
} }
} }
_reactToDependencyBecomingStale(msgComingFrom: IDerivation<unknown>) {
this._possiblyStaleDeps.add(msgComingFrom)
}
/** /**
* @internal * @internal
*/ */
protected _addDependency(d: IDerivation<$IntentionalAny>) { protected _addDependency(d: IDerivation<$IntentionalAny>) {
if (this._dependencies.has(d)) return if (this._dependencies.has(d)) return
this._dependencies.add(d) this._dependencies.add(d)
d.addDependent(this._markAsStale) d.addDependent(this._reactToDependencyGoingStale)
} }
/** /**
@ -179,7 +199,7 @@ class HotHandle<V> {
protected _removeDependency(d: IDerivation<$IntentionalAny>) { protected _removeDependency(d: IDerivation<$IntentionalAny>) {
if (!this._dependencies.has(d)) return if (!this._dependencies.has(d)) return
this._dependencies.delete(d) this._dependencies.delete(d)
d.removeDependent(this._markAsStale) d.removeDependent(this._reactToDependencyGoingStale)
} }
} }
@ -382,14 +402,12 @@ interface PrismScope {
state<T>(key: string, initialValue: T): [T, (val: T) => void] state<T>(key: string, initialValue: T): [T, (val: T) => void]
ref<T>(key: string, initialValue: T): IRef<T> ref<T>(key: string, initialValue: T): IRef<T>
sub(key: string): PrismScope sub(key: string): PrismScope
source<V>( source<V>(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V
key: string,
subscribe: (fn: (val: V) => void) => VoidFn,
getValue: () => V,
): V
} }
class HotScope implements PrismScope { class HotScope implements PrismScope {
constructor(private readonly _hotHandle: HotHandle<unknown>) {}
protected readonly _refs: Map<string, IRef<unknown>> = new Map() protected readonly _refs: Map<string, IRef<unknown>> = new Map()
ref<T>(key: string, initialValue: T): IRef<T> { ref<T>(key: string, initialValue: T): IRef<T> {
let ref = this._refs.get(key) let ref = this._refs.get(key)
@ -429,6 +447,17 @@ class HotScope implements PrismScope {
stopIgnoringDependencies() stopIgnoringDependencies()
effect.deps = deps effect.deps = deps
} }
/**
* TODO: we should cleanup dangling effects too.
* Example:
* ```ts
* let i = 0
* prism(() => {
* if (i === 0) prism.effect("this effect will only run once", () => {}, [])
* i++
* })
* ```
*/
} }
readonly memos: Map<string, IMemo> = new Map() readonly memos: Map<string, IMemo> = new Map()
@ -475,7 +504,7 @@ class HotScope implements PrismScope {
sub(key: string): HotScope { sub(key: string): HotScope {
if (!this.subs[key]) { if (!this.subs[key]) {
this.subs[key] = new HotScope() this.subs[key] = new HotScope(this._hotHandle)
} }
return this.subs[key] return this.subs[key]
} }
@ -487,11 +516,20 @@ class HotScope implements PrismScope {
this.effects.clear() this.effects.clear()
} }
source<V>( source<V>(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V {
key: string, const sourceKey = '$$source/blah'
subscribe: (fn: (val: V) => void) => VoidFn, this.effect(
getValue: () => V, sourceKey,
): V {} () => {
const unsub = subscribe(() => {
this._hotHandle.forceStale()
})
return unsub
},
[subscribe],
)
return getValue()
}
} }
function cleanupScopeStack(scope: HotScope) { function cleanupScopeStack(scope: HotScope) {
@ -705,7 +743,6 @@ const possibleDerivationToValue = <
} }
function source<V>( function source<V>(
key: string,
subscribe: (fn: (val: V) => void) => VoidFn, subscribe: (fn: (val: V) => void) => VoidFn,
getValue: () => V, getValue: () => V,
): V { ): V {
@ -714,7 +751,7 @@ function source<V>(
throw new Error(`prism.source() is called outside of a prism() call.`) throw new Error(`prism.source() is called outside of a prism() call.`)
} }
return scope.source(key, subscribe, getValue) return scope.source(subscribe, getValue)
} }
type IPrismFn = { type IPrismFn = {
@ -760,11 +797,7 @@ class ColdScope implements PrismScope {
sub(key: string): ColdScope { sub(key: string): ColdScope {
return new ColdScope() return new ColdScope()
} }
source<V>( source<V>(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V {
key: string,
subscribe: (fn: (val: V) => void) => VoidFn,
getValue: () => V,
): V {
return getValue() return getValue()
} }
} }