@needle-tools/engine 5.1.0-canary.db0c38f → 5.1.0-canary.deec6e4

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 (106) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +34 -0
  3. package/components.needle.json +1 -1
  4. package/dist/{needle-engine.bundle-YnpzzOPL.min.js → needle-engine.bundle-1s2gOoKZ.min.js} +144 -144
  5. package/dist/{needle-engine.bundle-B29kieh0.js → needle-engine.bundle-CvtELXh0.js} +6650 -6584
  6. package/dist/{needle-engine.bundle-Dq0Ly8fW.umd.cjs → needle-engine.bundle-j4nGJXCs.umd.cjs} +138 -138
  7. package/dist/needle-engine.d.ts +101 -89
  8. package/dist/needle-engine.js +188 -186
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/lib/engine/api.d.ts +1 -1
  12. package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
  13. package/lib/engine/debug/debug_spatial_console.js +10 -7
  14. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  15. package/lib/engine/engine_addressables.d.ts +2 -0
  16. package/lib/engine/engine_addressables.js +6 -3
  17. package/lib/engine/engine_addressables.js.map +1 -1
  18. package/lib/engine/engine_context.d.ts +21 -20
  19. package/lib/engine/engine_context.js +25 -14
  20. package/lib/engine/engine_context.js.map +1 -1
  21. package/lib/engine/engine_init.js +15 -0
  22. package/lib/engine/engine_init.js.map +1 -1
  23. package/lib/engine/engine_license.d.ts +2 -0
  24. package/lib/engine/engine_license.js +14 -6
  25. package/lib/engine/engine_license.js.map +1 -1
  26. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  27. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  28. package/lib/engine/engine_pmrem.js +2 -2
  29. package/lib/engine/engine_pmrem.js.map +1 -1
  30. package/lib/engine/engine_scenedata.d.ts +13 -17
  31. package/lib/engine/engine_scenedata.js +56 -29
  32. package/lib/engine/engine_scenedata.js.map +1 -1
  33. package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
  34. package/lib/engine/engine_serialization_builtin_serializer.js +28 -41
  35. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  36. package/lib/engine/engine_ssr.d.ts +2 -0
  37. package/lib/engine/engine_ssr.js +20 -0
  38. package/lib/engine/engine_ssr.js.map +1 -1
  39. package/lib/engine/engine_types.d.ts +2 -0
  40. package/lib/engine/engine_types.js.map +1 -1
  41. package/lib/engine/webcomponents/jsx.d.ts +51 -0
  42. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  43. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -3
  44. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  45. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  46. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  47. package/lib/engine-components/AnimatorController.d.ts +2 -0
  48. package/lib/engine-components/AnimatorController.js +4 -1
  49. package/lib/engine-components/AnimatorController.js.map +1 -1
  50. package/lib/engine-components/Light.d.ts +6 -8
  51. package/lib/engine-components/Light.js +40 -27
  52. package/lib/engine-components/Light.js.map +1 -1
  53. package/lib/engine-components/ReflectionProbe.js +2 -0
  54. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  55. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
  56. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
  57. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  58. package/lib/needle-engine.d.ts +2 -0
  59. package/lib/needle-engine.js +2 -0
  60. package/lib/needle-engine.js.map +1 -1
  61. package/package.json +3 -2
  62. package/plugins/dts-generator/dts.codegen.js +255 -50
  63. package/plugins/dts-generator/dts.scan.js +37 -9
  64. package/plugins/dts-generator/dts.writer.js +1 -1
  65. package/plugins/dts-generator/glb.discovery.js +140 -23
  66. package/plugins/dts-generator/glb.extractor.js +48 -8
  67. package/plugins/dts-generator/glb.reader.js +80 -27
  68. package/plugins/dts-generator/index.js +1 -1
  69. package/plugins/types/needle-bindings.d.ts +25 -14
  70. package/plugins/types/userconfig.d.ts +12 -0
  71. package/plugins/vite/asap.js +1 -1
  72. package/plugins/vite/dependency-watcher.d.ts +2 -2
  73. package/plugins/vite/dependency-watcher.js +3 -4
  74. package/plugins/vite/drop.d.ts +2 -2
  75. package/plugins/vite/drop.js +3 -4
  76. package/plugins/vite/dts-generator.d.ts +2 -2
  77. package/plugins/vite/dts-generator.js +43 -9
  78. package/plugins/vite/index.d.ts +9 -3
  79. package/plugins/vite/index.js +23 -10
  80. package/plugins/vite/meta.js +4 -2
  81. package/plugins/vite/poster.d.ts +2 -2
  82. package/plugins/vite/poster.js +3 -5
  83. package/plugins/vite/reload.d.ts +2 -2
  84. package/plugins/vite/reload.js +22 -22
  85. package/src/engine/api.ts +1 -1
  86. package/src/engine/debug/debug_spatial_console.ts +10 -7
  87. package/src/engine/engine_addressables.ts +6 -3
  88. package/src/engine/engine_context.ts +34 -20
  89. package/src/engine/engine_init.ts +14 -0
  90. package/src/engine/engine_license.ts +12 -10
  91. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  92. package/src/engine/engine_pmrem.ts +3 -3
  93. package/src/engine/engine_scenedata.ts +53 -27
  94. package/src/engine/engine_serialization_builtin_serializer.ts +32 -43
  95. package/src/engine/engine_ssr.ts +29 -3
  96. package/src/engine/engine_types.ts +2 -0
  97. package/src/engine/webcomponents/jsx.d.ts +51 -0
  98. package/src/engine/webcomponents/logo-element.ts +1 -0
  99. package/src/engine/webcomponents/needle menu/needle-menu.ts +2 -1
  100. package/src/engine/webcomponents/needle-button.ts +1 -0
  101. package/src/engine/webcomponents/needle-engine.ts +1 -0
  102. package/src/engine-components/AnimatorController.ts +4 -1
  103. package/src/engine-components/Light.ts +40 -26
  104. package/src/engine-components/ReflectionProbe.ts +2 -0
  105. package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
  106. package/src/needle-engine.ts +3 -0
