@playcanvas/web-components 0.11.0 → 0.12.0
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/README.md +1 -1
- package/dist/app.d.ts +31 -49
- package/dist/asset.d.ts +152 -12
- package/dist/async-element.d.ts +26 -9
- package/dist/components/button-component.d.ts +3 -7
- package/dist/components/camera-component.d.ts +3 -7
- package/dist/components/collision-component.d.ts +19 -7
- package/dist/components/component.d.ts +41 -4
- package/dist/components/element-component.d.ts +4 -8
- package/dist/components/gsplat-component.d.ts +2 -7
- package/dist/components/layoutchild-component.d.ts +2 -7
- package/dist/components/layoutgroup-component.d.ts +3 -7
- package/dist/components/light-component.d.ts +3 -7
- package/dist/components/listener-component.d.ts +1 -6
- package/dist/components/particlesystem-component.d.ts +2 -7
- package/dist/components/render-component.d.ts +2 -7
- package/dist/components/rigidbody-component.d.ts +3 -7
- package/dist/components/screen-component.d.ts +3 -7
- package/dist/components/script-component.d.ts +8 -20
- package/dist/components/script.d.ts +2 -22
- package/dist/components/scrollbar-component.d.ts +2 -7
- package/dist/components/scrollview-component.d.ts +3 -7
- package/dist/components/sound-component.d.ts +2 -7
- package/dist/components/sound-slot.d.ts +8 -6
- package/dist/custom-elements.json +5824 -10581
- package/dist/entity-base.d.ts +67 -0
- package/dist/entity.d.ts +7 -48
- package/dist/index.d.ts +41 -1
- package/dist/material.d.ts +14 -13
- package/dist/model.d.ts +43 -5
- package/dist/module.d.ts +0 -6
- package/dist/node.d.ts +253 -0
- package/dist/parse.d.ts +2 -1
- package/dist/pwc.cjs +1830 -274
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +1830 -274
- package/dist/pwc.js.map +1 -1
- package/dist/pwc.min.js +1 -1
- package/dist/pwc.min.js.map +1 -1
- package/dist/pwc.min.mjs +1 -1
- package/dist/pwc.min.mjs.map +1 -1
- package/dist/pwc.mjs +1830 -276
- package/dist/pwc.mjs.map +1 -1
- package/dist/scene.d.ts +4 -7
- package/dist/sky.d.ts +13 -5
- package/dist/vscode.html-custom-data.json +148 -26
- package/dist/web-types.json +894 -581
- package/package.json +9 -8
- package/src/app.ts +163 -88
- package/src/asset.ts +472 -36
- package/src/async-element.ts +39 -12
- package/src/components/button-component.ts +5 -9
- package/src/components/camera-component.ts +24 -10
- package/src/components/collision-component.ts +61 -15
- package/src/components/component.ts +151 -11
- package/src/components/element-component.ts +26 -30
- package/src/components/gsplat-component.ts +4 -9
- package/src/components/layoutchild-component.ts +4 -9
- package/src/components/layoutgroup-component.ts +14 -9
- package/src/components/light-component.ts +42 -12
- package/src/components/listener-component.ts +1 -7
- package/src/components/particlesystem-component.ts +7 -15
- package/src/components/render-component.ts +5 -10
- package/src/components/rigidbody-component.ts +23 -16
- package/src/components/screen-component.ts +5 -9
- package/src/components/script-component.ts +108 -46
- package/src/components/script.ts +38 -33
- package/src/components/scrollbar-component.ts +6 -16
- package/src/components/scrollview-component.ts +16 -11
- package/src/components/sound-component.ts +10 -15
- package/src/components/sound-slot.ts +30 -20
- package/src/entity-base.ts +136 -0
- package/src/entity.ts +47 -118
- package/src/index.ts +50 -1
- package/src/loading-bar.ts +8 -8
- package/src/material.ts +65 -39
- package/src/model.ts +140 -17
- package/src/module.ts +8 -7
- package/src/node.ts +715 -0
- package/src/parse.ts +62 -17
- package/src/scene.ts +12 -9
- package/src/sky.ts +50 -10
package/src/material.ts
CHANGED
|
@@ -21,17 +21,26 @@ import {
|
|
|
21
21
|
SPECOCC_GLOSSDEPENDENT,
|
|
22
22
|
SPECOCC_NONE,
|
|
23
23
|
StandardMaterial,
|
|
24
|
-
Vec2
|
|
25
|
-
type EventHandle,
|
|
26
|
-
type Texture
|
|
24
|
+
Vec2
|
|
27
25
|
} from 'playcanvas';
|
|
26
|
+
import type { EventHandle, Texture } from 'playcanvas';
|
|
28
27
|
|
|
29
|
-
import { AppElement } from './app';
|
|
28
|
+
import type { AppElement } from './app';
|
|
30
29
|
import { AssetElement } from './asset';
|
|
31
30
|
import { parseBool, parseColor, parseEnum, parseNumber, parseVec2 } from './parse';
|
|
32
31
|
|
|
33
|
-
type BlendType =
|
|
34
|
-
|
|
32
|
+
type BlendType =
|
|
33
|
+
| 'none'
|
|
34
|
+
| 'normal'
|
|
35
|
+
| 'additive'
|
|
36
|
+
| 'additive-alpha'
|
|
37
|
+
| 'premultiplied'
|
|
38
|
+
| 'multiplicative'
|
|
39
|
+
| 'multiplicative-2x'
|
|
40
|
+
| 'screen'
|
|
41
|
+
| 'min'
|
|
42
|
+
| 'max'
|
|
43
|
+
| 'subtractive';
|
|
35
44
|
|
|
36
45
|
const blendTypes = new Map<BlendType, number>([
|
|
37
46
|
['none', BLEND_NONE],
|
|
@@ -100,8 +109,8 @@ const roughnessAliases = ['roughness', 'roughness-map'];
|
|
|
100
109
|
* The texture slots a `pc-material` can populate. Each is backed by a `pc-asset` id rather than a
|
|
101
110
|
* `Texture`, so the element can be authored before the asset has loaded.
|
|
102
111
|
*/
|
|
103
|
-
type TextureSlot =
|
|
104
|
-
'metalnessMap' | 'normalMap' | 'opacityMap';
|
|
112
|
+
type TextureSlot =
|
|
113
|
+
'aoMap' | 'diffuseMap' | 'emissiveMap' | 'glossMap' | 'heightMap' | 'metalnessMap' | 'normalMap' | 'opacityMap';
|
|
105
114
|
|
|
106
115
|
/**
|
|
107
116
|
* The MaterialElement interface provides properties and methods for manipulating
|
|
@@ -286,7 +295,7 @@ class MaterialElement extends HTMLElement {
|
|
|
286
295
|
|
|
287
296
|
private _useLighting = true;
|
|
288
297
|
|
|
289
|
-
// Diverges from the engine default of false - see the class docblock and
|
|
298
|
+
// Diverges from the engine default of false - see the class docblock and _createMaterial()
|
|
290
299
|
private _useMetalness = true;
|
|
291
300
|
|
|
292
301
|
private _useMetalnessSpecularColor = false;
|
|
@@ -306,10 +315,14 @@ class MaterialElement extends HTMLElement {
|
|
|
306
315
|
|
|
307
316
|
private _glossConflictWarned = false;
|
|
308
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The material. `null` until the containing application has created it — an element present
|
|
320
|
+
* at startup has its material once the application is ready.
|
|
321
|
+
*/
|
|
309
322
|
material: StandardMaterial | null = null;
|
|
310
323
|
|
|
311
324
|
async connectedCallback() {
|
|
312
|
-
const appElement = this.parentElement?.closest('pc-app') as AppElement | null ?? null;
|
|
325
|
+
const appElement = (this.parentElement?.closest('pc-app') as AppElement | null) ?? null;
|
|
313
326
|
|
|
314
327
|
// Materials must be direct children of pc-app (matches the boot query ':scope > pc-material')
|
|
315
328
|
if (!appElement || this.parentElement !== appElement) {
|
|
@@ -326,11 +339,18 @@ class MaterialElement extends HTMLElement {
|
|
|
326
339
|
// elements inserted (or re-inserted) after the app is already running
|
|
327
340
|
if (!this.material) {
|
|
328
341
|
if (!appElement.app) return; // pc-app is re-connecting; its own boot will create this
|
|
329
|
-
this.
|
|
342
|
+
this._createMaterial();
|
|
330
343
|
}
|
|
331
344
|
}
|
|
332
345
|
|
|
333
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Creates the material from the element's cached properties. Called by the containing
|
|
348
|
+
* `<pc-app>` element during its boot sweep, and on connection for elements inserted while
|
|
349
|
+
* the application is already running.
|
|
350
|
+
*
|
|
351
|
+
* @internal
|
|
352
|
+
*/
|
|
353
|
+
_createMaterial() {
|
|
334
354
|
const material = new StandardMaterial();
|
|
335
355
|
this.material = material;
|
|
336
356
|
|
|
@@ -464,10 +484,10 @@ class MaterialElement extends HTMLElement {
|
|
|
464
484
|
* warning latches and reports once per episode, clearing when the clash is resolved.
|
|
465
485
|
*/
|
|
466
486
|
private _warnGlossConflict() {
|
|
467
|
-
const quote = (names: string[]) => `'${names.join('
|
|
487
|
+
const quote = (names: string[]) => `'${names.join("', '")}'`;
|
|
468
488
|
|
|
469
|
-
const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
|
|
470
|
-
const gloss = glossConflicts.filter(name => this.hasAttribute(name));
|
|
489
|
+
const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
|
|
490
|
+
const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
|
|
471
491
|
|
|
472
492
|
if (roughness.length === 0 || gloss.length === 0) {
|
|
473
493
|
this._glossConflictWarned = false;
|
|
@@ -477,8 +497,10 @@ class MaterialElement extends HTMLElement {
|
|
|
477
497
|
if (this._glossConflictWarned) return;
|
|
478
498
|
this._glossConflictWarned = true;
|
|
479
499
|
|
|
480
|
-
console.warn(
|
|
481
|
-
'
|
|
500
|
+
console.warn(
|
|
501
|
+
`pc-material '${this.id}' sets both ${quote(roughness)} and ${quote(gloss)} - ` +
|
|
502
|
+
'the roughness-* attributes invert gloss, so the two families contradict each other. Use one or the other.'
|
|
503
|
+
);
|
|
482
504
|
}
|
|
483
505
|
|
|
484
506
|
/**
|
|
@@ -488,7 +510,7 @@ class MaterialElement extends HTMLElement {
|
|
|
488
510
|
* @param id - The id of the `pc-asset`, or an empty string to clear the slot.
|
|
489
511
|
* @param slot - The material property to write.
|
|
490
512
|
*/
|
|
491
|
-
|
|
513
|
+
private _setMap(id: string, slot: TextureSlot) {
|
|
492
514
|
// Drop any load still pending for this slot - its texture is no longer the one we want
|
|
493
515
|
this._mapHandles.get(slot)?.off();
|
|
494
516
|
this._mapHandles.delete(slot);
|
|
@@ -509,20 +531,23 @@ class MaterialElement extends HTMLElement {
|
|
|
509
531
|
return;
|
|
510
532
|
}
|
|
511
533
|
|
|
512
|
-
this._mapHandles.set(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
534
|
+
this._mapHandles.set(
|
|
535
|
+
slot,
|
|
536
|
+
asset.once('load', () => {
|
|
537
|
+
this._mapHandles.delete(slot);
|
|
538
|
+
this._applyMap(slot, asset.resource as Texture);
|
|
539
|
+
})
|
|
540
|
+
);
|
|
516
541
|
}
|
|
517
542
|
|
|
518
543
|
/**
|
|
519
544
|
* @param slot - The material property to write.
|
|
520
|
-
* @param texture - The loaded texture
|
|
545
|
+
* @param texture - The loaded texture, applied with its sampler state untouched - anisotropy
|
|
546
|
+
* and friends belong to the `pc-asset`'s texture options.
|
|
521
547
|
*/
|
|
522
548
|
private _applyMap(slot: TextureSlot, texture: Texture) {
|
|
523
549
|
if (!this.material) return;
|
|
524
550
|
this.material[slot] = texture;
|
|
525
|
-
texture.anisotropy = 4;
|
|
526
551
|
this._scheduleUpdate();
|
|
527
552
|
}
|
|
528
553
|
|
|
@@ -592,7 +617,7 @@ class MaterialElement extends HTMLElement {
|
|
|
592
617
|
*/
|
|
593
618
|
set aoMap(value: string) {
|
|
594
619
|
this._aoMap = value;
|
|
595
|
-
this.
|
|
620
|
+
this._setMap(value, 'aoMap');
|
|
596
621
|
}
|
|
597
622
|
|
|
598
623
|
/**
|
|
@@ -850,7 +875,7 @@ class MaterialElement extends HTMLElement {
|
|
|
850
875
|
*/
|
|
851
876
|
set diffuseMap(value: string) {
|
|
852
877
|
this._diffuseMap = value;
|
|
853
|
-
this.
|
|
878
|
+
this._setMap(value, 'diffuseMap');
|
|
854
879
|
}
|
|
855
880
|
|
|
856
881
|
/**
|
|
@@ -1007,7 +1032,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1007
1032
|
*/
|
|
1008
1033
|
set emissiveMap(value: string) {
|
|
1009
1034
|
this._emissiveMap = value;
|
|
1010
|
-
this.
|
|
1035
|
+
this._setMap(value, 'emissiveMap');
|
|
1011
1036
|
}
|
|
1012
1037
|
|
|
1013
1038
|
/**
|
|
@@ -1205,7 +1230,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1205
1230
|
*/
|
|
1206
1231
|
set glossMap(value: string) {
|
|
1207
1232
|
this._glossMap = value;
|
|
1208
|
-
this.
|
|
1233
|
+
this._setMap(value, 'glossMap');
|
|
1209
1234
|
}
|
|
1210
1235
|
|
|
1211
1236
|
/**
|
|
@@ -1322,7 +1347,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1322
1347
|
*/
|
|
1323
1348
|
set heightMap(value: string) {
|
|
1324
1349
|
this._heightMap = value;
|
|
1325
|
-
this.
|
|
1350
|
+
this._setMap(value, 'heightMap');
|
|
1326
1351
|
}
|
|
1327
1352
|
|
|
1328
1353
|
/**
|
|
@@ -1479,7 +1504,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1479
1504
|
*/
|
|
1480
1505
|
set metalnessMap(value: string) {
|
|
1481
1506
|
this._metalnessMap = value;
|
|
1482
|
-
this.
|
|
1507
|
+
this._setMap(value, 'metalnessMap');
|
|
1483
1508
|
}
|
|
1484
1509
|
|
|
1485
1510
|
/**
|
|
@@ -1596,7 +1621,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1596
1621
|
*/
|
|
1597
1622
|
set normalMap(value: string) {
|
|
1598
1623
|
this._normalMap = value;
|
|
1599
|
-
this.
|
|
1624
|
+
this._setMap(value, 'normalMap');
|
|
1600
1625
|
}
|
|
1601
1626
|
|
|
1602
1627
|
/**
|
|
@@ -1694,7 +1719,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1694
1719
|
set occludeDirect(value: boolean) {
|
|
1695
1720
|
this._occludeDirect = value;
|
|
1696
1721
|
if (this.material) {
|
|
1697
|
-
// @ts-ignore see
|
|
1722
|
+
// @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
|
|
1698
1723
|
this.material.occludeDirect = value;
|
|
1699
1724
|
this._scheduleUpdate();
|
|
1700
1725
|
}
|
|
@@ -1796,7 +1821,7 @@ class MaterialElement extends HTMLElement {
|
|
|
1796
1821
|
*/
|
|
1797
1822
|
set opacityMap(value: string) {
|
|
1798
1823
|
this._opacityMap = value;
|
|
1799
|
-
this.
|
|
1824
|
+
this._setMap(value, 'opacityMap');
|
|
1800
1825
|
}
|
|
1801
1826
|
|
|
1802
1827
|
/**
|
|
@@ -2150,6 +2175,13 @@ class MaterialElement extends HTMLElement {
|
|
|
2150
2175
|
return this._useTonemap;
|
|
2151
2176
|
}
|
|
2152
2177
|
|
|
2178
|
+
/**
|
|
2179
|
+
* Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
|
|
2180
|
+
* `id`, or `undefined` if there is no such element or its material has not been created yet.
|
|
2181
|
+
*
|
|
2182
|
+
* @param id - The `id` of the `<pc-material>` element.
|
|
2183
|
+
* @returns The material, or `undefined`.
|
|
2184
|
+
*/
|
|
2153
2185
|
static get(id: string) {
|
|
2154
2186
|
const materialElement = document.querySelector<MaterialElement>(`pc-material[id="${id}"]`);
|
|
2155
2187
|
return materialElement?.material;
|
|
@@ -2508,10 +2540,4 @@ class MaterialElement extends HTMLElement {
|
|
|
2508
2540
|
|
|
2509
2541
|
customElements.define('pc-material', MaterialElement);
|
|
2510
2542
|
|
|
2511
|
-
declare global {
|
|
2512
|
-
interface HTMLElementTagNameMap {
|
|
2513
|
-
'pc-material': MaterialElement;
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
2543
|
export { MaterialElement };
|
package/src/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContainerResource, Entity } from 'playcanvas';
|
|
1
|
+
import type { ContainerResource, Entity, EventHandle } from 'playcanvas';
|
|
2
2
|
|
|
3
3
|
import { AssetElement } from './asset';
|
|
4
4
|
import { AsyncElement } from './async-element';
|
|
@@ -8,41 +8,123 @@ import { AsyncElement } from './async-element';
|
|
|
8
8
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-model/ | `<pc-model>`} elements.
|
|
9
9
|
* The ModelElement interface also inherits the properties and methods of the
|
|
10
10
|
* {@link HTMLElement} interface.
|
|
11
|
+
*
|
|
12
|
+
* The element becomes ready once its container asset has loaded and the instantiated hierarchy has
|
|
13
|
+
* been added to the scene — `entity` is non-null by then. A failed load also settles readiness,
|
|
14
|
+
* with `entity` remaining `null`: readiness means the load settled, not that it succeeded — listen
|
|
15
|
+
* for `error`, or check `entity`, to tell the outcomes apart. Changing `asset` re-arms readiness
|
|
16
|
+
* and instantiates anew, so a `ready()` obtained after the change resolves against the new
|
|
17
|
+
* hierarchy. A `pc-model` outside a `pc-app`, or referencing an unknown asset id, warns and never
|
|
18
|
+
* becomes ready.
|
|
19
|
+
*
|
|
20
|
+
* @fires {Event} load - Fired each time a container asset finishes instantiating, including
|
|
21
|
+
* re-instantiation after `asset` changes. Does not bubble — listen on this element, or use a
|
|
22
|
+
* capture-phase listener on an ancestor.
|
|
23
|
+
* @fires {ErrorEvent} error - Fired when the container asset fails to load, with the engine's
|
|
24
|
+
* error in `message`. Does not bubble. The element still becomes ready — readiness means the load
|
|
25
|
+
* settled, not that it succeeded.
|
|
11
26
|
*/
|
|
12
27
|
class ModelElement extends AsyncElement {
|
|
13
|
-
private _asset
|
|
28
|
+
private _asset = '';
|
|
14
29
|
|
|
15
30
|
private _entity: Entity | null = null;
|
|
16
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
34
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
35
|
+
* on, so a superseded load can neither instantiate a second entity nor parent one that has
|
|
36
|
+
* since been destroyed.
|
|
37
|
+
*/
|
|
38
|
+
private _loadGeneration = 0;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The pending asset subscriptions of the current load, if it is waiting for its asset. Held
|
|
42
|
+
* so that whatever supersedes the load can detach the handlers from the asset, rather than
|
|
43
|
+
* leave them registered until the asset settles (or forever, if it never does).
|
|
44
|
+
*/
|
|
45
|
+
private _loadHandle: EventHandle | null = null;
|
|
46
|
+
|
|
47
|
+
private _errorHandle: EventHandle | null = null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The root entity of the instantiated model. `null` until the container asset has loaded
|
|
51
|
+
* and been instantiated, and again once the element has been removed from the document.
|
|
52
|
+
* @returns The model's root entity, or `null`.
|
|
53
|
+
*/
|
|
54
|
+
get entity(): Entity | null {
|
|
55
|
+
return this._entity;
|
|
56
|
+
}
|
|
57
|
+
|
|
17
58
|
connectedCallback() {
|
|
59
|
+
// A model outside an application is inert and never becomes ready, so awaiting it hangs.
|
|
60
|
+
// Warn rather than fail silently, naming the parent it requires, as every other misplaced
|
|
61
|
+
// element does.
|
|
62
|
+
if (!this.closestApp) {
|
|
63
|
+
const label = this._asset ? ` '${this._asset}'` : '';
|
|
64
|
+
console.warn(`pc-model${label} must be a descendant of pc-app - model not created`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
18
67
|
this._loadModel();
|
|
19
|
-
this._onReady();
|
|
20
68
|
}
|
|
21
69
|
|
|
22
70
|
disconnectedCallback() {
|
|
71
|
+
this._loadGeneration++;
|
|
72
|
+
this._detachLoadHandlers();
|
|
23
73
|
this._unloadModel();
|
|
74
|
+
this._resetReady();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private _detachLoadHandlers() {
|
|
78
|
+
this._loadHandle?.off();
|
|
79
|
+
this._loadHandle = null;
|
|
80
|
+
this._errorHandle?.off();
|
|
81
|
+
this._errorHandle = null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolves readiness and dispatches the `load` event. Called once the instantiated hierarchy
|
|
86
|
+
* has been parented — readiness means "in the scene graph", matching `pc-entity`, so a ready
|
|
87
|
+
* model's entity always has world transforms.
|
|
88
|
+
*/
|
|
89
|
+
private _announceLoad() {
|
|
90
|
+
this._onReady();
|
|
91
|
+
this.dispatchEvent(new Event('load'));
|
|
24
92
|
}
|
|
25
93
|
|
|
26
94
|
private _instantiate(container: ContainerResource) {
|
|
27
|
-
|
|
95
|
+
const generation = this._loadGeneration;
|
|
96
|
+
|
|
97
|
+
const entity = container.instantiateRenderEntity();
|
|
98
|
+
this._entity = entity;
|
|
28
99
|
|
|
29
100
|
// @ts-ignore
|
|
30
101
|
if (container.animations.length > 0) {
|
|
31
|
-
|
|
102
|
+
entity.addComponent('anim');
|
|
32
103
|
// @ts-ignore
|
|
33
|
-
|
|
104
|
+
entity.anim.assignAnimation('animation', container.animations[0].resource);
|
|
34
105
|
}
|
|
35
106
|
|
|
107
|
+
// The parent's readiness re-arms when it is torn down, so these can resume in a later
|
|
108
|
+
// connection cycle. The entity is captured above and the generation re-checked, so a
|
|
109
|
+
// stale resume cannot parent an entity a newer cycle has already destroyed.
|
|
36
110
|
const parentEntityElement = this.closestEntity;
|
|
37
111
|
if (parentEntityElement) {
|
|
38
112
|
parentEntityElement.ready().then(() => {
|
|
39
|
-
|
|
113
|
+
if (generation !== this._loadGeneration) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
parentEntityElement.entity!.addChild(entity);
|
|
117
|
+
this._announceLoad();
|
|
40
118
|
});
|
|
41
119
|
} else {
|
|
42
120
|
const appElement = this.closestApp;
|
|
43
121
|
if (appElement) {
|
|
44
122
|
appElement.ready().then(() => {
|
|
45
|
-
|
|
123
|
+
if (generation !== this._loadGeneration) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
appElement.app!.root.addChild(entity);
|
|
127
|
+
this._announceLoad();
|
|
46
128
|
});
|
|
47
129
|
}
|
|
48
130
|
}
|
|
@@ -51,20 +133,67 @@ class ModelElement extends AsyncElement {
|
|
|
51
133
|
private async _loadModel() {
|
|
52
134
|
this._unloadModel();
|
|
53
135
|
|
|
54
|
-
|
|
55
|
-
const
|
|
136
|
+
// Supersede any load already in flight - only the newest load may instantiate
|
|
137
|
+
const generation = ++this._loadGeneration;
|
|
138
|
+
this._detachLoadHandlers();
|
|
139
|
+
|
|
140
|
+
// Re-arm readiness so a waiter obtained after an asset change resolves against the new
|
|
141
|
+
// hierarchy. A no-op on first connection, where readiness is still pending.
|
|
142
|
+
this._resetReady();
|
|
143
|
+
|
|
144
|
+
const appElement = this.closestApp;
|
|
145
|
+
if (!appElement) {
|
|
146
|
+
// Outside pc-app; connectedCallback already warned. Reached through the asset setter.
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await appElement.ready();
|
|
151
|
+
|
|
152
|
+
// The element may have been removed, or another load started, while we waited
|
|
153
|
+
if (generation !== this._loadGeneration) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const app = appElement.app;
|
|
56
158
|
|
|
57
159
|
const asset = AssetElement.get(this._asset);
|
|
58
160
|
if (!asset) {
|
|
161
|
+
// An empty id is a legitimate transient (the asset may be assigned later); a
|
|
162
|
+
// non-empty one that resolves to nothing is a dead end - say so rather than staying
|
|
163
|
+
// silently pending.
|
|
164
|
+
if (this._asset) {
|
|
165
|
+
console.warn(`pc-model could not find asset '${this._asset}' - model not created`);
|
|
166
|
+
}
|
|
59
167
|
return;
|
|
60
168
|
}
|
|
61
169
|
|
|
62
170
|
if (asset.loaded) {
|
|
63
171
|
this._instantiate(asset.resource as ContainerResource);
|
|
64
172
|
} else {
|
|
65
|
-
|
|
173
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
174
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
175
|
+
// holds on its own. Whichever of load/error fires first detaches the other.
|
|
176
|
+
this._loadHandle = asset.once('load', () => {
|
|
177
|
+
this._detachLoadHandlers();
|
|
178
|
+
if (generation !== this._loadGeneration) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
66
181
|
this._instantiate(asset.resource as ContainerResource);
|
|
67
182
|
});
|
|
183
|
+
this._errorHandle = asset.once('error', (err: string | Error) => {
|
|
184
|
+
this._detachLoadHandlers();
|
|
185
|
+
if (generation !== this._loadGeneration) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// A failed load settles readiness with a null entity, mirroring pc-asset:
|
|
189
|
+
// readiness means the load settled, not that it succeeded.
|
|
190
|
+
this.dispatchEvent(
|
|
191
|
+
new ErrorEvent('error', {
|
|
192
|
+
message: err instanceof Error ? err.message : String(err)
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
this._onReady();
|
|
196
|
+
});
|
|
68
197
|
app!.assets.load(asset);
|
|
69
198
|
}
|
|
70
199
|
}
|
|
@@ -108,10 +237,4 @@ class ModelElement extends AsyncElement {
|
|
|
108
237
|
|
|
109
238
|
customElements.define('pc-model', ModelElement);
|
|
110
239
|
|
|
111
|
-
declare global {
|
|
112
|
-
interface HTMLElementTagNameMap {
|
|
113
|
-
'pc-model': ModelElement;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
240
|
export { ModelElement };
|
package/src/module.ts
CHANGED
|
@@ -43,17 +43,18 @@ class ModuleElement extends HTMLElement {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Returns the promise that settles when the module has loaded. Awaited by the containing
|
|
48
|
+
* `<pc-app>` element before it creates its graphics device.
|
|
49
|
+
*
|
|
50
|
+
* @returns The load promise.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
_getLoadPromise(): Promise<void> {
|
|
47
54
|
return this.loadPromise;
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
customElements.define('pc-module', ModuleElement);
|
|
52
59
|
|
|
53
|
-
declare global {
|
|
54
|
-
interface HTMLElementTagNameMap {
|
|
55
|
-
'pc-module': ModuleElement;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
60
|
export { ModuleElement };
|