Implement prism.source()
This commit is contained in:
parent
b117ee0aff
commit
ee68112867
3 changed files with 86 additions and 53 deletions
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue