@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
@@ -1,5 +1,5 @@
1
- import { resolveDracoDecoderPath, applyTransform } from './chunk-AXRCGF63.js';
2
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance } from './chunk-V7BNVTJW.js';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-WKGCC7YL.js';
2
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance } from './chunk-JHCPRVBC.js';
3
3
  import { __name } from './chunk-OY7ZODZP.js';
4
4
  import * as THREE7 from 'three';
5
5
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
@@ -2741,6 +2741,10 @@ function buildComponentValueMaps(snapshot) {
2741
2741
  animationValues: readByComponent(snapshot.components, "AnimationComponent"),
2742
2742
  imageTargetAnchorValues: readByComponent(snapshot.components, "ImageTargetAnchor"),
2743
2743
  scriptValues: readByComponent(snapshot.components, "ScriptComponent"),
2744
+ scriptObjectValues: readByComponent(
2745
+ snapshot.components,
2746
+ "ScriptObjectComponent"
2747
+ ),
2744
2748
  instancedMeshValues: readByComponent(snapshot.components, "InstancedMeshComponent"),
2745
2749
  instanceSlotValues: readByComponent(snapshot.components, "InstanceSlotComponent"),
2746
2750
  prefabNodeValues: readByComponent(snapshot.components, "PrefabNode"),
@@ -2858,6 +2862,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2858
2862
  new ScriptComponent(entries)
2859
2863
  );
2860
2864
  }
2865
+ const sourceScriptObject = asRecord(values.scriptObjectValues.get(sourceEntityId));
2866
+ if (sourceScriptObject) {
2867
+ const soc = world.getComponent(targetEntityId, ScriptObjectComponent);
2868
+ const data = asRecord(sourceScriptObject.data);
2869
+ if (soc && data) {
2870
+ soc.data = remapEntityRefsInScriptData(data, idMap);
2871
+ }
2872
+ }
2861
2873
  {
2862
2874
  const imComponent = world.getComponent(targetEntityId, InstancedMeshComponent);
2863
2875
  if (imComponent && imComponent.instanceEntityIds.length > 0) {
@@ -3444,7 +3456,8 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
3444
3456
  "spotLight",
3445
3457
  "areaLight",
3446
3458
  "instancedMesh",
3447
- "instanceSlot"
3459
+ "instanceSlot",
3460
+ "scriptObject"
3448
3461
  ]);
3449
3462
 
3450
3463
  // core/hydration/hydrateWorld.ts
@@ -3546,6 +3559,16 @@ function buildEntityCreationPayload(sourceEntityId, kind, componentValues) {
3546
3559
  };
3547
3560
  }
3548
3561
  }
3562
+ if (kind === "scriptObject") {
3563
+ const so = asRecord(componentValues.scriptObjectValues.get(sourceEntityId));
3564
+ if (so) {
3565
+ payload.options = {
3566
+ scriptUrl: typeof so.scriptUrl === "string" ? so.scriptUrl : null,
3567
+ className: typeof so.className === "string" ? so.className : null,
3568
+ enabled: typeof so.enabled === "boolean" ? so.enabled : true
3569
+ };
3570
+ }
3571
+ }
3549
3572
  return payload;
3550
3573
  }
3551
3574
  __name(buildEntityCreationPayload, "buildEntityCreationPayload");
@@ -3877,6 +3900,33 @@ var toRefOrRaw = /* @__PURE__ */ __name((id, idToNode) => {
3877
3900
  const nodeId = idToNode.get(id);
3878
3901
  return nodeId !== void 0 ? makeNodeRef(nodeId) : id;
3879
3902
  }, "toRefOrRaw");
