bireactive 0.3.3 → 0.3.5
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/automerge/apply-patches.d.ts +4 -0
- package/dist/automerge/apply-patches.js +62 -0
- package/dist/automerge/doc-cell.d.ts +5 -1
- package/dist/automerge/doc-cell.js +13 -6
- package/dist/automerge/index.d.ts +1 -1
- package/dist/automerge/reconcile.d.ts +12 -2
- package/dist/automerge/reconcile.js +86 -27
- package/dist/core/_counts.d.ts +0 -2
- package/dist/core/_counts.js +0 -1
- package/dist/core/cell.d.ts +44 -87
- package/dist/core/cell.js +195 -301
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/lenses/memory.js +25 -21
- package/dist/core/lenses/point-cloud.js +66 -47
- package/dist/core/lenses/snap.js +8 -7
- package/dist/core/lenses/text.js +8 -6
- package/dist/core/optic.d.ts +0 -5
- package/dist/core/optic.js +16 -28
- package/dist/core/values/audio.js +6 -5
- package/dist/core/values/canvas.js +20 -12
- package/dist/core/values/field.d.ts +2 -0
- package/dist/core/values/field.js +25 -0
- package/dist/core/values/reg.d.ts +2 -3
- package/dist/core/values/reg.js +3 -4
- package/dist/core/values/str.js +6 -6
- package/dist/formats/lens.js +21 -13
- package/dist/jsx-runtime.js +88 -22
- package/dist/learn/lens-net.js +3 -5
- package/dist/schema/lens.d.ts +4 -1
- package/dist/schema/lens.js +14 -7
- package/package.json +1 -1
package/dist/core/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { type Counts, counts, resetCounts, snapshotCounts, withCounts } from "./_counts.js";
|
|
2
|
-
export { batch, Cell, type CellOptions, cachedDerive, cell, derive, effect, fieldLens, fieldOf, type Init, type Inner, isCell, isLens, isReadonly, lazy, lens, type Network, network, type Optic, type Read, reader, readNow, SKIP, type Skip,
|
|
2
|
+
export { batch, Cell, type CellOptions, cachedDerive, cell, derive, effect, fieldLens, fieldOf, type Init, type Inner, isCell, isLens, isReadonly, lazy, lens, type Network, network, type Optic, type Read, reader, readNow, SKIP, type Skip, setCellWriteHook, settle, transitiveDeps, untracked, type Val, type Writable, type WritableBrand, } from "./cell.js";
|
|
3
3
|
export { type DumpOpts, dumpGraph, explain, kind as cellKind, label as cellLabel, traceWrites, upstream, } from "./debug.js";
|
|
4
4
|
export { bezier2, bezier3 } from "./derived-geometry.js";
|
|
5
5
|
export * from "./lenses/index.js";
|
|
6
6
|
export { each, type Lifecycle } from "./lifecycle.js";
|
|
7
|
-
export { atKey,
|
|
7
|
+
export { atKey, iso, optic } from "./optic.js";
|
|
8
8
|
export { at, fields } from "./optics.js";
|
|
9
9
|
export { type Store, store } from "./store.js";
|
|
10
10
|
export { type Equals, type Lerp, type Linear, type Metric, type Pack, type Pivotal, requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, type TraitDict, type Traits, } from "./traits.js";
|
package/dist/core/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { dumpGraph, explain, kind as cellKind, label as cellLabel, traceWrites,
|
|
|
4
4
|
export { bezier2, bezier3 } from "./derived-geometry.js";
|
|
5
5
|
export * from "./lenses/index.js";
|
|
6
6
|
export { each } from "./lifecycle.js";
|
|
7
|
-
export { atKey,
|
|
7
|
+
export { atKey, iso, optic } from "./optic.js";
|
|
8
8
|
export { at, fields } from "./optics.js";
|
|
9
9
|
export { store } from "./store.js";
|
|
10
10
|
export { requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, } from "./traits.js";
|
|
@@ -31,28 +31,29 @@ export function remember(sources, opts) {
|
|
|
31
31
|
};
|
|
32
32
|
// biome-ignore lint/suspicious/noExplicitAny: spec is checked structurally
|
|
33
33
|
return Num.lens(sources, {
|
|
34
|
-
|
|
34
|
+
complement: (vals) => {
|
|
35
35
|
const a = anchor(vals);
|
|
36
36
|
return { shape: shapeOf(vals, a, feature(vals, a), null) };
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
// `get` is the sole refresh: recompute the view and (idempotently) the shape.
|
|
39
|
+
get: (vals, c) => {
|
|
39
40
|
const a = anchor(vals);
|
|
40
|
-
|
|
41
|
+
const f = feature(vals, a);
|
|
42
|
+
c.shape = shapeOf(vals, a, f, c.shape);
|
|
43
|
+
return f;
|
|
41
44
|
},
|
|
42
|
-
|
|
43
|
-
bwd: (target, vals, c) => {
|
|
45
|
+
put: (target, vals, c) => {
|
|
44
46
|
const a = anchor(vals);
|
|
45
47
|
const f = feature(vals, a);
|
|
46
48
|
// Magnitude is lossy (|−f| = f): a same-magnitude target re-projects
|
|
47
49
|
// to the current feature, so the cluster is left put.
|
|
48
|
-
if (magnitude && Math.abs(target) === f)
|
|
49
|
-
return
|
|
50
|
-
}
|
|
50
|
+
if (magnitude && Math.abs(target) === f)
|
|
51
|
+
return vals.map(() => SKIP);
|
|
51
52
|
if (f > eps) {
|
|
52
53
|
const k = target / f;
|
|
53
|
-
return
|
|
54
|
+
return vals.map(v => lin.add(a, lin.scale(lin.sub(v, a), k)));
|
|
54
55
|
}
|
|
55
|
-
return
|
|
56
|
+
return c.shape.map(s => lin.add(a, lin.scale(s, target)));
|
|
56
57
|
},
|
|
57
58
|
});
|
|
58
59
|
}
|
|
@@ -66,24 +67,27 @@ export function continuous(sources, opts) {
|
|
|
66
67
|
const unwrap = (rawv, prev) => prev + wrap(rawv - prev);
|
|
67
68
|
// biome-ignore lint/suspicious/noExplicitAny: spec is checked structurally
|
|
68
69
|
return Num.lens(sources, {
|
|
69
|
-
|
|
70
|
+
complement: (vals) => {
|
|
70
71
|
const r = raw(vals);
|
|
71
72
|
return { prev: r.defined ? r.value : 0 };
|
|
72
73
|
},
|
|
73
|
-
|
|
74
|
+
// `get` is the sole refresh: unwrap relative to `prev` and accumulate the
|
|
75
|
+
// winding. Idempotent — once the source settles, `unwrap` is a fixpoint.
|
|
76
|
+
get: (vals, c) => {
|
|
74
77
|
const r = raw(vals);
|
|
75
|
-
|
|
78
|
+
if (r.defined)
|
|
79
|
+
c.prev = unwrap(r.value, c.prev);
|
|
80
|
+
return c.prev;
|
|
76
81
|
},
|
|
77
|
-
|
|
82
|
+
put: (target, vals, c) => {
|
|
78
83
|
const r = raw(vals);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!r.defined)
|
|
84
|
-
return { updates: vals.map(() => SKIP), complement: { prev: target } };
|
|
84
|
+
if (!r.defined) {
|
|
85
|
+
c.prev = target;
|
|
86
|
+
return vals.map(() => SKIP);
|
|
87
|
+
}
|
|
85
88
|
const current = unwrap(r.value, c.prev);
|
|
86
|
-
|
|
89
|
+
c.prev = target;
|
|
90
|
+
return apply(target, vals, current);
|
|
87
91
|
},
|
|
88
92
|
});
|
|
89
93
|
}
|
|
@@ -61,33 +61,37 @@ export function scaleAbout(points, pivot) {
|
|
|
61
61
|
});
|
|
62
62
|
// biome-ignore lint/suspicious/noExplicitAny: variance escape — spec is checked structurally
|
|
63
63
|
return Num.lens(points, {
|
|
64
|
-
|
|
64
|
+
complement: (vals) => {
|
|
65
65
|
const p = pivot.peek();
|
|
66
66
|
return { devs: vals.map(v => ({ x: v.x - p.x, y: v.y - p.y })) };
|
|
67
67
|
},
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// `get` is the sole refresh: re-derive the offsets (idempotent on a settled
|
|
69
|
+
// source), then emit the radius.
|
|
70
|
+
get: (vals, c) => {
|
|
70
71
|
const p = pivot.peek();
|
|
72
|
+
c.devs = refresh(c.devs, vals, p);
|
|
71
73
|
return Math.hypot(vals[0].x - p.x, vals[0].y - p.y);
|
|
72
74
|
},
|
|
73
|
-
|
|
75
|
+
put: (target, vals, c) => {
|
|
74
76
|
const p = pivot.peek();
|
|
75
77
|
// Lossy magnitude view: |−r| = r, so a same-magnitude target
|
|
76
78
|
// re-projects to the current radius and is absorbed (sources put).
|
|
77
79
|
const rNow = Math.hypot(vals[0].x - p.x, vals[0].y - p.y);
|
|
78
80
|
if (Math.abs(target) === rNow)
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
+
return vals.map(() => SKIP);
|
|
82
|
+
// Re-derive offsets from the live source (so a sibling move is reflected);
|
|
83
|
+
// the guard keeps the stored direction through a collapse.
|
|
84
|
+
const devs = (c.devs = refresh(c.devs, vals, p));
|
|
85
|
+
const d0 = devs[0];
|
|
81
86
|
const r0 = Math.hypot(d0.x, d0.y);
|
|
82
87
|
if (r0 < 1e-12)
|
|
83
|
-
return
|
|
88
|
+
return vals.map(() => SKIP);
|
|
84
89
|
const k = target / r0;
|
|
85
|
-
|
|
90
|
+
return vals.map((v, i) => ({
|
|
86
91
|
...v,
|
|
87
|
-
x: p.x + k *
|
|
88
|
-
y: p.y + k *
|
|
92
|
+
x: p.x + k * devs[i].x,
|
|
93
|
+
y: p.y + k * devs[i].y,
|
|
89
94
|
}));
|
|
90
|
-
return { updates: out, complement: c };
|
|
91
95
|
},
|
|
92
96
|
});
|
|
93
97
|
}
|
|
@@ -109,7 +113,7 @@ export function scaleAboutXY(points, pivot) {
|
|
|
109
113
|
}));
|
|
110
114
|
};
|
|
111
115
|
return Vec.lens(points, {
|
|
112
|
-
|
|
116
|
+
complement: (vals) => {
|
|
113
117
|
const p = pivot.peek();
|
|
114
118
|
const ox = vals[0].x - p.x;
|
|
115
119
|
const oy = vals[0].y - p.y;
|
|
@@ -120,15 +124,19 @@ export function scaleAboutXY(points, pivot) {
|
|
|
120
124
|
})),
|
|
121
125
|
};
|
|
122
126
|
},
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
// `get` is the sole refresh: re-derive the per-axis fractions, then emit
|
|
128
|
+
// point 0's offset from the pivot.
|
|
129
|
+
get: (vals, c) => {
|
|
125
130
|
const p = pivot.peek();
|
|
131
|
+
c.fracs = refresh(c.fracs, vals, p);
|
|
126
132
|
return { x: vals[0].x - p.x, y: vals[0].y - p.y };
|
|
127
133
|
},
|
|
128
|
-
|
|
134
|
+
put: (target, vals, c) => {
|
|
129
135
|
const p = pivot.peek();
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
// Re-derive fractions from the live source (reflect a sibling move); the
|
|
137
|
+
// guard keeps the stored fraction through a per-axis collapse.
|
|
138
|
+
const fracs = (c.fracs = refresh(c.fracs, vals, p));
|
|
139
|
+
return fracs.map(f => ({ x: p.x + f.x * target.x, y: p.y + f.y * target.y }));
|
|
132
140
|
},
|
|
133
141
|
});
|
|
134
142
|
}
|
|
@@ -318,7 +326,7 @@ export function pca(points) {
|
|
|
318
326
|
return { uX: ux, uY: uy, vX: vx, vY: vy, lenThis, lenOther, projThis, projOther };
|
|
319
327
|
};
|
|
320
328
|
return Num.lens(points, {
|
|
321
|
-
|
|
329
|
+
complement: (vals) => {
|
|
322
330
|
const seed = {
|
|
323
331
|
uX: 1,
|
|
324
332
|
uY: 0,
|
|
@@ -332,26 +340,30 @@ export function pca(points) {
|
|
|
332
340
|
const d = decompose(vals);
|
|
333
341
|
return d ? axisFrom(d, seed, vals) : seed;
|
|
334
342
|
},
|
|
335
|
-
|
|
343
|
+
// `get` is the sole refresh: recompute the axis frame (idempotent — the
|
|
344
|
+
// sign is pinned to the previous frame), then emit this axis's length.
|
|
345
|
+
get: (vals, c) => {
|
|
336
346
|
const d = decompose(vals);
|
|
337
|
-
|
|
347
|
+
if (d)
|
|
348
|
+
Object.assign(c, axisFrom(d, c, vals));
|
|
349
|
+
return d ? c.lenThis : 0;
|
|
338
350
|
},
|
|
339
|
-
|
|
340
|
-
bwd: (target, vals, c) => {
|
|
351
|
+
put: (target, vals, c) => {
|
|
341
352
|
const d = decompose(vals);
|
|
353
|
+
// Re-derive the frame from the live source (reflect a sibling move); when
|
|
354
|
+
// collapsed (`!d`) the stored frame/projections are kept as the fallback.
|
|
355
|
+
if (d)
|
|
356
|
+
Object.assign(c, axisFrom(d, c, vals));
|
|
342
357
|
if (d && c.lenThis > 1e-12) {
|
|
343
358
|
// Lossy magnitude view: a same-magnitude target re-projects to
|
|
344
359
|
// the current axis length and is absorbed (cluster left put).
|
|
345
360
|
if (Math.abs(target) === c.lenThis)
|
|
346
|
-
return
|
|
347
|
-
// Non-degenerate fast path: scale current cluster along axis.
|
|
348
|
-
// sets the axis length to |target|, so the complement is consistent
|
|
349
|
-
// without a post-write `step` (the engine no longer re-steps own writes).
|
|
361
|
+
return vals.map(() => SKIP);
|
|
362
|
+
// Non-degenerate fast path: scale current cluster along the axis.
|
|
350
363
|
const k = target / c.lenThis;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
};
|
|
364
|
+
const out = scaleAlongAxis(vals, d.cx, d.cy, c.uX, c.uY, k);
|
|
365
|
+
c.lenThis = Math.abs(target);
|
|
366
|
+
return out;
|
|
355
367
|
}
|
|
356
368
|
// Degenerate: reconstruct from complement. Centroid still
|
|
357
369
|
// derivable from current source (mean translates always work).
|
|
@@ -369,7 +381,8 @@ export function pca(points) {
|
|
|
369
381
|
const b = c.projOther[i] * c.lenOther;
|
|
370
382
|
out[i] = { x: cx + a * c.uX + b * c.vX, y: cy + a * c.uY + b * c.vY };
|
|
371
383
|
}
|
|
372
|
-
|
|
384
|
+
c.lenThis = Math.abs(target);
|
|
385
|
+
return out;
|
|
373
386
|
},
|
|
374
387
|
});
|
|
375
388
|
};
|
|
@@ -481,24 +494,27 @@ export function procrustes(points) {
|
|
|
481
494
|
});
|
|
482
495
|
};
|
|
483
496
|
const scale = Num.lens(points, {
|
|
484
|
-
|
|
497
|
+
complement: (vals) => {
|
|
485
498
|
const c = centroidOf(vals);
|
|
486
499
|
return { devs: vals.map(v => ({ x: v.x - c.x, y: v.y - c.y })) };
|
|
487
500
|
},
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
501
|
+
// `get` is the sole refresh: re-derive the centroid offsets, then emit the radius.
|
|
502
|
+
get: (vals, c) => {
|
|
503
|
+
const cen = centroidOf(vals);
|
|
504
|
+
c.devs = refreshDevs(c.devs, vals);
|
|
505
|
+
return Math.hypot(vals[0].x - cen.x, vals[0].y - cen.y);
|
|
492
506
|
},
|
|
493
|
-
|
|
507
|
+
put: (target, vals, c) => {
|
|
494
508
|
const cen = centroidOf(vals);
|
|
495
|
-
|
|
509
|
+
// Re-derive offsets from the live source (reflect a sibling move); the
|
|
510
|
+
// guard keeps the stored direction through a collapse to the centroid.
|
|
511
|
+
const devs = (c.devs = refreshDevs(c.devs, vals));
|
|
512
|
+
const d0 = devs[0];
|
|
496
513
|
const r0 = Math.hypot(d0.x, d0.y);
|
|
497
514
|
if (r0 < 1e-12)
|
|
498
|
-
return
|
|
515
|
+
return vals.map(() => SKIP);
|
|
499
516
|
const k = target / r0;
|
|
500
|
-
|
|
501
|
-
return { updates: out, complement: c };
|
|
517
|
+
return devs.map(d => ({ x: cen.x + k * d.x, y: cen.y + k * d.y }));
|
|
502
518
|
},
|
|
503
519
|
});
|
|
504
520
|
return { centroid, rotation, scale };
|
|
@@ -556,7 +572,7 @@ export function bbox(points) {
|
|
|
556
572
|
}));
|
|
557
573
|
};
|
|
558
574
|
const size = Vec.lens(points, {
|
|
559
|
-
|
|
575
|
+
complement: (vals) => {
|
|
560
576
|
const b = computeBox(vals);
|
|
561
577
|
const halfX0 = b.sx > 1e-12 ? b.sx / 2 : 1;
|
|
562
578
|
const halfY0 = b.sy > 1e-12 ? b.sy / 2 : 1;
|
|
@@ -567,17 +583,20 @@ export function bbox(points) {
|
|
|
567
583
|
})),
|
|
568
584
|
};
|
|
569
585
|
},
|
|
570
|
-
|
|
571
|
-
|
|
586
|
+
// `get` is the sole refresh: re-derive the half-size fractions, then emit the size.
|
|
587
|
+
get: (vals, c) => {
|
|
572
588
|
const b = computeBox(vals);
|
|
589
|
+
c.fracs = refreshFracs(c.fracs, vals);
|
|
573
590
|
return { x: b.sx, y: b.sy };
|
|
574
591
|
},
|
|
575
|
-
|
|
592
|
+
put: (target, vals, c) => {
|
|
576
593
|
const b = computeBox(vals);
|
|
577
594
|
const halfTx = target.x / 2;
|
|
578
595
|
const halfTy = target.y / 2;
|
|
579
|
-
|
|
580
|
-
|
|
596
|
+
// Re-derive fractions from the live source (reflect a sibling move); the
|
|
597
|
+
// guard keeps the stored fraction through a per-axis collapse.
|
|
598
|
+
const fracs = (c.fracs = refreshFracs(c.fracs, vals));
|
|
599
|
+
return fracs.map(f => ({ x: b.cx + f.x * halfTx, y: b.cy + f.y * halfTy }));
|
|
581
600
|
},
|
|
582
601
|
});
|
|
583
602
|
return { center, size };
|
package/dist/core/lenses/snap.js
CHANGED
|
@@ -127,12 +127,13 @@ export function nearestIndex(pointer, candidates, opts = {}) {
|
|
|
127
127
|
const sticky = opts.sticky ?? 0;
|
|
128
128
|
const parents = [pointer, ...candidates];
|
|
129
129
|
return lens(parents, {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
130
|
+
complement: (sources) => ({ index: pick(sources, -1, 0) }),
|
|
131
|
+
// `get` is the sole refresh: re-pick with hysteresis from the current pick.
|
|
132
|
+
// Idempotent — re-running on a settled source keeps the same index.
|
|
133
|
+
get: (sources, c) => {
|
|
134
|
+
c.index = pick(sources, c.index, sticky);
|
|
135
|
+
return c.index;
|
|
136
|
+
},
|
|
137
|
+
put: (_t, sources, _c) => sources.map(() => SKIP),
|
|
137
138
|
});
|
|
138
139
|
}
|
package/dist/core/lenses/text.js
CHANGED
|
@@ -192,11 +192,13 @@ function buildCaseComplement(s) {
|
|
|
192
192
|
export function caseFold(parent, to = "lower") {
|
|
193
193
|
const fold = to === "upper" ? (s) => s.toUpperCase() : (s) => s.toLowerCase();
|
|
194
194
|
return Str.lens(parent, {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
complement: (s) => buildCaseComplement(s),
|
|
196
|
+
// `get` is the sole refresh: re-derive the case complement from the source
|
|
197
|
+
// (pure ⇒ idempotent), then fold.
|
|
198
|
+
get: (s, c) => {
|
|
199
|
+
refreshCaseComplement(s, c);
|
|
200
|
+
return fold(s);
|
|
201
|
+
},
|
|
202
|
+
put: (target, _s, c) => applyCaseComplement(target, c),
|
|
201
203
|
});
|
|
202
204
|
}
|
package/dist/core/optic.d.ts
CHANGED
|
@@ -6,8 +6,3 @@ export declare function optic<A, B>(get: (a: A) => B, put: (b: B, a: A) => A): O
|
|
|
6
6
|
export declare function iso<A, B>(to: (a: A) => B, from: (b: B) => A): Optic<A, B>;
|
|
7
7
|
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
8
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>;
|
package/dist/core/optic.js
CHANGED
|
@@ -1,39 +1,27 @@
|
|
|
1
|
-
// Lenses as first-class values, independent of any `Cell`. An `Optic<
|
|
2
|
-
// lens transform *unbound* — a `get`/`put` pair you
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// `
|
|
1
|
+
// Lenses as first-class values, independent of any `Cell`. An `Optic<S, V>` is a
|
|
2
|
+
// lens transform *unbound* — a `get`/`put` pair you store and apply to a source
|
|
3
|
+
// with `cell.lens(optic)` / `lens(source, optic)`. Chain several by passing them to
|
|
4
|
+
// one `lens(source, a, b, …)` call; the binder folds the chain by re-binding (its
|
|
5
|
+
// `put` reconstructs the inner source on each back-write). An `iso` is the lossless
|
|
6
|
+
// case whose `put` ignores the source (a 1-arg `put`). These constructors build the
|
|
7
|
+
// *pure* (complement-free) optics; complement-carrying optics are plain objects with
|
|
8
|
+
// a `complement` seed (see the Optic type and the stateful-lens header in cell.ts).
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
function make(get, put, readsSource) {
|
|
13
|
-
return {
|
|
14
|
-
get,
|
|
15
|
-
put,
|
|
16
|
-
readsSource,
|
|
17
|
-
through(next) {
|
|
18
|
-
// Composed backward reconstructs the inner B from the outer A, so it always
|
|
19
|
-
// reads the source regardless of either side's own `readsSource`.
|
|
20
|
-
return make(a => next.get(get(a)), (c, a) => put(next.put(c, get(a)), a), true);
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
}
|
|
10
|
+
// `optic.ts` imports only *types* from `cell.ts`, so `cell.ts` can apply optics by
|
|
11
|
+
// re-binding without importing this module (no cycle).
|
|
24
12
|
/** Build an optic from a forward and a backward. A 2-arg `put(b, a)` reads the
|
|
25
13
|
* source; a 1-arg `put(b)` reconstructs it (and is treated as an `iso`). */
|
|
26
14
|
export function optic(get, put) {
|
|
27
|
-
return
|
|
15
|
+
return { get, put: put };
|
|
28
16
|
}
|
|
29
17
|
/** A lossless, source-independent optic (an isomorphism): `to`/`from` invert. */
|
|
30
18
|
export function iso(to, from) {
|
|
31
|
-
return
|
|
19
|
+
return { get: to, put: ((b) => from(b)) };
|
|
32
20
|
}
|
|
33
21
|
/** Field optic: project key `K`, putting back with a spread-replace. */
|
|
34
22
|
export function atKey(key) {
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
return {
|
|
24
|
+
get: (t) => t[key],
|
|
25
|
+
put: ((v, t) => ({ ...t, [key]: v })),
|
|
26
|
+
};
|
|
39
27
|
}
|
|
@@ -61,14 +61,15 @@ export class Audio extends Cell {
|
|
|
61
61
|
const tf = reader(target);
|
|
62
62
|
const self = this;
|
|
63
63
|
return Audio.lens(self, {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
complement: (s) => ({ peak: peak(s) }),
|
|
65
|
+
// `get` is the sole refresh: re-measure the source peak (pure ⇒ idempotent).
|
|
66
|
+
get: (s, c) => {
|
|
67
|
+
const p = (c.peak = peak(s));
|
|
67
68
|
return p === 0 ? s : scaled(s, tf() / p);
|
|
68
69
|
},
|
|
69
|
-
|
|
70
|
+
put: (view, _src, c) => {
|
|
70
71
|
const t = tf();
|
|
71
|
-
return
|
|
72
|
+
return t === 0 ? view : scaled(view, c.peak / t);
|
|
72
73
|
},
|
|
73
74
|
});
|
|
74
75
|
}
|
|
@@ -189,19 +189,22 @@ export class Canvas extends Cell {
|
|
|
189
189
|
};
|
|
190
190
|
const self = this;
|
|
191
191
|
return Canvas.lens(self, {
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
complement: (s) => chromaOf(s),
|
|
193
|
+
// `get` is the sole refresh: re-run the chroma pass into the complement
|
|
194
|
+
// tex (pure GPU pass ⇒ idempotent), then emit the luma view.
|
|
195
|
+
get: (s, c) => {
|
|
196
|
+
Object.assign(c, chromaOf(s));
|
|
194
197
|
const out = sf(s.w, s.h);
|
|
195
198
|
pass(LUMA, out, x => x.tex("u_s", 0, s.tex));
|
|
196
199
|
return stamp(out.tex, s.w, s.h);
|
|
197
200
|
},
|
|
198
|
-
|
|
201
|
+
put: (target, s, c) => {
|
|
199
202
|
const out = sb(s.w, s.h);
|
|
200
203
|
pass(RECOLOR, out, x => {
|
|
201
204
|
x.tex("u_t", 0, target.tex);
|
|
202
205
|
x.tex("u_c", 1, c.tex);
|
|
203
206
|
});
|
|
204
|
-
return
|
|
207
|
+
return stamp(out.tex, s.w, s.h);
|
|
205
208
|
},
|
|
206
209
|
});
|
|
207
210
|
}
|
|
@@ -218,19 +221,21 @@ export class Canvas extends Cell {
|
|
|
218
221
|
};
|
|
219
222
|
const self = this;
|
|
220
223
|
return Canvas.lens(self, {
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
complement: (s) => lumaOf(s),
|
|
225
|
+
// `get` is the sole refresh: re-run the luma pass into the complement tex.
|
|
226
|
+
get: (s, c) => {
|
|
227
|
+
Object.assign(c, lumaOf(s));
|
|
223
228
|
const out = sf(s.w, s.h);
|
|
224
229
|
pass(CHROMA_VIEW, out, x => x.tex("u_s", 0, s.tex));
|
|
225
230
|
return stamp(out.tex, s.w, s.h);
|
|
226
231
|
},
|
|
227
|
-
|
|
232
|
+
put: (target, s, c) => {
|
|
228
233
|
const out = sb(s.w, s.h);
|
|
229
234
|
pass(DELUMA, out, x => {
|
|
230
235
|
x.tex("u_t", 0, target.tex);
|
|
231
236
|
x.tex("u_c", 1, c.tex);
|
|
232
237
|
});
|
|
233
|
-
return
|
|
238
|
+
return stamp(out.tex, s.w, s.h);
|
|
234
239
|
},
|
|
235
240
|
});
|
|
236
241
|
}
|
|
@@ -305,12 +310,15 @@ export class Canvas extends Cell {
|
|
|
305
310
|
};
|
|
306
311
|
const self = this;
|
|
307
312
|
return Canvas.lens(self, {
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
complement: (s) => residualOf(s),
|
|
314
|
+
// `get` is the sole refresh: recompute the Laplacian residual (pure ⇒
|
|
315
|
+
// idempotent), then emit the box-downsampled thumbnail.
|
|
316
|
+
get: (s, c) => {
|
|
317
|
+
Object.assign(c, residualOf(s));
|
|
310
318
|
const small = down(sdF, s.tex, s.w, s.h);
|
|
311
319
|
return stamp(small.tex, small.w, small.h);
|
|
312
320
|
},
|
|
313
|
-
|
|
321
|
+
put: (target, s, c) => {
|
|
314
322
|
const up = suB(s.w, s.h);
|
|
315
323
|
pass(UP, up, x => {
|
|
316
324
|
x.tex("u_small", 0, target.tex);
|
|
@@ -322,7 +330,7 @@ export class Canvas extends Cell {
|
|
|
322
330
|
x.tex("u_a", 0, up.tex);
|
|
323
331
|
x.tex("u_b", 1, c.tex);
|
|
324
332
|
});
|
|
325
|
-
return
|
|
333
|
+
return stamp(out.tex, s.w, s.h);
|
|
326
334
|
},
|
|
327
335
|
});
|
|
328
336
|
}
|
|
@@ -48,6 +48,8 @@ export declare class Field<T> extends Cell<FieldVal> {
|
|
|
48
48
|
evolve(frag: string, uniforms?: Record<string, number | readonly number[]>, steps?: number): void;
|
|
49
49
|
/** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. */
|
|
50
50
|
splat(x: number, y: number, r: number, value: T, strength?: number): void;
|
|
51
|
+
/** Fill a hard-edged rectangle `(x, y, w, h)` in data pixels with `value`. */
|
|
52
|
+
fillRect(x: number, y: number, w: number, h: number, value: T, strength?: number): void;
|
|
51
53
|
/** Whole-field mean as a read-only `T` cell. Recomputes per epoch; one 1×1
|
|
52
54
|
* GPU readback. */
|
|
53
55
|
mean(): Read<T>;
|
|
@@ -44,6 +44,15 @@ void main() {
|
|
|
44
44
|
float f = clamp(exp(-(d * d) / (u_r * u_r)) * u_mix, 0.0, 1.0);
|
|
45
45
|
o = vec4(mix(s.rgb, u_v, f), s.a);
|
|
46
46
|
}`;
|
|
47
|
+
const FILL_RECT = `${HEAD}
|
|
48
|
+
uniform sampler2D u_src; uniform vec2 u_res; uniform vec2 u_o; uniform vec2 u_wh;
|
|
49
|
+
uniform vec3 u_v; uniform float u_mix;
|
|
50
|
+
void main() {
|
|
51
|
+
vec2 px = v_uv * u_res;
|
|
52
|
+
bool inside = px.x >= u_o.x && px.y >= u_o.y && px.x < u_o.x + u_wh.x && px.y < u_o.y + u_wh.y;
|
|
53
|
+
vec4 s = texture(u_src, v_uv);
|
|
54
|
+
o = inside ? vec4(mix(s.rgb, u_v, u_mix), s.a) : s;
|
|
55
|
+
}`;
|
|
47
56
|
/** GLSL float literal (always carries a decimal point). */
|
|
48
57
|
const glf = (n) => (Number.isInteger(n) ? `${n}.0` : String(n));
|
|
49
58
|
const glv3 = (c) => `${glf(c[0])}, ${glf(c[1])}, ${glf(c[2])}`;
|
|
@@ -144,6 +153,22 @@ export class Field extends Cell {
|
|
|
144
153
|
});
|
|
145
154
|
this.value = stamp(dst.tex, v.w, v.h);
|
|
146
155
|
}
|
|
156
|
+
/** Fill a hard-edged rectangle `(x, y, w, h)` in data pixels with `value`. */
|
|
157
|
+
fillRect(x, y, w, h, value, strength = 1) {
|
|
158
|
+
const ping = this.pingTex();
|
|
159
|
+
const v = this.peek();
|
|
160
|
+
this.kind.pack.read(value, TMP, 0);
|
|
161
|
+
const dst = ping(v.w, v.h, v.tex);
|
|
162
|
+
pass(FILL_RECT, dst, s => {
|
|
163
|
+
s.tex("u_src", 0, v.tex);
|
|
164
|
+
s.v2("u_res", v.w, v.h);
|
|
165
|
+
s.v2("u_o", x, y);
|
|
166
|
+
s.v2("u_wh", w, h);
|
|
167
|
+
s.v3("u_v", TMP[0], TMP[1], TMP[2]);
|
|
168
|
+
s.f("u_mix", strength);
|
|
169
|
+
});
|
|
170
|
+
this.value = stamp(dst.tex, v.w, v.h);
|
|
171
|
+
}
|
|
147
172
|
/** Whole-field mean as a read-only `T` cell. Recomputes per epoch; one 1×1
|
|
148
173
|
* GPU readback. */
|
|
149
174
|
mean() {
|
|
@@ -190,9 +190,8 @@ export declare class Reg<V = RegVal, N extends boolean = boolean, F extends Boun
|
|
|
190
190
|
/** This grammar as a first-class, composable `Optic<string, V>`: `get`
|
|
191
191
|
* parses (falling back to the default value off-language), `put` reprints
|
|
192
192
|
* and round-trip-guards (an off-language source or a non-round-tripping
|
|
193
|
-
* value leaves the source untouched). Drops straight into `
|
|
194
|
-
*
|
|
195
|
-
* lenses like `caseFold`. */
|
|
193
|
+
* value leaves the source untouched). Drops straight into `cell.lens(...)`,
|
|
194
|
+
* so it chains with `atKey`/`iso` and string lenses like `caseFold`. */
|
|
196
195
|
optic(): Optic<string, V>;
|
|
197
196
|
/** The whole abstract value as a writable lens over `source`. */
|
|
198
197
|
view(source: Cell<string>): Writable<Cell<V>>;
|
package/dist/core/values/reg.js
CHANGED
|
@@ -465,9 +465,8 @@ export class Reg {
|
|
|
465
465
|
/** This grammar as a first-class, composable `Optic<string, V>`: `get`
|
|
466
466
|
* parses (falling back to the default value off-language), `put` reprints
|
|
467
467
|
* and round-trip-guards (an off-language source or a non-round-tripping
|
|
468
|
-
* value leaves the source untouched). Drops straight into `
|
|
469
|
-
*
|
|
470
|
-
* lenses like `caseFold`. */
|
|
468
|
+
* value leaves the source untouched). Drops straight into `cell.lens(...)`,
|
|
469
|
+
* so it chains with `atKey`/`iso` and string lenses like `caseFold`. */
|
|
471
470
|
optic() {
|
|
472
471
|
const def = defaultVal(this.root);
|
|
473
472
|
return optic((s) => (this.match(s) ?? def), (v, s) => {
|
|
@@ -479,7 +478,7 @@ export class Reg {
|
|
|
479
478
|
}
|
|
480
479
|
/** The whole abstract value as a writable lens over `source`. */
|
|
481
480
|
view(source) {
|
|
482
|
-
return source.
|
|
481
|
+
return source.lens(this.optic());
|
|
483
482
|
}
|
|
484
483
|
bind(source, opts = {}) {
|
|
485
484
|
const captures = new Map();
|