@unrulysystems/native-motion-core 0.1.0-alpha.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/clock.cjs +71 -0
- package/dist/clock.d.cts +23 -0
- package/dist/clock.d.ts +23 -0
- package/dist/clock.js +66 -0
- package/dist/collect-reads.cjs +68 -0
- package/dist/collect-reads.d.cts +23 -0
- package/dist/collect-reads.d.ts +23 -0
- package/dist/collect-reads.js +62 -0
- package/dist/component/boundedArray.cjs +74 -0
- package/dist/component/boundedArray.d.cts +25 -0
- package/dist/component/boundedArray.d.ts +25 -0
- package/dist/component/boundedArray.js +68 -0
- package/dist/component/index.cjs +57 -0
- package/dist/component/index.d.cts +12 -0
- package/dist/component/index.d.ts +12 -0
- package/dist/component/index.js +22 -0
- package/dist/component/orchestration.cjs +129 -0
- package/dist/component/orchestration.d.cts +66 -0
- package/dist/component/orchestration.d.ts +66 -0
- package/dist/component/orchestration.js +124 -0
- package/dist/component/resolve.cjs +104 -0
- package/dist/component/resolve.d.cts +11 -0
- package/dist/component/resolve.d.ts +11 -0
- package/dist/component/resolve.js +97 -0
- package/dist/component/transition.cjs +352 -0
- package/dist/component/transition.d.cts +33 -0
- package/dist/component/transition.d.ts +33 -0
- package/dist/component/transition.js +339 -0
- package/dist/component/types.cjs +114 -0
- package/dist/component/types.d.cts +60 -0
- package/dist/component/types.d.ts +60 -0
- package/dist/component/types.js +111 -0
- package/dist/component/validate.cjs +1015 -0
- package/dist/component/validate.d.cts +37 -0
- package/dist/component/validate.d.ts +37 -0
- package/dist/component/validate.js +1002 -0
- package/dist/component/variants.cjs +333 -0
- package/dist/component/variants.d.cts +106 -0
- package/dist/component/variants.d.ts +106 -0
- package/dist/component/variants.js +321 -0
- package/dist/config/constants.cjs +41 -0
- package/dist/config/constants.d.cts +29 -0
- package/dist/config/constants.d.ts +29 -0
- package/dist/config/constants.js +38 -0
- package/dist/delay.cjs +72 -0
- package/dist/delay.d.cts +26 -0
- package/dist/delay.d.ts +26 -0
- package/dist/delay.js +70 -0
- package/dist/derived.cjs +143 -0
- package/dist/derived.d.cts +41 -0
- package/dist/derived.d.ts +41 -0
- package/dist/derived.js +139 -0
- package/dist/driver/index.cjs +22 -0
- package/dist/driver/index.d.cts +7 -0
- package/dist/driver/index.d.ts +7 -0
- package/dist/driver/index.js +14 -0
- package/dist/driver/keyframeTiming.cjs +130 -0
- package/dist/driver/keyframeTiming.d.cts +18 -0
- package/dist/driver/keyframeTiming.d.ts +18 -0
- package/dist/driver/keyframeTiming.js +124 -0
- package/dist/driver/keyframeTimingConfig.cjs +24 -0
- package/dist/driver/keyframeTimingConfig.d.cts +5 -0
- package/dist/driver/keyframeTimingConfig.d.ts +5 -0
- package/dist/driver/keyframeTimingConfig.js +22 -0
- package/dist/driver/prepare.cjs +459 -0
- package/dist/driver/prepare.d.cts +4 -0
- package/dist/driver/prepare.d.ts +4 -0
- package/dist/driver/prepare.js +454 -0
- package/dist/driver/reference.cjs +762 -0
- package/dist/driver/reference.d.cts +53 -0
- package/dist/driver/reference.d.ts +53 -0
- package/dist/driver/reference.js +757 -0
- package/dist/driver/step.cjs +55 -0
- package/dist/driver/step.d.cts +9 -0
- package/dist/driver/step.d.ts +9 -0
- package/dist/driver/step.js +52 -0
- package/dist/driver/tiers.cjs +40 -0
- package/dist/driver/tiers.d.cts +2 -0
- package/dist/driver/tiers.d.ts +2 -0
- package/dist/driver/tiers.js +37 -0
- package/dist/driver/types.cjs +9 -0
- package/dist/driver/types.d.cts +68 -0
- package/dist/driver/types.d.ts +68 -0
- package/dist/driver/types.js +8 -0
- package/dist/external-animation-ledger.cjs +1250 -0
- package/dist/external-animation-ledger.d.cts +416 -0
- package/dist/external-animation-ledger.d.ts +416 -0
- package/dist/external-animation-ledger.js +1243 -0
- package/dist/gesture/directionLock.cjs +25 -0
- package/dist/gesture/directionLock.d.cts +8 -0
- package/dist/gesture/directionLock.d.ts +8 -0
- package/dist/gesture/directionLock.js +21 -0
- package/dist/gesture/dragConfig.cjs +634 -0
- package/dist/gesture/dragConfig.d.cts +298 -0
- package/dist/gesture/dragConfig.d.ts +298 -0
- package/dist/gesture/dragConfig.js +624 -0
- package/dist/gesture/elastic.cjs +44 -0
- package/dist/gesture/elastic.d.cts +4 -0
- package/dist/gesture/elastic.d.ts +4 -0
- package/dist/gesture/elastic.js +39 -0
- package/dist/gesture/handoffSession.cjs +161 -0
- package/dist/gesture/handoffSession.d.cts +35 -0
- package/dist/gesture/handoffSession.d.ts +35 -0
- package/dist/gesture/handoffSession.js +158 -0
- package/dist/gesture/index.cjs +37 -0
- package/dist/gesture/index.d.cts +12 -0
- package/dist/gesture/index.d.ts +12 -0
- package/dist/gesture/index.js +11 -0
- package/dist/gesture/projection.cjs +95 -0
- package/dist/gesture/projection.d.cts +7 -0
- package/dist/gesture/projection.d.ts +7 -0
- package/dist/gesture/projection.js +87 -0
- package/dist/gesture/session.cjs +162 -0
- package/dist/gesture/session.d.cts +29 -0
- package/dist/gesture/session.d.ts +29 -0
- package/dist/gesture/session.js +158 -0
- package/dist/gesture/types.cjs +5 -0
- package/dist/gesture/types.d.cts +13 -0
- package/dist/gesture/types.d.ts +13 -0
- package/dist/gesture/types.js +4 -0
- package/dist/gesture/viewportConstraints.cjs +38 -0
- package/dist/gesture/viewportConstraints.d.cts +6 -0
- package/dist/gesture/viewportConstraints.d.ts +6 -0
- package/dist/gesture/viewportConstraints.js +34 -0
- package/dist/graph.cjs +226 -0
- package/dist/graph.d.cts +2 -0
- package/dist/graph.d.ts +2 -0
- package/dist/graph.js +223 -0
- package/dist/index.cjs +140 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +70 -0
- package/dist/inertia.cjs +214 -0
- package/dist/inertia.d.cts +53 -0
- package/dist/inertia.d.ts +53 -0
- package/dist/inertia.js +212 -0
- package/dist/instant.cjs +66 -0
- package/dist/instant.d.cts +16 -0
- package/dist/instant.d.ts +16 -0
- package/dist/instant.js +63 -0
- package/dist/internal-driver.cjs +81 -0
- package/dist/internal-driver.d.cts +19 -0
- package/dist/internal-driver.d.ts +19 -0
- package/dist/internal-driver.js +35 -0
- package/dist/keyframes.cjs +191 -0
- package/dist/keyframes.d.cts +13 -0
- package/dist/keyframes.d.ts +13 -0
- package/dist/keyframes.js +188 -0
- package/dist/layout/commitDetector.cjs +67 -0
- package/dist/layout/commitDetector.d.cts +21 -0
- package/dist/layout/commitDetector.d.ts +21 -0
- package/dist/layout/commitDetector.js +64 -0
- package/dist/layout/compose.cjs +67 -0
- package/dist/layout/compose.d.cts +30 -0
- package/dist/layout/compose.d.ts +30 -0
- package/dist/layout/compose.js +65 -0
- package/dist/layout/constants.cjs +13 -0
- package/dist/layout/constants.d.cts +6 -0
- package/dist/layout/constants.d.ts +6 -0
- package/dist/layout/constants.js +10 -0
- package/dist/layout/identity.cjs +700 -0
- package/dist/layout/identity.d.cts +67 -0
- package/dist/layout/identity.d.ts +67 -0
- package/dist/layout/identity.js +698 -0
- package/dist/layout/index.cjs +36 -0
- package/dist/layout/index.d.cts +18 -0
- package/dist/layout/index.d.ts +18 -0
- package/dist/layout/index.js +13 -0
- package/dist/layout/measure.cjs +81 -0
- package/dist/layout/measure.d.cts +43 -0
- package/dist/layout/measure.d.ts +43 -0
- package/dist/layout/measure.js +78 -0
- package/dist/layout/projection.cjs +99 -0
- package/dist/layout/projection.d.cts +23 -0
- package/dist/layout/projection.d.ts +23 -0
- package/dist/layout/projection.js +97 -0
- package/dist/layout/scroll.cjs +27 -0
- package/dist/layout/scroll.d.cts +13 -0
- package/dist/layout/scroll.d.ts +13 -0
- package/dist/layout/scroll.js +24 -0
- package/dist/layout/session.cjs +207 -0
- package/dist/layout/session.d.cts +73 -0
- package/dist/layout/session.d.ts +73 -0
- package/dist/layout/session.js +205 -0
- package/dist/layout/tree.cjs +826 -0
- package/dist/layout/tree.d.cts +70 -0
- package/dist/layout/tree.d.ts +70 -0
- package/dist/layout/tree.js +823 -0
- package/dist/layout/types.cjs +36 -0
- package/dist/layout/types.d.cts +31 -0
- package/dist/layout/types.d.ts +31 -0
- package/dist/layout/types.js +35 -0
- package/dist/motion-arc.cjs +184 -0
- package/dist/motion-arc.d.cts +78 -0
- package/dist/motion-arc.d.ts +78 -0
- package/dist/motion-arc.js +183 -0
- package/dist/motion-mix.cjs +205 -0
- package/dist/motion-mix.d.cts +3 -0
- package/dist/motion-mix.d.ts +3 -0
- package/dist/motion-mix.js +202 -0
- package/dist/motion-value-driver-port.cjs +636 -0
- package/dist/motion-value-driver-port.d.cts +406 -0
- package/dist/motion-value-driver-port.d.ts +406 -0
- package/dist/motion-value-driver-port.js +627 -0
- package/dist/motion-value.cjs +189 -0
- package/dist/motion-value.d.cts +51 -0
- package/dist/motion-value.d.ts +51 -0
- package/dist/motion-value.js +185 -0
- package/dist/presence/controller.cjs +657 -0
- package/dist/presence/controller.d.cts +6 -0
- package/dist/presence/controller.d.ts +6 -0
- package/dist/presence/controller.js +652 -0
- package/dist/presence/index.cjs +19 -0
- package/dist/presence/index.d.cts +4 -0
- package/dist/presence/index.d.ts +4 -0
- package/dist/presence/index.js +12 -0
- package/dist/presence/machine.cjs +50 -0
- package/dist/presence/machine.d.cts +10 -0
- package/dist/presence/machine.d.ts +10 -0
- package/dist/presence/machine.js +46 -0
- package/dist/presence/types.cjs +6 -0
- package/dist/presence/types.d.cts +32 -0
- package/dist/presence/types.d.ts +32 -0
- package/dist/presence/types.js +5 -0
- package/dist/repeat.cjs +311 -0
- package/dist/repeat.d.cts +174 -0
- package/dist/repeat.d.ts +174 -0
- package/dist/repeat.js +300 -0
- package/dist/spring.cjs +128 -0
- package/dist/spring.d.cts +18 -0
- package/dist/spring.d.ts +18 -0
- package/dist/spring.js +125 -0
- package/dist/subscriptions.cjs +74 -0
- package/dist/subscriptions.d.cts +19 -0
- package/dist/subscriptions.d.ts +19 -0
- package/dist/subscriptions.js +69 -0
- package/dist/subset/index.cjs +23 -0
- package/dist/subset/index.d.cts +6 -0
- package/dist/subset/index.d.ts +6 -0
- package/dist/subset/index.js +13 -0
- package/dist/subset/normalize.cjs +90 -0
- package/dist/subset/normalize.d.cts +10 -0
- package/dist/subset/normalize.d.ts +10 -0
- package/dist/subset/normalize.js +85 -0
- package/dist/subset/registry.cjs +259 -0
- package/dist/subset/registry.d.cts +25 -0
- package/dist/subset/registry.d.ts +25 -0
- package/dist/subset/registry.js +256 -0
- package/dist/subset/resolve.cjs +98 -0
- package/dist/subset/resolve.d.cts +24 -0
- package/dist/subset/resolve.d.ts +24 -0
- package/dist/subset/resolve.js +90 -0
- package/dist/timing.cjs +206 -0
- package/dist/timing.d.cts +17 -0
- package/dist/timing.d.ts +17 -0
- package/dist/timing.js +201 -0
- package/dist/transformTemplate.cjs +219 -0
- package/dist/transformTemplate.d.cts +29 -0
- package/dist/transformTemplate.d.ts +29 -0
- package/dist/transformTemplate.js +215 -0
- package/dist/transition.cjs +231 -0
- package/dist/transition.d.cts +89 -0
- package/dist/transition.d.ts +89 -0
- package/dist/transition.js +223 -0
- package/dist/types.cjs +4 -0
- package/dist/types.d.cts +175 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.js +3 -0
- package/dist/value-types/color.cjs +220 -0
- package/dist/value-types/color.d.cts +19 -0
- package/dist/value-types/color.d.ts +19 -0
- package/dist/value-types/color.js +218 -0
- package/dist/value-types/complex.cjs +160 -0
- package/dist/value-types/complex.d.cts +19 -0
- package/dist/value-types/complex.d.ts +19 -0
- package/dist/value-types/complex.js +155 -0
- package/dist/value-types/constants.cjs +15 -0
- package/dist/value-types/constants.d.cts +6 -0
- package/dist/value-types/constants.d.ts +6 -0
- package/dist/value-types/constants.js +12 -0
- package/dist/value-types/discrete.cjs +62 -0
- package/dist/value-types/discrete.d.cts +4 -0
- package/dist/value-types/discrete.d.ts +4 -0
- package/dist/value-types/discrete.js +56 -0
- package/dist/value-types/index.cjs +59 -0
- package/dist/value-types/index.d.cts +14 -0
- package/dist/value-types/index.d.ts +14 -0
- package/dist/value-types/index.js +24 -0
- package/dist/value-types/measure-resolve.cjs +325 -0
- package/dist/value-types/measure-resolve.d.cts +91 -0
- package/dist/value-types/measure-resolve.d.ts +91 -0
- package/dist/value-types/measure-resolve.js +313 -0
- package/dist/value-types/mix.cjs +90 -0
- package/dist/value-types/mix.d.cts +27 -0
- package/dist/value-types/mix.d.ts +27 -0
- package/dist/value-types/mix.js +85 -0
- package/dist/value-types/named-colors.cjs +61 -0
- package/dist/value-types/named-colors.d.cts +2 -0
- package/dist/value-types/named-colors.d.ts +2 -0
- package/dist/value-types/named-colors.js +58 -0
- package/dist/value-types/numeric.cjs +86 -0
- package/dist/value-types/numeric.d.cts +16 -0
- package/dist/value-types/numeric.d.ts +16 -0
- package/dist/value-types/numeric.js +79 -0
- package/dist/worklet-layout/config/constants.js +39 -0
- package/dist/worklet-layout/layout/constants.js +11 -0
- package/dist/worklet-layout/layout/identity.js +699 -0
- package/dist/worklet-layout/layout/projection.js +98 -0
- package/dist/worklet-layout/layout/session.js +206 -0
- package/dist/worklet-layout/layout/tree.js +824 -0
- package/dist/worklet-layout/layout/types.js +36 -0
- package/dist/worklet-layout/spring.js +125 -0
- package/dist/worklet-layout/timing.js +201 -0
- package/dist/worklet-layout/transition.js +223 -0
- package/dist/worklet-layout.cjs +47 -0
- package/dist/worklet-layout.d.cts +15 -0
- package/dist/worklet-layout.d.ts +15 -0
- package/dist/worklet-layout.js +28 -0
- package/dist/wrap.cjs +7 -0
- package/dist/wrap.d.cts +1 -0
- package/dist/wrap.d.ts +1 -0
- package/dist/wrap.js +4 -0
- package/package.json +45 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Private, host-free CORE seam for an exact public MotionValue driven by an external runtime
|
|
3
|
+
// (REQ-VALUE-014/015). Implements the direct-public creation brand, binding eligibility, the
|
|
4
|
+
// direct-operation epoch with synchronous public-method order, the token-blind public control
|
|
5
|
+
// slot, transported write/callback application with drain-progress accounting, guarded
|
|
6
|
+
// candidate-cache terminals, and the local-frame transition event port. Native adapters import
|
|
7
|
+
// it only through `internal-driver`.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MotionValueDriverPortUnimplementedError = exports.MOTION_VALUE_DRIVER_PORT_UNIMPLEMENTED = exports.MotionValueDriverPortInvariantError = exports.MOTION_VALUE_DRIVER_PORT_INVARIANT = void 0;
|
|
10
|
+
exports.createDirectPublicMotionValue = createDirectPublicMotionValue;
|
|
11
|
+
exports.isDirectPublicNumericMotionValue = isDirectPublicNumericMotionValue;
|
|
12
|
+
exports.createLocalFrameEventPort = createLocalFrameEventPort;
|
|
13
|
+
exports.createMotionValueDriverPort = createMotionValueDriverPort;
|
|
14
|
+
const constants_1 = require("./config/constants.cjs");
|
|
15
|
+
const motion_value_1 = require("./motion-value.cjs");
|
|
16
|
+
exports.MOTION_VALUE_DRIVER_PORT_INVARIANT = 'NATIVE_MOTION_CORE_MOTION_VALUE_DRIVER_PORT_INVARIANT';
|
|
17
|
+
/** Typed fail-closed refusal for an unusable restored public-control slot. */
|
|
18
|
+
class MotionValueDriverPortInvariantError extends Error {
|
|
19
|
+
code = exports.MOTION_VALUE_DRIVER_PORT_INVARIANT;
|
|
20
|
+
invariant;
|
|
21
|
+
constructor(invariant) {
|
|
22
|
+
super(`native-motion-core: MotionValue driver-port invariant '${invariant}' was violated ` +
|
|
23
|
+
'(REQ-VALUE-015)');
|
|
24
|
+
this.name = 'MotionValueDriverPortInvariantError';
|
|
25
|
+
this.invariant = invariant;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.MotionValueDriverPortInvariantError = MotionValueDriverPortInvariantError;
|
|
29
|
+
exports.MOTION_VALUE_DRIVER_PORT_UNIMPLEMENTED = 'NATIVE_MOTION_CORE_MOTION_VALUE_DRIVER_PORT_UNIMPLEMENTED';
|
|
30
|
+
class MotionValueDriverPortUnimplementedError extends Error {
|
|
31
|
+
code = exports.MOTION_VALUE_DRIVER_PORT_UNIMPLEMENTED;
|
|
32
|
+
operation;
|
|
33
|
+
constructor(operation) {
|
|
34
|
+
super(`native-motion-core: MotionValue driver-port operation '${operation}' is unimplemented ` +
|
|
35
|
+
'(REQ-VALUE-014/015)');
|
|
36
|
+
this.name = 'MotionValueDriverPortUnimplementedError';
|
|
37
|
+
this.operation = operation;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.MotionValueDriverPortUnimplementedError = MotionValueDriverPortUnimplementedError;
|
|
41
|
+
const DIRECT_PUBLIC_CREATION = new WeakMap();
|
|
42
|
+
function initializerProvenance(initial) {
|
|
43
|
+
if (typeof initial !== 'number')
|
|
44
|
+
return { kind: 'ineligible', reason: 'non-number' };
|
|
45
|
+
if (!Number.isFinite(initial))
|
|
46
|
+
return { kind: 'ineligible', reason: 'non-finite-number' };
|
|
47
|
+
return { kind: 'finite-number' };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The one private direct-public creation seam used by native `useMotionValue`. It returns the exact
|
|
51
|
+
* graph object and records immutable initializer provenance without widening the public graph API.
|
|
52
|
+
*/
|
|
53
|
+
function createDirectPublicMotionValue(graph, initial) {
|
|
54
|
+
const value = graph.motionValue(initial);
|
|
55
|
+
DIRECT_PUBLIC_CREATION.set(value, {
|
|
56
|
+
kind: 'direct-public',
|
|
57
|
+
initializer: initializerProvenance(initial),
|
|
58
|
+
});
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Private mount-time routing predicate for the identity channel. It reads only the immutable
|
|
63
|
+
* creation brand plus the caller's collector-silent mount snapshot; behavioral eligibility remains
|
|
64
|
+
* owned by `MotionValueDriverPort.evaluateBindingEligibility` once the canonical port is attached.
|
|
65
|
+
*/
|
|
66
|
+
function isDirectPublicNumericMotionValue(value, currentValue) {
|
|
67
|
+
const creation = DIRECT_PUBLIC_CREATION.get(value);
|
|
68
|
+
if (creation?.kind !== 'direct-public' || creation.initializer.kind !== 'finite-number') {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return typeof currentValue === 'number' && Number.isFinite(currentValue);
|
|
72
|
+
}
|
|
73
|
+
function defaultPortState(rawSnapshot, channelId) {
|
|
74
|
+
return {
|
|
75
|
+
channelId,
|
|
76
|
+
rawCurrentValue: rawSnapshot.currentValue,
|
|
77
|
+
history: rawSnapshot.history,
|
|
78
|
+
directOperationEpoch: rawSnapshot.directOperationEpoch,
|
|
79
|
+
authoritativeSequence: rawSnapshot.authoritativeSequence,
|
|
80
|
+
currentExecutionId: null,
|
|
81
|
+
executionLease: 0,
|
|
82
|
+
publicControlSlot: { kind: 'empty' },
|
|
83
|
+
acceptedWriteSequence: 0,
|
|
84
|
+
demandedWriteSequence: 0,
|
|
85
|
+
deliveredEventWatermark: 0,
|
|
86
|
+
synchronousSnapshotReadCount: 0,
|
|
87
|
+
outboundOriginCommandCount: 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Pinned finite-difference velocity (motion-dom `getVelocity`): last two timed samples, delta
|
|
92
|
+
* clamped to the staleness window, zero for a non-positive delta or a single sample.
|
|
93
|
+
*/
|
|
94
|
+
function finiteDifferenceVelocity(history) {
|
|
95
|
+
if (history.kind !== 'finite-difference')
|
|
96
|
+
return 0;
|
|
97
|
+
const dt = Math.min(history.currentTimestampMs - history.previousTimestampMs, constants_1.MAX_VELOCITY_DELTA_MS);
|
|
98
|
+
if (dt <= 0)
|
|
99
|
+
return 0;
|
|
100
|
+
return ((history.currentValue - history.previousValue) * 1000) / dt;
|
|
101
|
+
}
|
|
102
|
+
class MotionValueDriverPortImpl {
|
|
103
|
+
value;
|
|
104
|
+
#impl;
|
|
105
|
+
#creation;
|
|
106
|
+
#state;
|
|
107
|
+
constructor(value, creation, state, initialPublicControlSlotInstallation) {
|
|
108
|
+
this.value = value;
|
|
109
|
+
this.#impl = value instanceof motion_value_1.MotionValueImpl ? value : null;
|
|
110
|
+
this.#creation = creation;
|
|
111
|
+
// Copy so the caller's serializable record is never mutated through the port.
|
|
112
|
+
this.#state = { ...state };
|
|
113
|
+
if (initialPublicControlSlotInstallation !== null) {
|
|
114
|
+
this.#installSlot(initialPublicControlSlotInstallation);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Every behavioral operation owns the exact graph-created value (the eligibility law admits
|
|
119
|
+
* only direct-public identities, which this graph created). A structural duck never reaches
|
|
120
|
+
* them: fail closed with a typed invariant, never a raw TypeError.
|
|
121
|
+
*/
|
|
122
|
+
#requireGraphMotionValue() {
|
|
123
|
+
const impl = this.#impl;
|
|
124
|
+
if (impl === null) {
|
|
125
|
+
throw new MotionValueDriverPortInvariantError('value-not-graph-motion-value');
|
|
126
|
+
}
|
|
127
|
+
return impl;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The public control slot IS `isAnimating()` (the pinned `value.animation` member): install the
|
|
131
|
+
* liveness hook whose invocation stops exactly this installation's physical execution. Motion's
|
|
132
|
+
* stop/jump order — cancellation listeners, then the physical stop, then the slot clear — falls
|
|
133
|
+
* out of `MotionValueImpl.stop()` running this hook after its notification.
|
|
134
|
+
*/
|
|
135
|
+
#installSlot(installation) {
|
|
136
|
+
const impl = this.#requireGraphMotionValue();
|
|
137
|
+
this.#state.publicControlSlot = {
|
|
138
|
+
kind: 'installed',
|
|
139
|
+
controlId: installation.controlId,
|
|
140
|
+
executionId: installation.executionId,
|
|
141
|
+
};
|
|
142
|
+
impl.attachAnimationStopper(() => {
|
|
143
|
+
installation.stopPhysicalExecution();
|
|
144
|
+
impl.endAnimation();
|
|
145
|
+
this.#state.publicControlSlot = { kind: 'empty' };
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
observe() {
|
|
149
|
+
const current = this.#state.rawCurrentValue;
|
|
150
|
+
const currentValueKind = typeof current !== 'number'
|
|
151
|
+
? 'non-number'
|
|
152
|
+
: Number.isFinite(current)
|
|
153
|
+
? 'finite-number'
|
|
154
|
+
: 'non-finite-number';
|
|
155
|
+
const observedInitializerProvenance = this.#creation.kind === 'unrecognized'
|
|
156
|
+
? 'unrecognized'
|
|
157
|
+
: this.#creation.initializer.kind === 'finite-number'
|
|
158
|
+
? 'finite-number'
|
|
159
|
+
: this.#creation.initializer.reason;
|
|
160
|
+
const history = this.#state.history;
|
|
161
|
+
return {
|
|
162
|
+
channelId: this.#state.channelId,
|
|
163
|
+
creationKind: this.#creation.kind,
|
|
164
|
+
initializerProvenance: observedInitializerProvenance,
|
|
165
|
+
currentValueKind,
|
|
166
|
+
cachedCurrentValue: history.kind === 'unavailable' ? null : history.currentValue,
|
|
167
|
+
cachedPreviousValue: history.kind === 'finite-difference' ? history.previousValue : null,
|
|
168
|
+
cachedCurrentTimestampMs: history.kind === 'unavailable' ? null : history.currentTimestampMs,
|
|
169
|
+
cachedPreviousTimestampMs: history.kind === 'finite-difference' ? history.previousTimestampMs : null,
|
|
170
|
+
directOperationEpoch: this.#state.directOperationEpoch,
|
|
171
|
+
authoritativeSequence: this.#state.authoritativeSequence,
|
|
172
|
+
currentExecutionId: this.#state.currentExecutionId,
|
|
173
|
+
executionLease: this.#state.executionLease,
|
|
174
|
+
publicControlSlotControlId: this.#state.publicControlSlot.kind === 'empty'
|
|
175
|
+
? null
|
|
176
|
+
: this.#state.publicControlSlot.controlId,
|
|
177
|
+
publicControlSlotExecutionId: this.#state.publicControlSlot.kind === 'empty'
|
|
178
|
+
? null
|
|
179
|
+
: this.#state.publicControlSlot.executionId,
|
|
180
|
+
acceptedWriteSequence: this.#state.acceptedWriteSequence,
|
|
181
|
+
demandedWriteSequence: this.#state.demandedWriteSequence,
|
|
182
|
+
deliveredEventWatermark: this.#state.deliveredEventWatermark,
|
|
183
|
+
synchronousSnapshotReadCount: this.#state.synchronousSnapshotReadCount,
|
|
184
|
+
outboundOriginCommandCount: this.#state.outboundOriginCommandCount,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
evaluateBindingEligibility(currentValue) {
|
|
188
|
+
if (this.#creation.kind !== 'direct-public') {
|
|
189
|
+
return { kind: 'ineligible', reason: 'not-direct-public' };
|
|
190
|
+
}
|
|
191
|
+
// The initializer brand is immutable: a finite `set` after a non-numeric/non-finite
|
|
192
|
+
// initializer never upgrades the value (the creation provenance is the law, not the value).
|
|
193
|
+
if (this.#creation.initializer.kind !== 'finite-number') {
|
|
194
|
+
return { kind: 'ineligible', reason: 'initializer-not-finite-number' };
|
|
195
|
+
}
|
|
196
|
+
if (typeof currentValue !== 'number' || !Number.isFinite(currentValue)) {
|
|
197
|
+
return { kind: 'ineligible', reason: 'current-not-finite-number' };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
kind: 'eligible',
|
|
201
|
+
creationKind: 'direct-public',
|
|
202
|
+
initializerProvenance: 'finite-number',
|
|
203
|
+
currentValue,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
beginDirectOperation(operation) {
|
|
207
|
+
const impl = this.#requireGraphMotionValue();
|
|
208
|
+
// The epoch advances BEFORE the synchronous public method, so change/cancel/destroy
|
|
209
|
+
// listeners already observe the new operation epoch (pinned public-method order).
|
|
210
|
+
this.#state.directOperationEpoch += 1;
|
|
211
|
+
this.#state.outboundOriginCommandCount += 1;
|
|
212
|
+
switch (operation.kind) {
|
|
213
|
+
case 'set':
|
|
214
|
+
impl.set(operation.value);
|
|
215
|
+
this.#state.rawCurrentValue = operation.value;
|
|
216
|
+
break;
|
|
217
|
+
case 'jump':
|
|
218
|
+
impl.jump(operation.value);
|
|
219
|
+
this.#state.rawCurrentValue = operation.value;
|
|
220
|
+
break;
|
|
221
|
+
case 'stop':
|
|
222
|
+
// With an installed slot this routes through its exact stop capability; with a graph
|
|
223
|
+
// animation through the graph's stopper. Either way the value's own `stop()` order holds.
|
|
224
|
+
impl.stop();
|
|
225
|
+
break;
|
|
226
|
+
case 'destroy':
|
|
227
|
+
impl.destroy();
|
|
228
|
+
break;
|
|
229
|
+
case 'start':
|
|
230
|
+
// A new execution supersedes the prior one: identify it and roll the lease so the prior
|
|
231
|
+
// execution's transported samples reject as stale from here on.
|
|
232
|
+
this.#state.currentExecutionId = operation.executionId;
|
|
233
|
+
this.#state.executionLease += 1;
|
|
234
|
+
break;
|
|
235
|
+
default: {
|
|
236
|
+
const exhaustive = operation;
|
|
237
|
+
void exhaustive;
|
|
238
|
+
throw new MotionValueDriverPortInvariantError('direct-operation-kind-unrecognized');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
kind: 'direct-operation-begun',
|
|
243
|
+
operation: operation.kind,
|
|
244
|
+
directOperationEpoch: this.#state.directOperationEpoch,
|
|
245
|
+
outboundOriginCommand: 'required',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
installPublicControlSlot(installation) {
|
|
249
|
+
this.#installSlot(installation);
|
|
250
|
+
return {
|
|
251
|
+
kind: 'public-control-slot-updated',
|
|
252
|
+
publicControlSlot: this.#state.publicControlSlot,
|
|
253
|
+
physicalExecution: 'retained',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
clearPublicControlSlotTokenBlind(causedByExecutionId) {
|
|
257
|
+
// Token-blind law (pinned completion/cancellation continuation): the post-listener slot
|
|
258
|
+
// clears unconditionally — never matched against the causing execution and NEVER stopping
|
|
259
|
+
// the retained physical execution, which may keep sampling as an orphan.
|
|
260
|
+
void causedByExecutionId;
|
|
261
|
+
const impl = this.#requireGraphMotionValue();
|
|
262
|
+
this.#state.publicControlSlot = { kind: 'empty' };
|
|
263
|
+
impl.detachAnimationStopper();
|
|
264
|
+
return {
|
|
265
|
+
kind: 'public-control-slot-updated',
|
|
266
|
+
publicControlSlot: this.#state.publicControlSlot,
|
|
267
|
+
physicalExecution: 'retained',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
applyTransportedSample(sample, progress) {
|
|
271
|
+
const impl = this.#requireGraphMotionValue();
|
|
272
|
+
switch (sample.kind) {
|
|
273
|
+
case 'generator-sample': {
|
|
274
|
+
const armedCompletion = sample.completion.kind === 'armed' ? 'retained' : 'not-armed';
|
|
275
|
+
if (sample.directOperationEpoch < this.#state.directOperationEpoch) {
|
|
276
|
+
return {
|
|
277
|
+
kind: 'rejected',
|
|
278
|
+
reason: 'stale-direct-operation-epoch',
|
|
279
|
+
armedCompletion,
|
|
280
|
+
originEcho: 'suppressed',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (sample.executionLease < this.#state.executionLease) {
|
|
284
|
+
return {
|
|
285
|
+
kind: 'rejected',
|
|
286
|
+
reason: 'stale-execution-lease',
|
|
287
|
+
armedCompletion,
|
|
288
|
+
originEcho: 'suppressed',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (sample.writeSequence <= this.#state.acceptedWriteSequence) {
|
|
292
|
+
return {
|
|
293
|
+
kind: 'rejected',
|
|
294
|
+
reason: 'stale-write-sequence',
|
|
295
|
+
armedCompletion,
|
|
296
|
+
originEcho: 'suppressed',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return this.#adoptTransportedWrite(impl, sample.history, sample.writeSequence, sample.authoritativeSequence, {
|
|
300
|
+
channelId: this.#state.channelId,
|
|
301
|
+
entryKind: 'generator-sample',
|
|
302
|
+
executionId: sample.executionId,
|
|
303
|
+
deliverySequence: sample.authoritativeSequence,
|
|
304
|
+
}, progress);
|
|
305
|
+
}
|
|
306
|
+
case 'tokenless-task-write': {
|
|
307
|
+
if (sample.writeSequence <= this.#state.acceptedWriteSequence) {
|
|
308
|
+
return {
|
|
309
|
+
kind: 'rejected',
|
|
310
|
+
reason: 'stale-write-sequence',
|
|
311
|
+
armedCompletion: 'not-armed',
|
|
312
|
+
originEcho: 'suppressed',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return this.#adoptTransportedWrite(impl, sample.history, sample.writeSequence, sample.authoritativeSequence, {
|
|
316
|
+
channelId: this.#state.channelId,
|
|
317
|
+
entryKind: 'tokenless-task-write',
|
|
318
|
+
taskKind: sample.taskKind,
|
|
319
|
+
taskSequence: sample.taskSequence,
|
|
320
|
+
deliverySequence: sample.authoritativeSequence,
|
|
321
|
+
}, progress);
|
|
322
|
+
}
|
|
323
|
+
case 'tokenless-task-callback': {
|
|
324
|
+
// Replay suppression precedes progress, cache adoption, and callback dispatch: an
|
|
325
|
+
// already-delivered event sequence is a pure retransmission and mutates nothing.
|
|
326
|
+
if (sample.eventSequence <= this.#state.deliveredEventWatermark) {
|
|
327
|
+
return {
|
|
328
|
+
kind: 'event-replay-rejected',
|
|
329
|
+
reason: 'stale-event-sequence',
|
|
330
|
+
callbackKind: sample.callbackKind,
|
|
331
|
+
callbackDelivery: 'suppressed-replay',
|
|
332
|
+
candidateCache: 'unchanged',
|
|
333
|
+
progress: 'unchanged',
|
|
334
|
+
originEcho: 'suppressed',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
progress.beginEntry({
|
|
338
|
+
channelId: this.#state.channelId,
|
|
339
|
+
entryKind: 'tokenless-task-callback',
|
|
340
|
+
taskKind: sample.taskKind,
|
|
341
|
+
callbackKind: sample.callbackKind,
|
|
342
|
+
taskSequence: sample.taskSequence,
|
|
343
|
+
deliverySequence: sample.authoritativeSequence,
|
|
344
|
+
});
|
|
345
|
+
// The callback event is immutable and always deliverable; only its carried cache
|
|
346
|
+
// candidate is guarded against the write/operation sequences (pinned delayed-terminal
|
|
347
|
+
// law — the callback must still observe the newer direct state, never a rewind).
|
|
348
|
+
let candidateCache;
|
|
349
|
+
if (sample.candidateDirectOperationEpoch < this.#state.directOperationEpoch) {
|
|
350
|
+
candidateCache = 'rejected-stale-direct-operation-epoch';
|
|
351
|
+
}
|
|
352
|
+
else if (sample.candidateWriteSequence <= this.#state.acceptedWriteSequence) {
|
|
353
|
+
candidateCache = 'rejected-stale-write-sequence';
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
this.#adoptCandidateCacheSilently(impl, sample.history, sample.candidateWriteSequence);
|
|
357
|
+
candidateCache = 'adopted';
|
|
358
|
+
}
|
|
359
|
+
this.#state.authoritativeSequence = sample.authoritativeSequence;
|
|
360
|
+
this.#state.deliveredEventWatermark = sample.eventSequence;
|
|
361
|
+
progress.finalizeDeliveredEventWatermarkAndRetention(this.#state.channelId, sample.eventSequence);
|
|
362
|
+
progress.clearEntry();
|
|
363
|
+
return {
|
|
364
|
+
kind: 'callback-delivered',
|
|
365
|
+
callbackKind: sample.callbackKind,
|
|
366
|
+
callbackDelivery: 'required',
|
|
367
|
+
candidateCache,
|
|
368
|
+
originEcho: 'suppressed',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
default: {
|
|
372
|
+
const exhaustive = sample;
|
|
373
|
+
void exhaustive;
|
|
374
|
+
throw new MotionValueDriverPortInvariantError('transported-sample-kind-unrecognized');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* One accepted write entry: adopt cache + ledger positions BEFORE notification (observers read
|
|
380
|
+
* the adopted state), deliver change, then finalize the delivered watermark. A throwing
|
|
381
|
+
* observer aborts later observers and derived dirtying but never rolls the adoption back: the
|
|
382
|
+
* watermark still finalizes exactly once (so a retransmission rejects as stale) and the exact
|
|
383
|
+
* error rethrows unchanged.
|
|
384
|
+
*/
|
|
385
|
+
#adoptTransportedWrite(impl, history, writeSequence, authoritativeSequence, entry, progress) {
|
|
386
|
+
progress.beginEntry(entry);
|
|
387
|
+
const currentValue = history.currentValue;
|
|
388
|
+
const changed = currentValue !== this.#state.rawCurrentValue;
|
|
389
|
+
const hadChangeObservers = impl.hasChangeObservers();
|
|
390
|
+
const hadDerivedDependents = impl.hasDerivedDependentObserver();
|
|
391
|
+
this.#state.rawCurrentValue = currentValue;
|
|
392
|
+
this.#state.history = history;
|
|
393
|
+
this.#state.acceptedWriteSequence = writeSequence;
|
|
394
|
+
this.#state.authoritativeSequence = authoritativeSequence;
|
|
395
|
+
progress.markIrreversibleSubphase('cache-history-adopted');
|
|
396
|
+
try {
|
|
397
|
+
// Marked BEFORE delivery: observer delivery is irreversible once begun, so an aborting
|
|
398
|
+
// observer leaves this subphase — never 'dependent-dirtying-complete' — as the resume point.
|
|
399
|
+
progress.markIrreversibleSubphase('change-observer-delivered');
|
|
400
|
+
this.#writeExternalSample(impl, history, changed);
|
|
401
|
+
progress.markIrreversibleSubphase('dependent-dirtying-complete');
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
progress.finalizeDeliveredWriteWatermarkAndRetention(this.#state.channelId, writeSequence);
|
|
405
|
+
progress.clearEntry();
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
progress.finalizeDeliveredWriteWatermarkAndRetention(this.#state.channelId, writeSequence);
|
|
409
|
+
progress.clearEntry();
|
|
410
|
+
return {
|
|
411
|
+
kind: 'applied',
|
|
412
|
+
finiteDifferenceVelocity: finiteDifferenceVelocity(history),
|
|
413
|
+
changeNotification: changed
|
|
414
|
+
? hadChangeObservers
|
|
415
|
+
? 'emitted'
|
|
416
|
+
: 'suppressed-no-observer'
|
|
417
|
+
: 'suppressed-equal-value',
|
|
418
|
+
derivedDependents: changed && hadDerivedDependents ? 'marked-dirty' : 'unchanged',
|
|
419
|
+
derivedFrame: changed && hadDerivedDependents ? 'requested' : 'not-requested',
|
|
420
|
+
originEcho: 'suppressed',
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Guarded candidate-cache adoption (pinned callback/cache split): the terminal's carried
|
|
425
|
+
* snapshot writes the value and history WITHOUT change notification — publication belongs to
|
|
426
|
+
* the write entries; the candidate exists so callback-time `get()`/`getVelocity()` need no
|
|
427
|
+
* synchronous origin read.
|
|
428
|
+
*/
|
|
429
|
+
#adoptCandidateCacheSilently(impl, history, writeSequence) {
|
|
430
|
+
this.#state.rawCurrentValue = history.currentValue;
|
|
431
|
+
this.#state.history = history;
|
|
432
|
+
this.#state.acceptedWriteSequence = writeSequence;
|
|
433
|
+
this.#writeExternalSample(impl, history, false);
|
|
434
|
+
}
|
|
435
|
+
#writeExternalSample(impl, history, notifyChange) {
|
|
436
|
+
impl.applyExternalNumericSample(history.currentValue, history.kind === 'finite-difference' ? history.previousValue : undefined, history.currentTimestampMs, history.kind === 'finite-difference' ? history.previousTimestampMs : undefined, notifyChange);
|
|
437
|
+
}
|
|
438
|
+
adoptSynchronousSnapshot(snapshot) {
|
|
439
|
+
const impl = this.#requireGraphMotionValue();
|
|
440
|
+
// Every explicit snapshot read counts, adopted or rejected (the read itself is the demand).
|
|
441
|
+
this.#state.synchronousSnapshotReadCount += 1;
|
|
442
|
+
if (snapshot.directOperationEpoch < this.#state.directOperationEpoch) {
|
|
443
|
+
return {
|
|
444
|
+
kind: 'synchronous-snapshot-rejected',
|
|
445
|
+
reason: 'stale-direct-operation-epoch',
|
|
446
|
+
callbackDelivery: 'silent',
|
|
447
|
+
demandedWriteSequence: 'unchanged',
|
|
448
|
+
publicControlSlot: 'unchanged',
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (snapshot.authoritativeSequence <= this.#state.authoritativeSequence) {
|
|
452
|
+
return {
|
|
453
|
+
kind: 'synchronous-snapshot-rejected',
|
|
454
|
+
reason: 'stale-authoritative-sequence',
|
|
455
|
+
callbackDelivery: 'silent',
|
|
456
|
+
demandedWriteSequence: 'unchanged',
|
|
457
|
+
publicControlSlot: 'unchanged',
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// Callback-silent by contract: the adoption never notifies, never touches the demanded write
|
|
461
|
+
// sequence or the public control slot, and never consumes an async sequence.
|
|
462
|
+
this.#state.rawCurrentValue = snapshot.history.currentValue;
|
|
463
|
+
this.#state.history = snapshot.history;
|
|
464
|
+
this.#state.authoritativeSequence = snapshot.authoritativeSequence;
|
|
465
|
+
this.#writeExternalSample(impl, snapshot.history, false);
|
|
466
|
+
return {
|
|
467
|
+
kind: 'synchronous-snapshot-adopted',
|
|
468
|
+
callbackDelivery: 'silent',
|
|
469
|
+
demandedWriteSequence: 'unchanged',
|
|
470
|
+
publicControlSlot: 'unchanged',
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
readCachedVelocity(atTimestampMs) {
|
|
474
|
+
const history = this.#state.history;
|
|
475
|
+
if (history.kind !== 'finite-difference')
|
|
476
|
+
return 0;
|
|
477
|
+
// Pinned staleness window: no fresh sample within it reads as at rest.
|
|
478
|
+
if (atTimestampMs - history.currentTimestampMs > constants_1.MAX_VELOCITY_DELTA_MS)
|
|
479
|
+
return 0;
|
|
480
|
+
return finiteDifferenceVelocity(history);
|
|
481
|
+
}
|
|
482
|
+
applyTransitionTerminal(event, mailboxPosition, progress) {
|
|
483
|
+
return this.#applyTerminal(event, {
|
|
484
|
+
channelId: this.#state.channelId,
|
|
485
|
+
entryKind: 'transition-terminal',
|
|
486
|
+
callbackKind: event.kind,
|
|
487
|
+
executionId: event.executionId,
|
|
488
|
+
deliverySequence: event.eventSequence,
|
|
489
|
+
}, mailboxPosition, progress);
|
|
490
|
+
}
|
|
491
|
+
applyLifecycleTerminal(event, mailboxPosition, progress) {
|
|
492
|
+
return this.#applyTerminal(event, {
|
|
493
|
+
channelId: this.#state.channelId,
|
|
494
|
+
entryKind: 'lifecycle-terminal',
|
|
495
|
+
callbackKind: 'animation-complete',
|
|
496
|
+
executionId: event.executionId,
|
|
497
|
+
deliverySequence: event.eventSequence,
|
|
498
|
+
}, mailboxPosition, progress);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* One transition/lifecycle terminal at its own ordered mailbox position (pinned candidate
|
|
502
|
+
* law): drain-order replay is suppressed before any progress or cache work; otherwise the
|
|
503
|
+
* immutable event delivers unconditionally while its carried candidate is adopted only if its
|
|
504
|
+
* epoch/lease/write sequence still wins. The mailbox position is the owning drain's
|
|
505
|
+
* bookkeeping — the port guards by its own ledger sequences.
|
|
506
|
+
*/
|
|
507
|
+
#applyTerminal(event, entry, mailboxPosition, progress) {
|
|
508
|
+
void mailboxPosition;
|
|
509
|
+
if (event.eventSequence <= this.#state.deliveredEventWatermark) {
|
|
510
|
+
return {
|
|
511
|
+
kind: 'event-replay-rejected',
|
|
512
|
+
reason: 'stale-event-sequence',
|
|
513
|
+
callbackDelivery: 'suppressed-replay',
|
|
514
|
+
armedCompletion: 'retained',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
const impl = this.#requireGraphMotionValue();
|
|
518
|
+
progress.beginEntry(entry);
|
|
519
|
+
let result;
|
|
520
|
+
if (event.directOperationEpoch < this.#state.directOperationEpoch) {
|
|
521
|
+
result = {
|
|
522
|
+
kind: 'candidate-cache-rejected',
|
|
523
|
+
reason: 'stale-direct-operation-epoch',
|
|
524
|
+
callbackDelivery: 'required',
|
|
525
|
+
armedCompletion: 'retained',
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
else if (event.executionLease < this.#state.executionLease) {
|
|
529
|
+
result = {
|
|
530
|
+
kind: 'candidate-cache-rejected',
|
|
531
|
+
reason: 'stale-execution-lease',
|
|
532
|
+
callbackDelivery: 'required',
|
|
533
|
+
armedCompletion: 'retained',
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
else if (event.writeSequence <= this.#state.acceptedWriteSequence) {
|
|
537
|
+
result = {
|
|
538
|
+
kind: 'candidate-cache-rejected',
|
|
539
|
+
reason: 'stale-write-sequence',
|
|
540
|
+
callbackDelivery: 'required',
|
|
541
|
+
armedCompletion: 'retained',
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
this.#adoptCandidateCacheSilently(impl, event.history, event.writeSequence);
|
|
546
|
+
result = {
|
|
547
|
+
kind: 'candidate-adopted',
|
|
548
|
+
callbackDelivery: 'required',
|
|
549
|
+
armedCompletion: 'retained',
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
this.#state.deliveredEventWatermark = event.eventSequence;
|
|
553
|
+
progress.finalizeDeliveredEventWatermarkAndRetention(this.#state.channelId, event.eventSequence);
|
|
554
|
+
progress.clearEntry();
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function initialLocalFrameEventPortState() {
|
|
559
|
+
return {
|
|
560
|
+
stagedTransitionEvents: [],
|
|
561
|
+
drainedEventSequences: [],
|
|
562
|
+
lastGuardedMailboxPosition: null,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
class LocalFrameEventPortImpl {
|
|
566
|
+
// The serializable record is the state; every transition replaces it immutably.
|
|
567
|
+
#state;
|
|
568
|
+
#candidateCacheGuard;
|
|
569
|
+
constructor(state, candidateCacheGuard) {
|
|
570
|
+
this.#state = state;
|
|
571
|
+
this.#candidateCacheGuard = candidateCacheGuard;
|
|
572
|
+
}
|
|
573
|
+
observe() {
|
|
574
|
+
return {
|
|
575
|
+
stagedTransitionEventCount: this.#state.stagedTransitionEvents.length,
|
|
576
|
+
stagedEventSequences: this.#state.stagedTransitionEvents.map((event) => event.eventSequence),
|
|
577
|
+
drainedEventSequences: this.#state.drainedEventSequences,
|
|
578
|
+
lastGuardedMailboxPosition: this.#state.lastGuardedMailboxPosition,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
stageTransitionOnUpdate(event) {
|
|
582
|
+
this.#stage(event);
|
|
583
|
+
}
|
|
584
|
+
stageTransitionOnComplete(event) {
|
|
585
|
+
this.#stage(event);
|
|
586
|
+
}
|
|
587
|
+
#stage(event) {
|
|
588
|
+
this.#state = {
|
|
589
|
+
...this.#state,
|
|
590
|
+
stagedTransitionEvents: [...this.#state.stagedTransitionEvents, event],
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
drainTransitionEvents() {
|
|
594
|
+
// Delivery order is event-sequence order regardless of staging order; every drained
|
|
595
|
+
// sequence is recorded so a later drain never replays it.
|
|
596
|
+
const drained = [...this.#state.stagedTransitionEvents].sort((left, right) => left.eventSequence - right.eventSequence);
|
|
597
|
+
this.#state = {
|
|
598
|
+
...this.#state,
|
|
599
|
+
stagedTransitionEvents: [],
|
|
600
|
+
drainedEventSequences: [
|
|
601
|
+
...this.#state.drainedEventSequences,
|
|
602
|
+
...drained.map((event) => event.eventSequence),
|
|
603
|
+
],
|
|
604
|
+
};
|
|
605
|
+
return drained;
|
|
606
|
+
}
|
|
607
|
+
guardAndAdoptTransitionTerminalCandidateCacheAtMailboxPosition(event, mailboxPosition, progress) {
|
|
608
|
+
this.#state = { ...this.#state, lastGuardedMailboxPosition: mailboxPosition };
|
|
609
|
+
return this.#candidateCacheGuard.applyTransitionTerminal(event, mailboxPosition, progress);
|
|
610
|
+
}
|
|
611
|
+
guardAndAdoptLifecycleTerminalCandidateCacheAtMailboxPosition(event, mailboxPosition, progress) {
|
|
612
|
+
this.#state = { ...this.#state, lastGuardedMailboxPosition: mailboxPosition };
|
|
613
|
+
return this.#candidateCacheGuard.applyLifecycleTerminal(event, mailboxPosition, progress);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function createLocalFrameEventPort(options) {
|
|
617
|
+
return new LocalFrameEventPortImpl(options.initialState ?? initialLocalFrameEventPortState(), options.candidateCacheGuard);
|
|
618
|
+
}
|
|
619
|
+
function createMotionValueDriverPort(options) {
|
|
620
|
+
const creation = DIRECT_PUBLIC_CREATION.get(options.value) ?? { kind: 'unrecognized' };
|
|
621
|
+
const state = options.initialState ?? defaultPortState(options.rawSnapshot, options.channelId);
|
|
622
|
+
const installation = options.initialPublicControlSlotInstallation;
|
|
623
|
+
if (state.publicControlSlot.kind === 'empty') {
|
|
624
|
+
if (installation !== undefined) {
|
|
625
|
+
throw new MotionValueDriverPortInvariantError('rehydrated-public-control-slot-installation-unexpected');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else if (installation === undefined) {
|
|
629
|
+
throw new MotionValueDriverPortInvariantError('rehydrated-public-control-slot-installation-required');
|
|
630
|
+
}
|
|
631
|
+
else if (installation.controlId !== state.publicControlSlot.controlId ||
|
|
632
|
+
installation.executionId !== state.publicControlSlot.executionId) {
|
|
633
|
+
throw new MotionValueDriverPortInvariantError('rehydrated-public-control-slot-reference-mismatch');
|
|
634
|
+
}
|
|
635
|
+
return new MotionValueDriverPortImpl(options.value, creation, state, installation ?? null);
|
|
636
|
+
}
|