@needle-tools/engine 5.1.0-alpha.3 → 5.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/SKILL.md +4 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DF01sSGQ.js → needle-engine.bundle-C-LG00ZZ.js} +10681 -10100
- package/dist/needle-engine.bundle-D7tzaiYE.min.js +1733 -0
- package/dist/{needle-engine.bundle-C-ixARur.umd.cjs → needle-engine.bundle-OPkPmdUM.umd.cjs} +161 -161
- package/dist/needle-engine.d.ts +1349 -317
- package/dist/needle-engine.js +556 -555
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +5 -0
- package/lib/engine/api.js +4 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -18
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_context.d.ts +20 -7
- package/lib/engine/engine_context.js +31 -15
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_disposable.d.ts +172 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +20 -118
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_input.d.ts +23 -4
- package/lib/engine/engine_input.js +2 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_mainloop_utils.js +2 -2
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +51 -37
- package/lib/engine/engine_networking.js +132 -82
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
- package/lib/engine/engine_networking.transport.websocket.js +38 -0
- package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
- package/lib/engine/engine_networking_instantiate.js +2 -2
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_types.d.ts +39 -1
- package/lib/engine/engine_networking_types.js +7 -0
- package/lib/engine/engine_networking_types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +21 -3
- package/lib/engine/engine_physics_rapier.js +94 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -5
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +29 -11
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +23 -13
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +191 -0
- package/lib/engine-components/AnimatorController.builder.js +263 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +2 -119
- package/lib/engine-components/AnimatorController.js +33 -232
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +72 -9
- package/lib/engine-components/Component.js +114 -10
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DragControls.js +0 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.js +3 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +9 -1
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +0 -2
- package/lib/engine-components/OrbitControls.js +14 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +2 -1
- package/lib/engine-components/api.js +2 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +7 -13
- package/lib/engine-components/codegen/components.js +7 -13
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +21 -11
- package/lib/engine-components/timeline/PlayableDirector.js +75 -67
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +413 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +506 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +37 -6
- package/lib/engine-components/timeline/TimelineTracks.js +92 -26
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +0 -1
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +5 -2
- package/plugins/common/worker.js +9 -4
- package/plugins/vite/dependencies.js +1 -11
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/license.js +19 -1
- package/plugins/vite/reload.js +1 -1
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +7 -0
- package/src/engine/codegen/register_types.ts +10 -18
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_context.ts +32 -16
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_disposable.ts +214 -0
- package/src/engine/engine_gameobject.ts +52 -157
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_input.ts +27 -6
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_mainloop_utils.ts +2 -2
- package/src/engine/engine_networking.transport.websocket.ts +45 -0
- package/src/engine/engine_networking.ts +161 -137
- package/src/engine/engine_networking_instantiate.ts +2 -2
- package/src/engine/engine_networking_types.ts +41 -1
- package/src/engine/engine_physics_rapier.ts +102 -33
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -6
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +46 -27
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +24 -12
- package/src/engine-components/AnimatorController.builder.ts +387 -0
- package/src/engine-components/AnimatorController.ts +20 -291
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +118 -20
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DragControls.ts +0 -9
- package/src/engine-components/DropListener.ts +3 -0
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +10 -2
- package/src/engine-components/OrbitControls.ts +16 -5
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/api.ts +2 -1
- package/src/engine-components/codegen/components.ts +7 -13
- package/src/engine-components/timeline/PlayableDirector.ts +83 -81
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +824 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +96 -27
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/web/CursorFollow.ts +0 -1
- package/dist/needle-engine.bundle-CHmXdnE1.min.js +0 -1733
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
|
@@ -77,7 +77,7 @@ export function needleDependencies(command, config, userSettings) {
|
|
|
77
77
|
if (req.url) {
|
|
78
78
|
for (const pkg of rewritePackages) {
|
|
79
79
|
const marker = `/${pkg}/`;
|
|
80
|
-
if (req.url.includes(marker) && !req.url.startsWith(`/node_modules/${pkg}/`)) {
|
|
80
|
+
if (req.url.includes(marker) && !req.url.startsWith(`/node_modules/${pkg}/`) && !req.url.startsWith('/@fs/')) {
|
|
81
81
|
const idx = req.url.indexOf(marker);
|
|
82
82
|
const rewritten = '/node_modules' + req.url.slice(idx);
|
|
83
83
|
needleLog('needle-dependencies', `Rewriting worker URL → ${rewritten}`);
|
|
@@ -187,16 +187,6 @@ function handleOptimizeDeps(config) {
|
|
|
187
187
|
config.optimizeDeps.exclude.push('@needle-tools/engine');
|
|
188
188
|
needleLog("needle-dependencies", 'Detected local @needle-tools/engine package → will exclude it from optimization');
|
|
189
189
|
}
|
|
190
|
-
// When engine is excluded from optimizeDeps, three-mesh-bvh must also be excluded.
|
|
191
|
-
// The BVH worker (generateMeshBVH.worker.js) uses bare imports like `import 'three'`
|
|
192
|
-
// which only resolve correctly when served through Vite's dev server module system.
|
|
193
|
-
// If three-mesh-bvh is pre-bundled, the worker URL points into the cache and bare
|
|
194
|
-
// imports fail at runtime → "Unknown error. Please check the server console."
|
|
195
|
-
if (!config.optimizeDeps.include?.includes('three-mesh-bvh') &&
|
|
196
|
-
!config.optimizeDeps.exclude.includes('three-mesh-bvh')) {
|
|
197
|
-
config.optimizeDeps.exclude.push('three-mesh-bvh');
|
|
198
|
-
needleLog("needle-dependencies", 'Detected local @needle-tools/engine package → will also exclude three-mesh-bvh from optimization');
|
|
199
|
-
}
|
|
200
190
|
}
|
|
201
191
|
}
|
|
202
192
|
|
|
@@ -98,7 +98,7 @@ function watchPackageJson(server, projectDir, packageJsonPath, cachePath) {
|
|
|
98
98
|
|
|
99
99
|
setTimeout(() => {
|
|
100
100
|
requireInstall = testIfInstallIsRequired(projectDir, packageJson);
|
|
101
|
-
}, 1000);
|
|
101
|
+
}, 1000).unref();
|
|
102
102
|
|
|
103
103
|
setInterval(() => {
|
|
104
104
|
if (!packageJson || lastEditTime === undefined) return;
|
|
@@ -149,7 +149,7 @@ function watchPackageJson(server, projectDir, packageJsonPath, cachePath) {
|
|
|
149
149
|
restart(server, projectDir, cachePath);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
}, 2000);
|
|
152
|
+
}, 2000).unref();
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/** @param {string} projectDir @param {PackageJson | undefined} packageJson @returns {boolean} */
|
|
@@ -27,7 +27,7 @@ export async function editorConnection(command, config, userSettings, pluginsArr
|
|
|
27
27
|
if (typeof config.generator === "string" && !config.generator.includes("Unity")) return;
|
|
28
28
|
|
|
29
29
|
if (!config) {
|
|
30
|
-
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync can not be installed automatically to vite: missing config", "warn"), 1000);
|
|
30
|
+
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync can not be installed automatically to vite: missing config", "warn"), 1000).unref();
|
|
31
31
|
return createPlugin(false);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -42,14 +42,14 @@ export async function editorConnection(command, config, userSettings, pluginsArr
|
|
|
42
42
|
// }
|
|
43
43
|
// }
|
|
44
44
|
if (needleEditorSettings && needleEditorSettings.enabled === false) {
|
|
45
|
-
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync is not enabled. Add a 'Needle Editor Sync' component to your scene to enable", "warn"), 1000);
|
|
45
|
+
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync is not enabled. Add a 'Needle Editor Sync' component to your scene to enable", "warn"), 1000).unref();
|
|
46
46
|
return createPlugin(false);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Check if the editor package is installed
|
|
50
50
|
let path = root + `/node_modules/${editorSyncPackageName}/plugins/index.js`;
|
|
51
51
|
if (existsSync(path) === false) {
|
|
52
|
-
setTimeout(() => needleLog("needle-editor-sync", `${editorSyncPackageName} is not installed: Add the "Needle Editor Sync" component to your scene if you want to send changes directly from the Unity Editor to web app`, "warn"), 1000);
|
|
52
|
+
setTimeout(() => needleLog("needle-editor-sync", `${editorSyncPackageName} is not installed: Add the "Needle Editor Sync" component to your scene if you want to send changes directly from the Unity Editor to web app`, "warn"), 1000).unref();
|
|
53
53
|
return createPlugin(false);
|
|
54
54
|
}
|
|
55
55
|
|
package/plugins/vite/license.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadConfig } from './config.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export function needleLicense(command, config, userSettings) {
|
|
11
11
|
let license = undefined;
|
|
12
|
+
let appliedLicense = false;
|
|
12
13
|
|
|
13
14
|
return {
|
|
14
15
|
name: "needle:license",
|
|
@@ -31,7 +32,15 @@ export function needleLicense(command, config, userSettings) {
|
|
|
31
32
|
|
|
32
33
|
},
|
|
33
34
|
async transform(src, id) {
|
|
34
|
-
|
|
35
|
+
if (appliedLicense === true) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Vite 4 and 8 handling:
|
|
40
|
+
const isNeedleEngineFile = id.includes("engine/engine_license")
|
|
41
|
+
|| id.includes("needle-tools_engine")
|
|
42
|
+
|| id.includes("@needle-tools")
|
|
43
|
+
|| id.includes("needle-engine");
|
|
35
44
|
// sometimes the actual license parameter is in a unnamed chunk file
|
|
36
45
|
const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
|
|
37
46
|
if (isNeedleEngineFile || isViteChunkFile) {
|
|
@@ -44,12 +53,21 @@ export function needleLicense(command, config, userSettings) {
|
|
|
44
53
|
if (index >= 0) {
|
|
45
54
|
const end = src.indexOf(";", index);
|
|
46
55
|
if (end >= 0) {
|
|
56
|
+
appliedLicense = true;
|
|
47
57
|
const line = src.substring(index, end);
|
|
48
58
|
const replaced = "NEEDLE_ENGINE_LICENSE_TYPE = \"" + license + "\"";
|
|
49
59
|
src = src.replace(line, replaced);
|
|
50
60
|
return { code: src, map: null }
|
|
51
61
|
}
|
|
52
62
|
}
|
|
63
|
+
// @TODO: detect local needle engine dev setup and log error if not found
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
buildEnd() {
|
|
67
|
+
if (!appliedLicense) {
|
|
68
|
+
if (process.env.NEEDLE_TEST_ENV) {
|
|
69
|
+
console.error("ERR: License was not applied!");
|
|
70
|
+
}
|
|
53
71
|
}
|
|
54
72
|
}
|
|
55
73
|
}
|
package/plugins/vite/reload.js
CHANGED
|
@@ -225,7 +225,7 @@ async function scheduleReload(server, level = 0) {
|
|
|
225
225
|
if (existsSync(lockFile)) {
|
|
226
226
|
if (level === 0)
|
|
227
227
|
needleLog(pluginName, "Lock file exists, waiting for export to finish...");
|
|
228
|
-
setTimeout(() => scheduleReload(server, level += 1), 300);
|
|
228
|
+
setTimeout(() => scheduleReload(server, level += 1), 300).unref();
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
231
|
|
package/plugins/vite/server.js
CHANGED
|
@@ -60,7 +60,8 @@ export function needleServer(command, config, userSettings) {
|
|
|
60
60
|
})
|
|
61
61
|
.catch((err) => console.error("ERR: [needle:server] 'open' package not found - please make sure to install 'open' in your package.json\n", err));
|
|
62
62
|
}
|
|
63
|
-
}, 100)
|
|
63
|
+
}, 100);
|
|
64
|
+
i.unref();
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
}
|
package/src/engine/api.ts
CHANGED
|
@@ -167,6 +167,9 @@ export * from "./engine_constants.js";
|
|
|
167
167
|
*/
|
|
168
168
|
export * from "./engine_context.js";
|
|
169
169
|
|
|
170
|
+
/** Typed event bus for decoupled component communication via {@link Context.events} */
|
|
171
|
+
export * from "./engine_context_eventbus.js";
|
|
172
|
+
|
|
170
173
|
/** Registry for managing multiple engine contexts */
|
|
171
174
|
export * from "./engine_context_registry.js";
|
|
172
175
|
|
|
@@ -176,6 +179,10 @@ export * from "./engine_context_registry.js";
|
|
|
176
179
|
*/
|
|
177
180
|
export * from "./engine_coroutine.js"
|
|
178
181
|
|
|
182
|
+
/** DisposableStore for managing lifecycle-bound cleanup in components */
|
|
183
|
+
export { DisposableStore, on, isDisposable } from "./engine_disposable.js";
|
|
184
|
+
export type { DisposeFn, IDisposable } from "./engine_disposable.js";
|
|
185
|
+
|
|
179
186
|
/** Factory functions for creating primitives and objects */
|
|
180
187
|
export * from "./engine_create_objects.js";
|
|
181
188
|
|
|
@@ -7,11 +7,6 @@ import { Animation } from "../../engine-components/Animation.js";
|
|
|
7
7
|
import { Animator } from "../../engine-components/Animator.js";
|
|
8
8
|
import { AudioListener } from "../../engine-components/AudioListener.js";
|
|
9
9
|
import { AudioSource } from "../../engine-components/AudioSource.js";
|
|
10
|
-
import { Avatar_Brain_LookAt } from "../../engine-components/avatar/Avatar_Brain_LookAt.js";
|
|
11
|
-
import { Avatar_MouthShapes } from "../../engine-components/avatar/Avatar_MouthShapes.js";
|
|
12
|
-
import { Avatar_MustacheShake } from "../../engine-components/avatar/Avatar_MustacheShake.js";
|
|
13
|
-
import { AvatarBlink_Simple } from "../../engine-components/avatar/AvatarBlink_Simple.js";
|
|
14
|
-
import { AvatarEyeLook_Rotation } from "../../engine-components/avatar/AvatarEyeLook_Rotation.js";
|
|
15
10
|
import { AxesHelper } from "../../engine-components/AxesHelper.js";
|
|
16
11
|
import { BasicIKConstraint } from "../../engine-components/BasicIKConstraint.js";
|
|
17
12
|
import { BoxHelperComponent } from "../../engine-components/BoxHelperComponent.js";
|
|
@@ -106,11 +101,12 @@ import { TestRunner } from "../../engine-components/TestRunner.js";
|
|
|
106
101
|
import { TestSimulateUserData } from "../../engine-components/TestRunner.js";
|
|
107
102
|
import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector.js";
|
|
108
103
|
import { SignalReceiver } from "../../engine-components/timeline/SignalAsset.js";
|
|
109
|
-
import {
|
|
110
|
-
import {
|
|
111
|
-
import {
|
|
104
|
+
import { TimelineAnimationTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
105
|
+
import { TimelineAudioTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
106
|
+
import { TimelineMarkerTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
112
107
|
import { SignalTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
113
|
-
import {
|
|
108
|
+
import { TimelineActivationTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
109
|
+
import { TimelineControlTrack } from "../../engine-components/timeline/TimelineTracks.js";
|
|
114
110
|
import { TransformGizmo } from "../../engine-components/TransformGizmo.js";
|
|
115
111
|
import { BaseUIComponent } from "../../engine-components/ui/BaseUIComponent.js";
|
|
116
112
|
import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent.js";
|
|
@@ -167,11 +163,6 @@ export function initBuiltinTypes() {
|
|
|
167
163
|
TypeStore.add("Animator", Animator);
|
|
168
164
|
TypeStore.add("AudioListener", AudioListener);
|
|
169
165
|
TypeStore.add("AudioSource", AudioSource);
|
|
170
|
-
TypeStore.add("Avatar_Brain_LookAt", Avatar_Brain_LookAt);
|
|
171
|
-
TypeStore.add("Avatar_MouthShapes", Avatar_MouthShapes);
|
|
172
|
-
TypeStore.add("Avatar_MustacheShake", Avatar_MustacheShake);
|
|
173
|
-
TypeStore.add("AvatarBlink_Simple", AvatarBlink_Simple);
|
|
174
|
-
TypeStore.add("AvatarEyeLook_Rotation", AvatarEyeLook_Rotation);
|
|
175
166
|
TypeStore.add("AxesHelper", AxesHelper);
|
|
176
167
|
TypeStore.add("BasicIKConstraint", BasicIKConstraint);
|
|
177
168
|
TypeStore.add("BoxHelperComponent", BoxHelperComponent);
|
|
@@ -266,11 +257,12 @@ export function initBuiltinTypes() {
|
|
|
266
257
|
TypeStore.add("TestSimulateUserData", TestSimulateUserData);
|
|
267
258
|
TypeStore.add("PlayableDirector", PlayableDirector);
|
|
268
259
|
TypeStore.add("SignalReceiver", SignalReceiver);
|
|
269
|
-
TypeStore.add("
|
|
270
|
-
TypeStore.add("
|
|
271
|
-
TypeStore.add("
|
|
260
|
+
TypeStore.add("TimelineAnimationTrack", TimelineAnimationTrack);
|
|
261
|
+
TypeStore.add("TimelineAudioTrack", TimelineAudioTrack);
|
|
262
|
+
TypeStore.add("TimelineMarkerTrack", TimelineMarkerTrack);
|
|
272
263
|
TypeStore.add("SignalTrackHandler", SignalTrackHandler);
|
|
273
|
-
TypeStore.add("
|
|
264
|
+
TypeStore.add("TimelineActivationTrack", TimelineActivationTrack);
|
|
265
|
+
TypeStore.add("TimelineControlTrack", TimelineControlTrack);
|
|
274
266
|
TypeStore.add("TransformGizmo", TransformGizmo);
|
|
275
267
|
TypeStore.add("BaseUIComponent", BaseUIComponent);
|
|
276
268
|
TypeStore.add("UIRootComponent", UIRootComponent);
|
|
@@ -257,12 +257,23 @@ export function fitCamera(options?: FitCameraOptions): null | FitCameraReturnTyp
|
|
|
257
257
|
else {
|
|
258
258
|
direction.sub(camera.worldPosition);
|
|
259
259
|
}
|
|
260
|
-
if (centerCamera === "y")
|
|
261
|
-
|
|
260
|
+
if (centerCamera === "y") {
|
|
261
|
+
// Preserve the camera's current elevation angle when it's already above the center,
|
|
262
|
+
// but clamp to a minimum elevation to prevent the camera from ending up at or below
|
|
263
|
+
// the scene center (which causes a "looking up from below" effect).
|
|
264
|
+
// direction points FROM camera TO center, so negative Y = camera is above center.
|
|
265
|
+
const horizontalLen = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
|
|
266
|
+
if (horizontalLen > 0.0001) {
|
|
267
|
+
const minY = -horizontalLen * verticalOffset * 4;
|
|
268
|
+
if (direction.y > minY) direction.y = minY;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Camera is directly above/below center — pick a default slight angle from +Z
|
|
272
|
+
direction.set(0, -verticalOffset * 4, 1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
262
275
|
direction.normalize();
|
|
263
276
|
direction.multiplyScalar(distance);
|
|
264
|
-
if (centerCamera === "y")
|
|
265
|
-
direction.y += -verticalOffset * 4 * distance;
|
|
266
277
|
|
|
267
278
|
let cameraLocalPosition = center.clone().sub(direction);
|
|
268
279
|
if (options.cameraOffset) {
|
|
@@ -23,6 +23,7 @@ import { Application } from './engine_application.js';
|
|
|
23
23
|
import { AssetDatabase } from './engine_assetdatabase.js';
|
|
24
24
|
import { FocusRect, FocusRectSettings, updateCameraFocusRect } from './engine_camera.js';
|
|
25
25
|
import { VERSION } from './engine_constants.js';
|
|
26
|
+
import { EventBus } from './engine_context_eventbus.js';
|
|
26
27
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
|
27
28
|
import { WaitForPromise } from './engine_coroutine.js';
|
|
28
29
|
import { ObjectUtils } from "./engine_create_objects.js";
|
|
@@ -151,7 +152,7 @@ export function registerComponent(script: IComponent, context?: Context) {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
|
-
* The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
|
|
155
|
+
* The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
|
|
155
156
|
* It can be used to access the {@link Context.scene}, {@link Context.renderer}, {@link Context.mainCamera}, {@link Context.input}, {@link Context.physics}, {@link Context.time}, {@link Context.connection} (networking), and more.
|
|
156
157
|
*
|
|
157
158
|
* The context is automatically created when using the `<needle-engine>` web component.
|
|
@@ -522,19 +523,31 @@ export class Context implements IContext {
|
|
|
522
523
|
private _fallbackCamera: PerspectiveCamera | null = null;
|
|
523
524
|
|
|
524
525
|
/** access application state (e.g. if all audio should be muted) */
|
|
525
|
-
application: Application;
|
|
526
|
+
get application(): Application { return this._application; }
|
|
527
|
+
private _application!: Application;
|
|
526
528
|
/** access animation mixer used by components in the scene */
|
|
527
|
-
animations: AnimationsRegistry;
|
|
529
|
+
get animations(): AnimationsRegistry { return this._animations; }
|
|
530
|
+
private _animations!: AnimationsRegistry;
|
|
528
531
|
/** access timings (current frame number, deltaTime, timeScale, ...) */
|
|
529
|
-
time: Time;
|
|
532
|
+
get time(): Time { return this._time; }
|
|
533
|
+
private _time!: Time;
|
|
530
534
|
/** access input data (e.g. click or touch events) */
|
|
531
|
-
input: Input;
|
|
535
|
+
get input(): Input { return this._input; }
|
|
536
|
+
private _input!: Input;
|
|
532
537
|
/** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
|
|
533
|
-
physics: Physics;
|
|
538
|
+
get physics(): Physics { return this._physics; }
|
|
539
|
+
private _physics!: Physics;
|
|
534
540
|
/** access postprocessing effects stack. Add/remove effects and configure adaptive performance settings */
|
|
535
|
-
postprocessing: PostProcessing;
|
|
541
|
+
get postprocessing(): PostProcessing { return this._postprocessing; }
|
|
542
|
+
private _postprocessing!: PostProcessing;
|
|
536
543
|
/** access networking methods (use it to send or listen to messages or join a networking backend) */
|
|
537
|
-
connection: NetworkConnection;
|
|
544
|
+
get connection(): NetworkConnection { return this._connection; }
|
|
545
|
+
private _connection!: NetworkConnection;
|
|
546
|
+
/** context-level event bus for decoupled component communication
|
|
547
|
+
* @see {@link ContextEventMap} for known event types
|
|
548
|
+
*/
|
|
549
|
+
get events(): EventBus { return this._events; }
|
|
550
|
+
private _events = new EventBus();
|
|
538
551
|
/** @deprecated AssetDatabase is deprecated */
|
|
539
552
|
assets: AssetDatabase;
|
|
540
553
|
|
|
@@ -606,12 +619,12 @@ export class Context implements IContext {
|
|
|
606
619
|
else this.scene = new Scene();
|
|
607
620
|
if (args?.camera) this._mainCamera = args.camera;
|
|
608
621
|
|
|
609
|
-
this.
|
|
610
|
-
this.
|
|
611
|
-
this.
|
|
612
|
-
this.
|
|
613
|
-
this.
|
|
614
|
-
this.
|
|
622
|
+
this._application = new Application(this);
|
|
623
|
+
this._time = new Time();
|
|
624
|
+
this._input = new Input(this);
|
|
625
|
+
this._physics = new Physics(this);
|
|
626
|
+
this._postprocessing = new PostProcessing(this);
|
|
627
|
+
this._connection = new NetworkConnection(this);
|
|
615
628
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
616
629
|
this.assets = new AssetDatabase();
|
|
617
630
|
this.sceneLighting = new SceneLighting(this);
|
|
@@ -620,7 +633,7 @@ export class Context implements IContext {
|
|
|
620
633
|
this.players = new PlayerViewManager(this);
|
|
621
634
|
this.menu = new NeedleMenu(this);
|
|
622
635
|
this.lodsManager = new LODsManager(this);
|
|
623
|
-
this.
|
|
636
|
+
this._animations = new AnimationsRegistry(this);
|
|
624
637
|
this.accessibility = new AccessibilityManager(this);
|
|
625
638
|
|
|
626
639
|
|
|
@@ -863,12 +876,14 @@ export class Context implements IContext {
|
|
|
863
876
|
this.scene = new Scene();
|
|
864
877
|
this.addressables?.dispose();
|
|
865
878
|
this.lightmaps?.clear();
|
|
866
|
-
this.physics?.engine?.
|
|
879
|
+
this.physics?.engine?.dispose();
|
|
867
880
|
this.lodsManager.disable();
|
|
868
881
|
this.accessibility?.clear();
|
|
869
882
|
|
|
870
883
|
this._onBeforeRenderListeners.clear();
|
|
871
884
|
this._onAfterRenderListeners.clear();
|
|
885
|
+
this._events.clear();
|
|
886
|
+
this._events = new EventBus();
|
|
872
887
|
|
|
873
888
|
this.lights.length = 0;
|
|
874
889
|
|
|
@@ -912,6 +927,7 @@ export class Context implements IContext {
|
|
|
912
927
|
this.scene = null!;
|
|
913
928
|
this.renderer = null!;
|
|
914
929
|
this.input.dispose();
|
|
930
|
+
this.connection.dispose();
|
|
915
931
|
this.menu.onDestroy();
|
|
916
932
|
this.animations.onDestroy();
|
|
917
933
|
for (const cb of this._disposeCallbacks) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Object3D } from "three";
|
|
2
|
+
|
|
3
|
+
import type { IComponent } from "./engine_types.js";
|
|
4
|
+
|
|
5
|
+
/** Typed event map for {@link Context.events}.
|
|
6
|
+
* Known events get full autocomplete; custom events can be typed at the call site via generic parameter.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContextEventMap {
|
|
9
|
+
"scene-content-changed": {
|
|
10
|
+
/** The component that triggered the change (e.g. SceneSwitcher, DropListener) */
|
|
11
|
+
readonly source: IComponent;
|
|
12
|
+
/** The root object that was added/loaded */
|
|
13
|
+
readonly object: Object3D;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Options for {@link EventBus.on}. */
|
|
18
|
+
export interface EventBusListenerOptions {
|
|
19
|
+
/** If true the listener is automatically removed after the first invocation. */
|
|
20
|
+
once?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Typed event bus. Known {@link ContextEventMap} events get full autocomplete.
|
|
24
|
+
* Custom events can be typed at the call site via generic parameter.
|
|
25
|
+
* @example Known events
|
|
26
|
+
* ```ts
|
|
27
|
+
* context.events.on("scene-content-changed", e => e.object);
|
|
28
|
+
* ```
|
|
29
|
+
* @example Custom events — type at call site
|
|
30
|
+
* ```ts
|
|
31
|
+
* context.events.emit<{ pts: number }>("scored", { pts: 10 });
|
|
32
|
+
* context.events.on<{ pts: number }>("scored", e => e.pts);
|
|
33
|
+
* ```
|
|
34
|
+
* @example Once
|
|
35
|
+
* ```ts
|
|
36
|
+
* context.events.on("scene-content-changed", e => { ... }, { once: true });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class EventBus {
|
|
40
|
+
private _listeners = new Map<string, Function[]>();
|
|
41
|
+
|
|
42
|
+
/** Emit a known {@link ContextEventMap} event */
|
|
43
|
+
emit<K extends keyof ContextEventMap & string>(type: K, detail?: ContextEventMap[K]): void;
|
|
44
|
+
/** Emit a custom event with user-provided type */
|
|
45
|
+
emit<T>(type: string, detail?: T): void;
|
|
46
|
+
emit(type: string, detail?: unknown): void {
|
|
47
|
+
const arr = this._listeners.get(type);
|
|
48
|
+
if (arr) for (const cb of [...arr]) cb(detail);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Subscribe to a known {@link ContextEventMap} event. Returns an unsubscribe function. */
|
|
52
|
+
on<K extends keyof ContextEventMap & string>(type: K, callback: (args: ContextEventMap[K]) => void, options?: EventBusListenerOptions): () => void;
|
|
53
|
+
/** Subscribe to a custom event with user-provided type. Returns an unsubscribe function. */
|
|
54
|
+
on<T>(type: string, callback: (args: T) => void, options?: EventBusListenerOptions): () => void;
|
|
55
|
+
on(type: string, callback: Function, options?: EventBusListenerOptions): () => void {
|
|
56
|
+
let arr = this._listeners.get(type);
|
|
57
|
+
if (!arr) { arr = []; this._listeners.set(type, arr); }
|
|
58
|
+
const unsub = () => {
|
|
59
|
+
const i = arr.indexOf(wrapped);
|
|
60
|
+
if (i >= 0) arr.splice(i, 1);
|
|
61
|
+
};
|
|
62
|
+
const wrapped = options?.once
|
|
63
|
+
? (...args: unknown[]) => { unsub(); callback(...args); }
|
|
64
|
+
: callback;
|
|
65
|
+
arr.push(wrapped);
|
|
66
|
+
return unsub;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Remove all listeners. Called when the context is cleared or destroyed. */
|
|
70
|
+
clear(): void {
|
|
71
|
+
this._listeners.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/** A function that performs cleanup when called */
|
|
2
|
+
export type DisposeFn = () => void;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for objects that hold resources and can be disposed.
|
|
6
|
+
* Implement this interface on any object that needs deterministic cleanup.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* class MyResource implements IDisposable {
|
|
11
|
+
* dispose() { /* release resources *\/ }
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @category Utilities
|
|
16
|
+
* @group Lifecycle
|
|
17
|
+
*/
|
|
18
|
+
export interface IDisposable {
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if an object implements {@link IDisposable}.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* if (isDisposable(obj)) {
|
|
28
|
+
* obj.dispose(); // safe to call
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function isDisposable(value: unknown): value is IDisposable {
|
|
33
|
+
return value !== null
|
|
34
|
+
&& typeof value === "object"
|
|
35
|
+
&& "dispose" in (value as object)
|
|
36
|
+
&& typeof (value as IDisposable).dispose === "function";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @experimental
|
|
41
|
+
* Subscribe to a DOM event on any {@link EventTarget} (window, document, HTML elements, etc.)
|
|
42
|
+
* and return an {@link IDisposable} that removes the listener when disposed.
|
|
43
|
+
*
|
|
44
|
+
* Provides full TypeScript event type inference — the callback parameter
|
|
45
|
+
* is automatically typed based on the event name (e.g. `"resize"` → `UIEvent`,
|
|
46
|
+
* `"click"` → `MouseEvent`).
|
|
47
|
+
*
|
|
48
|
+
* Use with {@link DisposableStore.add} for automatic lifecycle cleanup in components.
|
|
49
|
+
*
|
|
50
|
+
* @param target The EventTarget to listen on (window, document, an element, etc.)
|
|
51
|
+
* @param type The event name (e.g. `"resize"`, `"click"`, `"keydown"`)
|
|
52
|
+
* @param listener The event handler callback
|
|
53
|
+
* @param options Optional addEventListener options (passive, capture, once, signal)
|
|
54
|
+
* @returns An {@link IDisposable} that removes the event listener when disposed
|
|
55
|
+
*
|
|
56
|
+
* @example Standalone usage
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { on } from "@needle-tools/engine";
|
|
59
|
+
*
|
|
60
|
+
* const sub = on(window, "resize", (ev) => {
|
|
61
|
+
* // ev is typed as UIEvent
|
|
62
|
+
* console.log("resized", ev.target);
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* // Later: clean up
|
|
66
|
+
* sub.dispose();
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @example With autoCleanup in a component
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { Behaviour, on } from "@needle-tools/engine";
|
|
72
|
+
*
|
|
73
|
+
* export class MyComponent extends Behaviour {
|
|
74
|
+
* onEnable() {
|
|
75
|
+
* this.autoCleanup(on(window, "resize", (ev) => { /* UIEvent *\/ }));
|
|
76
|
+
* this.autoCleanup(on(document, "keydown", (ev) => { /* KeyboardEvent *\/ }));
|
|
77
|
+
* this.autoCleanup(on(this.context.domElement, "click", (ev) => { /* MouseEvent *\/ }));
|
|
78
|
+
* }
|
|
79
|
+
* // All listeners removed automatically on disable!
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @category Utilities
|
|
84
|
+
* @group Lifecycle
|
|
85
|
+
*/
|
|
86
|
+
// #region on
|
|
87
|
+
export function on<K extends keyof WindowEventMap>(target: Window, type: K, listener: (ev: WindowEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
|
|
88
|
+
export function on<K extends keyof DocumentEventMap>(target: Document, type: K, listener: (ev: DocumentEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
|
|
89
|
+
export function on<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, listener: (ev: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions): IDisposable;
|
|
90
|
+
export function on(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): IDisposable;
|
|
91
|
+
export function on(target: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): IDisposable {
|
|
92
|
+
target.addEventListener(type, listener, options);
|
|
93
|
+
return {
|
|
94
|
+
dispose() {
|
|
95
|
+
target.removeEventListener(type, listener, options);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A store for managing disposable resources (event subscriptions, listeners, callbacks)
|
|
103
|
+
* that should be cleaned up together.
|
|
104
|
+
*
|
|
105
|
+
* DisposableStore collects disposables and disposes them all at once when
|
|
106
|
+
* {@link dispose} is called. After disposal, the store can be reused — new items
|
|
107
|
+
* can be added and a subsequent {@link dispose} call will clean those up.
|
|
108
|
+
*
|
|
109
|
+
* This is the same pattern used internally by VSCode for lifecycle-bound resource management.
|
|
110
|
+
*
|
|
111
|
+
* @example Basic usage
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { DisposableStore, on } from "@needle-tools/engine";
|
|
114
|
+
*
|
|
115
|
+
* const store = new DisposableStore();
|
|
116
|
+
*
|
|
117
|
+
* // Register a DOM event listener (typed!)
|
|
118
|
+
* store.add(on(window, "resize", (ev) => console.log(ev)));
|
|
119
|
+
*
|
|
120
|
+
* // Register the return value of EventList.on()
|
|
121
|
+
* store.add(myEventList.on(data => console.log(data)));
|
|
122
|
+
*
|
|
123
|
+
* // Register a raw cleanup function
|
|
124
|
+
* store.add(() => someSDK.off("event", handler));
|
|
125
|
+
*
|
|
126
|
+
* // Later: dispose everything at once
|
|
127
|
+
* store.dispose();
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example Use with Needle Engine components
|
|
131
|
+
* ```ts
|
|
132
|
+
* import { Behaviour, serializable, EventList, on } from "@needle-tools/engine";
|
|
133
|
+
*
|
|
134
|
+
* export class MyComponent extends Behaviour {
|
|
135
|
+
* @serializable(EventList)
|
|
136
|
+
* onClick?: EventList;
|
|
137
|
+
*
|
|
138
|
+
* onEnable() {
|
|
139
|
+
* // DOM events — fully typed
|
|
140
|
+
* this.autoCleanup(on(window, "resize", (ev) => this.onResize(ev)));
|
|
141
|
+
*
|
|
142
|
+
* // EventList — .on() returns a function, autoCleanup accepts it
|
|
143
|
+
* this.autoCleanup(this.onClick?.on(() => console.log("clicked!")));
|
|
144
|
+
* }
|
|
145
|
+
* // No onDisable needed — cleaned up automatically!
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @category Utilities
|
|
150
|
+
* @group Lifecycle
|
|
151
|
+
*/
|
|
152
|
+
// #region DisposableStore
|
|
153
|
+
export class DisposableStore implements IDisposable {
|
|
154
|
+
|
|
155
|
+
private _disposables: Array<DisposeFn> = [];
|
|
156
|
+
|
|
157
|
+
/** The number of registered disposables */
|
|
158
|
+
get size() { return this._disposables.length; }
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Register a disposable resource. Accepts:
|
|
162
|
+
* - An {@link IDisposable} object (has a `dispose()` method) — e.g. from {@link on}
|
|
163
|
+
* - A cleanup function (e.g. return value of `EventList.on()`)
|
|
164
|
+
* - `null` or `undefined` (safe no-op for conditional subscriptions)
|
|
165
|
+
*
|
|
166
|
+
* When {@link dispose} is called, all registered resources are cleaned up.
|
|
167
|
+
*
|
|
168
|
+
* @param disposable The resource to register for disposal
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const store = new DisposableStore();
|
|
173
|
+
*
|
|
174
|
+
* // IDisposable object from on()
|
|
175
|
+
* store.add(on(window, "resize", handler));
|
|
176
|
+
*
|
|
177
|
+
* // Function returned by EventList.on()
|
|
178
|
+
* store.add(myEvent.on(handler));
|
|
179
|
+
*
|
|
180
|
+
* // Raw cleanup function
|
|
181
|
+
* store.add(() => connection.close());
|
|
182
|
+
*
|
|
183
|
+
* // Conditional — safe with undefined
|
|
184
|
+
* store.add(this.maybeEvent?.on(handler));
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
add(disposable: IDisposable | DisposeFn | Function | null | undefined): void {
|
|
188
|
+
if (!disposable) return;
|
|
189
|
+
if (typeof disposable === "function") {
|
|
190
|
+
this._disposables.push(disposable as DisposeFn);
|
|
191
|
+
}
|
|
192
|
+
else if (typeof disposable === "object" && "dispose" in disposable) {
|
|
193
|
+
this._disposables.push(() => disposable.dispose());
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Dispose all registered resources. Each registered disposable is cleaned up,
|
|
199
|
+
* then the internal list is cleared. The store can be reused after disposal.
|
|
200
|
+
*
|
|
201
|
+
* Called automatically by the engine when a component's `onDisable` lifecycle fires.
|
|
202
|
+
*/
|
|
203
|
+
dispose(): void {
|
|
204
|
+
for (let i = this._disposables.length - 1; i >= 0; i--) {
|
|
205
|
+
try {
|
|
206
|
+
this._disposables[i]();
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
console.error("Error disposing resource", err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
this._disposables.length = 0;
|
|
213
|
+
}
|
|
214
|
+
}
|