@vibecook/ice 0.6.0 → 0.8.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/dist/{chunk-BFT7YWS2.js → chunk-3SUDKHUF.js} +3 -3
- package/dist/{chunk-JLXETATP.js → chunk-BE4F4X3Q.js} +485 -77
- package/dist/chunk-BE4F4X3Q.js.map +1 -0
- package/dist/{chunk-ELCBCTKY.js → chunk-CBCSIEXW.js} +2 -2
- package/dist/devtools.js +1 -1
- package/dist/dom.js +2 -2
- package/dist/ground.js +1 -1
- package/dist/index.js +3 -1
- package/dist/r3f.js +3 -3
- package/dist/react.js +3 -3
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +2 -1
- package/dist/types/packages/core/src/behavior/runtime.d.ts +16 -2
- package/dist/types/packages/core/src/behavior/types.d.ts +47 -1
- package/dist/types/packages/core/src/engine/guests.d.ts +5 -2
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +38 -6
- package/dist/types/packages/core/src/index.d.ts +4 -3
- package/dist/types/packages/core/src/interaction/install.d.ts +1 -1
- package/dist/types/packages/core/src/nav/nested-canvas.d.ts +1 -1
- package/dist/types/packages/core/src/ops/point-pick.d.ts +1 -1
- package/dist/types/packages/core/src/ops/port-geometry.d.ts +1 -1
- package/dist/types/packages/core/src/presence/presence-kit.d.ts +11 -0
- package/dist/types/packages/core/src/presence/remote-cursors.d.ts +10 -7
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l1-wires.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-connect.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-drop.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-marquee.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-ports.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-snap.d.ts +1 -1
- package/dist/types/packages/core/src/systems/nav-flight.d.ts +1 -1
- package/dist/types/packages/devtools/src/attach.d.ts +1 -1
- package/dist/types/packages/dom/src/loop.d.ts +1 -1
- package/dist/types/packages/dom/src/measure-adapter.d.ts +1 -1
- package/dist/types/packages/dom/src/measure-wiring.d.ts +1 -1
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/chrome.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/cursor.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/graybox.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +1 -1
- package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +1 -1
- package/dist/types/packages/dom/src/widget-focus.d.ts +1 -1
- package/dist/types/packages/ground/src/index.d.ts +2 -2
- package/dist/types/packages/ground/src/layer.d.ts +1 -1
- package/dist/types/packages/ground/src/pass.d.ts +1 -1
- package/dist/types/packages/ground/src/passes/grid.d.ts +1 -1
- package/dist/types/packages/ground/src/passes/guides-collect.d.ts +1 -1
- package/dist/types/packages/ground/src/passes/guides.d.ts +1 -1
- package/dist/types/packages/ground/src/passes/wires-collect.d.ts +1 -1
- package/dist/types/packages/ground/src/passes/wires.d.ts +1 -1
- package/dist/types/packages/r3f/src/bridge.d.ts +1 -1
- package/dist/types/packages/r3f/src/compositor-pass.d.ts +1 -1
- package/dist/types/packages/r3f/src/dev-write-trap.d.ts +1 -1
- package/dist/types/packages/r3f/src/gl-root.d.ts +1 -1
- package/dist/types/packages/r3f/src/gl-router.d.ts +2 -2
- package/dist/types/packages/r3f/src/island-state.d.ts +1 -1
- package/dist/types/packages/r3f/src/island.d.ts +1 -1
- package/dist/types/packages/r3f/src/use-island-frame.d.ts +1 -1
- package/dist/types/packages/react/src/engine-context.d.ts +1 -1
- package/dist/types/packages/react/src/hooks.d.ts +1 -1
- package/dist/types/packages/react/src/infinite-canvas.d.ts +2 -2
- package/dist/types/packages/react/src/keymap.d.ts +1 -1
- package/dist/types/packages/react/src/use-commit.d.ts +1 -1
- package/dist/types/packages/react/src/use-presence.d.ts +1 -1
- package/dist/types/packages/react/src/widget-root.d.ts +1 -1
- package/package.json +2 -2
- package/dist/chunk-JLXETATP.js.map +0 -1
- /package/dist/{chunk-BFT7YWS2.js.map → chunk-3SUDKHUF.js.map} +0 -0
- /package/dist/{chunk-ELCBCTKY.js.map → chunk-CBCSIEXW.js.map} +0 -0
|
@@ -843,6 +843,7 @@ function createGuestRegistry(world, opts) {
|
|
|
843
843
|
},
|
|
844
844
|
fault(err) {
|
|
845
845
|
if (!entry.alive || disposed) return;
|
|
846
|
+
if (entry.suspended) return;
|
|
846
847
|
entry.faultedThisFrame = true;
|
|
847
848
|
entry.accrued = true;
|
|
848
849
|
entry.consecutiveThrows++;
|
|
@@ -1733,6 +1734,102 @@ function validate(name, spec, phase2) {
|
|
|
1733
1734
|
}
|
|
1734
1735
|
}
|
|
1735
1736
|
}
|
|
1737
|
+
function cloneJsonShape(shape) {
|
|
1738
|
+
switch (shape.kind) {
|
|
1739
|
+
case "string":
|
|
1740
|
+
case "number":
|
|
1741
|
+
case "boolean":
|
|
1742
|
+
return { kind: shape.kind };
|
|
1743
|
+
case "enum":
|
|
1744
|
+
return { kind: "enum", options: [...shape.options] };
|
|
1745
|
+
case "array":
|
|
1746
|
+
return { kind: "array", item: cloneJsonShape(shape.item) };
|
|
1747
|
+
case "object":
|
|
1748
|
+
return {
|
|
1749
|
+
kind: "object",
|
|
1750
|
+
fields: Object.fromEntries(
|
|
1751
|
+
Object.entries(shape.fields).map(([name, field15]) => [name, cloneJsonShape(field15)])
|
|
1752
|
+
)
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
function describeProp(spec) {
|
|
1757
|
+
switch (spec.kind) {
|
|
1758
|
+
case "string":
|
|
1759
|
+
return {
|
|
1760
|
+
kind: "string",
|
|
1761
|
+
...spec.default === void 0 ? {} : { default: spec.default }
|
|
1762
|
+
};
|
|
1763
|
+
case "number":
|
|
1764
|
+
return {
|
|
1765
|
+
kind: "number",
|
|
1766
|
+
...spec.default === void 0 ? {} : { default: spec.default },
|
|
1767
|
+
...spec.min === void 0 ? {} : { min: spec.min },
|
|
1768
|
+
...spec.max === void 0 ? {} : { max: spec.max }
|
|
1769
|
+
};
|
|
1770
|
+
case "boolean":
|
|
1771
|
+
return {
|
|
1772
|
+
kind: "boolean",
|
|
1773
|
+
...spec.default === void 0 ? {} : { default: spec.default }
|
|
1774
|
+
};
|
|
1775
|
+
case "enum":
|
|
1776
|
+
return {
|
|
1777
|
+
kind: "enum",
|
|
1778
|
+
options: [...spec.options],
|
|
1779
|
+
...spec.default === void 0 ? {} : { default: spec.default }
|
|
1780
|
+
};
|
|
1781
|
+
case "json":
|
|
1782
|
+
return {
|
|
1783
|
+
kind: "json",
|
|
1784
|
+
inner: cloneJsonShape(spec.inner),
|
|
1785
|
+
...spec.default === void 0 ? {} : { default: spec.default }
|
|
1786
|
+
};
|
|
1787
|
+
case "entityKey":
|
|
1788
|
+
return {
|
|
1789
|
+
kind: "entityKey",
|
|
1790
|
+
...spec.default === void 0 ? {} : { default: spec.default }
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
var DESCRIPTION_HOOK_ORDER = [
|
|
1795
|
+
"init",
|
|
1796
|
+
"update",
|
|
1797
|
+
"changed",
|
|
1798
|
+
"tick",
|
|
1799
|
+
"dispose"
|
|
1800
|
+
];
|
|
1801
|
+
function describeBehavior(behavior) {
|
|
1802
|
+
const reads = [];
|
|
1803
|
+
for (const read of behavior.reads) {
|
|
1804
|
+
const description = classifyRead(read);
|
|
1805
|
+
if (description !== void 0) reads.push(description);
|
|
1806
|
+
}
|
|
1807
|
+
const writes = [];
|
|
1808
|
+
for (const component of behavior.writes) {
|
|
1809
|
+
const meta = schemaMeta.component(component);
|
|
1810
|
+
if (meta !== void 0) writes.push(meta.name);
|
|
1811
|
+
}
|
|
1812
|
+
return {
|
|
1813
|
+
id: behavior.name,
|
|
1814
|
+
store: behavior.store,
|
|
1815
|
+
derived: behavior.derived,
|
|
1816
|
+
deriveDuringGesture: behavior.deriveDuringGesture,
|
|
1817
|
+
version: behavior.version,
|
|
1818
|
+
phase: behavior.phase,
|
|
1819
|
+
...behavior.budgetMs === void 0 ? {} : { budgetMs: behavior.budgetMs },
|
|
1820
|
+
tickWhile: behavior.tickWhile,
|
|
1821
|
+
schema: Object.entries(behavior.schema).map(([name, spec]) => ({
|
|
1822
|
+
name,
|
|
1823
|
+
spec: describeProp(spec)
|
|
1824
|
+
})),
|
|
1825
|
+
reads,
|
|
1826
|
+
writes,
|
|
1827
|
+
migrationFrom: Object.keys(behavior.migrate).map(Number).sort((left, right) => left - right),
|
|
1828
|
+
hooks: DESCRIPTION_HOOK_ORDER.filter(
|
|
1829
|
+
(hook) => typeof behavior.on[hook] === "function"
|
|
1830
|
+
)
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1736
1833
|
function defineBehavior(name, spec) {
|
|
1737
1834
|
const phase2 = spec.phase ?? BEHAVIOR_DEFAULT_PHASE[spec.store];
|
|
1738
1835
|
if (devGuardsEnabled()) validate(name, spec, phase2);
|
|
@@ -7399,6 +7496,19 @@ function allowedQueryTerms(behavior, readComponents, readTags) {
|
|
|
7399
7496
|
// ../core/src/behavior/runtime.ts
|
|
7400
7497
|
var INSTANCE_THROW_LIMIT = 3;
|
|
7401
7498
|
var SUPPRESSION_WARN_FRAMES = 600;
|
|
7499
|
+
function isThenable(value) {
|
|
7500
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
7501
|
+
}
|
|
7502
|
+
var BehaviorHookThenableError = class extends TypeError {
|
|
7503
|
+
};
|
|
7504
|
+
function invokeHook(behavior, hook, body) {
|
|
7505
|
+
const result = body();
|
|
7506
|
+
if (!isThenable(result)) return;
|
|
7507
|
+
void Promise.resolve(result).catch(() => void 0);
|
|
7508
|
+
throw new BehaviorHookThenableError(
|
|
7509
|
+
`ice: behavior "${behavior}" ${hook} returned a thenable; behavior hooks are synchronous`
|
|
7510
|
+
);
|
|
7511
|
+
}
|
|
7402
7512
|
function isClaimed(world, e) {
|
|
7403
7513
|
if (!world.isAlive(e)) return false;
|
|
7404
7514
|
for (const claimant of world.getReverse(e, Captures)) {
|
|
@@ -7426,6 +7536,7 @@ function cellForBehavior(world, b, e, patch) {
|
|
|
7426
7536
|
function createBehaviorRuntime(opts) {
|
|
7427
7537
|
const { world } = opts;
|
|
7428
7538
|
const nodes = /* @__PURE__ */ new Map();
|
|
7539
|
+
let nextRegistrationSequence = 0;
|
|
7429
7540
|
const onFault = opts.onFault ?? ((behavior, hook, entity, err) => {
|
|
7430
7541
|
console.error(`[ice] behavior "${behavior}" ${hook}${entity === void 0 ? "" : ` on entity ${entity}`} threw`, err);
|
|
7431
7542
|
});
|
|
@@ -7437,8 +7548,42 @@ function createBehaviorRuntime(opts) {
|
|
|
7437
7548
|
for (const n of nodes.values()) n.markGeneration();
|
|
7438
7549
|
}
|
|
7439
7550
|
});
|
|
7551
|
+
const publishOrder = [];
|
|
7552
|
+
const pendingWithdrawals = [];
|
|
7553
|
+
let publishHookRemover;
|
|
7554
|
+
function ensurePublishHook() {
|
|
7555
|
+
if (publishHookRemover !== void 0) return;
|
|
7556
|
+
publishHookRemover = opts.engine.onPublish(() => {
|
|
7557
|
+
if (pendingWithdrawals.length > 0) {
|
|
7558
|
+
const jobs = pendingWithdrawals.splice(0, pendingWithdrawals.length);
|
|
7559
|
+
for (const job of jobs) job();
|
|
7560
|
+
}
|
|
7561
|
+
if (publishOrder.length > 0) {
|
|
7562
|
+
const pass = publishOrder.slice();
|
|
7563
|
+
for (const node of pass) if (!node.isStopped()) node.runPublish();
|
|
7564
|
+
}
|
|
7565
|
+
if (publishOrder.length === 0 && pendingWithdrawals.length === 0) {
|
|
7566
|
+
publishHookRemover?.();
|
|
7567
|
+
publishHookRemover = void 0;
|
|
7568
|
+
}
|
|
7569
|
+
});
|
|
7570
|
+
}
|
|
7571
|
+
function installPublishNode(node) {
|
|
7572
|
+
publishOrder.push(node);
|
|
7573
|
+
ensurePublishHook();
|
|
7574
|
+
return () => {
|
|
7575
|
+
const i = publishOrder.indexOf(node);
|
|
7576
|
+
if (i !== -1) publishOrder.splice(i, 1);
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
function deferWithdrawal(job) {
|
|
7580
|
+
pendingWithdrawals.push(job);
|
|
7581
|
+
ensurePublishHook();
|
|
7582
|
+
}
|
|
7440
7583
|
class Node {
|
|
7441
7584
|
b;
|
|
7585
|
+
orderKey;
|
|
7586
|
+
registrationSequence;
|
|
7442
7587
|
compiled;
|
|
7443
7588
|
own;
|
|
7444
7589
|
/** The same handle, typed for `readField`'s keyof-based field parameter. */
|
|
@@ -7449,6 +7594,28 @@ function createBehaviorRuntime(opts) {
|
|
|
7449
7594
|
peersQuery;
|
|
7450
7595
|
/** Ephemeral only: the local peer this node last published a facet on. */
|
|
7451
7596
|
facetPeer;
|
|
7597
|
+
/**
|
|
7598
|
+
* The entity whose HOOK is currently executing (review finding 2, 0.8.0).
|
|
7599
|
+
* `writeFacet` consults it so a hook writes only the peer whose instance
|
|
7600
|
+
* it runs for — a same-gap presence swap otherwise lets the OLD peer's
|
|
7601
|
+
* dispose resolve `localPeer` LIVE and land a corpse's farewell on the
|
|
7602
|
+
* fresh peer. Unset outside hooks (captured `ctx.write` closures keep the
|
|
7603
|
+
* blessed behavior: they publish the CURRENT peer).
|
|
7604
|
+
*/
|
|
7605
|
+
hookEntity;
|
|
7606
|
+
/** The ephemeral singleton was quarantined THIS generation (I17). */
|
|
7607
|
+
singletonQuarantined = false;
|
|
7608
|
+
/** Uninstalled — a captured ctx.write closure must go inert (I17). */
|
|
7609
|
+
stopped = false;
|
|
7610
|
+
/** Per-FRAME strike dedupe (review finding 4): the ladder counts FRAMES,
|
|
7611
|
+
* not calls — 500 async instances must cost ONE strike, not write 500
|
|
7612
|
+
* into a ledger hosts persist across engine generations. Keyed by
|
|
7613
|
+
* FrameInfo.tick because no single pass entry point runs every frame
|
|
7614
|
+
* (the delivery system is runIf-gated off on no-churn frames while the
|
|
7615
|
+
* tick system keeps running). */
|
|
7616
|
+
strikeFrame = -1;
|
|
7617
|
+
thenableStruckPass = false;
|
|
7618
|
+
spanStruckPass = false;
|
|
7452
7619
|
/** The session whose version state has already been checked (§5.6). */
|
|
7453
7620
|
checkedSession;
|
|
7454
7621
|
/**
|
|
@@ -7519,8 +7686,10 @@ function createBehaviorRuntime(opts) {
|
|
|
7519
7686
|
/** Set for the duration of a delivery/tick pass; see BehaviorWalker. */
|
|
7520
7687
|
walker;
|
|
7521
7688
|
ctx;
|
|
7522
|
-
constructor(b) {
|
|
7689
|
+
constructor(b, registration, registrationSequence) {
|
|
7523
7690
|
this.b = b;
|
|
7691
|
+
this.orderKey = registration.orderKey;
|
|
7692
|
+
this.registrationSequence = registrationSequence;
|
|
7524
7693
|
this.own = b.component;
|
|
7525
7694
|
this.ownFields = b.component;
|
|
7526
7695
|
this.schema = b.schema;
|
|
@@ -7543,6 +7712,7 @@ function createBehaviorRuntime(opts) {
|
|
|
7543
7712
|
this.guest = opts.engine.guests.addDriven({
|
|
7544
7713
|
id: `behavior:${b.name}`,
|
|
7545
7714
|
...b.budgetMs !== void 0 ? { budgetMs: b.budgetMs } : {},
|
|
7715
|
+
...registration.ledger === void 0 ? {} : { ledger: registration.ledger },
|
|
7546
7716
|
busy: () => this.owed()
|
|
7547
7717
|
});
|
|
7548
7718
|
this.ctx = this.buildCtx();
|
|
@@ -7576,6 +7746,7 @@ function createBehaviorRuntime(opts) {
|
|
|
7576
7746
|
this.readsCollector?.dispose();
|
|
7577
7747
|
this.ownCollector = void 0;
|
|
7578
7748
|
this.readsCollector = void 0;
|
|
7749
|
+
this.withdrawFacet();
|
|
7579
7750
|
}
|
|
7580
7751
|
/**
|
|
7581
7752
|
* Resumed. Fresh collectors journal from NOW, so everything that happened
|
|
@@ -7597,18 +7768,10 @@ function createBehaviorRuntime(opts) {
|
|
|
7597
7768
|
if (this.compiled.readRelations.length > 0) {
|
|
7598
7769
|
world.orderStamp(0, this.compiled.readRelations[0]);
|
|
7599
7770
|
}
|
|
7771
|
+
}
|
|
7772
|
+
installExecution() {
|
|
7600
7773
|
if (this.b.phase === "publish") {
|
|
7601
|
-
this.removeSystems =
|
|
7602
|
-
if (this.guest.suspended()) {
|
|
7603
|
-
this.goCold();
|
|
7604
|
-
return;
|
|
7605
|
-
}
|
|
7606
|
-
this.guest.run(() => {
|
|
7607
|
-
if (this.b.store === "ephemeral") this.ensureFacet();
|
|
7608
|
-
if (this.shouldDeliver()) this.runDeliver(publishWalker(world));
|
|
7609
|
-
this.publishTick();
|
|
7610
|
-
});
|
|
7611
|
-
});
|
|
7774
|
+
this.removeSystems = installPublishNode(this);
|
|
7612
7775
|
} else {
|
|
7613
7776
|
this.removeSystems = opts.engine.addSystems(
|
|
7614
7777
|
this.b.phase,
|
|
@@ -7616,6 +7779,25 @@ function createBehaviorRuntime(opts) {
|
|
|
7616
7779
|
);
|
|
7617
7780
|
}
|
|
7618
7781
|
}
|
|
7782
|
+
/** The publish-slot body — driven by the runtime's one stable hook. */
|
|
7783
|
+
runPublish() {
|
|
7784
|
+
if (this.guest.suspended()) {
|
|
7785
|
+
this.goCold();
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7788
|
+
this.guest.run(() => {
|
|
7789
|
+
if (this.b.store === "ephemeral") this.ensureFacet();
|
|
7790
|
+
if (this.shouldDeliver()) this.runDeliver(publishWalker(world));
|
|
7791
|
+
this.publishTick();
|
|
7792
|
+
});
|
|
7793
|
+
}
|
|
7794
|
+
isStopped() {
|
|
7795
|
+
return this.stopped;
|
|
7796
|
+
}
|
|
7797
|
+
uninstallExecution() {
|
|
7798
|
+
this.removeSystems?.();
|
|
7799
|
+
this.removeSystems = void 0;
|
|
7800
|
+
}
|
|
7619
7801
|
/** The version-marker surface for the current session, if it has one. */
|
|
7620
7802
|
metaOf(session) {
|
|
7621
7803
|
const snapshot = session?.store.snapshot;
|
|
@@ -7701,6 +7883,14 @@ function createBehaviorRuntime(opts) {
|
|
|
7701
7883
|
this.facetPeer = void 0;
|
|
7702
7884
|
return;
|
|
7703
7885
|
}
|
|
7886
|
+
if (this.singletonQuarantined) {
|
|
7887
|
+
this.facetPeer = void 0;
|
|
7888
|
+
return;
|
|
7889
|
+
}
|
|
7890
|
+
if (this.instances.size > 0 && !this.instances.has(peer)) {
|
|
7891
|
+
this.facetPeer = void 0;
|
|
7892
|
+
return;
|
|
7893
|
+
}
|
|
7704
7894
|
if (peer === this.facetPeer && world.has(peer, this.own)) return;
|
|
7705
7895
|
this.facetPeer = peer;
|
|
7706
7896
|
if (!world.has(peer, this.own)) {
|
|
@@ -7711,6 +7901,48 @@ function createBehaviorRuntime(opts) {
|
|
|
7711
7901
|
);
|
|
7712
7902
|
}
|
|
7713
7903
|
}
|
|
7904
|
+
/**
|
|
7905
|
+
* Withdraw the published facet (petition I17). An ephemeral facet is LIVE
|
|
7906
|
+
* PUBLICATION — the local peer telling every remote peer "this value is
|
|
7907
|
+
* current" — so every edge where the producer stops executing must reverse
|
|
7908
|
+
* the publication too, or a disabled, suspended, or quarantined behavior
|
|
7909
|
+
* keeps asserting its last value forever (remote peers have no TTL for a
|
|
7910
|
+
* peer that is still connected). Idempotent by construction: presence
|
|
7911
|
+
* absent, a dead peer (mid re-mint window), and an already-gone facet are
|
|
7912
|
+
* all quiet no-ops. The removal goes through the PRESENCE writer, never
|
|
7913
|
+
* `world.removeComponent`, because only the presence path encodes a
|
|
7914
|
+
* tombstone remote projections honor.
|
|
7915
|
+
*/
|
|
7916
|
+
withdrawFacet() {
|
|
7917
|
+
if (this.b.store !== "ephemeral") return;
|
|
7918
|
+
this.facetPeer = void 0;
|
|
7919
|
+
const presence = opts.presence?.();
|
|
7920
|
+
if (presence === void 0) return;
|
|
7921
|
+
const peer = presence.localPeer;
|
|
7922
|
+
if (!(world.isAlive(peer) && world.has(peer, this.own))) return;
|
|
7923
|
+
const midTick = world.inImmediateProjectionUnsafeContext ?? false;
|
|
7924
|
+
if (midTick) {
|
|
7925
|
+
const behaviorName = this.b.name;
|
|
7926
|
+
const own = this.own;
|
|
7927
|
+
deferWithdrawal(() => {
|
|
7928
|
+
const p2 = opts.presence?.();
|
|
7929
|
+
if (p2 === void 0) return;
|
|
7930
|
+
if (p2.localPeer !== peer) return;
|
|
7931
|
+
if (!(world.isAlive(peer) && world.has(peer, own))) return;
|
|
7932
|
+
try {
|
|
7933
|
+
p2.eph.removeComponent(peer, own);
|
|
7934
|
+
} catch (err) {
|
|
7935
|
+
onFault(behaviorName, "withdraw", peer, err);
|
|
7936
|
+
}
|
|
7937
|
+
});
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
try {
|
|
7941
|
+
presence.eph.removeComponent(peer, this.own);
|
|
7942
|
+
} catch (err) {
|
|
7943
|
+
onFault(this.b.name, "withdraw", peer, err);
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7714
7946
|
/** The publish-slot twin of the tick SYSTEM (no query, no chunks). */
|
|
7715
7947
|
publishTick() {
|
|
7716
7948
|
const on = this.b.on;
|
|
@@ -7723,16 +7955,19 @@ function createBehaviorRuntime(opts) {
|
|
|
7723
7955
|
const inst = this.instances.get(e);
|
|
7724
7956
|
if (inst === void 0 || inst.failed || !inst.inited) continue;
|
|
7725
7957
|
const data = this.dataOf(e);
|
|
7726
|
-
this.callHook(
|
|
7727
|
-
|
|
7728
|
-
|
|
7958
|
+
this.callHook(
|
|
7959
|
+
"tick",
|
|
7960
|
+
inst,
|
|
7961
|
+
faulted,
|
|
7962
|
+
() => on.tick?.(e, data, frame2, this.ctx)
|
|
7963
|
+
);
|
|
7729
7964
|
}
|
|
7730
7965
|
this.walker = void 0;
|
|
7731
7966
|
}
|
|
7732
7967
|
uninstall() {
|
|
7968
|
+
this.stopped = true;
|
|
7733
7969
|
this.endGeneration();
|
|
7734
|
-
this.
|
|
7735
|
-
this.removeSystems = void 0;
|
|
7970
|
+
this.uninstallExecution();
|
|
7736
7971
|
this.ownCollector?.dispose();
|
|
7737
7972
|
this.readsCollector?.dispose();
|
|
7738
7973
|
this.ownCollector = void 0;
|
|
@@ -7995,9 +8230,12 @@ function createBehaviorRuntime(opts) {
|
|
|
7995
8230
|
for (const e of appeared) {
|
|
7996
8231
|
const inst = this.instances.get(e);
|
|
7997
8232
|
if (inst === void 0 || inst.failed) continue;
|
|
7998
|
-
if (this.callHook(
|
|
7999
|
-
|
|
8000
|
-
|
|
8233
|
+
if (this.callHook(
|
|
8234
|
+
"init",
|
|
8235
|
+
inst,
|
|
8236
|
+
faultedEntities,
|
|
8237
|
+
() => on.init?.(e, this.dataOf(e), this.ctx)
|
|
8238
|
+
)) {
|
|
8001
8239
|
inst.inited = true;
|
|
8002
8240
|
}
|
|
8003
8241
|
}
|
|
@@ -8019,16 +8257,19 @@ function createBehaviorRuntime(opts) {
|
|
|
8019
8257
|
if (valueEquals2(this.own, this.scratchCell, inst.prevCell)) continue;
|
|
8020
8258
|
const data = this.viewOf(this.scratchCell, this.scratchData);
|
|
8021
8259
|
const prev = this.viewOf(inst.prevCell, this.scratchPrevData);
|
|
8022
|
-
this.callHook(
|
|
8023
|
-
|
|
8024
|
-
|
|
8260
|
+
this.callHook(
|
|
8261
|
+
"update",
|
|
8262
|
+
inst,
|
|
8263
|
+
faultedEntities,
|
|
8264
|
+
() => on.update?.(e, data, prev, this.ctx)
|
|
8265
|
+
);
|
|
8025
8266
|
this.copyInto(this.scratchCell, inst.prevCell);
|
|
8026
8267
|
}
|
|
8027
8268
|
}
|
|
8028
8269
|
const anyVisible = this.visible.length > 0;
|
|
8029
8270
|
if (on.changed !== void 0 && (full || (work?.readsFired ?? false) || lifted) && anyVisible) {
|
|
8030
8271
|
try {
|
|
8031
|
-
on.changed(this.ctx);
|
|
8272
|
+
invokeHook(this.b.name, "changed", () => on.changed?.(this.ctx));
|
|
8032
8273
|
} catch (err) {
|
|
8033
8274
|
onFault(this.b.name, "changed", void 0, err);
|
|
8034
8275
|
this.guest.fault(err);
|
|
@@ -8036,10 +8277,17 @@ function createBehaviorRuntime(opts) {
|
|
|
8036
8277
|
}
|
|
8037
8278
|
if (on.dispose !== void 0) {
|
|
8038
8279
|
for (const inst of departed) {
|
|
8280
|
+
this.hookEntity = inst.e;
|
|
8039
8281
|
try {
|
|
8040
|
-
|
|
8282
|
+
invokeHook(
|
|
8283
|
+
this.b.name,
|
|
8284
|
+
"dispose",
|
|
8285
|
+
() => on.dispose?.(inst.e, this.ctx)
|
|
8286
|
+
);
|
|
8041
8287
|
} catch (err) {
|
|
8042
8288
|
onFault(this.b.name, "dispose", inst.e, err);
|
|
8289
|
+
} finally {
|
|
8290
|
+
this.hookEntity = void 0;
|
|
8043
8291
|
}
|
|
8044
8292
|
}
|
|
8045
8293
|
}
|
|
@@ -8062,9 +8310,12 @@ function createBehaviorRuntime(opts) {
|
|
|
8062
8310
|
const inst = this.instances.get(e);
|
|
8063
8311
|
if (inst === void 0 || inst.failed || !inst.inited) continue;
|
|
8064
8312
|
const data = this.dataOf(e);
|
|
8065
|
-
this.callHook(
|
|
8066
|
-
|
|
8067
|
-
|
|
8313
|
+
this.callHook(
|
|
8314
|
+
"tick",
|
|
8315
|
+
inst,
|
|
8316
|
+
faulted,
|
|
8317
|
+
() => on.tick?.(e, data, frame2, this.ctx)
|
|
8318
|
+
);
|
|
8068
8319
|
}
|
|
8069
8320
|
this.walker = void 0;
|
|
8070
8321
|
}
|
|
@@ -8075,17 +8326,38 @@ function createBehaviorRuntime(opts) {
|
|
|
8075
8326
|
* kill a 1000-node layout.
|
|
8076
8327
|
*/
|
|
8077
8328
|
callHook(hook, inst, faulted, body) {
|
|
8329
|
+
this.hookEntity = inst.e;
|
|
8078
8330
|
try {
|
|
8079
|
-
body
|
|
8331
|
+
invokeHook(this.b.name, hook, body);
|
|
8080
8332
|
inst.consecutiveThrows = 0;
|
|
8081
8333
|
return true;
|
|
8082
8334
|
} catch (err) {
|
|
8083
8335
|
inst.consecutiveThrows++;
|
|
8084
8336
|
onFault(this.b.name, hook, inst.e, err);
|
|
8085
|
-
if (inst.consecutiveThrows >= INSTANCE_THROW_LIMIT)
|
|
8337
|
+
if (inst.consecutiveThrows >= INSTANCE_THROW_LIMIT) {
|
|
8338
|
+
inst.failed = true;
|
|
8339
|
+
if (this.b.store === "ephemeral") this.singletonQuarantined = true;
|
|
8340
|
+
this.withdrawFacet();
|
|
8341
|
+
}
|
|
8086
8342
|
faulted.add(inst.e);
|
|
8087
|
-
|
|
8343
|
+
const frameTick = world.getResource(FrameInfo)?.tick ?? -1;
|
|
8344
|
+
if (frameTick !== this.strikeFrame) {
|
|
8345
|
+
this.strikeFrame = frameTick;
|
|
8346
|
+
this.thenableStruckPass = false;
|
|
8347
|
+
this.spanStruckPass = false;
|
|
8348
|
+
}
|
|
8349
|
+
if (err instanceof BehaviorHookThenableError) {
|
|
8350
|
+
if (!this.thenableStruckPass) {
|
|
8351
|
+
this.thenableStruckPass = true;
|
|
8352
|
+
this.guest.fault(err);
|
|
8353
|
+
}
|
|
8354
|
+
} else if (faulted.size > 1 && !this.spanStruckPass) {
|
|
8355
|
+
this.spanStruckPass = true;
|
|
8356
|
+
this.guest.fault(err);
|
|
8357
|
+
}
|
|
8088
8358
|
return false;
|
|
8359
|
+
} finally {
|
|
8360
|
+
this.hookEntity = void 0;
|
|
8089
8361
|
}
|
|
8090
8362
|
}
|
|
8091
8363
|
// --- generation --------------------------------------------------------
|
|
@@ -8093,15 +8365,23 @@ function createBehaviorRuntime(opts) {
|
|
|
8093
8365
|
const on = this.b.on;
|
|
8094
8366
|
if (on.dispose !== void 0) {
|
|
8095
8367
|
for (const inst of this.instances.values()) {
|
|
8368
|
+
this.hookEntity = inst.e;
|
|
8096
8369
|
try {
|
|
8097
|
-
|
|
8370
|
+
invokeHook(
|
|
8371
|
+
this.b.name,
|
|
8372
|
+
"dispose",
|
|
8373
|
+
() => on.dispose?.(inst.e, this.ctx)
|
|
8374
|
+
);
|
|
8098
8375
|
} catch (err) {
|
|
8099
8376
|
onFault(this.b.name, "dispose", inst.e, err);
|
|
8377
|
+
} finally {
|
|
8378
|
+
this.hookEntity = void 0;
|
|
8100
8379
|
}
|
|
8101
8380
|
}
|
|
8102
8381
|
}
|
|
8103
8382
|
this.instances.clear();
|
|
8104
|
-
this.
|
|
8383
|
+
this.withdrawFacet();
|
|
8384
|
+
this.singletonQuarantined = false;
|
|
8105
8385
|
this.suppressed.clear();
|
|
8106
8386
|
this.suppressedFrames.clear();
|
|
8107
8387
|
this.carry.clear();
|
|
@@ -8199,10 +8479,12 @@ function createBehaviorRuntime(opts) {
|
|
|
8199
8479
|
}
|
|
8200
8480
|
/** `ctx.write(patch)` for the ephemeral class — publish THIS peer's facet. */
|
|
8201
8481
|
writeFacet(patch) {
|
|
8482
|
+
if (this.stopped || this.cold || this.singletonQuarantined) return;
|
|
8202
8483
|
const presence = opts.presence?.();
|
|
8203
8484
|
if (presence === void 0) return;
|
|
8204
8485
|
const peer = presence.localPeer;
|
|
8205
8486
|
if (!world.isAlive(peer)) return;
|
|
8487
|
+
if (this.hookEntity !== void 0 && this.hookEntity !== peer) return;
|
|
8206
8488
|
const cell = cellForBehavior(world, this.b, peer, patch);
|
|
8207
8489
|
if (world.has(peer, this.own)) presence.eph.edit(peer).set(this.own, cell);
|
|
8208
8490
|
else presence.eph.addComponent(peer, this.own, cell);
|
|
@@ -8368,10 +8650,32 @@ function createBehaviorRuntime(opts) {
|
|
|
8368
8650
|
instances: this.instances.size,
|
|
8369
8651
|
failed,
|
|
8370
8652
|
ticking: this.b.on.tick === void 0 ? 0 : this.instances.size - failed,
|
|
8371
|
-
suspended: this.guest.suspended()
|
|
8653
|
+
suspended: this.guest.suspended(),
|
|
8654
|
+
quarantined: this.singletonQuarantined
|
|
8372
8655
|
};
|
|
8373
8656
|
}
|
|
8374
8657
|
}
|
|
8658
|
+
function orderedNodes() {
|
|
8659
|
+
return [...nodes.values()].sort((left, right) => {
|
|
8660
|
+
if (left.orderKey !== void 0 || right.orderKey !== void 0) {
|
|
8661
|
+
if (left.orderKey === void 0) return 1;
|
|
8662
|
+
if (right.orderKey === void 0) return -1;
|
|
8663
|
+
if (left.orderKey < right.orderKey) return -1;
|
|
8664
|
+
if (left.orderKey > right.orderKey) return 1;
|
|
8665
|
+
}
|
|
8666
|
+
return left.registrationSequence - right.registrationSequence;
|
|
8667
|
+
});
|
|
8668
|
+
}
|
|
8669
|
+
function reorderExecution(inserted) {
|
|
8670
|
+
const order = orderedNodes();
|
|
8671
|
+
const at = order.indexOf(inserted);
|
|
8672
|
+
if (at === order.length - 1) {
|
|
8673
|
+
inserted.installExecution();
|
|
8674
|
+
return;
|
|
8675
|
+
}
|
|
8676
|
+
for (let i = order.length - 1; i > at; i--) order[i]?.uninstallExecution();
|
|
8677
|
+
for (let i = at; i < order.length; i++) order[i]?.installExecution();
|
|
8678
|
+
}
|
|
8375
8679
|
function safeParse(raw) {
|
|
8376
8680
|
try {
|
|
8377
8681
|
return JSON.parse(raw);
|
|
@@ -8380,13 +8684,17 @@ function createBehaviorRuntime(opts) {
|
|
|
8380
8684
|
}
|
|
8381
8685
|
}
|
|
8382
8686
|
return {
|
|
8383
|
-
register(b) {
|
|
8687
|
+
register(b, options = {}) {
|
|
8688
|
+
if (options.orderKey !== void 0 && (typeof options.orderKey !== "string" || options.orderKey.length === 0)) {
|
|
8689
|
+
throw new TypeError("ice: behavior registration orderKey must be a non-empty string");
|
|
8690
|
+
}
|
|
8384
8691
|
if (nodes.has(b.name)) {
|
|
8385
8692
|
throw new Error(`ice: behavior "${b.name}" is already registered with this engine.`);
|
|
8386
8693
|
}
|
|
8387
|
-
const node = new Node(b);
|
|
8694
|
+
const node = new Node(b, options, nextRegistrationSequence++);
|
|
8388
8695
|
nodes.set(b.name, node);
|
|
8389
8696
|
node.install();
|
|
8697
|
+
reorderExecution(node);
|
|
8390
8698
|
return () => {
|
|
8391
8699
|
if (nodes.get(b.name) !== node) return;
|
|
8392
8700
|
nodes.delete(b.name);
|
|
@@ -8394,7 +8702,7 @@ function createBehaviorRuntime(opts) {
|
|
|
8394
8702
|
};
|
|
8395
8703
|
},
|
|
8396
8704
|
registered() {
|
|
8397
|
-
return
|
|
8705
|
+
return orderedNodes().map((n) => n.b);
|
|
8398
8706
|
},
|
|
8399
8707
|
attach(e, b, data) {
|
|
8400
8708
|
if (b.store === "durable") {
|
|
@@ -8427,12 +8735,18 @@ function createBehaviorRuntime(opts) {
|
|
|
8427
8735
|
return cell;
|
|
8428
8736
|
},
|
|
8429
8737
|
list() {
|
|
8430
|
-
return
|
|
8738
|
+
return orderedNodes().map((n) => n.status());
|
|
8431
8739
|
},
|
|
8432
8740
|
dispose() {
|
|
8433
8741
|
stopResetWatch();
|
|
8434
8742
|
for (const n of nodes.values()) n.uninstall();
|
|
8435
8743
|
nodes.clear();
|
|
8744
|
+
if (pendingWithdrawals.length > 0) {
|
|
8745
|
+
const jobs = pendingWithdrawals.splice(0, pendingWithdrawals.length);
|
|
8746
|
+
for (const job of jobs) job();
|
|
8747
|
+
}
|
|
8748
|
+
publishHookRemover?.();
|
|
8749
|
+
publishHookRemover = void 0;
|
|
8436
8750
|
}
|
|
8437
8751
|
};
|
|
8438
8752
|
}
|
|
@@ -9416,6 +9730,10 @@ var DEFAULT_PRESENCE_TTL_MS = 5e3;
|
|
|
9416
9730
|
function attachPresence(world, opts) {
|
|
9417
9731
|
const peerId = opts.peerId ?? crypto.randomUUID();
|
|
9418
9732
|
const ttlMs = opts.ttlMs ?? DEFAULT_PRESENCE_TTL_MS;
|
|
9733
|
+
const onFault = opts.onFault ?? ((where, error) => {
|
|
9734
|
+
const detail = where === "outbound" ? "presence outbound subscriber threw \u2014 bytes dropped for that subscriber (transport handlers must not throw)" : where === "leave" ? "presence leave failed \u2014 tearing the binding down anyway (remote peers drop us at TTL instead of immediately)" : "presence reset-heal leave failed \u2014 rebinding anyway (stale keys age out at TTL)";
|
|
9735
|
+
console.error(`[ice] ${detail}`, error);
|
|
9736
|
+
});
|
|
9419
9737
|
const loro = new LoroEphemeralStore(ttlMs);
|
|
9420
9738
|
const source = new LoroEphemeralSnapshot(loro);
|
|
9421
9739
|
const outbound = /* @__PURE__ */ new Set();
|
|
@@ -9423,7 +9741,13 @@ function attachPresence(world, opts) {
|
|
|
9423
9741
|
peerId,
|
|
9424
9742
|
ttlMs,
|
|
9425
9743
|
send: (bytes) => {
|
|
9426
|
-
for (const fn of [...outbound])
|
|
9744
|
+
for (const fn of [...outbound]) {
|
|
9745
|
+
try {
|
|
9746
|
+
fn(bytes);
|
|
9747
|
+
} catch (err) {
|
|
9748
|
+
onFault("outbound", err);
|
|
9749
|
+
}
|
|
9750
|
+
}
|
|
9427
9751
|
}
|
|
9428
9752
|
});
|
|
9429
9753
|
let attachment = attachEphemeral(world, eph);
|
|
@@ -9441,7 +9765,11 @@ function attachPresence(world, opts) {
|
|
|
9441
9765
|
queueMicrotask(() => {
|
|
9442
9766
|
healQueued = false;
|
|
9443
9767
|
if (detached) return;
|
|
9444
|
-
|
|
9768
|
+
try {
|
|
9769
|
+
eph.leave();
|
|
9770
|
+
} catch (err) {
|
|
9771
|
+
onFault("heal", err);
|
|
9772
|
+
}
|
|
9445
9773
|
attachment.detach();
|
|
9446
9774
|
attachment = attachEphemeral(world, eph);
|
|
9447
9775
|
localPeer = spawnLocalPeer();
|
|
@@ -9467,7 +9795,11 @@ function attachPresence(world, opts) {
|
|
|
9467
9795
|
if (detached) return;
|
|
9468
9796
|
detached = true;
|
|
9469
9797
|
stopResetHeal();
|
|
9470
|
-
|
|
9798
|
+
try {
|
|
9799
|
+
eph.leave();
|
|
9800
|
+
} catch (err) {
|
|
9801
|
+
onFault("leave", err);
|
|
9802
|
+
}
|
|
9471
9803
|
attachment.detach();
|
|
9472
9804
|
outbound.clear();
|
|
9473
9805
|
loro.destroy();
|
|
@@ -9576,9 +9908,9 @@ function summaryEquals(a, b) {
|
|
|
9576
9908
|
import { Not as Not9, defineQuery as defineQuery38, defineTickSystem as defineTickSystem15 } from "@vibecook/strata-ecs";
|
|
9577
9909
|
import { Local as Local2 } from "@vibecook/strata-ecs";
|
|
9578
9910
|
var remotePeerCursorsQ = defineQuery38([PresenceCursor, PresencePeer, Not9(Local2)]);
|
|
9579
|
-
function
|
|
9911
|
+
function createRemoteCursorsRig(world) {
|
|
9580
9912
|
const pool = /* @__PURE__ */ new Map();
|
|
9581
|
-
|
|
9913
|
+
const system = defineTickSystem15(
|
|
9582
9914
|
(ctx) => {
|
|
9583
9915
|
const live = /* @__PURE__ */ new Set();
|
|
9584
9916
|
ctx.query(remotePeerCursorsQ).each((b) => {
|
|
@@ -9612,16 +9944,38 @@ function createRemoteCursorsSystem(_world) {
|
|
|
9612
9944
|
},
|
|
9613
9945
|
{
|
|
9614
9946
|
name: "remoteCursors",
|
|
9615
|
-
|
|
9947
|
+
// orderIndependent: every row this system writes is its OWN pool's
|
|
9948
|
+
// cursor entity (one per remote peer) — row-disjoint from any other
|
|
9949
|
+
// Position writer by construction. The attestation is what lets a
|
|
9950
|
+
// userland `present`-phase behavior that co-writes Position silence
|
|
9951
|
+
// strata's double-writer advisory from its own side: suppression is
|
|
9952
|
+
// conjunctive over ALL co-writers, so an un-attested engine system
|
|
9953
|
+
// would make the warning unsilenceable for authors doing it right
|
|
9954
|
+
// (0.8.0 review finding).
|
|
9955
|
+
access: { write: [Position, CursorVisual], orderIndependent: [Position] }
|
|
9616
9956
|
}
|
|
9617
9957
|
);
|
|
9958
|
+
return {
|
|
9959
|
+
system,
|
|
9960
|
+
reap() {
|
|
9961
|
+
for (const [, entry] of pool) {
|
|
9962
|
+
if (world.isAlive(entry.cursor)) world.destroy(entry.cursor);
|
|
9963
|
+
}
|
|
9964
|
+
pool.clear();
|
|
9965
|
+
}
|
|
9966
|
+
};
|
|
9967
|
+
}
|
|
9968
|
+
function createRemoteCursorsSystem(world) {
|
|
9969
|
+
return createRemoteCursorsRig(world).system;
|
|
9618
9970
|
}
|
|
9619
9971
|
function installPresence(engine, session, opts = {}) {
|
|
9620
9972
|
const removePublish = engine.onPublish(createPresencePublish(engine.world, session, opts));
|
|
9621
|
-
const
|
|
9973
|
+
const rig = createRemoteCursorsRig(engine.world);
|
|
9974
|
+
const removeSystem = engine.addSystems("present", rig.system);
|
|
9622
9975
|
return () => {
|
|
9623
9976
|
removePublish();
|
|
9624
9977
|
removeSystem();
|
|
9978
|
+
rig.reap();
|
|
9625
9979
|
};
|
|
9626
9980
|
}
|
|
9627
9981
|
|
|
@@ -10030,7 +10384,10 @@ function createCanvasEngine(opts = {}) {
|
|
|
10030
10384
|
}
|
|
10031
10385
|
}
|
|
10032
10386
|
const world = createWorld2();
|
|
10033
|
-
const engine = createEngine(world
|
|
10387
|
+
const engine = createEngine(world, {
|
|
10388
|
+
...opts.onGuestFault === void 0 ? {} : { onGuestFault: opts.onGuestFault },
|
|
10389
|
+
...opts.onGuestNotice === void 0 ? {} : { onGuestNotice: opts.onGuestNotice }
|
|
10390
|
+
});
|
|
10034
10391
|
const sink = createForwardingSink();
|
|
10035
10392
|
const stack = installInteractionStack(engine, { sink });
|
|
10036
10393
|
const budgets = {
|
|
@@ -10087,15 +10444,36 @@ function createCanvasEngine(opts = {}) {
|
|
|
10087
10444
|
sink.target = next.sink;
|
|
10088
10445
|
return next;
|
|
10089
10446
|
};
|
|
10447
|
+
const acquirePresence = (o) => {
|
|
10448
|
+
const p2 = attachPresence(world, o);
|
|
10449
|
+
presence = p2;
|
|
10450
|
+
uninstallPresence = installPresence(engine, p2, {
|
|
10451
|
+
keyOf: (e) => session?.store.keyOf(e)
|
|
10452
|
+
});
|
|
10453
|
+
return p2;
|
|
10454
|
+
};
|
|
10455
|
+
const releasePresence = () => {
|
|
10456
|
+
const uninstall = uninstallPresence;
|
|
10457
|
+
const p2 = presence;
|
|
10458
|
+
uninstallPresence = void 0;
|
|
10459
|
+
presence = void 0;
|
|
10460
|
+
try {
|
|
10461
|
+
uninstall?.();
|
|
10462
|
+
} catch (err) {
|
|
10463
|
+
console.error("[ice] presence uninstall threw \u2014 teardown continues", err);
|
|
10464
|
+
}
|
|
10465
|
+
try {
|
|
10466
|
+
p2?.detach();
|
|
10467
|
+
} catch (err) {
|
|
10468
|
+
console.error("[ice] presence detach threw \u2014 teardown continues", err);
|
|
10469
|
+
}
|
|
10470
|
+
};
|
|
10090
10471
|
const closeDoc = () => {
|
|
10091
10472
|
joinAbort?.abort(new Error("ice: docs.join superseded \u2014 another document was opened while joining."));
|
|
10092
10473
|
joinAbort = void 0;
|
|
10093
10474
|
for (const a of [...liveAutosaves]) a.stop();
|
|
10094
10475
|
liveAutosaves.clear();
|
|
10095
|
-
|
|
10096
|
-
uninstallPresence = void 0;
|
|
10097
|
-
presence?.detach();
|
|
10098
|
-
presence = void 0;
|
|
10476
|
+
releasePresence();
|
|
10099
10477
|
joined?.leave();
|
|
10100
10478
|
joined = void 0;
|
|
10101
10479
|
session?.close();
|
|
@@ -10140,29 +10518,35 @@ function createCanvasEngine(opts = {}) {
|
|
|
10140
10518
|
joinAbort = ac;
|
|
10141
10519
|
const { presence: presenceOpts, ...joinOpts } = o;
|
|
10142
10520
|
if (presenceOpts !== void 0) {
|
|
10143
|
-
|
|
10144
|
-
uninstallPresence = installPresence(engine, presence, {
|
|
10145
|
-
keyOf: (e) => session?.store.keyOf(e)
|
|
10146
|
-
});
|
|
10521
|
+
acquirePresence({ name: presenceOpts.name, color: presenceOpts.color });
|
|
10147
10522
|
}
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10523
|
+
let result;
|
|
10524
|
+
try {
|
|
10525
|
+
result = await joinDoc(world, channel, {
|
|
10526
|
+
...gatePolicy !== void 0 ? { docOpts: { onGate: gatePolicy } } : {},
|
|
10527
|
+
...presence !== void 0 ? { presence } : {},
|
|
10528
|
+
...joinOpts,
|
|
10529
|
+
signal: ac.signal,
|
|
10530
|
+
// Re-bootstrap swaps the session inside joinDoc (PendingImportError
|
|
10531
|
+
// recovery) — re-target the forwarding sink or ops keep committing
|
|
10532
|
+
// into the quarantined session.
|
|
10533
|
+
onSession: (s) => {
|
|
10534
|
+
if (joinAbort !== ac) return;
|
|
10535
|
+
if (s !== void 0) {
|
|
10536
|
+
adoptSession(s);
|
|
10537
|
+
} else {
|
|
10538
|
+
session = void 0;
|
|
10539
|
+
sink.target = void 0;
|
|
10540
|
+
}
|
|
10163
10541
|
}
|
|
10542
|
+
});
|
|
10543
|
+
} catch (err) {
|
|
10544
|
+
if (joinAbort === ac) {
|
|
10545
|
+
joinAbort = void 0;
|
|
10546
|
+
releasePresence();
|
|
10164
10547
|
}
|
|
10165
|
-
|
|
10548
|
+
throw err;
|
|
10549
|
+
}
|
|
10166
10550
|
if (joinAbort !== ac) {
|
|
10167
10551
|
result.leave();
|
|
10168
10552
|
throw new Error("ice: docs.join superseded \u2014 another document was opened while joining.");
|
|
@@ -10172,6 +10556,26 @@ function createCanvasEngine(opts = {}) {
|
|
|
10172
10556
|
return result;
|
|
10173
10557
|
},
|
|
10174
10558
|
current: () => session,
|
|
10559
|
+
attachPresence(o) {
|
|
10560
|
+
if (session === void 0) {
|
|
10561
|
+
throw new Error(
|
|
10562
|
+
"ice: docs.attachPresence needs a live document \u2014 call docs.create()/open()/join() first."
|
|
10563
|
+
);
|
|
10564
|
+
}
|
|
10565
|
+
if (presence !== void 0) {
|
|
10566
|
+
throw new Error(
|
|
10567
|
+
"ice: docs.attachPresence \u2014 a presence session is already live (join({presence}) or a prior attach); detach it (its inverse, or docs.close()) before attaching another."
|
|
10568
|
+
);
|
|
10569
|
+
}
|
|
10570
|
+
const mine = acquirePresence(o);
|
|
10571
|
+
let done = false;
|
|
10572
|
+
return () => {
|
|
10573
|
+
if (done) return;
|
|
10574
|
+
done = true;
|
|
10575
|
+
if (presence !== mine) return;
|
|
10576
|
+
releasePresence();
|
|
10577
|
+
};
|
|
10578
|
+
},
|
|
10175
10579
|
presence: () => presence,
|
|
10176
10580
|
close: closeDoc,
|
|
10177
10581
|
// Read-only documents must not mutate through history either — undo/redo
|
|
@@ -10201,11 +10605,14 @@ function createCanvasEngine(opts = {}) {
|
|
|
10201
10605
|
world,
|
|
10202
10606
|
engine,
|
|
10203
10607
|
session: () => session,
|
|
10204
|
-
// Presence attaches with `docs.join({presence})`
|
|
10205
|
-
//
|
|
10206
|
-
//
|
|
10207
|
-
//
|
|
10208
|
-
|
|
10608
|
+
// Presence attaches with `docs.join({presence})` OR `docs.attachPresence`
|
|
10609
|
+
// (petition I18) and detaches with the document / the attach inverse, so
|
|
10610
|
+
// this reads through per publish: an ephemeral behavior installed before
|
|
10611
|
+
// there is a presence session simply lies dormant until there is a peer
|
|
10612
|
+
// for it to be — and goes dormant again if the session detaches under it.
|
|
10613
|
+
presence: () => presence,
|
|
10614
|
+
...opts.onBehaviorFault === void 0 ? {} : { onFault: opts.onBehaviorFault },
|
|
10615
|
+
...opts.onBehaviorLog === void 0 ? {} : { onLog: opts.onBehaviorLog }
|
|
10209
10616
|
});
|
|
10210
10617
|
for (const b of opts.behaviors ?? []) behaviors2.register(b);
|
|
10211
10618
|
const widgetQuery = () => {
|
|
@@ -10437,7 +10844,7 @@ import {
|
|
|
10437
10844
|
Local as Local3
|
|
10438
10845
|
} from "@vibecook/strata-ecs";
|
|
10439
10846
|
import { DurableUndoStatus } from "@vibecook/strata-ecs/durable";
|
|
10440
|
-
var CORE_VERSION = "0.
|
|
10847
|
+
var CORE_VERSION = "0.8.0";
|
|
10441
10848
|
|
|
10442
10849
|
export {
|
|
10443
10850
|
defineSchemaOnce,
|
|
@@ -10579,6 +10986,7 @@ export {
|
|
|
10579
10986
|
BEHAVIOR_PHASES,
|
|
10580
10987
|
BEHAVIOR_DEFAULT_PHASE,
|
|
10581
10988
|
behaviors,
|
|
10989
|
+
describeBehavior,
|
|
10582
10990
|
defineBehavior,
|
|
10583
10991
|
isDurableEligible,
|
|
10584
10992
|
compileBehavior,
|
|
@@ -10721,4 +11129,4 @@ export {
|
|
|
10721
11129
|
Local3 as Local,
|
|
10722
11130
|
DurableUndoStatus
|
|
10723
11131
|
};
|
|
10724
|
-
//# sourceMappingURL=chunk-
|
|
11132
|
+
//# sourceMappingURL=chunk-BE4F4X3Q.js.map
|