Skip to content

Instantly share code, notes, and snippets.

@alandipert
Last active December 20, 2021 18:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save alandipert/b433959f6e0ddf9487c9bdbe73789a75 to your computer and use it in GitHub Desktop.
Save alandipert/b433959f6e0ddf9487c9bdbe73789a75 to your computer and use it in GitHub Desktop.
const depGraph = new Map(),
suspended = new Set();
let inTransaction = false;
function addEdge(g, from, to) {
let s = g.has(from) ? g.get(from) : new Set();
s.add(to);
g.set(from, s);
}
function propagate(g, from, walked = new Set()) {
if (!g.has(from)) return;
g.get(from).forEach(to => {
if (!walked.has(to)) {
walked.add(to);
if (to.update()) {
propagate(g, to, walked);
}
}
});
}
function transaction(thunk) {
if (inTransaction) {
thunk();
} else {
try {
inTransaction = true;
thunk();
} finally {
inTransaction = false;
let walked = new Set();
suspended.forEach(s => propagate(depGraph, s, walked));
suspended.clear();
}
}
}
class Input {
constructor(value) {
this.value = this.previousValue = value;
}
set(value) {
if (this.value !== value) {
this.previousValue = this.value;
this.value = value;
if (inTransaction) {
suspended.add(this);
} else {
propagate(depGraph, this);
}
}
return value;
}
}
class Formula {
constructor(f, sources) {
this.f = f;
this.sources = sources;
sources
.filter(Formula.isReactiveSource)
.forEach(source => addEdge(depGraph, source, this));
this.value = this.previousValue = f.apply(null, this.sourceValues());
}
static isReactiveSource(x) {
return x instanceof Input || x instanceof Formula;
}
sourceValues() {
return this.sources.map(x => Formula.isReactiveSource(x) ? x.value : x);
}
update() {
let value = this.f.apply(null, this.sourceValues());
if (this.value !== value) {
this.previousValue = this.value;
this.value = value;
return true;
}
return false;
}
}
function input(initialValue) {
return new Input(initialValue);
}
function formula(f) {
return (...sources) => new Formula(f, sources);
}
let A = input(100),
B = input(200),
C = formula(console.log)(A, B);
console.log("Setting A and B in succession")
A.set(A.value + 1);
B.set(B.value + 1);
setTimeout(() => transaction(() => {
console.log("Setting A and B in a transaction")
A.set(A.value + 1);
B.set(B.value + 1);
}), 1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment