Needle Engine

Changes between version 3.10.6-beta and 3.10.7-beta
Files changed (9) hide show
  1. src/engine/codegen/register_types.js +2 -2
  2. src/engine-components/Camera.ts +5 -1
  3. src/engine/engine_assetdatabase.ts +50 -56
  4. src/engine/engine_patcher.ts +53 -24
  5. src/engine/engine_serialization_builtin_serializer.ts +13 -1
  6. src/engine/engine_texture.ts +2 -2
  7. src/engine/extensions/extensions.ts +2 -2
  8. src/engine-components/ParticleSystem.ts +7 -1
  9. src/engine-components/Renderer.ts +2 -2
src/engine/codegen/register_types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
@@ -217,7 +217,7 @@
217
217
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
218
218
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
219
219
  import { XRState } from "../../engine-components/XRFlag";
220
-
220
+
221
221
  // Register types
222
222
  TypeStore.add("__Ignore", __Ignore);
223
223
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/Camera.ts CHANGED
@@ -239,7 +239,11 @@
239
239
  const useNormalRenderer = true;// this.context.isInXR || !composer;
240
240
  const renderer = useNormalRenderer ? this.context.renderer : composer;
241
241
  if (renderer) {
242
- this._targetTexture.render(this.context.scene, this._cam, renderer)
242
+ // TODO: we should do this in onBeforeRender for the main camera only
243
+ const mainCam = this.context.mainCameraComponent;
244
+ this.applyClearFlags();
245
+ this._targetTexture.render(this.context.scene, this._cam, renderer);
246
+ mainCam?.applyClearFlags();
243
247
  }
244
248
  }
245
249
  }
src/engine/engine_assetdatabase.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { InternalUsageTrackerPlugin } from "./extensions/usage_tracker";
2
- import { Bone, BufferAttribute, BufferGeometry, InterleavedBuffer, InterleavedBufferAttribute, Material, Mesh, Object3D, Scene, Skeleton, SkinnedMesh, Source, Texture, Uniform, WebGLRenderer } from "three";
2
+ import { Bone, BufferAttribute, BufferGeometry, InterleavedBuffer, InterleavedBufferAttribute, Material, Mesh, NeverCompare, Object3D, Scene, Skeleton, SkinnedMesh, Source, Texture, Uniform, WebGLRenderer } from "three";
3
3
  import { addPatch } from "./engine_patcher";
4
4
  import { getParam } from "./engine_utils";
5
5
 
@@ -20,19 +20,22 @@
20
20
  }
21
21
  }
22
22
 
23
- const trackUsageParam = getParam("trackusage");
23
+ const trackUsageParam = getParam("trackresources");
24
24
 
25
25
  function autoDispose() {
26
26
  return trackUsageParam === "dispose";
27
27
  }
28
28
 
29
- // For testing only track when explictly enabled
30
- let allowUsageTracking = trackUsageParam !== undefined && trackUsageParam !== false && trackUsageParam !== 0;
29
+ let allowUsageTracking = true;// trackUsageParam === true || trackUsageParam === 1;
30
+ if (trackUsageParam === 0) allowUsageTracking = false;
31
31
 
