@playcanvas/web-components 0.11.0 → 0.11.1
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 +18 -38
- package/dist/asset.d.ts +8 -7
- package/dist/async-element.d.ts +21 -5
- 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 +3 -7
- package/dist/components/component.d.ts +22 -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 +5049 -10696
- package/dist/entity.d.ts +5 -11
- package/dist/index.d.ts +37 -0
- package/dist/material.d.ts +12 -12
- package/dist/model.d.ts +21 -5
- package/dist/module.d.ts +0 -6
- package/dist/parse.d.ts +2 -1
- package/dist/pwc.cjs +496 -136
- package/dist/pwc.cjs.map +1 -1
- package/dist/pwc.js +496 -136
- 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 +496 -136
- 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 +26 -26
- package/dist/web-types.json +545 -536
- package/package.json +8 -7
- package/src/app.ts +142 -70
- package/src/asset.ts +34 -28
- package/src/async-element.ts +34 -8
- package/src/components/button-component.ts +5 -9
- package/src/components/camera-component.ts +24 -10
- package/src/components/collision-component.ts +26 -15
- package/src/components/component.ts +58 -8
- 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.ts +42 -19
- package/src/index.ts +45 -1
- package/src/loading-bar.ts +8 -8
- package/src/material.ts +63 -37
- package/src/model.ts +69 -14
- package/src/module.ts +8 -7
- package/src/parse.ts +62 -17
- package/src/scene.ts +12 -9
- package/src/sky.ts +50 -9
package/dist/pwc.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* Base class for all PlayCanvas Web Components that initialize asynchronously.
|
|
9
9
|
*
|
|
10
|
-
* @fires {CustomEvent} ready - Fired
|
|
11
|
-
*
|
|
10
|
+
* @fires {CustomEvent} ready - Fired when the element is fully initialized — once per readiness
|
|
11
|
+
* cycle, so an element that is torn down and re-initialized (for example by removing and
|
|
12
|
+
* re-inserting it) fires it again. Bubbles and is composed.
|
|
12
13
|
*/
|
|
13
14
|
class AsyncElement extends HTMLElement {
|
|
14
15
|
_readyPromise;
|
|
15
16
|
_readyResolve;
|
|
17
|
+
_readyResolved = false;
|
|
16
18
|
/** @ignore */
|
|
17
19
|
constructor() {
|
|
18
20
|
super();
|
|
@@ -39,15 +41,39 @@
|
|
|
39
41
|
/**
|
|
40
42
|
* Called when the element is fully initialized and ready. Subclasses should call this when
|
|
41
43
|
* they're ready. Resolves the ready promise and dispatches a bubbling, composed `ready`
|
|
42
|
-
* event.
|
|
44
|
+
* event. Signals at most once per readiness cycle: a repeat call before {@link _resetReady}
|
|
45
|
+
* has re-armed the promise does nothing.
|
|
43
46
|
*/
|
|
44
47
|
_onReady() {
|
|
48
|
+
if (this._readyResolved)
|
|
49
|
+
return;
|
|
50
|
+
this._readyResolved = true;
|
|
45
51
|
this._readyResolve();
|
|
46
52
|
this.dispatchEvent(new CustomEvent('ready', { bubbles: true, composed: true }));
|
|
47
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the ready promise to its pending state. Subclasses should call this when the
|
|
56
|
+
* resource their readiness announced is torn down (typically from `disconnectedCallback`),
|
|
57
|
+
* so that a later re-initialization can signal readiness again. Does nothing while the
|
|
58
|
+
* promise is still pending — an in-flight waiter carries over to the next readiness cycle
|
|
59
|
+
* rather than being stranded on a promise nothing will ever resolve.
|
|
60
|
+
*/
|
|
61
|
+
_resetReady() {
|
|
62
|
+
if (!this._readyResolved)
|
|
63
|
+
return;
|
|
64
|
+
this._readyResolved = false;
|
|
65
|
+
this._readyPromise = new Promise((resolve) => {
|
|
66
|
+
this._readyResolve = resolve;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
48
69
|
/**
|
|
49
70
|
* Returns a promise that resolves with this element when it's ready. This is the low-level
|
|
50
71
|
* primitive underlying {@link whenReady}, which is the recommended way to wait for elements.
|
|
72
|
+
*
|
|
73
|
+
* Readiness tracks the element's current lifecycle: once a ready element is torn down (for
|
|
74
|
+
* example by removing it from the document), this returns a fresh promise that resolves when
|
|
75
|
+
* the element is next ready. A promise obtained earlier stays resolved — call this again
|
|
76
|
+
* after re-inserting an element rather than reusing a promise from before its removal.
|
|
51
77
|
* @returns A promise that resolves with this element when it's ready.
|
|
52
78
|
*/
|
|
53
79
|
ready() {
|
|
@@ -122,7 +148,14 @@
|
|
|
122
148
|
});
|
|
123
149
|
}
|
|
124
150
|
}
|
|
125
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Returns the promise that settles when the module has loaded. Awaited by the containing
|
|
153
|
+
* `<pc-app>` element before it creates its graphics device.
|
|
154
|
+
*
|
|
155
|
+
* @returns The load promise.
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
_getLoadPromise() {
|
|
126
159
|
return this.loadPromise;
|
|
127
160
|
}
|
|
128
161
|
}
|
|
@@ -181,10 +214,7 @@
|
|
|
181
214
|
// aria-valuenow is set, which is what marks a progressbar indeterminate. jsdom has no Web
|
|
182
215
|
// Animations API, so the guard degrades to a static bar there rather than crashing boot.
|
|
183
216
|
if (typeof this._fill.animate === 'function') {
|
|
184
|
-
this._sweep = this._fill.animate([
|
|
185
|
-
{ transform: 'scaleX(0.25) translateX(-100%)' },
|
|
186
|
-
{ transform: 'scaleX(0.25) translateX(500%)' }
|
|
187
|
-
], {
|
|
217
|
+
this._sweep = this._fill.animate([{ transform: 'scaleX(0.25) translateX(-100%)' }, { transform: 'scaleX(0.25) translateX(500%)' }], {
|
|
188
218
|
duration: 1000,
|
|
189
219
|
iterations: Infinity,
|
|
190
220
|
easing: 'ease-in-out'
|
|
@@ -417,7 +447,7 @@
|
|
|
417
447
|
*/
|
|
418
448
|
const parseComponents = (value, count) => {
|
|
419
449
|
const components = value.trim().split(/\s+/).map(Number);
|
|
420
|
-
if (components.length !== count || components.some(component => !Number.isFinite(component))) {
|
|
450
|
+
if (components.length !== count || components.some((component) => !Number.isFinite(component))) {
|
|
421
451
|
return null;
|
|
422
452
|
}
|
|
423
453
|
return components;
|
|
@@ -473,7 +503,10 @@
|
|
|
473
503
|
if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) {
|
|
474
504
|
let hex = value.slice(1);
|
|
475
505
|
if (hex.length === 3 || hex.length === 4) {
|
|
476
|
-
hex = hex
|
|
506
|
+
hex = hex
|
|
507
|
+
.split('')
|
|
508
|
+
.map((char) => char + char)
|
|
509
|
+
.join('');
|
|
477
510
|
}
|
|
478
511
|
return new playcanvas.Color().fromString(`#${hex}`);
|
|
479
512
|
}
|
|
@@ -570,7 +603,10 @@
|
|
|
570
603
|
// caller's default, or a later mutation would write back through it.
|
|
571
604
|
return [...defaultValue];
|
|
572
605
|
}
|
|
573
|
-
return value
|
|
606
|
+
return value
|
|
607
|
+
.split(',')
|
|
608
|
+
.map((tag) => tag.trim())
|
|
609
|
+
.filter((tag) => tag !== '');
|
|
574
610
|
};
|
|
575
611
|
/**
|
|
576
612
|
* Parse a Vec2 attribute value. The expected format is 2 space-separated numbers (e.g. '1 2').
|
|
@@ -662,6 +698,8 @@
|
|
|
662
698
|
return element?.entity ?? null;
|
|
663
699
|
};
|
|
664
700
|
|
|
701
|
+
/** The pointer event types the application synthesizes on `<pc-entity>` elements via picking. */
|
|
702
|
+
const pointerEventTypes = ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'];
|
|
665
703
|
/**
|
|
666
704
|
* The AppElement interface provides properties and methods for manipulating
|
|
667
705
|
* {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements.
|
|
@@ -692,7 +730,20 @@
|
|
|
692
730
|
*/
|
|
693
731
|
_optionsLocked = false;
|
|
694
732
|
_bar = null;
|
|
733
|
+
/**
|
|
734
|
+
* Whether the application has created its initial entity hierarchy. Read by EntityElement to
|
|
735
|
+
* decide whether a newly connected element must create its entity itself or leave it to the
|
|
736
|
+
* boot sweep.
|
|
737
|
+
* @internal
|
|
738
|
+
*/
|
|
695
739
|
_hierarchyReady = false;
|
|
740
|
+
/**
|
|
741
|
+
* Incremented on every connect and disconnect. Boot captures the value on entry and abandons
|
|
742
|
+
* itself wherever it resumes from an await if the value has moved on — so a boot whose
|
|
743
|
+
* element was removed cannot complete against a torn-down element, and a boot whose element
|
|
744
|
+
* was removed and re-inserted (which starts a boot of its own) cannot race the newer one.
|
|
745
|
+
*/
|
|
746
|
+
_bootGeneration = 0;
|
|
696
747
|
/**
|
|
697
748
|
* The elements backing this application's entities, keyed by the entity itself. Registered
|
|
698
749
|
* by EntityElement at creation and removed when an entity is destroyed, this joins engine
|
|
@@ -745,8 +796,16 @@
|
|
|
745
796
|
super();
|
|
746
797
|
// Bind methods to maintain 'this' context
|
|
747
798
|
this._onWindowResize = this._onWindowResize.bind(this);
|
|
799
|
+
// Track pointer listeners being added to and removed from descendant entities.
|
|
800
|
+
// Registered once here rather than on every boot - the handlers no-op while there is no
|
|
801
|
+
// canvas, and a re-booted element must not stack a second set.
|
|
802
|
+
pointerEventTypes.forEach((type) => {
|
|
803
|
+
this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type));
|
|
804
|
+
this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type));
|
|
805
|
+
});
|
|
748
806
|
}
|
|
749
807
|
async connectedCallback() {
|
|
808
|
+
const generation = ++this._bootGeneration;
|
|
750
809
|
// Created before the first await, so the bar is visible while modules and the graphics
|
|
751
810
|
// device are created, and exists before any disconnect could need to clean it up
|
|
752
811
|
if (this._loadingBar && !this._bar) {
|
|
@@ -755,7 +814,12 @@
|
|
|
755
814
|
// Get all pc-module elements that are direct children of the pc-app element
|
|
756
815
|
const moduleElements = this.querySelectorAll(':scope > pc-module');
|
|
757
816
|
// Wait for all modules to load
|
|
758
|
-
await Promise.all(Array.from(moduleElements).map(module => module.
|
|
817
|
+
await Promise.all(Array.from(moduleElements).map((module) => module._getLoadPromise()));
|
|
818
|
+
// The element may have been removed while the modules loaded. Nothing beyond the loading
|
|
819
|
+
// bar exists yet, and disconnectedCallback has already destroyed that.
|
|
820
|
+
if (generation !== this._bootGeneration) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
759
823
|
// Create and append the canvas to the element
|
|
760
824
|
this._canvas = document.createElement('canvas');
|
|
761
825
|
this.appendChild(this._canvas);
|
|
@@ -775,6 +839,13 @@
|
|
|
775
839
|
deviceTypes: deviceTypes,
|
|
776
840
|
stencil: this._stencilBuffer
|
|
777
841
|
});
|
|
842
|
+
// The element may have been removed while the device was created. disconnectedCallback
|
|
843
|
+
// has already cleaned up the canvas; the device was created inside the await, so it is
|
|
844
|
+
// this boot's to release.
|
|
845
|
+
if (generation !== this._bootGeneration) {
|
|
846
|
+
device.destroy();
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
778
849
|
// Assigned rather than resolved to a number here: the engine caps against the live
|
|
779
850
|
// window.devicePixelRatio on every resize, so an uncapped Infinity keeps following the
|
|
780
851
|
// display when a window moves between monitors of differing density.
|
|
@@ -851,27 +922,41 @@
|
|
|
851
922
|
this._pickerCreate();
|
|
852
923
|
// Get all pc-asset elements that are direct children of the pc-app element
|
|
853
924
|
const assetElements = this.querySelectorAll(':scope > pc-asset');
|
|
854
|
-
Array.from(assetElements)
|
|
855
|
-
assetElement.
|
|
925
|
+
for (const assetElement of Array.from(assetElements)) {
|
|
926
|
+
assetElement._createAsset();
|
|
856
927
|
const asset = assetElement.asset;
|
|
857
928
|
if (asset) {
|
|
858
929
|
app.assets.add(asset);
|
|
930
|
+
// Adding a fileless asset (one built purely from data, such as a sprite)
|
|
931
|
+
// completes it synchronously, dispatching the element's load event - whose
|
|
932
|
+
// listeners may have removed this element. Stop before the next addition
|
|
933
|
+
// reaches the destroyed registry, and before orphan entities are created.
|
|
934
|
+
if (generation !== this._bootGeneration) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
859
937
|
}
|
|
860
|
-
}
|
|
938
|
+
}
|
|
861
939
|
// Get all pc-material elements that are direct children of the pc-app element
|
|
862
940
|
const materialElements = this.querySelectorAll(':scope > pc-material');
|
|
863
941
|
Array.from(materialElements).forEach((materialElement) => {
|
|
864
|
-
materialElement.
|
|
942
|
+
materialElement._createMaterial();
|
|
865
943
|
});
|
|
866
944
|
// Create all entities
|
|
867
945
|
const entityElements = this.querySelectorAll('pc-entity');
|
|
868
946
|
Array.from(entityElements).forEach((entityElement) => {
|
|
869
|
-
entityElement.
|
|
947
|
+
entityElement._createEntity(app);
|
|
870
948
|
});
|
|
871
949
|
// Build hierarchy
|
|
872
950
|
entityElements.forEach((entityElement) => {
|
|
873
|
-
entityElement.
|
|
951
|
+
entityElement._buildHierarchy(app);
|
|
874
952
|
});
|
|
953
|
+
// Building the hierarchy dispatched each entity's ready event synchronously, and a
|
|
954
|
+
// listener may have removed the element. The sweep itself degrades safely - destroying
|
|
955
|
+
// the application nulls every element's entity, so the remaining builds no-op - but the
|
|
956
|
+
// teardown's reset must not be overwritten here.
|
|
957
|
+
if (generation !== this._bootGeneration) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
875
960
|
this._hierarchyReady = true;
|
|
876
961
|
// Forward the engine's preload lifecycle as DOM ProgressEvents on this element. The
|
|
877
962
|
// listener must be attached before preload() is called: an asset that is already loaded
|
|
@@ -888,8 +973,19 @@
|
|
|
888
973
|
this._loadProgress = total === 0 ? 1 : 0;
|
|
889
974
|
this._bar?.progress(0, total);
|
|
890
975
|
this.dispatchEvent(new ProgressEvent('progress', { lengthComputable: true, loaded: 0, total }));
|
|
976
|
+
// The progress dispatch above ran listeners synchronously, and one may have removed the
|
|
977
|
+
// element. The application is already destroyed - it must not be asked to preload.
|
|
978
|
+
if (generation !== this._bootGeneration) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
891
981
|
// Load assets before starting the application
|
|
892
982
|
app.preload(() => {
|
|
983
|
+
// The element may have been removed while assets loaded. The application is already
|
|
984
|
+
// destroyed, so it must not be started — and readiness must not be signaled for a
|
|
985
|
+
// boot that no longer owns the element.
|
|
986
|
+
if (generation !== this._bootGeneration) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
893
989
|
// Scope the counter to this preload pass, so a later app.preload() call by user code
|
|
894
990
|
// cannot push `loaded` past `total`
|
|
895
991
|
app.off('preload:progress', onPreloadProgress);
|
|
@@ -905,6 +1001,9 @@
|
|
|
905
1001
|
});
|
|
906
1002
|
}
|
|
907
1003
|
disconnectedCallback() {
|
|
1004
|
+
// Invalidate any boot still in flight, so it abandons itself when it next resumes
|
|
1005
|
+
// instead of completing against a torn-down element.
|
|
1006
|
+
this._bootGeneration++;
|
|
908
1007
|
this._optionsLocked = false;
|
|
909
1008
|
this._pickerDestroy();
|
|
910
1009
|
// Clean up the application. Destroying it destroys every entity, whose destroy hooks
|
|
@@ -917,6 +1016,11 @@
|
|
|
917
1016
|
this._loadProgress = 0;
|
|
918
1017
|
this._bar?.destroy();
|
|
919
1018
|
this._bar = null;
|
|
1019
|
+
// Return the element to its pre-boot state, so re-inserting it boots afresh: descendants
|
|
1020
|
+
// must neither see a hierarchy that no longer exists nor resume against a readiness that
|
|
1021
|
+
// no longer holds.
|
|
1022
|
+
this._hierarchyReady = false;
|
|
1023
|
+
this._resetReady();
|
|
920
1024
|
// Remove event listeners
|
|
921
1025
|
window.removeEventListener('resize', this._onWindowResize);
|
|
922
1026
|
// Remove the canvas
|
|
@@ -944,14 +1048,11 @@
|
|
|
944
1048
|
this._pointerHandlers.pointermove = listener(this._onPointerMove);
|
|
945
1049
|
this._pointerHandlers.pointerdown = listener(this._onPointerDown);
|
|
946
1050
|
this._pointerHandlers.pointerup = listener(this._onPointerUp);
|
|
947
|
-
//
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
// created from onpointer* attributes when their elements were first upgraded)
|
|
953
|
-
const anyListeners = Array.from(this.querySelectorAll('pc-entity'))
|
|
954
|
-
.some(entity => entity.hasListeners(type));
|
|
1051
|
+
// Attach canvas handlers for listeners registered before this boot (e.g. handlers
|
|
1052
|
+
// created from onpointer* attributes when their elements were first upgraded, or
|
|
1053
|
+
// listeners carried over from before a re-boot)
|
|
1054
|
+
pointerEventTypes.forEach((type) => {
|
|
1055
|
+
const anyListeners = Array.from(this.querySelectorAll('pc-entity')).some((entity) => entity._hasListeners(type));
|
|
955
1056
|
if (anyListeners) {
|
|
956
1057
|
this._onPointerListenerAdded(type);
|
|
957
1058
|
}
|
|
@@ -966,6 +1067,7 @@
|
|
|
966
1067
|
});
|
|
967
1068
|
}
|
|
968
1069
|
this._picker = null;
|
|
1070
|
+
this._hoveredEntity = null;
|
|
969
1071
|
this._pointerHandlers = {
|
|
970
1072
|
pointermove: null,
|
|
971
1073
|
pointerdown: null,
|
|
@@ -985,7 +1087,7 @@
|
|
|
985
1087
|
*
|
|
986
1088
|
* @param entity - The entity.
|
|
987
1089
|
* @param element - The element that created it.
|
|
988
|
-
* @
|
|
1090
|
+
* @internal
|
|
989
1091
|
*/
|
|
990
1092
|
_registerEntityElement(entity, element) {
|
|
991
1093
|
this._entityElements.set(entity, element);
|
|
@@ -994,7 +1096,7 @@
|
|
|
994
1096
|
* Removes the registration for a destroyed entity. Called by EntityElement.
|
|
995
1097
|
*
|
|
996
1098
|
* @param entity - The entity.
|
|
997
|
-
* @
|
|
1099
|
+
* @internal
|
|
998
1100
|
*/
|
|
999
1101
|
_unregisterEntityElement(entity) {
|
|
1000
1102
|
this._entityElements.delete(entity);
|
|
@@ -1040,7 +1142,7 @@
|
|
|
1040
1142
|
_elementWithListener(node, type) {
|
|
1041
1143
|
while (node !== null) {
|
|
1042
1144
|
const element = this._entityElements.get(node);
|
|
1043
|
-
if (element?.
|
|
1145
|
+
if (element?._hasListeners(type)) {
|
|
1044
1146
|
return element;
|
|
1045
1147
|
}
|
|
1046
1148
|
node = node.parent;
|
|
@@ -1097,17 +1199,17 @@
|
|
|
1097
1199
|
const newHoverEntity = this._elementFromNode(node);
|
|
1098
1200
|
// Handle enter/leave events
|
|
1099
1201
|
if (this._hoveredEntity !== newHoverEntity) {
|
|
1100
|
-
if (this._hoveredEntity && this._hoveredEntity.
|
|
1202
|
+
if (this._hoveredEntity && this._hoveredEntity._hasListeners('pointerleave')) {
|
|
1101
1203
|
this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event));
|
|
1102
1204
|
}
|
|
1103
|
-
if (newHoverEntity && newHoverEntity.
|
|
1205
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointerenter')) {
|
|
1104
1206
|
newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event));
|
|
1105
1207
|
}
|
|
1106
1208
|
}
|
|
1107
1209
|
// Update hover state
|
|
1108
1210
|
this._hoveredEntity = newHoverEntity;
|
|
1109
1211
|
// Handle pointermove event
|
|
1110
|
-
if (newHoverEntity && newHoverEntity.
|
|
1212
|
+
if (newHoverEntity && newHoverEntity._hasListeners('pointermove')) {
|
|
1111
1213
|
newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event));
|
|
1112
1214
|
}
|
|
1113
1215
|
}
|
|
@@ -1137,22 +1239,21 @@
|
|
|
1137
1239
|
if (!this._hasPointerListeners[type] && this._canvas) {
|
|
1138
1240
|
this._hasPointerListeners[type] = true;
|
|
1139
1241
|
// For enter/leave events, we need the move handler
|
|
1140
|
-
const handler =
|
|
1141
|
-
this._pointerHandlers.pointermove
|
|
1142
|
-
this._pointerHandlers[type];
|
|
1242
|
+
const handler = type === 'pointerenter' || type === 'pointerleave'
|
|
1243
|
+
? this._pointerHandlers.pointermove
|
|
1244
|
+
: this._pointerHandlers[type];
|
|
1143
1245
|
if (handler) {
|
|
1144
1246
|
this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
|
|
1145
1247
|
}
|
|
1146
1248
|
}
|
|
1147
1249
|
}
|
|
1148
1250
|
_onPointerListenerRemoved(type) {
|
|
1149
|
-
const hasListeners = Array.from(this.querySelectorAll('pc-entity'))
|
|
1150
|
-
.some(entity => entity.hasListeners(type));
|
|
1251
|
+
const hasListeners = Array.from(this.querySelectorAll('pc-entity')).some((entity) => entity._hasListeners(type));
|
|
1151
1252
|
if (!hasListeners && this._canvas) {
|
|
1152
1253
|
this._hasPointerListeners[type] = false;
|
|
1153
|
-
const handler =
|
|
1154
|
-
this._pointerHandlers.pointermove
|
|
1155
|
-
this._pointerHandlers[type];
|
|
1254
|
+
const handler = type === 'pointerenter' || type === 'pointerleave'
|
|
1255
|
+
? this._pointerHandlers.pointermove
|
|
1256
|
+
: this._pointerHandlers[type];
|
|
1156
1257
|
if (handler) {
|
|
1157
1258
|
this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler);
|
|
1158
1259
|
}
|
|
@@ -1233,14 +1334,6 @@
|
|
|
1233
1334
|
get depthBuffer() {
|
|
1234
1335
|
return this._depthBuffer;
|
|
1235
1336
|
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Gets the hierarchy ready flag.
|
|
1238
|
-
* @returns The hierarchy ready flag.
|
|
1239
|
-
* @ignore
|
|
1240
|
-
*/
|
|
1241
|
-
get hierarchyReady() {
|
|
1242
|
-
return this._hierarchyReady;
|
|
1243
|
-
}
|
|
1244
1337
|
/**
|
|
1245
1338
|
* Sets whether the application shows its built-in loading bar while it boots and preloads its
|
|
1246
1339
|
* assets. Enabled by default; setting `false` removes the bar immediately, while setting
|
|
@@ -1409,7 +1502,14 @@
|
|
|
1409
1502
|
get entity() {
|
|
1410
1503
|
return this._entity;
|
|
1411
1504
|
}
|
|
1412
|
-
|
|
1505
|
+
/**
|
|
1506
|
+
* Creates the backing entity. Called by the containing `<pc-app>` element during its boot
|
|
1507
|
+
* sweep, and on connection for elements inserted while the application is already running.
|
|
1508
|
+
*
|
|
1509
|
+
* @param app - The application to create the entity in.
|
|
1510
|
+
* @internal
|
|
1511
|
+
*/
|
|
1512
|
+
_createEntity(app) {
|
|
1413
1513
|
// Guard against double creation. When a subtree is inserted at runtime (e.g. cloning a
|
|
1414
1514
|
// `<template>`), an ancestor's connectedCallback eagerly creates descendant entities; the
|
|
1415
1515
|
// descendants' own connectedCallbacks would otherwise create them a second time.
|
|
@@ -1440,8 +1540,10 @@
|
|
|
1440
1540
|
}
|
|
1441
1541
|
/**
|
|
1442
1542
|
* Handles the destruction of the backing entity. Resets the element so a later re-insertion
|
|
1443
|
-
* starts clean: `_built` must be cleared alongside `_entity`, or
|
|
1444
|
-
* and a re-created entity would never be parented.
|
|
1543
|
+
* starts clean: `_built` must be cleared alongside `_entity`, or _buildHierarchy would bail
|
|
1544
|
+
* and a re-created entity would never be parented. Readiness is re-armed for the same
|
|
1545
|
+
* reason — with the entity gone, a resolved ready promise would resume its awaiters against
|
|
1546
|
+
* a null `entity`.
|
|
1445
1547
|
*
|
|
1446
1548
|
* @param entity - The entity that was destroyed.
|
|
1447
1549
|
*/
|
|
@@ -1450,8 +1552,18 @@
|
|
|
1450
1552
|
this._appElement = null;
|
|
1451
1553
|
this._entity = null;
|
|
1452
1554
|
this._built = false;
|
|
1555
|
+
this._resetReady();
|
|
1453
1556
|
}
|
|
1454
|
-
|
|
1557
|
+
/**
|
|
1558
|
+
* Parents the backing entity: under the entity of the nearest ancestor `<pc-entity>` when
|
|
1559
|
+
* there is one, and under the application root otherwise. Called by the containing `<pc-app>`
|
|
1560
|
+
* element once a sweep has created every entity, so a parent's existence never depends on
|
|
1561
|
+
* document order.
|
|
1562
|
+
*
|
|
1563
|
+
* @param app - The application whose root adopts parentless entities.
|
|
1564
|
+
* @internal
|
|
1565
|
+
*/
|
|
1566
|
+
_buildHierarchy(app) {
|
|
1455
1567
|
if (!this.entity || this._built)
|
|
1456
1568
|
return;
|
|
1457
1569
|
this._built = true;
|
|
@@ -1477,17 +1589,17 @@
|
|
|
1477
1589
|
return;
|
|
1478
1590
|
}
|
|
1479
1591
|
// If app is already running, create entity immediately
|
|
1480
|
-
if (closestApp.
|
|
1592
|
+
if (closestApp._hierarchyReady) {
|
|
1481
1593
|
const app = closestApp.app;
|
|
1482
|
-
this.
|
|
1483
|
-
this.
|
|
1594
|
+
this._createEntity(app);
|
|
1595
|
+
this._buildHierarchy(app);
|
|
1484
1596
|
// Handle any child entities that might exist
|
|
1485
1597
|
const childEntities = this.querySelectorAll('pc-entity');
|
|
1486
1598
|
childEntities.forEach((child) => {
|
|
1487
|
-
child.
|
|
1599
|
+
child._createEntity(app);
|
|
1488
1600
|
});
|
|
1489
1601
|
childEntities.forEach((child) => {
|
|
1490
|
-
child.
|
|
1602
|
+
child._buildHierarchy(app);
|
|
1491
1603
|
});
|
|
1492
1604
|
}
|
|
1493
1605
|
}
|
|
@@ -1681,14 +1793,23 @@
|
|
|
1681
1793
|
}
|
|
1682
1794
|
removeEventListener(type, listener, options) {
|
|
1683
1795
|
if (this._listeners[type]) {
|
|
1684
|
-
this._listeners[type] = this._listeners[type].filter(l => l !== listener);
|
|
1796
|
+
this._listeners[type] = this._listeners[type].filter((l) => l !== listener);
|
|
1685
1797
|
}
|
|
1686
1798
|
super.removeEventListener(type, listener, options);
|
|
1687
1799
|
if (type.startsWith('pointer')) {
|
|
1688
1800
|
this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true }));
|
|
1689
1801
|
}
|
|
1690
1802
|
}
|
|
1691
|
-
|
|
1803
|
+
/**
|
|
1804
|
+
* Whether the element has a listener for an event type, registered either with
|
|
1805
|
+
* {@link addEventListener} or with the matching inline `onpointer*` attribute. Read by the
|
|
1806
|
+
* containing `<pc-app>` element to gate pointer event synthesis.
|
|
1807
|
+
*
|
|
1808
|
+
* @param type - The event type.
|
|
1809
|
+
* @returns Whether a listener is registered.
|
|
1810
|
+
* @internal
|
|
1811
|
+
*/
|
|
1812
|
+
_hasListeners(type) {
|
|
1692
1813
|
return Boolean(this._listeners[type]?.length) || this._inlineHandlerTypes.has(type);
|
|
1693
1814
|
}
|
|
1694
1815
|
}
|
|
@@ -1979,7 +2100,7 @@
|
|
|
1979
2100
|
const app = appElement.app;
|
|
1980
2101
|
if (!app)
|
|
1981
2102
|
return; // pc-app is re-connecting; its own boot will create this asset
|
|
1982
|
-
this.
|
|
2103
|
+
this._createAsset();
|
|
1983
2104
|
if (this.asset) {
|
|
1984
2105
|
app.assets.add(this.asset); // add() auto-loads when preload is true
|
|
1985
2106
|
if (!this.lazy) {
|
|
@@ -1987,13 +2108,15 @@
|
|
|
1987
2108
|
}
|
|
1988
2109
|
}
|
|
1989
2110
|
}
|
|
1990
|
-
// Never ready if
|
|
2111
|
+
// Never ready if _createAsset failed (unsupported asset type)
|
|
1991
2112
|
if (this.asset) {
|
|
1992
2113
|
this._onReady();
|
|
1993
2114
|
}
|
|
1994
2115
|
}
|
|
1995
2116
|
disconnectedCallback() {
|
|
1996
|
-
this.
|
|
2117
|
+
this._destroyAsset();
|
|
2118
|
+
// Re-arm readiness so a re-inserted element announces the asset it creates then
|
|
2119
|
+
this._resetReady();
|
|
1997
2120
|
}
|
|
1998
2121
|
_onAssetLoad() {
|
|
1999
2122
|
this.dispatchEvent(new Event('load'));
|
|
@@ -2003,7 +2126,14 @@
|
|
|
2003
2126
|
message: err instanceof Error ? err.message : String(err)
|
|
2004
2127
|
}));
|
|
2005
2128
|
}
|
|
2006
|
-
|
|
2129
|
+
/**
|
|
2130
|
+
* Creates the asset from the element's attributes. Called by the containing `<pc-app>`
|
|
2131
|
+
* element during its boot sweep, and on connection for elements inserted while the
|
|
2132
|
+
* application is already running.
|
|
2133
|
+
*
|
|
2134
|
+
* @internal
|
|
2135
|
+
*/
|
|
2136
|
+
_createAsset() {
|
|
2007
2137
|
const id = this.getAttribute('id') || '';
|
|
2008
2138
|
const src = this.getAttribute('src') || '';
|
|
2009
2139
|
let type = this.getAttribute('type');
|
|
@@ -2093,7 +2223,7 @@
|
|
|
2093
2223
|
}
|
|
2094
2224
|
return data;
|
|
2095
2225
|
}
|
|
2096
|
-
|
|
2226
|
+
_destroyAsset() {
|
|
2097
2227
|
if (this.asset) {
|
|
2098
2228
|
// A caller that keeps the Asset alive must not dispatch on a removed element
|
|
2099
2229
|
this.asset.off('load', this._onAssetLoad, this);
|
|
@@ -2121,6 +2251,13 @@
|
|
|
2121
2251
|
get lazy() {
|
|
2122
2252
|
return this._lazy;
|
|
2123
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Returns the {@link Asset} created by the `<pc-asset>` element with the given `id`, or
|
|
2256
|
+
* `undefined` if there is no such element or its asset has not been created yet.
|
|
2257
|
+
*
|
|
2258
|
+
* @param id - The `id` of the `<pc-asset>` element.
|
|
2259
|
+
* @returns The asset, or `undefined`.
|
|
2260
|
+
*/
|
|
2124
2261
|
static get(id) {
|
|
2125
2262
|
const assetElement = document.querySelector(`pc-asset[id="${id}"]`);
|
|
2126
2263
|
return assetElement?.asset;
|
|
@@ -2146,6 +2283,14 @@
|
|
|
2146
2283
|
_enabled = true;
|
|
2147
2284
|
_component = null;
|
|
2148
2285
|
_appElement = null;
|
|
2286
|
+
/**
|
|
2287
|
+
* Incremented on every connect and disconnect. connectedCallback captures the value on entry
|
|
2288
|
+
* and abandons itself wherever it resumes from an await if the value has moved on — so a
|
|
2289
|
+
* callback whose element was removed cannot act on a torn-down tree, and one whose element
|
|
2290
|
+
* was removed and re-inserted (which runs a callback of its own) cannot add the component a
|
|
2291
|
+
* second time.
|
|
2292
|
+
*/
|
|
2293
|
+
_connectionGeneration = 0;
|
|
2149
2294
|
/**
|
|
2150
2295
|
* Creates a new ComponentElement instance.
|
|
2151
2296
|
*
|
|
@@ -2156,11 +2301,17 @@
|
|
|
2156
2301
|
super();
|
|
2157
2302
|
this._componentName = componentName;
|
|
2158
2303
|
}
|
|
2159
|
-
|
|
2304
|
+
/**
|
|
2305
|
+
* Returns the data the component is created with. Overridden by subclasses to supply the
|
|
2306
|
+
* initial values of their cached properties.
|
|
2307
|
+
*
|
|
2308
|
+
* @returns The initial component data.
|
|
2309
|
+
*/
|
|
2160
2310
|
getInitialComponentData() {
|
|
2161
2311
|
return {};
|
|
2162
2312
|
}
|
|
2163
|
-
async
|
|
2313
|
+
async _addComponent() {
|
|
2314
|
+
const generation = this._connectionGeneration;
|
|
2164
2315
|
const entityElement = this.closestEntity;
|
|
2165
2316
|
if (!entityElement) {
|
|
2166
2317
|
// A component can only exist on an entity, so an element placed outside one is inert.
|
|
@@ -2170,19 +2321,42 @@
|
|
|
2170
2321
|
return;
|
|
2171
2322
|
}
|
|
2172
2323
|
await entityElement.ready();
|
|
2324
|
+
// The element may have been removed, or removed and re-inserted, while the entity became
|
|
2325
|
+
// ready — the component belongs to the connection that owns the current generation.
|
|
2326
|
+
if (generation !== this._connectionGeneration) {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2173
2329
|
// Add the component to the entity
|
|
2174
2330
|
const data = this.getInitialComponentData();
|
|
2175
2331
|
this._component = entityElement.entity.addComponent(this._componentName, data);
|
|
2176
2332
|
}
|
|
2177
|
-
|
|
2333
|
+
/**
|
|
2334
|
+
* Configures the newly added component. Overridden by subclasses whose setup goes beyond
|
|
2335
|
+
* the initial data — child-element handling, asset resolution and the like.
|
|
2336
|
+
*/
|
|
2337
|
+
initComponent() {
|
|
2338
|
+
// optional hook
|
|
2339
|
+
}
|
|
2178
2340
|
async connectedCallback() {
|
|
2341
|
+
const generation = ++this._connectionGeneration;
|
|
2179
2342
|
this._appElement = this.closestApp ?? null;
|
|
2180
2343
|
await this._appElement?.ready();
|
|
2181
|
-
|
|
2344
|
+
// The element may have been removed, or removed and re-inserted, while the application
|
|
2345
|
+
// became ready. A re-insertion runs a connectedCallback of its own, so a stale resume
|
|
2346
|
+
// must not add the component alongside it.
|
|
2347
|
+
if (generation !== this._connectionGeneration) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
await this._addComponent();
|
|
2351
|
+
if (generation !== this._connectionGeneration) {
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2182
2354
|
this.initComponent();
|
|
2183
2355
|
this._onReady();
|
|
2184
2356
|
}
|
|
2185
2357
|
disconnectedCallback() {
|
|
2358
|
+
// Invalidate any connectedCallback still suspended on an await
|
|
2359
|
+
this._connectionGeneration++;
|
|
2186
2360
|
// Remove the component when the element is disconnected. Skip this when the owning
|
|
2187
2361
|
// application has already been destroyed — removing a <pc-app> disconnects it before
|
|
2188
2362
|
// its children, taking the component systems with it.
|
|
@@ -2191,6 +2365,7 @@
|
|
|
2191
2365
|
}
|
|
2192
2366
|
this._component = null;
|
|
2193
2367
|
this._appElement = null;
|
|
2368
|
+
this._resetReady();
|
|
2194
2369
|
}
|
|
2195
2370
|
/**
|
|
2196
2371
|
* The PlayCanvas component instance. `null` until the element is ready, and also for an
|
|
@@ -3248,7 +3423,17 @@
|
|
|
3248
3423
|
return this._type;
|
|
3249
3424
|
}
|
|
3250
3425
|
static get observedAttributes() {
|
|
3251
|
-
return [
|
|
3426
|
+
return [
|
|
3427
|
+
...super.observedAttributes,
|
|
3428
|
+
'angular-offset',
|
|
3429
|
+
'axis',
|
|
3430
|
+
'convex-hull',
|
|
3431
|
+
'half-extents',
|
|
3432
|
+
'height',
|
|
3433
|
+
'linear-offset',
|
|
3434
|
+
'radius',
|
|
3435
|
+
'type'
|
|
3436
|
+
];
|
|
3252
3437
|
}
|
|
3253
3438
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
3254
3439
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -4953,7 +5138,7 @@
|
|
|
4953
5138
|
}
|
|
4954
5139
|
// Set all the config properties on the component
|
|
4955
5140
|
for (const key in resource) {
|
|
4956
|
-
if (
|
|
5141
|
+
if (Object.hasOwn(resource, key)) {
|
|
4957
5142
|
this.component[key] = resource[key];
|
|
4958
5143
|
}
|
|
4959
5144
|
}
|
|
@@ -5026,10 +5211,7 @@
|
|
|
5026
5211
|
}
|
|
5027
5212
|
}
|
|
5028
5213
|
static get observedAttributes() {
|
|
5029
|
-
return [
|
|
5030
|
-
...super.observedAttributes,
|
|
5031
|
-
'asset'
|
|
5032
|
-
];
|
|
5214
|
+
return [...super.observedAttributes, 'asset'];
|
|
5033
5215
|
}
|
|
5034
5216
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
5035
5217
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -5189,7 +5371,7 @@
|
|
|
5189
5371
|
_twoSidedLighting = false;
|
|
5190
5372
|
_useFog = true;
|
|
5191
5373
|
_useLighting = true;
|
|
5192
|
-
// Diverges from the engine default of false - see the class docblock and
|
|
5374
|
+
// Diverges from the engine default of false - see the class docblock and _createMaterial()
|
|
5193
5375
|
_useMetalness = true;
|
|
5194
5376
|
_useMetalnessSpecularColor = false;
|
|
5195
5377
|
_useSkybox = true;
|
|
@@ -5202,6 +5384,10 @@
|
|
|
5202
5384
|
_mapHandles = new Map();
|
|
5203
5385
|
_updateScheduled = false;
|
|
5204
5386
|
_glossConflictWarned = false;
|
|
5387
|
+
/**
|
|
5388
|
+
* The material. `null` until the containing application has created it — an element present
|
|
5389
|
+
* at startup has its material once the application is ready.
|
|
5390
|
+
*/
|
|
5205
5391
|
material = null;
|
|
5206
5392
|
async connectedCallback() {
|
|
5207
5393
|
const appElement = this.parentElement?.closest('pc-app') ?? null;
|
|
@@ -5219,10 +5405,17 @@
|
|
|
5219
5405
|
if (!this.material) {
|
|
5220
5406
|
if (!appElement.app)
|
|
5221
5407
|
return; // pc-app is re-connecting; its own boot will create this
|
|
5222
|
-
this.
|
|
5408
|
+
this._createMaterial();
|
|
5223
5409
|
}
|
|
5224
5410
|
}
|
|
5225
|
-
|
|
5411
|
+
/**
|
|
5412
|
+
* Creates the material from the element's cached properties. Called by the containing
|
|
5413
|
+
* `<pc-app>` element during its boot sweep, and on connection for elements inserted while
|
|
5414
|
+
* the application is already running.
|
|
5415
|
+
*
|
|
5416
|
+
* @internal
|
|
5417
|
+
*/
|
|
5418
|
+
_createMaterial() {
|
|
5226
5419
|
const material = new playcanvas.StandardMaterial();
|
|
5227
5420
|
this.material = material;
|
|
5228
5421
|
material.alphaTest = this._alphaTest;
|
|
@@ -5349,9 +5542,9 @@
|
|
|
5349
5542
|
* warning latches and reports once per episode, clearing when the clash is resolved.
|
|
5350
5543
|
*/
|
|
5351
5544
|
_warnGlossConflict() {
|
|
5352
|
-
const quote = (names) => `'${names.join('
|
|
5353
|
-
const roughness = roughnessAliases.filter(name => this.hasAttribute(name));
|
|
5354
|
-
const gloss = glossConflicts.filter(name => this.hasAttribute(name));
|
|
5545
|
+
const quote = (names) => `'${names.join("', '")}'`;
|
|
5546
|
+
const roughness = roughnessAliases.filter((name) => this.hasAttribute(name));
|
|
5547
|
+
const gloss = glossConflicts.filter((name) => this.hasAttribute(name));
|
|
5355
5548
|
if (roughness.length === 0 || gloss.length === 0) {
|
|
5356
5549
|
this._glossConflictWarned = false;
|
|
5357
5550
|
return;
|
|
@@ -5369,7 +5562,7 @@
|
|
|
5369
5562
|
* @param id - The id of the `pc-asset`, or an empty string to clear the slot.
|
|
5370
5563
|
* @param slot - The material property to write.
|
|
5371
5564
|
*/
|
|
5372
|
-
|
|
5565
|
+
_setMap(id, slot) {
|
|
5373
5566
|
// Drop any load still pending for this slot - its texture is no longer the one we want
|
|
5374
5567
|
this._mapHandles.get(slot)?.off();
|
|
5375
5568
|
this._mapHandles.delete(slot);
|
|
@@ -5463,7 +5656,7 @@
|
|
|
5463
5656
|
*/
|
|
5464
5657
|
set aoMap(value) {
|
|
5465
5658
|
this._aoMap = value;
|
|
5466
|
-
this.
|
|
5659
|
+
this._setMap(value, 'aoMap');
|
|
5467
5660
|
}
|
|
5468
5661
|
/**
|
|
5469
5662
|
* Gets the id of the `pc-asset` used as the ambient occlusion map.
|
|
@@ -5695,7 +5888,7 @@
|
|
|
5695
5888
|
*/
|
|
5696
5889
|
set diffuseMap(value) {
|
|
5697
5890
|
this._diffuseMap = value;
|
|
5698
|
-
this.
|
|
5891
|
+
this._setMap(value, 'diffuseMap');
|
|
5699
5892
|
}
|
|
5700
5893
|
/**
|
|
5701
5894
|
* Gets the id of the `pc-asset` used as the diffuse map.
|
|
@@ -5836,7 +6029,7 @@
|
|
|
5836
6029
|
*/
|
|
5837
6030
|
set emissiveMap(value) {
|
|
5838
6031
|
this._emissiveMap = value;
|
|
5839
|
-
this.
|
|
6032
|
+
this._setMap(value, 'emissiveMap');
|
|
5840
6033
|
}
|
|
5841
6034
|
/**
|
|
5842
6035
|
* Gets the id of the `pc-asset` used as the emissive map.
|
|
@@ -6014,7 +6207,7 @@
|
|
|
6014
6207
|
*/
|
|
6015
6208
|
set glossMap(value) {
|
|
6016
6209
|
this._glossMap = value;
|
|
6017
|
-
this.
|
|
6210
|
+
this._setMap(value, 'glossMap');
|
|
6018
6211
|
}
|
|
6019
6212
|
/**
|
|
6020
6213
|
* Gets the id of the `pc-asset` used as the gloss map.
|
|
@@ -6119,7 +6312,7 @@
|
|
|
6119
6312
|
*/
|
|
6120
6313
|
set heightMap(value) {
|
|
6121
6314
|
this._heightMap = value;
|
|
6122
|
-
this.
|
|
6315
|
+
this._setMap(value, 'heightMap');
|
|
6123
6316
|
}
|
|
6124
6317
|
/**
|
|
6125
6318
|
* Gets the id of the `pc-asset` used as the height map.
|
|
@@ -6260,7 +6453,7 @@
|
|
|
6260
6453
|
*/
|
|
6261
6454
|
set metalnessMap(value) {
|
|
6262
6455
|
this._metalnessMap = value;
|
|
6263
|
-
this.
|
|
6456
|
+
this._setMap(value, 'metalnessMap');
|
|
6264
6457
|
}
|
|
6265
6458
|
/**
|
|
6266
6459
|
* Gets the id of the `pc-asset` used as the metalness map.
|
|
@@ -6365,7 +6558,7 @@
|
|
|
6365
6558
|
*/
|
|
6366
6559
|
set normalMap(value) {
|
|
6367
6560
|
this._normalMap = value;
|
|
6368
|
-
this.
|
|
6561
|
+
this._setMap(value, 'normalMap');
|
|
6369
6562
|
}
|
|
6370
6563
|
/**
|
|
6371
6564
|
* Gets the id of the `pc-asset` used as the normal map.
|
|
@@ -6453,7 +6646,7 @@
|
|
|
6453
6646
|
set occludeDirect(value) {
|
|
6454
6647
|
this._occludeDirect = value;
|
|
6455
6648
|
if (this.material) {
|
|
6456
|
-
// @ts-ignore see
|
|
6649
|
+
// @ts-ignore see _createMaterial() - the engine mistypes occludeDirect as a number
|
|
6457
6650
|
this.material.occludeDirect = value;
|
|
6458
6651
|
this._scheduleUpdate();
|
|
6459
6652
|
}
|
|
@@ -6545,7 +6738,7 @@
|
|
|
6545
6738
|
*/
|
|
6546
6739
|
set opacityMap(value) {
|
|
6547
6740
|
this._opacityMap = value;
|
|
6548
|
-
this.
|
|
6741
|
+
this._setMap(value, 'opacityMap');
|
|
6549
6742
|
}
|
|
6550
6743
|
/**
|
|
6551
6744
|
* Gets the id of the `pc-asset` used as the opacity map.
|
|
@@ -6863,6 +7056,13 @@
|
|
|
6863
7056
|
get useTonemap() {
|
|
6864
7057
|
return this._useTonemap;
|
|
6865
7058
|
}
|
|
7059
|
+
/**
|
|
7060
|
+
* Returns the {@link StandardMaterial} created by the `<pc-material>` element with the given
|
|
7061
|
+
* `id`, or `undefined` if there is no such element or its material has not been created yet.
|
|
7062
|
+
*
|
|
7063
|
+
* @param id - The `id` of the `<pc-material>` element.
|
|
7064
|
+
* @returns The material, or `undefined`.
|
|
7065
|
+
*/
|
|
6866
7066
|
static get(id) {
|
|
6867
7067
|
const materialElement = document.querySelector(`pc-material[id="${id}"]`);
|
|
6868
7068
|
return materialElement?.material;
|
|
@@ -7501,7 +7701,18 @@
|
|
|
7501
7701
|
return this._type;
|
|
7502
7702
|
}
|
|
7503
7703
|
static get observedAttributes() {
|
|
7504
|
-
return [
|
|
7704
|
+
return [
|
|
7705
|
+
...super.observedAttributes,
|
|
7706
|
+
'angular-damping',
|
|
7707
|
+
'angular-factor',
|
|
7708
|
+
'friction',
|
|
7709
|
+
'linear-damping',
|
|
7710
|
+
'linear-factor',
|
|
7711
|
+
'mass',
|
|
7712
|
+
'restitution',
|
|
7713
|
+
'rolling-friction',
|
|
7714
|
+
'type'
|
|
7715
|
+
];
|
|
7505
7716
|
}
|
|
7506
7717
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
7507
7718
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -7806,13 +8017,7 @@
|
|
|
7806
8017
|
return this._handle;
|
|
7807
8018
|
}
|
|
7808
8019
|
static get observedAttributes() {
|
|
7809
|
-
return [
|
|
7810
|
-
...super.observedAttributes,
|
|
7811
|
-
'orientation',
|
|
7812
|
-
'value',
|
|
7813
|
-
'handle-size',
|
|
7814
|
-
'handle'
|
|
7815
|
-
];
|
|
8020
|
+
return [...super.observedAttributes, 'orientation', 'value', 'handle-size', 'handle'];
|
|
7816
8021
|
}
|
|
7817
8022
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
7818
8023
|
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
@@ -8038,7 +8243,8 @@
|
|
|
8038
8243
|
set horizontalScrollbarVisibility(value) {
|
|
8039
8244
|
this._horizontalScrollbarVisibility = value;
|
|
8040
8245
|
if (this.component) {
|
|
8041
|
-
this.component.horizontalScrollbarVisibility =
|
|
8246
|
+
this.component.horizontalScrollbarVisibility =
|
|
8247
|
+
visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
|
|
8042
8248
|
}
|
|
8043
8249
|
}
|
|
8044
8250
|
/**
|
|
@@ -8056,7 +8262,8 @@
|
|
|
8056
8262
|
set verticalScrollbarVisibility(value) {
|
|
8057
8263
|
this._verticalScrollbarVisibility = value;
|
|
8058
8264
|
if (this.component) {
|
|
8059
|
-
this.component.verticalScrollbarVisibility =
|
|
8265
|
+
this.component.verticalScrollbarVisibility =
|
|
8266
|
+
visibilities.get(value) ?? playcanvas.SCROLLBAR_VISIBILITY_SHOW_WHEN_REQUIRED;
|
|
8060
8267
|
}
|
|
8061
8268
|
}
|
|
8062
8269
|
/**
|
|
@@ -8243,15 +8450,9 @@
|
|
|
8243
8450
|
class ScriptElement extends AsyncElement {
|
|
8244
8451
|
_attributes = {};
|
|
8245
8452
|
_enabled = true;
|
|
8246
|
-
/**
|
|
8247
|
-
* Whether readiness has been signalled. Creation can happen more than once over an
|
|
8248
|
-
* element's life (a runtime `name` change recreates the instance), but `ready` is a
|
|
8249
|
-
* one-shot signal, so only the first successful creation fires it.
|
|
8250
|
-
*/
|
|
8251
|
-
_readySignalled = false;
|
|
8252
8453
|
/**
|
|
8253
8454
|
* The Script instance created for this element by its parent `<pc-scripts>` element.
|
|
8254
|
-
* @
|
|
8455
|
+
* @internal
|
|
8255
8456
|
*/
|
|
8256
8457
|
_script = null;
|
|
8257
8458
|
/**
|
|
@@ -8333,14 +8534,20 @@
|
|
|
8333
8534
|
console.warn(`pc-script '${this.getAttribute('name')}' must be a direct child of pc-scripts - script not created`);
|
|
8334
8535
|
}
|
|
8335
8536
|
}
|
|
8537
|
+
disconnectedCallback() {
|
|
8538
|
+
// Re-arm readiness so a re-inserted element announces the instance created for it then.
|
|
8539
|
+
// `_script` is deliberately NOT cleared here: the parent's mutation observer processes
|
|
8540
|
+
// this removal afterwards and reads it to establish which engine script this element
|
|
8541
|
+
// owned - the parent is what clears it.
|
|
8542
|
+
this._resetReady();
|
|
8543
|
+
}
|
|
8336
8544
|
/**
|
|
8337
8545
|
* Called by the parent `<pc-scripts>` element when the script instance has been created.
|
|
8338
|
-
*
|
|
8546
|
+
* Creation can happen more than once per connection (a runtime `name` change recreates the
|
|
8547
|
+
* instance), but `_onReady` signals readiness at most once per cycle.
|
|
8548
|
+
* @internal
|
|
8339
8549
|
*/
|
|
8340
8550
|
_onScriptCreated() {
|
|
8341
|
-
if (this._readySignalled)
|
|
8342
|
-
return;
|
|
8343
|
-
this._readySignalled = true;
|
|
8344
8551
|
this._onReady();
|
|
8345
8552
|
}
|
|
8346
8553
|
static get observedAttributes() {
|
|
@@ -8385,10 +8592,34 @@
|
|
|
8385
8592
|
*/
|
|
8386
8593
|
const RESERVED_ATTRIBUTES = new Set([
|
|
8387
8594
|
...ScriptElement.observedAttributes,
|
|
8388
|
-
'accesskey',
|
|
8389
|
-
'
|
|
8390
|
-
'
|
|
8391
|
-
'
|
|
8595
|
+
'accesskey',
|
|
8596
|
+
'autocapitalize',
|
|
8597
|
+
'autofocus',
|
|
8598
|
+
'class',
|
|
8599
|
+
'contenteditable',
|
|
8600
|
+
'dir',
|
|
8601
|
+
'draggable',
|
|
8602
|
+
'exportparts',
|
|
8603
|
+
'hidden',
|
|
8604
|
+
'id',
|
|
8605
|
+
'inert',
|
|
8606
|
+
'is',
|
|
8607
|
+
'itemid',
|
|
8608
|
+
'itemprop',
|
|
8609
|
+
'itemref',
|
|
8610
|
+
'itemscope',
|
|
8611
|
+
'itemtype',
|
|
8612
|
+
'lang',
|
|
8613
|
+
'nonce',
|
|
8614
|
+
'part',
|
|
8615
|
+
'popover',
|
|
8616
|
+
'role',
|
|
8617
|
+
'slot',
|
|
8618
|
+
'spellcheck',
|
|
8619
|
+
'style',
|
|
8620
|
+
'tabindex',
|
|
8621
|
+
'title',
|
|
8622
|
+
'translate'
|
|
8392
8623
|
]);
|
|
8393
8624
|
/**
|
|
8394
8625
|
* Checks whether a `pc-script` attribute name is reserved (and so never maps to a script
|
|
@@ -8400,18 +8631,25 @@
|
|
|
8400
8631
|
* @returns Whether the attribute name is reserved.
|
|
8401
8632
|
*/
|
|
8402
8633
|
const isReservedAttribute = (name) => {
|
|
8403
|
-
return RESERVED_ATTRIBUTES.has(name) ||
|
|
8634
|
+
return (RESERVED_ATTRIBUTES.has(name) ||
|
|
8404
8635
|
name.startsWith('data-') ||
|
|
8405
8636
|
name.startsWith('aria-') ||
|
|
8406
8637
|
name.startsWith('_') ||
|
|
8407
|
-
(name.startsWith('on') && name in HTMLElement.prototype);
|
|
8638
|
+
(name.startsWith('on') && name in HTMLElement.prototype));
|
|
8408
8639
|
};
|
|
8409
8640
|
/**
|
|
8410
8641
|
* Script API members that per-property attributes must never overwrite: the engine bindings and
|
|
8411
8642
|
* the (optional, so possibly undefined) lifecycle methods.
|
|
8412
8643
|
*/
|
|
8413
8644
|
const SCRIPT_API_MEMBERS = new Set([
|
|
8414
|
-
'app',
|
|
8645
|
+
'app',
|
|
8646
|
+
'entity',
|
|
8647
|
+
'destroy',
|
|
8648
|
+
'initialize',
|
|
8649
|
+
'postInitialize',
|
|
8650
|
+
'postUpdate',
|
|
8651
|
+
'swap',
|
|
8652
|
+
'update'
|
|
8415
8653
|
]);
|
|
8416
8654
|
/**
|
|
8417
8655
|
* Converts a kebab-case attribute name to the camelCase script attribute name.
|
|
@@ -8427,7 +8665,7 @@
|
|
|
8427
8665
|
* @returns The kebab-case name.
|
|
8428
8666
|
*/
|
|
8429
8667
|
const camelToKebab = (name) => {
|
|
8430
|
-
return name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);
|
|
8668
|
+
return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
8431
8669
|
};
|
|
8432
8670
|
/**
|
|
8433
8671
|
* Resolves an `asset:` prefix to the Asset created by the `pc-asset` element with that id.
|
|
@@ -8626,7 +8864,10 @@
|
|
|
8626
8864
|
// Only recurse into plain objects. Class instances (Vec3, Color, Asset, Entity...)
|
|
8627
8865
|
// are leaf values assigned whole, so accessor-typed script attributes receive them
|
|
8628
8866
|
// through their setters instead of having a getter's returned copy mutated.
|
|
8629
|
-
if (value &&
|
|
8867
|
+
if (value &&
|
|
8868
|
+
typeof value === 'object' &&
|
|
8869
|
+
!Array.isArray(value) &&
|
|
8870
|
+
Object.getPrototypeOf(value) === Object.prototype) {
|
|
8630
8871
|
if (!current || typeof current !== 'object') {
|
|
8631
8872
|
target[key] = {};
|
|
8632
8873
|
}
|
|
@@ -8644,7 +8885,11 @@
|
|
|
8644
8885
|
* @returns Whether the value is a math type.
|
|
8645
8886
|
*/
|
|
8646
8887
|
isMathType(value) {
|
|
8647
|
-
return value instanceof playcanvas.Vec2 ||
|
|
8888
|
+
return (value instanceof playcanvas.Vec2 ||
|
|
8889
|
+
value instanceof playcanvas.Vec3 ||
|
|
8890
|
+
value instanceof playcanvas.Vec4 ||
|
|
8891
|
+
value instanceof playcanvas.Color ||
|
|
8892
|
+
value instanceof playcanvas.Quat);
|
|
8648
8893
|
}
|
|
8649
8894
|
/**
|
|
8650
8895
|
* Converts a plain numeric array to the math type of `current`. A 3-element array targeting
|
|
@@ -8657,7 +8902,7 @@
|
|
|
8657
8902
|
* @returns The converted value, or `null`.
|
|
8658
8903
|
*/
|
|
8659
8904
|
arrayToMathType(current, value, key) {
|
|
8660
|
-
if (value.every(component => typeof component === 'number' && Number.isFinite(component))) {
|
|
8905
|
+
if (value.every((component) => typeof component === 'number' && Number.isFinite(component))) {
|
|
8661
8906
|
if (current instanceof playcanvas.Vec2 && value.length === 2)
|
|
8662
8907
|
return new playcanvas.Vec2(value);
|
|
8663
8908
|
if (current instanceof playcanvas.Vec3 && value.length === 3)
|
|
@@ -8929,7 +9174,10 @@
|
|
|
8929
9174
|
mutation.removedNodes.forEach((node) => {
|
|
8930
9175
|
if (node instanceof ScriptElement) {
|
|
8931
9176
|
const scriptName = node.getAttribute('name');
|
|
8932
|
-
if (scriptName &&
|
|
9177
|
+
if (scriptName &&
|
|
9178
|
+
node._script &&
|
|
9179
|
+
this.component &&
|
|
9180
|
+
this.component.get(scriptName) === node._script) {
|
|
8933
9181
|
this.destroyScript(scriptName);
|
|
8934
9182
|
}
|
|
8935
9183
|
node._script = null;
|
|
@@ -9178,18 +9426,27 @@
|
|
|
9178
9426
|
* emit a misleading "must be a direct child" warning for what is an ordinary removal.
|
|
9179
9427
|
*/
|
|
9180
9428
|
_soundElement = null;
|
|
9429
|
+
/**
|
|
9430
|
+
* Incremented on every connect and disconnect, and captured by connectedCallback on entry —
|
|
9431
|
+
* a resume from an await abandons itself if the value has moved on, so a stale callback can
|
|
9432
|
+
* neither act on a torn-down tree nor add its slot alongside a re-inserted element's own
|
|
9433
|
+
* callback.
|
|
9434
|
+
*/
|
|
9435
|
+
_connectionGeneration = 0;
|
|
9181
9436
|
/**
|
|
9182
9437
|
* The sound slot.
|
|
9183
9438
|
*/
|
|
9184
9439
|
soundSlot = null;
|
|
9185
9440
|
async connectedCallback() {
|
|
9441
|
+
const generation = ++this._connectionGeneration;
|
|
9186
9442
|
const soundElement = this.soundElement;
|
|
9187
9443
|
await soundElement?.ready();
|
|
9188
|
-
// The element may have been removed
|
|
9189
|
-
//
|
|
9190
|
-
// already be gone - see the
|
|
9444
|
+
// The element may have been removed (perhaps re-inserted, which runs a callback of its
|
|
9445
|
+
// own), or its parent torn down, while we were waiting. A <pc-app> disconnects before
|
|
9446
|
+
// its children, so by the time we resume the component can already be gone - see the
|
|
9447
|
+
// matching guard in disconnectedCallback below.
|
|
9191
9448
|
const component = soundElement?.component;
|
|
9192
|
-
if (
|
|
9449
|
+
if (generation !== this._connectionGeneration || !component) {
|
|
9193
9450
|
return;
|
|
9194
9451
|
}
|
|
9195
9452
|
const options = {
|
|
@@ -9212,12 +9469,15 @@
|
|
|
9212
9469
|
this._onReady();
|
|
9213
9470
|
}
|
|
9214
9471
|
disconnectedCallback() {
|
|
9472
|
+
// Invalidate any connectedCallback still suspended on an await
|
|
9473
|
+
this._connectionGeneration++;
|
|
9215
9474
|
// Uses the cached parent rather than a fresh lookup, since parentElement is already null
|
|
9216
9475
|
// by now. The component itself is null if the parent <pc-sound> (or the whole <pc-app>) is
|
|
9217
9476
|
// being torn down — parents disconnect first and have already removed the component.
|
|
9218
9477
|
this._soundElement?.component?.removeSlot(this._name);
|
|
9219
9478
|
this._soundElement = null;
|
|
9220
9479
|
this.soundSlot = null;
|
|
9480
|
+
this._resetReady();
|
|
9221
9481
|
}
|
|
9222
9482
|
get soundElement() {
|
|
9223
9483
|
const soundElement = this.parentElement;
|
|
@@ -9617,39 +9877,85 @@
|
|
|
9617
9877
|
class ModelElement extends AsyncElement {
|
|
9618
9878
|
_asset = '';
|
|
9619
9879
|
_entity = null;
|
|
9880
|
+
/**
|
|
9881
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
9882
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
9883
|
+
* on, so a superseded load can neither instantiate a second entity nor parent one that has
|
|
9884
|
+
* since been destroyed.
|
|
9885
|
+
*/
|
|
9886
|
+
_loadGeneration = 0;
|
|
9887
|
+
/**
|
|
9888
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
9889
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
9890
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
9891
|
+
*/
|
|
9892
|
+
_loadHandle = null;
|
|
9893
|
+
/**
|
|
9894
|
+
* The root entity of the instantiated model. `null` until the container asset has loaded
|
|
9895
|
+
* and been instantiated, and again once the element has been removed from the document.
|
|
9896
|
+
* @returns The model's root entity, or `null`.
|
|
9897
|
+
*/
|
|
9898
|
+
get entity() {
|
|
9899
|
+
return this._entity;
|
|
9900
|
+
}
|
|
9620
9901
|
connectedCallback() {
|
|
9621
9902
|
this._loadModel();
|
|
9622
9903
|
this._onReady();
|
|
9623
9904
|
}
|
|
9624
9905
|
disconnectedCallback() {
|
|
9906
|
+
this._loadGeneration++;
|
|
9907
|
+
this._detachLoadHandler();
|
|
9625
9908
|
this._unloadModel();
|
|
9909
|
+
this._resetReady();
|
|
9910
|
+
}
|
|
9911
|
+
_detachLoadHandler() {
|
|
9912
|
+
this._loadHandle?.off();
|
|
9913
|
+
this._loadHandle = null;
|
|
9626
9914
|
}
|
|
9627
9915
|
_instantiate(container) {
|
|
9628
|
-
|
|
9916
|
+
const generation = this._loadGeneration;
|
|
9917
|
+
const entity = container.instantiateRenderEntity();
|
|
9918
|
+
this._entity = entity;
|
|
9629
9919
|
// @ts-ignore
|
|
9630
9920
|
if (container.animations.length > 0) {
|
|
9631
|
-
|
|
9921
|
+
entity.addComponent('anim');
|
|
9632
9922
|
// @ts-ignore
|
|
9633
|
-
|
|
9923
|
+
entity.anim.assignAnimation('animation', container.animations[0].resource);
|
|
9634
9924
|
}
|
|
9925
|
+
// The parent's readiness re-arms when it is torn down, so these can resume in a later
|
|
9926
|
+
// connection cycle. The entity is captured above and the generation re-checked, so a
|
|
9927
|
+
// stale resume cannot parent an entity a newer cycle has already destroyed.
|
|
9635
9928
|
const parentEntityElement = this.closestEntity;
|
|
9636
9929
|
if (parentEntityElement) {
|
|
9637
9930
|
parentEntityElement.ready().then(() => {
|
|
9638
|
-
|
|
9931
|
+
if (generation !== this._loadGeneration) {
|
|
9932
|
+
return;
|
|
9933
|
+
}
|
|
9934
|
+
parentEntityElement.entity.addChild(entity);
|
|
9639
9935
|
});
|
|
9640
9936
|
}
|
|
9641
9937
|
else {
|
|
9642
9938
|
const appElement = this.closestApp;
|
|
9643
9939
|
if (appElement) {
|
|
9644
9940
|
appElement.ready().then(() => {
|
|
9645
|
-
|
|
9941
|
+
if (generation !== this._loadGeneration) {
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
appElement.app.root.addChild(entity);
|
|
9646
9945
|
});
|
|
9647
9946
|
}
|
|
9648
9947
|
}
|
|
9649
9948
|
}
|
|
9650
9949
|
async _loadModel() {
|
|
9651
9950
|
this._unloadModel();
|
|
9951
|
+
// Supersede any load already in flight - only the newest load may instantiate
|
|
9952
|
+
const generation = ++this._loadGeneration;
|
|
9953
|
+
this._detachLoadHandler();
|
|
9652
9954
|
const appElement = await this.closestApp?.ready();
|
|
9955
|
+
// The element may have been removed, or another load started, while we waited
|
|
9956
|
+
if (generation !== this._loadGeneration) {
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9653
9959
|
const app = appElement?.app;
|
|
9654
9960
|
const asset = AssetElement.get(this._asset);
|
|
9655
9961
|
if (!asset) {
|
|
@@ -9659,7 +9965,14 @@
|
|
|
9659
9965
|
this._instantiate(asset.resource);
|
|
9660
9966
|
}
|
|
9661
9967
|
else {
|
|
9662
|
-
|
|
9968
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
9969
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
9970
|
+
// holds on its own.
|
|
9971
|
+
this._loadHandle = asset.once('load', () => {
|
|
9972
|
+
this._loadHandle = null;
|
|
9973
|
+
if (generation !== this._loadGeneration) {
|
|
9974
|
+
return;
|
|
9975
|
+
}
|
|
9663
9976
|
this._instantiate(asset.resource);
|
|
9664
9977
|
});
|
|
9665
9978
|
app.assets.load(asset);
|
|
@@ -9761,10 +10074,17 @@
|
|
|
9761
10074
|
return;
|
|
9762
10075
|
}
|
|
9763
10076
|
this._scene = app.scene;
|
|
9764
|
-
this.
|
|
10077
|
+
this._updateSceneSettings();
|
|
9765
10078
|
this._onReady();
|
|
9766
10079
|
}
|
|
9767
|
-
|
|
10080
|
+
disconnectedCallback() {
|
|
10081
|
+
// The scene belongs to the application, and removing this element - or the <pc-app>
|
|
10082
|
+
// above it, which disconnects first - parts the two. Re-arm readiness so a re-inserted
|
|
10083
|
+
// element announces the scene it acquires then, not the one it lost here.
|
|
10084
|
+
this._scene = null;
|
|
10085
|
+
this._resetReady();
|
|
10086
|
+
}
|
|
10087
|
+
_updateSceneSettings() {
|
|
9768
10088
|
if (this._scene) {
|
|
9769
10089
|
this._scene.fog.type = this._fog;
|
|
9770
10090
|
this._scene.fog.color = this._fogColor;
|
|
@@ -9932,13 +10252,32 @@
|
|
|
9932
10252
|
_type = 'infinite';
|
|
9933
10253
|
_scene = null;
|
|
9934
10254
|
_appElement = null;
|
|
10255
|
+
/**
|
|
10256
|
+
* Incremented on every new load and on disconnect, and captured by a load when it starts. A
|
|
10257
|
+
* load that resumes from an await or a load callback abandons itself if the value has moved
|
|
10258
|
+
* on, so a superseded load cannot generate a skybox for a scene it no longer configures.
|
|
10259
|
+
*/
|
|
10260
|
+
_loadGeneration = 0;
|
|
10261
|
+
/**
|
|
10262
|
+
* The pending asset-load subscription of the current load, if it is waiting for its asset.
|
|
10263
|
+
* Held so that whatever supersedes the load can detach the handler from the asset, rather
|
|
10264
|
+
* than leave it registered until the asset loads (or forever, if it never does).
|
|
10265
|
+
*/
|
|
10266
|
+
_loadHandle = null;
|
|
9935
10267
|
connectedCallback() {
|
|
9936
10268
|
this._loadSkybox();
|
|
9937
10269
|
this._onReady();
|
|
9938
10270
|
}
|
|
9939
10271
|
disconnectedCallback() {
|
|
10272
|
+
this._loadGeneration++;
|
|
10273
|
+
this._detachLoadHandler();
|
|
9940
10274
|
this._unloadSkybox();
|
|
9941
10275
|
this._appElement = null;
|
|
10276
|
+
this._resetReady();
|
|
10277
|
+
}
|
|
10278
|
+
_detachLoadHandler() {
|
|
10279
|
+
this._loadHandle?.off();
|
|
10280
|
+
this._loadHandle = null;
|
|
9942
10281
|
}
|
|
9943
10282
|
_generateSkybox(asset) {
|
|
9944
10283
|
if (!this._scene)
|
|
@@ -9946,10 +10285,17 @@
|
|
|
9946
10285
|
const source = asset.resource;
|
|
9947
10286
|
const skybox = playcanvas.EnvLighting.generateSkyboxCubemap(source);
|
|
9948
10287
|
skybox.anisotropy = 4;
|
|
10288
|
+
// This element owns what it generated (see _unloadSkybox) - replacing a skybox from an
|
|
10289
|
+
// earlier load must release it, not orphan it on the GPU
|
|
10290
|
+
this._scene.skybox?.destroy();
|
|
9949
10291
|
this._scene.skybox = skybox;
|
|
9950
10292
|
if (this._lighting) {
|
|
9951
10293
|
const lighting = playcanvas.EnvLighting.generateLightingSource(source);
|
|
9952
10294
|
const envAtlas = playcanvas.EnvLighting.generateAtlas(lighting);
|
|
10295
|
+
// The lighting source is an intermediate: the atlas is rendered from it and it is
|
|
10296
|
+
// not needed afterwards
|
|
10297
|
+
lighting.destroy();
|
|
10298
|
+
this._scene.envAtlas?.destroy();
|
|
9953
10299
|
this._scene.envAtlas = envAtlas;
|
|
9954
10300
|
}
|
|
9955
10301
|
const layer = this._scene.layers.getLayerById(playcanvas.LAYERID_SKYBOX);
|
|
@@ -9963,7 +10309,14 @@
|
|
|
9963
10309
|
this._scene.skyboxMip = this._mipLevel;
|
|
9964
10310
|
}
|
|
9965
10311
|
async _loadSkybox() {
|
|
10312
|
+
// Supersede any load already in flight - only the newest load may generate the skybox
|
|
10313
|
+
const generation = ++this._loadGeneration;
|
|
10314
|
+
this._detachLoadHandler();
|
|
9966
10315
|
const appElement = await this.closestApp?.ready();
|
|
10316
|
+
// The element may have been removed, or another load started, while we waited
|
|
10317
|
+
if (generation !== this._loadGeneration) {
|
|
10318
|
+
return;
|
|
10319
|
+
}
|
|
9967
10320
|
const app = appElement?.app;
|
|
9968
10321
|
if (!appElement || !app) {
|
|
9969
10322
|
return;
|
|
@@ -9978,7 +10331,14 @@
|
|
|
9978
10331
|
this._generateSkybox(asset);
|
|
9979
10332
|
}
|
|
9980
10333
|
else {
|
|
9981
|
-
|
|
10334
|
+
// The generation is re-checked even though a superseded handler is detached: the
|
|
10335
|
+
// detach relies on how the engine's event emitter treats removal, while the check
|
|
10336
|
+
// holds on its own.
|
|
10337
|
+
this._loadHandle = asset.once('load', () => {
|
|
10338
|
+
this._loadHandle = null;
|
|
10339
|
+
if (generation !== this._loadGeneration) {
|
|
10340
|
+
return;
|
|
10341
|
+
}
|
|
9982
10342
|
this._generateSkybox(asset);
|
|
9983
10343
|
});
|
|
9984
10344
|
app.assets.load(asset);
|