3903
+ function canonicalizeScriptDataFields(data, idToNode) {
3904
+ const canonicalData = {};
3905
+ for (const [key, fieldValue] of Object.entries(data)) {
3906
+ const field = asRecord3(fieldValue);
3907
+ if (field?.type === "entity" && typeof field.value === "number") {
3908
+ canonicalData[key] = {
3909
+ ...field,
3910
+ value: toRefOrRaw(field.value, idToNode)
3911
+ };
3912
+ continue;
3913
+ }
3914
+ const componentRef = asRecord3(field?.value);
3915
+ if (field?.type === "component" && componentRef && typeof componentRef.entityId === "number") {
3916
+ canonicalData[key] = {
3917
+ ...field,
3918
+ value: {
3919
+ ...componentRef,
3920
+ entityId: toRefOrRaw(componentRef.entityId, idToNode)
3921
+ }
3922
+ };
3923
+ continue;
3924
+ }
3925
+ canonicalData[key] = fieldValue;
3926
+ }
3927
+ return canonicalData;
3928
+ }
3929
+ __name(canonicalizeScriptDataFields, "canonicalizeScriptDataFields");
3880
3930
  function canonicalizeComponentValue(componentName, value, idToNode) {
3881
3931
  const cloned = clone(value);
3882
3932
  const record = asRecord3(cloned);
@@ -3902,39 +3952,23 @@ function canonicalizeComponentValue(componentName, value, idToNode) {
3902
3952
  if (!entry) {
3903
3953
  return rawEntry;
3904
3954
  }
3905
- const data = asRecord3(entry.data) ?? {};
3906
- const canonicalData = {};
3907
- for (const [key, fieldValue] of Object.entries(data)) {
3908
- const field = asRecord3(fieldValue);
3909
- if (field?.type === "entity" && typeof field.value === "number") {
3910
- canonicalData[key] = {
3911
- ...field,
3912
- value: toRefOrRaw(field.value, idToNode)
3913
- };
3914
- continue;
3915
- }
3916
- const componentRef = asRecord3(field?.value);
3917
- if (field?.type === "component" && componentRef && typeof componentRef.entityId === "number") {
3918
- canonicalData[key] = {
3919
- ...field,
3920
- value: {
3921
- ...componentRef,
3922
- entityId: toRefOrRaw(componentRef.entityId, idToNode)
3923
- }
3924
- };
3925
- continue;
3926
- }
3927
- canonicalData[key] = fieldValue;
3928
- }
3929
3955
  return {
3930
3956
  className: typeof entry.className === "string" ? entry.className : "",
3931
3957
  scriptUrl: typeof entry.scriptUrl === "string" ? entry.scriptUrl : "",
3932
3958
  enabled: typeof entry.enabled === "boolean" ? entry.enabled : true,
3933
- data: canonicalData
3959
+ data: canonicalizeScriptDataFields(asRecord3(entry.data) ?? {}, idToNode)
3934
3960
  };
3935
3961
  })
3936
3962
  };
3937
3963
  }