32
- export function setUsageTrackingEnabled(enabled: boolean) {
32
+ /**
33
+ * Disable usage tracking
34
+ */
35
+ export function setResourceTrackingEnabled(enabled: boolean) {
33
36
  allowUsageTracking = enabled;
34
37
  }
35
- export function isUsageTrackingEnabled() {
38
+ export function isResourceTrackingEnabled() {
36
39
  return allowUsageTracking;
37
40
  }
38
41
 
@@ -52,7 +55,7 @@
52
55
  export function disposeObjectResources(obj: object | null | undefined) {
53
56
  if (!obj) return;
54
57
  if (obj[$disposable] === false) {
55
- if(debug) console.warn("Object is marked as not disposable", obj);
58
+ if (debug) console.warn("Object is marked as not disposable", obj);
56
59
  return;
57
60
  }
58
61
 
@@ -183,7 +186,7 @@
183
186
  * @param set Set to add users to, a new one will be created if none is provided
184
187
  * @returns a set of users
185
188
  */
186
- export function findUsers(object: object, recursive: boolean, predicate: UserFilter | null | undefined = null, set?: Set<object>): Set<object> {
189
+ export function findResourceUsers(object: object, recursive: boolean, predicate: UserFilter | null | undefined = null, set?: Set<object>): Set<object> {
187
190
  if (!set) {
188
191
  set = usersBuffer;
189
192
  set.clear();
@@ -198,29 +201,29 @@
198
201
  if (predicate?.call(null, user) === false) continue;
199
202
  set.add(user);
200
203
  if (recursive)
201
- findUsers(user, true, predicate, set);
204
+ findResourceUsers(user, true, predicate, set);
202
205
  }
203
206
  }
204
207
  return set;
205
208
  }
206
209
 
207
- export function getUserCount(object: object): number | undefined {
210
+ export function getResourceUserCount(object: object): number | undefined {
208
211
  return object[$objectUsersCountKey];
209
212
  }
210
213
 
211
214
 
212
215
 
213
- const debug = getParam("debugusers") || getParam("debugmemory");
216
+ const debug = getParam("debugresourceusers") || getParam("debugmemory");
214
217
 
215
218
  // Should we check if the type has the
216
- const $objectUsersKey = Symbol("needle-users");
217
- const $objectUsersCountKey = Symbol("needle-users-count");
219
+ const $objectUsersKey = Symbol("needle-resource-users");
220
+ const $objectUsersCountKey = Symbol("needle-resource-users-count");
218
221
 
219
222
  function trackValueChange(prototype, fieldName) {
220
- addPatch(prototype, fieldName, (obj, oldValue, newValue) => {
223
+ addPatch(prototype, fieldName, function (this: object, oldValue, newValue) {
221
224
  if (allowUsageTracking) {
222
- updateUsers($objectUsersKey, obj, oldValue, false);
223
- updateUsers($objectUsersKey, obj, newValue, true);
225
+ updateUsers($objectUsersKey, this, oldValue, false);
226
+ updateUsers($objectUsersKey, this, newValue, true);
224
227
  }
225
228
  });
226
229
  }
@@ -234,29 +237,26 @@
234
237
  // }
235
238
 
236
239
 
237
- trackValueChange(Mesh.prototype, "material");
238
- trackValueChange(Mesh.prototype, "geometry");
239
- trackValueChange(Material.prototype, "map");
240
- trackValueChange(Material.prototype, "bumpMap");
241
- trackValueChange(Material.prototype, "alphaMap");
242
- trackValueChange(Material.prototype, "normalMap");
243
- trackValueChange(Material.prototype, "displacementMap");
244
- trackValueChange(Material.prototype, "roughnessMap");
245
- trackValueChange(Material.prototype, "metalnessMap");
246
- trackValueChange(Material.prototype, "emissiveMap");
247
- trackValueChange(Material.prototype, "specularMap");
248
- trackValueChange(Material.prototype, "envMap");
249
- trackValueChange(Material.prototype, "lightMap");
250
- trackValueChange(Material.prototype, "aoMap");
251
- trackValueChange(Material.prototype, "gradientMap");
252
- // trackValueChange(Object3D.prototype, "parent");
240
+ if (allowUsageTracking) {
241
+ trackValueChange(Mesh.prototype, "material");
242
+ trackValueChange(Mesh.prototype, "geometry");
243
+ trackValueChange(Material.prototype, "map");
244
+ trackValueChange(Material.prototype, "bumpMap");
245
+ trackValueChange(Material.prototype, "alphaMap");
246
+ trackValueChange(Material.prototype, "normalMap");
247
+ trackValueChange(Material.prototype, "displacementMap");
248
+ trackValueChange(Material.prototype, "roughnessMap");
249
+ trackValueChange(Material.prototype, "metalnessMap");
250
+ trackValueChange(Material.prototype, "emissiveMap");
251
+ trackValueChange(Material.prototype, "specularMap");
252
+ trackValueChange(Material.prototype, "envMap");
253
+ trackValueChange(Material.prototype, "lightMap");
254
+ trackValueChange(Material.prototype, "aoMap");
255
+ trackValueChange(Material.prototype, "gradientMap");
256
+ }
253
257
 
254
258
 
255
- // setTimeout(()=>{
256
- // stopTracking(Mesh.prototype, "material");
257
- // },100);
258
259
 
259
-
260
260
  // TODO: patch dispose?
261
261
 
262
262
 
@@ -270,18 +270,17 @@
270
270
  }
271
271
  }
272
272
 
273
- function trackDispose(prototype, methodName: string) {
274
- addPatch(prototype, methodName, (instance) => {
275
- onDispose(instance);
276
- });
273
+ if (allowUsageTracking) {
274
+ addPatch(Material.prototype, "dispose", function (this: object) { onDispose(this) });
277
275
  }
278
276
 
279
- trackDispose(Material.prototype, "dispose");
280
277
 
281
278
 
282
279
 
283
-
284
-
280
+ // This variable is crucial for performance:
281
+ // it is incremented during rendering to prevent usage updates during the three.js render loop
282
+ // where materials and properties are updated every frame (e.g. the DepthMaterial)
283
+ // and we don't care about those
285
284
  let noUpdateScope = 0;
286
285
 
287
286
  // Main method called by wrapped fields/properties to update the users for an object
@@ -331,21 +330,16 @@
331
330
  }
332
331
 
333
332
 
334
-
335
-
336
333
  // We dont want to update users during rendering
337
-
338
-
339
334
  try {
340
-
341
- // addPatch(WebGLRenderer.prototype, "render",
342
- // () => {
343
- // noUpdateScope++;
344
- // },
345
- // () => {
346
- // noUpdateScope--;
347
- // }
348
- // );
335
+ addPatch(WebGLRenderer.prototype, "render",
336
+ function () {
337
+ noUpdateScope++;
338
+ },
339
+ function () {
340
+ noUpdateScope--;
341
+ }
342
+ );
349
343
  }
350
344
  catch (e) {
351
345
  console.warn("Could not wrap WebGLRenderer.render", e);
src/engine/engine_patcher.ts CHANGED
@@ -1,9 +1,11 @@
1
1
 
2
2
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
3
3
 
4
- const _wrappedMethods = new WeakSet();
4
+ import { getParam } from "./engine_utils";
5
5
 
6
+ // const _wrappedMethods = new WeakSet();
6
7
 
8
+
7
9
  // export function wrap<T>(prototype: object, methodName: string, before: (t: T) => void, after: (t: T) => void) {
8
10
 
9
11
  // const $key = Symbol(methodName + "-patched");
@@ -40,34 +42,43 @@
40
42
  export type Prefix = (...args) => any;
41
43
  export type Postfix = (...args) => any;
42
44
 
45
+ const debugPatch = getParam("debugpatch");
46
+
43
47
  /**
44
48
  * Use patcher for patching properties insteadof calling Object.defineProperty individually
45
49
  * since this will cause conflicts if multiple patches need to be applied to the same property
46
50
  */
47
- export function addPatch<T extends object>(prototype: T, fieldName: string, beforeCallback?: Prefix, afterCallback?: Postfix) {
51
+ export function addPatch<T extends object>(prototype: T, fieldName: string, beforeCallback?: Prefix | null, afterCallback?: Postfix | null) {
52
+ const debug = debugPatch === fieldName;
48
53
 
49
- // TODO
50
- return;
54
+ // If no callbacks are provided, we don't need to do anything
55
+ if (!beforeCallback && !afterCallback) {
56
+ return;
57
+ }
51
58
 
52
59
  // TODO: we probably want to turn this into a symbol to prevent anyone from overriding it
53
60
  // But when we need to store the symbol per prototype to allow e.g. material disposing to iterate those and dispose all
54
61
  const backingField = Symbol(fieldName + "__needle");// Symbol(fieldName);// + " (patched)";
55
62
 
56
- internalAddPatch(prototype, fieldName, afterCallback, beforeCallback);
63
+ internalAddPatch(prototype, fieldName, beforeCallback, afterCallback);
57
64
 
58
65
  const desc = Object.getOwnPropertyDescriptor(prototype, fieldName);
59
-
60
66
  const existing = prototype[fieldName];
61
- console.log(prototype);
67
+ if (debug) console.log("Patch", prototype.constructor.name, fieldName, desc, existing);
62
68
 
63
69
  if (desc) {
70
+ if (debug) console.log("Apply patch with existing descriptor", prototype.constructor.name, fieldName, desc);
71
+ if (typeof desc.value === "function") {
72
+ prototype[fieldName] = ensureFunctionWrapped(desc.value, prototype, fieldName);
73
+ }
64
74
  }
65
75
  else {
76
+ if (debug) console.log("Create patch with new property", prototype.constructor.name, fieldName, desc);
66
77
  Object.defineProperty(prototype, fieldName, {
67
78
  set: function (this: object, value: any) {
68
- console.log("setting", fieldName, value);
69
79
  if (typeof value === "function") {
70
- this[backingField] = addWrapper(value, prototype, fieldName);
80
+ // TODO: not sure if this is correct (if the value that is set is a function)
81
+ this[backingField] = ensureFunctionWrapped(value, prototype, fieldName);
71
82
  }
72
83
  else {
73
84
  const prev = this[backingField];
@@ -77,7 +88,6 @@
77
88
  }
78
89
  },
79
90
  get: function (this: any) {
80
- console.log("GET", fieldName);
81
91
  const value = this[backingField];
82
92
  if (typeof value === "function") {
83
93
  if (value[backingField]) {
@@ -90,20 +100,21 @@
90
100
  }
91
101
  }
92
102
 
93
- function addWrapper(originalFunction: Function, prototype, fieldname) {
94
- return function (this: object, ...args: any[]) {
95
- executePrefixes(prototype, fieldname, this, ...args);
96
- const result = originalFunction.apply(this, args);
97
- executePostFixes(prototype, fieldname, this, result, ...args);
98
- return result;
99
- }
100
- }
101
-
102
- export function removePatch(prototype: object, fieldName: string, cb: Function) {
103
+ /** Removes prefix or postfix */
104
+ export function removePatch(prototype: object, fieldName: string, prefixOrPostfix: Prefix | Postfix) {
103
105
  const patches = getPatches(prototype, fieldName);
104
106
  if (patches) {
105
107
  for (let i = patches.length - 1; i >= 0; i--) {
106
- if (patches[i] === cb) {
108
+ const patch = patches[i];
109
+ // Remove either the prefix or postfix
110
+ if (patch.prefix === prefixOrPostfix) {
111
+ patch.prefix = null;
112
+ }
113
+ if (patch.postfix === prefixOrPostfix) {
114
+ patch.postfix = null;
115
+ }
116
+ // If the patch is empty, remove it from the list
117
+ if (!patch.prefix && !patch.postfix) {
107
118
  patches.splice(i, 1);
108
119
  }
109
120
  }
@@ -111,11 +122,29 @@
111
122
  }
112
123
 
113
124
 
125
+
126
+ const $wrappedFunctionSymbol = Symbol("Needle:Patches:WrappedFunction");
127
+
128
+ function ensureFunctionWrapped(originalFunction: Function, prototype, fieldname) {
129
+ if (originalFunction[$wrappedFunctionSymbol]) {
130
+ return originalFunction;
131
+ }
132
+ const wrappedFunction = function (this: object, ...args: any[]) {
133
+ executePrefixes(prototype, fieldname, this, ...args);
134
+ const result = originalFunction.apply(this, args);
135
+ executePostFixes(prototype, fieldname, this, result, ...args);
136
+ return result;
137
+ }
138
+ wrappedFunction[$wrappedFunctionSymbol] = true;
139
+ return wrappedFunction;
140
+ }
141
+
142
+
114
143
  export const NeedlePatchesKey = "Needle:Patches";
115
144
 
116
145
  declare type PatchInfo = {
117
- prefix?: Prefix;
118
- postfix?: Postfix;
146
+ prefix?: Prefix | null;
147
+ postfix?: Postfix | null;
119
148
  }
120
149
  function patches(): WeakMap<object, Map<string, PatchInfo[]>> {
121
150
  if (!globalThis[NeedlePatchesKey]) {
@@ -132,7 +161,7 @@
132
161
  return patchesMap.get(fieldName);;
133
162
  }
134
163
 
135
- function internalAddPatch(prototype, fieldName: string, postfix?: Postfix, prefix?: Prefix) {
164
+ function internalAddPatch(prototype, fieldName: string, prefix?: Prefix | null, postfix?: Postfix | null) {
136
165
  let patchesMap = patches().get(prototype);
137
166
  if (!patchesMap) {
138
167
  patchesMap = new Map();
src/engine/engine_serialization_builtin_serializer.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Behaviour, Component, GameObject } from "../engine-components/Component";
5
5
  import { debugExtension } from "./engine_default_parameters";
6
6
  import { CallInfo, EventList } from "../engine-components/EventList";
7
- import { Color, Object3D, Texture, WebGLRenderTarget } from "three";
7
+ import { Color, CompressedTexture, Object3D, Texture, WebGLRenderTarget } from "three";
8
8
  import { RenderTexture } from "./engine_texture";
9
9
  import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../engine/debug/debug";
10
10
  import { resolveUrl } from "./engine_utils";
@@ -350,6 +350,18 @@
350
350
  const tex = data as Texture;
351
351
  const rt = new RenderTexture(tex.image.width, tex.image.height);
352
352
  rt.texture = tex;
353
+
354
+ if (tex instanceof CompressedTexture) {
355
+ //@ts-ignore
356
+ tex["isCompressedTexture"] = false;
357
+ //@ts-ignore
358
+ tex.format = THREE.RGBAFormat;
359
+ }
360
+
361
+ rt.texture.flipY = true;
362
+ rt.texture.offset.y = 1;
363
+ rt.texture.repeat.y = -1;
364
+
353
365
  return rt;
354
366
  }
355
367
  return undefined;
src/engine/engine_texture.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Camera, Mesh, Object3D, Texture, WebGLRenderer, WebGLRenderTarget } from "three";
2
2
  import { EffectComposer } from "postprocessing";
3
- import { findUsers } from "./engine_assetdatabase";
3
+ import { findResourceUsers } from "./engine_assetdatabase";
4
4
 
5
5
 
6
6
  const _prevVisible = Symbol("previous-visibility");
@@ -34,7 +34,7 @@
34
34
 
35
35
  private onBeforeRender() {
36
36
  RenderTexture._userSet.clear();
37
- const users = findUsers(this.texture, true, null, RenderTexture._userSet);
37
+ const users = findResourceUsers(this.texture, true, null, RenderTexture._userSet);
38
38
  for (const user of users) {
39
39
  if (user instanceof Mesh) {
40
40
  user[_prevVisible] = user.visible;
src/engine/extensions/extensions.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  import { NEEDLE_render_objects } from "./NEEDLE_render_objects";
15
15
  import { NEEDLE_progressive } from "./NEEDLE_progressive";
16
16
  import { InternalUsageTrackerPlugin } from "./usage_tracker";
17
- import { isUsageTrackingEnabled } from "../engine_assetdatabase";
17
+ import { isResourceTrackingEnabled } from "../engine_assetdatabase";
18
18
  import { GLTFLoaderPlugin } from "three/examples/jsm/loaders/GLTFLoader.js";
19
19
  import { getParam } from "../engine_utils";
20
20
  import { isDevEnvironment } from "../debug";
@@ -79,7 +79,7 @@
79
79
  loader.register(p => new NEEDLE_render_objects(p, sourceId));
80
80
  loader.register(p => new NEEDLE_progressive(p, sourceId, context));
81
81
  loader.register(p => new EXT_texture_exr(p));
82
- if (isUsageTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p))
82
+ if (isResourceTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p))
83
83
 
84
84
  for (const ext of _addedCustomExtension)
85
85
  loader.register(p => new ext(p));
src/engine-components/ParticleSystem.ts CHANGED
@@ -1027,7 +1027,12 @@
1027
1027
  if (this._subEmitterSystems && this._particleSystem) {
1028
1028
  for (const sys of this._subEmitterSystems) {
1029
1029
  // Make sure the particle system is created
1030
- if (sys.particleSystem) sys.particleSystem.__internalAwake();
1030
+ if (sys.particleSystem) {
1031
+ if (sys.particleSystem.__internalAwake)
1032
+ sys.particleSystem.__internalAwake();
1033
+ else if (isDevEnvironment())
1034
+ console.warn("SubParticleSystem serialization issue(?)", sys.particleSystem, sys);
1035
+ }
1031
1036
  const system = sys.particleSystem?._particleSystem;
1032
1037
  if (system) {
1033
1038
  sys.particleSystem!._isUsedAsSubsystem = true;
@@ -1037,6 +1042,7 @@
1037
1042
  sub.emitterProbability = sys.emitProbability;
1038
1043
  this._particleSystem.addBehavior(sub);
1039
1044
  }
1045
+ else if (debug) console.warn("Could not add SubParticleSystem", sys, this);
1040
1046
  }
1041
1047
  }
1042
1048
  }
src/engine-components/Renderer.ts CHANGED
@@ -365,8 +365,8 @@
365
365
  // ignore nested groups or objects that have their own renderer (aka their own render order settings)
366
366
  if (!this.isMeshOrSkinnedMesh(ch) || GameObject.getComponent(ch, Renderer)) continue;
367
367
  if (this.renderOrder.length <= index) {
368
- console.error("Incorrect element count", this);
369
- break;
368
+ console.warn("Incorrect renderOrder element count", this, this.renderOrder.length + " but expected " + this.gameObject.children.length, "Index: " + index, "ChildElement:", ch);
369
+ continue;
370
370
  }
371
371
  // if(debugRenderer) console.log("Setting render order", ch, this.renderOrder[index])
372
372
  ch.renderOrder = this.renderOrder[index];