Needle Engine

Changes between version 3.38.0-alpha.1 and 3.38.0-alpha.2
Files changed (5) hide show
  1. plugins/common/license.cjs +34 -15
  2. src/engine-components/AudioListener.ts +59 -15
  3. src/engine/engine_application.ts +11 -2
  4. src/engine/engine_physics_rapier.ts +6 -1
  5. src/engine/engine_utils.ts +32 -8
plugins/common/license.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  const { getMeta } = require("./config.cjs");
2
+ const https = require('https');
2
3
 
4
+
3
5
  /**
4
6
  * @param {string} code
5
7
  * @param {string | null | undefined} licenseType
@@ -49,6 +51,10 @@
49
51
  return license;
50
52
  }
51
53
 
54
+ if (!license) {
55
+ return null;
56
+ }
57
+
52
58
  if (!license.key) {
53
59
  console.warn("WARN: License key is missing.");
54
60
  return null;
@@ -59,28 +65,23 @@
59
65
  }
60
66
 
61
67
  console.log("INFO: Resolve license for " + obscure(license.id + "::" + license.key));
62
- const url = await fetch(LICENSE_ENDPOINT, { method: "GET" });
63
- if (!url.ok) {
68
+ const url = await fetch(LICENSE_ENDPOINT, { method: "GET" }).catch(console.error);
69
+ if (!url) {
64
70
  console.warn("WARN: Failed to fetch license URL from endpoint. " + url.statusText);
65
71
  return null;
66
72
  }
67
- const str = await url.text();
68
- if (!str) {
69
- console.warn("WARN: Failed to fetch license URL from endpoint. " + url.statusText);
73
+ const licenseRequestUrl = `${url}?email=${license.id}&key=${license.key}&version=2`;
74
+ const licenseResponse = await fetch(licenseRequestUrl, { method: "GET" }).catch(console.error);
75
+ if (!licenseResponse) {
76
+ console.warn("WARN: Failed to fetch license");
70
77
  return null;
71
78
  }
72
- const licenseRequestUrl = `${str}?email=${license.id}&key=${license.key}&version=2`;
73
- const req = await fetch(licenseRequestUrl, { method: "GET" });
74
- if (!req.ok) {
75
- console.warn("WARN: Failed to fetch license: " + req.statusText);
76
- return null;
77
- }
78
79
  /** @type {{license:string}} */
79
- const licenseResponse = await req.json();
80
+ const licenseJson = JSON.parse(licenseResponse);
80
81
  console.log("\n");
81
- if (licenseResponse.license) {
82
- console.log(`INFO: Successfully received \"${licenseResponse.license?.toUpperCase()}\" license`)
83
- return licenseResponse.license;
82
+ if (licenseJson.license) {
83
+ console.log(`INFO: Successfully received \"${licenseJson.license?.toUpperCase()}\" license`)
84
+ return licenseJson.license;
84
85
  }
85
86
  console.warn("WARN: Received invalid license.");
86
87
  return null;
@@ -94,4 +95,22 @@
94
95
  const start = str.substring(0, 3);
95
96
  const end = str.substring(str.length - 3);
96
97
  return start + "***" + end;
98
+ }
99
+
100
+
101
+ // NODE 16 doesn't support fetch yet
102
+ function fetch(url, options) {
103
+ return new Promise((resolve, reject) => {
104
+ https.get(url, options, (res) => {
105
+ let data = '';
106
+ res.on('data', (chunk) => {
107
+ data += chunk;
108
+ });
109
+ res.on('end', () => {
110
+ resolve(data);
111
+ });
112
+ }).on("error", (err) => {
113
+ reject(err);
114
+ });
115
+ });
97
116
  }
src/engine-components/AudioListener.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AudioListener as ThreeAudioListener } from "three";
2
2
 
3
- import { AudioSource } from "./AudioSource.js";
3
+ import { Application } from "../engine/engine_application.js";
4
4
  import { Camera } from "./Camera.js";
5
5
  import { Behaviour, GameObject } from "./Component.js";
6
6
 
@@ -9,6 +9,10 @@
9
9
  */
