Needle Engine

Changes between version 3.6.0-beta and 3.6.1-beta
Files changed (8) hide show
  1. src/engine/engine_addressables.ts +5 -1
  2. src/engine/engine_context.ts +9 -3
  3. src/engine/engine_element_attributes.ts +3 -0
  4. src/engine/engine_element.ts +20 -8
  5. src/engine/engine_input.ts +50 -43
  6. src/engine/engine_license.ts +1 -1
  7. src/engine/engine_physics_rapier.ts +5 -2
  8. src/engine-components/Skybox.ts +6 -2
src/engine/engine_addressables.ts CHANGED
@@ -43,7 +43,11 @@
43
43
  return ref;
44
44
  }
45
45
 
46
- clear() {
46
+ dispose() {
47
+ for (let key in this._assetReferences) {
48
+ const ref = this._assetReferences[key];
49
+ ref?.unload();
50
+ }
47
51
  this._assetReferences = {};
48
52
  }
49
53
 
src/engine/engine_context.ts CHANGED
@@ -386,7 +386,7 @@
386
386
  // which is probably not desired if it is set via the skybox-image attribute
387
387
  destroy(this.scene, true, true);
388
388
  this.scene = new Scene();
389
- this.addressables?.clear();
389
+ this.addressables?.dispose();
390
390
  this.lightmaps?.clear();
391
391
  this.physics?.engine?.clearCaches();
392
392
 
@@ -416,7 +416,9 @@
416
416
  if (!this.isManagedExternally) {
417
417
  this.renderer.dispose();
418
418
  }
419
+ this.scene = null!;
419
420
  this.renderer = null!;
421
+ this.input.dispose();
420
422
  for (const cb of this._disposeCallbacks) {
421
423
  try {
422
424
  cb();
@@ -601,10 +603,14 @@
601
603
 
602
604
  this.internalOnUpdateVisible();
603
605
 
604
- // console.log(prepare_succeeded);
606
+ if (!this.renderer) {
607
+ if(debug) console.warn("Context has no renderer (perhaps it was disconnected?", this.domElement.isConnected);
608
+ return false;
609
+ }
605
610
 
606
- if (!this.isManagedExternally && !this.domElement.shadowRoot)
611
+ if (!this.isManagedExternally && !this.domElement.shadowRoot) {
607
612
  this.domElement.prepend(this.renderer.domElement);
613
+ }
608
614
 
609
615
  Context.Current = this;
610
616
 
src/engine/engine_element_attributes.ts CHANGED
@@ -23,6 +23,9 @@
23
23
  /** override the default ktx2 decoder path */
24
24
  "ktx2DecoderPath"?: string;
25
25
 
26
+ /** Add to prevent Needle Engine context from being disposed when the element is removed from the DOM */
27
+ "keep-alive"? : boolean;
28
+
26
29
  addEventListener(event: "ready", callback: (event: CustomEvent) => void): void;
27
30
  addEventListener(event: "error", callback: (event: CustomEvent) => void): void;
28
31
  }
src/engine/engine_element.ts CHANGED
@@ -96,10 +96,10 @@
96
96
  this.addEventListener("error", this.onError);
97
97
  }
98
98
 
99
+
99
100
  async connectedCallback() {
100
101
  if (debug) {
101
102
  console.log("<needle-engine> connected");
102
- console.dir(this);
103
103
  }
104
104
 
105
105
  this.onSetupDesktop();
@@ -111,22 +111,28 @@
111
111
  this.setAttribute("src", global);
112
112
  }
113
113
  }
114
- this.onSetupDesktop();
114
+
115
115
  // we have to wait because codegen does set the src attribute when it's loaded
116
116
  // which might happen after the element is connected
117
117
  // if the `src` is then still null we want to initialize the default scene
118
+ const loadId = this._loadId;
118
119
  setTimeout(() => {
119
120
  if (this.isConnected === false) return;
120
- const src = this.getAttribute("src");
121
- if (src === undefined || src === null) {
122
- this.onLoad();
123
- }
121
+ if (loadId !== this._loadId) return;
122
+ this.onLoad();
124
123
  }, 1);
125
124
  }
126
125
 
127
126
  disconnectedCallback() {
128
- if (debug) console.warn("<needle-engine> disconnected");
129
- this._context?.dispose();
127
+ const keepAlive = this.getAttribute("keep-alive");
128
+ if (debug) console.warn("<needle-engine> disconnected, keep-alive:", keepAlive);
129
+ if (keepAlive === undefined || keepAlive !== "true") {
130
+ console.warn("<needle-engine> dispose!");
131
+ this._context?.dispose();
132
+ this._context = null!;
133
+ this._lastSourceFiles = null;
134
+ this._loadId += 1;
135
+ }
130
136
  }
131
137
 
132
138
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -173,7 +179,13 @@
173
179
 