@@ -16,12 +16,24 @@
16
16
  * needleDtsGenerator(command, needleConfig, userSettings)
17
17
  */
18
18
 
19
- import { join, resolve } from 'path';
20
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
19
+ import { join, resolve, dirname } from 'path';
20
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync } from 'fs';
21
21
  import { tryLoadProjectConfig } from './config.js';
22
22
  import { generateBindingsDts } from '../dts-generator/index.js';
23
23
  import { needleLog } from './logging.js';
24
24
 
25
+ // Two dirs up from plugins/vite/ → package root.
26
+ // Use fileURLToPath so %7E and other URL-encoded characters in the path are decoded correctly.
27
+ // realpathSync follows symlinks so the path is stable even in npm link / monorepo setups.
28
+ import { fileURLToPath } from 'url';
29
+ let _packageRoot = /** @type {string | null} */ (null);
30
+ try {
31
+ _packageRoot = realpathSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..'));
32
+ } catch (_e) {
33
+ // If we can't resolve the package root (e.g. unusual install layout), the plugin
34
+ // will silently skip generation rather than crashing the dev server.
35
+ }
36
+
25
37
  /**
26
38
  * Ensure `.vscode/settings.json` references the generated `needle-html-data.json`
27
39
  * so VS Code provides `data-bind-needle` completions in HTML files automatically.
@@ -60,9 +72,14 @@ function ensureVscodeHtmlCustomData(projectRoot, htmlDataPath) {
60
72
  * @param {"build" | "serve"} _command Vite command (unused — runs in both modes)
61
73
  * @param {import('../types/needleConfig').needleMeta | null | undefined} _config
62
74
  * @param {import('../types').userSettings} [_userSettings]
63
- * @returns {import('vite').Plugin}
75
+ * @returns {import('vite').Plugin | null}
64
76
  */
