@relu-interactives/spatial-ecs 0.2.20 → 0.2.22

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.
Files changed (51) hide show
  1. package/dist/{System-BjwOBx81.d.cts → System-CYWZ_Zx5.d.cts} +9 -4
  2. package/dist/{System-BjwOBx81.d.ts → System-CYWZ_Zx5.d.ts} +9 -4
  3. package/dist/{Video-B8yVI1tF.d.cts → Video-C1IOat7L.d.ts} +38 -9
  4. package/dist/{Video-B9z5AtIf.d.ts → Video-ppAXaKt0.d.cts} +38 -9
  5. package/dist/{chunk-HUHN6IFD.js → chunk-52CHDPRQ.js} +118 -92
  6. package/dist/chunk-52CHDPRQ.js.map +1 -0
  7. package/dist/{chunk-67MFQDHX.js → chunk-GZ2NKSMV.js} +609 -90
  8. package/dist/chunk-GZ2NKSMV.js.map +1 -0
  9. package/dist/{chunk-Z6OMYEXU.js → chunk-JHCPRVBC.js} +34 -5
  10. package/dist/chunk-JHCPRVBC.js.map +1 -0
  11. package/dist/{chunk-DY4ETMP2.js → chunk-WKGCC7YL.js} +3 -3
  12. package/dist/{chunk-DY4ETMP2.js.map → chunk-WKGCC7YL.js.map} +1 -1
  13. package/dist/components/index.cjs +31 -2
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +3 -3
  16. package/dist/components/index.d.ts +3 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/authoring-a-behaviour.md +3 -40
  19. package/dist/docs/guide/authoring-a-script-object.md +102 -0
  20. package/dist/docs/guide/behaviour-lifecycle.md +4 -4
  21. package/dist/docs/guide/prefabs.md +24 -5
  22. package/dist/docs/guide/spawning/script-object.md +36 -0
  23. package/dist/docs/guide/systems/_sidebar.json +4 -0
  24. package/dist/docs/guide/systems/script-behaviour-system.md +1 -17
  25. package/dist/docs/guide/systems/script-object-system.md +44 -0
  26. package/dist/factories/index.cjs +164 -117
  27. package/dist/factories/index.cjs.map +1 -1
  28. package/dist/factories/index.d.cts +5 -3
  29. package/dist/factories/index.d.ts +5 -3
  30. package/dist/factories/index.js +3 -3
  31. package/dist/{index-LHLKpJhZ.d.cts → index-Cu2Pt7wN.d.cts} +33 -14
  32. package/dist/{index-BBI2YdCu.d.ts → index-DdiWu1b6.d.ts} +33 -14
  33. package/dist/index.cjs +1132 -302
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +219 -32
  36. package/dist/index.d.ts +219 -32
  37. package/dist/index.js +254 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/systems/index.cjs +429 -43
  40. package/dist/systems/index.cjs.map +1 -1
  41. package/dist/systems/index.d.cts +44 -3
  42. package/dist/systems/index.d.ts +44 -3
  43. package/dist/systems/index.js +3 -3
  44. package/dist/types.d.cts +1 -1
  45. package/dist/types.d.ts +1 -1
  46. package/dist/worldSnapshotUtils.d.cts +1 -1
  47. package/dist/worldSnapshotUtils.d.ts +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-67MFQDHX.js.map +0 -1
  50. package/dist/chunk-HUHN6IFD.js.map +0 -1
  51. package/dist/chunk-Z6OMYEXU.js.map +0 -1
@@ -1854,11 +1854,19 @@ __name(_Name, "Name");
1854
1854
  var Name = _Name;
1855
1855
 
1856
1856
  // core/components/PrefabInstance.ts