174
180
  private async onLoad() {
175
181
 
182
+ if (!this.isConnected) return;
176
183
  if (!this._context) {
184
+ if(debug) console.warn("Create new context");
185
+ this._context = new Context({ domElement: this });
186
+ }
187
+
188
+ if (!this._context) {
177
189
  console.error("Needle Engine: Context not initialized");
178
190
  return;
179
191
  }
src/engine/engine_input.ts CHANGED
@@ -215,7 +215,7 @@
215
215
  }
216
216
 
217
217
  private _pointerIsActive(index: number) {
218
- if(index < 0) return false;
218
+ if (index < 0) return false;
219
219
  return this._pointerPressed[index] || this._pointerDown[index] || this._pointerUp[index];
220
220
  }
221
221
 
@@ -330,45 +330,52 @@
330
330
  constructor(context: Context) {
331
331
  super();
332
332
  this.context = context;
333
- this.context.post_render_callbacks.push(this.onEndOfFrame.bind(this));
333
+ this.context.post_render_callbacks.push(this.onEndOfFrame);
334
334
 
335
- // const eventElement = this.context.renderer.domElement;
335
+ window.addEventListener('touchstart', this.onTouchStart, false);
336
+ window.addEventListener('touchmove', this.onTouchMove, { passive: true });
337
+ window.addEventListener('touchend', this.onTouchUp, false);
336
338
 
337
- // this.context.renderer.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), false);
338
- // this.context.renderer.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
339
- // this.context.renderer.domElement.addEventListener('mouseup', this.onMouseUp.bind(this), false);
339
+ window.addEventListener('mousedown', this.onMouseDown, false);
340
+ window.addEventListener('mousemove', this.onMouseMove, false);
341
+ window.addEventListener('mouseup', this.onMouseUp, false);
342
+ window.addEventListener('wheel', this.onMouseWheel, { passive: true });
340
343
 
341
- window.addEventListener('touchstart', this.onTouchStart.bind(this), false);
342
- window.addEventListener('touchmove', this.onTouchMove.bind(this), { passive: true });
343
- window.addEventListener('touchend', this.onTouchUp.bind(this), false);
344
+ window.addEventListener("keydown", this.onKeyDown, false);
345
+ window.addEventListener("keypress", this.onKeyPressed, false);
346
+ window.addEventListener("keyup", this.onKeyUp, false);
344
347
 
345
- window.addEventListener('mousedown', this.onMouseDown.bind(this), false);
346
- window.addEventListener('mousemove', this.onMouseMove.bind(this), false);
347
- window.addEventListener('mouseup', this.onMouseUp.bind(this), false);
348
- window.addEventListener('wheel', this.onMouseWheel.bind(this), { passive: true });
348
+ // e.g. when using sharex to capture we loose focus thus dont get e.g. key up events
349
+ window.addEventListener('blur', this.onLostFocus);
350
+ }
349
351
 
350
- window.addEventListener("keydown", this.onKeyDown.bind(this), false);
351
- window.addEventListener("keypress", this.onKeyPressed.bind(this), false);
352
- window.addEventListener("keyup", this.onKeyUp.bind(this), false);
352
+ dispose() {
353
+ const index = this.context.post_render_callbacks.indexOf(this.onEndOfFrame);
354
+ if (index >= 0) this.context.post_render_callbacks.splice(index, 1);
353
355
 
354
- // e.g. when using sharex to capture we loose focus thus dont get e.g. key up events
355
- window.addEventListener('blur', this.onLostFocus.bind(this));
356
+ window.removeEventListener('touchstart', this.onTouchStart, false);
357
+ window.removeEventListener('touchmove', this.onTouchMove, false);
358
+ window.removeEventListener('touchend', this.onTouchUp, false);
356
359
 
357
- // setTimeout(() => {
358
- // this.createPointerDown({ pointerId: 0, button: 0, clientX: 0, clientY: 0, pointerType: "mouse" });
359
- // setTimeout(() => {
360
- // this.createPointerUp({ pointerId: 0, button: 0, clientX: 0, clientY: 0, pointerType: "mouse" });
361
- // }, 1000);
362
- // }, 2000);
360
+ window.removeEventListener('mousedown', this.onMouseDown, false);
361
+ window.removeEventListener('mousemove', this.onMouseMove, false);
362
+ window.removeEventListener('mouseup', this.onMouseUp, false);
363
+ window.removeEventListener('wheel', this.onMouseWheel, false);
364
+
365
+ window.removeEventListener("keydown", this.onKeyDown, false);
366
+ window.removeEventListener("keypress", this.onKeyPressed, false);
367
+ window.removeEventListener("keyup", this.onKeyUp, false);
368
+
369
+ window.removeEventListener('blur', this.onLostFocus);
363
370
  }
364
371
 
