@needle-tools/engine 4.8.2 → 4.8.3-next.64a46ce

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,8 +1,8 @@
1
-
2
1
  const peerjsString = `/* needle: fix for peerjs */ window.global = window; var parcelRequire;`
3
2
 
4
3
  /**
5
4
  * @param {import('../types').userSettings} userSettings
5
+ * @returns {import("vite").Plugin | undefined}
6
6
  */
7
7
  export const needlePeerjs = (command, config, userSettings) => {
8
8
 
@@ -23,7 +23,65 @@ export const needlePeerjs = (command, config, userSettings) => {
23
23
  },
24
24
  ]
25
25
  }
26
- }
26
+ },
27
+ },
28
+ enforce: "pre",
29
+ transform: (code, id) => {
30
+ return patchWebRTCAdapterForGemini(code, id);
27
31
  }
28
32
  }
29
33
  }
34
+
35
+
36
+
37
+
38
+ /**
39
+ * @param {string} code
40
+ * @param {string} id
41
+ * @returns {string | undefined}
42
+ */
43
+ function patchWebRTCAdapterForGemini(code, id) {
44
+ // Match navigator.mediaDevices.getUserMedia assignments
45
+ const assignmentRegex = /(navigator\d{0,1})\.mediaDevices\.getUserMedia\s*=\s*function/gm;
46
+
47
+ let didTransform = false;
48
+ let transformedCode = code;
49
+ let match;
50
+
51
+ while ((match = assignmentRegex.exec(code)) !== null) {
52
+ const navigatorRef = match[1];
53
+ const matchStart = match.index;
54
+
55
+ // Find the end of the function assignment
56
+ let braceCount = 0;
57
+ let inFunction = false;
58
+ let endIndex = matchStart;
59
+
60
+ for (let i = matchStart; i < code.length; i++) {
61
+ const char = code[i];
62
+ if (char === '{') {
63
+ braceCount++;
64
+ inFunction = true;
65
+ } else if (char === '}') {
66
+ braceCount--;
67
+ if (inFunction && braceCount === 0) {
68
+ endIndex = i + 1;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+
74
+ const originalAssignment = code.substring(matchStart, endIndex);
75
+ const wrappedAssignment = `if (Object.getOwnPropertyDescriptor(${navigatorRef}.mediaDevices, "getUserMedia")?.writable) {\n ${originalAssignment}\n}`;
76
+ didTransform = true;
77
+ transformedCode = transformedCode.replace(originalAssignment, wrappedAssignment);
78
+ // console.log("-------------------------------\nTRANSFORMED\n", id, "\nOriginal:", originalAssignment, "\nWrapped:", wrappedAssignment, "\n\n");
79
+ }
80
+
81
+ if(!didTransform) return undefined;
82
+
83
+ console.log("[needle:peerjs] Fixed WebRTC assignment");
84
+
85
+ return transformedCode;
86
+
87
+ }
@@ -1,40 +1,46 @@
1
1
 
2
2
  async function generatePoster() {
3
- const { screenshot2 } = await import("@needle-tools/engine");
4
3
 
5
4
  try {
6
- const needleEngine = document.querySelector("needle-engine");
7
- if (!needleEngine) return null;
5
+ const { screenshot2, onStart } = await import("@needle-tools/engine");
8
6
 
9
7
  // Keep in sync with og:image:width meta tags
10
8
  // https://developers.facebook.com/docs/sharing/best-practices/
11
9
  const width = 1080;
12
10
  const height = 1080;
13
- const context = await needleEngine.getContext();
14
11
 
15
- // wait a fixed time for e.g. fonts to load
16
- while(context.time.frameCount < 2)
17
- await new Promise((resolve) => setTimeout(resolve, 200));
12
+ return new Promise(res => {
13
+ onStart(async context => {
18
14
 
19
- const mimeType = "image/webp";
15
+ if (context.lodsManager.manager) {
16
+ await context.lodsManager.manager.awaitLoading({ frames: 20, maxPromisesPerObject: 2 });
17
+ }
20
18
 
21
- // We're reading back as a blob here because that's async, and doesn't seem
22
- // to stress the GPU so much on memory-constrained devices.
23
- const blob = await screenshot2({context, width, height, mimeType, type: "blob"});
24
-
25
- // We can only send a DataURL, so we need to convert it back here.
26
- const dataUrl = await new Promise((resolve, reject) => {
27
- const reader = new FileReader();
28
- reader.onload = function() {
29
- resolve(reader.result);
30
- };
31
- reader.onloadend = function() {
32
- resolve(null);
33
- };
34
- reader.readAsDataURL(blob);
35
- });
19
+ const mimeType = "image/webp";
20
+
21
+ // We're reading back as a blob here because that's async, and doesn't seem
22
+ // to stress the GPU so much on memory-constrained devices.
23
+ const blob = await screenshot2({ context, width, height, mimeType, type: "blob" });
24
+
25
+ // We can only send a DataURL, so we need to convert it back here.
26
+ const dataUrl = await new Promise((resolve, reject) => {
27
+ const reader = new FileReader();
28
+ reader.onload = function () {
29
+ resolve(reader.result);
30
+ };
31
+ reader.onloadend = function () {
32
+ resolve(null);
33
+ };
34
+ reader.readAsDataURL(blob);
35
+ });
36
+
37
+
38
+ res(dataUrl);
39
+
40
+
41
+ }, { once: true });
42
+ })
36
43
 
37
- return dataUrl;
38
44
  }
39
45
  catch (e) {
40
46
  console.error(e);
@@ -42,33 +48,11 @@ async function generatePoster() {
42
48
  }
43
49
  }
44
50
 
45
- async function sendPoster() {
46
- const blob = await generatePoster();
47
- import.meta.hot.send("needle:screenshot", { data: blob });
48
- }
49
51
 
50
- // communicate via vite
51
52
  if (import.meta.hot) {
52
- // wait for needle engine to be fully loaded
53
- const needleEngine = document.querySelector("needle-engine");
54
- needleEngine?.addEventListener("loadfinished", () => {
55
- // wait a moment
56
- setTimeout(() => {
57
- sendPoster();
58
- }, 200);
59
- });
60
-
61
- // for debugging: build extra button with dev-only options
62
- /*
63
- var button = document.createElement("button");
64
- button.id = "send-msg";
65
- button.innerHTML = "Generate Poster";
66
- button.style.position = "fixed";
67
- button.style.zIndex = "9999";
68
- document.body.appendChild(button);
69
-
70
- document.querySelector("#send-msg").addEventListener("click", async () => {
71
- sendPoster();
72
- });
73
- */
53
+ async function run() {
54
+ const blob = await generatePoster();
55
+ import.meta.hot.send("needle:screenshot", { data: blob });
56
+ }
57
+ run();
74
58
  }
@@ -36,7 +36,7 @@ export const needlePoster = (command, config, userSettings) => {
36
36
  }
37
37
  try {
38
38
  const targetPath = "./" + getPosterPath();
39
- console.debug(`Received poster, saving to ${targetPath}`);
39
+ console.debug(`[needle:poster] Saving automatic poster to ${targetPath}`);
40
40
  // remove data:image/png;base64, from the beginning of the string
41
41
  if (targetPath.endsWith(".webp"))
42
42
  data.data = data.data.replace(/^data:image\/webp;base64,/, "");
@@ -47,10 +47,9 @@ export const needlePoster = (command, config, userSettings) => {
47
47
  fs.mkdirSync(dir, { recursive: true })
48
48
  }
49
49
  fs.writeFileSync(targetPath, Buffer.from(data.data, "base64"));
50
- console.debug("Saved poster to file");
51
50
  }
52
51
  catch (err) {
53
- console.error("Failed to save poster", err.message);
52
+ console.error("[needle:poster] Failed to save poster", err.message);
54
53
  }
55
54
  });
56
55
  },
@@ -980,14 +980,14 @@ export class Context implements IContext {
980
980
  if (this.isInXR) return true;
981
981
  if (!this._isVisible) return false;
982
982
  // Make sure not to call getComputedStyle multiple times per frame
983
- if (this.time.frame === this._lastStyleComputedFrame) return this._lastStyleComputedResult;
984
- this._lastStyleComputedFrame = this.time.frame;
983
+ if (!this._needsVisibleUpdate && this._lastStyleComputedResult !== undefined) return this._lastStyleComputedResult;
984
+ this._needsVisibleUpdate = false;
985
985
  const style = getComputedStyle(this.domElement);
986
986
  this._lastStyleComputedResult = style.visibility !== "hidden" && style.display !== "none" && style.opacity !== "0";
987
987
  return this._lastStyleComputedResult;
988
988
  }
989
- private _lastStyleComputedFrame: number = -1;
990
- private _lastStyleComputedResult: boolean = true;
989
+ private _needsVisibleUpdate: boolean = true;
990
+ private _lastStyleComputedResult: boolean | undefined = undefined;
991
991
 
992
992
  private _createId: number = 0;
993
993
  private async internalOnCreate(opts?: ContextCreateArgs): Promise<boolean> {
@@ -1391,6 +1391,7 @@ export class Context implements IContext {
1391
1391
  this.renderer.info.reset();
1392
1392
  }
1393
1393
 
1394
+ this._needsVisibleUpdate = true;
1394
1395
 
1395
1396
  const sessionStarted = frame !== null && this._xrFrame === null;
1396
1397
  this._xrFrame = frame;
@@ -1420,8 +1421,8 @@ export class Context implements IContext {
1420
1421
 
1421
1422
  Context.Current = this;
1422
1423
  this.time.update();
1423
- if (debugframerate)
1424
- console.log("FPS", (this.time.smoothedFps).toFixed(0));
1424
+
1425
+ if (debugframerate) console.log("FPS", (this.time.smoothedFps).toFixed(0));
1425
1426
 
1426
1427
 
1427
1428
  looputils.processNewScripts(this);
@@ -1460,7 +1461,6 @@ export class Context implements IContext {
1460
1461
  }
1461
1462
  this.executeCoroutines(FrameEvent.EarlyUpdate);
1462
1463
  invokeLifecycleFunctions(this, FrameEvent.EarlyUpdate);
1463
- if (this.onHandlePaused()) return false;
1464
1464
 
1465
1465
  this._currentFrameEvent = FrameEvent.Update;
1466
1466
 
@@ -1474,8 +1474,6 @@ export class Context implements IContext {
1474
1474
  }
1475
1475
  this.executeCoroutines(FrameEvent.Update);
1476
1476
  invokeLifecycleFunctions(this, FrameEvent.Update);
1477
- if (this.onHandlePaused()) return false;
1478
-
1479
1477
  this._currentFrameEvent = FrameEvent.LateUpdate;
1480
1478
 
1481
1479
  for (let i = 0; i < this.scripts_lateUpdate.length; i++) {
@@ -1490,7 +1488,6 @@ export class Context implements IContext {
1490
1488
  // this.mainLight = null;
1491
1489
  this.executeCoroutines(FrameEvent.LateUpdate);
1492
1490
  invokeLifecycleFunctions(this, FrameEvent.LateUpdate);
1493
- if (this.onHandlePaused()) return false;
1494
1491
 
1495
1492
  if (this.physicsSteps === undefined) {
1496
1493
  this.physicsSteps = 1;
@@ -1499,8 +1496,6 @@ export class Context implements IContext {
1499
1496
  this.internalUpdatePhysics(this.physicsSteps);
1500
1497
  }
1501
1498
 
1502
- if (this.onHandlePaused()) return false;
1503
-
1504
1499
  if (this.isVisibleToUser || this.runInBackground) {
1505
1500
 
1506
1501
  this._currentFrameEvent = FrameEvent.OnBeforeRender;
@@ -1607,8 +1602,6 @@ export class Context implements IContext {
1607
1602
  }
1608
1603
  this.handleRendererContextLost();
1609
1604
 
1610
- if(this.time.frame % 10 !== 0) return;
1611
-
1612
1605
  this._isRendering = true;
1613
1606
  this.renderRequiredTextures();
1614
1607
 
@@ -8,7 +8,7 @@ import { showBalloonMessage } from "./debug/index.js";
8
8
  import { Application } from "./engine_application.js";
9
9
  import { Context } from "./engine_context.js";
10
10
  import type { IModel } from "./engine_networking_types.js";
11
- import { type IComponent,isComponent } from "./engine_types.js";
11
+ import { type IComponent, isComponent } from "./engine_types.js";
12
12
  import { getParam } from "./engine_utils.js";
13
13
 
14
14
 
@@ -113,8 +113,8 @@ class CallHandle extends EventDispatcher<any> {
113
113
  }
114
114
  }
115
115
 
116
- function applySdpTransform(sdp){
117
- sdp = sdp.replace("a=fmtp:111 minptime=10;useinbandfec=1","a=fmtp:111 ptime=5;useinbandfec=1;stereo=1;maxplaybackrate=48000;maxaveragebitrat=128000;sprop-stereo=1");
116
+ function applySdpTransform(sdp) {
117
+ sdp = sdp.replace("a=fmtp:111 minptime=10;useinbandfec=1", "a=fmtp:111 ptime=5;useinbandfec=1;stereo=1;maxplaybackrate=48000;maxaveragebitrat=128000;sprop-stereo=1");
118
118
  return sdp;
119
119
  }
120
120
 
@@ -237,10 +237,19 @@ export class PeerHandle extends EventDispatcher<any> {
237
237
  this.context = context;
238
238
  this.id = id;
239
239
  this.setupPeer();
240
- navigator["getUserMedia"] = (
241
- navigator["getUserMedia"] || navigator["webkitGetUserMedia"] ||
242
- navigator["mozGetUserMedia"] || navigator["msGetUserMedia"]
243
- );
240
+ const isGetUserMediaWriteable = Object.getOwnPropertyDescriptor(navigator, "getUserMedia")?.writable;
241
+ try {
242
+ if (isGetUserMediaWriteable) {
243
+ navigator["getUserMedia"] = (
244
+ navigator["getUserMedia"] || navigator["webkitGetUserMedia"] ||
245
+ navigator["mozGetUserMedia"] || navigator["msGetUserMedia"]
246
+ );
247
+ }
248
+ else if (debug) console.warn("[PeerJs] getUserMedia is not writable");
249
+ }
250
+ catch (err) {
251
+ if (debug) console.error("[PeerJs] Error setting getUserMedia", err);
252
+ }
244
253
  }
245
254
 
246
255
  private _enabled: boolean = false;
@@ -479,7 +488,7 @@ export class NetworkedStreams extends EventDispatcher<any> {
479
488
  }
480
489
 
481
490
  if (!context) throw new Error("Failed to create NetworkedStreams because context is undefined");
482
- else if(!(context instanceof Context)) throw new Error("Failed to create NetworkedStreams because context is not an instance of Context");
491
+ else if (!(context instanceof Context)) throw new Error("Failed to create NetworkedStreams because context is not an instance of Context");
483
492
  if (!peer) throw new Error("Failed to create NetworkedStreams because peer is undefined");
484
493
 
485
494
  this.context = context;
@@ -565,6 +565,11 @@ export class OrbitControls extends Behaviour implements ICameraController {
565
565
  }
566
566
  }
567
567
 
568
+ onPausedChanged(isPaused: boolean): void {
569
+ if (!this._controls) return;
570
+ if (isPaused) this._controls.enabled = false;
571
+ }
572
+
568
573
 
569
574
  /** @internal */
570
575
  onBeforeRender() {