import {Op} from "../op/op.js" import {ob} from "../tools/ob.js" import {Signal} from "./signal.js" import {OpSignal} from "./op_signal.js" import {LeanTrack, NormalTrack, SignalTracker} from "./parts/tracker.js" import {Collector, Lean, ReactorCore, Responder} from "../reactor/types.js" export class SignalTower implements ReactorCore { // TODO wrap all signals in WeakRef, to promote garbage collection? #signals = new Set>() #waiters = new Set>() signal(value: V): Signal { const signal = new Signal(value) this.#signals.add(signal) return signal } computed(fun: () => V) { const signal = this.signal(fun()) this.reaction(() => { signal.value = fun() }) return signal } async computedAsync( collector: () => X, responder: (x: X) => Promise, ) { const value = await responder(collector()) const signal = this.signal(value) this.reaction( collector, async x => { signal.value = await responder(x) }, ) return signal } op(op: Op.For = Op.loading()) { const signal = new OpSignal(op) this.#signals.add(signal) return signal } load(fn: () => Promise) { const signal = this.op(Op.loading()) signal.load(fn) return signal } many(states: S) { return ( ob(states).map(state => this.signal(state)) ) as any as {[P in keyof S]: Signal} } reaction

(collector: Collector

, responder?: Responder

) { const tracker = new SignalTracker({ waiters: this.#waiters, all_signals: this.#signals, }) const track: NormalTrack

= {collector, responder} const {recording} = tracker.observe(track.collector) tracker.add_listeners(track, recording) return () => tracker.shutdown() } lean(actor: () => void): Lean { const tracker = new SignalTracker({ waiters: this.#waiters, all_signals: this.#signals, }) const track: LeanTrack = {lean: true, actor} return { stop: () => tracker.shutdown(), collect: collector => { const {payload, recording} = tracker.observe(collector) tracker.add_listeners(track, recording) return payload }, } } // TODO this is a hack, we shouldn't have to wait two cycles to be sure everything's done async #waitOneCycle() { return await Promise.all([...this.#signals].map(s => s.wait)) .then(() => Promise.all([...this.#waiters])) .then(() => { this.#waiters.clear() }) } get wait(): Promise { return Promise.resolve() .then(() => this.#waitOneCycle()) .then(() => this.#waitOneCycle()) } }