Skip to content

Commit

Permalink
peek, batch
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyraspopov committed Oct 13, 2023
1 parent f05327c commit 4b9a18b
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 12 deletions.
4 changes: 2 additions & 2 deletions benchmark/package-size.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { buildSync } from "esbuild";
import { gzipSync, brotliCompressSync } from "node:zlib";

console.log("reactivity");
console.log("dataflow");
build(`
import { ObservableScope } from "../reactivity.js";
import { ObservableScope } from "../dataflow.js";
export function vm() {
let os = ObservableScope();
Expand Down
12 changes: 12 additions & 0 deletions dataflow.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
export type Signal<Value> = {
/** Read signal value */
(): Value;
/** Write new value */
(value: Value): void;
/** Update signal value */
(update: (value: Value) => Value): void;
};

export type Scope = {
/** Subscribe to external source of values */
observe: <Value>(
get: () => Value,
subscribe: (cb: () => void) => () => void,
equals?: (a: Value, b: Value) => boolean,
) => Signal<Value>;
/** Create a signal */
signal: <Value>(value?: Value, equals?: (a: Value, b: Value) => boolean) => Signal<Value>;
/** */
derive: <Value>(get: () => Value, equals?: (a: Value, b: Value) => boolean) => Signal<Value>;
/** */
watch: (cb: (() => void) | (() => () => void)) => void;
/** Get signal values inside derive/watch functions without tracking dependencies */
peek: <Value>(get: () => Value) => Value;
/** Update multiple signals at once before performing the update cycle */
batch: (fn: () => void) => void;
/** Dispose all scope's observables and effects */
dispose: () => void;
};

Expand Down
32 changes: 24 additions & 8 deletions dataflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function ObservableScope(schedule = (cb) => cb()) {
let current = get();
let key = sets.push();
let clear = subscribe(() => {
// writing
let val = get();
if (!equals(current, val)) {
current = val;
Expand All @@ -97,6 +98,22 @@ export function ObservableScope(schedule = (cb) => cb()) {
};
}

function peek(get) {
let temp = tracking;
tracking = null;
let result = get();
tracking = temp;
return result;
}

function batch(fn) {
let temp = schedule;
schedule = () => {};
fn();
schedule = temp;
schedule(digest);
}

function dispose() {
for (let cb of cbs.values()) cb("dispose");
}
Expand All @@ -114,28 +131,21 @@ export function ObservableScope(schedule = (cb) => cb()) {
wip = null;
}

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

function DisjointSet() {
let cursor = 0;
let parents = new Uint32Array(32);
let ranks = new Uint32Array(32);

function grow(v) {
let n = new Uint32Array(v.length + 32);
n.set(v);
return n;
}

function push() {
let x = cursor++;
if (x === parents.length) {
parents = grow(parents);
ranks = grow(ranks);
}
parents[x] = x;
ranks[x] = 0;
return x;
}

Expand Down Expand Up @@ -193,3 +203,9 @@ function DisjointSet() {
union,
};
}

function grow(v) {
let n = new Uint32Array(v.length + 32);
n.set(v);
return n;
}
53 changes: 51 additions & 2 deletions dataflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,55 @@ test("observe + watch", () => {
equal(cleanup.mock.callCount(), 2);
});

test.todo("peek");
test("signal + peek + watch", () => {
let os = ObservableScope();

let valueA = os.signal(13);
let valueB = os.derive(() => valueA() * 2);
let valueC = os.signal(false);

let watcherA = mock.fn();
let watcherB = mock.fn();

os.watch(() => {
watcherA(os.peek(() => valueA() + valueB()));
watcherB(valueC());
});

let args = (mock) => mock.calls.map((call) => call.arguments);
deepEqual(args(watcherA.mock), [[39]]);
deepEqual(args(watcherB.mock), [[false]]);

test.todo("batch");
valueA(2);
deepEqual(args(watcherA.mock), [[39]]);
deepEqual(args(watcherB.mock), [[false]]);

valueC(true);
deepEqual(args(watcherA.mock), [[39], [6]]);
deepEqual(args(watcherB.mock), [[false], [true]]);
});

test("signal + derive + batch", () => {
let os = ObservableScope();

let valueA = os.signal(13);
let valueB = os.signal(10);
let compute = mock.fn((v) => v);
let valueC = os.derive(() => compute(valueA() * valueB()));

let args = (mock) => mock.calls.map((call) => call.arguments);
deepEqual(args(compute.mock), [[130]]);
equal(valueC(), 130);

valueA(14);
valueB(1);
deepEqual(args(compute.mock), [[130], [140], [14]]);
equal(valueC(), 14);

os.batch(() => {
valueA(15);
valueB(10);
});
deepEqual(args(compute.mock), [[130], [140], [14], [150]]);
equal(valueC(), 150);
});

0 comments on commit 4b9a18b

Please sign in to comment.