65
77
  export function needleDtsGenerator(_command, _config, _userSettings) {
78
+
79
+ if(_userSettings?.dts?.enabled === false) {
80
+ return null;
81
+ }
82
+
66
83
  let projectRoot = process.cwd();
67
84
 
68
85
  function resolveCodegenDir() {
@@ -73,7 +90,11 @@ export function needleDtsGenerator(_command, _config, _userSettings) {
73
90
  }
74
91
 
75
92
  function resolveOutputPath() {
76
- return join(resolveCodegenDir(), "needle-bindings.d.ts");
93
+ // Write to a .needle dotfolder at the package root.
94
+ // Not in the `files` allowlist → never npm-published.
95
+ // ../../.needle/ from plugins/types/ resolves correctly for both
96
+ // symlinked (js/package~/) and published (node_modules/@needle-tools/engine/) layouts.
97
+ return join(/** @type {string} */ (_packageRoot), ".needle", "generated", "needle-bindings.gen.d.ts");
77
98
  }
78
99
 
79
100
  function resolveAssetsDir() {
@@ -86,14 +107,24 @@ export function needleDtsGenerator(_command, _config, _userSettings) {
86
107
  /** @type {import('vite').ViteDevServer | undefined} */
87
108
  let devServer;
88
109
 
89
- async function run() {
110
+ /** @type {Promise<void> | null} */
111
+ let _runInFlight = null;
112
+
113
+ function run() {
114
+ if (_runInFlight) return _runInFlight;
115
+ _runInFlight = _doRun().finally(() => { _runInFlight = null; });
116
+ return _runInFlight;
117
+ }
118
+
119
+ async function _doRun() {
90
120
  try {
121
+ if (!_packageRoot) return;
91
122
  const assetsDir = resolveAssetsDir();
92
123
  const outputPath = resolveOutputPath();
93
124
  const codegenDir = resolveCodegenDir();
94
125
  const count = await generateBindingsDts({ assetsDir, outputPath, projectRoot, codegenDir });
95
- // Always register HTML custom data the file is written on every run
96
- const htmlDataPath = join(codegenDir, "needle-html-data.json");
126
+ // HTML custom data sits next to the generated dts in .needle/generated/
127
+ const htmlDataPath = join(/** @type {string} */ (_packageRoot), ".needle", "generated", "needle-html-data.json");
97
128
  ensureVscodeHtmlCustomData(projectRoot, htmlDataPath);
98
129
  if (count !== false) {
99
130
  needleLog("needle:dts-generator", `${count} binding(s) → ${outputPath.replace(process.cwd(), ".")}`);
@@ -102,7 +133,7 @@ export function needleDtsGenerator(_command, _config, _userSettings) {
102
133
  hot.send({ type: "full-reload", path: "*" });
103
134
  }
104
135
  } else {
105
- needleLog("needle:dts-generator", `up-to-date`);
136
+ needleLog("needle:dts-generator", `up-to-date → ${outputPath.replace(process.cwd(), ".")}`);
106
137
  }
107
138
  } catch (err) {
108
139
  needleLog("needle:dts-generator", "Failed: " + (/** @type {any} */ (err)?.message ?? err));
@@ -118,7 +149,10 @@ export function needleDtsGenerator(_command, _config, _userSettings) {
118
149
  },
119
150
 
120
151
  buildStart() {
121
- return run();
152
+ // In serve mode, the configureServer post-hook runs instead.
153
+ // Only run here for actual builds (no devServer).
154
+ if (!devServer) return run();
155
+ return undefined;
122
156
  },
123
157
 
124
158
  /** @param {import('vite').ViteDevServer} server */
@@ -19,12 +19,18 @@
19
19
  * ],
20
20
  * }
21
21
  * ```
22
- * @param {"build" | "serve"} command
23
- * @param {import('../types/needleConfig').needleMeta | null | undefined} config
22
+ * When used without arguments (e.g. in a SvelteKit `defineConfig({})` plain object),
23
+ * the command is resolved automatically from Vite's `configResolved` hook.
24
+ * ```js
25
+ * // SvelteKit — no command needed:
26
+ * export default defineConfig({ plugins: [sveltekit(), needlePlugins()] });
27
+ * ```
28
+ * @param {"build" | "serve" | undefined} [command]
29
+ * @param {import('../types/needleConfig').needleMeta | null | undefined} [config]
24
30
  * @param {import('../types/index.js').userSettings} [userSettings]
25
31
  * @returns {Promise<import('vite').Plugin[]>}
26
32
  */
27
- export function needlePlugins(command: "build" | "serve", config?: import("../types/needleConfig").needleMeta | null | undefined, userSettings?: import("../types/index.js").userSettings): Promise<import("vite").Plugin[]>;
33
+ export function needlePlugins(command?: "build" | "serve" | undefined, config?: import("../types/needleConfig").needleMeta | null | undefined, userSettings?: import("../types/index.js").userSettings): Promise<import("vite").Plugin[]>;
28
34
  export { needleAI } from "./ai.js";
29
35
  export { needleDtsGenerator } from "./dts-generator.js";
30
36
  export { needleAsap } from "./asap.js";
@@ -116,12 +116,18 @@ const defaultUserSettings = {
116
116
  * ],
117
117
  * }
118
118
  * ```
119
- * @param {"build" | "serve"} command
120
- * @param {import('../types/needleConfig').needleMeta | null | undefined} config
119
+ * When used without arguments (e.g. in a SvelteKit `defineConfig({})` plain object),
120
+ * the command is resolved automatically from Vite's `configResolved` hook.
121
+ * ```js
122
+ * // SvelteKit — no command needed:
123
+ * export default defineConfig({ plugins: [sveltekit(), needlePlugins()] });
124
+ * ```
125
+ * @param {"build" | "serve" | undefined} [command]
126
+ * @param {import('../types/needleConfig').needleMeta | null | undefined} [config]
121
127
  * @param {import('../types/index.js').userSettings} [userSettings]
122
128
  * @returns {Promise<import('vite').Plugin[]>}
123
129
  */
124
- export async function needlePlugins(command, config = undefined, userSettings = {}) {
130
+ export async function needlePlugins(command = undefined, config = undefined, userSettings = {}) {
125
131
 
126
132
  if (!config) config = {}
127
133
 
@@ -168,14 +174,21 @@ export async function needlePlugins(command, config = undefined, userSettings =
168
174
  array.push(await editorConnection(command, config, userSettings, array));
169
175
  array.push(needleDependencyWatcher(command, config, userSettings));
170
176
 
171
- // Ensure the process exits on SIGINT (Ctrl+C) since plugin timers/sockets can keep the event loop alive
172
- if (command === "serve") {
177
+ // Ensure the process exits on SIGINT (Ctrl+C) since plugin timers/sockets can keep the event loop alive.
178
+ // If command is unknown at call time (e.g. SvelteKit plain defineConfig), defer to configResolved.
179
+ const registerSigint = () => {
173
180
  process.on('SIGINT', () => {
174
- // Wait a moment to allow plugins to clean up if needed, then exit
175
- setTimeout(() => {
176
- console.debug('\nGoodbye!');
177
- process.exit();
178
- }, 1000);
181
+ setTimeout(() => { console.debug('\nGoodbye!'); process.exit(); }, 1000);
182
+ });
183
+ };
184
+ if (command === "serve") {
185
+ registerSigint();
186
+ } else if (command === undefined) {
187
+ array.push({
188
+ name: "needle:sigint",
189
+ configResolved(config) {
190
+ if (config.command === "serve") registerSigint();
191
+ }
179
192
  });
180
193
  }
181
194
 
@@ -13,8 +13,7 @@ import { needleGreenBold, needleLog } from './logging.js';
13
13
  */
14
14
  export function needleMeta(command, config, userSettings) {
15
15
 
16
- // we can check if this is a build
17
- const isBuild = command === 'build';
16
+ let isBuild = command === 'build';
18
17
 
19
18
  async function updateConfig() {
20
19
  config = await loadConfig();
@@ -25,6 +24,9 @@ export function needleMeta(command, config, userSettings) {
25
24
  return {
26
25
  // replace meta tags
27
26
  name: 'needle:meta',
27
+ configResolved(resolvedConfig) {
28
+ isBuild = resolvedConfig.command === 'build';
29
+ },
28
30
  transformIndexHtml: {
29
31
  order: 'pre',
30
32
  handler(/** @type {string} */ html, /** @type {unknown} */ _ctx) {
@@ -1,8 +1,8 @@
1
1
  export function getPosterPath(): string;
2
2
  /**
3
- * @param {"build" | "serve"} command
3
+ * @param {"build" | "serve" | undefined} _command
4
4
  * @param {import('../types/needleConfig').needleMeta | null | undefined} config
5
5
  * @param {import('../types').userSettings} userSettings
6
6
  * @returns {import('vite').Plugin | undefined}
7
7
  */
8
- export function needlePoster(command: "build" | "serve", config: import("../types/needleConfig").needleMeta | null | undefined, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
8
+ export function needlePoster(_command: "build" | "serve" | undefined, config: import("../types/needleConfig").needleMeta | null | undefined, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
@@ -10,19 +10,17 @@ export function getPosterPath() {
10
10
  }
11
11
 
12
12
  /**
13
- * @param {"build" | "serve"} command
13
+ * @param {"build" | "serve" | undefined} _command
14
14
  * @param {import('../types/needleConfig').needleMeta | null | undefined} config
15
15
  * @param {import('../types').userSettings} userSettings
16
16
  * @returns {import('vite').Plugin | undefined}
17
17
  */
18
- export function needlePoster(command, config, userSettings) {
19
- // only relevant for local development
20
- if (command === 'build') return [];
21
-
18
+ export function needlePoster(_command, config, userSettings) {
22
19
  if (userSettings.noPoster) return;
23
20
 
24
21
  return {
25
22
  name: 'needle:poster',
23
+ apply: 'serve',
26
24
  configureServer(server) {
27
25
  const hot = server.hot ?? server.ws;
28
26
  hot.on('needle:screenshot', async (data, client) => {
@@ -1,7 +1,7 @@
1
1
  /**
2
- * @param {"build" | "serve"} command
2
+ * @param {"build" | "serve" | undefined} _command
3
3
  * @param {import('../types/needleConfig').needleMeta | null} config
4
4
  * @param {import('../types').userSettings} userSettings
5
5
  * @returns {import('vite').Plugin | undefined}
6
6
  */
7
- export function needleReload(command: "build" | "serve", config: import("../types/needleConfig").needleMeta | null, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
7
+ export function needleReload(_command: "build" | "serve" | undefined, config: import("../types/needleConfig").needleMeta | null, userSettings: import("../types").userSettings): import("vite").Plugin | undefined;
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, statSync } from 'fs';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { needleLog } from './logging.js';
9
9
 
10
+ const pluginName = "needle-reload";
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
12
13
 
@@ -15,14 +16,12 @@ const filesUsingHotReload = new Set();
15
16
  let assetsDirectory = "";
16
17
 
17
18
  /**
18
- * @param {"build" | "serve"} command
19
+ * @param {"build" | "serve" | undefined} _command
19
20
  * @param {import('../types/needleConfig').needleMeta | null} config
20
21
  * @param {import('../types').userSettings} userSettings
21
22
  * @returns {import('vite').Plugin | undefined}
22
23
  */
23
- export function needleReload(command, config, userSettings) {
24
- if (command === "build") return undefined;
25
-
24
+ export function needleReload(_command, config, userSettings) {
26
25
  if (userSettings?.noReload === true) return undefined;
27
26
 
28
27
 
@@ -41,7 +40,7 @@ export function needleReload(command, config, userSettings) {
41
40
  const buildDirectory = projectConfig?.buildDirectory?.length ? process.cwd().replaceAll("\\", "/") + "/" + projectConfig?.buildDirectory : "";
42
41
  if (buildDirectory?.length) {
43
42
  const relativeBuildDirectory = path.relative(process.cwd(), buildDirectory).replaceAll("\\", "/") || ".";
44
- setTimeout(() => needleLog("needle-reload", "Build directory: " + relativeBuildDirectory), 100);
43
+ setTimeout(() => needleLog(pluginName, "Build directory: " + relativeBuildDirectory), 100);
45
44
  }
46
45
 
47
46
  // These ignore patterns will be injected into user config to better control vite reloading
@@ -51,6 +50,7 @@ export function needleReload(command, config, userSettings) {
51
50
 
52
51
  return {
53
52
  name: 'needle:reload',
53
+ apply: 'serve',
54
54
  /** @param {import('vite').UserConfig} config */
55
55
  config(config) {
56
56
  if (!config.server) config.server = { watch: { ignored: [] } };
@@ -60,7 +60,7 @@ export function needleReload(command, config, userSettings) {
60
60
  // @ts-ignore - watch.ignored is guaranteed to be string[] by the guards above
61
61
  config.server.watch.ignored.push(pattern);
62
62
  if (userSettings?.debug === true)
63
- setTimeout(() => needleLog("needle-reload", "Updated server ignore patterns: " + JSON.stringify(config.server?.watch?.ignored)), 100);
63
+ setTimeout(() => needleLog(pluginName, "Updated server ignore patterns: " + JSON.stringify(config.server?.watch?.ignored)), 100);
64
64
  },
65
65
  /** @param {import('vite').HmrContext & {buildDirectory?: string}} args */
66
66
  handleHotUpdate(args) {
@@ -119,7 +119,7 @@ function getHot(server) {
119
119
 
120
120
  /** @param {import('vite').ViteDevServer} server @param {string} [file] */
121
121
  function notifyClientWillReload(server, file) {
122
- console.log("Send reload notification");
122
+ needleLog(pluginName, "Send reload notification");
123
123
  getHot(server).send('needle:reload', { type: 'will-reload', file: file });
124
124
  }
125
125
 
@@ -135,7 +135,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
135
135
 
136
136
  // Dont reload the whole server when a file that is using hot reload changes
137
137
  if (filesUsingHotReload.has(file)) {
138
- console.log("File is using hot reload: " + file);
138
+ needleLog(pluginName, "File is using hot reload: " + file);
139
139
  return null;
140
140
  }
141
141
 
@@ -154,7 +154,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
154
154
  // instead of relying on the vite server watch ignore array
155
155
  // we could here also match paths that we know we dont want to track
156
156
  if (ignorePatterns.length > 0 && ignoreRegex.test(file)) {
157
- console.log("Ignore change in file: " + getFileNameLog(file));
157
+ needleLog(pluginName, "Ignore change in file: " + getFileNameLog(file));
158
158
  return [];
159
159
  }
160
160
 
@@ -164,7 +164,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
164
164
  if (buildDirectory?.length) {
165
165
  const dir = path.dirname(file).replaceAll("\\", "/");
166
166
  if (dir.startsWith(buildDirectory)) {
167
- console.log("Ignore change in build directory: " + getFileNameLog(file));
167
+ needleLog(pluginName, "Ignore change in build directory: " + getFileNameLog(file));
168
168
  return [];
169
169
  }
170
170
  }
@@ -174,18 +174,18 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
174
174
  // meaning if its the first export after the server starts those will not trigger a reload
175
175
  // Ignore d.ts
176
176
  const shouldCheckIfContentChanged = file.includes("/codegen/") || file.includes("/generated/") || file.endsWith("gen.js");// || file.endsWith(".glb") || file.endsWith(".gltf") || file.endsWith(".bin");
177
- if (shouldCheckIfContentChanged && !file.endsWith(".d.ts") && !file.endsWith("needle-html-data.json")) {
177
+ if (!file.includes("/.svelte-kit/") && !file.endsWith(".d.ts") && !file.endsWith("needle-html-data.json") && shouldCheckIfContentChanged) {
178
178
  if (reloadIsScheduled) {
179
179
  return [];
180
180
  }
181
181
  if (await testIfFileContentChanged(file, read) === false) {
182
- console.log("File content didnt change: " + getFileNameLog(file));
182
+ needleLog(pluginName, "File content didnt change: " + getFileNameLog(file));
183
183
  return [];
184
184
  }
185
185
  }
186
186
 
187
187
  // these are known file types we export from integrations
188
- const knownExportFileTypes = [ ".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm" ];
188
+ const knownExportFileTypes = [".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm"];
189
189
  if (!knownExportFileTypes.some((type) => file.endsWith(type)))
190
190
  return null;
191
191
 
@@ -209,7 +209,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
209
209
  }
210
210
  }
211
211
 
212
- console.log("> Detected file change: ", getFileNameLog(file) + " (" + ((fileSize / (1024 * 1024)).toFixed(1)) + " MB)");
212
+ needleLog(pluginName, "> Detected file change: " + getFileNameLog(file) + " (" + ((fileSize / (1024 * 1024)).toFixed(1)) + " MB)");
213
213
  notifyClientWillReload(server);
214
214
  scheduleReload(server);
215
215
  return [];
@@ -224,7 +224,7 @@ async function scheduleReload(server, level = 0) {
224
224
  const lockFile = path.join(process.cwd(), lockFileName);
225
225
  if (existsSync(lockFile)) {
226
226
  if (level === 0)
227
- console.log("Lock file exists, waiting for export to finish...");
227
+ needleLog(pluginName, "Lock file exists, waiting for export to finish...");
228
228
  setTimeout(() => scheduleReload(server, level += 1), 300);
229
229
  return;
230
230
  }
@@ -235,13 +235,13 @@ async function scheduleReload(server, level = 0) {
235
235
  if (timeDiff < 1000) {
236
236
  // Sometimes file changes happen immediately after triggering a reload
237
237
  // we dont want to reload again in that case
238
- console.log("Ignoring reload, last reload was too recent", timeDiff);
238
+ needleLog(pluginName, "Ignoring reload, last reload was too recent" + timeDiff + "ms ago");
239
239
  return;
240
240
  }
241
241
 
242
242
  lastReloadTime = Date.now();
243
243
  const readableTime = new Date(lastReloadTime).toLocaleTimeString();
244
- console.log("< Reloading... " + readableTime)
244
+ needleLog(pluginName, "< Reloading... " + readableTime);
245
245
  getHot(server).send({
246
246
  type: 'full-reload',
247
247
  path: '*'
@@ -300,10 +300,10 @@ function removeVersionQueryArgument(content) {
300
300
  function insertScriptRegisterHotReloadCode(src, filePath) {
301
301
 
302
302
  // We only want to inject the hot reload code in the needle-engine root file
303
- if(!filePath.includes("/src/needle-engine.ts")) {
303
+ if (!filePath.includes("/src/needle-engine.ts")) {
304
304
  return src;
305
305
  }
306
- console.log("[Needle HMR] Hot reload is enabled");
306
+ needleLog(pluginName, "[Needle HMR] Hot reload is enabled");
307
307
  // this code let's the engine know that we are in hot reload mode
308
308
  const code = `
309
309
  globalThis.NEEDLE_HOT_RELOAD_ENABLED = true;
@@ -339,12 +339,12 @@ function insertScriptHotReloadCode(src, filePath) {
339
339
  return undefined;
340
340
  // make import path from engine package
341
341
  const fullPathToHotReload = process.cwd() + "/node_modules/@needle-tools/engine/src/engine/engine_hot_reload.ts";
342
- // console.log(fullPathToHotReload);
342
+ // needleLog(pluginName, fullPathToHotReload);
343
343
  const fileDirectory = path.dirname(filePath);
344
- // console.log("DIR", fileDirectory)
344
+ // needleLog(pluginName, "DIR", fileDirectory)
345
345
  const relativePath = path.relative(fileDirectory, fullPathToHotReload);
346
346
  importPath = relativePath.replace(/\\/g, "/");
347
- // console.log("importPath: ", importPath);
347
+ // needleLog(pluginName, "importPath: ", importPath);
348
348
  }
349
349
 
350
350
  // console.log(importPath, ">", filePath);
package/src/engine/api.ts CHANGED
@@ -450,4 +450,4 @@ export * from "./xr/api.js"
450
450
  * import type { SceneData } from "@needle-tools/engine";
451
451
  * type OrbitSettings = SceneData["Camera"]["OrbitControls"];
452
452
  */
453
- export type { SceneData } from "needle:bindings";
453
+ export type { SceneData } from "needle-bindings";
@@ -12,13 +12,16 @@ import { onError } from "./debug_overlay.js";
12
12
 
13
13
  let _isActive = false;
14
14
 
15
- // enable the spatial console if we receive an error while in dev session and in XR
16
- onError((...args: any[]) => {
17
- if (isDevEnvironment() && ContextRegistry.Current?.isInXR) {
18
- enableSpatialConsole(true);
19
- onLog("error", ...args);
20
- }
21
- })
15
+ /** @internal */
16
+ export function initSpatialConsole() {
17
+ // enable the spatial console if we receive an error while in dev session and in XR
18
+ onError((...args: any[]) => {
19
+ if (isDevEnvironment() && ContextRegistry.Current?.isInXR) {
20
+ enableSpatialConsole(true);
21
+ onLog("error", ...args);
22
+ }
23
+ });
24
+ }
22
25
 
23
26
 
24
27
  /** Enable a spatial debug console that follows the camera */
@@ -567,7 +567,6 @@ class AddressableSerializer extends TypeSerializer {
567
567
  }
568
568
 
569
569
  }
570
- new AddressableSerializer();
571
570
 
572
571
 
573
572
 
@@ -703,7 +702,6 @@ export class ImageReferenceSerializer extends TypeSerializer {
703
702
  return undefined;
704
703
  }
705
704
  }
706
- new ImageReferenceSerializer();
707
705
 
708
706
 
709
707
 
@@ -772,4 +770,9 @@ export class FileReferenceSerializer extends TypeSerializer {
772
770
  return undefined;
773
771
  }
774
772
  }
775
- new FileReferenceSerializer();
773
+ /** @internal */
774
+ export function initAddressableSerializers() {
775
+ new AddressableSerializer();
776
+ new ImageReferenceSerializer();
777
+ new FileReferenceSerializer();
778
+ }
@@ -38,7 +38,7 @@ import { Physics } from './engine_physics.js';
38
38
  import { PlayerViewManager } from './engine_playerview.js';
39
39
  import { RendererData as SceneLighting } from './engine_scenelighting.js';
40
40
  import { getSceneData } from './engine_scenedata.js';
41
- import type { SceneData } from 'needle:bindings';
41
+ import type { SceneData } from 'needle-bindings';
42
42
  import { getTempColor, logHierarchy } from './engine_three_utils.js';
43
43
  import { Time } from './engine_time.js';
44
44
  import { patchTonemapping } from './engine_tonemapping.js';
@@ -478,13 +478,14 @@ export class Context implements IContext {
478
478
  * Use `setCurrentCamera` for updating the main camera.
479
479
  */
480
480
  /**
481
- * Typed proxy providing direct access to scene components by node and component name.
482
- * Types are auto-generated from your GLB assets by the `needle:dts-generator` Vite plugin
483
- * and written to `src/generated/needle-bindings.d.ts` on every dev-server start and GLB change.
481
+ * Access your scene's full hierarchy, objects, and components directly by name with full autocomplete.
482
+ * Types are generated automatically from your GLB files when the dev server starts.
484
483
  *
485
- * Each property access traverses the live scene graph on demand — no caching, always fresh.
486
- * This is a convenience shorthand for `getComponent` lookups; the same result can be
487
- * achieved manually via `scene.getObjectByName(name)` + `getComponent(node, Type)`.
484
+ * You can store references to objects or components for later use.
485
+ * Note that the scene hierarchy can change at runtime (e.g. when objects are added, removed, or re-parented),
486
+ * in which case stored references may become stale.
487
+ *
488
+ * @experimental This API may change in future versions.
488
489
  *
489
490
  * @example
490
491
  * // Toggle auto-rotate on the orbit camera
@@ -493,12 +494,6 @@ export class Context implements IContext {
493
494
  * @example
494
495
  * // Change the background color
495
496
  * ctx.sceneData.Camera.Camera.backgroundColor = new RGBAColor(1, 0, 0, 1);
496
- *
497
- * @example
498
- * // Equivalent manual approach (without sceneData)
499
- * const node = ctx.scene.getObjectByName("Camera");
500
- * const orbit = getComponent(node, OrbitControls);
501
- * if (orbit) orbit.autoRotate = true;
502
497
  */
503
498
  get sceneData(): SceneData {
504
499
  return getSceneData(this);
@@ -542,15 +537,32 @@ export class Context implements IContext {
542
537
  connection: NetworkConnection;
543
538
  /** @deprecated AssetDatabase is deprecated */
544
539
  assets: AssetDatabase;
545
- /** The main light in the scene */
546
- mainLight: ILight | null = null;
540
+
541
+ /** All registered lights in the scene, maintained by the Light component.
542
+ * @see mainLight for accessing the main directional light in the scene
543
+ */
544
+ readonly lights = new Array<ILight>();
545
+
546
+ /** The brightest registered directional light, or null if none are registered
547
+ * @see lights
548
+ */
549
+ get mainLight(): ILight | null {
550
+ let best: ILight | null = null;
551
+ for (const light of this.lights) {
552
+ if (light.type !== "directional") continue;
553
+ if (!best || light.intensity > best.intensity) best = light;
554
+ }
555
+ return best;
556
+ }
557
+
547
558
  /** @deprecated Use sceneLighting */
548
- get rendererData() { return this.sceneLighting }
559
+ private get rendererData() { return this.sceneLighting }
560
+
549
561
  /** Access the scene lighting manager to control lighting settings in the context */
550
- sceneLighting: SceneLighting;
551
- addressables: Addressables;
552
- lightmaps: ILightDataRegistry;
553
- players: PlayerViewManager;
562
+ readonly sceneLighting: SceneLighting;
563
+ readonly addressables: Addressables;
564
+ readonly lightmaps: ILightDataRegistry;
565
+ readonly players: PlayerViewManager;
554
566
 
555
567
  /** Access the LODs manager to control LOD behavior in the context */
556
568
  readonly lodsManager: LODsManager;
@@ -858,6 +870,8 @@ export class Context implements IContext {
858
870
  this._onBeforeRenderListeners.clear();
859
871
  this._onAfterRenderListeners.clear();
860
872
 
873
+ this.lights.length = 0;
874
+
861
875
  if (!this.isManagedExternally) {
862
876
  if (this.renderer) {
863
877
  this.renderer.renderLists.dispose();
@@ -1,18 +1,25 @@
1
+ import { initAnimatorControllerSerializer } from "../engine-components/AnimatorController.js";
1
2
  import { initAnimationAutoplay } from "../engine-components/AnimationUtilsAutoplay.js";
2
3
  import { initCameraUtils } from "../engine-components/CameraUtils.js";
4
+ import { initVolumeParameterSerializer } from "../engine-components/postprocessing/VolumeParameter.js";
3
5
  import { initSceneSwitcherAttributes } from "../engine-components/SceneSwitcher.js";
4
6
  import { initSkyboxAttributes } from "../engine-components/Skybox.js";
7
+ import { initAddressableSerializers } from "./engine_addressables.js";
5
8
  import { initBuiltinTypes } from "./codegen/register_types.js";
6
9
  import { ensureAudioContextIsResumed } from "./engine_audio.js";
7
10
  import { initNeedleLoader } from "./engine_loaders.js";
11
+ import { initBuiltinSerializers } from "./engine_serialization_builtin_serializer.js";
8
12
  import { initShims } from "./engine_shims.js";
9
13
  import { patchTonemapping } from "./engine_tonemapping.js";
10
14
  import { initCameraExtensions } from "./js-extensions/Camera.js";
11
15
  import { patchLayers } from "./js-extensions/Layers.js";
12
16
  import { initObject3DExtensions } from "./js-extensions/Object3D.js";
13
17
  import { initVectorExtensions } from "./js-extensions/Vector.js";
18
+ import { SSR } from "./engine_ssr.js";
19
+ import { initLicense } from "./engine_license.js";
14
20
  import { initWebComponents } from "./webcomponents/init.js";
15
21
  import { initPhysics } from "./engine_physics_rapier.js";
22
+ import { initSpatialConsole } from "./debug/debug_spatial_console.js";
16
23
  import { initXR } from "./xr/init.js";
17
24
 
18
25
  let initialized = false;
@@ -28,10 +35,15 @@ let initialized = false;
28
35
  */
29
36
  export function initEngine() {
30
37
  if (initialized) return;
38
+ if (SSR) return;
31
39
  initialized = true;
32
40
 
33
41
  initWebComponents();
34
42
  initShims();
43
+ initBuiltinSerializers();
44
+ initAddressableSerializers();
45
+ initAnimatorControllerSerializer();
46
+ initVolumeParameterSerializer();
35
47
  patchTonemapping();
36
48
  patchLayers();
37
49
  initCameraExtensions();
@@ -46,4 +58,6 @@ export function initEngine() {
46
58
  initSceneSwitcherAttributes();
47
59
  initPhysics();
48
60
  initXR();
61
+ initSpatialConsole();
62
+ initLicense();
49
63
  }