3964
+ if (componentName === "ScriptObjectComponent") {
3965
+ return {
3966
+ scriptUrl: typeof record.scriptUrl === "string" ? record.scriptUrl : null,
3967
+ className: typeof record.className === "string" ? record.className : null,
3968
+ enabled: typeof record.enabled === "boolean" ? record.enabled : true,
3969
+ data: canonicalizeScriptDataFields(asRecord3(record.data) ?? {}, idToNode)
3970
+ };
3971
+ }
3938
3972
  if (componentName === "InstancedMeshComponent") {
3939
3973
  if (Array.isArray(record.instanceEntityIds)) {
3940
3974
  record.instanceEntityIds = record.instanceEntityIds.map(
@@ -4637,22 +4671,14 @@ var _Behaviour = class _Behaviour {
4637
4671
  /**
4638
4672
  * Called once before the first {@link Behaviour.onUpdate}. Override to
4639
4673
  * default and validate {@link Behaviour.data}, cache references, or set
4640
- * initial state.
4641
- *
4642
- * @remarks
4643
- * Also called in editor mode when `executeInEditor` is `true` on the script
4644
- * entry.
4674
+ * initial state. Runs at preview/runtime only — never in the editor.
4645
4675
  */
4646
4676
  init() {
4647
4677
  }
4648
4678
  /**
4649
4679
  * Called every frame while the script is enabled. Override with the
4650
- * script's main per-frame logic.
4651
- *
4652
- * @remarks
4653
- * Also called every frame in editor mode when `executeInEditor` is `true`.
4654
- * Access the Three.js scene with `this.world.getScene()` for editor-only
4655
- * rendering work such as custom shaders or procedural geometry.
4680
+ * script's main per-frame logic. Runs at preview/runtime only — never in the
4681
+ * editor (use {@link ScriptObject} for editor-visible visuals).
4656
4682
  */
4657
4683
  onUpdate() {
4658
4684
  }
@@ -4691,7 +4717,14 @@ __name(_Behaviour, "Behaviour");
4691
4717
  _Behaviour.__reluBehaviourClass = true;
4692
4718
  var Behaviour = _Behaviour;
4693
4719
 
4694
- // core/systems/ScriptBehaviourSystem.ts
4720
+ // core/systems/scriptShared.ts
4721
+ async function defaultModuleLoader(scriptUrl) {
4722
+ return await import(
4723
+ /* webpackIgnore: true */
4724
+ scriptUrl
4725
+ );
4726
+ }
4727
+ __name(defaultModuleLoader, "defaultModuleLoader");
4695
4728
  function toErrorMessage(error) {
4696
4729
  const sanitize = /* @__PURE__ */ __name((value) => value.replace(/localasset:\/\/[^/]+\//g, "localasset://").replace(/\/Users\/[^\s)]+/g, "[local path]"), "sanitize");
4697
4730
  if (error instanceof Error && error.message) {
@@ -4703,26 +4736,22 @@ function toErrorMessage(error) {
4703
4736
  return "Unknown script error.";
4704
4737
  }
4705
4738
  __name(toErrorMessage, "toErrorMessage");
4706
- function buildInstanceKey(entityId, scriptUrl, className) {
4707
- return `${entityId}:${scriptUrl}:${className}`;
4708
- }
4709
- __name(buildInstanceKey, "buildInstanceKey");
4710
- function resolveBehaviourCtor(moduleValue, className) {
4739
+ function resolveScriptCtor(moduleValue, className, options) {
4711
4740
  const named = moduleValue[className];
4712
4741
  const fallback = moduleValue.default;
4713
4742
  const candidate = typeof named === "function" ? named : typeof fallback === "function" ? fallback : null;
4714
4743
  if (!candidate) {
4715
4744
  return null;
4716
4745
  }
4717
- if (!(candidate.prototype instanceof Behaviour)) {
4718
- const isBehaviourSubclass = !!candidate.__reluBehaviourClass;
4719
- if (!isBehaviourSubclass) {
4746
+ if (!(candidate.prototype instanceof options.baseClass)) {
4747
+ const marker = candidate[options.markerKey];
4748
+ if (!marker) {
4720
4749
  return null;
4721
4750
  }
4722
4751
  }
4723
4752
  return candidate;
4724
4753
  }
4725
- __name(resolveBehaviourCtor, "resolveBehaviourCtor");
4754
+ __name(resolveScriptCtor, "resolveScriptCtor");
4726
4755
  function normalizeScriptData(value) {
4727
4756
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4728
4757
  }
@@ -4804,8 +4833,8 @@ function resolveInputData(world, rawData) {
4804
4833
  return resolved;
4805
4834
  }
4806
4835
  __name(resolveInputData, "resolveInputData");
4807
- function serializeDataForStorage(rawData, behaviourData) {
4808
- const result = { ...behaviourData };
4836
+ function serializeDataForStorage(rawData, instanceData) {
4837
+ const result = { ...instanceData };
4809
4838
  for (const [key, rawValue] of Object.entries(rawData)) {
4810
4839
  if (isRawEntityInput(rawValue) || isRawComponentInput(rawValue)) {
4811
4840
  result[key] = rawValue;
@@ -4814,13 +4843,25 @@ function serializeDataForStorage(rawData, behaviourData) {
4814
4843
  return result;
4815
4844
  }
4816
4845
  __name(serializeDataForStorage, "serializeDataForStorage");
4846
+
4847
+ // core/systems/ScriptBehaviourSystem.ts
4848
+ function buildInstanceKey(entityId, scriptUrl, className) {
4849
+ return `${entityId}:${scriptUrl}:${className}`;
4850
+ }
4851
+ __name(buildInstanceKey, "buildInstanceKey");
4852
+ function resolveBehaviourCtor(moduleValue, className) {
4853
+ return resolveScriptCtor(moduleValue, className, {
4854
+ baseClass: Behaviour,
4855
+ markerKey: "__reluBehaviourClass"
4856
+ });
4857
+ }
4858
+ __name(resolveBehaviourCtor, "resolveBehaviourCtor");
4817
4859
  function normalizeEntry(value, fallbackEntityId) {
4818
4860
  return {
4819
4861
  entityId: typeof value?.entityId === "number" ? value.entityId : fallbackEntityId,
4820
4862
  className: typeof value?.className === "string" ? value.className : "",
4821
4863
  scriptUrl: typeof value?.scriptUrl === "string" ? value.scriptUrl : "",
4822
4864
  enabled: typeof value?.enabled === "boolean" ? value.enabled : true,
4823
- executeInEditor: typeof value?.executeInEditor === "boolean" ? value.executeInEditor : false,
4824
4865
  data: normalizeScriptData(value?.data),
4825
4866
  runtimeState: value?.runtimeState === "idle" || value?.runtimeState === "loading" || value?.runtimeState === "ready" || value?.runtimeState === "error" ? value.runtimeState : void 0,
4826
4867
  runtimeMessage: typeof value?.runtimeMessage === "string" ? value.runtimeMessage : void 0
@@ -4843,13 +4884,6 @@ function writeScriptEntries(component, entries) {
4843
4884
  }
4844
4885
  }
4845
4886
  __name(writeScriptEntries, "writeScriptEntries");
4846
- async function defaultModuleLoader(scriptUrl) {
4847
- return await import(
4848
- /* webpackIgnore: true */
4849
- scriptUrl
4850
- );
4851
- }
4852
- __name(defaultModuleLoader, "defaultModuleLoader");
4853
4887
  var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
4854
4888
  constructor(moduleLoader, selectionContext) {
4855
4889
  this.instances = /* @__PURE__ */ new Map();
@@ -4965,11 +4999,11 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
4965
4999
  let hasEnabledScript = false;
4966
5000
  for (let entryIndex = 0; entryIndex < nextEntries.length; entryIndex += 1) {
4967
5001
  const entry = nextEntries[entryIndex];
4968
- const skipInEditor = world.isEditorMode() && !entry.executeInEditor;
5002
+ const skipInEditor = world.isEditorMode();
4969
5003
  if (!entry.enabled || !entry.scriptUrl || skipInEditor) {
4970
5004
  nextEntries[entryIndex] = {
4971
5005
  ...entry,
4972
- runtimeState: skipInEditor ? "idle" : "idle",
5006
+ runtimeState: "idle",
4973
5007
  runtimeMessage: void 0
4974
5008
  };
4975
5009
  continue;
@@ -5156,7 +5190,7 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
5156
5190
  const mergedData = mergeDataByCurrentDefaults(defaults, currentData);
5157
5191
  const resolvedData = resolveInputData(world, mergedData);
5158
5192
  behaviour.data = resolvedData;
5159
- if (!world.isEditorMode() || liveEntry.executeInEditor === true) {
5193
+ if (!world.isEditorMode()) {
5160
5194
  behaviour.init();
5161
5195
  }
5162
5196
  liveEntries[entryIndex] = {
@@ -5184,6 +5218,328 @@ __name(_ScriptBehaviourSystem, "ScriptBehaviourSystem");
5184
5218
  _ScriptBehaviourSystem.CLICK_THRESHOLD = 6;
5185
5219
  var ScriptBehaviourSystem = _ScriptBehaviourSystem;
5186
5220
 
5221
+ // core/ScriptObject.ts
5222
+ var _ScriptObject = class _ScriptObject {
5223
+ /**
5224
+ * Engine-managed constructor. `ScriptObjectSystem` instantiates subclasses
5225
+ * with the owning world, the entity id, and the component's `data`. User code
5226
+ * should not call this directly.
5227
+ */
5228
+ constructor(world, entityId, data) {
5229
+ /**
5230
+ * The script's data payload (the `ScriptObjectComponent.data`). Declare a
5231
+ * concrete shape on subclasses with `declare data: MyShape` for type safety.
5232
+ * Use the same typed input envelopes as Behaviours (`ColorInput`,
5233
+ * `SliderInput`, `EntityInput`, …) to get inspector fields.
5234
+ */
5235
+ this.data = {};
5236
+ /**
5237
+ * The Object3D returned by {@link ScriptObject.build}. Assigned by
5238
+ * `ScriptObjectSystem` after `build()` runs; `null` until then.
5239
+ */
5240
+ this.root = null;
5241
+ this.world = world;
5242
+ this.entityId = entityId;
5243
+ if (data) this.data = data;
5244
+ }
5245
+ /**
5246
+ * REQUIRED — build and return the visual root. Called once by
5247
+ * `ScriptObjectSystem` after construction. The returned object is attached
5248
+ * under the entity's Object3D (so it inherits the entity transform, selection
5249
+ * and visibility). Subclasses MUST override.
5250
+ */
5251
+ build() {
5252
+ throw new Error("ScriptObject subclasses must implement build()");
5253
+ }
5254
+ };
5255
+ __name(_ScriptObject, "ScriptObject");
5256
+ /**
5257
+ * Static marker used by `ScriptObjectSystem` to detect `ScriptObject`
5258
+ * subclasses even when a bundler ships a second copy of the package into the
5259
+ * script blob (breaking `instanceof`). Do NOT override.
5260
+ */
5261
+ _ScriptObject.__reluScriptObjectClass = true;
5262
+ var ScriptObject = _ScriptObject;
5263
+
5264
+ // core/systems/ScriptObjectSystem.ts
5265
+ var SCRIPT_OBJECT_ROOT_FLAG = "__scriptObjectRoot";
5266
+ function setInstanceRef(component, value) {
5267
+ Object.defineProperty(component, "instance", {
5268
+ value,
5269
+ writable: true,
5270
+ configurable: true,
5271
+ enumerable: false
5272
+ });
5273
+ }
5274
+ __name(setInstanceRef, "setInstanceRef");
5275
+ function disposeBuiltObject(root) {
5276
+ root.parent?.remove(root);
5277
+ root.traverse((node) => {
5278
+ const mesh = node;
5279
+ const geometry = mesh.geometry;
5280
+ if (geometry?.dispose && !geometry.userData?.[SHARED_MODEL_RESOURCE_FLAG]) {
5281
+ geometry.dispose();
5282
+ }
5283
+ const material = mesh.material;
5284
+ const materials = Array.isArray(material) ? material : material ? [material] : [];
5285
+ for (const mat of materials) {
5286
+ if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
5287
+ for (const value of Object.values(mat)) {
5288
+ if (value instanceof THREE7.Texture) value.dispose();
5289
+ }
5290
+ mat.dispose?.();
5291
+ }
5292
+ });
5293
+ }
5294
+ __name(disposeBuiltObject, "disposeBuiltObject");
5295
+ var _ScriptObjectSystem = class _ScriptObjectSystem {
5296
+ constructor(moduleLoader) {
5297
+ this.instances = /* @__PURE__ */ new Map();
5298
+ this.loading = /* @__PURE__ */ new Map();
5299
+ /** Bumped by invalidate*() so in-flight loads that predate it are discarded. */
5300
+ this.epoch = 0;
5301
+ this.moduleLoader = moduleLoader ?? defaultModuleLoader;
5302
+ }
5303
+ update(world, deltaTime) {
5304
+ const active = /* @__PURE__ */ new Map();
5305
+ for (const entityId of world.query(ScriptObjectComponent)) {
5306
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5307
+ if (!component) continue;
5308
+ const wantsRun = component.enabled && typeof component.scriptUrl === "string" && typeof component.className === "string";
5309
+ if (!wantsRun) {
5310
+ const record = this.instances.get(entityId);
5311
+ if (record) this.teardownRecord(world, entityId, record, false);
5312
+ this.loading.delete(entityId);
5313
+ if (component.runtimeState !== "idle") {
5314
+ component.runtimeState = "idle";
5315
+ component.runtimeMessage = void 0;
5316
+ }
5317
+ continue;
5318
+ }
5319
+ active.set(entityId, component);
5320
+ }
5321
+ for (const [entityId, record] of this.instances) {
5322
+ const component = active.get(entityId);
5323
+ const identityChanged = !component || record.scriptUrl !== component.scriptUrl || record.className !== component.className;
5324
+ if (!world.hasEntity(entityId) || identityChanged) {
5325
+ this.teardownRecord(world, entityId, record, false);
5326
+ }
5327
+ }
5328
+ for (const [entityId, component] of active) {
5329
+ const record = this.instances.get(entityId);
5330
+ if (!record || record.status !== "ready" || !record.instance) continue;
5331
+ if (!world.isEntityVisible(entityId)) continue;
5332
+ if (component.data !== record.lastWrittenData) {
5333
+ try {
5334
+ const merged = mergeDataByCurrentDefaults(
5335
+ record.rawData,
5336
+ normalizeScriptData(component.data)
5337
+ );
5338
+ record.rawData = merged;
5339
+ record.instance.data = resolveInputData(world, merged);
5340
+ const stored = serializeDataForStorage(merged, merged);
5341
+ component.data = stored;
5342
+ record.lastWrittenData = stored;
5343
+ } catch (error) {
5344
+ this.markError(world, entityId, component, error);
5345
+ continue;
5346
+ }
5347
+ }
5348
+ try {
5349
+ record.instance.update?.(deltaTime);
5350
+ } catch (error) {
5351
+ this.markError(world, entityId, component, error);
5352
+ }
5353
+ }
5354
+ for (const [entityId, component] of active) {
5355
+ if (this.instances.has(entityId) || this.loading.has(entityId)) continue;
5356
+ const scriptUrl = component.scriptUrl;
5357
+ const className = component.className;
5358
+ if (component.runtimeState !== "loading") {
5359
+ component.runtimeState = "loading";
5360
+ component.runtimeMessage = void 0;
5361
+ }
5362
+ const epochAtStart = this.epoch;
5363
+ const key = `${epochAtStart}:${scriptUrl}:${className}`;
5364
+ const promise = this.loadScriptObject(
5365
+ world,
5366
+ entityId,
5367
+ scriptUrl,
5368
+ className,
5369
+ key,
5370
+ epochAtStart
5371
+ ).catch((error) => {
5372
+ this.markError(world, entityId, void 0, error);
5373
+ console.error(
5374
+ `[ScriptObjectSystem] Failed loading ${className} (${scriptUrl})`,
5375
+ error
5376
+ );
5377
+ }).finally(() => {
5378
+ if (this.loading.get(entityId)?.key === key) {
5379
+ this.loading.delete(entityId);
5380
+ }
5381
+ });
5382
+ this.loading.set(entityId, { key, promise });
5383
+ }
5384
+ for (const entityId of this.loading.keys()) {
5385
+ if (!active.has(entityId)) this.loading.delete(entityId);
5386
+ }
5387
+ }
5388
+ async loadScriptObject(world, entityId, scriptUrl, className, key, epochAtStart) {
5389
+ const moduleValue = await this.moduleLoader(scriptUrl);
5390
+ if (this.loading.get(entityId)?.key !== key || epochAtStart !== this.epoch || !world.hasEntity(entityId)) {
5391
+ return;
5392
+ }
5393
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5394
+ if (!component || !component.enabled || component.scriptUrl !== scriptUrl || component.className !== className) {
5395
+ return;
5396
+ }
5397
+ const ctor = resolveScriptCtor(moduleValue, className, {
5398
+ baseClass: ScriptObject,
5399
+ markerKey: "__reluScriptObjectClass"
5400
+ });
5401
+ if (!ctor) {
5402
+ throw new Error(
5403
+ `Missing ScriptObject export '${className}' in ${scriptUrl}`
5404
+ );
5405
+ }
5406
+ const currentData = normalizeScriptData(component.data);
5407
+ let instance;
5408
+ try {
5409
+ instance = new ctor(world, entityId, void 0);
5410
+ } catch {
5411
+ instance = new ctor(world, entityId, currentData);
5412
+ }
5413
+ const defaults = normalizeScriptData(instance.data);
5414
+ const merged = mergeDataByCurrentDefaults(defaults, currentData);
5415
+ instance.data = resolveInputData(world, merged);
5416
+ const parent = world.getComponent(entityId, Object3DRef)?.object;
5417
+ if (!parent) {
5418
+ throw new Error("Script object entity has no Object3D to attach to.");
5419
+ }
5420
+ for (const child of [...parent.children]) {
5421
+ if (child.userData?.[SCRIPT_OBJECT_ROOT_FLAG]) {
5422
+ disposeBuiltObject(child);
5423
+ }
5424
+ }
5425
+ let built;
5426
+ try {
5427
+ built = instance.build();
5428
+ } catch (error) {
5429
+ try {
5430
+ instance.dispose?.();
5431
+ } catch {
5432
+ }
5433
+ throw error;
5434
+ }
5435
+ if (!built || built.isObject3D !== true) {
5436
+ try {
5437
+ instance.dispose?.();
5438
+ } catch {
5439
+ }
5440
+ throw new Error("build() must return a THREE.Object3D.");
5441
+ }
5442
+ built.userData[SCRIPT_OBJECT_ROOT_FLAG] = true;
5443
+ parent.add(built);
5444
+ instance.root = built;
5445
+ const stored = serializeDataForStorage(merged, merged);
5446
+ component.data = stored;
5447
+ component.runtimeState = "ready";
5448
+ component.runtimeMessage = void 0;
5449
+ setInstanceRef(component, instance);
5450
+ this.instances.set(entityId, {
5451
+ entityId,
5452
+ scriptUrl,
5453
+ className,
5454
+ status: "ready",
5455
+ instance,
5456
+ root: built,
5457
+ rawData: merged,
5458
+ lastWrittenData: stored
5459
+ });
5460
+ }
5461
+ /** Dispose a record's instance + built subtree. `keepTombstone` leaves an error marker. */
5462
+ teardownRecord(world, entityId, record, keepTombstone) {
5463
+ try {
5464
+ record.instance?.dispose?.();
5465
+ } catch {
5466
+ }
5467
+ if (record.root) disposeBuiltObject(record.root);
5468
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5469
+ if (component) setInstanceRef(component, null);
5470
+ if (keepTombstone) {
5471
+ this.instances.set(entityId, {
5472
+ ...record,
5473
+ status: "error",
5474
+ instance: null,
5475
+ root: null
5476
+ });
5477
+ } else {
5478
+ this.instances.delete(entityId);
5479
+ }
5480
+ }
5481
+ markError(world, entityId, component, error) {
5482
+ const comp = component ?? world.getComponent(entityId, ScriptObjectComponent) ?? null;
5483
+ if (comp) {
5484
+ comp.runtimeState = "error";
5485
+ comp.runtimeMessage = toErrorMessage(error);
5486
+ }
5487
+ const record = this.instances.get(entityId);
5488
+ if (record) {
5489
+ this.teardownRecord(world, entityId, record, true);
5490
+ } else {
5491
+ this.instances.set(entityId, {
5492
+ entityId,
5493
+ scriptUrl: comp?.scriptUrl ?? "",
5494
+ className: comp?.className ?? "",
5495
+ status: "error",
5496
+ instance: null,
5497
+ root: null,
5498
+ rawData: {},
5499
+ lastWrittenData: null
5500
+ });
5501
+ }
5502
+ }
5503
+ /** Dispose all live instances so the next update() reloads with fresh modules. */
5504
+ invalidateAll() {
5505
+ this.epoch += 1;
5506
+ for (const [entityId, record] of [...this.instances]) {
5507
+ try {
5508
+ record.instance?.dispose?.();
5509
+ } catch {
5510
+ }
5511
+ if (record.root) disposeBuiltObject(record.root);
5512
+ this.instances.delete(entityId);
5513
+ }
5514
+ this.loading.clear();
5515
+ }
5516
+ /** Invalidate only the entities whose script matches `scriptUrl`. */
5517
+ invalidate(scriptUrl) {
5518
+ this.epoch += 1;
5519
+ for (const [entityId, record] of [...this.instances]) {
5520
+ if (record.scriptUrl !== scriptUrl) continue;
5521
+ try {
5522
+ record.instance?.dispose?.();
5523
+ } catch {
5524
+ }
5525
+ if (record.root) disposeBuiltObject(record.root);
5526
+ this.instances.delete(entityId);
5527
+ }
5528
+ for (const [entityId, entry] of [...this.loading]) {
5529
+ if (entry.key.includes(`:${scriptUrl}:`)) this.loading.delete(entityId);
5530
+ }
5531
+ }
5532
+ dispose(world) {
5533
+ for (const [entityId, record] of [...this.instances]) {
5534
+ this.teardownRecord(world, entityId, record, false);
5535
+ }
5536
+ this.instances.clear();
5537
+ this.loading.clear();
5538
+ }
5539
+ };
5540
+ __name(_ScriptObjectSystem, "ScriptObjectSystem");
5541
+ var ScriptObjectSystem = _ScriptObjectSystem;
5542
+
5187
5543
  // core/systems/TransformSyncSystem.ts
5188
5544
  var _TransformSyncSystem = class _TransformSyncSystem {
5189
5545
  update(world) {
@@ -5445,6 +5801,6 @@ var _VideoSystem = class _VideoSystem {
5445
5801
  __name(_VideoSystem, "VideoSystem");
5446
5802
  var VideoSystem = _VideoSystem;
5447
5803
 
5448
- export { ADDED_NODE_MARKER_COMPONENT, AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, ScriptBehaviourSystem, TransformSyncSystem, VideoSystem, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeInstanceOverrides, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, restoreComponentState, restorePostCreateComponents, setPrefabAssetUrlResolver, spawnPrefab };
5449
- //# sourceMappingURL=chunk-5U3YS4JD.js.map
5450
- //# sourceMappingURL=chunk-5U3YS4JD.js.map
5804
+ export { ADDED_NODE_MARKER_COMPONENT, AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TransformSyncSystem, VideoSystem, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeInstanceOverrides, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, restoreComponentState, restorePostCreateComponents, setPrefabAssetUrlResolver, spawnPrefab };
5805
+ //# sourceMappingURL=chunk-GZ2NKSMV.js.map
5806
+ //# sourceMappingURL=chunk-GZ2NKSMV.js.map