bireactive 0.2.0
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/animation/anim.d.ts +57 -0
- package/dist/animation/anim.js +318 -0
- package/dist/animation/combinators.d.ts +39 -0
- package/dist/animation/combinators.js +113 -0
- package/dist/animation/easings.d.ts +5 -0
- package/dist/animation/easings.js +5 -0
- package/dist/animation/index.d.ts +3 -0
- package/dist/animation/index.js +3 -0
- package/dist/assert/algebra.d.ts +20 -0
- package/dist/assert/algebra.js +79 -0
- package/dist/assert/claim.d.ts +40 -0
- package/dist/assert/claim.js +129 -0
- package/dist/assert/index.d.ts +7 -0
- package/dist/assert/index.js +19 -0
- package/dist/assert/predicates.d.ts +18 -0
- package/dist/assert/predicates.js +43 -0
- package/dist/assert/record.d.ts +20 -0
- package/dist/assert/record.js +78 -0
- package/dist/assert/scope.d.ts +42 -0
- package/dist/assert/scope.js +233 -0
- package/dist/assert/span.d.ts +37 -0
- package/dist/assert/span.js +68 -0
- package/dist/assert/tree.d.ts +22 -0
- package/dist/assert/tree.js +65 -0
- package/dist/code/code.d.ts +70 -0
- package/dist/code/code.js +361 -0
- package/dist/code/index.d.ts +2 -0
- package/dist/code/index.js +9 -0
- package/dist/code/morph.d.ts +5 -0
- package/dist/code/morph.js +194 -0
- package/dist/code/tokenize.d.ts +8 -0
- package/dist/code/tokenize.js +51 -0
- package/dist/constraints/cluster.d.ts +83 -0
- package/dist/constraints/cluster.js +213 -0
- package/dist/constraints/drivers.d.ts +15 -0
- package/dist/constraints/drivers.js +40 -0
- package/dist/constraints/factories.d.ts +73 -0
- package/dist/constraints/factories.js +248 -0
- package/dist/constraints/index.d.ts +11 -0
- package/dist/constraints/index.js +39 -0
- package/dist/constraints/interaction.d.ts +21 -0
- package/dist/constraints/interaction.js +148 -0
- package/dist/constraints/linalg.d.ts +18 -0
- package/dist/constraints/linalg.js +141 -0
- package/dist/constraints/phases.d.ts +21 -0
- package/dist/constraints/phases.js +60 -0
- package/dist/constraints/physics.d.ts +34 -0
- package/dist/constraints/physics.js +128 -0
- package/dist/constraints/rigid.d.ts +210 -0
- package/dist/constraints/rigid.js +835 -0
- package/dist/constraints/solver.d.ts +107 -0
- package/dist/constraints/solver.js +510 -0
- package/dist/constraints/term.d.ts +50 -0
- package/dist/constraints/term.js +80 -0
- package/dist/constraints/terms.d.ts +80 -0
- package/dist/constraints/terms.js +302 -0
- package/dist/constraints/world.d.ts +31 -0
- package/dist/constraints/world.js +245 -0
- package/dist/core/aggregates.d.ts +64 -0
- package/dist/core/aggregates.js +198 -0
- package/dist/core/anim.d.ts +84 -0
- package/dist/core/anim.js +301 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +38 -0
- package/dist/core/introspect.d.ts +5 -0
- package/dist/core/introspect.js +31 -0
- package/dist/core/lenses/closed-form-policies.d.ts +64 -0
- package/dist/core/lenses/closed-form-policies.js +452 -0
- package/dist/core/lenses/domain-aggregates.d.ts +54 -0
- package/dist/core/lenses/domain-aggregates.js +259 -0
- package/dist/core/lenses/factor-lens.d.ts +42 -0
- package/dist/core/lenses/factor-lens.js +419 -0
- package/dist/core/lenses/index.d.ts +5 -0
- package/dist/core/lenses/index.js +16 -0
- package/dist/core/lenses/memory.d.ts +47 -0
- package/dist/core/lenses/memory.js +102 -0
- package/dist/core/lenses/typed-factor.d.ts +45 -0
- package/dist/core/lenses/typed-factor.js +376 -0
- package/dist/core/network-utils.d.ts +14 -0
- package/dist/core/network-utils.js +62 -0
- package/dist/core/new-primitives.d.ts +33 -0
- package/dist/core/new-primitives.js +113 -0
- package/dist/core/signal.d.ts +254 -0
- package/dist/core/signal.js +1349 -0
- package/dist/core/traits.d.ts +61 -0
- package/dist/core/traits.js +56 -0
- package/dist/core/tree.d.ts +23 -0
- package/dist/core/tree.js +62 -0
- package/dist/core/values/anchor.d.ts +23 -0
- package/dist/core/values/anchor.js +23 -0
- package/dist/core/values/audio.d.ts +33 -0
- package/dist/core/values/audio.js +107 -0
- package/dist/core/values/bool.d.ts +37 -0
- package/dist/core/values/bool.js +75 -0
- package/dist/core/values/box.d.ts +77 -0
- package/dist/core/values/box.js +211 -0
- package/dist/core/values/canvas.d.ts +71 -0
- package/dist/core/values/canvas.js +495 -0
- package/dist/core/values/color.d.ts +49 -0
- package/dist/core/values/color.js +106 -0
- package/dist/core/values/flags.d.ts +18 -0
- package/dist/core/values/flags.js +50 -0
- package/dist/core/values/gpu.d.ts +74 -0
- package/dist/core/values/gpu.js +426 -0
- package/dist/core/values/matrix.d.ts +53 -0
- package/dist/core/values/matrix.js +140 -0
- package/dist/core/values/num.d.ts +62 -0
- package/dist/core/values/num.js +166 -0
- package/dist/core/values/pose.d.ts +31 -0
- package/dist/core/values/pose.js +83 -0
- package/dist/core/values/range.d.ts +83 -0
- package/dist/core/values/range.js +167 -0
- package/dist/core/values/str.d.ts +76 -0
- package/dist/core/values/str.js +346 -0
- package/dist/core/values/template.d.ts +49 -0
- package/dist/core/values/template.js +148 -0
- package/dist/core/values/transform.d.ts +49 -0
- package/dist/core/values/transform.js +115 -0
- package/dist/core/values/tri.d.ts +31 -0
- package/dist/core/values/tri.js +95 -0
- package/dist/core/values/vec.d.ts +72 -0
- package/dist/core/values/vec.js +219 -0
- package/dist/core/writable.d.ts +15 -0
- package/dist/core/writable.js +29 -0
- package/dist/ext/events.d.ts +10 -0
- package/dist/ext/events.js +31 -0
- package/dist/ext/index.d.ts +4 -0
- package/dist/ext/index.js +4 -0
- package/dist/ext/snapshot.d.ts +8 -0
- package/dist/ext/snapshot.js +29 -0
- package/dist/ext/timeline.d.ts +56 -0
- package/dist/ext/timeline.js +94 -0
- package/dist/ext/waapi.d.ts +25 -0
- package/dist/ext/waapi.js +198 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/propagators/index.d.ts +6 -0
- package/dist/propagators/index.js +6 -0
- package/dist/propagators/layout.d.ts +68 -0
- package/dist/propagators/layout.js +336 -0
- package/dist/propagators/network.d.ts +52 -0
- package/dist/propagators/network.js +185 -0
- package/dist/propagators/propagator.d.ts +12 -0
- package/dist/propagators/propagator.js +16 -0
- package/dist/propagators/range.d.ts +45 -0
- package/dist/propagators/range.js +147 -0
- package/dist/propagators/relations.d.ts +60 -0
- package/dist/propagators/relations.js +343 -0
- package/dist/shapes/annular-sector.d.ts +15 -0
- package/dist/shapes/annular-sector.js +64 -0
- package/dist/shapes/button.d.ts +14 -0
- package/dist/shapes/button.js +31 -0
- package/dist/shapes/choreographers.d.ts +22 -0
- package/dist/shapes/choreographers.js +69 -0
- package/dist/shapes/circle.d.ts +17 -0
- package/dist/shapes/circle.js +57 -0
- package/dist/shapes/clip.d.ts +5 -0
- package/dist/shapes/clip.js +31 -0
- package/dist/shapes/connect.d.ts +16 -0
- package/dist/shapes/connect.js +70 -0
- package/dist/shapes/curve.d.ts +60 -0
- package/dist/shapes/curve.js +285 -0
- package/dist/shapes/dashed.d.ts +16 -0
- package/dist/shapes/dashed.js +142 -0
- package/dist/shapes/debug.d.ts +43 -0
- package/dist/shapes/debug.js +97 -0
- package/dist/shapes/group.d.ts +5 -0
- package/dist/shapes/group.js +10 -0
- package/dist/shapes/handle.d.ts +32 -0
- package/dist/shapes/handle.js +88 -0
- package/dist/shapes/index.d.ts +23 -0
- package/dist/shapes/index.js +23 -0
- package/dist/shapes/interaction.d.ts +32 -0
- package/dist/shapes/interaction.js +187 -0
- package/dist/shapes/label.d.ts +20 -0
- package/dist/shapes/label.js +42 -0
- package/dist/shapes/layout.d.ts +29 -0
- package/dist/shapes/layout.js +74 -0
- package/dist/shapes/line.d.ts +21 -0
- package/dist/shapes/line.js +79 -0
- package/dist/shapes/list.d.ts +18 -0
- package/dist/shapes/list.js +51 -0
- package/dist/shapes/mount.d.ts +7 -0
- package/dist/shapes/mount.js +10 -0
- package/dist/shapes/path.d.ts +77 -0
- package/dist/shapes/path.js +227 -0
- package/dist/shapes/rect.d.ts +30 -0
- package/dist/shapes/rect.js +131 -0
- package/dist/shapes/shape.d.ts +132 -0
- package/dist/shapes/shape.js +306 -0
- package/dist/shapes/text.d.ts +24 -0
- package/dist/shapes/text.js +53 -0
- package/dist/shapes/tokens.d.ts +28 -0
- package/dist/shapes/tokens.js +27 -0
- package/dist/shapes/transitions.d.ts +23 -0
- package/dist/shapes/transitions.js +62 -0
- package/dist/tex/decorations.d.ts +26 -0
- package/dist/tex/decorations.js +116 -0
- package/dist/tex/index.d.ts +5 -0
- package/dist/tex/index.js +5 -0
- package/dist/tex/marker.d.ts +17 -0
- package/dist/tex/marker.js +63 -0
- package/dist/tex/motion.d.ts +43 -0
- package/dist/tex/motion.js +290 -0
- package/dist/tex/parts.d.ts +65 -0
- package/dist/tex/parts.js +149 -0
- package/dist/tex/tex.d.ts +45 -0
- package/dist/tex/tex.js +244 -0
- package/dist/web/attr.d.ts +16 -0
- package/dist/web/attr.js +98 -0
- package/dist/web/diagram.d.ts +49 -0
- package/dist/web/diagram.js +260 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +6 -0
- package/dist/web/md-marker.d.ts +6 -0
- package/dist/web/md-marker.js +39 -0
- package/dist/web/md-tex.d.ts +6 -0
- package/dist/web/md-tex.js +61 -0
- package/dist/web/raf.d.ts +6 -0
- package/dist/web/raf.js +24 -0
- package/dist/web/viewport.d.ts +7 -0
- package/dist/web/viewport.js +13 -0
- package/package.json +87 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Bool-signal primitives. The claim algebra is these three plus signal
|
|
2
|
+
// arithmetic; compose with `and` / `or` / `not`.
|
|
3
|
+
//
|
|
4
|
+
// intervals(scope) — Scope → "is this open now?" bool signal.
|
|
5
|
+
// latch(p, init, sc) — invariant/liveness latch, auto re-arm on `sc`.
|
|
6
|
+
// firstOf(...e) — event ordering over bool signals.
|
|
7
|
+
import { cell, derive, effect } from "../core/index.js";
|
|
8
|
+
import { activeRecorder } from "./record.js";
|
|
9
|
+
const ALWAYS_TRUE = derive(() => true);
|
|
10
|
+
/** "Is this scope open right now?" Spans → one-shot interval; scoped
|
|
11
|
+
* factories → "any open invocation"; bool signals pass through. */
|
|
12
|
+
export function intervals(s) {
|
|
13
|
+
if (isScoped(s))
|
|
14
|
+
return s.alive;
|
|
15
|
+
if (isSpan(s)) {
|
|
16
|
+
return derive(() => s.status === "open");
|
|
17
|
+
}
|
|
18
|
+
return s;
|
|
19
|
+
}
|
|
20
|
+
/** Always-true scope; useful as a default when no scope is given. */
|
|
21
|
+
export function always() {
|
|
22
|
+
return ALWAYS_TRUE;
|
|
23
|
+
}
|
|
24
|
+
/** Latch a predicate. Holds at `init` until `pred` is observed `!init`
|
|
25
|
+
* within `scope`, then flips and stays; re-arms on each `scope` rising
|
|
26
|
+
* edge. Outside `scope`, `pred` isn't consulted (latch holds). */
|
|
27
|
+
export function latch(pred, init, scope = ALWAYS_TRUE) {
|
|
28
|
+
const held = cell(init);
|
|
29
|
+
let prevScope = false;
|
|
30
|
+
effect(() => {
|
|
31
|
+
const inScope = scope.value;
|
|
32
|
+
// Track pred.value before any short-circuit so deps stay live.
|
|
33
|
+
const pv = pred.value;
|
|
34
|
+
if (inScope && !prevScope) {
|
|
35
|
+
held.value = init;
|
|
36
|
+
}
|
|
37
|
+
prevScope = inScope;
|
|
38
|
+
if (!inScope)
|
|
39
|
+
return;
|
|
40
|
+
if (held.peek() === init && pv !== init) {
|
|
41
|
+
held.value = !init;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return held;
|
|
45
|
+
}
|
|
46
|
+
/** First false→true edge wins. Returns `{ first, at }` (winner index +
|
|
47
|
+
* recorder-clock time), or undefined until one fires. Sticky once decided. */
|
|
48
|
+
export function firstOf(...events) {
|
|
49
|
+
const result = cell(undefined);
|
|
50
|
+
const prev = events.map(e => e.peek());
|
|
51
|
+
effect(() => {
|
|
52
|
+
if (result.peek() !== undefined) {
|
|
53
|
+
for (const e of events)
|
|
54
|
+
e.value;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < events.length; i++) {
|
|
58
|
+
const v = events[i].value;
|
|
59
|
+
if (v && !prev[i]) {
|
|
60
|
+
const at = activeRecorder()?.anim.clock ?? 0;
|
|
61
|
+
result.value = { first: i, at };
|
|
62
|
+
prev[i] = v;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
prev[i] = v;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
function isScoped(v) {
|
|
71
|
+
return typeof v === "function" && "alive" in v && "last" in v;
|
|
72
|
+
}
|
|
73
|
+
function isSpan(v) {
|
|
74
|
+
return (typeof v === "object" &&
|
|
75
|
+
v !== null &&
|
|
76
|
+
"status" in v &&
|
|
77
|
+
"fn" in v &&
|
|
78
|
+
"id" in v);
|
|
79
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Box, Vec } from "../core/index.js";
|
|
2
|
+
import { type Inner, type Read } from "../core/index.js";
|
|
3
|
+
import { type Scope } from "./algebra.js";
|
|
4
|
+
/** Fluent claim — a labeled bool signal with the algebra. */
|
|
5
|
+
export interface Claim extends Read<boolean> {
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
/** The raw predicate (pre-latching), useful for custom `latch()` shapes. */
|
|
8
|
+
readonly pred: Read<boolean>;
|
|
9
|
+
and(other: Read<boolean>): Claim;
|
|
10
|
+
or(other: Read<boolean>): Claim;
|
|
11
|
+
not(): Claim;
|
|
12
|
+
during(scope: Scope): Claim;
|
|
13
|
+
labelled(name: string): Claim;
|
|
14
|
+
}
|
|
15
|
+
/** Entry point: `claim(sig).stays.in([0,1])`. `label` flows into
|
|
16
|
+
* sub-clause failure messages. */
|
|
17
|
+
export declare function claim<T>(sig: Read<T>, label?: string): SignalClaim<T>;
|
|
18
|
+
/** Mood selector. */
|
|
19
|
+
export interface SignalClaim<T> {
|
|
20
|
+
readonly sig: Read<T>;
|
|
21
|
+
readonly label?: string;
|
|
22
|
+
readonly stays: Predicates<T>;
|
|
23
|
+
readonly becomes: Predicates<T>;
|
|
24
|
+
readonly never: Predicates<T>;
|
|
25
|
+
}
|
|
26
|
+
/** Predicate vocabulary; numeric/vector preds narrow via `this:`. */
|
|
27
|
+
export interface Predicates<T> {
|
|
28
|
+
satisfies(fn: (v: T) => boolean, label?: string): Claim;
|
|
29
|
+
equal(v: T): Claim;
|
|
30
|
+
isEqual(other: Read<T>): Claim;
|
|
31
|
+
in(this: Predicates<number>, range: readonly [number, number]): Claim;
|
|
32
|
+
above(this: Predicates<number>, n: number): Claim;
|
|
33
|
+
below(this: Predicates<number>, n: number): Claim;
|
|
34
|
+
near(this: Predicates<number>, n: number, tol?: number): Claim;
|
|
35
|
+
following(this: Predicates<number>, other: Read<number>, tol?: number): Claim;
|
|
36
|
+
inside(this: Predicates<Inner<Vec>>, region: Box): Claim;
|
|
37
|
+
/** True/false predicates — for moods over already-bool signals. */
|
|
38
|
+
true(this: Predicates<boolean>): Claim;
|
|
39
|
+
false(this: Predicates<boolean>): Claim;
|
|
40
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// claim() — fluent builder over `latch` + the predicate library.
|
|
2
|
+
//
|
|
3
|
+
// claim(sig, "α").stays.in([0, 1]).during(intro)
|
|
4
|
+
import { derive } from "../core/index.js";
|
|
5
|
+
import { intervals, latch } from "./algebra.js";
|
|
6
|
+
import { above, below, equal, following, inRange, inside, isEqual, near } from "./predicates.js";
|
|
7
|
+
/** Entry point: `claim(sig).stays.in([0,1])`. `label` flows into
|
|
8
|
+
* sub-clause failure messages. */
|
|
9
|
+
export function claim(sig, label) {
|
|
10
|
+
return {
|
|
11
|
+
sig,
|
|
12
|
+
label,
|
|
13
|
+
get stays() {
|
|
14
|
+
return predicates(sig, "stays", label);
|
|
15
|
+
},
|
|
16
|
+
get becomes() {
|
|
17
|
+
return predicates(sig, "becomes", label);
|
|
18
|
+
},
|
|
19
|
+
get never() {
|
|
20
|
+
return predicates(sig, "never", label);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function predicates(sig, mood, lbl) {
|
|
25
|
+
const build = (pred, what) => {
|
|
26
|
+
const label = `${lbl ?? "cell"} ${mood} ${what}`;
|
|
27
|
+
// "never": operative predicate is `¬pred`. Carry operative predicate
|
|
28
|
+
// and init through to `during()`.
|
|
29
|
+
switch (mood) {
|
|
30
|
+
case "stays":
|
|
31
|
+
return makeClaim(pred, latch(pred, true), true, label);
|
|
32
|
+
case "never": {
|
|
33
|
+
const negated = derive(() => !pred.value);
|
|
34
|
+
return makeClaim(negated, latch(negated, true), true, label);
|
|
35
|
+
}
|
|
36
|
+
case "becomes":
|
|
37
|
+
return makeClaim(pred, latch(pred, false), false, label);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
satisfies: (fn, what = "predicate") => build(derive(() => fn(sig.value)), what),
|
|
42
|
+
equal: v => build(equal(sig, v), `= ${fmt(v)}`),
|
|
43
|
+
isEqual: other => build(isEqual(sig, other), `= other`),
|
|
44
|
+
in(range) {
|
|
45
|
+
return build(inRange(sig, range), `∈ [${range[0]}, ${range[1]}]`);
|
|
46
|
+
},
|
|
47
|
+
above(n) {
|
|
48
|
+
return build(above(sig, n), `> ${n}`);
|
|
49
|
+
},
|
|
50
|
+
below(n) {
|
|
51
|
+
return build(below(sig, n), `< ${n}`);
|
|
52
|
+
},
|
|
53
|
+
near(n, tol) {
|
|
54
|
+
return build(near(sig, n, tol), `≈ ${n}`);
|
|
55
|
+
},
|
|
56
|
+
following(other, tol) {
|
|
57
|
+
return build(following(sig, other, tol), `≈ other`);
|
|
58
|
+
},
|
|
59
|
+
inside(region) {
|
|
60
|
+
return build(inside(sig, region), `inside`);
|
|
61
|
+
},
|
|
62
|
+
true: () => build(sig, `= true`),
|
|
63
|
+
false: () => build(derive(() => !sig.value), `= false`),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Wrap (predicate, latched-signal, init, label) into a Claim. `init`
|
|
67
|
+
* (true for stays/never, false for becomes) is kept so `.during()`
|
|
68
|
+
* can rebuild the latch; `pred` is the operative (mood-applied) predicate. */
|
|
69
|
+
function makeClaim(pred, latched, init, label) {
|
|
70
|
+
return wrapClaim(pred, latched, init, label);
|
|
71
|
+
}
|
|
72
|
+
function wrapClaim(pred, body, init, label) {
|
|
73
|
+
return {
|
|
74
|
+
get value() {
|
|
75
|
+
return body.value;
|
|
76
|
+
},
|
|
77
|
+
peek() {
|
|
78
|
+
return body.peek();
|
|
79
|
+
},
|
|
80
|
+
label,
|
|
81
|
+
pred,
|
|
82
|
+
and(other) {
|
|
83
|
+
const next = derive(() => body.value && other.value);
|
|
84
|
+
const otherLabel = other.label;
|
|
85
|
+
return wrapClaim(pred, next, init, `${label} ∧ ${otherLabel ?? "?"}`);
|
|
86
|
+
},
|
|
87
|
+
or(other) {
|
|
88
|
+
const next = derive(() => body.value || other.value);
|
|
89
|
+
const otherLabel = other.label;
|
|
90
|
+
return wrapClaim(pred, next, init, `${label} ∨ ${otherLabel ?? "?"}`);
|
|
91
|
+
},
|
|
92
|
+
not() {
|
|
93
|
+
// Flipping init too swaps invariant ↔ liveness.
|
|
94
|
+
return wrapClaim(pred, derive(() => !body.value), !init, `¬(${label})`);
|
|
95
|
+
},
|
|
96
|
+
during(scope) {
|
|
97
|
+
const sc = intervals(scope);
|
|
98
|
+
// Rebuild the latch with the scope so it auto-rearms on each rising
|
|
99
|
+
// edge. Outside the scope the claim is vacuously satisfied.
|
|
100
|
+
const next = latch(pred, init, sc);
|
|
101
|
+
const gated = init === true
|
|
102
|
+
? derive(() => !sc.value || next.value)
|
|
103
|
+
: derive(() => sc.value && next.value);
|
|
104
|
+
return wrapClaim(pred, gated, init, `(${label}) during ${scopeName(scope)}`);
|
|
105
|
+
},
|
|
106
|
+
labelled(name) {
|
|
107
|
+
return wrapClaim(pred, body, init, name);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function scopeName(s) {
|
|
112
|
+
if (typeof s === "function")
|
|
113
|
+
return s.name || "fn";
|
|
114
|
+
if (typeof s === "object" && s !== null && "name" in s) {
|
|
115
|
+
return s.name ?? "span";
|
|
116
|
+
}
|
|
117
|
+
return "scope";
|
|
118
|
+
}
|
|
119
|
+
function fmt(v) {
|
|
120
|
+
if (typeof v === "number")
|
|
121
|
+
return String(+v.toFixed(6));
|
|
122
|
+
if (typeof v === "string")
|
|
123
|
+
return JSON.stringify(v);
|
|
124
|
+
if (v && typeof v === "object" && "x" in v && "y" in v) {
|
|
125
|
+
const p = v;
|
|
126
|
+
return `(${fmt(p.x)}, ${fmt(p.y)})`;
|
|
127
|
+
}
|
|
128
|
+
return String(v);
|
|
129
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { always, firstOf, intervals, latch, type Scope, } from "./algebra.js";
|
|
2
|
+
export { type Claim, claim, type Predicates, type SignalClaim, } from "./claim.js";
|
|
3
|
+
export { above, below, equal, following, inRange, inside, isEqual, near, } from "./predicates.js";
|
|
4
|
+
export { activeRecorder, authorOf, type Recorder, record } from "./record.js";
|
|
5
|
+
export { type Scoped, scope, scopeAll, } from "./scope.js";
|
|
6
|
+
export { addSpanListener, closeSpan, currentSpan, notifySpanOpen, openSpan, type Span, type SpanStatus, withSpan, } from "./span.js";
|
|
7
|
+
export { type TraceBatch, type TraceNode, type TraceTree, traceTree, } from "./tree.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// bireactive/assert — identity, recording, claims.
|
|
2
|
+
//
|
|
3
|
+
// One nominal data type (`Span`); everything else is a function over
|
|
4
|
+
// spans and signals. Three entry points:
|
|
5
|
+
//
|
|
6
|
+
// scope(name, fn) — give a factory identity (becomes traceable).
|
|
7
|
+
// record(anim) — start recording; spans flow into a Read<Span[]>.
|
|
8
|
+
// claim(sig).… — fluent builder over `latch` + predicates.
|
|
9
|
+
//
|
|
10
|
+
// Composition is signal algebra. Predicates, scopes, and claims are
|
|
11
|
+
// all `Read<boolean>` values; `and` / `or` / `not` / `during` are the
|
|
12
|
+
// only verbs. See `_test/assert.test.ts` for the full vocabulary.
|
|
13
|
+
export { always, firstOf, intervals, latch, } from "./algebra.js";
|
|
14
|
+
export { claim, } from "./claim.js";
|
|
15
|
+
export { above, below, equal, following, inRange, inside, isEqual, near, } from "./predicates.js";
|
|
16
|
+
export { activeRecorder, authorOf, record } from "./record.js";
|
|
17
|
+
export { scope, scopeAll, } from "./scope.js";
|
|
18
|
+
export { addSpanListener, closeSpan, currentSpan, notifySpanOpen, openSpan, withSpan, } from "./span.js";
|
|
19
|
+
export { traceTree, } from "./tree.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Box, Vec } from "../core/index.js";
|
|
2
|
+
import { type Inner, type Read } from "../core/index.js";
|
|
3
|
+
/** `lo ≤ s ≤ hi`. */
|
|
4
|
+
export declare function inRange(s: Read<number>, range: readonly [number, number]): Read<boolean>;
|
|
5
|
+
/** `s === v` (strict). */
|
|
6
|
+
export declare function equal<T>(s: Read<T>, v: T): Read<boolean>;
|
|
7
|
+
/** `s > n`. */
|
|
8
|
+
export declare function above(s: Read<number>, n: number): Read<boolean>;
|
|
9
|
+
/** `s < n`. */
|
|
10
|
+
export declare function below(s: Read<number>, n: number): Read<boolean>;
|
|
11
|
+
/** `|s - n| ≤ tol`. */
|
|
12
|
+
export declare function near(s: Read<number>, n: number, tol?: number): Read<boolean>;
|
|
13
|
+
/** Point lies inside a Box (signal or shape's `.box`). */
|
|
14
|
+
export declare function inside(s: Read<Inner<Vec>>, region: Box): Read<boolean>;
|
|
15
|
+
/** `|a - b| ≤ tol`. */
|
|
16
|
+
export declare function following(a: Read<number>, b: Read<number>, tol?: number): Read<boolean>;
|
|
17
|
+
/** Pointwise equality with another signal. */
|
|
18
|
+
export declare function isEqual<T>(a: Read<T>, b: Read<T>): Read<boolean>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Predicate factories. Each returns a plain `derive()`d `Read<boolean>`,
|
|
2
|
+
// composable with `and` / `or` / `not` / hand-rolled `derive()`.
|
|
3
|
+
import { derive } from "../core/index.js";
|
|
4
|
+
/** `lo ≤ s ≤ hi`. */
|
|
5
|
+
export function inRange(s, range) {
|
|
6
|
+
const [lo, hi] = range;
|
|
7
|
+
return derive(() => {
|
|
8
|
+
const v = s.value;
|
|
9
|
+
return v >= lo && v <= hi;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** `s === v` (strict). */
|
|
13
|
+
export function equal(s, v) {
|
|
14
|
+
return derive(() => s.value === v);
|
|
15
|
+
}
|
|
16
|
+
/** `s > n`. */
|
|
17
|
+
export function above(s, n) {
|
|
18
|
+
return derive(() => s.value > n);
|
|
19
|
+
}
|
|
20
|
+
/** `s < n`. */
|
|
21
|
+
export function below(s, n) {
|
|
22
|
+
return derive(() => s.value < n);
|
|
23
|
+
}
|
|
24
|
+
/** `|s - n| ≤ tol`. */
|
|
25
|
+
export function near(s, n, tol = 1e-6) {
|
|
26
|
+
return derive(() => Math.abs(s.value - n) <= tol);
|
|
27
|
+
}
|
|
28
|
+
/** Point lies inside a Box (signal or shape's `.box`). */
|
|
29
|
+
export function inside(s, region) {
|
|
30
|
+
return derive(() => {
|
|
31
|
+
const v = s.value;
|
|
32
|
+
const b = region.value;
|
|
33
|
+
return v.x >= b.x && v.x <= b.x + b.w && v.y >= b.y && v.y <= b.y + b.h;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** `|a - b| ≤ tol`. */
|
|
37
|
+
export function following(a, b, tol = 1e-9) {
|
|
38
|
+
return derive(() => Math.abs(a.value - b.value) <= tol);
|
|
39
|
+
}
|
|
40
|
+
/** Pointwise equality with another signal. */
|
|
41
|
+
export function isEqual(a, b) {
|
|
42
|
+
return derive(() => a.value === b.value);
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Anim } from "../animation/index.js";
|
|
2
|
+
import { type Cell, type Read } from "../core/index.js";
|
|
3
|
+
import { type Span } from "./span.js";
|
|
4
|
+
/** A live recording session. */
|
|
5
|
+
export interface Recorder {
|
|
6
|
+
/** The bound anim; exposed so helpers (e.g. `firstOf`) read the clock. */
|
|
7
|
+
readonly anim: Anim;
|
|
8
|
+
/** Every span seen, in open-time order; re-runs subscribers on open/close. */
|
|
9
|
+
readonly spans: Read<readonly Span[]>;
|
|
10
|
+
/** Disengage hooks. Existing spans stay queryable; no new attribution. */
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Begin recording. Concurrent sessions each receive every span (filter
|
|
14
|
+
* by anim yourself). Stamps start/end from `anim.clock` on open/close. */
|
|
15
|
+
export declare function record(anim: Anim): Recorder;
|
|
16
|
+
/** Any active recorder, or undefined. */
|
|
17
|
+
export declare function activeRecorder(): Recorder | undefined;
|
|
18
|
+
/** Most recent span that wrote to `sig`, or undefined. One memoized
|
|
19
|
+
* Cell per asked-about Cell. */
|
|
20
|
+
export declare function authorOf<T>(sig: Cell<T>): Read<Span | undefined>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// record(anim) — start collecting a live trace.
|
|
2
|
+
//
|
|
3
|
+
// Multiple recorders coexist: span listeners and the signal write hook
|
|
4
|
+
// fan out to all of them, each keeping its own list (two demos on one
|
|
5
|
+
// page get independent traces). The recorder owns the clock — it stamps
|
|
6
|
+
// `span.start` / `span.end` from its `anim.clock`; the engine is unaware.
|
|
7
|
+
import { cell, derive, setCellWriteHook, } from "../core/index.js";
|
|
8
|
+
import { bumpTraceVersion } from "./scope.js";
|
|
9
|
+
import { addSpanListener, currentSpan } from "./span.js";
|
|
10
|
+
/** Active recorders; the write hook fans attribution out to all. */
|
|
11
|
+
const recorders = new Set();
|
|
12
|
+
/** Global write-hook disposer; installed on first recorder, removed on last. */
|
|
13
|
+
let removeWriteHook;
|
|
14
|
+
/** Per-signal "current writer", lazily created by `authorOf`. GC-safe via WeakMap. */
|
|
15
|
+
const writerOf = new WeakMap();
|
|
16
|
+
/** Begin recording. Concurrent sessions each receive every span (filter
|
|
17
|
+
* by anim yourself). Stamps start/end from `anim.clock` on open/close. */
|
|
18
|
+
export function record(anim) {
|
|
19
|
+
const list = [];
|
|
20
|
+
const ver = cell(0);
|
|
21
|
+
const removeListener = addSpanListener(s => {
|
|
22
|
+
s.start = anim.clock;
|
|
23
|
+
list.push(s);
|
|
24
|
+
ver.value++;
|
|
25
|
+
bumpTraceVersion();
|
|
26
|
+
}, s => {
|
|
27
|
+
s.end = anim.clock;
|
|
28
|
+
ver.value++;
|
|
29
|
+
bumpTraceVersion();
|
|
30
|
+
});
|
|
31
|
+
// Install the write hook on first recorder; remove on last stop.
|
|
32
|
+
if (recorders.size === 0) {
|
|
33
|
+
removeWriteHook = setCellWriteHook(sig => {
|
|
34
|
+
const s = currentSpan;
|
|
35
|
+
if (s)
|
|
36
|
+
s.touched.add(sig);
|
|
37
|
+
const writer = writerOf.get(sig);
|
|
38
|
+
if (writer)
|
|
39
|
+
writer.value = s;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const spansRead = derive(() => {
|
|
43
|
+
ver.value;
|
|
44
|
+
return list;
|
|
45
|
+
});
|
|
46
|
+
const recorder = {
|
|
47
|
+
anim,
|
|
48
|
+
spans: spansRead,
|
|
49
|
+
stop() {
|
|
50
|
+
if (!recorders.has(recorder))
|
|
51
|
+
return;
|
|
52
|
+
recorders.delete(recorder);
|
|
53
|
+
removeListener();
|
|
54
|
+
if (recorders.size === 0 && removeWriteHook) {
|
|
55
|
+
removeWriteHook();
|
|
56
|
+
removeWriteHook = undefined;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
recorders.add(recorder);
|
|
61
|
+
return recorder;
|
|
62
|
+
}
|
|
63
|
+
/** Any active recorder, or undefined. */
|
|
64
|
+
export function activeRecorder() {
|
|
65
|
+
for (const r of recorders)
|
|
66
|
+
return r;
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Most recent span that wrote to `sig`, or undefined. One memoized
|
|
70
|
+
* Cell per asked-about Cell. */
|
|
71
|
+
export function authorOf(sig) {
|
|
72
|
+
let writer = writerOf.get(sig);
|
|
73
|
+
if (!writer) {
|
|
74
|
+
writer = cell(undefined);
|
|
75
|
+
writerOf.set(sig, writer);
|
|
76
|
+
}
|
|
77
|
+
return writer;
|
|
78
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Animator } from "../animation/index.js";
|
|
2
|
+
import { type Cell, type Read } from "../core/index.js";
|
|
3
|
+
import { type Span } from "./span.js";
|
|
4
|
+
/** Factory function shape. */
|
|
5
|
+
type AnyFactory = (...args: any[]) => Animator<any>;
|
|
6
|
+
/** Bump so `alive` / `last` / `runs` etc. recompute. Called by record(). */
|
|
7
|
+
export declare function bumpTraceVersion(): void;
|
|
8
|
+
/** Version signal driving the lazy per-factory stat signals. */
|
|
9
|
+
export declare function traceVersionSignal(): Read<number>;
|
|
10
|
+
export declare function recordFactorySpan(s: Span): void;
|
|
11
|
+
export declare function spansOf(fn: Function): readonly Span[];
|
|
12
|
+
/** Scoped factory: callable, carries name + lazy stats. */
|
|
13
|
+
export interface Scoped<F extends AnyFactory> {
|
|
14
|
+
(...args: Parameters<F>): ReturnType<F>;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly alive: Read<boolean>;
|
|
17
|
+
readonly last: Read<Span | undefined>;
|
|
18
|
+
readonly runs: Read<number>;
|
|
19
|
+
/** Total wall-time across all completed spans; in-flight spans
|
|
20
|
+
* contribute up to the current clock. */
|
|
21
|
+
readonly duration: Read<number>;
|
|
22
|
+
/** Signals written during the most recent invocation (self only). */
|
|
23
|
+
readonly touched: Read<readonly Cell<unknown>[]>;
|
|
24
|
+
/** Signals written during the most recent invocation, plus its
|
|
25
|
+
* descendants. */
|
|
26
|
+
readonly touchedDeep: Read<readonly Cell<unknown>[]>;
|
|
27
|
+
}
|
|
28
|
+
/** Wrap `fn` so its invocations open Spans with identity = fn.
|
|
29
|
+
*
|
|
30
|
+
* const fadeIn = scope("fadeIn", function* () { … });
|
|
31
|
+
* const fadeIn = scope(function* () { … });
|
|
32
|
+
*
|
|
33
|
+
* Prefer the name-first form: bare `fn.name` gets renamed by bundlers
|
|
34
|
+
* when an inner `function* fadeIn` collides with an outer `const fadeIn`. */
|
|
35
|
+
export declare function scope<F extends AnyFactory>(fn: F): Scoped<F>;
|
|
36
|
+
export declare function scope<F extends AnyFactory>(name: string, fn: F): Scoped<F>;
|
|
37
|
+
export declare function rememberFactory(fn: Function): void;
|
|
38
|
+
/** Batch-scope a record of factories; each key becomes the `name`. */
|
|
39
|
+
export declare function scopeAll<R extends Record<string, AnyFactory>>(o: R): {
|
|
40
|
+
[K in keyof R]: Scoped<R[K]>;
|
|
41
|
+
};
|
|
42
|
+
export {};
|