@spearwolf/shadow-objects 0.33.0 → 0.35.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 +350 -61
- package/README.md +66 -13
- package/bundle.js +6 -53
- package/package.json +15 -3
- package/src/ChangeTrailRefusedError.d.ts +27 -0
- package/src/ChangeTrailRefusedError.d.ts.map +1 -0
- package/src/ChangeTrailRefusedError.js +12 -0
- package/src/ChangeTrailRefusedError.js.map +7 -0
- package/src/EntityUuidInUseError.d.ts +19 -0
- package/src/EntityUuidInUseError.d.ts.map +1 -0
- package/src/EntityUuidInUseError.js +11 -0
- package/src/EntityUuidInUseError.js.map +7 -0
- package/src/WorkerTimeoutError.d.ts +26 -0
- package/src/WorkerTimeoutError.d.ts.map +1 -0
- package/src/WorkerTimeoutError.js +12 -0
- package/src/WorkerTimeoutError.js.map +7 -0
- package/src/bundle.d.ts +1 -1
- package/src/bundle.d.ts.map +1 -1
- package/src/bundle.js +1 -1
- package/src/bundle.js.map +2 -2
- package/src/constants.d.ts +13 -2
- package/src/constants.d.ts.map +1 -1
- package/src/constants.js +7 -1
- package/src/constants.js.map +2 -2
- package/src/create-worker.bundle.d.ts +1 -1
- package/src/create-worker.bundle.d.ts.map +1 -1
- package/src/create-worker.bundle.js.map +1 -1
- package/src/create-worker.d.ts +1 -1
- package/src/create-worker.d.ts.map +1 -1
- package/src/create-worker.js.map +1 -1
- package/src/elements/ShaeElement.d.ts +70 -2
- package/src/elements/ShaeElement.d.ts.map +1 -1
- package/src/elements/ShaeElement.js +129 -25
- package/src/elements/ShaeElement.js.map +3 -3
- package/src/elements/ShaeEntElement.d.ts +39 -1
- package/src/elements/ShaeEntElement.d.ts.map +1 -1
- package/src/elements/ShaeEntElement.js +397 -167
- package/src/elements/ShaeEntElement.js.map +2 -2
- package/src/elements/ShaeLifecycleElement.d.ts +71 -0
- package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
- package/src/elements/ShaeLifecycleElement.js +83 -0
- package/src/elements/ShaeLifecycleElement.js.map +7 -0
- package/src/elements/ShaePropElement.d.ts +61 -2
- package/src/elements/ShaePropElement.d.ts.map +1 -1
- package/src/elements/ShaePropElement.js +264 -219
- package/src/elements/ShaePropElement.js.map +2 -2
- package/src/elements/ShaeWorkerElement.d.ts +59 -5
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +271 -73
- package/src/elements/ShaeWorkerElement.js.map +2 -2
- package/src/elements/constants.d.ts +9 -0
- package/src/elements/constants.d.ts.map +1 -1
- package/src/elements/constants.js +18 -0
- package/src/elements/constants.js.map +2 -2
- package/src/elements/deferredTeardown.d.ts +24 -0
- package/src/elements/deferredTeardown.d.ts.map +1 -0
- package/src/elements/deferredTeardown.js +30 -0
- package/src/elements/deferredTeardown.js.map +7 -0
- package/src/elements/displayContentsRule.d.ts +25 -0
- package/src/elements/displayContentsRule.d.ts.map +1 -0
- package/src/elements/displayContentsRule.js +37 -0
- package/src/elements/displayContentsRule.js.map +7 -0
- package/src/elements/events.d.ts +10 -7
- package/src/elements/events.d.ts.map +1 -1
- package/src/elements/forwardCustomEvents.d.ts +19 -0
- package/src/elements/forwardCustomEvents.d.ts.map +1 -0
- package/src/elements/forwardCustomEvents.js +47 -0
- package/src/elements/forwardCustomEvents.js.map +7 -0
- package/src/elements/hostedSlots.d.ts +36 -0
- package/src/elements/hostedSlots.d.ts.map +1 -0
- package/src/elements/hostedSlots.js +118 -0
- package/src/elements/hostedSlots.js.map +7 -0
- package/src/elements/parentRemoval.d.ts +18 -0
- package/src/elements/parentRemoval.d.ts.map +1 -0
- package/src/elements/parentRemoval.js +60 -0
- package/src/elements/parentRemoval.js.map +7 -0
- package/src/elements/propValueConverters.d.ts +3 -0
- package/src/elements/propValueConverters.d.ts.map +1 -0
- package/src/elements/propValueConverters.js +54 -0
- package/src/elements/propValueConverters.js.map +7 -0
- package/src/elements/requestEntAncestor.d.ts +32 -0
- package/src/elements/requestEntAncestor.d.ts.map +1 -0
- package/src/elements/requestEntAncestor.js +16 -0
- package/src/elements/requestEntAncestor.js.map +7 -0
- package/src/elements.js.map +1 -1
- package/src/in-the-dark/Entity.d.ts +143 -3
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +330 -63
- package/src/in-the-dark/Entity.js.map +3 -3
- package/src/in-the-dark/Kernel.d.ts +98 -16
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +466 -379
- package/src/in-the-dark/Kernel.js.map +3 -3
- package/src/in-the-dark/Registry.d.ts +7 -1
- package/src/in-the-dark/Registry.d.ts.map +1 -1
- package/src/in-the-dark/Registry.js +112 -14
- package/src/in-the-dark/Registry.js.map +2 -2
- package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
- package/src/in-the-dark/ShadowObject.js +1 -0
- package/src/in-the-dark/ShadowObject.js.map +2 -2
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts +133 -0
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js +603 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
- package/src/in-the-dark/SignalsPath.d.ts +2 -0
- package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
- package/src/in-the-dark/SignalsPath.js +6 -2
- package/src/in-the-dark/SignalsPath.js.map +2 -2
- package/src/in-the-dark/displayName.d.ts +4 -0
- package/src/in-the-dark/displayName.d.ts.map +1 -0
- package/src/in-the-dark/displayName.js +5 -0
- package/src/in-the-dark/displayName.js.map +7 -0
- package/src/in-the-dark/events.js.map +1 -1
- package/src/in-the-dark/importModule.d.ts +7 -0
- package/src/in-the-dark/importModule.d.ts.map +1 -1
- package/src/in-the-dark/importModule.js +5 -2
- package/src/in-the-dark/importModule.js.map +2 -2
- package/src/index.d.ts +9 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +9 -1
- package/src/index.js.map +2 -2
- package/src/inspect/createKernelSnapshot.d.ts +17 -0
- package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
- package/src/inspect/createKernelSnapshot.js +247 -0
- package/src/inspect/createKernelSnapshot.js.map +7 -0
- package/src/inspect/createViewSnapshot.d.ts +10 -0
- package/src/inspect/createViewSnapshot.d.ts.map +1 -0
- package/src/inspect/createViewSnapshot.js +143 -0
- package/src/inspect/createViewSnapshot.js.map +7 -0
- package/src/inspect/normalizeInspectRequest.d.ts +32 -0
- package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
- package/src/inspect/normalizeInspectRequest.js +55 -0
- package/src/inspect/normalizeInspectRequest.js.map +7 -0
- package/src/inspect/serializeValue.d.ts +13 -0
- package/src/inspect/serializeValue.d.ts.map +1 -0
- package/src/inspect/serializeValue.js +149 -0
- package/src/inspect/serializeValue.js.map +7 -0
- package/src/inspect/types.d.ts +260 -0
- package/src/inspect/types.d.ts.map +1 -0
- package/src/inspect/types.js +1 -0
- package/src/inspect/types.js.map +7 -0
- package/src/model-context/ModelContextLike.d.ts +59 -0
- package/src/model-context/ModelContextLike.d.ts.map +1 -0
- package/src/model-context/ModelContextLike.js +15 -0
- package/src/model-context/ModelContextLike.js.map +7 -0
- package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
- package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
- package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
- package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
- package/src/model-context/redactProps.d.ts +14 -0
- package/src/model-context/redactProps.d.ts.map +1 -0
- package/src/model-context/redactProps.js +22 -0
- package/src/model-context/redactProps.js.map +7 -0
- package/src/model-context/sharedExposure.d.ts +32 -0
- package/src/model-context/sharedExposure.d.ts.map +1 -0
- package/src/model-context/sharedExposure.js +108 -0
- package/src/model-context/sharedExposure.js.map +7 -0
- package/src/model-context/toolSupport.d.ts +72 -0
- package/src/model-context/toolSupport.d.ts.map +1 -0
- package/src/model-context/toolSupport.js +145 -0
- package/src/model-context/toolSupport.js.map +7 -0
- package/src/model-context/tools/findEntities.d.ts +15 -0
- package/src/model-context/tools/findEntities.d.ts.map +1 -0
- package/src/model-context/tools/findEntities.js +71 -0
- package/src/model-context/tools/findEntities.js.map +7 -0
- package/src/model-context/tools/getEntity.d.ts +16 -0
- package/src/model-context/tools/getEntity.d.ts.map +1 -0
- package/src/model-context/tools/getEntity.js +65 -0
- package/src/model-context/tools/getEntity.js.map +7 -0
- package/src/model-context/tools/getEntityTree.d.ts +4 -0
- package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
- package/src/model-context/tools/getEntityTree.js +58 -0
- package/src/model-context/tools/getEntityTree.js.map +7 -0
- package/src/model-context/tools/getRegistry.d.ts +14 -0
- package/src/model-context/tools/getRegistry.d.ts.map +1 -0
- package/src/model-context/tools/getRegistry.js +39 -0
- package/src/model-context/tools/getRegistry.js.map +7 -0
- package/src/model-context/tools/index.d.ts +9 -0
- package/src/model-context/tools/index.d.ts.map +1 -0
- package/src/model-context/tools/index.js +16 -0
- package/src/model-context/tools/index.js.map +7 -0
- package/src/model-context/tools/listEnvs.d.ts +33 -0
- package/src/model-context/tools/listEnvs.d.ts.map +1 -0
- package/src/model-context/tools/listEnvs.js +37 -0
- package/src/model-context/tools/listEnvs.js.map +7 -0
- package/src/model-context.d.ts +13 -0
- package/src/model-context.d.ts.map +1 -0
- package/src/model-context.js +15 -0
- package/src/model-context.js.map +7 -0
- package/src/shadow-objects.d.ts +4 -0
- package/src/shadow-objects.d.ts.map +1 -1
- package/src/shadow-objects.js +6 -0
- package/src/shadow-objects.js.map +2 -2
- package/src/shadow-objects.worker.js +0 -1
- package/src/shadow-objects.worker.js.map +2 -2
- package/src/shae-ent.js.map +1 -1
- package/src/shae-prop.js +2 -2
- package/src/shae-prop.js.map +2 -2
- package/src/shae-worker.js.map +1 -1
- package/src/testing/TestEntity.d.ts +79 -0
- package/src/testing/TestEntity.d.ts.map +1 -0
- package/src/testing/TestEntity.js +135 -0
- package/src/testing/TestEntity.js.map +7 -0
- package/src/testing/createTestKernel.d.ts +11 -0
- package/src/testing/createTestKernel.d.ts.map +1 -0
- package/src/testing/createTestKernel.js +132 -0
- package/src/testing/createTestKernel.js.map +7 -0
- package/src/testing/mountShadowObject.d.ts +21 -0
- package/src/testing/mountShadowObject.d.ts.map +1 -0
- package/src/testing/mountShadowObject.js +72 -0
- package/src/testing/mountShadowObject.js.map +7 -0
- package/src/testing/recordKernelErrors.d.ts +21 -0
- package/src/testing/recordKernelErrors.d.ts.map +1 -0
- package/src/testing/recordKernelErrors.js +39 -0
- package/src/testing/recordKernelErrors.js.map +7 -0
- package/src/testing/settle.d.ts +15 -0
- package/src/testing/settle.d.ts.map +1 -0
- package/src/testing/settle.js +19 -0
- package/src/testing/settle.js.map +7 -0
- package/src/testing/types.d.ts +119 -0
- package/src/testing/types.d.ts.map +1 -0
- package/src/testing/types.js +1 -0
- package/src/testing/types.js.map +7 -0
- package/src/testing.d.ts +14 -0
- package/src/testing.d.ts.map +1 -0
- package/src/testing.js +11 -0
- package/src/testing.js.map +7 -0
- package/src/types.d.ts +185 -14
- package/src/types.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.d.ts +45 -10
- package/src/utils/ConsoleLogger.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.js +51 -26
- package/src/utils/ConsoleLogger.js.map +2 -2
- package/src/utils/FrameLoop.d.ts +42 -3
- package/src/utils/FrameLoop.d.ts.map +1 -1
- package/src/utils/FrameLoop.js +71 -10
- package/src/utils/FrameLoop.js.map +2 -2
- package/src/utils/MicrotaskCollector.d.ts +45 -0
- package/src/utils/MicrotaskCollector.d.ts.map +1 -0
- package/src/utils/MicrotaskCollector.js +33 -0
- package/src/utils/MicrotaskCollector.js.map +7 -0
- package/src/utils/MicrotaskGate.d.ts +17 -0
- package/src/utils/MicrotaskGate.d.ts.map +1 -0
- package/src/utils/MicrotaskGate.js +20 -0
- package/src/utils/MicrotaskGate.js.map +7 -0
- package/src/utils/array-utils.js.map +1 -1
- package/src/utils/attr-utils.d.ts +11 -0
- package/src/utils/attr-utils.d.ts.map +1 -1
- package/src/utils/attr-utils.js +5 -1
- package/src/utils/attr-utils.js.map +2 -2
- package/src/utils/constants.js.map +1 -1
- package/src/utils/generateUUID.d.ts +11 -0
- package/src/utils/generateUUID.d.ts.map +1 -1
- package/src/utils/generateUUID.js +31 -266
- package/src/utils/generateUUID.js.map +2 -2
- package/src/utils/props-utils.d.ts +3 -2
- package/src/utils/props-utils.d.ts.map +1 -1
- package/src/utils/props-utils.js +13 -24
- package/src/utils/props-utils.js.map +2 -2
- package/src/utils/runGuarded.d.ts +27 -0
- package/src/utils/runGuarded.d.ts.map +1 -0
- package/src/utils/runGuarded.js +11 -0
- package/src/utils/runGuarded.js.map +7 -0
- package/src/utils/toMaybe.d.ts.map +1 -1
- package/src/utils/toMaybe.js.map +1 -1
- package/src/utils/toNamespace.d.ts.map +1 -1
- package/src/utils/toNamespace.js.map +1 -1
- package/src/utils/toUrlString.d.ts +12 -0
- package/src/utils/toUrlString.d.ts.map +1 -1
- package/src/utils/toUrlString.js.map +2 -2
- package/src/utils/waitForMessageOfType.d.ts +12 -1
- package/src/utils/waitForMessageOfType.d.ts.map +1 -1
- package/src/utils/waitForMessageOfType.js +16 -1
- package/src/utils/waitForMessageOfType.js.map +2 -2
- package/src/view/ComponentChanges.d.ts +88 -1
- package/src/view/ComponentChanges.d.ts.map +1 -1
- package/src/view/ComponentChanges.js +243 -73
- package/src/view/ComponentChanges.js.map +2 -2
- package/src/view/ComponentContext.d.ts +206 -16
- package/src/view/ComponentContext.d.ts.map +1 -1
- package/src/view/ComponentContext.js +548 -91
- package/src/view/ComponentContext.js.map +2 -2
- package/src/view/ComponentMemory.d.ts +3 -3
- package/src/view/ComponentMemory.d.ts.map +1 -1
- package/src/view/ComponentMemory.js.map +1 -1
- package/src/view/IShadowObjectEnvProxy.d.ts +38 -3
- package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts +8 -1
- package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.js +23 -7
- package/src/view/LocalShadowObjectEnv.js.map +2 -2
- package/src/view/RemoteWorkerEnv.d.ts +108 -5
- package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
- package/src/view/RemoteWorkerEnv.js +244 -77
- package/src/view/RemoteWorkerEnv.js.map +2 -2
- package/src/view/ShadowEnv.d.ts +79 -2
- package/src/view/ShadowEnv.d.ts.map +1 -1
- package/src/view/ShadowEnv.js +323 -59
- package/src/view/ShadowEnv.js.map +3 -3
- package/src/view/ViewComponent.d.ts +78 -14
- package/src/view/ViewComponent.d.ts.map +1 -1
- package/src/view/ViewComponent.js +90 -14
- package/src/view/ViewComponent.js.map +2 -2
- package/src/view/cloneChangeTrail.js.map +2 -2
- package/src/worker/MessageRouter.d.ts +14 -0
- package/src/worker/MessageRouter.d.ts.map +1 -1
- package/src/worker/MessageRouter.js +98 -15
- package/src/worker/MessageRouter.js.map +2 -2
- package/src/worker/WorkerRuntime.d.ts +22 -1
- package/src/worker/WorkerRuntime.d.ts.map +1 -1
- package/src/worker/WorkerRuntime.js +60 -8
- package/src/worker/WorkerRuntime.js.map +2 -2
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
createEffect,
|
|
5
|
-
createMemo,
|
|
6
|
-
createSignal,
|
|
7
|
-
destroySignal,
|
|
8
|
-
isSignal,
|
|
9
|
-
link
|
|
10
|
-
} from "@spearwolf/signalize";
|
|
1
|
+
import { emitSafe, emitStrict, eventize, off, on } from "@spearwolf/eventize";
|
|
2
|
+
import { batch } from "@spearwolf/signalize";
|
|
3
|
+
import { ChangeTrailRefusedError } from "../ChangeTrailRefusedError.js";
|
|
11
4
|
import { ComponentChangeType, MessageToView } from "../constants.js";
|
|
5
|
+
import { EntityUuidInUseError } from "../EntityUuidInUseError.js";
|
|
12
6
|
import { ConsoleLogger } from "../utils/ConsoleLogger.js";
|
|
13
|
-
import {
|
|
7
|
+
import { runGuarded } from "../utils/runGuarded.js";
|
|
8
|
+
import { getDisplayName } from "./displayName.js";
|
|
14
9
|
import { Entity } from "./Entity.js";
|
|
15
10
|
import { onCreate, onDestroy, onParentChanged, onViewEvent } from "./events.js";
|
|
16
11
|
import { Registry } from "./Registry.js";
|
|
12
|
+
import { ShadowObjectCreationScope } from "./ShadowObjectCreationScope.js";
|
|
17
13
|
import { SignalsPath } from "./SignalsPath.js";
|
|
18
14
|
var ShadowObjectAction = /* @__PURE__ */ ((ShadowObjectAction2) => {
|
|
19
15
|
ShadowObjectAction2[ShadowObjectAction2["CreateAndDestroy"] = 0] = "CreateAndDestroy";
|
|
@@ -21,38 +17,69 @@ var ShadowObjectAction = /* @__PURE__ */ ((ShadowObjectAction2) => {
|
|
|
21
17
|
ShadowObjectAction2[ShadowObjectAction2["DestroyOnly"] = 2] = "DestroyOnly";
|
|
22
18
|
return ShadowObjectAction2;
|
|
23
19
|
})(ShadowObjectAction || {});
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
const LIFECYCLE_HOOKS = [
|
|
21
|
+
["onCreate", onCreate],
|
|
22
|
+
["onDestroy", onDestroy],
|
|
23
|
+
["onParentChanged", onParentChanged],
|
|
24
|
+
["onViewEvent", onViewEvent]
|
|
25
|
+
];
|
|
30
26
|
class Kernel {
|
|
27
|
+
#logger = new ConsoleLogger("Kernel");
|
|
28
|
+
/** The logger this Kernel reports through. */
|
|
29
|
+
get logger() {
|
|
30
|
+
return this.#logger;
|
|
31
|
+
}
|
|
32
|
+
#entities = /* @__PURE__ */ new Map();
|
|
33
|
+
#rootEntities = /* @__PURE__ */ new Set();
|
|
34
|
+
#allEntities = [];
|
|
35
|
+
#allEntitiesReversed = [];
|
|
36
|
+
#allEntitiesNeedUpdate = true;
|
|
37
|
+
#rootContexts = /* @__PURE__ */ new Map();
|
|
38
|
+
/**
|
|
39
|
+
* The creation scope of a shadow-object, keyed by the shadow-object itself.
|
|
40
|
+
*
|
|
41
|
+
* `#destroyShadowObject()` needs to reach the scope from the outside when a shadow-object leaves the
|
|
42
|
+
* constructor set of an entity that stays alive. The scope removes its own entry when it tears down,
|
|
43
|
+
* so nothing here outlives the shadow-object it belongs to.
|
|
44
|
+
*
|
|
45
|
+
* The key is the shadow-object instance, which assumes one instance per construction. A
|
|
46
|
+
* constructor handing out the same instance twice — to a second entity, or to the same one
|
|
47
|
+
* under another token — leaves only the later scope reachable from here; the earlier one
|
|
48
|
+
* still tears down when its entity is destroyed.
|
|
49
|
+
*/
|
|
50
|
+
#shadowObjectScopes = /* @__PURE__ */ new WeakMap();
|
|
51
|
+
// The member names whose deprecation report this kernel has already made, one entry each. Handed
|
|
52
|
+
// to every creation scope this kernel builds, which is what gives the report the lifetime of the
|
|
53
|
+
// kernel rather than that of this module: an application running two shadow environments would
|
|
54
|
+
// otherwise report the deprecated call form to whichever of them got there first and to no other.
|
|
55
|
+
// One entry per name rather than a single flag, because a flag would swallow the reports of the
|
|
56
|
+
// four members that come after the first. `destroy()` leaves the set alone -- a kernel that has
|
|
57
|
+
// said it once has said it.
|
|
58
|
+
#shownDeprecations = /* @__PURE__ */ new Set();
|
|
31
59
|
constructor(registry) {
|
|
32
|
-
this.logger = new ConsoleLogger("Kernel");
|
|
33
|
-
this.#entities = /* @__PURE__ */ new Map();
|
|
34
|
-
this.#rootEntities = /* @__PURE__ */ new Set();
|
|
35
|
-
this.#allEntities = [];
|
|
36
|
-
this.#allEntitiesReversed = [];
|
|
37
|
-
this.#allEntitiesNeedUpdate = true;
|
|
38
|
-
this.#rootContexts = /* @__PURE__ */ new Map();
|
|
39
60
|
eventize(this);
|
|
40
61
|
this.registry = Registry.get(registry);
|
|
41
62
|
}
|
|
42
|
-
#entities;
|
|
43
|
-
#rootEntities;
|
|
44
|
-
#allEntities;
|
|
45
|
-
#allEntitiesReversed;
|
|
46
|
-
#allEntitiesNeedUpdate;
|
|
47
|
-
#rootContexts;
|
|
48
63
|
getEntity(uuid) {
|
|
49
64
|
return this.#requireEntry(uuid).entity;
|
|
50
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The entity behind a uuid, or `undefined` when the kernel does not hold one.
|
|
68
|
+
*
|
|
69
|
+
* The counterpart to `getEntity()`, which throws. Which of the two a caller wants follows from what an
|
|
70
|
+
* absent entity means to it: a change that describes the entity tree -- a new parent, a new order, new
|
|
71
|
+
* properties -- names an entity the view believes to be there, and a uuid the kernel does not know is a
|
|
72
|
+
* disagreement the caller has to hear about. An event is the other case: it carries no structure, and the
|
|
73
|
+
* entity it was meant for may have been torn down between the two sides.
|
|
74
|
+
*/
|
|
75
|
+
findEntity(uuid) {
|
|
76
|
+
return this.#entities.get(uuid)?.entity;
|
|
77
|
+
}
|
|
51
78
|
/**
|
|
52
79
|
* The entry for an entity that is expected to exist.
|
|
53
80
|
*
|
|
54
81
|
* The uuid is the caller's responsibility: `getEntity()` hands the throw on to its own
|
|
55
|
-
* callers, and
|
|
82
|
+
* callers, and `#updateShadowObjects()` is reached only for an entity the caller has just
|
|
56
83
|
* confirmed. Failing here names the uuid instead of failing later on an undefined field.
|
|
57
84
|
*/
|
|
58
85
|
#requireEntry(uuid) {
|
|
@@ -65,14 +92,46 @@ class Kernel {
|
|
|
65
92
|
hasEntity(uuid) {
|
|
66
93
|
return this.#entities.has(uuid);
|
|
67
94
|
}
|
|
95
|
+
/** The token of the entity behind `uuid`, or `undefined` when the kernel does not hold one. */
|
|
96
|
+
tokenOf(uuid) {
|
|
97
|
+
return this.#entities.get(uuid)?.token;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* How many entities the kernel is holding, one count per store, read without waiting on a garbage
|
|
101
|
+
* collector: a test can destroy a kernel and see that it let go of every one of them, rather than
|
|
102
|
+
* inferring it from what a collector happened to do. The two traversal counts are the cached
|
|
103
|
+
* breadth-first order and its reverse.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
get debugEntityCounts() {
|
|
108
|
+
return {
|
|
109
|
+
entities: this.#entities.size,
|
|
110
|
+
rootEntities: this.#rootEntities.size,
|
|
111
|
+
traversal: this.#allEntities.length,
|
|
112
|
+
traversalReversed: this.#allEntitiesReversed.length
|
|
113
|
+
};
|
|
114
|
+
}
|
|
68
115
|
/**
|
|
69
116
|
* @returns all entities in breadth-first order
|
|
117
|
+
*
|
|
118
|
+
* The list belongs to the caller: sorting or reversing it changes nothing about what the next call
|
|
119
|
+
* hands out. The walk behind it visits every entity once, which also carries it over a children list
|
|
120
|
+
* that points back at an ancestor -- `Entity.addChild()` writes such a list without touching the parent
|
|
121
|
+
* link, so no check along the parent chain can cover it.
|
|
122
|
+
*
|
|
123
|
+
* A uuid the kernel no longer holds drops out, and what hangs below it with it -- `getEntityGraph()`
|
|
124
|
+
* walks the same tree by the same rule.
|
|
70
125
|
*/
|
|
71
126
|
traverseLevelOrderBFS(reverse = false) {
|
|
72
127
|
if (this.#allEntitiesNeedUpdate) {
|
|
73
128
|
const lvl = /* @__PURE__ */ new Map();
|
|
129
|
+
const visited = /* @__PURE__ */ new Set();
|
|
74
130
|
const traverse = (uuid, depth) => {
|
|
75
|
-
|
|
131
|
+
if (visited.has(uuid)) return;
|
|
132
|
+
visited.add(uuid);
|
|
133
|
+
const e = this.findEntity(uuid);
|
|
134
|
+
if (e === void 0) return;
|
|
76
135
|
const entities = lvl.get(depth);
|
|
77
136
|
if (entities) {
|
|
78
137
|
entities.push(e);
|
|
@@ -90,46 +149,84 @@ class Kernel {
|
|
|
90
149
|
this.#allEntitiesReversed = this.#allEntities.slice().reverse();
|
|
91
150
|
this.#allEntitiesNeedUpdate = false;
|
|
92
151
|
}
|
|
93
|
-
return reverse ? this.#allEntitiesReversed : this.#allEntities;
|
|
152
|
+
return reverse ? this.#allEntitiesReversed.slice() : this.#allEntities.slice();
|
|
94
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The entity tree as nested nodes, one node per entity. The visited set is what makes that count
|
|
156
|
+
* hold: a children list that points back at an ancestor would otherwise be walked forever, and this
|
|
157
|
+
* is the one traversal a caller reaches for while debugging a tree that is already broken. A child
|
|
158
|
+
* the walk leaves out is named at its parent under `omittedChildren`, with the reason -- a uuid the
|
|
159
|
+
* kernel does not hold, or one the walk has already placed elsewhere in the graph. A root the walk
|
|
160
|
+
* has already reached through another root's children falls off the top level without a note; it
|
|
161
|
+
* still stands in the graph, just at the other position.
|
|
162
|
+
*/
|
|
95
163
|
getEntityGraph() {
|
|
96
|
-
|
|
164
|
+
const visited = /* @__PURE__ */ new Set();
|
|
165
|
+
return Array.from(this.#rootEntities).map((uuid) => this.#getEntityGraphNode(uuid, visited)).filter((node) => node !== void 0);
|
|
97
166
|
}
|
|
98
|
-
getEntityGraphNode(uuid) {
|
|
167
|
+
#getEntityGraphNode(uuid, visited, omitted) {
|
|
99
168
|
const entry = this.#entities.get(uuid);
|
|
100
|
-
if (entry === void 0)
|
|
169
|
+
if (entry === void 0) {
|
|
170
|
+
omitted?.push({ uuid, reason: "not-in-kernel" });
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
if (visited.has(uuid)) {
|
|
174
|
+
omitted?.push({ uuid, reason: "already-in-graph" });
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
visited.add(uuid);
|
|
101
178
|
const { token, entity } = entry;
|
|
179
|
+
const omittedChildren = [];
|
|
180
|
+
const children = entity.children.map((child) => this.#getEntityGraphNode(child.uuid, visited, omittedChildren)).filter((node) => node !== void 0);
|
|
102
181
|
return {
|
|
103
182
|
token,
|
|
104
183
|
entity,
|
|
105
184
|
props: Object.fromEntries(entity.propEntries()),
|
|
106
|
-
|
|
107
|
-
|
|
185
|
+
children,
|
|
186
|
+
...omittedChildren.length > 0 ? { omittedChildren } : {}
|
|
108
187
|
};
|
|
109
188
|
}
|
|
110
189
|
upgradeEntities() {
|
|
111
190
|
const entityConstructors = /* @__PURE__ */ new Map();
|
|
112
191
|
for (const entity of this.traverseLevelOrderBFS(true)) {
|
|
113
192
|
if (!this.hasEntity(entity.uuid)) continue;
|
|
114
|
-
entityConstructors.set(entity.uuid, this
|
|
193
|
+
entityConstructors.set(entity.uuid, this.#updateShadowObjects(entity.uuid, 2 /* DestroyOnly */));
|
|
115
194
|
}
|
|
116
195
|
for (const entity of this.traverseLevelOrderBFS(false)) {
|
|
117
196
|
if (!this.hasEntity(entity.uuid)) continue;
|
|
118
|
-
this
|
|
197
|
+
this.#updateShadowObjects(entity.uuid, 1 /* JustCreate */, entityConstructors.get(entity.uuid));
|
|
119
198
|
}
|
|
120
199
|
entityConstructors.clear();
|
|
121
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Apply a change trail, entry by entry, and say how far it got if it cannot be applied in full.
|
|
203
|
+
*
|
|
204
|
+
* The loop ends at the entry that throws, so what the kernel holds afterwards is a prefix of the
|
|
205
|
+
* trail; the {@link ChangeTrailRefusedError} names its length. The counter sits behind
|
|
206
|
+
* `#parse` and therefore counts only entries that returned normally.
|
|
207
|
+
*
|
|
208
|
+
* An effect the batch defers belongs to no single entry: the batch releases it once the loop is
|
|
209
|
+
* through, and a throw from there arrives with every entry already counted. `appliedCount` then
|
|
210
|
+
* equals `entryCount` — everything was applied, something failed nevertheless, and both
|
|
211
|
+
* statements are true at once.
|
|
212
|
+
*
|
|
213
|
+
* @throws {ChangeTrailRefusedError} if any entry, or any effect the batch deferred, threw
|
|
214
|
+
*/
|
|
122
215
|
run(event) {
|
|
123
|
-
|
|
124
|
-
|
|
216
|
+
this.logger.debug("sync", event);
|
|
217
|
+
let appliedCount = 0;
|
|
218
|
+
try {
|
|
219
|
+
batch(() => {
|
|
220
|
+
for (const entry of event.changeTrail) {
|
|
221
|
+
this.#parse(entry);
|
|
222
|
+
appliedCount++;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw new ChangeTrailRefusedError(appliedCount, event.changeTrail.length, { cause: error });
|
|
125
227
|
}
|
|
126
|
-
batch(() => {
|
|
127
|
-
for (const entry of event.changeTrail) {
|
|
128
|
-
this.parse(entry);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
228
|
}
|
|
132
|
-
parse(entry) {
|
|
229
|
+
#parse(entry) {
|
|
133
230
|
switch (entry.type) {
|
|
134
231
|
case ComponentChangeType.CreateEntities:
|
|
135
232
|
this.createEntity(
|
|
@@ -140,19 +237,15 @@ class Kernel {
|
|
|
140
237
|
entry.properties,
|
|
141
238
|
entry.autoDestructionOnParentRemoval
|
|
142
239
|
);
|
|
143
|
-
this.#allEntitiesNeedUpdate = true;
|
|
144
240
|
break;
|
|
145
241
|
case ComponentChangeType.DestroyEntities:
|
|
146
242
|
this.destroyEntity(entry.uuid);
|
|
147
|
-
this.#allEntitiesNeedUpdate = true;
|
|
148
243
|
break;
|
|
149
244
|
case ComponentChangeType.SetParent:
|
|
150
245
|
this.setParent(entry.uuid, entry.parentUuid, entry.order);
|
|
151
|
-
this.#allEntitiesNeedUpdate = true;
|
|
152
246
|
break;
|
|
153
247
|
case ComponentChangeType.UpdateOrder:
|
|
154
248
|
this.updateOrder(entry.uuid, entry.order);
|
|
155
|
-
this.#allEntitiesNeedUpdate = true;
|
|
156
249
|
break;
|
|
157
250
|
case ComponentChangeType.ChangeProperties:
|
|
158
251
|
this.changeProperties(entry.uuid, entry.properties);
|
|
@@ -166,41 +259,94 @@ class Kernel {
|
|
|
166
259
|
}
|
|
167
260
|
}
|
|
168
261
|
createEntity(uuid, token, parentUuid, order = 0, properties, autoDestructionOnParentRemoval = false) {
|
|
262
|
+
if (this.#entities.has(uuid)) {
|
|
263
|
+
throw new EntityUuidInUseError(uuid);
|
|
264
|
+
}
|
|
169
265
|
const e = new Entity(this, uuid);
|
|
170
266
|
e.order = order;
|
|
171
267
|
const entry = { token, entity: e, usedConstructors: /* @__PURE__ */ new Map() };
|
|
172
268
|
this.#entities.set(uuid, entry);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
269
|
+
this.noteEntityTreeChange(uuid);
|
|
270
|
+
try {
|
|
271
|
+
if (parentUuid) {
|
|
272
|
+
e.parentUuid = parentUuid;
|
|
273
|
+
}
|
|
274
|
+
e.autoDestructionOnParentRemoval = autoDestructionOnParentRemoval;
|
|
275
|
+
if (properties) {
|
|
276
|
+
e.setProperties(properties);
|
|
277
|
+
}
|
|
278
|
+
this.#createShadowObjects(entry);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
this.#rollbackFailedCreation(uuid);
|
|
281
|
+
throw error;
|
|
182
282
|
}
|
|
183
|
-
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Takes back what a creation managed before it failed. The teardown is the regular one, so a
|
|
286
|
+
* shadow-object that already stands hears its `onDestroy` and its creation scope ends the way
|
|
287
|
+
* it would on any other destruction; the entry, the root registration and the link to the
|
|
288
|
+
* parent go with it. `destroyEntity()` clears the bookkeeping of the entity whatever a callback
|
|
289
|
+
* along the way does, so there is nothing left here to take back by hand; only a failure of the
|
|
290
|
+
* teardown itself is reported, because the creation error is the one the caller is waiting for.
|
|
291
|
+
*
|
|
292
|
+
* The rollback reaches this one entity, not the kernel around it. What a constructor did to other
|
|
293
|
+
* entities before it threw stands, and the teardown even adds to it, because `destroyEntity()` walks
|
|
294
|
+
* the children list: an entity the constructor hung under this one survives as a root, and one it
|
|
295
|
+
* moved under this one is taken off the parent it came from -- or destroyed outright, where it
|
|
296
|
+
* carries `autoDestructionOnParentRemoval`. Both leave an entity in a state that is neither the one
|
|
297
|
+
* before the call nor the one the constructor built. A rollback that covers those cases needs a
|
|
298
|
+
* snapshot of the kernel, which this path does not take.
|
|
299
|
+
*/
|
|
300
|
+
#rollbackFailedCreation(uuid) {
|
|
301
|
+
runGuarded(
|
|
302
|
+
this.logger,
|
|
303
|
+
() => this.destroyEntity(uuid),
|
|
304
|
+
"rollback of a failed entity creation could not destroy the entity:",
|
|
305
|
+
uuid
|
|
306
|
+
);
|
|
184
307
|
}
|
|
185
308
|
destroyEntity(uuid) {
|
|
186
309
|
const entry = this.#entities.get(uuid);
|
|
187
310
|
if (entry === void 0) return;
|
|
188
311
|
const { entity, usedConstructors } = entry;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
312
|
+
try {
|
|
313
|
+
const childrenSnapshot = [...entity.children];
|
|
314
|
+
for (const child of childrenSnapshot) {
|
|
315
|
+
runGuarded(
|
|
316
|
+
this.logger,
|
|
317
|
+
() => {
|
|
318
|
+
if (child.autoDestructionOnParentRemoval) {
|
|
319
|
+
this.destroyEntity(child.uuid);
|
|
320
|
+
} else {
|
|
321
|
+
child.removeFromParent();
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"child of a destroyed entity could not be handed on:",
|
|
325
|
+
child.uuid
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
runGuarded(this.logger, () => entity.removeFromParent(), "entity could not be detached from its parent:", entity.uuid);
|
|
329
|
+
const shadowObjects = this.findShadowObjects(entity.uuid);
|
|
330
|
+
for (const shadowObject of shadowObjects) {
|
|
331
|
+
off(entity, shadowObject);
|
|
332
|
+
this.#notifyShadowObjectDestroy(shadowObject, entity);
|
|
196
333
|
}
|
|
334
|
+
runGuarded(this.logger, () => emitStrict(entity, onDestroy, entity), "entity onDestroy notification failed:", entity.uuid);
|
|
335
|
+
for (const shadowObject of shadowObjects) {
|
|
336
|
+
runGuarded(
|
|
337
|
+
this.logger,
|
|
338
|
+
() => this.#shadowObjectScopes.get(shadowObject)?.tearDown(),
|
|
339
|
+
"creation scope teardown of a destroyed entity failed:",
|
|
340
|
+
entity.uuid
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
runGuarded(this.logger, () => entity[onDestroy](), "entity release failed:", entity.uuid);
|
|
344
|
+
} finally {
|
|
345
|
+
usedConstructors.clear();
|
|
346
|
+
this.#entities.delete(entity.uuid);
|
|
347
|
+
this.#rootEntities.delete(entity.uuid);
|
|
348
|
+
this.#allEntitiesNeedUpdate = true;
|
|
197
349
|
}
|
|
198
|
-
entity.removeFromParent();
|
|
199
|
-
emit(entity, onDestroy, entity);
|
|
200
|
-
usedConstructors.clear();
|
|
201
|
-
this.#entities.delete(entity.uuid);
|
|
202
|
-
this.#rootEntities.delete(entity.uuid);
|
|
203
|
-
this.#allEntitiesNeedUpdate = true;
|
|
204
350
|
}
|
|
205
351
|
/**
|
|
206
352
|
* @param order the new order, or `undefined` to keep the current one. A set-parent change
|
|
@@ -214,59 +360,93 @@ class Kernel {
|
|
|
214
360
|
if (parentUuid && !this.#entities.has(parentUuid)) {
|
|
215
361
|
throw new Error(`entity with uuid "${parentUuid}" not found!`);
|
|
216
362
|
}
|
|
217
|
-
|
|
363
|
+
if (parentUuid) {
|
|
364
|
+
e.assertAttachableTo(this.getEntity(parentUuid));
|
|
365
|
+
}
|
|
366
|
+
e.removeFromParent(parentUuid == null);
|
|
218
367
|
e.order = nextOrder;
|
|
219
368
|
e.parentUuid = parentUuid;
|
|
220
|
-
|
|
369
|
+
this.logger.debug("entity.onParentChanged", { uuid, parentUuid, order: nextOrder, entity: e });
|
|
370
|
+
runGuarded(this.logger, () => emitStrict(e, onParentChanged, e), "entity onParentChanged notification failed:", uuid);
|
|
371
|
+
}
|
|
372
|
+
updateOrder(uuid, order) {
|
|
373
|
+
this.getEntity(uuid).order = order;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Records that an entity has changed its place in the entity tree: it took a parent, lost one,
|
|
377
|
+
* or moved among its siblings. Two pieces of bookkeeping follow from that -- the cached
|
|
378
|
+
* traversal is dropped, and an entity without a parent joins the set of root entities while one
|
|
379
|
+
* with a parent leaves it.
|
|
380
|
+
*
|
|
381
|
+
* `Entity` reports here from every write that moves it, because those writes are reachable from
|
|
382
|
+
* a shadow-object with no kernel call in between: `entity.parent`, `entity.parentUuid`,
|
|
383
|
+
* `entity.order` and `entity.removeFromParent()`. The state is read off the entity rather than
|
|
384
|
+
* taken as an argument, so a caller cannot describe a move the entity did not make.
|
|
385
|
+
*
|
|
386
|
+
* A uuid the kernel does not hold reaches the cache and stops there: an entity joins the root
|
|
387
|
+
* set when the kernel takes it in and leaves it when the kernel lets go.
|
|
388
|
+
*
|
|
389
|
+
* That such a uuid passes in silence, where `setParent()` and `updateOrder()` throw at one, is the
|
|
390
|
+
* difference between a request and a report. Those two are asked to move an entity and cannot do it
|
|
391
|
+
* without one, so an unknown uuid is a disagreement the caller has to hear about. This is an entity
|
|
392
|
+
* saying where it stands, and it says so while the kernel is still taking it in: `createEntity()`
|
|
393
|
+
* writes the order on the entity before the entry is registered, so an entity created with an order
|
|
394
|
+
* of its own reports once before the kernel holds it. A throw there would cost the creation its own
|
|
395
|
+
* bookkeeping, and there is nothing to disagree about -- dropping the cache is right either way.
|
|
396
|
+
*/
|
|
397
|
+
noteEntityTreeChange(uuid) {
|
|
398
|
+
this.#allEntitiesNeedUpdate = true;
|
|
399
|
+
const entry = this.#entities.get(uuid);
|
|
400
|
+
if (entry === void 0) return;
|
|
401
|
+
if (entry.entity.hasParent) {
|
|
221
402
|
this.#rootEntities.delete(uuid);
|
|
222
403
|
} else {
|
|
223
404
|
this.#rootEntities.add(uuid);
|
|
224
405
|
}
|
|
225
|
-
e.reSubscribeToParentContexts();
|
|
226
|
-
queueMicrotask(() => {
|
|
227
|
-
if (this.logger.isDebug) {
|
|
228
|
-
this.logger.debug("entity.onParentChanged", { uuid, parentUuid, order: nextOrder, entity: e });
|
|
229
|
-
}
|
|
230
|
-
emit(e, onParentChanged, e);
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
updateOrder(uuid, order) {
|
|
234
|
-
this.getEntity(uuid).order = order;
|
|
235
406
|
}
|
|
236
407
|
dispatchEventsToEntity(uuid, events) {
|
|
237
|
-
this.
|
|
408
|
+
this.findEntity(uuid)?.dispatchViewEvents(events);
|
|
238
409
|
}
|
|
239
410
|
changeProperties(uuid, properties) {
|
|
240
411
|
this.getEntity(uuid).setProperties(properties);
|
|
241
|
-
this
|
|
412
|
+
this.#updateShadowObjects(uuid);
|
|
242
413
|
}
|
|
243
414
|
changeToken(uuid, token) {
|
|
244
415
|
const entry = this.#entities.get(uuid);
|
|
245
416
|
if (entry === void 0) return;
|
|
246
417
|
if (entry.token === token) return;
|
|
418
|
+
const previousToken = entry.token;
|
|
247
419
|
entry.token = token;
|
|
248
|
-
this
|
|
420
|
+
this.#updateShadowObjects(uuid, 0 /* CreateAndDestroy */, void 0, previousToken);
|
|
249
421
|
}
|
|
250
422
|
dispatchMessageToView(message) {
|
|
251
423
|
queueMicrotask(() => {
|
|
252
|
-
|
|
424
|
+
emitSafe(this, MessageToView, message);
|
|
253
425
|
});
|
|
254
426
|
}
|
|
255
427
|
/**
|
|
256
428
|
* Create or destroy the shadow-objects of an entity using the registered constructors.
|
|
257
429
|
* After a token change or registry changes, an entity may be given different shadow-objects.
|
|
430
|
+
*
|
|
431
|
+
* @param previousToken the token the entry carried before the caller wrote the new one, or
|
|
432
|
+
* `undefined` where the caller left the token alone. A rebuild that does not get through puts it
|
|
433
|
+
* back before it restores the shadow-objects, so an entity is never built against a token it does
|
|
434
|
+
* not carry.
|
|
258
435
|
*/
|
|
259
|
-
updateShadowObjects(uuid, action = 0 /* CreateAndDestroy */, nextConstructors) {
|
|
436
|
+
#updateShadowObjects(uuid, action = 0 /* CreateAndDestroy */, nextConstructors, previousToken) {
|
|
260
437
|
const entry = this.#requireEntry(uuid);
|
|
261
438
|
nextConstructors ??= new Set(this.registry.findConstructors(entry.token, entry.entity.truthyProps()));
|
|
262
439
|
const shouldDestroy = action === 0 /* CreateAndDestroy */ || action === 2 /* DestroyOnly */;
|
|
263
440
|
const shouldCreate = action === 0 /* CreateAndDestroy */ || action === 1 /* JustCreate */;
|
|
441
|
+
const removed = [];
|
|
442
|
+
const created = [];
|
|
264
443
|
if (shouldDestroy) {
|
|
265
444
|
for (const [construct, shadowObjects] of entry.usedConstructors) {
|
|
266
445
|
if (!nextConstructors.has(construct)) {
|
|
267
446
|
entry.usedConstructors.delete(construct);
|
|
447
|
+
removed.push({ construct, count: shadowObjects.size });
|
|
268
448
|
for (const obj of shadowObjects) {
|
|
269
|
-
this
|
|
449
|
+
this.#destroyShadowObject(obj, entry.entity);
|
|
270
450
|
}
|
|
271
451
|
}
|
|
272
452
|
}
|
|
@@ -274,304 +454,110 @@ class Kernel {
|
|
|
274
454
|
if (shouldCreate) {
|
|
275
455
|
for (const construct of nextConstructors) {
|
|
276
456
|
if (!entry.usedConstructors.has(construct)) {
|
|
277
|
-
|
|
457
|
+
try {
|
|
458
|
+
created.push(this.#constructShadowObject(construct, entry));
|
|
459
|
+
} catch (error) {
|
|
460
|
+
this.#rollbackFailedShadowObjectUpdate(entry, created, removed, previousToken);
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
278
463
|
}
|
|
279
464
|
}
|
|
280
465
|
}
|
|
281
466
|
return nextConstructors;
|
|
282
467
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Takes a rebuild of the shadow-objects of one entity back to where the call found it.
|
|
470
|
+
*
|
|
471
|
+
* The identity check comes first, because a constructor may destroy the entity it is coming to life
|
|
472
|
+
* on. Where the kernel no longer holds this entry -- including where another entry stands under the
|
|
473
|
+
* same uuid by now -- `destroyEntity()` has already notified every shadow-object of the entry and
|
|
474
|
+
* cleared `usedConstructors`; both halves of the rollback would find nothing to work with.
|
|
475
|
+
*
|
|
476
|
+
* The token before the shadow-objects: what is rebuilt is built against the token the entity carries
|
|
477
|
+
* afterwards.
|
|
478
|
+
*
|
|
479
|
+
* Backwards down, forwards up: the new shadow-objects leave before the previous ones come back, so
|
|
480
|
+
* the two sets never stand on the entity at the same time.
|
|
481
|
+
*
|
|
482
|
+
* Every step behind a guard of its own and nothing re-thrown -- the caller is waiting for the error
|
|
483
|
+
* of the construction, not for one from the way back.
|
|
484
|
+
*
|
|
485
|
+
* What the rollback does not reach is named rather than passed over. `changeProperties()` has written its
|
|
486
|
+
* properties before it gets here, and they stay written -- a constructor restored below can therefore
|
|
487
|
+
* stand on an entity whose properties no longer route to it, until the next re-resolution of the set
|
|
488
|
+
* leaves it out and takes it down. And `upgradeEntities()` takes its shadow-objects down in one pass over
|
|
489
|
+
* the entity tree and builds in the next, so a rollback in the second pass knows nothing of what the first
|
|
490
|
+
* one took -- at this entity as much as at any other; the entity keeps the token it came in with, an
|
|
491
|
+
* upgrade having written none, and is left with whatever the first pass spared. Both would need a snapshot
|
|
492
|
+
* of the kernel, which this path does not take.
|
|
493
|
+
*/
|
|
494
|
+
#rollbackFailedShadowObjectUpdate(entry, created, removed, previousToken) {
|
|
495
|
+
if (this.#entities.get(entry.entity.uuid) !== entry) return;
|
|
496
|
+
if (previousToken !== void 0) {
|
|
497
|
+
entry.token = previousToken;
|
|
498
|
+
}
|
|
499
|
+
for (let i = created.length - 1; i >= 0; i--) {
|
|
500
|
+
runGuarded(
|
|
501
|
+
this.logger,
|
|
502
|
+
() => this.#destroyShadowObject(created[i], entry.entity),
|
|
503
|
+
"rollback of a failed shadow-object update could not remove a new shadow-object:",
|
|
504
|
+
entry.entity.uuid
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
for (const { construct, count } of removed) {
|
|
508
|
+
for (let i = 0; i < count; i++) {
|
|
509
|
+
runGuarded(
|
|
510
|
+
this.logger,
|
|
511
|
+
() => this.#constructShadowObject(construct, entry),
|
|
512
|
+
"rollback of a failed shadow-object update could not restore a shadow-object:",
|
|
513
|
+
getDisplayName(construct),
|
|
514
|
+
entry.entity.uuid
|
|
312
515
|
);
|
|
313
516
|
}
|
|
314
|
-
return propReader;
|
|
315
|
-
};
|
|
316
|
-
const shadowObject = eventize(
|
|
317
|
-
new construct({
|
|
318
|
-
entity: entry.entity,
|
|
319
|
-
provideContext(name, sourceOrInitialValue, options) {
|
|
320
|
-
if (!provideContextOptionsDeprecatedShown && options != null && typeof options === "function") {
|
|
321
|
-
console.warn(
|
|
322
|
-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideContext()" is now passed as {compare} argument. Please update your code accordingly.'
|
|
323
|
-
);
|
|
324
|
-
provideContextOptionsDeprecatedShown = true;
|
|
325
|
-
}
|
|
326
|
-
const opts = typeof options === "function" ? { compare: options } : options;
|
|
327
|
-
let ctxProvider = contextProviders.get(name);
|
|
328
|
-
if (ctxProvider == null) {
|
|
329
|
-
const isSig = isSignal(sourceOrInitialValue);
|
|
330
|
-
const initialValue = isSig ? void 0 : toMaybe(sourceOrInitialValue);
|
|
331
|
-
ctxProvider = createSignal(initialValue, opts?.compare ? { compare: opts.compare } : void 0);
|
|
332
|
-
if (isSig) {
|
|
333
|
-
const ln2 = link(sourceOrInitialValue, ctxProvider);
|
|
334
|
-
unsubscribeSecondary.add(ln2.destroy.bind(ln2));
|
|
335
|
-
}
|
|
336
|
-
const ln = link(ctxProvider, entry.entity.provideContext(name));
|
|
337
|
-
unsubscribeSecondary.add(ln.destroy.bind(ln));
|
|
338
|
-
contextProviders.set(name, ctxProvider);
|
|
339
|
-
}
|
|
340
|
-
if (ctxProvider != null && (opts?.clearOnDestroy ?? true)) {
|
|
341
|
-
unsubscribeSecondary.add(() => {
|
|
342
|
-
ctxProvider.set(void 0);
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
return ctxProvider;
|
|
346
|
-
},
|
|
347
|
-
provideGlobalContext(name, sourceOrInitialValue, options) {
|
|
348
|
-
if (!provideGlobalContextOptionsDeprecatedShown && options != null && typeof options === "function") {
|
|
349
|
-
console.warn(
|
|
350
|
-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "provideGlobalContext()" is now passed as {compare} argument. Please update your code accordingly.'
|
|
351
|
-
);
|
|
352
|
-
provideGlobalContextOptionsDeprecatedShown = true;
|
|
353
|
-
}
|
|
354
|
-
const opts = typeof options === "function" ? { compare: options } : options;
|
|
355
|
-
let ctxProvider = contextRootProviders.get(name);
|
|
356
|
-
if (ctxProvider == null) {
|
|
357
|
-
const isSig = isSignal(sourceOrInitialValue);
|
|
358
|
-
const initialValue = isSig ? void 0 : toMaybe(sourceOrInitialValue);
|
|
359
|
-
ctxProvider = createSignal(initialValue, opts?.compare ? { compare: opts.compare } : void 0);
|
|
360
|
-
if (isSig) {
|
|
361
|
-
const ln2 = link(sourceOrInitialValue, ctxProvider);
|
|
362
|
-
unsubscribeSecondary.add(ln2.destroy.bind(ln2));
|
|
363
|
-
}
|
|
364
|
-
const ln = link(ctxProvider, entry.entity.provideGlobalContext(name));
|
|
365
|
-
unsubscribeSecondary.add(ln.destroy.bind(ln));
|
|
366
|
-
contextRootProviders.set(name, ctxProvider);
|
|
367
|
-
}
|
|
368
|
-
if (ctxProvider != null && (opts?.clearOnDestroy ?? true)) {
|
|
369
|
-
unsubscribeSecondary.add(() => {
|
|
370
|
-
ctxProvider.set(void 0);
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
return ctxProvider;
|
|
374
|
-
},
|
|
375
|
-
useContext(name, options) {
|
|
376
|
-
if (!useContextOptionsDeprecatedShown && options != null && typeof options === "function") {
|
|
377
|
-
console.warn(
|
|
378
|
-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "useContext()" is now passed as {compare} argument. Please update your code accordingly.'
|
|
379
|
-
);
|
|
380
|
-
useContextOptionsDeprecatedShown = true;
|
|
381
|
-
}
|
|
382
|
-
const opts = typeof options === "function" ? { compare: options } : options;
|
|
383
|
-
let ctxReader = contextReaders.get(name);
|
|
384
|
-
if (ctxReader === void 0) {
|
|
385
|
-
ctxReader = createSignal(void 0, opts).get;
|
|
386
|
-
contextReaders.set(name, ctxReader);
|
|
387
|
-
contextReaderCompares.set(name, opts?.compare);
|
|
388
|
-
const ln = link(entry.entity.useContext(name), ctxReader);
|
|
389
|
-
unsubscribeSecondary.add(ln.destroy.bind(ln));
|
|
390
|
-
} else if (opts?.compare != null && contextReaderCompares.get(name) !== opts.compare) {
|
|
391
|
-
console.warn(
|
|
392
|
-
`[shadow-objects] useContext("${String(name)}"): the cached signal already exists with a different (or no) {compare} function \u2014 the new options are ignored. Pass options only on the first call per context.`
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
return ctxReader;
|
|
396
|
-
},
|
|
397
|
-
useParentContext(name, options) {
|
|
398
|
-
if (!useParentContextOptionsDeprecatedShown && options != null && typeof options === "function") {
|
|
399
|
-
console.warn(
|
|
400
|
-
'[shadow-objects] Deprecation Warning: The "isEqual" option of "useParentContext()" is now passed as {compare} argument. Please update your code accordingly.'
|
|
401
|
-
);
|
|
402
|
-
useParentContextOptionsDeprecatedShown = true;
|
|
403
|
-
}
|
|
404
|
-
const opts = typeof options === "function" ? { compare: options } : options;
|
|
405
|
-
let ctxReader = contextParentReaders.get(name);
|
|
406
|
-
if (ctxReader === void 0) {
|
|
407
|
-
ctxReader = createSignal(void 0, opts).get;
|
|
408
|
-
contextParentReaders.set(name, ctxReader);
|
|
409
|
-
contextParentReaderCompares.set(name, opts?.compare);
|
|
410
|
-
const ln = link(entry.entity.useParentContext(name), ctxReader);
|
|
411
|
-
unsubscribeSecondary.add(ln.destroy.bind(ln));
|
|
412
|
-
} else if (opts?.compare != null && contextParentReaderCompares.get(name) !== opts.compare) {
|
|
413
|
-
console.warn(
|
|
414
|
-
`[shadow-objects] useParentContext("${String(name)}"): the cached signal already exists with a different (or no) {compare} function \u2014 the new options are ignored. Pass options only on the first call per parent context.`
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
return ctxReader;
|
|
418
|
-
},
|
|
419
|
-
dispatchMessageToView(type, data, transferables, traverseChildren = false) {
|
|
420
|
-
entry.entity.dispatchMessageToView(type, data, transferables, traverseChildren);
|
|
421
|
-
},
|
|
422
|
-
useProperty: getUseProperty,
|
|
423
|
-
useProperties(props) {
|
|
424
|
-
const result = {};
|
|
425
|
-
for (const key in props) {
|
|
426
|
-
if (Object.hasOwn(props, key)) {
|
|
427
|
-
result[key] = getUseProperty(props[key]);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return result;
|
|
431
|
-
},
|
|
432
|
-
createResource(factory, cleanup) {
|
|
433
|
-
const resourceSignal = createSignal();
|
|
434
|
-
const effect = createEffect(() => {
|
|
435
|
-
const resource = toMaybe(factory());
|
|
436
|
-
resourceSignal.set(resource);
|
|
437
|
-
if (resource !== void 0 && cleanup) {
|
|
438
|
-
return () => {
|
|
439
|
-
cleanup(resource);
|
|
440
|
-
resourceSignal.set(void 0);
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
return () => {
|
|
444
|
-
resourceSignal.set(void 0);
|
|
445
|
-
};
|
|
446
|
-
});
|
|
447
|
-
unsubscribeSecondary.add(() => {
|
|
448
|
-
effect.destroy();
|
|
449
|
-
resourceSignal.set(void 0);
|
|
450
|
-
destroySignal(resourceSignal);
|
|
451
|
-
});
|
|
452
|
-
return resourceSignal;
|
|
453
|
-
},
|
|
454
|
-
createEffect(...args) {
|
|
455
|
-
const effect = createEffect(...args);
|
|
456
|
-
unsubscribeSecondary.add(effect.destroy);
|
|
457
|
-
return effect;
|
|
458
|
-
},
|
|
459
|
-
createSignal(...args) {
|
|
460
|
-
const sig = createSignal(...args);
|
|
461
|
-
unsubscribeSecondary.add(() => {
|
|
462
|
-
destroySignal(sig);
|
|
463
|
-
});
|
|
464
|
-
return sig;
|
|
465
|
-
},
|
|
466
|
-
createMemo(...args) {
|
|
467
|
-
const sig = createMemo(...args);
|
|
468
|
-
unsubscribeSecondary.add(() => {
|
|
469
|
-
destroySignal(sig);
|
|
470
|
-
});
|
|
471
|
-
return sig;
|
|
472
|
-
},
|
|
473
|
-
on(...args) {
|
|
474
|
-
const [firstArg] = args;
|
|
475
|
-
if (typeof firstArg === "string" || typeof firstArg === "symbol" || Array.isArray(firstArg)) {
|
|
476
|
-
const unsub2 = on(entry.entity, ...args);
|
|
477
|
-
unsubscribeSecondary.add(unsub2);
|
|
478
|
-
return unsub2;
|
|
479
|
-
}
|
|
480
|
-
const unsub = on(...args);
|
|
481
|
-
unsubscribeSecondary.add(unsub);
|
|
482
|
-
return Object.assign(() => {
|
|
483
|
-
unsubscribeSecondary.delete(unsub);
|
|
484
|
-
unsub();
|
|
485
|
-
}, unsub);
|
|
486
|
-
},
|
|
487
|
-
once(...args) {
|
|
488
|
-
const [firstArg] = args;
|
|
489
|
-
if (typeof firstArg === "string" || typeof firstArg === "symbol" || Array.isArray(firstArg)) {
|
|
490
|
-
const unsub2 = once(entry.entity, ...args);
|
|
491
|
-
unsubscribeSecondary.add(unsub2);
|
|
492
|
-
return unsub2;
|
|
493
|
-
}
|
|
494
|
-
const unsub = once(...args);
|
|
495
|
-
unsubscribeSecondary.add(unsub);
|
|
496
|
-
return Object.assign(() => {
|
|
497
|
-
unsubscribeSecondary.delete(unsub);
|
|
498
|
-
unsub();
|
|
499
|
-
}, unsub);
|
|
500
|
-
},
|
|
501
|
-
emit(...args) {
|
|
502
|
-
const [firstArg] = args;
|
|
503
|
-
if (typeof firstArg === "string" || typeof firstArg === "symbol" || Array.isArray(firstArg)) {
|
|
504
|
-
emit(entry.entity, ...args);
|
|
505
|
-
} else {
|
|
506
|
-
emit(...args);
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
onViewEvent(callback) {
|
|
510
|
-
const unsub = on(entry.entity, onViewEvent, (type, data) => {
|
|
511
|
-
callback(type, data);
|
|
512
|
-
});
|
|
513
|
-
unsubscribeSecondary.add(unsub);
|
|
514
|
-
},
|
|
515
|
-
onDestroy(callback) {
|
|
516
|
-
unsubscribePrimary.add(callback);
|
|
517
|
-
}
|
|
518
|
-
})
|
|
519
|
-
);
|
|
520
|
-
if (this.logger.isInfo) {
|
|
521
|
-
this.logger.info("create shadow-object", getDisplayName(construct), { shadowObject, entity: entry.entity });
|
|
522
517
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
destroySignal(sig);
|
|
547
|
-
}
|
|
548
|
-
unsubscribePrimary.clear();
|
|
549
|
-
unsubscribeSecondary.clear();
|
|
550
|
-
contextReaders.clear();
|
|
551
|
-
contextParentReaders.clear();
|
|
552
|
-
propertyReaders.clear();
|
|
553
|
-
contextProviders.clear();
|
|
554
|
-
contextRootProviders.clear();
|
|
555
|
-
const otherShadowObjects = entry.usedConstructors.get(construct);
|
|
556
|
-
if (otherShadowObjects) {
|
|
557
|
-
otherShadowObjects.delete(shadowObject);
|
|
558
|
-
if (otherShadowObjects.size === 0) {
|
|
559
|
-
entry.usedConstructors.delete(construct);
|
|
518
|
+
}
|
|
519
|
+
#constructShadowObject(construct, entry) {
|
|
520
|
+
const scope = new ShadowObjectCreationScope(entry.entity, this.logger, getDisplayName(construct), this.#shownDeprecations);
|
|
521
|
+
let shadowObject;
|
|
522
|
+
try {
|
|
523
|
+
shadowObject = eventize(new construct(scope.createAPI()));
|
|
524
|
+
} catch (error) {
|
|
525
|
+
scope.tearDown();
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
this.#shadowObjectScopes.set(shadowObject, scope);
|
|
529
|
+
scope.bindTo(
|
|
530
|
+
shadowObject,
|
|
531
|
+
() => {
|
|
532
|
+
this.#shadowObjectScopes.delete(shadowObject);
|
|
533
|
+
},
|
|
534
|
+
() => {
|
|
535
|
+
const otherShadowObjects = entry.usedConstructors.get(construct);
|
|
536
|
+
if (otherShadowObjects) {
|
|
537
|
+
otherShadowObjects.delete(shadowObject);
|
|
538
|
+
if (otherShadowObjects.size === 0) {
|
|
539
|
+
entry.usedConstructors.delete(construct);
|
|
540
|
+
}
|
|
560
541
|
}
|
|
561
542
|
}
|
|
562
|
-
|
|
543
|
+
);
|
|
563
544
|
const createdBy = entry.usedConstructors.get(construct);
|
|
564
545
|
if (createdBy) {
|
|
565
546
|
createdBy.add(shadowObject);
|
|
566
547
|
} else {
|
|
567
548
|
entry.usedConstructors.set(construct, /* @__PURE__ */ new Set([shadowObject]));
|
|
568
549
|
}
|
|
569
|
-
|
|
550
|
+
try {
|
|
551
|
+
this.#attachShadowObject(shadowObject, entry.entity);
|
|
552
|
+
} catch (error) {
|
|
553
|
+
this.#destroyShadowObject(shadowObject, entry.entity);
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
570
556
|
return shadowObject;
|
|
571
557
|
}
|
|
572
|
-
createShadowObjects(entry) {
|
|
558
|
+
#createShadowObjects(entry) {
|
|
573
559
|
this.registry.findConstructors(entry.token, entry.entity.truthyProps())?.forEach((construct) => {
|
|
574
|
-
this
|
|
560
|
+
this.#constructShadowObject(construct, entry);
|
|
575
561
|
});
|
|
576
562
|
}
|
|
577
563
|
findShadowObjects(uuid) {
|
|
@@ -580,17 +566,90 @@ class Kernel {
|
|
|
580
566
|
const { usedConstructors } = entry;
|
|
581
567
|
return Array.from(new Set(Array.from(usedConstructors.values()).flatMap((objs) => Array.from(objs))));
|
|
582
568
|
}
|
|
583
|
-
|
|
569
|
+
/**
|
|
570
|
+
* One description per Shadow Object of the entity, in the order `findShadowObjects()` lists them:
|
|
571
|
+
* what the creation scope knows (display name, the five name lists), the tokens the constructor is
|
|
572
|
+
* defined under in this kernel's registry, and which of the four lifecycle hooks the instance
|
|
573
|
+
* implements. `[]` for a uuid the kernel does not hold. Reads only; a scope that is already gone
|
|
574
|
+
* contributes its display name and empty lists.
|
|
575
|
+
*/
|
|
576
|
+
describeShadowObjects(uuid) {
|
|
577
|
+
const entry = this.#entities.get(uuid);
|
|
578
|
+
if (entry === void 0) return [];
|
|
579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
580
|
+
const descriptions = [];
|
|
581
|
+
for (const [construct, shadowObjects] of entry.usedConstructors) {
|
|
582
|
+
for (const shadowObject of shadowObjects) {
|
|
583
|
+
if (seen.has(shadowObject)) continue;
|
|
584
|
+
seen.add(shadowObject);
|
|
585
|
+
const scope = this.#shadowObjectScopes.get(shadowObject);
|
|
586
|
+
const hooks = LIFECYCLE_HOOKS.filter(([, symbol]) => typeof shadowObject[symbol] === "function").map(
|
|
587
|
+
([name]) => name
|
|
588
|
+
);
|
|
589
|
+
descriptions.push({
|
|
590
|
+
...scope?.describe() ?? {
|
|
591
|
+
displayName: getDisplayName(construct),
|
|
592
|
+
usesProperties: [],
|
|
593
|
+
usesContexts: [],
|
|
594
|
+
usesParentContexts: [],
|
|
595
|
+
providesContexts: [],
|
|
596
|
+
providesGlobalContexts: []
|
|
597
|
+
},
|
|
598
|
+
definedUnder: this.registry.tokensOf(construct),
|
|
599
|
+
hooks
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return descriptions;
|
|
604
|
+
}
|
|
605
|
+
#attachShadowObject(shadowObject, entity) {
|
|
606
|
+
for (const [name, symbol] of LIFECYCLE_HOOKS) {
|
|
607
|
+
if (typeof shadowObject[name] === "function" && typeof shadowObject[symbol] !== "function") {
|
|
608
|
+
const displayName = this.#shadowObjectScopes.get(shadowObject)?.displayName;
|
|
609
|
+
this.logger.error(
|
|
610
|
+
`the "${name}" lifecycle hook is a plain method and is never called; use the [${name}] symbol from "@spearwolf/shadow-objects/shadow-objects.js":`,
|
|
611
|
+
displayName
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
584
615
|
on(entity, shadowObject);
|
|
585
616
|
if (typeof shadowObject[onCreate] === "function") {
|
|
586
617
|
shadowObject[onCreate](entity);
|
|
587
618
|
}
|
|
588
619
|
}
|
|
589
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Tells one shadow-object that it is about to end, in both halves the notification has: the
|
|
622
|
+
* class-side `[onDestroy]` hook and the event other objects can listen for.
|
|
623
|
+
*
|
|
624
|
+
* Each half stands behind a guard of its own rather than a shared one, because neither is allowed to
|
|
625
|
+
* cost the other: a hook that throws still lets the event go out, and a listener that throws leaves
|
|
626
|
+
* the hook it came after untouched. Within the event half the same rule holds one level down, through
|
|
627
|
+
* `emitStrict()`: a listener that throws costs no other listener its turn. Nothing is re-thrown -- the
|
|
628
|
+
* shadow-objects of one entity reach their end in one sweep, and a failure at one of them may not take
|
|
629
|
+
* the sweep with it. Reports are
|
|
630
|
+
* keyed by the name the scope carries rather than the one the instance would give -- see
|
|
631
|
+
* `ShadowObjectCreationScope.displayName` for where the two part ways.
|
|
632
|
+
*/
|
|
633
|
+
#notifyShadowObjectDestroy(shadowObject, entity) {
|
|
634
|
+
const displayName = this.#shadowObjectScopes.get(shadowObject)?.displayName;
|
|
590
635
|
if (typeof shadowObject[onDestroy] === "function") {
|
|
591
|
-
|
|
636
|
+
runGuarded(
|
|
637
|
+
this.logger,
|
|
638
|
+
() => shadowObject[onDestroy](entity),
|
|
639
|
+
"shadow-object onDestroy hook failed:",
|
|
640
|
+
displayName
|
|
641
|
+
);
|
|
592
642
|
}
|
|
593
|
-
|
|
643
|
+
runGuarded(
|
|
644
|
+
this.logger,
|
|
645
|
+
() => emitStrict(shadowObject, onDestroy, entity),
|
|
646
|
+
"shadow-object onDestroy notification failed:",
|
|
647
|
+
displayName
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
#destroyShadowObject(shadowObject, entity) {
|
|
651
|
+
this.#notifyShadowObjectDestroy(shadowObject, entity);
|
|
652
|
+
this.#shadowObjectScopes.get(shadowObject)?.tearDown();
|
|
594
653
|
off(entity, shadowObject);
|
|
595
654
|
}
|
|
596
655
|
findOrCreateRootContext(name) {
|
|
@@ -601,14 +660,42 @@ class Kernel {
|
|
|
601
660
|
}
|
|
602
661
|
return ctx;
|
|
603
662
|
}
|
|
663
|
+
/** The names of the kernel-wide context chains -- every name a root entity uses or any entity provides globally. */
|
|
664
|
+
rootContextNames() {
|
|
665
|
+
return Array.from(this.#rootContexts.keys());
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* One kernel-wide context chain: what it resolves to, and its members in chain order. The members
|
|
669
|
+
* are the signals the entities contributed -- `Entity.describeGlobalContext()` hands out the same
|
|
670
|
+
* objects, so a caller can match them by identity. `undefined` for a name the kernel holds no
|
|
671
|
+
* chain for; asking does not create one.
|
|
672
|
+
*/
|
|
673
|
+
describeRootContext(name) {
|
|
674
|
+
const path = this.#rootContexts.get(name);
|
|
675
|
+
if (path === void 0) return void 0;
|
|
676
|
+
return { value: path.value, signals: path.signals };
|
|
677
|
+
}
|
|
604
678
|
destroy() {
|
|
679
|
+
for (const entity of this.traverseLevelOrderBFS(true)) {
|
|
680
|
+
runGuarded(
|
|
681
|
+
this.logger,
|
|
682
|
+
() => this.destroyEntity(entity.uuid),
|
|
683
|
+
"entity teardown during kernel destroy failed:",
|
|
684
|
+
entity.uuid
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
this.#entities.clear();
|
|
688
|
+
this.#rootEntities.clear();
|
|
689
|
+
this.#allEntities.length = 0;
|
|
690
|
+
this.#allEntitiesReversed.length = 0;
|
|
691
|
+
this.#allEntitiesNeedUpdate = true;
|
|
605
692
|
for (const ctx of this.#rootContexts.values()) {
|
|
606
693
|
ctx.dispose();
|
|
607
694
|
}
|
|
608
695
|
this.#rootContexts.clear();
|
|
609
|
-
|
|
610
|
-
this
|
|
611
|
-
}
|
|
696
|
+
queueMicrotask(() => {
|
|
697
|
+
off(this);
|
|
698
|
+
});
|
|
612
699
|
}
|
|
613
700
|
}
|
|
614
701
|
export {
|