@relu-interactives/spatial-ecs 0.2.21 → 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 (50) hide show
  1. package/dist/{System-me3iAcMF.d.cts → System-CYWZ_Zx5.d.cts} +4 -3
  2. package/dist/{System-me3iAcMF.d.ts → System-CYWZ_Zx5.d.ts} +4 -3
  3. package/dist/{Video-DHnxjGhW.d.ts → Video-C1IOat7L.d.ts} +38 -9
  4. package/dist/{Video-U9hAnf9y.d.cts → Video-ppAXaKt0.d.cts} +38 -9
  5. package/dist/{chunk-K6KMKKJ6.js → chunk-52CHDPRQ.js} +118 -92
  6. package/dist/chunk-52CHDPRQ.js.map +1 -0
  7. package/dist/{chunk-5U3YS4JD.js → chunk-GZ2NKSMV.js} +421 -65
  8. package/dist/chunk-GZ2NKSMV.js.map +1 -0
  9. package/dist/{chunk-V7BNVTJW.js → chunk-JHCPRVBC.js} +25 -4
  10. package/dist/chunk-JHCPRVBC.js.map +1 -0
  11. package/dist/{chunk-AXRCGF63.js → chunk-WKGCC7YL.js} +3 -3
  12. package/dist/{chunk-AXRCGF63.js.map → chunk-WKGCC7YL.js.map} +1 -1
  13. package/dist/components/index.cjs +22 -1
  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/spawning/script-object.md +36 -0
  22. package/dist/docs/guide/systems/_sidebar.json +4 -0
  23. package/dist/docs/guide/systems/script-behaviour-system.md +1 -17
  24. package/dist/docs/guide/systems/script-object-system.md +44 -0
  25. package/dist/factories/index.cjs +164 -117
  26. package/dist/factories/index.cjs.map +1 -1
  27. package/dist/factories/index.d.cts +5 -3
  28. package/dist/factories/index.d.ts +5 -3
  29. package/dist/factories/index.js +3 -3
  30. package/dist/{index-D7FSg0Au.d.ts → index-Cu2Pt7wN.d.cts} +6 -3
  31. package/dist/{index-CZo5I5Gs.d.cts → index-DdiWu1b6.d.ts} +6 -3
  32. package/dist/index.cjs +677 -265
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +117 -28
  35. package/dist/index.d.ts +117 -28
  36. package/dist/index.js +13 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/systems/index.cjs +403 -36
  39. package/dist/systems/index.cjs.map +1 -1
  40. package/dist/systems/index.d.cts +44 -3
  41. package/dist/systems/index.d.ts +44 -3
  42. package/dist/systems/index.js +3 -3
  43. package/dist/types.d.cts +1 -1
  44. package/dist/types.d.ts +1 -1
  45. package/dist/worldSnapshotUtils.d.cts +1 -1
  46. package/dist/worldSnapshotUtils.d.ts +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunk-5U3YS4JD.js.map +0 -1
  49. package/dist/chunk-K6KMKKJ6.js.map +0 -1
  50. package/dist/chunk-V7BNVTJW.js.map +0 -1
@@ -1891,7 +1891,6 @@ var _ScriptComponent = class _ScriptComponent extends Component {
1891
1891
  className: typeof entry.className === "string" ? entry.className : "",
1892
1892
  scriptUrl: typeof entry.scriptUrl === "string" ? entry.scriptUrl : "",
1893
1893
  enabled: typeof entry.enabled === "boolean" ? entry.enabled : true,
1894
- executeInEditor: typeof entry.executeInEditor === "boolean" ? entry.executeInEditor : false,
1895
1894
  data: entry.data && typeof entry.data === "object" && !Array.isArray(entry.data) ? entry.data : {},
1896
1895
  runtimeState: entry.runtimeState,
1897
1896
  runtimeMessage: typeof entry.runtimeMessage === "string" ? entry.runtimeMessage : void 0
@@ -1913,6 +1912,27 @@ var _ScriptComponent = class _ScriptComponent extends Component {
1913
1912
  __name(_ScriptComponent, "ScriptComponent");
1914
1913
  var ScriptComponent = _ScriptComponent;
1915
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
+
1916
1936
  // core/components/Selectable.ts
1917
1937
  var _Selectable = class _Selectable extends Component {
1918
1938
  constructor(enabled = true) {
@@ -4760,6 +4780,10 @@ function buildComponentValueMaps(snapshot) {
4760
4780
  animationValues: readByComponent(snapshot.components, "AnimationComponent"),
4761
4781
  imageTargetAnchorValues: readByComponent(snapshot.components, "ImageTargetAnchor"),
4762
4782
  scriptValues: readByComponent(snapshot.components, "ScriptComponent"),
4783
+ scriptObjectValues: readByComponent(
4784
+ snapshot.components,
4785
+ "ScriptObjectComponent"
4786
+ ),
4763
4787
  instancedMeshValues: readByComponent(snapshot.components, "InstancedMeshComponent"),
4764
4788
  instanceSlotValues: readByComponent(snapshot.components, "InstanceSlotComponent"),
4765
4789
  prefabNodeValues: readByComponent(snapshot.components, "PrefabNode"),
@@ -4877,6 +4901,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
4877
4901
  new ScriptComponent(entries)
4878
4902
  );
4879
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
+ }
4880
4912
  {
4881
4913
  const imComponent = world.getComponent(targetEntityId, InstancedMeshComponent);
4882
4914
  if (imComponent && imComponent.instanceEntityIds.length > 0) {
@@ -5463,7 +5495,8 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
5463
5495
  "spotLight",
5464
5496
  "areaLight",
5465
5497
  "instancedMesh",
5466
- "instanceSlot"
5498
+ "instanceSlot",
5499
+ "scriptObject"
5467
5500
  ]);
5468
5501
 
5469
5502
  // core/hydration/hydrateWorld.ts
@@ -5565,6 +5598,16 @@ function buildEntityCreationPayload(sourceEntityId, kind, componentValues) {
5565
5598
  };
5566
5599
  }
5567
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
+ }
5568
5611
  return payload;
5569
5612
  }
