track signal dependencies
This commit is contained in:
parent
c0198d419f
commit
b9332ad565
2 changed files with 32 additions and 16 deletions
|
|
@ -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
|
// Setup a subscription to each dependency, when it changes, poll every signal and re-evaluate
|
||||||
// TODO: This is extremely inefficient.
|
// TODO: This is extremely inefficient.
|
||||||
for (const { signal } of signalBindings) {
|
for (const { signal } of signalBindings) {
|
||||||
signal.subscribe(() => {
|
const cancelSignal = signal.subscribe(() => {
|
||||||
letSignal.set(() => {
|
letSignal.set(() => {
|
||||||
const value = eval_expr_in_signal_bindings(program, env, signalBindings, e.body);
|
const value = eval_expr_in_signal_bindings(program, env, signalBindings, e.body);
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
letSignal.dependencies.push(cancelSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return letSignal;
|
return letSignal;
|
||||||
|
|
@ -117,16 +118,20 @@ export interface Signal<T> {
|
||||||
observers: Observer<T>[],
|
observers: Observer<T>[],
|
||||||
set(transform: (state: T) => T): void,
|
set(transform: (state: T) => T): void,
|
||||||
read(): T,
|
read(): T,
|
||||||
subscribe(observer: Observer<T>): void,
|
subscribe(observer: Observer<T>): UnsubscribeCapability,
|
||||||
map<S>(transform: (state: T) => S): Signal<S>,
|
map<S>(transform: (state: T) => S): Signal<S>,
|
||||||
|
dependencies: UnsubscribeCapability[],
|
||||||
|
dropDependencies(): void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Observer<T> = (state: T) => void;
|
export type Observer<T> = (state: T) => void;
|
||||||
|
export type UnsubscribeCapability = () => void;
|
||||||
|
|
||||||
export function signal<T>(initState: T): Signal<T> {
|
export function signal<T>(initState: T): Signal<T> {
|
||||||
return {
|
return {
|
||||||
state: initState,
|
state: initState,
|
||||||
observers: [],
|
observers: [],
|
||||||
|
dependencies: [],
|
||||||
set(transform: (state: T) => T) {
|
set(transform: (state: T) => T) {
|
||||||
const state = transform(this.state);
|
const state = transform(this.state);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
@ -138,27 +143,39 @@ export function signal<T>(initState: T): Signal<T> {
|
||||||
return this.state;
|
return this.state;
|
||||||
},
|
},
|
||||||
subscribe(observer: Observer<T>) {
|
subscribe(observer: Observer<T>) {
|
||||||
// TODO: This needs to return `cancellation`
|
|
||||||
this.observers.push(observer);
|
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<S>(transform: (state: T) => S): Signal<S> {
|
map<S>(transform: (state: T) => S): Signal<S> {
|
||||||
const Y = signal(transform(this.state));
|
const Y = signal(transform(this.state));
|
||||||
this.subscribe((state: T) => {
|
const cancelY = this.subscribe((state: T) => {
|
||||||
Y.set(() => transform(state));
|
Y.set(() => transform(state));
|
||||||
});
|
});
|
||||||
|
Y.dependencies.push(cancelY);
|
||||||
return Y;
|
return Y;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function pair<A, B>(X: Signal<A>, Y: Signal<B>): Signal<[A, B]> {
|
function pair<A, B>(X: Signal<A>, Y: Signal<B>): Signal<[A, B]> {
|
||||||
const Z = signal([X.read(), Y.read()] as [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()]);
|
Z.set(() => [x, Y.read()]);
|
||||||
});
|
});
|
||||||
Y.subscribe(y => {
|
Z.dependencies.push(cancelX);
|
||||||
|
const cancelY = Y.subscribe(y => {
|
||||||
Z.set(() => [X.read(), y]);
|
Z.set(() => [X.read(), y]);
|
||||||
});
|
});
|
||||||
|
Z.dependencies.push(cancelY);
|
||||||
return Z;
|
return Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,9 +184,10 @@ function tupleThen<A, B>(Xs: Signal<A>[], f: (values: A[]) => B): Signal<B> {
|
||||||
|
|
||||||
// TODO: This is just preliminary. Has the glitch bug. Also has insane quadratic behaviour.
|
// TODO: This is just preliminary. Has the glitch bug. Also has insane quadratic behaviour.
|
||||||
Xs.forEach((X, i) => {
|
Xs.forEach((X, i) => {
|
||||||
X.subscribe(_ => {
|
const cancelX = X.subscribe(_ => {
|
||||||
Z.set(() => f(Xs.map(X => X.read())));
|
Z.set(() => f(Xs.map(X => X.read())));
|
||||||
});
|
});
|
||||||
|
Z.dependencies.push(cancelX);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Z;
|
return Z;
|
||||||
|
|
@ -183,7 +201,7 @@ function tupleThen<A, B>(Xs: Signal<A>[], f: (values: A[]) => B): Signal<B> {
|
||||||
|
|
||||||
// console.log(count.read())
|
// console.log(count.read())
|
||||||
|
|
||||||
// count.subscribe(x => {
|
// const _ = count.subscribe(x => {
|
||||||
// console.log("count is now", x);
|
// console.log("count is now", x);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
@ -194,7 +212,7 @@ function tupleThen<A, B>(Xs: Signal<A>[], f: (values: A[]) => B): Signal<B> {
|
||||||
// console.log("DOUBLE EXISTS");
|
// console.log("DOUBLE EXISTS");
|
||||||
|
|
||||||
|
|
||||||
// double.subscribe(x => {
|
// const _ = double.subscribe(x => {
|
||||||
// console.log("double is now", x);
|
// console.log("double is now", x);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
@ -204,7 +222,7 @@ function tupleThen<A, B>(Xs: Signal<A>[], f: (values: A[]) => B): Signal<B> {
|
||||||
// const WTF = pair(count, double)
|
// const WTF = pair(count, double)
|
||||||
// console.log("-> WTF EXISTS");
|
// console.log("-> WTF EXISTS");
|
||||||
|
|
||||||
// WTF.subscribe(([x, y]) => {
|
// const _ = WTF.subscribe(([x, y]) => {
|
||||||
// console.log("WTF is now ", [x, y]);
|
// console.log("WTF is now ", [x, y]);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,11 @@ export function SignalDigith(props: { signal: Digith.Signal }) {
|
||||||
|
|
||||||
// TODO: Not sure about this one. Setting a signal in `setValue` is discouraged.
|
// TODO: Not sure about this one. Setting a signal in `setValue` is discouraged.
|
||||||
setTimeout(() => setValue(signal.read()), 0);
|
setTimeout(() => setValue(signal.read()), 0);
|
||||||
// TODO: Handle cancelation...
|
|
||||||
signal.subscribe((newValue) => {
|
const cancel = signal.subscribe((newValue) => {
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
});
|
});
|
||||||
onCleanup(() => {
|
onCleanup(cancel);
|
||||||
// TODO:
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: setErrors... but how? Shouldn't this be independent of `errors`?
|
// TODO: setErrors... but how? Shouldn't this be independent of `errors`?
|
||||||
console.log("Failed to link Signal: ", e);
|
console.log("Failed to link Signal: ", e);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue