@needle-tools/engine 4.16.11 → 4.16.12
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.
- package/CHANGELOG.md +5 -0
- package/SKILL.md +64 -4
- package/dist/gltf-progressive-DLDvZ8Xm.umd.cjs +10 -0
- package/dist/{gltf-progressive-CzxjNmG6.js → gltf-progressive-DMK8kKVB.js} +352 -346
- package/dist/gltf-progressive-RPgYR6rv.min.js +10 -0
- package/dist/{needle-engine.bundle-Du3vf5L1.js → needle-engine.bundle-BfsnYTeP.js} +6960 -6823
- package/dist/{needle-engine.bundle-DOChN3At.umd.cjs → needle-engine.bundle-COr8D1vc.umd.cjs} +130 -130
- package/dist/{needle-engine.bundle-fP5tOMI1.min.js → needle-engine.bundle-DG1sGkxn.min.js} +131 -131
- package/dist/needle-engine.d.ts +11 -1
- package/dist/needle-engine.js +3 -3
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three-examples.js +52 -52
- package/dist/three-examples.min.js +4 -4
- package/dist/three-examples.umd.cjs +2 -2
- package/lib/engine/api.d.ts +2 -1
- package/lib/engine/api.js +1 -6
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_addressables.d.ts +32 -1
- package/lib/engine/engine_addressables.js +71 -9
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_application.d.ts +2 -0
- package/lib/engine/engine_application.js +7 -3
- package/lib/engine/engine_application.js.map +1 -1
- package/lib/engine/engine_context.js +25 -1
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_input.js +1 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_license.d.ts +1 -1
- package/lib/engine/engine_license.js +31 -5
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_lifecycle_functions_internal.js +20 -2
- package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
- package/lib/engine/engine_networking_streams.d.ts +5 -1
- package/lib/engine/engine_networking_streams.js +64 -17
- package/lib/engine/engine_networking_streams.js.map +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +5 -0
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
- package/lib/engine/webcomponents/fonts.d.ts +1 -1
- package/lib/engine/webcomponents/fonts.js +7 -0
- package/lib/engine/webcomponents/fonts.js.map +1 -1
- package/lib/engine/webcomponents/icons.js +8 -1
- package/lib/engine/webcomponents/icons.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +5 -0
- package/lib/engine/webcomponents/needle menu/needle-menu.js +31 -13
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.js +16 -4
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.d.ts +2 -0
- package/lib/engine/webcomponents/needle-engine.loading.js +10 -0
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/package.json +2 -2
- package/src/engine/api.ts +3 -1
- package/src/engine/engine_addressables.ts +75 -9
- package/src/engine/engine_application.ts +7 -2
- package/src/engine/engine_context.ts +25 -1
- package/src/engine/engine_input.ts +2 -2
- package/src/engine/engine_license.ts +28 -5
- package/src/engine/engine_lifecycle_functions_internal.ts +21 -2
- package/src/engine/engine_networking_streams.ts +64 -19
- package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +8 -1
- package/src/engine/webcomponents/fonts.ts +6 -0
- package/src/engine/webcomponents/icons.ts +9 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +30 -12
- package/src/engine/webcomponents/needle-engine.loading.ts +13 -1
- package/src/engine/webcomponents/needle-engine.ts +15 -5
- package/dist/gltf-progressive-BacJPTD6.umd.cjs +0 -10
- package/dist/gltf-progressive-BmXoz_HR.min.js +0 -10
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'three/examples/jsm/renderers/webgl-legacy/nodes/WebGLNodes.js';
|
|
2
2
|
|
|
3
|
+
import { NEEDLE_progressive } from '@needle-tools/gltf-progressive';
|
|
3
4
|
import type { EffectComposer } from "postprocessing";
|
|
4
5
|
import {
|
|
5
6
|
BufferGeometry, Camera, Color, DepthTexture, Group,
|
|
@@ -16,7 +17,7 @@ import { nodeFrame } from "three/examples/jsm/renderers/webgl-legacy/nodes/WebGL
|
|
|
16
17
|
import { initSpectorIfAvailable } from './debug/debug_spector.js';
|
|
17
18
|
import { isDevEnvironment, LogType, showBalloonError, showBalloonMessage } from './debug/index.js';
|
|
18
19
|
import { AccessibilityManager } from './engine_accessibility.js';
|
|
19
|
-
import { Addressables } from './engine_addressables.js';
|
|
20
|
+
import { Addressables, clearAssetCaches } from './engine_addressables.js';
|
|
20
21
|
import { AnimationsRegistry } from './engine_animation.js';
|
|
21
22
|
import { Application } from './engine_application.js';
|
|
22
23
|
import { AssetDatabase } from './engine_assetdatabase.js';
|
|
@@ -850,6 +851,19 @@ export class Context implements IContext {
|
|
|
850
851
|
Context.Current = this;
|
|
851
852
|
ContextRegistry.dispatchCallback(ContextEvent.ContextDestroying, this);
|
|
852
853
|
invokeLifecycleFunctions(this, ContextEvent.ContextDestroying);
|
|
854
|
+
// The global legacy node frame must not retain this context's scene after teardown.
|
|
855
|
+
if (nodeFrame.camera === this._mainCamera || nodeFrame.camera === this._fallbackCamera
|
|
856
|
+
|| nodeFrame.camera === this.mainCameraComponent?.threeCamera) {
|
|
857
|
+
nodeFrame.camera = null;
|
|
858
|
+
}
|
|
859
|
+
if (this.renderer && nodeFrame.renderer === this.renderer) {
|
|
860
|
+
nodeFrame.camera = null;
|
|
861
|
+
nodeFrame.renderer = null;
|
|
862
|
+
nodeFrame.object = null;
|
|
863
|
+
nodeFrame.material = null;
|
|
864
|
+
nodeFrame.scene = null;
|
|
865
|
+
nodeFrame.geometry = null;
|
|
866
|
+
}
|
|
853
867
|
this.clear();
|
|
854
868
|
this.renderer?.setAnimationLoop(null);
|
|
855
869
|
if (this.renderer) {
|
|
@@ -862,6 +876,7 @@ export class Context implements IContext {
|
|
|
862
876
|
}
|
|
863
877
|
this.scene = null!;
|
|
864
878
|
this.renderer = null!;
|
|
879
|
+
this.application.dispose();
|
|
865
880
|
this.input.dispose();
|
|
866
881
|
this.menu.onDestroy();
|
|
867
882
|
this.animations.onDestroy();
|
|
@@ -880,6 +895,15 @@ export class Context implements IContext {
|
|
|
880
895
|
ContextRegistry.dispatchCallback(ContextEvent.ContextDestroyed, this);
|
|
881
896
|
invokeLifecycleFunctions(this, ContextEvent.ContextDestroyed);
|
|
882
897
|
ContextRegistry.unregister(this);
|
|
898
|
+
// The asset caches (FileReference/ImageReference) are static and shared by all contexts, so
|
|
899
|
+
// they can only be released once nothing is left that could still be using them. Without
|
|
900
|
+
// this they are never emptied: a SPA that repeatedly creates and destroys <needle-engine>
|
|
901
|
+
// elements keeps every loaded file and image alive for the lifetime of the document.
|
|
902
|
+
if (ContextRegistry.Registered.length === 0) {
|
|
903
|
+
clearAssetCaches();
|
|
904
|
+
// Geometry promises in this shared cache retain resource users and their textures.
|
|
905
|
+
NEEDLE_progressive.dispose();
|
|
906
|
+
}
|
|
883
907
|
if (Context.Current === this) {
|
|
884
908
|
//@ts-ignore
|
|
885
909
|
Context.Current = null;
|
|
@@ -942,7 +942,7 @@ export class Input implements IInput {
|
|
|
942
942
|
window.removeEventListener('contextmenu', this.onContextMenu);
|
|
943
943
|
|
|
944
944
|
this._htmlEventSource?.removeEventListener('pointerdown', this.onPointerDown);
|
|
945
|
-
window.removeEventListener('pointermove', this.onPointerMove);
|
|
945
|
+
window.removeEventListener('pointermove', this.onPointerMove, true);
|
|
946
946
|
window.removeEventListener('pointerup', this.onPointerUp);
|
|
947
947
|
window.removeEventListener('pointercancel', this.onPointerCancel);
|
|
948
948
|
|
|
@@ -1597,4 +1597,4 @@ export declare type KeyCode =
|
|
|
1597
1597
|
// OPEN_BRACKET = 219,
|
|
1598
1598
|
// BACK_SLASH = 220,
|
|
1599
1599
|
// CLOSE_BRACKET = 221,
|
|
1600
|
-
// SINGLE_QUOTE = 222
|
|
1600
|
+
// SINGLE_QUOTE = 222
|
|
@@ -10,6 +10,23 @@ import type { IContext } from "./engine_types.js";
|
|
|
10
10
|
import { getParam } from "./engine_utils.js";
|
|
11
11
|
import { InternalAttributeUtils } from "./engine_utils_attributes.js";
|
|
12
12
|
|
|
13
|
+
// License UI polling must stop when its context goes away; otherwise timers retain scenes.
|
|
14
|
+
const licenseIntervals = new WeakMap<IContext, Set<ReturnType<typeof setInterval>>>();
|
|
15
|
+
const disposedLicenseContexts = new WeakSet<IContext>();
|
|
16
|
+
ContextRegistry.registerCallback(ContextEvent.ContextDestroying, ({ context }) => {
|
|
17
|
+
disposedLicenseContexts.add(context);
|
|
18
|
+
for (const timer of licenseIntervals.get(context) ?? []) clearInterval(timer);
|
|
19
|
+
licenseIntervals.delete(context);
|
|
20
|
+
});
|
|
21
|
+
function setLicenseInterval(ctx: IContext, callback: () => void, delay: number) {
|
|
22
|
+
if (disposedLicenseContexts.has(ctx)) return undefined;
|
|
23
|
+
let timers = licenseIntervals.get(ctx);
|
|
24
|
+
if (!timers) licenseIntervals.set(ctx, timers = new Set());
|
|
25
|
+
const timer = setInterval(callback, delay);
|
|
26
|
+
timers.add(timer);
|
|
27
|
+
return timer;
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
const debug = getParam("debuglicense");
|
|
14
31
|
|
|
15
32
|
const _licenseCheckResultChangedCallbacks: ((result: boolean) => void)[] = [];
|
|
@@ -56,9 +73,15 @@ export function hasCommercialLicense() {
|
|
|
56
73
|
|
|
57
74
|
/** @internal */
|
|
58
75
|
export function onLicenseCheckResultChanged(cb: (result: boolean) => void) {
|
|
59
|
-
if (hasProLicense() || hasIndieLicense() || hasEduLicense())
|
|
60
|
-
|
|
76
|
+
if (hasProLicense() || hasIndieLicense() || hasEduLicense()) {
|
|
77
|
+
cb(true);
|
|
78
|
+
return () => {};
|
|
79
|
+
}
|
|
61
80
|
_licenseCheckResultChangedCallbacks.push(cb);
|
|
81
|
+
return () => {
|
|
82
|
+
const index = _licenseCheckResultChangedCallbacks.indexOf(cb);
|
|
83
|
+
if (index >= 0) _licenseCheckResultChangedCallbacks.splice(index, 1);
|
|
84
|
+
};
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
function invokeLicenseCheckResultChanged(result: boolean) {
|
|
@@ -328,7 +351,7 @@ async function handleForbidden(ctx: IContext) {
|
|
|
328
351
|
const expectedTextStyle = text.style.cssText;
|
|
329
352
|
const forbiddenText = applicationForbiddenText?.length > 1 ? applicationForbiddenText : "This web application has been paused.<br/>You might be in violation of the Needle Engine terms of use.<br/>Please contact the Needle support if you think this is a mistake.";
|
|
330
353
|
text.innerHTML = forbiddenText;
|
|
331
|
-
|
|
354
|
+
setLicenseInterval(ctx, () => {
|
|
332
355
|
if (text.innerHTML !== forbiddenText) text.innerHTML = forbiddenText;
|
|
333
356
|
if (text.parentNode !== div) div.appendChild(text);
|
|
334
357
|
if (div.style.cssText !== expectedStyle) div.style.cssText = expectedStyle;
|
|
@@ -338,7 +361,7 @@ async function handleForbidden(ctx: IContext) {
|
|
|
338
361
|
}
|
|
339
362
|
let forbiddenElement = createForbiddenElement();
|
|
340
363
|
const expectedCSS = forbiddenElement.style.cssText;
|
|
341
|
-
|
|
364
|
+
setLicenseInterval(ctx, () => {
|
|
342
365
|
if (applicationIsForbidden === true) {
|
|
343
366
|
if (forbiddenElement.style.cssText !== expectedCSS) forbiddenElement = createForbiddenElement();
|
|
344
367
|
if (ctx.domElement.shadowRoot) {
|
|
@@ -452,7 +475,7 @@ function insertNonCommercialUseHint(ctx: IContext) {
|
|
|
452
475
|
}, 100);
|
|
453
476
|
|
|
454
477
|
// ensure the banner is always visible
|
|
455
|
-
const interval =
|
|
478
|
+
const interval = setLicenseInterval(ctx, () => {
|
|
456
479
|
const parent = ctx.domElement.shadowRoot || ctx.domElement;
|
|
457
480
|
if (banner.parentNode !== parent) {
|
|
458
481
|
parent.appendChild(banner);
|
|
@@ -141,13 +141,27 @@ function internalInvokeLifecycleFunctions(ctx: Context, evt: Event) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
const
|
|
144
|
+
const buffers: Array<Array<RegisteredLifecycleMethod>> = [];
|
|
145
|
+
let invocationDepth = 0;
|
|
145
146
|
const hookContext: LifecycleHookContext = {
|
|
146
147
|
context: null as any as Context
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
function invoke(ctx: Context, methods: Array<RegisteredLifecycleMethod>, invokeOnce: boolean) {
|
|
150
|
-
|
|
151
|
+
// Reuse storage per nesting depth: hooks may synchronously invoke other hooks.
|
|
152
|
+
const bufferArray = buffers[invocationDepth] ?? (buffers[invocationDepth] = []);
|
|
153
|
+
invocationDepth++;
|
|
154
|
+
try {
|
|
155
|
+
invokeWithBuffer(ctx, methods, invokeOnce, bufferArray);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
// One-time/unregistered callbacks can capture entire scenes as well.
|
|
159
|
+
bufferArray.length = 0;
|
|
160
|
+
invocationDepth--;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function invokeWithBuffer(ctx: Context, methods: Array<RegisteredLifecycleMethod>, invokeOnce: boolean, bufferArray: Array<RegisteredLifecycleMethod>) {
|
|
151
165
|
for (let i = 0; i < methods.length; i++) {
|
|
152
166
|
bufferArray.push(methods[i]);
|
|
153
167
|
}
|
|
@@ -161,6 +175,7 @@ function invoke(ctx: Context, methods: Array<RegisteredLifecycleMethod>, invokeO
|
|
|
161
175
|
}
|
|
162
176
|
|
|
163
177
|
if (invoke) {
|
|
178
|
+
const previousContext = hookContext.context;
|
|
164
179
|
try {
|
|
165
180
|
hookContext.context = ctx;
|
|
166
181
|
entry.method?.call(hookContext, ctx);
|
|
@@ -168,6 +183,10 @@ function invoke(ctx: Context, methods: Array<RegisteredLifecycleMethod>, invokeO
|
|
|
168
183
|
catch (e) {
|
|
169
184
|
console.error("Error in lifecycle method", e);
|
|
170
185
|
}
|
|
186
|
+
finally {
|
|
187
|
+
// This shared receiver must not keep the last invoked context alive.
|
|
188
|
+
hookContext.context = previousContext;
|
|
189
|
+
}
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
// Remove the method if it's a one time call
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import Peer
|
|
1
|
+
import type Peer from "peerjs";
|
|
2
|
+
import type { CallOption, MediaConnection } from "peerjs";
|
|
2
3
|
import { EventDispatcher } from "three";
|
|
3
4
|
|
|
4
5
|
import { RoomEvents } from "../engine/engine_networking.js";
|
|
@@ -7,6 +8,7 @@ import { getPeerjsInstance } from "../engine/engine_networking_peer.js";
|
|
|
7
8
|
import { showBalloonMessage } from "./debug/index.js";
|
|
8
9
|
import { Application } from "./engine_application.js";
|
|
9
10
|
import { Context } from "./engine_context.js";
|
|
11
|
+
import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
|
|
10
12
|
import type { IModel } from "./engine_networking_types.js";
|
|
11
13
|
import { type IComponent, isComponent } from "./engine_types.js";
|
|
12
14
|
import { getParam } from "./engine_utils.js";
|
|
@@ -120,17 +122,31 @@ function applySdpTransform(sdp) {
|
|
|
120
122
|
|
|
121
123
|
export class PeerHandle extends EventDispatcher<any> {
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
// Weak ownership prevents this cache from rooting a context and its scene indefinitely.
|
|
126
|
+
// IDs are scoped to contexts: separate engine elements may use the same stream ID.
|
|
127
|
+
private static readonly instances = new WeakMap<Context, Map<string, PeerHandle>>();
|
|
128
|
+
|
|
129
|
+
static {
|
|
130
|
+
ContextRegistry.registerCallback(ContextEvent.ContextDestroying, ({ context }) => {
|
|
131
|
+
const handles = PeerHandle.instances.get(context as Context);
|
|
132
|
+
if (!handles) return;
|
|
133
|
+
PeerHandle.instances.delete(context as Context);
|
|
134
|
+
for (const handle of handles.values()) handle.dispose();
|
|
135
|
+
handles.clear();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
124
138
|
|
|
125
139
|
static getOrCreate(context: Context, guid: string): PeerHandle {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
let handles = PeerHandle.instances.get(context);
|
|
141
|
+
if (!handles) {
|
|
142
|
+
handles = new Map();
|
|
143
|
+
PeerHandle.instances.set(context, handles);
|
|
144
|
+
}
|
|
145
|
+
let peer = handles.get(guid);
|
|
146
|
+
if (!peer) {
|
|
147
|
+
peer = new PeerHandle(context, guid);
|
|
148
|
+
handles.set(guid, peer);
|
|
149
|
+
}
|
|
134
150
|
return peer;
|
|
135
151
|
}
|
|
136
152
|
|
|
@@ -150,6 +166,7 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
makeCall(peerId: string, stream: MediaStream): CallHandle | undefined {
|
|
169
|
+
if (this._disposed) return undefined;
|
|
153
170
|
if (!stream?.id) {
|
|
154
171
|
if (debug) console.warn("Can not make a call: mediastream has no id or is undefined");
|
|
155
172
|
else console.debug("Can not make a call: mediastream has no id or is undefined");
|
|
@@ -178,10 +195,8 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
closeAll() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
for (const call of this._outgoingCalls) {
|
|
198
|
+
// Closing a call synchronously removes it from the original array.
|
|
199
|
+
for (const call of [...this._incomingCalls, ...this._outgoingCalls]) {
|
|
185
200
|
call.close();
|
|
186
201
|
}
|
|
187
202
|
this.updateCalls();
|
|
@@ -252,16 +267,33 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
252
267
|
}
|
|
253
268
|
}
|
|
254
269
|
|
|
270
|
+
private _disposed = false;
|
|
271
|
+
private readonly _callIntervals = new Set<ReturnType<typeof setInterval>>();
|
|
272
|
+
|
|
273
|
+
private dispose() {
|
|
274
|
+
if (this._disposed) return;
|
|
275
|
+
this._disposed = true;
|
|
276
|
+
this.disable();
|
|
277
|
+
this.unsubscribePeerEvents();
|
|
278
|
+
for (const interval of this._callIntervals) clearInterval(interval);
|
|
279
|
+
this._callIntervals.clear();
|
|
280
|
+
this.closeAll();
|
|
281
|
+
this._incomingCalls.length = 0;
|
|
282
|
+
this._outgoingCalls.length = 0;
|
|
283
|
+
this._peer?.destroy();
|
|
284
|
+
this._peer = undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
255
287
|
private _enabled: boolean = false;
|
|
256
288
|
private _enabledPeer: boolean = false;
|
|
257
|
-
private onConnectRoomFn:
|
|
289
|
+
private onConnectRoomFn: () => void = this.onConnectRoom.bind(this);
|
|
258
290
|
// private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
|
|
259
291
|
// private onPeerConnectFn: (id) => void = this.onPeerConnect.bind(this);
|
|
260
292
|
// private onPeerReceiveCallFn: (call) => void = this.onPeerReceivingCall.bind(this);
|
|
261
293
|
// private _connectionPeerIdMap : Map<string, string> = new Map();
|
|
262
294
|
|
|
263
295
|
enable() {
|
|
264
|
-
if (this._enabled) return;
|
|
296
|
+
if (this._disposed || this._enabled) return;
|
|
265
297
|
this._enabled = true;
|
|
266
298
|
this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
|
|
267
299
|
// this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
|
|
@@ -286,13 +318,19 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
286
318
|
// };
|
|
287
319
|
|
|
288
320
|
private setupPeer() {
|
|
289
|
-
if (!this.context.connection.connectionId) return;
|
|
321
|
+
if (this._disposed || !this.context.connection.connectionId) return;
|
|
290
322
|
if (this._enabledPeer) return;
|
|
291
323
|
this._enabledPeer = true;
|
|
292
324
|
if (!this._peer) {
|
|
293
325
|
const peerId = this.getMyPeerId();
|
|
294
|
-
if (peerId)
|
|
295
|
-
|
|
326
|
+
if (peerId) {
|
|
327
|
+
const peer = getPeerjsInstance(peerId);
|
|
328
|
+
if (this._disposed) {
|
|
329
|
+
peer.destroy();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this._peer = peer;
|
|
333
|
+
}
|
|
296
334
|
else console.error("Failed to setup peerjs because we dont have a connection id", this.context.connection.connectionId);
|
|
297
335
|
}
|
|
298
336
|
if (this._enabled)
|
|
@@ -414,6 +452,7 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
414
452
|
});
|
|
415
453
|
|
|
416
454
|
call.on("stream", () => {
|
|
455
|
+
if (this._disposed) return;
|
|
417
456
|
if (debug) console.log("Received stream for call", call.metadata);
|
|
418
457
|
// workaround for https://github.com/peers/peerjs/issues/636
|
|
419
458
|
let intervalCounter = 0;
|
|
@@ -423,9 +462,15 @@ export class PeerHandle extends EventDispatcher<any> {
|
|
|
423
462
|
if (debug) console.warn("Close call because stream is not active", call.metadata);
|
|
424
463
|
intervalCounter += 1;
|
|
425
464
|
clearInterval(closeInterval);
|
|
465
|
+
this._callIntervals.delete(closeInterval);
|
|
426
466
|
handle.close();
|
|
427
467
|
}
|
|
428
468
|
}, 2000);
|
|
469
|
+
this._callIntervals.add(closeInterval);
|
|
470
|
+
call.on("close", () => {
|
|
471
|
+
clearInterval(closeInterval);
|
|
472
|
+
this._callIntervals.delete(closeInterval);
|
|
473
|
+
});
|
|
429
474
|
});
|
|
430
475
|
}
|
|
431
476
|
return handle;
|
|
@@ -120,7 +120,14 @@ export class GenerateMeshBVHWorker extends WorkerBase {
|
|
|
120
120
|
|
|
121
121
|
}, transferable.map( arr => arr.buffer ).filter( v => ( typeof SharedArrayBuffer === 'undefined' ) || ! ( v instanceof SharedArrayBuffer ) ) );
|
|
122
122
|
|
|
123
|
-
} )
|
|
123
|
+
} ).finally( () => {
|
|
124
|
+
|
|
125
|
+
// Pooled workers outlive each job. Both handlers capture the job scope,
|
|
126
|
+
// including its geometry, even after the promise has settled.
|
|
127
|
+
worker.onmessage = null;
|
|
128
|
+
worker.onerror = null;
|
|
129
|
+
|
|
130
|
+
} );
|
|
124
131
|
|
|
125
132
|
}
|
|
126
133
|
|
|
@@ -27,6 +27,12 @@ export function loadFont(url: string, opts?: LoadFontOptions) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
// The link lives in document.head and can outlive the component using this callback.
|
|
31
|
+
return () => {
|
|
32
|
+
if (opts?.loadedCallback) {
|
|
33
|
+
for (const link of elements) link.removeEventListener("load", opts.loadedCallback);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
|
|
@@ -20,7 +20,15 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
20
20
|
span.setAttribute("role", "img");
|
|
21
21
|
span.setAttribute("aria-label", str + " icon");
|
|
22
22
|
span.setAttribute("aria-hidden", "true");
|
|
23
|
+
updateIconWhenFontReady(new WeakRef(span), str);
|
|
24
|
+
return span;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Pending font loads must not retain detached menu trees.
|
|
28
|
+
function updateIconWhenFontReady(reference: WeakRef<HTMLSpanElement>, str: string) {
|
|
23
29
|
fontReady(fontname).then(res => {
|
|
30
|
+
const span = reference.deref();
|
|
31
|
+
if (!span) return;
|
|
24
32
|
if (res) span.style.visibility = "";
|
|
25
33
|
else {
|
|
26
34
|
if (str === "more_vert") {
|
|
@@ -31,8 +39,7 @@ export function getIconElement(str: string): HTMLElement {
|
|
|
31
39
|
span.style.display = "none";
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
|
-
})
|
|
35
|
-
return span;
|
|
42
|
+
});
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
/**@returns true if the element is an needle engine icon element */
|
|
@@ -3,7 +3,7 @@ import type { Context } from "../../engine_context.js";
|
|
|
3
3
|
import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
|
|
4
4
|
import { isLocalNetwork } from "../../engine_networking_utils.js";
|
|
5
5
|
import { DeviceUtilities, getParam } from "../../engine_utils.js";
|
|
6
|
-
import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
|
|
6
|
+
import { offXRSessionStart, onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
|
|
7
7
|
import { ButtonsFactory } from "../buttons.js";
|
|
8
8
|
import { ensureFonts, iconFontUrl, loadFont } from "../fonts.js";
|
|
9
9
|
import { getIconElement } from "../icons.js";
|
|
@@ -138,7 +138,9 @@ export class NeedleMenu {
|
|
|
138
138
|
|
|
139
139
|
/** @ignore internal method */
|
|
140
140
|
onDestroy() {
|
|
141
|
+
offXRSessionStart(this.onStartXR);
|
|
141
142
|
window.removeEventListener("message", this.onPostMessage);
|
|
143
|
+
this._menu.dispose();
|
|
142
144
|
this._menu.remove();
|
|
143
145
|
this._spatialMenu.onDestroy();
|
|
144
146
|
}
|
|
@@ -338,6 +340,19 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
338
340
|
|
|
339
341
|
private _domElement: HTMLElement | null = null;
|
|
340
342
|
private _context: Context | null = null;
|
|
343
|
+
private _unsubscribeFont?: () => void;
|
|
344
|
+
private _licenseFrame?: number;
|
|
345
|
+
private _unsubscribeLicense?: () => void;
|
|
346
|
+
|
|
347
|
+
/** @internal Release font and license subscriptions when the owning context is destroyed. */
|
|
348
|
+
dispose() {
|
|
349
|
+
this._unsubscribeFont?.();
|
|
350
|
+
this._unsubscribeFont = undefined;
|
|
351
|
+
if (this._licenseFrame !== undefined) window.cancelAnimationFrame(this._licenseFrame);
|
|
352
|
+
this._licenseFrame = undefined;
|
|
353
|
+
this._unsubscribeLicense?.();
|
|
354
|
+
this._unsubscribeLicense = undefined;
|
|
355
|
+
}
|
|
341
356
|
private _didInitialize = false;
|
|
342
357
|
|
|
343
358
|
constructor() {
|
|
@@ -748,7 +763,7 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
748
763
|
// we need to add the icons to both the shadow dom as well as the HEAD to work
|
|
749
764
|
// https://github.com/google/material-design-icons/issues/1165
|
|
750
765
|
ensureFonts();
|
|
751
|
-
loadFont(iconFontUrl, { loadedCallback: () => { this.handleSizeChange() } });
|
|
766
|
+
this._unsubscribeFont = loadFont(iconFontUrl, { loadedCallback: () => { this.handleSizeChange() } });
|
|
752
767
|
loadFont(iconFontUrl, { element: shadow });
|
|
753
768
|
|
|
754
769
|
const content = template.content.cloneNode(true) as DocumentFragment;
|
|
@@ -777,16 +792,19 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
777
792
|
try {
|
|
778
793
|
// if the user has a license then we CAN hide the needle logo
|
|
779
794
|
// calling this method immediately will cause an issue with vite bundling tho
|
|
780
|
-
window.requestAnimationFrame(() =>
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
795
|
+
this._licenseFrame = window.requestAnimationFrame(() => {
|
|
796
|
+
this._licenseFrame = undefined;
|
|
797
|
+
this._unsubscribeLicense = onLicenseCheckResultChanged(res => {
|
|
798
|
+
if (res == true && hasCommercialLicense() && !debugNonCommercial) {
|
|
799
|
+
let visible = this._userRequestedLogoVisible;
|
|
800
|
+
if (visible === undefined) visible = false;
|
|
801
|
+
this.___onSetLogoVisible(visible);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
this.___onSetLogoVisible(true);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
});
|
|
790
808
|
} catch (e) {
|
|
791
809
|
console.error("[Needle Menu] License check failed.", e);
|
|
792
810
|
}
|
|
@@ -19,6 +19,7 @@ export class LoadingElementOptions {
|
|
|
19
19
|
|
|
20
20
|
/** @internal */
|
|
21
21
|
export interface ILoadingViewHandler {
|
|
22
|
+
dispose?(): void;
|
|
22
23
|
onLoadingBegin(message?: string)
|
|
23
24
|
onLoadingUpdate(progress: LoadingProgressArgs | number, message?: string);
|
|
24
25
|
onLoadingFinished(message?: string);
|
|
@@ -79,6 +80,17 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
79
80
|
this._loadingElementOptions = opts;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
dispose() {
|
|
84
|
+
// Removal can happen before ready stops the progress interval.
|
|
85
|
+
clearInterval(this._progressLoop);
|
|
86
|
+
this._progressLoop = null;
|
|
87
|
+
this._loadingElement?.remove();
|
|
88
|
+
this._loadingElement = undefined;
|
|
89
|
+
this._loadingTextContainer = null;
|
|
90
|
+
this._loadingBar = null;
|
|
91
|
+
this._messageContainer = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
async onLoadingBegin(message?: string) {
|
|
83
95
|
const _element = this._element.shadowRoot || this._element;
|
|
84
96
|
if (debug) console.warn("Begin Loading")
|
|
@@ -402,4 +414,4 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
402
414
|
// nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
|
|
403
415
|
// nonCommercialContainer.style.opacity = "1";
|
|
404
416
|
// }
|
|
405
|
-
}
|
|
417
|
+
}
|
|
@@ -172,6 +172,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
172
172
|
private _overlay_ar!: AROverlayHandler;
|
|
173
173
|
private _loadingProgress01: number = 0;
|
|
174
174
|
private _loadingView?: ILoadingViewHandler;
|
|
175
|
+
private _cancelLoadingBlur?: () => void;
|
|
175
176
|
private _previousSrc: string | null | string[] = null;
|
|
176
177
|
/** @private set to true after <needle-engine> did load completely at least once. Set to false when < to false when <needle-engine> is removed from the document removed from the document */
|
|
177
178
|
private _didFullyLoad: boolean = false;
|
|
@@ -335,6 +336,9 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
335
336
|
const dispose = keepAlive == undefined || (keepAlive?.length > 0 && keepAlive !== "true" && keepAlive !== "1");
|
|
336
337
|
if (debug) console.warn("<needle-engine> disconnected, keep-alive: \"" + keepAlive + "\"", typeof keepAlive, "Dispose=", dispose);
|
|
337
338
|
if (dispose) {
|
|
339
|
+
this._cancelLoadingBlur?.();
|
|
340
|
+
this._cancelLoadingBlur = undefined;
|
|
341
|
+
this._loadingView?.dispose?.();
|
|
338
342
|
if (debug)
|
|
339
343
|
console.warn("<needle-engine> dispose");
|
|
340
344
|
this._context?.dispose();
|
|
@@ -583,7 +587,8 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
583
587
|
|
|
584
588
|
|
|
585
589
|
// Experimental loading blur
|
|
586
|
-
|
|
590
|
+
this._cancelLoadingBlur?.();
|
|
591
|
+
this._cancelLoadingBlur = handleLoadingBlur(this);
|
|
587
592
|
|
|
588
593
|
const currentHash = this.getAttribute("hash");
|
|
589
594
|
if (currentHash !== null && currentHash !== undefined)
|
|
@@ -1243,8 +1248,12 @@ function getDisplayName(str: string) {
|
|
|
1243
1248
|
|
|
1244
1249
|
|
|
1245
1250
|
function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
|
|
1246
|
-
|
|
1247
|
-
|
|
1251
|
+
const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
|
|
1252
|
+
if (userBlurSetting === null || userBlurSetting === "0") return undefined;
|
|
1253
|
+
const unregister = onStart((ctx) => {
|
|
1254
|
+
// A different context starting must not consume this element's callback.
|
|
1255
|
+
if (ctx.domElement !== needleEngineElement) return;
|
|
1256
|
+
unregister();
|
|
1248
1257
|
if (userBlurSetting !== null && userBlurSetting !== "0") {
|
|
1249
1258
|
if (ctx.domElement === needleEngineElement) {
|
|
1250
1259
|
|
|
@@ -1297,5 +1306,6 @@ function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
|
|
|
1297
1306
|
}
|
|
1298
1307
|
}
|
|
1299
1308
|
}
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1309
|
+
});
|
|
1310
|
+
return unregister;
|
|
1311
|
+
}
|