diff --git a/src/lang/eval/signal.ts b/src/lang/eval/signal.ts index 77252f7..578315c 100644 --- a/src/lang/eval/signal.ts +++ b/src/lang/eval/signal.ts @@ -65,12 +65,13 @@ export function eval_signal_expression(program: Program, env: Env, e: SignalExpr // Setup a subscription to each dependency, when it changes, poll every signal and re-evaluate // TODO: This is extremely inefficient. for (const { signal } of signalBindings) { - signal.subscribe(() => { + const cancelSignal = signal.subscribe(() => { letSignal.set(() => { const value = eval_expr_in_signal_bindings(program, env, signalBindings, e.body); return value; }); }); + letSignal.dependencies.push(cancelSignal); } return letSignal; @@ -117,16 +118,20 @@ export interface Signal { observers: Observer[], set(transform: (state: T) => T): void, read(): T, - subscribe(observer: Observer): void, + subscribe(observer: Observer): UnsubscribeCapability, map(transform: (state: T) => S): Signal, + dependencies: UnsubscribeCapability[], + dropDependencies(): void, }; export type Observer = (state: T) => void; +export type UnsubscribeCapability = () => void; export function signal(initState: T): Signal { return { state: initState, observers: [], + dependencies: [], set(transform: (state: T) => T) { const state = transform(this.state); this.state = state; @@ -138,27 +143,39 @@ export function signal(initState: T): Signal { return this.state; }, subscribe(observer: Observer) { - // TODO: This needs to return `cancellation` this.observers.push(observer); + const that = this; + return () => { + that.observers = that.observers.filter(sub => sub !== observer); + }; + }, + dropDependencies() { + for (const cancel of this.dependencies) { + cancel() + } + this.dependencies = []; }, map(transform: (state: T) => S): Signal { const Y = signal(transform(this.state)); - this.subscribe((state: T) => { + const cancelY = this.subscribe((state: T) => { Y.set(() => transform(state)); }); + Y.dependencies.push(cancelY); return Y; - } + }, }; } function pair(X: Signal, Y: Signal): Signal<[A, B]> { const Z = signal([X.read(), Y.read()] as [A, B]); - X.subscribe(x => { + const cancelX = X.subscribe(x => { Z.set(() => [x, Y.read()]); }); - Y.subscribe(y => { + Z.dependencies.push(cancelX); + const cancelY = Y.subscribe(y => { Z.set(() => [X.read(), y]); }); + Z.dependencies.push(cancelY); return Z; } @@ -167,9 +184,10 @@ function tupleThen(Xs: Signal[], f: (values: A[]) => B): Signal { // TODO: This is just preliminary. Has the glitch bug. Also has insane quadratic behaviour. Xs.forEach((X, i) => { - X.subscribe(_ => { + const cancelX = X.subscribe(_ => { Z.set(() => f(Xs.map(X => X.read()))); }); + Z.dependencies.push(cancelX); }); return Z; @@ -183,7 +201,7 @@ function tupleThen(Xs: Signal[], f: (values: A[]) => B): Signal { // console.log(count.read()) -// count.subscribe(x => { +// const _ = count.subscribe(x => { // console.log("count is now", x); // }); @@ -194,7 +212,7 @@ function tupleThen(Xs: Signal[], f: (values: A[]) => B): Signal { // console.log("DOUBLE EXISTS"); -// double.subscribe(x => { +// const _ = double.subscribe(x => { // console.log("double is now", x); // }); @@ -204,7 +222,7 @@ function tupleThen(Xs: Signal[], f: (values: A[]) => B): Signal { // const WTF = pair(count, double) // console.log("-> WTF EXISTS"); -// WTF.subscribe(([x, y]) => { +// const _ = WTF.subscribe(([x, y]) => { // console.log("WTF is now ", [x, y]); // }); diff --git a/src/ui/Digith/Signal/SignalDigith.tsx b/src/ui/Digith/Signal/SignalDigith.tsx index 3c955df..6f1f105 100644 --- a/src/ui/Digith/Signal/SignalDigith.tsx +++ b/src/ui/Digith/Signal/SignalDigith.tsx @@ -51,13 +51,11 @@ export function SignalDigith(props: { signal: Digith.Signal }) { // TODO: Not sure about this one. Setting a signal in `setValue` is discouraged. setTimeout(() => setValue(signal.read()), 0); - // TODO: Handle cancelation... - signal.subscribe((newValue) => { + + const cancel = signal.subscribe((newValue) => { setValue(newValue); }); - onCleanup(() => { - // TODO: - }); + onCleanup(cancel); } catch (e) { // TODO: setErrors... but how? Shouldn't this be independent of `errors`? console.log("Failed to link Signal: ", e);