1857
+ var generateInstanceId = /* @__PURE__ */ __name(() => {
1858
+ try {
1859
+ return crypto.randomUUID();
1860
+ } catch {
1861
+ return `i-${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
1862
+ }
1863
+ }, "generateInstanceId");
1857
1864
  var _PrefabInstance = class _PrefabInstance extends Component {
1858
- constructor(url, overrides = []) {
1865
+ constructor(url, overrides = [], instanceId) {
1859
1866
  super();
1860
1867
  this.url = url;
1861
1868
  this.overrides = overrides;
1869
+ this.instanceId = typeof instanceId === "string" && instanceId.length > 0 ? instanceId : generateInstanceId();
1862
1870
  }
1863
1871
  };
1864
1872
  __name(_PrefabInstance, "PrefabInstance");
@@ -1883,7 +1891,6 @@ var _ScriptComponent = class _ScriptComponent extends Component {
1883
1891
  className: typeof entry.className === "string" ? entry.className : "",
1884
1892
  scriptUrl: typeof entry.scriptUrl === "string" ? entry.scriptUrl : "",
1885
1893
  enabled: typeof entry.enabled === "boolean" ? entry.enabled : true,
1886
- executeInEditor: typeof entry.executeInEditor === "boolean" ? entry.executeInEditor : false,
1887
1894
  data: entry.data && typeof entry.data === "object" && !Array.isArray(entry.data) ? entry.data : {},
1888
1895
  runtimeState: entry.runtimeState,
1889
1896
  runtimeMessage: typeof entry.runtimeMessage === "string" ? entry.runtimeMessage : void 0
@@ -1905,6 +1912,27 @@ var _ScriptComponent = class _ScriptComponent extends Component {
1905
1912
  __name(_ScriptComponent, "ScriptComponent");
1906
1913
  var ScriptComponent = _ScriptComponent;
1907
1914
 
1915
+ // core/components/ScriptObjectComponent.ts
1916
+ var _ScriptObjectComponent = class _ScriptObjectComponent extends Component {
1917
+ constructor(input = {}) {
1918
+ super();
1919
+ this.scriptUrl = typeof input.scriptUrl === "string" && input.scriptUrl.length > 0 ? input.scriptUrl : null;
1920
+ this.className = typeof input.className === "string" && input.className.length > 0 ? input.className : null;
1921
+ this.enabled = typeof input.enabled === "boolean" ? input.enabled : true;
1922
+ this.data = input.data && typeof input.data === "object" && !Array.isArray(input.data) ? input.data : {};
1923
+ this.runtimeState = input.runtimeState === "idle" || input.runtimeState === "loading" || input.runtimeState === "ready" || input.runtimeState === "error" ? input.runtimeState : "idle";
1924
+ this.runtimeMessage = typeof input.runtimeMessage === "string" ? input.runtimeMessage : void 0;
1925
+ Object.defineProperty(this, "instance", {
1926
+ value: null,
1927
+ writable: true,
1928
+ configurable: true,
1929
+ enumerable: false
1930
+ });
1931
+ }
1932
+ };
1933
+ __name(_ScriptObjectComponent, "ScriptObjectComponent");
1934
+ var ScriptObjectComponent = _ScriptObjectComponent;
1935
+
1908
1936
  // core/components/Selectable.ts
1909
1937
  var _Selectable = class _Selectable extends Component {
1910
1938
  constructor(enabled = true) {
@@ -4752,6 +4780,10 @@ function buildComponentValueMaps(snapshot) {
4752
4780
  animationValues: readByComponent(snapshot.components, "AnimationComponent"),
4753
4781
  imageTargetAnchorValues: readByComponent(snapshot.components, "ImageTargetAnchor"),
4754
4782
  scriptValues: readByComponent(snapshot.components, "ScriptComponent"),
4783
+ scriptObjectValues: readByComponent(
4784
+ snapshot.components,
4785
+ "ScriptObjectComponent"
4786
+ ),
4755
4787
  instancedMeshValues: readByComponent(snapshot.components, "InstancedMeshComponent"),
4756
4788
  instanceSlotValues: readByComponent(snapshot.components, "InstanceSlotComponent"),
4757
4789
  prefabNodeValues: readByComponent(snapshot.components, "PrefabNode"),
@@ -4869,6 +4901,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
4869
4901
  new ScriptComponent(entries)
4870
4902
  );
4871
4903
  }
4904
+ const sourceScriptObject = asRecord3(values.scriptObjectValues.get(sourceEntityId));
4905
+ if (sourceScriptObject) {
4906
+ const soc = world.getComponent(targetEntityId, ScriptObjectComponent);
4907
+ const data = asRecord3(sourceScriptObject.data);
4908
+ if (soc && data) {
4909
+ soc.data = remapEntityRefsInScriptData(data, idMap);
4910
+ }
4911
+ }
4872
4912
  {
4873
4913
  const imComponent = world.getComponent(targetEntityId, InstancedMeshComponent);
4874
4914
  if (imComponent && imComponent.instanceEntityIds.length > 0) {
@@ -5421,7 +5461,13 @@ function restoreComponentState(world, objectManagementSystem, targetEntityId, so
5421
5461
  ) : [];
5422
5462
  world.addComponent(
5423
5463
  targetEntityId,
5424
- new PrefabInstance(sourcePrefabInstance.url, overrides)
5464
+ new PrefabInstance(
5465
+ sourcePrefabInstance.url,
5466
+ overrides,
5467
+ // Preserve the placement identity; the constructor generates a
5468
+ // fresh one for legacy snapshots that predate instanceId.
5469
+ typeof sourcePrefabInstance.instanceId === "string" && sourcePrefabInstance.instanceId.length > 0 ? sourcePrefabInstance.instanceId : void 0
5470
+ )
5425
5471
  );
5426
5472
  }
5427
5473
  }
@@ -5449,7 +5495,8 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
5449
5495
  "spotLight",
5450
5496
  "areaLight",
5451
5497
  "instancedMesh",
5452
- "instanceSlot"
5498
+ "instanceSlot",
5499
+ "scriptObject"
5453
5500
  ]);
5454
5501
 
5455
5502
  // core/hydration/hydrateWorld.ts
@@ -5551,6 +5598,16 @@ function buildEntityCreationPayload(sourceEntityId, kind, componentValues) {
5551
5598
  };
5552
5599
  }
5553
5600
  }
5601
+ if (kind === "scriptObject") {
5602
+ const so = asRecord3(componentValues.scriptObjectValues.get(sourceEntityId));
5603
+ if (so) {
5604
+ payload.options = {
5605
+ scriptUrl: typeof so.scriptUrl === "string" ? so.scriptUrl : null,
5606
+ className: typeof so.className === "string" ? so.className : null,
5607
+ enabled: typeof so.enabled === "boolean" ? so.enabled : true
5608
+ };
5609
+ }
5610
+ }
5554
5611
  return payload;
5555
5612
  }
5556
5613
  __name(buildEntityCreationPayload, "buildEntityCreationPayload");
@@ -5680,15 +5737,20 @@ __name(findSnapshotRootEntityIds, "findSnapshotRootEntityIds");
5680
5737
  var INSTANTIATE_EXCLUDED_COMPONENTS = /* @__PURE__ */ new Set([
5681
5738
  "Environment",
5682
5739
  "Postprocessing",
5683
- "SelectionTransformState",
5684
- "PrefabInstance"
5740
+ "SelectionTransformState"
5685
5741
  ]);
5686
5742
  function instantiateSubtree(world, objectManagementSystem, snapshot) {
5743
+ const snapshotRootIds = new Set(findSnapshotRootEntityIds(snapshot));
5687
5744
  const safeSnapshot = {
5688
5745
  entities: snapshot.entities,
5689
- components: snapshot.components.filter(
5690
- (block) => !INSTANTIATE_EXCLUDED_COMPONENTS.has(block.component)
5691
- )
5746
+ components: snapshot.components.filter((block) => !INSTANTIATE_EXCLUDED_COMPONENTS.has(block.component)).map(
5747
+ (block) => block.component === "PrefabInstance" ? {
5748
+ component: block.component,
5749
+ entries: block.entries.filter(
5750
+ (entry) => !snapshotRootIds.has(entry.entityId)
5751
+ )
5752
+ } : block
5753
+ ).filter((block) => block.entries.length > 0)
5692
5754
  };
5693
5755
  const result = hydrateWorldFromSnapshotDetailed(
5694
5756
  safeSnapshot,
@@ -6105,22 +6167,14 @@ var _Behaviour = class _Behaviour {
6105
6167
  /**
6106
6168
  * Called once before the first {@link Behaviour.onUpdate}. Override to
6107
6169
  * default and validate {@link Behaviour.data}, cache references, or set
6108
- * initial state.
6109
- *
6110
- * @remarks
6111
- * Also called in editor mode when `executeInEditor` is `true` on the script
6112
- * entry.
6170
+ * initial state. Runs at preview/runtime only — never in the editor.
6113
6171
  */
6114
6172
  init() {
6115
6173
  }
6116
6174
  /**
6117
6175
  * Called every frame while the script is enabled. Override with the
6118
- * script's main per-frame logic.
6119
- *
6120
- * @remarks
6121
- * Also called every frame in editor mode when `executeInEditor` is `true`.
6122
- * Access the Three.js scene with `this.world.getScene()` for editor-only
6123
- * rendering work such as custom shaders or procedural geometry.
6176
+ * script's main per-frame logic. Runs at preview/runtime only — never in the
6177
+ * editor (use {@link ScriptObject} for editor-visible visuals).
6124
6178
  */
6125
6179
  onUpdate() {
6126
6180
  }
@@ -6159,7 +6213,14 @@ __name(_Behaviour, "Behaviour");
6159
6213
  _Behaviour.__reluBehaviourClass = true;
6160
6214
  var Behaviour = _Behaviour;
6161
6215
 
6162
- // core/systems/ScriptBehaviourSystem.ts
6216
+ // core/systems/scriptShared.ts
6217
+ async function defaultModuleLoader(scriptUrl) {
6218
+ return await import(
6219
+ /* webpackIgnore: true */
6220
+ scriptUrl
6221
+ );
6222
+ }
6223
+ __name(defaultModuleLoader, "defaultModuleLoader");
6163
6224
  function toErrorMessage(error) {
6164
6225
  const sanitize = /* @__PURE__ */ __name((value) => value.replace(/localasset:\/\/[^/]+\//g, "localasset://").replace(/\/Users\/[^\s)]+/g, "[local path]"), "sanitize");
6165
6226
  if (error instanceof Error && error.message) {
@@ -6171,26 +6232,22 @@ function toErrorMessage(error) {
6171
6232
  return "Unknown script error.";
6172
6233
  }
6173
6234
  __name(toErrorMessage, "toErrorMessage");
6174
- function buildInstanceKey(entityId, scriptUrl, className) {
6175
- return `${entityId}:${scriptUrl}:${className}`;
6176
- }
6177
- __name(buildInstanceKey, "buildInstanceKey");
6178
- function resolveBehaviourCtor(moduleValue, className) {
6235
+ function resolveScriptCtor(moduleValue, className, options) {
6179
6236
  const named = moduleValue[className];
6180
6237
  const fallback = moduleValue.default;
6181
6238
  const candidate = typeof named === "function" ? named : typeof fallback === "function" ? fallback : null;
6182
6239
  if (!candidate) {
6183
6240
  return null;
6184
6241
  }
6185
- if (!(candidate.prototype instanceof Behaviour)) {
6186
- const isBehaviourSubclass = !!candidate.__reluBehaviourClass;
6187
- if (!isBehaviourSubclass) {
6242
+ if (!(candidate.prototype instanceof options.baseClass)) {
6243
+ const marker = candidate[options.markerKey];
6244
+ if (!marker) {
6188
6245
  return null;
6189
6246
  }
6190
6247
  }
6191
6248
  return candidate;
6192
6249
  }
6193
- __name(resolveBehaviourCtor, "resolveBehaviourCtor");
6250
+ __name(resolveScriptCtor, "resolveScriptCtor");
6194
6251
  function normalizeScriptData(value) {
6195
6252
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6196
6253
  }
@@ -6272,8 +6329,8 @@ function resolveInputData(world, rawData) {
6272
6329
  return resolved;
6273
6330
  }
6274
6331
  __name(resolveInputData, "resolveInputData");
6275
- function serializeDataForStorage(rawData, behaviourData) {
6276
- const result = { ...behaviourData };
6332
+ function serializeDataForStorage(rawData, instanceData) {
6333
+ const result = { ...instanceData };
6277
6334
  for (const [key, rawValue] of Object.entries(rawData)) {
6278
6335
  if (isRawEntityInput(rawValue) || isRawComponentInput(rawValue)) {
6279
6336
  result[key] = rawValue;
@@ -6282,13 +6339,25 @@ function serializeDataForStorage(rawData, behaviourData) {
6282
6339
  return result;
6283
6340
  }
6284
6341
  __name(serializeDataForStorage, "serializeDataForStorage");
6342
+
6343
+ // core/systems/ScriptBehaviourSystem.ts
6344
+ function buildInstanceKey(entityId, scriptUrl, className) {
6345
+ return `${entityId}:${scriptUrl}:${className}`;
6346
+ }
6347
+ __name(buildInstanceKey, "buildInstanceKey");
6348
+ function resolveBehaviourCtor(moduleValue, className) {
6349
+ return resolveScriptCtor(moduleValue, className, {
6350
+ baseClass: Behaviour,
6351
+ markerKey: "__reluBehaviourClass"
6352
+ });
6353
+ }
6354
+ __name(resolveBehaviourCtor, "resolveBehaviourCtor");
6285
6355
  function normalizeEntry(value, fallbackEntityId) {
6286
6356
  return {
6287
6357
  entityId: typeof value?.entityId === "number" ? value.entityId : fallbackEntityId,
6288
6358
  className: typeof value?.className === "string" ? value.className : "",
6289
6359
  scriptUrl: typeof value?.scriptUrl === "string" ? value.scriptUrl : "",
6290
6360
  enabled: typeof value?.enabled === "boolean" ? value.enabled : true,
6291
- executeInEditor: typeof value?.executeInEditor === "boolean" ? value.executeInEditor : false,
6292
6361
  data: normalizeScriptData(value?.data),
6293
6362
  runtimeState: value?.runtimeState === "idle" || value?.runtimeState === "loading" || value?.runtimeState === "ready" || value?.runtimeState === "error" ? value.runtimeState : void 0,
6294
6363
  runtimeMessage: typeof value?.runtimeMessage === "string" ? value.runtimeMessage : void 0
@@ -6311,13 +6380,6 @@ function writeScriptEntries(component, entries) {
6311
6380
  }
6312
6381
  }
6313
6382
  __name(writeScriptEntries, "writeScriptEntries");
6314
- async function defaultModuleLoader(scriptUrl) {
6315
- return await import(
6316
- /* webpackIgnore: true */
6317
- scriptUrl
6318
- );
6319
- }
6320
- __name(defaultModuleLoader, "defaultModuleLoader");
6321
6383
  var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6322
6384
  constructor(moduleLoader, selectionContext) {
6323
6385
  this.instances = /* @__PURE__ */ new Map();
@@ -6433,11 +6495,11 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6433
6495
  let hasEnabledScript = false;
6434
6496
  for (let entryIndex = 0; entryIndex < nextEntries.length; entryIndex += 1) {
6435
6497
  const entry = nextEntries[entryIndex];
6436
- const skipInEditor = world.isEditorMode() && !entry.executeInEditor;
6498
+ const skipInEditor = world.isEditorMode();
6437
6499
  if (!entry.enabled || !entry.scriptUrl || skipInEditor) {
6438
6500
  nextEntries[entryIndex] = {
6439
6501
  ...entry,
6440
- runtimeState: skipInEditor ? "idle" : "idle",
6502
+ runtimeState: "idle",
6441
6503
  runtimeMessage: void 0
6442
6504
  };
6443
6505
  continue;
@@ -6624,7 +6686,7 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6624
6686
  const mergedData = mergeDataByCurrentDefaults(defaults, currentData);
6625
6687
  const resolvedData = resolveInputData(world, mergedData);
6626
6688
  behaviour.data = resolvedData;
6627
- if (!world.isEditorMode() || liveEntry.executeInEditor === true) {
6689
+ if (!world.isEditorMode()) {
6628
6690
  behaviour.init();
6629
6691
  }
6630
6692
  liveEntries[entryIndex] = {
@@ -6652,6 +6714,328 @@ __name(_ScriptBehaviourSystem, "ScriptBehaviourSystem");
6652
6714
  _ScriptBehaviourSystem.CLICK_THRESHOLD = 6;
6653
6715
  var ScriptBehaviourSystem = _ScriptBehaviourSystem;
6654
6716
 
6717
+ // core/ScriptObject.ts
6718
+ var _ScriptObject = class _ScriptObject {
6719
+ /**
6720
+ * Engine-managed constructor. `ScriptObjectSystem` instantiates subclasses
6721
+ * with the owning world, the entity id, and the component's `data`. User code
6722
+ * should not call this directly.
6723
+ */
6724
+ constructor(world, entityId, data) {
6725
+ /**
6726
+ * The script's data payload (the `ScriptObjectComponent.data`). Declare a
6727
+ * concrete shape on subclasses with `declare data: MyShape` for type safety.
6728
+ * Use the same typed input envelopes as Behaviours (`ColorInput`,
6729
+ * `SliderInput`, `EntityInput`, …) to get inspector fields.
6730
+ */
6731
+ this.data = {};
6732
+ /**
6733
+ * The Object3D returned by {@link ScriptObject.build}. Assigned by
6734
+ * `ScriptObjectSystem` after `build()` runs; `null` until then.
6735
+ */
6736
+ this.root = null;
6737
+ this.world = world;
6738
+ this.entityId = entityId;
6739
+ if (data) this.data = data;
6740
+ }
6741
+ /**
6742
+ * REQUIRED — build and return the visual root. Called once by
6743
+ * `ScriptObjectSystem` after construction. The returned object is attached
6744
+ * under the entity's Object3D (so it inherits the entity transform, selection
6745
+ * and visibility). Subclasses MUST override.
6746
+ */
6747
+ build() {
6748
+ throw new Error("ScriptObject subclasses must implement build()");
6749
+ }
6750
+ };
6751
+ __name(_ScriptObject, "ScriptObject");
6752
+ /**
6753
+ * Static marker used by `ScriptObjectSystem` to detect `ScriptObject`
6754
+ * subclasses even when a bundler ships a second copy of the package into the
6755
+ * script blob (breaking `instanceof`). Do NOT override.
6756
+ */
6757
+ _ScriptObject.__reluScriptObjectClass = true;
6758
+ var ScriptObject = _ScriptObject;
6759
+
6760
+ // core/systems/ScriptObjectSystem.ts
6761
+ var SCRIPT_OBJECT_ROOT_FLAG = "__scriptObjectRoot";
6762
+ function setInstanceRef(component, value) {
6763
+ Object.defineProperty(component, "instance", {
6764
+ value,
6765
+ writable: true,
6766
+ configurable: true,
6767
+ enumerable: false
6768
+ });
6769
+ }
6770
+ __name(setInstanceRef, "setInstanceRef");
6771
+ function disposeBuiltObject(root) {
6772
+ root.parent?.remove(root);
6773
+ root.traverse((node) => {
6774
+ const mesh = node;
6775
+ const geometry = mesh.geometry;
6776
+ if (geometry?.dispose && !geometry.userData?.[SHARED_MODEL_RESOURCE_FLAG]) {
6777
+ geometry.dispose();
6778
+ }
6779
+ const material = mesh.material;
6780
+ const materials = Array.isArray(material) ? material : material ? [material] : [];
6781
+ for (const mat of materials) {
6782
+ if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
6783
+ for (const value of Object.values(mat)) {
6784
+ if (value instanceof THREE14__namespace.Texture) value.dispose();
6785
+ }
6786
+ mat.dispose?.();
6787
+ }
6788
+ });
6789
+ }
6790
+ __name(disposeBuiltObject, "disposeBuiltObject");
6791
+ var _ScriptObjectSystem = class _ScriptObjectSystem {
6792
+ constructor(moduleLoader) {
6793
+ this.instances = /* @__PURE__ */ new Map();
6794
+ this.loading = /* @__PURE__ */ new Map();
6795
+ /** Bumped by invalidate*() so in-flight loads that predate it are discarded. */
6796
+ this.epoch = 0;
6797
+ this.moduleLoader = moduleLoader ?? defaultModuleLoader;
6798
+ }
6799
+ update(world, deltaTime) {
6800
+ const active = /* @__PURE__ */ new Map();
6801
+ for (const entityId of world.query(ScriptObjectComponent)) {
6802
+ const component = world.getComponent(entityId, ScriptObjectComponent);
6803
+ if (!component) continue;
6804
+ const wantsRun = component.enabled && typeof component.scriptUrl === "string" && typeof component.className === "string";
6805
+ if (!wantsRun) {
6806
+ const record = this.instances.get(entityId);
6807
+ if (record) this.teardownRecord(world, entityId, record, false);
6808
+ this.loading.delete(entityId);
6809
+ if (component.runtimeState !== "idle") {
6810
+ component.runtimeState = "idle";
6811
+ component.runtimeMessage = void 0;
6812
+ }
6813
+ continue;
6814
+ }
6815
+ active.set(entityId, component);
6816
+ }
6817
+ for (const [entityId, record] of this.instances) {
6818
+ const component = active.get(entityId);
6819
+ const identityChanged = !component || record.scriptUrl !== component.scriptUrl || record.className !== component.className;
6820
+ if (!world.hasEntity(entityId) || identityChanged) {
6821
+ this.teardownRecord(world, entityId, record, false);
6822
+ }
6823
+ }
6824
+ for (const [entityId, component] of active) {
6825
+ const record = this.instances.get(entityId);
6826
+ if (!record || record.status !== "ready" || !record.instance) continue;
6827
+ if (!world.isEntityVisible(entityId)) continue;
6828
+ if (component.data !== record.lastWrittenData) {
6829
+ try {
6830
+ const merged = mergeDataByCurrentDefaults(
6831
+ record.rawData,
6832
+ normalizeScriptData(component.data)
6833
+ );
6834
+ record.rawData = merged;
6835
+ record.instance.data = resolveInputData(world, merged);
6836
+ const stored = serializeDataForStorage(merged, merged);
6837
+ component.data = stored;
6838
+ record.lastWrittenData = stored;
6839
+ } catch (error) {
6840
+ this.markError(world, entityId, component, error);
6841
+ continue;
6842
+ }
6843
+ }
6844
+ try {
6845
+ record.instance.update?.(deltaTime);
6846
+ } catch (error) {
6847
+ this.markError(world, entityId, component, error);
6848
+ }
6849
+ }
6850
+ for (const [entityId, component] of active) {
6851
+ if (this.instances.has(entityId) || this.loading.has(entityId)) continue;
6852
+ const scriptUrl = component.scriptUrl;
6853
+ const className = component.className;
6854
+ if (component.runtimeState !== "loading") {
6855
+ component.runtimeState = "loading";
6856
+ component.runtimeMessage = void 0;
6857
+ }
6858
+ const epochAtStart = this.epoch;
6859
+ const key = `${epochAtStart}:${scriptUrl}:${className}`;
6860
+ const promise = this.loadScriptObject(
6861
+ world,
6862
+ entityId,
6863
+ scriptUrl,
6864
+ className,
6865
+ key,
6866
+ epochAtStart
6867
+ ).catch((error) => {
6868
+ this.markError(world, entityId, void 0, error);
6869
+ console.error(
6870
+ `[ScriptObjectSystem] Failed loading ${className} (${scriptUrl})`,
6871
+ error
6872
+ );
6873
+ }).finally(() => {
6874
+ if (this.loading.get(entityId)?.key === key) {
6875
+ this.loading.delete(entityId);
6876
+ }
6877
+ });
6878
+ this.loading.set(entityId, { key, promise });
6879
+ }
6880
+ for (const entityId of this.loading.keys()) {
6881
+ if (!active.has(entityId)) this.loading.delete(entityId);
6882
+ }
6883
+ }
6884
+ async loadScriptObject(world, entityId, scriptUrl, className, key, epochAtStart) {
6885
+ const moduleValue = await this.moduleLoader(scriptUrl);
6886
+ if (this.loading.get(entityId)?.key !== key || epochAtStart !== this.epoch || !world.hasEntity(entityId)) {
6887
+ return;
6888
+ }
6889
+ const component = world.getComponent(entityId, ScriptObjectComponent);
6890
+ if (!component || !component.enabled || component.scriptUrl !== scriptUrl || component.className !== className) {
6891
+ return;
6892
+ }
6893
+ const ctor = resolveScriptCtor(moduleValue, className, {
6894
+ baseClass: ScriptObject,
6895
+ markerKey: "__reluScriptObjectClass"
6896
+ });
6897
+ if (!ctor) {
6898
+ throw new Error(
6899
+ `Missing ScriptObject export '${className}' in ${scriptUrl}`
6900
+ );
6901
+ }
6902
+ const currentData = normalizeScriptData(component.data);
6903
+ let instance;
6904
+ try {
6905
+ instance = new ctor(world, entityId, void 0);
6906
+ } catch {
6907
+ instance = new ctor(world, entityId, currentData);
6908
+ }
6909
+ const defaults = normalizeScriptData(instance.data);
6910
+ const merged = mergeDataByCurrentDefaults(defaults, currentData);
6911
+ instance.data = resolveInputData(world, merged);
6912
+ const parent = world.getComponent(entityId, Object3DRef)?.object;
6913
+ if (!parent) {
6914
+ throw new Error("Script object entity has no Object3D to attach to.");
6915
+ }
6916
+ for (const child of [...parent.children]) {
6917
+ if (child.userData?.[SCRIPT_OBJECT_ROOT_FLAG]) {
6918
+ disposeBuiltObject(child);
6919
+ }
6920
+ }
6921
+ let built;
6922
+ try {
6923
+ built = instance.build();
6924
+ } catch (error) {
6925
+ try {
6926
+ instance.dispose?.();
6927
+ } catch {
6928
+ }
6929
+ throw error;
6930
+ }
6931
+ if (!built || built.isObject3D !== true) {
6932
+ try {
6933
+ instance.dispose?.();
6934
+ } catch {
6935
+ }
6936
+ throw new Error("build() must return a THREE.Object3D.");
6937
+ }
6938
+ built.userData[SCRIPT_OBJECT_ROOT_FLAG] = true;
6939
+ parent.add(built);
6940
+ instance.root = built;
6941
+ const stored = serializeDataForStorage(merged, merged);
6942
+ component.data = stored;
6943
+ component.runtimeState = "ready";
6944
+ component.runtimeMessage = void 0;
6945
+ setInstanceRef(component, instance);
6946
+ this.instances.set(entityId, {
6947
+ entityId,
6948
+ scriptUrl,
6949
+ className,
6950
+ status: "ready",
6951
+ instance,
6952
+ root: built,
6953
+ rawData: merged,
6954
+ lastWrittenData: stored
6955
+ });
6956
+ }
6957
+ /** Dispose a record's instance + built subtree. `keepTombstone` leaves an error marker. */
6958
+ teardownRecord(world, entityId, record, keepTombstone) {
6959
+ try {
6960
+ record.instance?.dispose?.();
6961
+ } catch {
6962
+ }
6963
+ if (record.root) disposeBuiltObject(record.root);
6964
+ const component = world.getComponent(entityId, ScriptObjectComponent);
6965
+ if (component) setInstanceRef(component, null);
6966
+ if (keepTombstone) {
6967
+ this.instances.set(entityId, {
6968
+ ...record,
6969
+ status: "error",
6970
+ instance: null,
6971
+ root: null
6972
+ });
6973
+ } else {
6974
+ this.instances.delete(entityId);
6975
+ }
6976
+ }
6977
+ markError(world, entityId, component, error) {
6978
+ const comp = component ?? world.getComponent(entityId, ScriptObjectComponent) ?? null;
6979
+ if (comp) {
6980
+ comp.runtimeState = "error";
6981
+ comp.runtimeMessage = toErrorMessage(error);
6982
+ }
6983
+ const record = this.instances.get(entityId);
6984
+ if (record) {
6985
+ this.teardownRecord(world, entityId, record, true);
6986
+ } else {
6987
+ this.instances.set(entityId, {
6988
+ entityId,
6989
+ scriptUrl: comp?.scriptUrl ?? "",
6990
+ className: comp?.className ?? "",
6991
+ status: "error",
6992
+ instance: null,
6993
+ root: null,
6994
+ rawData: {},
6995
+ lastWrittenData: null
6996
+ });
6997
+ }
6998
+ }
6999
+ /** Dispose all live instances so the next update() reloads with fresh modules. */
7000
+ invalidateAll() {
7001
+ this.epoch += 1;
7002
+ for (const [entityId, record] of [...this.instances]) {
7003
+ try {
7004
+ record.instance?.dispose?.();
7005
+ } catch {
7006
+ }
7007
+ if (record.root) disposeBuiltObject(record.root);
7008
+ this.instances.delete(entityId);
7009
+ }
7010
+ this.loading.clear();
7011
+ }
7012
+ /** Invalidate only the entities whose script matches `scriptUrl`. */
7013
+ invalidate(scriptUrl) {
7014
+ this.epoch += 1;
7015
+ for (const [entityId, record] of [...this.instances]) {
7016
+ if (record.scriptUrl !== scriptUrl) continue;
7017
+ try {
7018
+ record.instance?.dispose?.();
7019
+ } catch {
7020
+ }
7021
+ if (record.root) disposeBuiltObject(record.root);
7022
+ this.instances.delete(entityId);
7023
+ }
7024
+ for (const [entityId, entry] of [...this.loading]) {
7025
+ if (entry.key.includes(`:${scriptUrl}:`)) this.loading.delete(entityId);
7026
+ }
7027
+ }
7028
+ dispose(world) {
7029
+ for (const [entityId, record] of [...this.instances]) {
7030
+ this.teardownRecord(world, entityId, record, false);
7031
+ }
7032
+ this.instances.clear();
7033
+ this.loading.clear();
7034
+ }
7035
+ };
7036
+ __name(_ScriptObjectSystem, "ScriptObjectSystem");
7037
+ var ScriptObjectSystem = _ScriptObjectSystem;
7038
+
6655
7039
  // core/systems/TransformSyncSystem.ts
6656
7040
  var _TransformSyncSystem = class _TransformSyncSystem {
6657
7041
  update(world) {
@@ -6928,7 +7312,9 @@ exports.ObjectVisibilitySystem = ObjectVisibilitySystem;
6928
7312
  exports.PhysicsSyncSystem = PhysicsSyncSystem;
6929
7313
  exports.PostprocessingSyncSystem = PostprocessingSyncSystem;
6930
7314
  exports.RenderSystem = RenderSystem;
7315
+ exports.SCRIPT_OBJECT_ROOT_FLAG = SCRIPT_OBJECT_ROOT_FLAG;
6931
7316
  exports.ScriptBehaviourSystem = ScriptBehaviourSystem;
7317
+ exports.ScriptObjectSystem = ScriptObjectSystem;
6932
7318
  exports.TransformSyncSystem = TransformSyncSystem;
6933
7319
  exports.VideoSystem = VideoSystem;
6934
7320
  //# sourceMappingURL=index.cjs.map