bireactive 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation/anim.js +4 -0
- package/dist/automerge/doc-cell.d.ts +20 -0
- package/dist/automerge/doc-cell.js +80 -0
- package/dist/automerge/index.d.ts +3 -0
- package/dist/automerge/index.js +12 -0
- package/dist/automerge/reconcile.d.ts +5 -0
- package/dist/automerge/reconcile.js +63 -0
- package/dist/coll.d.ts +7 -7
- package/dist/core/_counts.d.ts +48 -0
- package/dist/core/_counts.js +58 -0
- package/dist/core/cell.d.ts +182 -123
- package/dist/core/cell.js +1140 -721
- package/dist/core/debug.d.ts +25 -0
- package/dist/core/debug.js +121 -0
- package/dist/core/index.d.ts +9 -14
- package/dist/core/index.js +9 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +14 -9
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/index.d.ts +1 -0
- package/dist/core/lenses/index.js +1 -0
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/snap.d.ts +18 -0
- package/dist/core/lenses/snap.js +145 -0
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/optic.d.ts +13 -0
- package/dist/core/optic.js +44 -0
- package/dist/core/optics.d.ts +10 -0
- package/dist/core/optics.js +30 -0
- package/dist/core/store.d.ts +10 -0
- package/dist/core/store.js +85 -0
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/audio.js +4 -5
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/canvas.js +15 -18
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/str.js +8 -8
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +51 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.js +5 -0
- package/dist/jsx-runtime.d.ts +54 -0
- package/dist/jsx-runtime.js +219 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/drag-behaviors.d.ts +56 -0
- package/dist/shapes/drag-behaviors.js +102 -0
- package/dist/shapes/drag-spec.d.ts +52 -0
- package/dist/shapes/drag-spec.js +112 -0
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/index.d.ts +3 -1
- package/dist/shapes/index.js +3 -1
- package/dist/shapes/interaction.d.ts +2 -3
- package/dist/shapes/interaction.js +77 -56
- package/dist/shapes/label.js +7 -1
- package/dist/shapes/layout.d.ts +47 -1
- package/dist/shapes/layout.js +60 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/web/diagram.js +2 -2
- package/package.json +24 -2
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// snap.ts — the pointer math the `d` drag algebra (shapes/drag-spec.ts) and the
|
|
2
|
+
// demos build on. Candidate positions are live layout cells, so there's no
|
|
3
|
+
// speculative re-render to recover them:
|
|
4
|
+
//
|
|
5
|
+
// • hullWeights — barycentric weights of a point in the convex hull of K
|
|
6
|
+
// targets (closed form for K ≤ 3, Frank–Wolfe for K > 3). Powers
|
|
7
|
+
// `d.between`'s blend and any pointer-in-hull interpolation.
|
|
8
|
+
// • nearestIndex — the candidate nearest the pointer, with hysteresis. The
|
|
9
|
+
// "stickiness" lives in the lens complement (the sanctioned home for
|
|
10
|
+
// path-dependence), so reads stay pure.
|
|
11
|
+
import { lens, SKIP } from "../cell.js";
|
|
12
|
+
const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
13
|
+
const dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
14
|
+
const dist2 = (a, b) => {
|
|
15
|
+
const dx = a.x - b.x;
|
|
16
|
+
const dy = a.y - b.y;
|
|
17
|
+
return dx * dx + dy * dy;
|
|
18
|
+
};
|
|
19
|
+
// ── convex-hull barycentric weights ─────────────────────────────────
|
|
20
|
+
/** Project `q` onto segment p0→p1; weights `[1−t, t]`, t clamped to [0,1]. */
|
|
21
|
+
function segmentWeights(q, p0, p1) {
|
|
22
|
+
const d = sub(p1, p0);
|
|
23
|
+
const len2 = dot(d, d);
|
|
24
|
+
if (len2 < 1e-18)
|
|
25
|
+
return [0.5, 0.5];
|
|
26
|
+
const t = Math.max(0, Math.min(1, dot(sub(q, p0), d) / len2));
|
|
27
|
+
return [1 - t, t];
|
|
28
|
+
}
|
|
29
|
+
/** Barycentric weights of `q` in triangle (p0,p1,p2), CLAMPED to the
|
|
30
|
+
* triangle: inside → the true coords; outside → the nearest point on the
|
|
31
|
+
* hull (an edge projection or a vertex), so the blend never extrapolates. */
|
|
32
|
+
function triangleWeights(q, p0, p1, p2) {
|
|
33
|
+
const v0 = sub(p1, p0);
|
|
34
|
+
const v1 = sub(p2, p0);
|
|
35
|
+
const v2 = sub(q, p0);
|
|
36
|
+
const d00 = dot(v0, v0);
|
|
37
|
+
const d01 = dot(v0, v1);
|
|
38
|
+
const d11 = dot(v1, v1);
|
|
39
|
+
const d20 = dot(v2, v0);
|
|
40
|
+
const d21 = dot(v2, v1);
|
|
41
|
+
const denom = d00 * d11 - d01 * d01;
|
|
42
|
+
if (Math.abs(denom) > 1e-18) {
|
|
43
|
+
const b1 = (d11 * d20 - d01 * d21) / denom;
|
|
44
|
+
const b2 = (d00 * d21 - d01 * d20) / denom;
|
|
45
|
+
const b0 = 1 - b1 - b2;
|
|
46
|
+
if (b0 >= -1e-9 && b1 >= -1e-9 && b2 >= -1e-9) {
|
|
47
|
+
const s = b0 + b1 + b2;
|
|
48
|
+
return [b0 / s, b1 / s, b2 / s];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Outside (or degenerate): nearest point on the three edges.
|
|
52
|
+
const e01 = segmentWeights(q, p0, p1);
|
|
53
|
+
const e12 = segmentWeights(q, p1, p2);
|
|
54
|
+
const e20 = segmentWeights(q, p2, p0);
|
|
55
|
+
const at01 = { x: p0.x * e01[0] + p1.x * e01[1], y: p0.y * e01[0] + p1.y * e01[1] };
|
|
56
|
+
const at12 = { x: p1.x * e12[0] + p2.x * e12[1], y: p1.y * e12[0] + p2.y * e12[1] };
|
|
57
|
+
const at20 = { x: p2.x * e20[0] + p0.x * e20[1], y: p2.y * e20[0] + p0.y * e20[1] };
|
|
58
|
+
const c = [
|
|
59
|
+
[dist2(q, at01), [e01[0], e01[1], 0]],
|
|
60
|
+
[dist2(q, at12), [0, e12[0], e12[1]]],
|
|
61
|
+
[dist2(q, at20), [e20[1], 0, e20[0]]],
|
|
62
|
+
];
|
|
63
|
+
c.sort((a, b) => a[0] - b[0]);
|
|
64
|
+
return c[0][1];
|
|
65
|
+
}
|
|
66
|
+
/** Frank–Wolfe projection of `q` onto the convex hull of `pts` in
|
|
67
|
+
* barycentric coordinates (used for K > 3). Minimises |Σ wᵢ·pᵢ − q|² over
|
|
68
|
+
* the simplex; O(K·iters), plenty fast for the handful of targets a drag
|
|
69
|
+
* ever offers. */
|
|
70
|
+
function hullProjectWeights(q, pts, iters = 60) {
|
|
71
|
+
const K = pts.length;
|
|
72
|
+
const w = new Array(K).fill(1 / K);
|
|
73
|
+
for (let t = 0; t < iters; t++) {
|
|
74
|
+
let cx = 0;
|
|
75
|
+
let cy = 0;
|
|
76
|
+
for (let i = 0; i < K; i++) {
|
|
77
|
+
cx += w[i] * pts[i].x;
|
|
78
|
+
cy += w[i] * pts[i].y;
|
|
79
|
+
}
|
|
80
|
+
const rx = cx - q.x;
|
|
81
|
+
const ry = cy - q.y;
|
|
82
|
+
let best = 0;
|
|
83
|
+
let bestG = Number.POSITIVE_INFINITY;
|
|
84
|
+
for (let i = 0; i < K; i++) {
|
|
85
|
+
const g = rx * pts[i].x + ry * pts[i].y;
|
|
86
|
+
if (g < bestG) {
|
|
87
|
+
bestG = g;
|
|
88
|
+
best = i;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const gamma = 2 / (t + 2);
|
|
92
|
+
for (let i = 0; i < K; i++)
|
|
93
|
+
w[i] = w[i] * (1 - gamma);
|
|
94
|
+
w[best] = w[best] + gamma;
|
|
95
|
+
}
|
|
96
|
+
return w;
|
|
97
|
+
}
|
|
98
|
+
/** Convex-hull barycentric weights of `q` over `pts` (Σ = 1, all ≥ 0,
|
|
99
|
+
* clamped to the hull). Closed form for K ≤ 3; Frank–Wolfe for K > 3. */
|
|
100
|
+
export function hullWeights(q, pts) {
|
|
101
|
+
const K = pts.length;
|
|
102
|
+
if (K === 0)
|
|
103
|
+
return [];
|
|
104
|
+
if (K === 1)
|
|
105
|
+
return [1];
|
|
106
|
+
if (K === 2)
|
|
107
|
+
return segmentWeights(q, pts[0], pts[1]);
|
|
108
|
+
if (K === 3)
|
|
109
|
+
return triangleWeights(q, pts[0], pts[1], pts[2]);
|
|
110
|
+
return hullProjectWeights(q, pts);
|
|
111
|
+
}
|
|
112
|
+
function pick(sources, prev, sticky) {
|
|
113
|
+
const p = sources[0];
|
|
114
|
+
let best = -1;
|
|
115
|
+
let bestD = Number.POSITIVE_INFINITY;
|
|
116
|
+
for (let i = 1; i < sources.length; i++) {
|
|
117
|
+
const d = dist2(sources[i], p);
|
|
118
|
+
if (d < bestD) {
|
|
119
|
+
bestD = d;
|
|
120
|
+
best = i - 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (sticky > 0 && prev >= 0 && prev + 1 < sources.length) {
|
|
124
|
+
const prevD = Math.sqrt(dist2(sources[prev + 1], p));
|
|
125
|
+
if (prevD - Math.sqrt(bestD) < sticky)
|
|
126
|
+
return prev;
|
|
127
|
+
}
|
|
128
|
+
return best;
|
|
129
|
+
}
|
|
130
|
+
/** Index of the candidate nearest `pointer`, with hysteresis. Read-only
|
|
131
|
+
* selection: the stickiness state lives in the lens complement (the
|
|
132
|
+
* sanctioned place for path-dependence), so reads stay pure. */
|
|
133
|
+
export function nearestIndex(pointer, candidates, opts = {}) {
|
|
134
|
+
const sticky = opts.sticky ?? 0;
|
|
135
|
+
const parents = [pointer, ...candidates];
|
|
136
|
+
return lens(parents, {
|
|
137
|
+
init: (sources) => ({ index: pick(sources, -1, 0) }),
|
|
138
|
+
step: (sources, c) => ({ index: pick(sources, c.index, sticky) }),
|
|
139
|
+
fwd: (_sources, c) => c.index,
|
|
140
|
+
bwd: (_t, sources, c) => ({
|
|
141
|
+
updates: sources.map(() => SKIP),
|
|
142
|
+
complement: c,
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// centroid.value = { x: 100, y: 50 }; // typed
|
|
15
15
|
//
|
|
16
16
|
// `bundle()` is the 1→M case: `factor()` over a single typed source.
|
|
17
|
+
import { SKIP } from "../index.js";
|
|
17
18
|
import { solveSPD } from "../linalg.js";
|
|
18
19
|
function getPack(cell) {
|
|
19
20
|
const ctor = cell.constructor;
|
|
@@ -163,7 +164,7 @@ export function factor(inputs, outputs, opts = {}) {
|
|
|
163
164
|
}
|
|
164
165
|
// 6. Solve A · k = δy in place (LDLᵀ; `dy` is overwritten with k).
|
|
165
166
|
if (!solveSPD(A, dy, M)) {
|
|
166
|
-
return vals.map(() =>
|
|
167
|
+
return vals.map(() => SKIP);
|
|
167
168
|
}
|
|
168
169
|
// 7. δx = W J^T k, applied to flatIn → produces new flat input vector.
|
|
169
170
|
// Then unpack per-input to typed updates.
|
|
@@ -187,7 +188,7 @@ export function factor(inputs, outputs, opts = {}) {
|
|
|
187
188
|
}
|
|
188
189
|
updates[k] = anyChange
|
|
189
190
|
? inputPacks[k].write(flatIn, baseOff)
|
|
190
|
-
:
|
|
191
|
+
: SKIP;
|
|
191
192
|
}
|
|
192
193
|
return updates;
|
|
193
194
|
};
|
|
@@ -211,7 +212,7 @@ export function factor(inputs, outputs, opts = {}) {
|
|
|
211
212
|
for (let it = 0; it < maxIters; it++) {
|
|
212
213
|
const updates = computeBwd(idx, target, cur);
|
|
213
214
|
for (let i = 0; i < cur.length; i++) {
|
|
214
|
-
if (updates[i] !==
|
|
215
|
+
if (updates[i] !== SKIP)
|
|
215
216
|
cur[i] = updates[i];
|
|
216
217
|
}
|
|
217
218
|
outPack.read(spec.fwd(cur), currentBuf, 0);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Optic } from "./cell.js";
|
|
2
|
+
/** Build an optic from a forward and a backward. A 2-arg `put(b, a)` reads the
|
|
3
|
+
* source; a 1-arg `put(b)` reconstructs it (and is treated as an `iso`). */
|
|
4
|
+
export declare function optic<A, B>(get: (a: A) => B, put: (b: B, a: A) => A): Optic<A, B>;
|
|
5
|
+
/** A lossless, source-independent optic (an isomorphism): `to`/`from` invert. */
|
|
6
|
+
export declare function iso<A, B>(to: (a: A) => B, from: (b: B) => A): Optic<A, B>;
|
|
7
|
+
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
8
|
+
export declare function atKey<T, K extends keyof T>(key: K): Optic<T, T[K]>;
|
|
9
|
+
/** Compose optics left-to-right into one: `compose(a, b, c)` is `a` then `b` then
|
|
10
|
+
* `c`. Typed for up to three; falls back to `Optic<unknown, unknown>` beyond. */
|
|
11
|
+
export declare function compose<A, B>(a: Optic<A, B>): Optic<A, B>;
|
|
12
|
+
export declare function compose<A, B, C>(a: Optic<A, B>, b: Optic<B, C>): Optic<A, C>;
|
|
13
|
+
export declare function compose<A, B, C, D>(a: Optic<A, B>, b: Optic<B, C>, c: Optic<C, D>): Optic<A, D>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// optic.ts — lenses as first-class values, independent of any `Cell`.
|
|
2
|
+
//
|
|
3
|
+
// A `Cell` lens binds a transform to a specific source. An `Optic<A, B>` is that
|
|
4
|
+
// transform *unbound* — a pair of pure functions you can compose, store, and
|
|
5
|
+
// hand around, then apply to a source with `cell.through(optic)` (≡ `lens(cell,
|
|
6
|
+
// o.get, o.put)`). Composition is ordinary lens composition: `(f ∘ g).put(c, a) =
|
|
7
|
+
// f.put(g.put(c, f.get(a)), a)`, so the inner source is reconstructed from `a` on
|
|
8
|
+
// every back-write. An `iso` is the lossless special case whose `put` ignores the
|
|
9
|
+
// source (`readsSource = false`), letting `through` bind a cheaper 1-arg backward.
|
|
10
|
+
//
|
|
11
|
+
// This is deliberately the pure/stateless slice of the lens algebra: no
|
|
12
|
+
// complement, no effects. Stateful and effectful optics-as-values are future work
|
|
13
|
+
// (see _notes); for now reach for `lens(parent, spec)` when you need a complement.
|
|
14
|
+
//
|
|
15
|
+
// The `Optic` type lives in cell.ts (so cell.ts stays import-free and its
|
|
16
|
+
// `Cell.through` can name it); this module is the constructor/algebra surface.
|
|
17
|
+
function make(get, put, readsSource) {
|
|
18
|
+
return {
|
|
19
|
+
get,
|
|
20
|
+
put,
|
|
21
|
+
readsSource,
|
|
22
|
+
through(next) {
|
|
23
|
+
// Composed backward reconstructs the inner B from the outer A, so it always
|
|
24
|
+
// reads the source regardless of either side's own `readsSource`.
|
|
25
|
+
return make(a => next.get(get(a)), (c, a) => put(next.put(c, get(a)), a), true);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Build an optic from a forward and a backward. A 2-arg `put(b, a)` reads the
|
|
30
|
+
* source; a 1-arg `put(b)` reconstructs it (and is treated as an `iso`). */
|
|
31
|
+
export function optic(get, put) {
|
|
32
|
+
return make(get, put, put.length >= 2);
|
|
33
|
+
}
|
|
34
|
+
/** A lossless, source-independent optic (an isomorphism): `to`/`from` invert. */
|
|
35
|
+
export function iso(to, from) {
|
|
36
|
+
return make(to, b => from(b), false);
|
|
37
|
+
}
|
|
38
|
+
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
39
|
+
export function atKey(key) {
|
|
40
|
+
return make(t => t[key], (v, t) => ({ ...t, [key]: v }), true);
|
|
41
|
+
}
|
|
42
|
+
export function compose(...optics) {
|
|
43
|
+
return optics.reduce((a, b) => a.through(b));
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Cell, type Read, type Writable } from "./cell.js";
|
|
2
|
+
/** Writable field view of `c.value[key]` (spread-replace put). A read-only
|
|
3
|
+
* parent yields a read-only view. */
|
|
4
|
+
export declare function at<T, K extends keyof T>(c: Writable<Cell<T>>, key: K): Writable<Cell<T[K]>>;
|
|
5
|
+
export declare function at<T, K extends keyof T>(c: Read<T>, key: K): Cell<T[K]>;
|
|
6
|
+
/** Lens view of every field, lazily and memoized — `const { r, g, b } =
|
|
7
|
+
* fields(rgb)` yields one writable `at` per key. */
|
|
8
|
+
export declare function fields<T extends object>(c: Writable<Cell<T>>): {
|
|
9
|
+
[K in keyof T]-?: Writable<Cell<T[K]>>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// optics.ts — plain-record field optics over a `Cell<T>`.
|
|
2
|
+
//
|
|
3
|
+
// `fieldOf` / `fieldLens` (cell.ts) project a value-class field and need the
|
|
4
|
+
// field's Cell constructor, so `Vec.x` comes back as a typed `Num` carrying its
|
|
5
|
+
// domain methods. For a plain record you just want `Cell<T[K]>` with the same
|
|
6
|
+
// spread-replace put — `at` / `fields` supply that with full key inference and
|
|
7
|
+
// no constructor argument. Both are thin sugar over `fieldOf` with the base
|
|
8
|
+
// `Cell` as the result type.
|
|
9
|
+
import { Cell, fieldOf } from "./cell.js";
|
|
10
|
+
export function at(c, key) {
|
|
11
|
+
const ctor = Cell;
|
|
12
|
+
return fieldOf(c, key, ctor);
|
|
13
|
+
}
|
|
14
|
+
/** Lens view of every field, lazily and memoized — `const { r, g, b } =
|
|
15
|
+
* fields(rgb)` yields one writable `at` per key. */
|
|
16
|
+
export function fields(c) {
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
return new Proxy(Object.create(null), {
|
|
19
|
+
get(_t, key) {
|
|
20
|
+
if (typeof key === "symbol")
|
|
21
|
+
return undefined;
|
|
22
|
+
let v = cache.get(key);
|
|
23
|
+
if (v === undefined) {
|
|
24
|
+
v = at(c, key);
|
|
25
|
+
cache.set(key, v);
|
|
26
|
+
}
|
|
27
|
+
return v;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Cell, Writable } from "./cell.js";
|
|
2
|
+
/** Deep store view: the cell itself, plus a `Store` per object field. Primitives
|
|
3
|
+
* and functions bottom out at the plain `Writable<Cell<T>>`. */
|
|
4
|
+
export type Store<T> = Writable<Cell<T>> & (T extends readonly any[] ? unknown : T extends (...args: any[]) => any ? unknown : T extends object ? {
|
|
5
|
+
[K in keyof T]-?: Store<T[K]>;
|
|
6
|
+
} : unknown);
|
|
7
|
+
/** Deep, lens-backed store view of `cell`. Field access returns a nested `Store`;
|
|
8
|
+
* write through `.value` at any depth. Works over any writable cell, including a
|
|
9
|
+
* lens, so the store stays one source of truth with the rest of the graph. */
|
|
10
|
+
export declare function store<T>(cell: Writable<Cell<T>>): Store<T>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// store.ts — a lens-backed deep store proxy over a `Cell`.
|
|
2
|
+
//
|
|
3
|
+
// Signal libraries grow a separate `store` primitive for nested objects:
|
|
4
|
+
// `store.user.name` reads a path, `setStore(...)` writes one. Here that's just
|
|
5
|
+
// field lenses (`at`) under a recursive proxy — no second reactive primitive, no
|
|
6
|
+
// fine-grained store engine. `store(cell).a.b` is a `Cell` (a chain of `at`
|
|
7
|
+
// lenses), so it composes with everything else: read `.value`, write `.value`,
|
|
8
|
+
// pass it to a component, bind it in JSX. Writes are spread-replace puts straight
|
|
9
|
+
// back to the root cell, so the whole tree stays one source of truth.
|
|
10
|
+
//
|
|
11
|
+
// Navigation yields Stores; the leaf is written through `.value`
|
|
12
|
+
// (`store.user.name.value = "x"`). A property whose name collides with the cell
|
|
13
|
+
// surface below (`value`, `peek`, `lens`, `derive`, `merge`, `through`) resolves
|
|
14
|
+
// to the cell member, not a field — drop to `at(cell, key)` for such a field.
|
|
15
|
+
import { at } from "./optics.js";
|
|
16
|
+
// Cell members forwarded to the underlying cell rather than treated as a field,
|
|
17
|
+
// plus the object protocol methods JS itself may touch.
|
|
18
|
+
const FORWARD = new Set([
|
|
19
|
+
"value",
|
|
20
|
+
"peek",
|
|
21
|
+
"lens",
|
|
22
|
+
"derive",
|
|
23
|
+
"merge",
|
|
24
|
+
"through",
|
|
25
|
+
"toString",
|
|
26
|
+
"valueOf",
|
|
27
|
+
"toJSON",
|
|
28
|
+
"constructor",
|
|
29
|
+
]);
|
|
30
|
+
// One proxy per cell, so `store(c).a === store(c).a` and child stores are stable.
|
|
31
|
+
const wrapped = new WeakMap();
|
|
32
|
+
function wrap(cell) {
|
|
33
|
+
const hit = wrapped.get(cell);
|
|
34
|
+
if (hit !== undefined)
|
|
35
|
+
return hit;
|
|
36
|
+
// Per-key field lenses and their child stores, both memoized.
|
|
37
|
+
const lensFor = new Map();
|
|
38
|
+
const fieldLens = (key) => {
|
|
39
|
+
let l = lensFor.get(key);
|
|
40
|
+
if (l === undefined) {
|
|
41
|
+
l = at(cell, key);
|
|
42
|
+
lensFor.set(key, l);
|
|
43
|
+
}
|
|
44
|
+
return l;
|
|
45
|
+
};
|
|
46
|
+
const childStores = new Map();
|
|
47
|
+
const proxy = new Proxy(cell, {
|
|
48
|
+
get(target, key) {
|
|
49
|
+
if (typeof key === "symbol" || FORWARD.has(key)) {
|
|
50
|
+
const v = target[key];
|
|
51
|
+
return typeof v === "function" ? v.bind(target) : v;
|
|
52
|
+
}
|
|
53
|
+
if (key === "then")
|
|
54
|
+
return undefined; // never a thenable
|
|
55
|
+
let s = childStores.get(key);
|
|
56
|
+
if (s === undefined) {
|
|
57
|
+
s = wrap(fieldLens(key));
|
|
58
|
+
childStores.set(key, s);
|
|
59
|
+
}
|
|
60
|
+
return s;
|
|
61
|
+
},
|
|
62
|
+
set(target, key, value) {
|
|
63
|
+
if (typeof key === "symbol" || FORWARD.has(key)) {
|
|
64
|
+
target[key] = value;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
fieldLens(key).value = value;
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
has(target, key) {
|
|
71
|
+
if (typeof key === "symbol" || FORWARD.has(key))
|
|
72
|
+
return true;
|
|
73
|
+
const v = target.peek();
|
|
74
|
+
return typeof v === "object" && v !== null && key in v;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
wrapped.set(cell, proxy);
|
|
78
|
+
return proxy;
|
|
79
|
+
}
|
|
80
|
+
/** Deep, lens-backed store view of `cell`. Field access returns a nested `Store`;
|
|
81
|
+
* write through `.value` at any depth. Works over any writable cell, including a
|
|
82
|
+
* lens, so the store stays one source of truth with the rest of the graph. */
|
|
83
|
+
export function store(cell) {
|
|
84
|
+
return wrap(cell);
|
|
85
|
+
}
|
package/dist/core/traits.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export type TraitKey = keyof TraitDict<unknown>;
|
|
|
49
49
|
* function spring<T>(sig: Traits<T, "linear" | "metric">, target: Val<T>)
|
|
50
50
|
* function tween<T>(sig: Traits<T, "lerp">, target: T, dur: Val<number>) */
|
|
51
51
|
export type Traits<T, K extends TraitKey = never> = {
|
|
52
|
+
/** @internal Phantom slot; never accessed at runtime. */
|
|
52
53
|
readonly _t: {
|
|
53
54
|
[P in K]-?: NonNullable<TraitDict<T>[P]>;
|
|
54
55
|
} & TraitDict<T>;
|
|
@@ -76,16 +76,15 @@ export class Audio extends Cell {
|
|
|
76
76
|
normalize(target = 1) {
|
|
77
77
|
const tf = reader(target);
|
|
78
78
|
const self = this;
|
|
79
|
-
return Audio.lens(
|
|
80
|
-
init:
|
|
81
|
-
|
|
82
|
-
fwd: ([s]) => {
|
|
79
|
+
return Audio.lens(self, {
|
|
80
|
+
init: s => peak(s),
|
|
81
|
+
fwd: s => {
|
|
83
82
|
const p = peak(s);
|
|
84
83
|
return p === 0 ? s : scaled(s, tf() / p);
|
|
85
84
|
},
|
|
86
85
|
bwd: (view, _src, c) => {
|
|
87
86
|
const t = tf();
|
|
88
|
-
return {
|
|
87
|
+
return { update: t === 0 ? view : scaled(view, c / t), complement: c };
|
|
89
88
|
},
|
|
90
89
|
});
|
|
91
90
|
}
|
package/dist/core/values/box.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Invertibles (`add`, `sub`, `scale`, `expand`) return `: this` and ride
|
|
4
4
|
// on `Cell#lens(fwd, bwd)`. Chained calls compose into a lens chain.
|
|
5
5
|
import { tween } from "../../animation/index.js";
|
|
6
|
-
import { Cell, cachedDerive,
|
|
6
|
+
import { Cell, cachedDerive, fieldLens, isReadonly, lazy, reader, readNow, SKIP, } from "../cell.js";
|
|
7
7
|
import { Bool } from "./bool.js";
|
|
8
8
|
import { Num, num } from "./num.js";
|
|
9
9
|
import { Vec } from "./vec.js";
|
|
@@ -138,24 +138,24 @@ export class Box extends Cell {
|
|
|
138
138
|
return mk([this, p], vals => contains(vals[0], vals[1]), (target, vals) => {
|
|
139
139
|
const [b, v] = vals;
|
|
140
140
|
if (contains(b, v) === target)
|
|
141
|
-
return [
|
|
142
|
-
return [
|
|
141
|
+
return [SKIP, SKIP];
|
|
142
|
+
return [SKIP, target ? clampToBox(v, b) : ejectFromBox(v, b)];
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
return Bool.derive(() => contains(this.value, readNow(p)));
|
|
147
147
|
}
|
|
148
148
|
get x() {
|
|
149
|
-
return
|
|
149
|
+
return fieldLens(this, "x", Num);
|
|
150
150
|
}
|
|
151
151
|
get y() {
|
|
152
|
-
return
|
|
152
|
+
return fieldLens(this, "y", Num);
|
|
153
153
|
}
|
|
154
154
|
get w() {
|
|
155
|
-
return
|
|
155
|
+
return fieldLens(this, "w", Num);
|
|
156
156
|
}
|
|
157
157
|
get h() {
|
|
158
|
-
return
|
|
158
|
+
return fieldLens(this, "h", Num);
|
|
159
159
|
}
|
|
160
160
|
get area() {
|
|
161
161
|
return cachedDerive(this, "area", Num, b => b.w * b.h);
|
|
@@ -213,21 +213,20 @@ export class Canvas extends Cell {
|
|
|
213
213
|
return c;
|
|
214
214
|
};
|
|
215
215
|
const self = this;
|
|
216
|
-
return Canvas.lens(
|
|
217
|
-
init:
|
|
218
|
-
|
|
219
|
-
fwd: ([s]) => {
|
|
216
|
+
return Canvas.lens(self, {
|
|
217
|
+
init: s => chromaOf(s),
|
|
218
|
+
fwd: s => {
|
|
220
219
|
const out = sf(s.w, s.h);
|
|
221
220
|
pass(LUMA, out, x => x.tex("u_s", 0, s.tex));
|
|
222
221
|
return stamp(out.tex, s.w, s.h);
|
|
223
222
|
},
|
|
224
|
-
bwd: (target,
|
|
223
|
+
bwd: (target, s, c) => {
|
|
225
224
|
const out = sb(s.w, s.h);
|
|
226
225
|
pass(RECOLOR, out, x => {
|
|
227
226
|
x.tex("u_t", 0, target.tex);
|
|
228
227
|
x.tex("u_c", 1, c.tex);
|
|
229
228
|
});
|
|
230
|
-
return {
|
|
229
|
+
return { update: stamp(out.tex, s.w, s.h), complement: c };
|
|
231
230
|
},
|
|
232
231
|
});
|
|
233
232
|
}
|
|
@@ -244,21 +243,20 @@ export class Canvas extends Cell {
|
|
|
244
243
|
return c;
|
|
245
244
|
};
|
|
246
245
|
const self = this;
|
|
247
|
-
return Canvas.lens(
|
|
248
|
-
init:
|
|
249
|
-
|
|
250
|
-
fwd: ([s]) => {
|
|
246
|
+
return Canvas.lens(self, {
|
|
247
|
+
init: s => lumaOf(s),
|
|
248
|
+
fwd: s => {
|
|
251
249
|
const out = sf(s.w, s.h);
|
|
252
250
|
pass(CHROMA_VIEW, out, x => x.tex("u_s", 0, s.tex));
|
|
253
251
|
return stamp(out.tex, s.w, s.h);
|
|
254
252
|
},
|
|
255
|
-
bwd: (target,
|
|
253
|
+
bwd: (target, s, c) => {
|
|
256
254
|
const out = sb(s.w, s.h);
|
|
257
255
|
pass(DELUMA, out, x => {
|
|
258
256
|
x.tex("u_t", 0, target.tex);
|
|
259
257
|
x.tex("u_c", 1, c.tex);
|
|
260
258
|
});
|
|
261
|
-
return {
|
|
259
|
+
return { update: stamp(out.tex, s.w, s.h), complement: c };
|
|
262
260
|
},
|
|
263
261
|
});
|
|
264
262
|
}
|
|
@@ -332,14 +330,13 @@ export class Canvas extends Cell {
|
|
|
332
330
|
return res;
|
|
333
331
|
};
|
|
334
332
|
const self = this;
|
|
335
|
-
return Canvas.lens(
|
|
336
|
-
init:
|
|
337
|
-
|
|
338
|
-
fwd: ([s]) => {
|
|
333
|
+
return Canvas.lens(self, {
|
|
334
|
+
init: s => residualOf(s),
|
|
335
|
+
fwd: s => {
|
|
339
336
|
const small = down(sdF, s.tex, s.w, s.h);
|
|
340
337
|
return stamp(small.tex, small.w, small.h);
|
|
341
338
|
},
|
|
342
|
-
bwd: (target,
|
|
339
|
+
bwd: (target, s, c) => {
|
|
343
340
|
const up = suB(s.w, s.h);
|
|
344
341
|
pass(UP, up, x => {
|
|
345
342
|
x.tex("u_small", 0, target.tex);
|
|
@@ -351,7 +348,7 @@ export class Canvas extends Cell {
|
|
|
351
348
|
x.tex("u_a", 0, up.tex);
|
|
352
349
|
x.tex("u_b", 1, c.tex);
|
|
353
350
|
});
|
|
354
|
-
return {
|
|
351
|
+
return { update: stamp(out.tex, s.w, s.h), complement: c };
|
|
355
352
|
},
|
|
356
353
|
});
|
|
357
354
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Invertibles (`add`, `sub`, `scale`) return `: this` and ride on
|
|
4
4
|
// `Cell#lens(fwd, bwd)`. Chained calls compose into a lens chain.
|
|
5
5
|
import { tween } from "../../animation/index.js";
|
|
6
|
-
import { Cell, cachedDerive, derive,
|
|
6
|
+
import { Cell, cachedDerive, derive, fieldLens, lazy, reader, readNow, } from "../cell.js";
|
|
7
7
|
import { Num, num } from "./num.js";
|
|
8
8
|
export const add = (a, b) => ({ r: a.r + b.r, g: a.g + b.g, b: a.b + b.b, a: a.a + b.a });
|
|
9
9
|
export const sub = (a, b) => ({ r: a.r - b.r, g: a.g - b.g, b: a.b - b.b, a: a.a - b.a });
|
|
@@ -55,16 +55,16 @@ export class Color extends Cell {
|
|
|
55
55
|
return Color.derive(() => lerp(this.value, readNow(b), readNow(t)));
|
|
56
56
|
}
|
|
57
57
|
get r() {
|
|
58
|
-
return
|
|
58
|
+
return fieldLens(this, "r", Num);
|
|
59
59
|
}
|
|
60
60
|
get g() {
|
|
61
|
-
return
|
|
61
|
+
return fieldLens(this, "g", Num);
|
|
62
62
|
}
|
|
63
63
|
get b() {
|
|
64
|
-
return
|
|
64
|
+
return fieldLens(this, "b", Num);
|
|
65
65
|
}
|
|
66
66
|
get a() {
|
|
67
|
-
return
|
|
67
|
+
return fieldLens(this, "a", Num);
|
|
68
68
|
}
|
|
69
69
|
get luminance() {
|
|
70
70
|
return cachedDerive(this, "luminance", Num, c => 0.299 * c.r + 0.587 * c.g + 0.114 * c.b);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Cell, type Read, type Val } from "../cell.js";
|
|
2
|
+
import type { Pack } from "../traits.js";
|
|
3
|
+
import { Canvas } from "./canvas.js";
|
|
4
|
+
/** Field cell value: a GPU handle. The graph compares `epoch`. */
|
|
5
|
+
export interface FieldVal {
|
|
6
|
+
readonly tex: WebGLTexture;
|
|
7
|
+
readonly w: number;
|
|
8
|
+
readonly h: number;
|
|
9
|
+
readonly epoch: number;
|
|
10
|
+
}
|
|
11
|
+
type Ctor<T> = new (...args: never[]) => Cell<T>;
|
|
12
|
+
/** Texel encoding: channel count, the flat-buffer codec, and the boundary
|
|
13
|
+
* cell class that reductions return. */
|
|
14
|
+
export interface Kind<T> {
|
|
15
|
+
readonly dim: 1 | 2 | 4;
|
|
16
|
+
readonly pack: Pack<T>;
|
|
17
|
+
readonly cls: Ctor<T>;
|
|
18
|
+
}
|
|
19
|
+
/** Scalar field (1 channel) — heatmaps, SDFs, density, height. */
|
|
20
|
+
export declare const Scalar: Kind<number>;
|
|
21
|
+
/** Vector field (2 channels) — flow, gradient, displacement, RD chemicals. */
|
|
22
|
+
export declare const Vector: Kind<{
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}>;
|
|
26
|
+
/** Colour field (4 channels) — i.e. a Canvas. */
|
|
27
|
+
export declare const Colour: Kind<{
|
|
28
|
+
r: number;
|
|
29
|
+
g: number;
|
|
30
|
+
b: number;
|
|
31
|
+
a: number;
|
|
32
|
+
}>;
|
|
33
|
+
/** A colormap stop: a field value mapped to an RGB triple (0–1). */
|
|
34
|
+
export type ColorStop = readonly [number, readonly [number, number, number]];
|
|
35
|
+
export declare class Field<T> extends Cell<FieldVal> {
|
|
36
|
+
readonly kind: Kind<T>;
|
|
37
|
+
static traits: {
|
|
38
|
+
equals: (a: FieldVal, b: FieldVal) => boolean;
|
|
39
|
+
};
|
|
40
|
+
readonly _t: typeof Field.traits;
|
|
41
|
+
/** Feedback-safe ping-pong pair for `evolve`/`splat`, lazily allocated. */
|
|
42
|
+
private io;
|
|
43
|
+
constructor(kind: Kind<T>, v?: FieldVal);
|
|
44
|
+
private pingTex;
|
|
45
|
+
/** Step the field by `steps` GPU passes of `frag` (which samples the current
|
|
46
|
+
* field as `u_src`; `u_texel` = 1/size is provided). One value is committed
|
|
47
|
+
* after the substeps, so the reactive graph sees a single new epoch/frame. */
|
|
48
|
+
evolve(frag: string, uniforms?: Record<string, number | readonly number[]>, steps?: number): void;
|
|
49
|
+
/** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. The
|
|
50
|
+
* raster interaction primitive — seed a sim, inject, paint density. */
|
|
51
|
+
splat(x: number, y: number, r: number, value: T, strength?: number): void;
|
|
52
|
+
/** Whole-field mean as a read-only `T` cell. Reactive: recomputes on every
|
|
53
|
+
* new epoch, propagates only when the value moves. One 1×1 GPU readback. */
|
|
54
|
+
mean(): Read<T>;
|
|
55
|
+
/** Mean over a sub-rectangle (data pixels, reactive `box`) as a read-only
|
|
56
|
+
* `T` cell — the field's "density of this region" observation. */
|
|
57
|
+
regionMean(box: Val<{
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
w: number;
|
|
61
|
+
h: number;
|
|
62
|
+
}>): Read<T>;
|
|
63
|
+
/** Render `channel` through a colormap to a read-only `Canvas`. Reactive:
|
|
64
|
+
* re-renders on every new epoch, so rendering is itself a reactive edge. */
|
|
65
|
+
colormap(channel: number, stops: readonly ColorStop[]): Canvas;
|
|
66
|
+
}
|
|
67
|
+
/** Writable `Field<T>` of size `w×h`. `painter` fills each texel (returns a
|
|
68
|
+
* `T`); omit for an all-zero field. Linear-filtered for continuous sampling. */
|
|
69
|
+
export declare function field<T>(kind: Kind<T>, w: number, h: number, painter?: (x: number, y: number) => T): Field<T>;
|
|
70
|
+
export {};
|