10
10
  export class AudioListener extends Behaviour {
11
11
 
12
+ /**
13
+ * Gets the existing or creates a new {@link ThreeAudioListener} instance
14
+ * @returns The {@link ThreeAudioListener} instance
15
+ */
12
16
  get listener(): ThreeAudioListener {
13
17
  if (this._listener == null)
14
18
  this._listener = new ThreeAudioListener();
@@ -17,20 +21,60 @@
17
21
 
18
22
  private _listener: ThreeAudioListener | null = null;
19
23
 
20
- awake() {
21
- AudioSource.registerWaitForAllowAudio(() => {
22
- if (this.destroyed) return;
23
- const listener = this.listener;
24
- if (listener == null) return;
25
- // if the listener is already parented to some object d0nt change it
26
- if (listener.parent) return;
24
+ /** @internal */
25
+ onEnable(): void {
26
+ Application.registerWaitForInteraction(this.onInteraction);
27
+ this.addListenerIfItExists();
28
+ }
27
29
 
28
- const cam = this.context.mainCameraComponent || GameObject.getComponentInParent(this.gameObject, Camera);
29
- if (cam?.cam) {
30
- cam.cam.add(listener);
31
- }
32
- else
33
- this.gameObject.add(listener);
34
- });
30
+ /** @internal */
31
+ onDisable(): void {
32
+ Application.unregisterWaitForInteraction(this.onInteraction);
33
+ this.removeListenerIfItExists();
35
34
  }
35
+
36
+ private onInteraction = () => {
37
+ if (this.destroyed) return;
38
+ const listener = this.listener;
39
+ if (listener == null) return;
40
+ this.addListenerIfItExists();
41
+ }
42
+
43
+ private addListenerIfItExists() {
44
+ const listener = this._listener;
45
+ if (!listener) return;
46
+ // if the listener is already parented to some object dont change it
47
+ if (listener?.parent) return;
48
+
49
+ const cam = this.context.mainCameraComponent || GameObject.getComponentInParent(this.gameObject, Camera);
50
+ if (cam?.cam) {
51
+ cam.cam.add(listener);
52
+ }
53
+ else {
54
+ this.gameObject.add(listener);
55
+ }
56
+
57
+ // connect the listeners audio nodes
58
+ if (!listener.filter) {
59
+ listener.gain.connect(listener.context.destination);
60
+ }
61
+ else {
62
+ listener.gain.connect(listener.filter);
63
+ listener.filter.connect(listener.context.destination);
64
+ }
65
+ }
66
+
67
+ private removeListenerIfItExists() {
68
+ const listener = this._listener;
69
+ if (!listener) return;
70
+ listener.removeFromParent();
71
+
72
+ // disconnect the listeners audio nodes
73
+ if (listener.filter) {
74
+ listener.filter.disconnect();
75
+ }
76
+ if (listener.gain) {
77
+ listener.gain.disconnect();
78
+ }
79
+ }
36
80
  }
src/engine/engine_application.ts CHANGED
@@ -36,6 +36,8 @@
36
36
  return userInteractionRegistered;
37
37
  }
38
38
 
39
+ /** @deprecated use Application.registerWaitForInteraction instead */
40
+ public static readonly registerWaitForAllowAudio = Application.registerWaitForInteraction;
39
41
  /**
40
42
  * Register a callback that will be called when the user interacts with the page (click, touch, keypress, etc).
41
43
  * If the user has already interacted with the page, the callback will be called immediately.
@@ -51,8 +53,15 @@
51
53
  userInteractionCallbacks.push(cb);
52
54
  }
53
55
  }
54
- /** @deprecated use Application.registerWaitForInteraction instead */
55
- public static readonly registerWaitForAllowAudio = Application.registerWaitForInteraction;
56
+ /**
57
+ * Unregister a callback that was previously registered with registerWaitForInteraction.
58
+ */
59
+ public static unregisterWaitForInteraction(cb: Function) {
60
+ const index = userInteractionCallbacks.indexOf(cb);
61
+ if (index !== -1) {
62
+ userInteractionCallbacks.splice(index, 1);
63
+ }
64
+ }
56
65
 
57
66
  private _mute: boolean = false;
58
67
  /** audio muted? */
src/engine/engine_physics_rapier.ts CHANGED
@@ -1031,7 +1031,12 @@
1031
1031
  // https://rapier.rs/docs/user_guides/javascript/integration_parameters/#dt
1032
1032
  this.world.timestep = Mathf.lerp(this.world.timestep, dt, 0.8);
1033
1033
  }
1034
- this.world.step(this.eventQueue);
1034
+ try {
1035
+ this.world.step(this.eventQueue);
1036
+ }
1037
+ catch (e) {
1038
+ console.warn("Error running physics step", e);
1039
+ }
1035
1040
  this._isUpdatingPhysicsWorld = false;
1036
1041
  }
1037
1042
 
src/engine/engine_utils.ts CHANGED
@@ -43,16 +43,40 @@
43
43
  }
44
44
  }
45
45
 
46
+ let saveParams: boolean = false;
47
+ const requestedParams: Array<string> = new Array();
46
48
 
49
+ if (typeof window !== "undefined") {
50
+ setTimeout(() => {
51
+ const debugHelp = getParam("debughelp");
52
+ if (saveParams) {
53
+ const params = {};
54
+ const url = new URL(window.location.href);
55
+ const exampleUrlT = new URL(url);
56
+ exampleUrlT.searchParams.append("help", "");
57
+ const exampleUrl = url.toString().replace(/=$|=(?=&)/g, '');
58
+ for (const param of requestedParams) {
59
+ const url2 = new URL(url);
60
+ url2.searchParams.append(param, "");
61
+ params[param] = url2.toString().replace(/=$|=(?=&)/g, '');
62
+ }
63
+ console.log(
64
+ "🌵 ?help: Debug Options for Needle Engine.\n" +
65
+ "Append any of these parameters to the URL to enable specific debug options.\n" +
66
+ `Example: ${exampleUrl} will show an onscreen console window.`);
67
+ console.group("Available URL parameters:");
68
+ for (const key in params) {
69
+ console.groupCollapsed(key);
70
+ // Needs to be a separate log, otherwise Safari doesn't turn the next line into a URL:
71
+ console.log("Reload with this flag enabled:");
72
+ console.log(params[key]);
73
+ console.groupEnd();
74
+ }
75
+ console.groupEnd();
76
+ }
77
+ }, 100);
78
+ }
47
79
 
48
- let saveParams: boolean = false;
49
- const requestedParams: Array<string> = [];
50
- setTimeout(() => {
51
- if (saveParams) {
52
- console.log(requestedParams.sort());
53
- }
54
- }, 100);
55
-
56
80
  export function getUrlParams() {
57
81
  // "window" may not exist in node.js
58
82
  return new URLSearchParams(globalThis.location?.search);