365
- private onLostFocus() {
372
+ private onLostFocus = () => {
366
373
  for (const kp in this.keysPressed) {
367
374
  this.keysPressed[kp].pressed = false;
368
375
  }
369
376
  }
370
377
 
371
- private onEndOfFrame() {
378
+ private onEndOfFrame = () => {
372
379
  for (let i = 0; i < this._pointerUp.length; i++)
373
380
  this._pointerUp[i] = false;
374
381
  for (let i = 0; i < this._pointerDown.length; i++)
@@ -385,7 +392,7 @@
385
392
  this._mouseWheelDeltaY[i] = 0;
386
393
  }
387
394
 
388
- private canReceiveInput(evt : Event) {
395
+ private canReceiveInput(evt: Event) {
389
396
  // If the user has HTML objects ontop of the canvas
390
397
  // if(evt.target === this.context.renderer.domElement) return true;
391
398
  // const css = window.getComputedStyle(evt.target as HTMLElement);
@@ -395,7 +402,7 @@
395
402
 
396
403
  private keysPressed: { [key: KeyCode | string]: { pressed: boolean, frame: number, startFrame: number, key: string, code: KeyCode | string } } = {};
397
404
 
398
- private onKeyDown(evt: KeyboardEvent) {
405
+ private onKeyDown = (evt: KeyboardEvent) => {
399
406
  if (!this.context.application.hasFocus)
400
407
  return;
401
408
  const ex = this.keysPressed[evt.code];
@@ -403,7 +410,7 @@
403
410
  this.keysPressed[evt.code] = { pressed: true, frame: this.context.time.frameCount + 1, startFrame: this.context.time.frameCount + 1, key: evt.key, code: evt.code };
404
411
  this.onDispatchEvent(InputEvents.KeyDown, new KeyEventArgs(evt));
405
412
  }
406
- private onKeyPressed(evt: KeyboardEvent) {
413
+ private onKeyPressed = (evt: KeyboardEvent) => {
407
414
  if (!this.context.application.hasFocus)
408
415
  return;
409
416
  const p = this.keysPressed[evt.code];
@@ -413,7 +420,7 @@
413
420
  this.onDispatchEvent(InputEvents.KeyPressed, new KeyEventArgs(evt));
414
421
 
415
422
  }
416
- private onKeyUp(evt: KeyboardEvent) {
423
+ private onKeyUp = (evt: KeyboardEvent) => {
417
424
  if (!this.context.application.hasFocus)
418
425
  return;
419
426
  const p = this.keysPressed[evt.code];
@@ -423,8 +430,8 @@
423
430
  this.onDispatchEvent(InputEvents.KeyUp, new KeyEventArgs(evt));
424
431
  }
425
432
 
426
- private onMouseWheel(evt: WheelEvent) {
427
- if(this.canReceiveInput(evt) === false) return;
433
+ private onMouseWheel = (evt: WheelEvent) => {
434
+ if (this.canReceiveInput(evt) === false) return;
428
435
  if (this._mouseWheelDeltaY.length <= 0) this._mouseWheelDeltaY.push(0);
429
436
  if (this._mouseWheelChanged.length <= 0) this._mouseWheelChanged.push(false);
430
437
  this._mouseWheelChanged[0] = true;
@@ -432,9 +439,9 @@
432
439
  this._mouseWheelDeltaY[0] = current + evt.deltaY;
433
440
  }
434
441
 
435
- private onTouchStart(evt: TouchEvent) {
442
+ private onTouchStart = (evt: TouchEvent) => {
436
443
  if (evt.changedTouches.length <= 0) return;
437
- if(this.canReceiveInput(evt) === false) return;
444
+ if (this.canReceiveInput(evt) === false) return;
438
445
  for (let i = 0; i < evt.changedTouches.length; i++) {
439
446
  const touch = evt.changedTouches[i];
440
447
  const id = this.getPointerIndex(touch.identifier)
@@ -445,7 +452,7 @@
445
452
  }
446
453
  }
447
454
 
448
- private onTouchMove(evt: TouchEvent) {
455
+ private onTouchMove = (evt: TouchEvent) => {
449
456
  if (evt.changedTouches.length <= 0) return;
450
457
  for (let i = 0; i < evt.changedTouches.length; i++) {
451
458
  const touch = evt.changedTouches[i];
@@ -455,7 +462,7 @@
455
462
  }
456
463
  }
457
464
 
458
- private onTouchUp(evt: TouchEvent) {
465
+ private onTouchUp = (evt: TouchEvent) => {
459
466
  if (evt.changedTouches.length <= 0) return;
460
467
  for (let i = 0; i < evt.changedTouches.length; i++) {
461
468
  const touch = evt.changedTouches[i];
@@ -469,21 +476,21 @@
469
476
  }
470
477
  }
471
478
 
472
- private onMouseDown(evt: MouseEvent) {
479
+ private onMouseDown = (evt: MouseEvent) => {
473
480
  if (evt.defaultPrevented) return;
474
- if(this.canReceiveInput(evt) === false) return;
481
+ if (this.canReceiveInput(evt) === false) return;
475
482
  let i = evt.button;
476
483
  this.onDown({ button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt });
477
484
  }
478
485
 
479
- private onMouseMove(evt: MouseEvent) {
486
+ private onMouseMove = (evt: MouseEvent) => {
480
487
  if (evt.defaultPrevented) return;
481
488
  let i = evt.button;
482
489
  const args: PointerEventArgs = { button: i, clientX: evt.clientX, clientY: evt.clientY, pointerType: PointerType.Mouse, source: evt, movementX: evt.movementX, movementY: evt.movementY };
483
490
  this.onMove(args);
484
491
  }
485
492
 
486
- private onMouseUp(evt: MouseEvent) {
493
+ private onMouseUp = (evt: MouseEvent) => {
487
494
  if (evt.defaultPrevented) return;
488
495
  let i = evt.button;
489
496
  if (!this.isNewEvent(evt.timeStamp, i, this._pointerUpTimestamp)) return;
@@ -504,7 +511,7 @@
504
511
  const px = e.clientX;
505
512
  const py = e.clientY;
506
513
  const isInRect = px >= rect.x && px <= rect.right && py >= rect.y && py <= rect.bottom;
507
- if(debug && !isInRect) console.log("Not in rect", rect, px, py);
514
+ if (debug && !isInRect) console.log("Not in rect", rect, px, py);
508
515
  return isInRect;
509
516
 
510
517
  }
@@ -518,7 +525,7 @@
518
525
  const upTime = this._pointerUpTimestamp[evt.button];
519
526
  if (upTime > 0 && upTime === evt.source?.timeStamp) {
520
527
  // we received an UP event for a touch, ignore this DOWN event
521
- if(debug) console.log("Ignoring mouse.down for touch.up");
528
+ if (debug) console.log("Ignoring mouse.down for touch.up");
522
529
  return;
523
530
  }
524
531
  }
@@ -678,7 +685,7 @@
678
685
  }
679
686
  }
680
687
 
681
- export declare type KeyCode =
688
+ export declare type KeyCode =
682
689
  | "Tab"
683
690
  | "Enter"
684
691
  | "ShiftLeft"
src/engine/engine_license.ts CHANGED
@@ -38,7 +38,7 @@
38
38
 
39
39
  async function showLicenseInfo(ctx: IContext) {
40
40
  try {
41
- if (hasProLicense() !== true) return onNonCommercialVersionDetected(ctx);
41
+ if (hasCommercialLicense() !== true) return onNonCommercialVersionDetected(ctx);
42
42
  }
43
43
  catch (err) {
44
44
  if (debug) console.log("License check failed", err)
src/engine/engine_physics_rapier.ts CHANGED
@@ -455,8 +455,10 @@
455
455
 
456
456
  clearCaches() {
457
457
  this._meshCache.clear();
458
- this.eventQueue?.free();
459
- this.world?.free();
458
+ if (this.eventQueue?.raw)
459
+ this.eventQueue?.free();
460
+ if (this.world?.bodies)
461
+ this.world?.free();
460
462
  }
461
463
 
462
464
  async addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
@@ -549,6 +551,7 @@
549
551
  if (Math.abs(scale.x - 1) > 0.0001 || Math.abs(scale.y - 1) > 0.0001 || Math.abs(scale.z - 1) > 0.0001) {
550
552
  const key = geo.uuid + "_" + scale.x + "_" + scale.y + "_" + scale.z + "_" + convex;
551
553
  if (this._meshCache.has(key)) {
554
+ if(debugPhysics) console.warn("Use cached mesh collider")
552
555
  positions = this._meshCache.get(key)!;
553
556
  }
554
557
  else {
src/engine-components/Skybox.ts CHANGED
@@ -74,13 +74,17 @@
74
74
  }
75
75
  return null;
76
76
  }
77
+ async function disposeCachedTexture(tex: Promise<Texture>) {
78
+ const texture = await tex;
79
+ setDisposable(texture, true);
80
+ disposeObjectResources(texture);
81
+ }
77
82
  function registerLoadedTexture(src: string, texture: Promise<Texture>) {
78
83
  const cache = ensureGlobalCache();
79
84
  // Make sure the cache doesnt get too big
80
85
  while (cache.length > 5) {
81
86
  const entry = cache.shift();
82
- entry?.texture.then(t => setDisposable(t, true));
83
- disposeObjectResources(entry?.texture);
87
+ if (entry) { disposeCachedTexture(entry.texture); }
84
88
  }
85
89
  texture.then(t => setDisposable(t, false));
86
90
  cache.push({ src, texture });