Skip to content

Commit

Permalink
going back to sll model; fork -> deref
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyraspopov committed Jul 4, 2024
1 parent 047b694 commit 072f25b
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 129 deletions.
4 changes: 2 additions & 2 deletions benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@preact/signals-core": "^1.5.0",
"@vue/reactivity": "^3.3.4",
"esbuild": "^0.19.4",
"inertial": "file:.."
"knockout": "^3.5.1",
"inertial": "file:..",
"knockout": "^3.5.1"
}
}
4 changes: 2 additions & 2 deletions inertial.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export type Scope = {
/** Update multiple signals at once before starting the update cycle. */
batch(fn: () => void): void;

/** Define temporary scope of signals that can be disposed separately. */
fork(fn: () => void): void;
/** Remove any reactive signal from the scope. */
deref(...fn: Array<Signal<any>>): void;

/** Dispose all scope's observables and effects. */
dispose(): void;
Expand Down
225 changes: 113 additions & 112 deletions inertial.js
Original file line number Diff line number Diff line change
@@ -1,101 +1,129 @@
const PROVIDER = 0b001;
const CONSUMER = 0b010;
const DISPOSER = 0b100;

export function ObservableScope(schedule = (cb) => cb()) {
let id = 0;
let head = { prev: null, next: null };
let tail = { prev: null, next: null };
(head.next = tail).prev = head;

/** @type {WeakSet<any> | null} */
let tracking = null;
let queue = new Set();
let wip = new Set();
let vertices = []; // vertices [(p0, c0), (p1, c1), ...]
let disposables = [];
let marking = [];
let future = [];
let flushing = false;

function signal(initial, equals = Object.is) {
let key = id++;
let node = { flag: PROVIDER, prev: tail.prev, next: tail };
tail.prev = tail.prev.next = node;
let current = initial;
return (value) => {
if (typeof value === "undefined") {
// reading
if (tracking != null) union(vertices, key, tracking);
if (tracking != null) tracking.add(node);
return current;
} else {
// writing
let val = typeof value === "function" ? value(current) : value;
if (!equals(current, val)) {
if (!equals(val, current)) {
current = val;
if (!wip.has(key)) queue.add(key);
schedule(digest);
if (!flushing) {
marking.push(node);
schedule(digest);
} else {
future.push(node);
}
}
}
};
}

function watch(fn) {
let clear;
let watcher = () => {
if (clear != null) clear();
clear = fn();
tracking = new WeakSet();
let clear = fn();
let node = {
flag: CONSUMER + DISPOSER,
tracking,
update() {
if (typeof clear === "function") clear();
clear = fn();
},
dispose() {
if (typeof clear === "function") clear();
clear = null;
(node.prev.next = node.next).prev = node.prev;
},
prev: tail.prev,
next: tail,
};
// capturing
tracking = watcher;
clear = fn();
tail.prev = tail.prev.next = node;
tracking = null;
let dispose = () => {
if (clear != null) clear();
clear = null;
for (let cursor = 0; cursor < vertices.length; ) {
if (vertices[cursor + 1] === watcher) {
vertices.splice(cursor, 2);
} else {
cursor += 2;
}
}
};
disposables.push(dispose);
return dispose;
return node.dispose;
}

function derive(get, equals = Object.is) {
let current;
let key = id++;
// capturing
tracking = () => {
let val = get();
if (!equals(current, val)) {
current = val;
wip.add(key);
}
tracking = new WeakSet();
let current = get();
let node = {
flag: PROVIDER + CONSUMER,
tracking,
update() {
let value = get();
if (!equals(value, current)) {
current = value;
marking.push(node);
}
},
prev: tail.prev,
next: tail,
};
current = get();
tail.prev = tail.prev.next = node;
tracking = null;
return (value) => {
if (typeof value === "undefined") {
// reading
if (tracking != null) union(vertices, key, tracking);
if (tracking != null) tracking.add(node);
return current;
} else {
// writing
let val = typeof value === "function" ? value(current) : value;
if (!equals(current, val)) {
if (!equals(val, current)) {
current = val;
if (!wip.has(key)) queue.add(key);
schedule(digest);
if (!flushing) {
marking.push(node);
schedule(digest);
} else {
future.push(node);
}
}
}
};
}

function observe(get, subscribe, equals = Object.is) {
let current = get();
let key = id++;
let clear = subscribe(() => {
// writing
let val = get();
if (!equals(current, val)) {
current = val;
if (!wip.has(key)) queue.add(key);
let clear;
let node = {
flag: PROVIDER + DISPOSER,
dispose() {
if (typeof clear === "function") clear();
clear = null;
(node.prev.next = node.next).prev = node.prev;
},
prev: tail.prev,
next: tail,
};
tail.prev = tail.prev.next = node;
clear = subscribe(() => {
let value = get();
if (!equals(value, current)) {
current = value;
marking.push(node);
schedule(digest);
}
});
disposables.push(clear);
return () => {
if (tracking != null) union(vertices, key, tracking);
if (tracking != null) tracking.add(node);
return current;
};
}
Expand All @@ -111,78 +139,51 @@ export function ObservableScope(schedule = (cb) => cb()) {
function batch(fn) {
let temp = schedule;
schedule = () => {};
// temporary measure since digest starts a cycle from marking[0] node
// which may not be the earliest node in a batch routine
marking = [head];
fn();
schedule = temp;
schedule(digest);
if (marking.length > 0) schedule(digest);
}

function fork(fn) {
let startId = id;
let currentDisposables = disposables;
disposables = [];
fn();
let newId = id;
let tempDisposables = disposables;
disposables = currentDisposables;
let clear = () => {
for (let fn of tempDisposables) fn();
for (let cursor = 0; cursor < vertices.length; ) {
if (vertices[cursor] >= startId && vertices[cursor] < newId) {
vertices.splice(cursor, 2);
} else {
cursor += 2;
}
}
};
let dispose = () => {
if (clear != null) clear();
clear = null;
function deref(...signals) {
tracking = {
add: (node) => {
if (node & DISPOSER) node.dispose();
(node.prev.next = node.next).prev = node.prev;
},
};
disposables.push(dispose);
return dispose;

for (let signal of signals) signal();

tracking = null;
}

function dispose() {
vertices = [];
for (let fn of disposables) fn();
let cursor = head;
while ((cursor = cursor.next) !== tail) {
if (cursor.flag & DISPOSER) cursor.dispose();
}
head = { prev: null, next: null };
tail = { prev: null, next: null };
(head.next = tail).prev = head;
}

function digest() {
while (queue.size > 0) {
let tmp = wip;
wip = queue;
queue = tmp;
tmp.clear();
for (
let cursor = 0, used = new WeakSet(), q = wip, fn, p;
cursor < vertices.length;
cursor += 2
) {
if (vertices[cursor] === p || q.has(vertices[cursor])) {
p = vertices[cursor];
fn = vertices[cursor + 1];
if (!used.has(fn)) {
used.add(fn);
fn();
}
}
flushing = true;
let cursor = marking[0];
while ((cursor = cursor.next) !== tail) {
if (cursor.flag & CONSUMER && marking.some((node) => cursor.tracking.has(node))) {
cursor.update();
}
}
wip.clear();
flushing = false;
if (future.length > 0) {
marking = [future.shift()];
schedule(digest);
} else marking = [];
}

return { signal, watch, derive, observe, peek, batch, fork, dispose };
}

function union(vs, pk, ck) {
let mid,
lo = 0,
hi = vs.length;
while (lo < hi) {
mid = (lo + hi) >>> 1;
mid -= mid % 2;
if (vs[mid] <= pk) lo = mid + 2;
else hi = mid;
}
vs.splice(lo, 0, pk, ck);
return { signal, watch, derive, observe, peek, batch, deref, dispose };
}
21 changes: 8 additions & 13 deletions inertial.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,30 +432,25 @@ test("nesting", () => {
equal(get.mock.callCount(), 2);
});

test("fork", () => {
test("deref", () => {
let os = ObservableScope();
let a = os.signal(1);
let b = os.signal(2);
let temp = {};
let getC;
let dispose = os.fork(() => {
getC = mock.fn(() => a() + b());
temp.c = os.signal();
os.watch(() => temp.c(getC()));
});
let d = os.derive(() => temp.c() * 2);
let getC = mock.fn(() => a() + b());
let c = os.derive(getC);
let d = os.derive(() => c() * 2);
let e = os.derive(() => a() + b());
equal(temp.c(), 3);
equal(c(), 3);
equal(d(), 6);
equal(e(), 3);
a(2);
equal(temp.c(), 4);
equal(c(), 4);
equal(d(), 8);
equal(e(), 4);
dispose();
os.deref(c);
a(3);
equal(getC.mock.callCount(), 2);
equal(temp.c(), 4);
equal(c(), 4);
equal(d(), 8);
equal(e(), 5);
});

0 comments on commit 072f25b

Please sign in to comment.