@needle-tools/engine 2.67.8-pre → 2.67.9-pre

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.
@@ -1,388 +1,396 @@
1
- import * as utils from "./engine_generic_utils";
2
- import * as constants from "./engine_constants";
3
- import { getParam } from './engine_utils';
4
- import { CubeCamera, Object3D, WebGLCubeRenderTarget } from 'three';
5
- import { IComponent, IContext } from './engine_types';
6
- import { isActiveSelf } from './engine_gameobject';
7
- import { ContextRegistry } from "./engine_context_registry";
8
-
9
- const debug = getParam("debugnewscripts");
10
- const debugHierarchy = getParam("debughierarchy");
11
-
12
- // if some other script adds new scripts in onEnable or awake
13
- // the original array should be cleared before processing it
14
- // so we use this copy buffer
15
- const new_scripts_buffer: any[] = [];
16
-
17
- export function processNewScripts(context: IContext) {
18
- if (context.new_scripts.length <= 0) return;
19
- if (debug)
20
- console.log("Register new components", context.new_scripts.length, [...context.new_scripts], context.alias ? ("element: " + context.alias) : context["hash"], context);
21
-
22
- // if(new_scripts_post_setup_callbacks.length > 0) console.log(new_scripts_post_setup_callbacks);
23
- if (context.new_scripts_pre_setup_callbacks.length > 0) {
24
- for (const cb of context.new_scripts_pre_setup_callbacks) {
25
- if (!cb) continue;
26
- cb();
27
- }
28
- context.new_scripts_pre_setup_callbacks.length = 0;
29
- }
30
-
31
- // TODO: update all the code from above to use this logic
32
- // basically code gen should add the scripts to new scripts
33
- // and this code below should go into some util method
34
- new_scripts_buffer.length = 0;
35
- if (context.new_scripts.length > 0) {
36
- new_scripts_buffer.push(...context.new_scripts);
37
- }
38
- context.new_scripts.length = 0;
39
-
40
- // Check valid scripts and add all valid to the scripts array
41
- for (let i = 0; i < new_scripts_buffer.length; i++) {
42
- try {
43
- const script: IComponent = new_scripts_buffer[i];
44
- if (script.destroyed) continue;
45
- if (!script.gameObject) {
46
- console.error("MISSING GAMEOBJECT - will ignore", script);
47
- new_scripts_buffer.splice(i, 1);
48
- i--;
49
- continue;
50
- }
51
- script.context = context;
52
- updateActiveInHierarchyWithoutEventCall(script.gameObject);
53
- addScriptToArrays(script, context);
54
- }
55
- catch (err) {
56
- console.error(err);
57
- removeScriptFromContext(new_scripts_buffer[i], context);
58
- new_scripts_buffer.splice(i, 1);
59
- i--;
60
- }
61
- }
62
-
63
- // Awake
64
- for (let i = 0; i < new_scripts_buffer.length; i++) {
65
- try {
66
- const script: IComponent = new_scripts_buffer[i];
67
- if (script.destroyed) {
68
- removeScriptFromContext(new_scripts_buffer[i], context);
69
- new_scripts_buffer.splice(i, 1);
70
- i--; continue;
71
- }
72
- if (script.registering) {
73
- try {
74
- script.registering();
75
- }
76
- catch (err) { console.error(err); }
77
- }
78
- // console.log(script, script.gameObject)
79
- // TODO: we should not call awake on components with inactive gameobjects
80
- if (script.__internalAwake !== undefined) {
81
- if (!script.gameObject) {
82
- console.error("MISSING GAMEOBJECT", script, script.gameObject);
83
- }
84
- updateActiveInHierarchyWithoutEventCall(script.gameObject);
85
- if (script.activeAndEnabled)
86
- utils.safeInvoke(script.__internalAwake.bind(script));
87
-
88
- // registerPrewarmObject(script.gameObject, context);
89
- }
90
- }
91
- catch (err) {
92
- console.error(err);
93
- removeScriptFromContext(new_scripts_buffer[i], context);
94
- new_scripts_buffer.splice(i, 1);
95
- i--;
96
- }
97
- }
98
-
99
- // OnEnable
100
- for (let i = 0; i < new_scripts_buffer.length; i++) {
101
- try {
102
- const script: IComponent = new_scripts_buffer[i];
103
- if (script.destroyed) continue;
104
- // console.log(script, script.enabled, script.activeAndEnabled);
105
- if (script.enabled === false) continue;
106
- updateActiveInHierarchyWithoutEventCall(script.gameObject);
107
- if (script.activeAndEnabled === false) continue;
108
- if (script.__internalEnable !== undefined) {
109
- script.enabled = true;
110
- utils.safeInvoke(script.__internalEnable.bind(script));
111
- }
112
- }
113
- catch (err) {
114
- console.error(err);
115
- removeScriptFromContext(new_scripts_buffer[i], context);
116
- new_scripts_buffer.splice(i, 1);
117
- i--;
118
- }
119
- }
120
-
121
- // Enqueue Start
122
- for (let i = 0; i < new_scripts_buffer.length; i++) {
123
- try {
124
- const script = new_scripts_buffer[i];
125
- if (script.destroyed) continue;
126
- if (!script.gameObject) continue;
127
- context.new_script_start.push(script);
128
- }
129
- catch (err) {
130
- console.error(err);
131
- removeScriptFromContext(new_scripts_buffer[i], context);
132
- new_scripts_buffer.splice(i, 1);
133
- i--;
134
- }
135
- }
136
-
137
- // for (const script of new_scripts_buffer) {
138
- // if (script.destroyed) continue;
139
- // context.scripts.push(script);
140
- // }
141
- new_scripts_buffer.length = 0;
142
-
143
- // if(new_scripts_post_setup_callbacks.length > 0) console.log(new_scripts_post_setup_callbacks);
144
- for (const cb of context.new_scripts_post_setup_callbacks) {
145
- if (cb)
146
- cb();
147
- }
148
- context.new_scripts_post_setup_callbacks.length = 0;
149
- }
150
-
151
- export function processRemoveFromScene(script: IComponent) {
152
- if (!script) return;
153
- script.__internalDisable();
154
- removeScriptFromContext(script, script.context);
155
- }
156
-
157
- export function processStart(context: IContext, object?: Object3D) {
158
- // Call start on scripts
159
- for (let i = 0; i < context.new_script_start.length; i++) {
160
- try {
161
- const script = context.new_script_start[i];
162
- if (object !== undefined && script.gameObject !== object) continue;
163
- if (script.destroyed) continue;
164
- if (script.activeAndEnabled === false) {
165
- continue;
166
- }
167
- // keep them in queue until script has started
168
- // call awake if the script was inactive before
169
- utils.safeInvoke(script.__internalAwake.bind(script));
170
- utils.safeInvoke(script.__internalEnable.bind(script));
171
- // now call start
172
- utils.safeInvoke(script.__internalStart.bind(script));
173
- context.new_script_start.splice(i, 1);
174
- i--;
175
- }
176
- catch (err) {
177
- console.error(err);
178
- removeScriptFromContext(context.new_script_start[i], context);
179
- context.new_script_start.splice(i, 1);
180
- i--;
181
- }
182
- }
183
- }
184
-
185
-
186
- export function addScriptToArrays(script: any, context: IContext) {
187
- // TODO: not sure if this is ideal - maybe we should add a map if we have many scripts?
188
- const index = context.scripts.indexOf(script);
189
- if (index !== -1) return;
190
- context.scripts.push(script);
191
- if (script.earlyUpdate) context.scripts_earlyUpdate.push(script);
192
- if (script.update) context.scripts_update.push(script);
193
- if (script.lateUpdate) context.scripts_lateUpdate.push(script);
194
- if (script.onBeforeRender) context.scripts_onBeforeRender.push(script);
195
- if (script.onAfterRender) context.scripts_onAfterRender.push(script);
196
- if (script.onPausedChanged) context.scripts_pausedChanged.push(script);
197
- }
198
-
199
-
200
- export function removeScriptFromContext(script: any, context: IContext) {
201
- removeFromArray(script, context.new_scripts);
202
- removeFromArray(script, context.new_script_start);
203
- removeFromArray(script, context.scripts);
204
- removeFromArray(script, context.scripts_earlyUpdate);
205
- removeFromArray(script, context.scripts_update);
206
- removeFromArray(script, context.scripts_lateUpdate);
207
- removeFromArray(script, context.scripts_onBeforeRender);
208
- removeFromArray(script, context.scripts_onAfterRender);
209
- removeFromArray(script, context.scripts_pausedChanged);
210
- context.stopAllCoroutinesFrom(script);
211
- }
212
-
213
- function removeFromArray(script: any, array: any[]) {
214
- const index = array.indexOf(script);
215
- if (index >= 0) array.splice(index, 1);
216
- }
217
-
218
-
219
- export function updateIsActive(obj?: Object3D) {
220
- if (!obj) obj = ContextRegistry.Current.scene;
221
- if (!obj) {
222
- console.trace("Invalid call - no current context.");
223
- return;
224
- }
225
- const activeSelf = isActiveSelf(obj);
226
- const wasSuccessful = updateIsActiveInHierarchyRecursiveRuntime(obj, activeSelf, true);
227
- if (!wasSuccessful) {
228
- console.error("Failed to update active state in hierarchy of \"" + obj.name + "\"", obj);
229
- console.warn(" ↑ this error might be caused by circular references. Please make sure you don't have files with circular references (e.g. one GLB 1 is loading GLB 2 which is then loading GLB 1 again).")
230
- }
231
- }
232
-
233
- function updateIsActiveInHierarchyRecursiveRuntime(go: THREE.Object3D, activeInHierarchy: boolean, allowEventCall: boolean, level: number = 0) {
234
- if (level > 1000) {
235
- console.warn("Hierarchy is too deep (> 1000 level) - will abort updating active state");
236
- return false;
237
- }
238
-
239
- const isActive = isActiveSelf(go);
240
- if (activeInHierarchy) {
241
- activeInHierarchy = isActive;
242
- // IF we update activeInHierarchy within a disabled hierarchy we need to check the parent
243
- if (activeInHierarchy && go.parent) {
244
- activeInHierarchy = go.parent[constants.activeInHierarchyFieldName];
245
- if(activeInHierarchy === undefined) activeInHierarchy = true;
246
- }
247
- }
248
-
249
- const prevActive = go[constants.activeInHierarchyFieldName];
250
- const changed = prevActive !== activeInHierarchy;
251
- go[constants.activeInHierarchyFieldName] = activeInHierarchy;
252
-
253
- // only raise events here if we didnt call enable etc already
254
- if (changed) {
255
- if (debugHierarchy)
256
- console.warn("ACTIVE CHANGE", go.name, isActive, go.visible, activeInHierarchy, "changed?" + changed, go);
257
- if (allowEventCall) {
258
- perComponent(go, comp => {
259
- if (activeInHierarchy) {
260
- if (comp.enabled) {
261
- utils.safeInvoke(comp.__internalAwake.bind(comp));
262
- comp["__didEnable"] = true;
263
- comp.onEnable();
264
- }
265
- }
266
- else {
267
- if (comp["__didAwake"]) {
268
- comp["__didEnable"] = false;
269
- comp.onDisable();
270
- }
271
- }
272
- });
273
- }
274
- }
275
-
276
- let success = true;
277
- if (go.children) {
278
- const nextLevel = level + 1;
279
- for (const ch of go.children) {
280
- const res = updateIsActiveInHierarchyRecursiveRuntime(ch, activeInHierarchy, allowEventCall, nextLevel);
281
- if (res === false) success = false;
282
- }
283
- }
284
- return success;
285
- }
286
-
287
- // let isRunning = false;
288
- // // Prevent: https://github.com/needle-tools/needle-tiny/issues/641
289
- // const temporyChildArrayBuffer: Array<Array<THREE.Object3D>> = [];
290
- // export function* iterateChildrenSafe(obj: Object3D) {
291
- // if (!obj || !obj.children) yield null;
292
- // // if(isRunning) return;
293
- // // isRunning = true;
294
- // const arr = temporyChildArrayBuffer.pop() || [];
295
- // arr.push(...obj.children);
296
- // for (const ch of arr) {
297
- // yield ch;
298
- // }
299
- // // isRunning = false;
300
- // arr.length = 0;
301
- // temporyChildArrayBuffer.push(arr);
302
- // }
303
-
304
- export function updateActiveInHierarchyWithoutEventCall(go: THREE.Object3D) {
305
- let activeInHierarchy = true;
306
- let current: THREE.Object3D | null = go;
307
- let foundScene: boolean = false;
308
- while (current) {
309
- if (!current) break;
310
- if (current.type === "Scene") foundScene = true;
311
- if (!isActiveSelf(current)) {
312
- activeInHierarchy = false;
313
- break;
314
- }
315
- current = current.parent;
316
- }
317
- if (!go) {
318
- console.error("GO is null");
319
- return;
320
- }
321
- go[constants.activeInHierarchyFieldName] = activeInHierarchy && foundScene;
322
- }
323
-
324
- function perComponent(go: THREE.Object3D, evt: (comp: IComponent) => void) {
325
- if (go.userData?.components) {
326
- for (const comp of go.userData.components) {
327
- evt(comp);
328
- }
329
- }
330
- }
331
-
332
-
333
- const prewarmList: Map<IContext, Object3D[]> = new Map();
334
- const $prewarmedFlag = Symbol("prewarmFlag");
335
- const $waitingForPrewarm = Symbol("waitingForPrewarm");
336
- const debugPrewarm = getParam("debugprewarm");
337
-
338
- export function registerPrewarmObject(obj: Object3D, context: IContext) {
339
- if (!obj) return;
340
- // allow objects to be marked as prewarmed in which case we dont need to register them again
341
- if (obj[$prewarmedFlag] === true) return;
342
- if (obj[$waitingForPrewarm] === true) return;
343
- if (!prewarmList.has(context)) {
344
- prewarmList.set(context, []);
345
- }
346
- obj[$waitingForPrewarm] = true;
347
- const list = prewarmList.get(context);
348
- list!.push(obj);
349
- if (debugPrewarm) console.debug("register prewarm", obj.name);
350
- }
351
-
352
- let prewarmTarget: WebGLCubeRenderTarget | null = null;
353
- let prewarmCamera: CubeCamera | null = null;
354
-
355
- // called by the engine to remove scroll or animation hiccup when objects are rendered/compiled for the first time
356
- export function runPrewarm(context: IContext) {
357
- if (!context) return;
358
- const list = prewarmList.get(context);
359
- if (!list?.length) return;
360
-
361
- const cam = context.mainCamera;
362
- if (cam) {
363
- if (debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
364
- const renderer = context.renderer;
365
- const scene = context.scene;
366
- renderer.compile(scene, cam!)
367
- prewarmTarget ??= new WebGLCubeRenderTarget(64)
368
- prewarmCamera ??= new CubeCamera(0.001, 9999999, prewarmTarget);
369
- prewarmCamera.update(renderer, scene);
370
- for (const obj of list) {
371
- obj[$prewarmedFlag] = true;
372
- obj[$waitingForPrewarm] = false;
373
- }
374
- list.length = 0;
375
- if (debugPrewarm) console.log("prewarm done");
376
- }
377
- }
378
-
379
- export function clearPrewarmList(context: IContext) {
380
- const list = prewarmList.get(context);
381
- if (list) {
382
- for (const obj of list) {
383
- obj[$waitingForPrewarm] = false;
384
- }
385
- list.length = 0;
386
- }
387
- prewarmList.delete(context);
1
+ import * as utils from "./engine_generic_utils";
2
+ import * as constants from "./engine_constants";
3
+ import { getParam } from './engine_utils';
4
+ import { CubeCamera, Object3D, Scene, WebGLCubeRenderTarget } from 'three';
5
+ import { IComponent, IContext } from './engine_types';
6
+ import { isActiveSelf } from './engine_gameobject';
7
+ import { ContextRegistry } from "./engine_context_registry";
8
+
9
+ const debug = getParam("debugnewscripts");
10
+ const debugHierarchy = getParam("debughierarchy");
11
+
12
+ // if some other script adds new scripts in onEnable or awake
13
+ // the original array should be cleared before processing it
14
+ // so we use this copy buffer
15
+ const new_scripts_buffer: any[] = [];
16
+
17
+ export function processNewScripts(context: IContext) {
18
+ if (context.new_scripts.length <= 0) return;
19
+ if (debug)
20
+ console.log("Register new components", context.new_scripts.length, [...context.new_scripts], context.alias ? ("element: " + context.alias) : context["hash"], context);
21
+
22
+ // if(new_scripts_post_setup_callbacks.length > 0) console.log(new_scripts_post_setup_callbacks);
23
+ if (context.new_scripts_pre_setup_callbacks.length > 0) {
24
+ for (const cb of context.new_scripts_pre_setup_callbacks) {
25
+ if (!cb) continue;
26
+ cb();
27
+ }
28
+ context.new_scripts_pre_setup_callbacks.length = 0;
29
+ }
30
+
31
+ // TODO: update all the code from above to use this logic
32
+ // basically code gen should add the scripts to new scripts
33
+ // and this code below should go into some util method
34
+ new_scripts_buffer.length = 0;
35
+ if (context.new_scripts.length > 0) {
36
+ new_scripts_buffer.push(...context.new_scripts);
37
+ }
38
+ context.new_scripts.length = 0;
39
+
40
+ // Check valid scripts and add all valid to the scripts array
41
+ for (let i = 0; i < new_scripts_buffer.length; i++) {
42
+ try {
43
+ const script: IComponent = new_scripts_buffer[i];
44
+ if (script.destroyed) continue;
45
+ if (!script.gameObject) {
46
+ console.error("MISSING GAMEOBJECT - will ignore", script);
47
+ new_scripts_buffer.splice(i, 1);
48
+ i--;
49
+ continue;
50
+ }
51
+ script.context = context;
52
+ updateActiveInHierarchyWithoutEventCall(script.gameObject);
53
+ addScriptToArrays(script, context);
54
+ }
55
+ catch (err) {
56
+ console.error(err);
57
+ removeScriptFromContext(new_scripts_buffer[i], context);
58
+ new_scripts_buffer.splice(i, 1);
59
+ i--;
60
+ }
61
+ }
62
+
63
+ // Awake
64
+ for (let i = 0; i < new_scripts_buffer.length; i++) {
65
+ try {
66
+ const script: IComponent = new_scripts_buffer[i];
67
+ if (script.destroyed) {
68
+ removeScriptFromContext(new_scripts_buffer[i], context);
69
+ new_scripts_buffer.splice(i, 1);
70
+ i--; continue;
71
+ }
72
+ if (script.registering) {
73
+ try {
74
+ script.registering();
75
+ }
76
+ catch (err) { console.error(err); }
77
+ }
78
+ // console.log(script, script.gameObject)
79
+ // TODO: we should not call awake on components with inactive gameobjects
80
+ if (script.__internalAwake !== undefined) {
81
+ if (!script.gameObject) {
82
+ console.error("MISSING GAMEOBJECT", script, script.gameObject);
83
+ }
84
+ updateActiveInHierarchyWithoutEventCall(script.gameObject);
85
+ if (script.activeAndEnabled)
86
+ utils.safeInvoke(script.__internalAwake.bind(script));
87
+
88
+ // registerPrewarmObject(script.gameObject, context);
89
+ }
90
+ }
91
+ catch (err) {
92
+ console.error(err);
93
+ removeScriptFromContext(new_scripts_buffer[i], context);
94
+ new_scripts_buffer.splice(i, 1);
95
+ i--;
96
+ }
97
+ }
98
+
99
+ // OnEnable
100
+ for (let i = 0; i < new_scripts_buffer.length; i++) {
101
+ try {
102
+ const script: IComponent = new_scripts_buffer[i];
103
+ if (script.destroyed) continue;
104
+ // console.log(script, script.enabled, script.activeAndEnabled);
105
+ if (script.enabled === false) continue;
106
+ updateActiveInHierarchyWithoutEventCall(script.gameObject);
107
+ if (script.activeAndEnabled === false) continue;
108
+ if (script.__internalEnable !== undefined) {
109
+ script.enabled = true;
110
+ utils.safeInvoke(script.__internalEnable.bind(script));
111
+ }
112
+ }
113
+ catch (err) {
114
+ console.error(err);
115
+ removeScriptFromContext(new_scripts_buffer[i], context);
116
+ new_scripts_buffer.splice(i, 1);
117
+ i--;
118
+ }
119
+ }
120
+
121
+ // Enqueue Start
122
+ for (let i = 0; i < new_scripts_buffer.length; i++) {
123
+ try {
124
+ const script = new_scripts_buffer[i];
125
+ if (script.destroyed) continue;
126
+ if (!script.gameObject) continue;
127
+ context.new_script_start.push(script);
128
+ }
129
+ catch (err) {
130
+ console.error(err);
131
+ removeScriptFromContext(new_scripts_buffer[i], context);
132
+ new_scripts_buffer.splice(i, 1);
133
+ i--;
134
+ }
135
+ }
136
+
137
+ // for (const script of new_scripts_buffer) {
138
+ // if (script.destroyed) continue;
139
+ // context.scripts.push(script);
140
+ // }
141
+ new_scripts_buffer.length = 0;
142
+
143
+ // if(new_scripts_post_setup_callbacks.length > 0) console.log(new_scripts_post_setup_callbacks);
144
+ for (const cb of context.new_scripts_post_setup_callbacks) {
145
+ if (cb)
146
+ cb();
147
+ }
148
+ context.new_scripts_post_setup_callbacks.length = 0;
149
+ }
150
+
151
+ export function processRemoveFromScene(script: IComponent) {
152
+ if (!script) return;
153
+ script.__internalDisable();
154
+ removeScriptFromContext(script, script.context);
155
+ }
156
+
157
+ export function processStart(context: IContext, object?: Object3D) {
158
+ // Call start on scripts
159
+ for (let i = 0; i < context.new_script_start.length; i++) {
160
+ try {
161
+ const script = context.new_script_start[i];
162
+ if (object !== undefined && script.gameObject !== object) continue;
163
+ if (script.destroyed) continue;
164
+ if (script.activeAndEnabled === false) {
165
+ continue;
166
+ }
167
+ // keep them in queue until script has started
168
+ // call awake if the script was inactive before
169
+ utils.safeInvoke(script.__internalAwake.bind(script));
170
+ utils.safeInvoke(script.__internalEnable.bind(script));
171
+ // now call start
172
+ utils.safeInvoke(script.__internalStart.bind(script));
173
+ context.new_script_start.splice(i, 1);
174
+ i--;
175
+ }
176
+ catch (err) {
177
+ console.error(err);
178
+ removeScriptFromContext(context.new_script_start[i], context);
179
+ context.new_script_start.splice(i, 1);
180
+ i--;
181
+ }
182
+ }
183
+ }
184
+
185
+
186
+ export function addScriptToArrays(script: any, context: IContext) {
187
+ // TODO: not sure if this is ideal - maybe we should add a map if we have many scripts?
188
+ const index = context.scripts.indexOf(script);
189
+ if (index !== -1) return;
190
+ context.scripts.push(script);
191
+ if (script.earlyUpdate) context.scripts_earlyUpdate.push(script);
192
+ if (script.update) context.scripts_update.push(script);
193
+ if (script.lateUpdate) context.scripts_lateUpdate.push(script);
194
+ if (script.onBeforeRender) context.scripts_onBeforeRender.push(script);
195
+ if (script.onAfterRender) context.scripts_onAfterRender.push(script);
196
+ if (script.onPausedChanged) context.scripts_pausedChanged.push(script);
197
+ }
198
+
199
+
200
+ export function removeScriptFromContext(script: any, context: IContext) {
201
+ removeFromArray(script, context.new_scripts);
202
+ removeFromArray(script, context.new_script_start);
203
+ removeFromArray(script, context.scripts);
204
+ removeFromArray(script, context.scripts_earlyUpdate);
205
+ removeFromArray(script, context.scripts_update);
206
+ removeFromArray(script, context.scripts_lateUpdate);
207
+ removeFromArray(script, context.scripts_onBeforeRender);
208
+ removeFromArray(script, context.scripts_onAfterRender);
209
+ removeFromArray(script, context.scripts_pausedChanged);
210
+ context.stopAllCoroutinesFrom(script);
211
+ }
212
+
213
+ function removeFromArray(script: any, array: any[]) {
214
+ const index = array.indexOf(script);
215
+ if (index >= 0) array.splice(index, 1);
216
+ }
217
+
218
+
219
+ export function updateIsActive(obj?: Object3D) {
220
+ if (!obj) obj = ContextRegistry.Current.scene;
221
+ if (!obj) {
222
+ console.trace("Invalid call - no current context.");
223
+ return;
224
+ }
225
+ const activeSelf = isActiveSelf(obj);
226
+ const wasSuccessful = updateIsActiveInHierarchyRecursiveRuntime(obj, activeSelf, true);
227
+ if (!wasSuccessful) {
228
+ console.error("Failed to update active state in hierarchy of \"" + obj.name + "\"", obj);
229
+ console.warn(" ↑ this error might be caused by circular references. Please make sure you don't have files with circular references (e.g. one GLB 1 is loading GLB 2 which is then loading GLB 1 again).")
230
+ }
231
+ }
232
+
233
+ function updateIsActiveInHierarchyRecursiveRuntime(go: THREE.Object3D, activeInHierarchy: boolean, allowEventCall: boolean, level: number = 0) {
234
+ if (level > 1000) {
235
+ console.warn("Hierarchy is too deep (> 1000 level) - will abort updating active state");
236
+ return false;
237
+ }
238
+
239
+ const isActive = isActiveSelf(go);
240
+ if (activeInHierarchy) {
241
+ activeInHierarchy = isActive;
242
+ // IF we update activeInHierarchy within a disabled hierarchy we need to check the parent
243
+ if (activeInHierarchy && go.parent) {
244
+ const parent = go.parent;
245
+ activeInHierarchy = parent[constants.activeInHierarchyFieldName];
246
+ if (activeInHierarchy === undefined) {
247
+ // TODO: come up with a better solution for this. When we are in a r3f hierarchy (externally managed) and the parent flag is undefined we set it to true if the parent is NOT a scene. This activates the object by default. We should probably walk up the stack and check if we can find either the root Scene or any object that is disabled and use that to set the activeInHierarchy flag.
248
+ if (parent instanceof Scene) {
249
+ }
250
+ else {
251
+ activeInHierarchy = true;
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ const prevActive = go[constants.activeInHierarchyFieldName];
258
+ const changed = prevActive !== activeInHierarchy;
259
+ go[constants.activeInHierarchyFieldName] = activeInHierarchy;
260
+
261
+ // only raise events here if we didnt call enable etc already
262
+ if (changed) {
263
+ if (debugHierarchy)
264
+ console.warn("ACTIVE CHANGE", go.name, isActive, go.visible, activeInHierarchy, "changed?" + changed, go);
265
+ if (allowEventCall) {
266
+ perComponent(go, comp => {
267
+ if (activeInHierarchy) {
268
+ if (comp.enabled) {
269
+ utils.safeInvoke(comp.__internalAwake.bind(comp));
270
+ comp["__didEnable"] = true;
271
+ comp.onEnable();
272
+ }
273
+ }
274
+ else {
275
+ if (comp["__didAwake"]) {
276
+ comp["__didEnable"] = false;
277
+ comp.onDisable();
278
+ }
279
+ }
280
+ });
281
+ }
282
+ }
283
+
284
+ let success = true;
285
+ if (go.children) {
286
+ const nextLevel = level + 1;
287
+ for (const ch of go.children) {
288
+ const res = updateIsActiveInHierarchyRecursiveRuntime(ch, activeInHierarchy, allowEventCall, nextLevel);
289
+ if (res === false) success = false;
290
+ }
291
+ }
292
+ return success;
293
+ }
294
+
295
+ // let isRunning = false;
296
+ // // Prevent: https://github.com/needle-tools/needle-tiny/issues/641
297
+ // const temporyChildArrayBuffer: Array<Array<THREE.Object3D>> = [];
298
+ // export function* iterateChildrenSafe(obj: Object3D) {
299
+ // if (!obj || !obj.children) yield null;
300
+ // // if(isRunning) return;
301
+ // // isRunning = true;
302
+ // const arr = temporyChildArrayBuffer.pop() || [];
303
+ // arr.push(...obj.children);
304
+ // for (const ch of arr) {
305
+ // yield ch;
306
+ // }
307
+ // // isRunning = false;
308
+ // arr.length = 0;
309
+ // temporyChildArrayBuffer.push(arr);
310
+ // }
311
+
312
+ export function updateActiveInHierarchyWithoutEventCall(go: THREE.Object3D) {
313
+ let activeInHierarchy = true;
314
+ let current: THREE.Object3D | null = go;
315
+ let foundScene: boolean = false;
316
+ while (current) {
317
+ if (!current) break;
318
+ if (current.type === "Scene") foundScene = true;
319
+ if (!isActiveSelf(current)) {
320
+ activeInHierarchy = false;
321
+ break;
322
+ }
323
+ current = current.parent;
324
+ }
325
+ if (!go) {
326
+ console.error("GO is null");
327
+ return;
328
+ }
329
+ go[constants.activeInHierarchyFieldName] = activeInHierarchy && foundScene;
330
+ }
331
+
332
+ function perComponent(go: THREE.Object3D, evt: (comp: IComponent) => void) {
333
+ if (go.userData?.components) {
334
+ for (const comp of go.userData.components) {
335
+ evt(comp);
336
+ }
337
+ }
338
+ }
339
+
340
+
341
+ const prewarmList: Map<IContext, Object3D[]> = new Map();
342
+ const $prewarmedFlag = Symbol("prewarmFlag");
343
+ const $waitingForPrewarm = Symbol("waitingForPrewarm");
344
+ const debugPrewarm = getParam("debugprewarm");
345
+
346
+ export function registerPrewarmObject(obj: Object3D, context: IContext) {
347
+ if (!obj) return;
348
+ // allow objects to be marked as prewarmed in which case we dont need to register them again
349
+ if (obj[$prewarmedFlag] === true) return;
350
+ if (obj[$waitingForPrewarm] === true) return;
351
+ if (!prewarmList.has(context)) {
352
+ prewarmList.set(context, []);
353
+ }
354
+ obj[$waitingForPrewarm] = true;
355
+ const list = prewarmList.get(context);
356
+ list!.push(obj);
357
+ if (debugPrewarm) console.debug("register prewarm", obj.name);
358
+ }
359
+
360
+ let prewarmTarget: WebGLCubeRenderTarget | null = null;
361
+ let prewarmCamera: CubeCamera | null = null;
362
+
363
+ // called by the engine to remove scroll or animation hiccup when objects are rendered/compiled for the first time
364
+ export function runPrewarm(context: IContext) {
365
+ if (!context) return;
366
+ const list = prewarmList.get(context);
367
+ if (!list?.length) return;
368
+
369
+ const cam = context.mainCamera;
370
+ if (cam) {
371
+ if (debugPrewarm) console.log("prewarm", list.length, "objects", [...list]);
372
+ const renderer = context.renderer;
373
+ const scene = context.scene;
374
+ renderer.compile(scene, cam!)
375
+ prewarmTarget ??= new WebGLCubeRenderTarget(64)
376
+ prewarmCamera ??= new CubeCamera(0.001, 9999999, prewarmTarget);
377
+ prewarmCamera.update(renderer, scene);
378
+ for (const obj of list) {
379
+ obj[$prewarmedFlag] = true;
380
+ obj[$waitingForPrewarm] = false;
381
+ }
382
+ list.length = 0;
383
+ if (debugPrewarm) console.log("prewarm done");
384
+ }
385
+ }
386
+
387
+ export function clearPrewarmList(context: IContext) {
388
+ const list = prewarmList.get(context);
389
+ if (list) {
390
+ for (const obj of list) {
391
+ obj[$waitingForPrewarm] = false;
392
+ }
393
+ list.length = 0;
394
+ }
395
+ prewarmList.delete(context);
388
396
  }