5570
5613
  __name(buildEntityCreationPayload, "buildEntityCreationPayload");
@@ -6124,22 +6167,14 @@ var _Behaviour = class _Behaviour {
6124
6167
  /**
6125
6168
  * Called once before the first {@link Behaviour.onUpdate}. Override to
6126
6169
  * default and validate {@link Behaviour.data}, cache references, or set
6127
- * initial state.
6128
- *
6129
- * @remarks
6130
- * Also called in editor mode when `executeInEditor` is `true` on the script
6131
- * entry.
6170
+ * initial state. Runs at preview/runtime only — never in the editor.
6132
6171
  */
6133
6172
  init() {
6134
6173
  }
6135
6174
  /**
6136
6175
  * Called every frame while the script is enabled. Override with the
6137
- * script's main per-frame logic.
6138
- *
6139
- * @remarks
6140
- * Also called every frame in editor mode when `executeInEditor` is `true`.
6141
- * Access the Three.js scene with `this.world.getScene()` for editor-only
6142
- * 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).
6143
6178
  */
6144
6179
  onUpdate() {
6145
6180
  }
@@ -6178,7 +6213,14 @@ __name(_Behaviour, "Behaviour");
6178
6213
  _Behaviour.__reluBehaviourClass = true;
6179
6214
  var Behaviour = _Behaviour;
6180
6215
 
6181
- // 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");
6182
6224
  function toErrorMessage(error) {
6183
6225
  const sanitize = /* @__PURE__ */ __name((value) => value.replace(/localasset:\/\/[^/]+\//g, "localasset://").replace(/\/Users\/[^\s)]+/g, "[local path]"), "sanitize");
6184
6226
  if (error instanceof Error && error.message) {
@@ -6190,26 +6232,22 @@ function toErrorMessage(error) {
6190
6232
  return "Unknown script error.";
6191
6233
  }
6192
6234
  __name(toErrorMessage, "toErrorMessage");
6193
- function buildInstanceKey(entityId, scriptUrl, className) {
6194
- return `${entityId}:${scriptUrl}:${className}`;
6195
- }
6196
- __name(buildInstanceKey, "buildInstanceKey");
6197
- function resolveBehaviourCtor(moduleValue, className) {
6235
+ function resolveScriptCtor(moduleValue, className, options) {
6198
6236
  const named = moduleValue[className];
6199
6237
  const fallback = moduleValue.default;
6200
6238
  const candidate = typeof named === "function" ? named : typeof fallback === "function" ? fallback : null;
6201
6239
  if (!candidate) {
6202
6240
  return null;
6203
6241
  }
6204
- if (!(candidate.prototype instanceof Behaviour)) {
6205
- const isBehaviourSubclass = !!candidate.__reluBehaviourClass;
6206
- if (!isBehaviourSubclass) {
6242
+ if (!(candidate.prototype instanceof options.baseClass)) {
6243
+ const marker = candidate[options.markerKey];
6244
+ if (!marker) {
6207
6245
  return null;
6208
6246
  }
6209
6247
  }
6210
6248
  return candidate;
6211
6249
  }
6212
- __name(resolveBehaviourCtor, "resolveBehaviourCtor");
6250
+ __name(resolveScriptCtor, "resolveScriptCtor");
6213
6251
  function normalizeScriptData(value) {
6214
6252
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6215
6253
  }
@@ -6291,8 +6329,8 @@ function resolveInputData(world, rawData) {
6291
6329
  return resolved;
6292
6330
  }
6293
6331
  __name(resolveInputData, "resolveInputData");
6294
- function serializeDataForStorage(rawData, behaviourData) {
6295
- const result = { ...behaviourData };
6332
+ function serializeDataForStorage(rawData, instanceData) {
6333
+ const result = { ...instanceData };
6296
6334
  for (const [key, rawValue] of Object.entries(rawData)) {
6297
6335
  if (isRawEntityInput(rawValue) || isRawComponentInput(rawValue)) {
6298
6336
  result[key] = rawValue;
@@ -6301,13 +6339,25 @@ function serializeDataForStorage(rawData, behaviourData) {
6301
6339
  return result;
6302
6340
  }
6303
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");
6304
6355
  function normalizeEntry(value, fallbackEntityId) {
6305
6356
  return {
6306
6357
  entityId: typeof value?.entityId === "number" ? value.entityId : fallbackEntityId,
6307
6358
  className: typeof value?.className === "string" ? value.className : "",
6308
6359
  scriptUrl: typeof value?.scriptUrl === "string" ? value.scriptUrl : "",
6309
6360
  enabled: typeof value?.enabled === "boolean" ? value.enabled : true,
6310
- executeInEditor: typeof value?.executeInEditor === "boolean" ? value.executeInEditor : false,
6311
6361
  data: normalizeScriptData(value?.data),
6312
6362
  runtimeState: value?.runtimeState === "idle" || value?.runtimeState === "loading" || value?.runtimeState === "ready" || value?.runtimeState === "error" ? value.runtimeState : void 0,
6313
6363
  runtimeMessage: typeof value?.runtimeMessage === "string" ? value.runtimeMessage : void 0
@@ -6330,13 +6380,6 @@ function writeScriptEntries(component, entries) {
6330
6380
  }
6331
6381
  }
6332
6382
  __name(writeScriptEntries, "writeScriptEntries");
6333
- async function defaultModuleLoader(scriptUrl) {
6334
- return await import(
6335
- /* webpackIgnore: true */
6336
- scriptUrl
6337
- );
6338
- }
6339
- __name(defaultModuleLoader, "defaultModuleLoader");
6340
6383
  var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6341
6384
  constructor(moduleLoader, selectionContext) {
6342
6385
  this.instances = /* @__PURE__ */ new Map();
@@ -6452,11 +6495,11 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6452
6495
  let hasEnabledScript = false;
6453
6496
  for (let entryIndex = 0; entryIndex < nextEntries.length; entryIndex += 1) {
6454
6497
  const entry = nextEntries[entryIndex];
6455
- const skipInEditor = world.isEditorMode() && !entry.executeInEditor;
6498
+ const skipInEditor = world.isEditorMode();
6456
6499
  if (!entry.enabled || !entry.scriptUrl || skipInEditor) {
6457
6500
  nextEntries[entryIndex] = {
6458
6501
  ...entry,
6459
- runtimeState: skipInEditor ? "idle" : "idle",
6502
+ runtimeState: "idle",
6460
6503
  runtimeMessage: void 0
6461
6504
  };
6462
6505
  continue;
@@ -6643,7 +6686,7 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
6643
6686
  const mergedData = mergeDataByCurrentDefaults(defaults, currentData);
6644
6687
  const resolvedData = resolveInputData(world, mergedData);
6645
6688
  behaviour.data = resolvedData;
6646
- if (!world.isEditorMode() || liveEntry.executeInEditor === true) {
6689
+ if (!world.isEditorMode()) {
6647
6690
  behaviour.init();
6648
6691
  }
6649
6692
  liveEntries[entryIndex] = {
@@ -6671,6 +6714,328 @@ __name(_ScriptBehaviourSystem, "ScriptBehaviourSystem");
6671
6714
  _ScriptBehaviourSystem.CLICK_THRESHOLD = 6;
6672
6715
  var ScriptBehaviourSystem = _ScriptBehaviourSystem;
6673
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
+
6674
7039
  // core/systems/TransformSyncSystem.ts
6675
7040
  var _TransformSyncSystem = class _TransformSyncSystem {
6676
7041
  update(world) {
@@ -6947,7 +7312,9 @@ exports.ObjectVisibilitySystem = ObjectVisibilitySystem;
6947
7312
  exports.PhysicsSyncSystem = PhysicsSyncSystem;
6948
7313
  exports.PostprocessingSyncSystem = PostprocessingSyncSystem;
6949
7314
  exports.RenderSystem = RenderSystem;
7315
+ exports.SCRIPT_OBJECT_ROOT_FLAG = SCRIPT_OBJECT_ROOT_FLAG;
6950
7316
  exports.ScriptBehaviourSystem = ScriptBehaviourSystem;
7317
+ exports.ScriptObjectSystem = ScriptObjectSystem;
6951
7318
  exports.TransformSyncSystem = TransformSyncSystem;
6952
7319
  exports.VideoSystem = VideoSystem;
6953
7320
  //# sourceMappingURL=index.cjs.map