Skip to content

ScriptObjectSystem ​

Loads, builds, ticks, and disposes ScriptObject scripts attached via ScriptObjectComponent. Unlike ScriptBehaviourSystem, it runs in the editor AND at runtime — there is no isEditorMode() gating.

Lifecycle ​

For each entity with an enabled ScriptObjectComponent that has a scriptUrl + className:

  1. Load — the module is fetched via the moduleLoader, the class resolved (named export → default → __reluScriptObjectClass marker), and instantiated. runtimeState transitions idle → loading.
  2. Build — build() runs; the returned THREE.Object3D is tagged (userData.__scriptObjectRoot) and attached under the entity's Object3DRef.object. runtimeState → ready.
  3. Update — every frame while enabled and visible, update(deltaTime) is called. Inspector data edits are pushed to this.data between frames (dirty-checked, not per-frame).
  4. Dispose — on removal, disable, script change, or invalidate*(), dispose() is called and the built subtree is removed and its geometries/materials/textures freed (shared model-cache resources are skipped).

runtimeState is written only on transitions (never every frame). A build()/update() error sets runtimeState: "error" with a sanitized message and tombstones the record so a broken script doesn't reload every frame (cleared by a script/enabled change or invalidate*()).

Keying ​

Instances are keyed by entity id — a duplicated entity gets its own fresh instance, and two entities sharing one scriptUrl get independent instances.

Hot reload ​

  • invalidateAll() — disposes every live instance so the next update() reloads with freshly compiled modules. The editor calls this on its asset-changed epoch.
  • invalidate(scriptUrl) — same, scoped to one script.

The moduleLoader owns cache-busting: with the default dynamic-import loader the URL must change for fresh code to load; invalidate*() guarantees re-instantiation.

Wiring ​

Construct it and pass it to World.setup:

ts
import {
  ScriptObjectSystem,
  World,
  type ModuleLoader,
} from "@relu-interactives/spatial-ecs";

declare const world: World;
// Resolves a script URL to its ES module — the templates supply this.
declare const myModuleLoader: ModuleLoader;

world.setup({
  systems: [new ScriptObjectSystem(myModuleLoader)],
  isEditor: false,
});

API reference ​