diff --git a/Extensions/3D/JsExtension.js b/Extensions/3D/JsExtension.js index 07e69a4b5f..c495acb596 100644 --- a/Extensions/3D/JsExtension.js +++ b/Extensions/3D/JsExtension.js @@ -2203,6 +2203,8 @@ module.exports = { if (!texture.baseTexture.valid) { // Post pone texture update if texture is not loaded. texture.once('update', () => { + if (this._wasDestroyed) return; + this.updateTexture(); this.updatePIXISprite(); }); @@ -2375,6 +2377,7 @@ module.exports = { getFaceMaterial(this._project, materialIndexToFaceIndex[4]), getFaceMaterial(this._project, materialIndexToFaceIndex[5]), ]); + if (this._wasDestroyed) return; this._threeObject.material[0] = materials[0]; this._threeObject.material[1] = materials[1]; @@ -2823,6 +2826,7 @@ module.exports = { this._pixiResourcesLoader .get3DModel(project, modelResourceName) .then((model3d) => { + if (this._wasDestroyed) return; const clonedModel3D = THREE_ADDONS.SkeletonUtils.clone( model3d.scene ); @@ -3338,6 +3342,7 @@ module.exports = { this._pixiResourcesLoader .get3DModel(this._project, modelResourceName) .then((model3d) => { + if (this._wasDestroyed) return; this._clonedModel3D = THREE_ADDONS.SkeletonUtils.clone( model3d.scene ); diff --git a/Extensions/BitmapText/JsExtension.js b/Extensions/BitmapText/JsExtension.js index 90c91c8ad4..55676914f3 100644 --- a/Extensions/BitmapText/JsExtension.js +++ b/Extensions/BitmapText/JsExtension.js @@ -697,6 +697,8 @@ module.exports = { this._currentBitmapFontResourceName, this._currentTextureAtlasResourceName ).then((bitmapFont) => { + if (this._wasDestroyed) return; + this._pixiObject.fontName = bitmapFont.font; this._pixiObject.fontSize = bitmapFont.size; this._pixiObject.dirty = true; diff --git a/Extensions/JsExtensionTypes.d.ts b/Extensions/JsExtensionTypes.d.ts index 4b2d346069..595ae275ac 100644 --- a/Extensions/JsExtensionTypes.d.ts +++ b/Extensions/JsExtensionTypes.d.ts @@ -14,9 +14,12 @@ class RenderedInstance { _associatedObjectConfiguration: gd.ObjectConfiguration; _pixiContainer: PIXI.Container; _pixiResourcesLoader: Class; - _pixiObject: PIXI.DisplayObject; + _pixiObject: PIXI.DisplayObject | null; wasUsed: boolean; + /** Set to true when onRemovedFromScene is called. Allows to cancel promises/asynchronous operations (notably: waiting for a resource load). */ + _wasDestroyed: boolean; + constructor( project: gdProject, instance: gdInitialInstance, @@ -89,10 +92,13 @@ class Rendered3DInstance { _pixiContainer: PIXI.Container; _threeGroup: THREE.Group; _pixiResourcesLoader: Class; - _pixiObject: PIXI.DisplayObject; + _pixiObject: PIXI.DisplayObject | null; _threeObject: THREE.Object3D | null; wasUsed: boolean; + /** Set to true when onRemovedFromScene is called. Allows to cancel promises/asynchronous operations (notably: waiting for a resource load). */ + _wasDestroyed: boolean; + constructor( project: gdProject, instance: gdInitialInstance, diff --git a/Extensions/Spine/JsExtension.js b/Extensions/Spine/JsExtension.js index c777a1aae4..94f107a0d7 100644 --- a/Extensions/Spine/JsExtension.js +++ b/Extensions/Spine/JsExtension.js @@ -323,6 +323,7 @@ module.exports = { this._pixiResourcesLoader .getSpineData(this._project, this._spineResourceName) .then((spineDataOrLoadingError) => { + if (this._wasDestroyed) return; if (this._spine) this._pixiObject.removeChild(this._spine); if (!spineDataOrLoadingError.skeleton) { diff --git a/Extensions/TextInput/JsExtension.js b/Extensions/TextInput/JsExtension.js index 65482a964b..d77a9f3d0b 100644 --- a/Extensions/TextInput/JsExtension.js +++ b/Extensions/TextInput/JsExtension.js @@ -711,6 +711,7 @@ module.exports = { this._pixiResourcesLoader .loadFontFamily(this._project, fontResourceName) .then((fontFamily) => { + if (this._wasDestroyed) return; this._pixiText.style.fontFamily = fontFamily; this._pixiText.dirty = true; }) diff --git a/Extensions/TileMap/JsExtension.js b/Extensions/TileMap/JsExtension.js index de589bb74a..65b01cd0ad 100644 --- a/Extensions/TileMap/JsExtension.js +++ b/Extensions/TileMap/JsExtension.js @@ -1760,6 +1760,7 @@ module.exports = { levelIndex, pako, (tileMap) => { + if (this._wasDestroyed) return; if (!tileMap) { this._onLoadingError(); // _loadTileMapWithCallback already log errors @@ -1781,6 +1782,7 @@ module.exports = { tilesetJsonFile, levelIndex, (textureCache) => { + if (this._wasDestroyed) return; if (!textureCache) { this._onLoadingError(); // getOrLoadTextureCache already log warns and errors. @@ -1809,6 +1811,7 @@ module.exports = { } else { // Wait for the atlas image to load. atlasTexture.once('update', () => { + if (this._wasDestroyed) return; loadTileMap(); }); } @@ -1855,6 +1858,7 @@ module.exports = { tilesetJsonFile, levelIndex, (textureCache) => { + if (this._wasDestroyed) return; if (!textureCache) { this._onLoadingError(); // getOrLoadTextureCache already log warns and errors. @@ -2208,6 +2212,7 @@ module.exports = { columnCount, rowCount, (tileMap) => { + if (this._wasDestroyed) return; if (!tileMap) { this._onLoadingError(); console.error('Could not parse tilemap.'); @@ -2230,6 +2235,7 @@ module.exports = { /** @type {TileMapHelper.TileTextureCache | null} */ textureCache ) => { + if (this._wasDestroyed) return; this._onLoadingSuccess(); if (!this._editableTileMap) return; @@ -2257,6 +2263,7 @@ module.exports = { } else { // Wait for the atlas image to load. atlasTexture.once('update', () => { + if (this._wasDestroyed) return; loadTileMap(); }); } @@ -2293,6 +2300,7 @@ module.exports = { /** @type {TileMapHelper.TileTextureCache | null} */ textureCache ) => { + if (this._wasDestroyed) return; this._onLoadingSuccess(); if (!this._editableTileMap) return; @@ -2536,6 +2544,7 @@ module.exports = { 0, // levelIndex pako, (tileMap) => { + if (this._wasDestroyed) return; if (!tileMap) { this._onLoadingError(); // _loadTiledMapWithCallback already log errors diff --git a/Extensions/Video/JsExtension.js b/Extensions/Video/JsExtension.js index ab991076ec..fe298fbb03 100644 --- a/Extensions/Video/JsExtension.js +++ b/Extensions/Video/JsExtension.js @@ -609,6 +609,7 @@ module.exports = { that._pixiObject.texture.on('error', function () { that._pixiObject.texture.off('error', this); + if (this._wasDestroyed) return; that._pixiObject.texture = that._pixiResourcesLoader.getInvalidPIXITexture(); }); diff --git a/newIDE/app/src/ObjectsRendering/Renderers/Rendered3DInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/Rendered3DInstance.js index 467ab50e1d..5dfe955ec3 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/Rendered3DInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/Rendered3DInstance.js @@ -18,6 +18,7 @@ export default class Rendered3DInstance { _pixiObject: PIXI.DisplayObject; _threeObject: THREE.Object3D | null; wasUsed: boolean; + _wasDestroyed: boolean; constructor( project: gdProject, @@ -36,6 +37,7 @@ export default class Rendered3DInstance { this._project = project; this._pixiResourcesLoader = pixiResourcesLoader; this.wasUsed = true; //Used by InstancesRenderer to track rendered instance that are not used anymore. + this._wasDestroyed = false; } isRenderedIn3D(): boolean { @@ -89,6 +91,7 @@ export default class Rendered3DInstance { * the default implementation of the method does. */ onRemovedFromScene() { + this._wasDestroyed = true; if (this._pixiObject !== null) this._pixiContainer.removeChild(this._pixiObject); if (this._threeObject !== null) this._threeGroup.remove(this._threeObject); diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedCustomObjectInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedCustomObjectInstance.js index 23292ed284..24ceca0a69 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedCustomObjectInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedCustomObjectInstance.js @@ -179,6 +179,12 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance for (const [i, renderedInstance] of this.renderedInstances) { if (!renderedInstance.wasUsed) { renderedInstance.onRemovedFromScene(); + if (!renderedInstance._wasDestroyed) + console.error( + 'Rendered instance was not marked as destroyed by onRemovedFromScene - verify the implementation.', + renderedInstance + ); + this.renderedInstances.delete(i); this.layoutedInstances.delete(i); } @@ -196,6 +202,11 @@ export default class RenderedCustomObjectInstance extends Rendered3DInstance // Destroy all instances for (const renderedInstance of this.renderedInstances.values()) { renderedInstance.onRemovedFromScene(); + if (!renderedInstance._wasDestroyed) + console.error( + 'Rendered instance (of a custom object) was not marked as destroyed by onRemovedFromScene - verify the implementation.', + renderedInstance + ); } this.renderedInstances.clear(); this.layoutedInstances.clear(); diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedInstance.js index 006aff5385..1605d5ca36 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedInstance.js @@ -14,6 +14,7 @@ export default class RenderedInstance { _pixiResourcesLoader: Class; _pixiObject: PIXI.DisplayObject; wasUsed: boolean; + _wasDestroyed: boolean; constructor( project: gdProject, @@ -29,6 +30,7 @@ export default class RenderedInstance { this._project = project; this._pixiResourcesLoader = pixiResourcesLoader; this.wasUsed = true; //Used by InstancesRenderer to track rendered instance that are not used anymore. + this._wasDestroyed = false; } isRenderedIn3D(): boolean { @@ -63,6 +65,7 @@ export default class RenderedInstance { * the default implementation of the method does. */ onRemovedFromScene(): void { + this._wasDestroyed = true; if (this._pixiObject !== null) this._pixiContainer.removeChild(this._pixiObject); } diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedPanelSpriteInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedPanelSpriteInstance.js index 4e17b663f0..6a3a54594b 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedPanelSpriteInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedPanelSpriteInstance.js @@ -265,7 +265,10 @@ export default class RenderedPanelSpriteInstance extends RenderedInstance { ); if (!texture.baseTexture.valid) { // Post pone texture update if texture is not loaded. - texture.once('update', () => this.updateTextures()); + texture.once('update', () => { + if (this._wasDestroyed) return; + this.updateTextures(); + }); return; } diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedSprite3DInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedSprite3DInstance.js index 54df71754a..d4c9789174 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedSprite3DInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedSprite3DInstance.js @@ -199,7 +199,10 @@ export default class RenderedSprite3DInstance extends Rendered3DInstance { if (!texture.baseTexture.valid) { // Post pone texture update if texture is not loaded. - texture.once('update', () => this.updateTextureAndSprite()); + texture.once('update', () => { + if (this._wasDestroyed) return; + this.updateTextureAndSprite(); + }); return; } this._textureWidth = texture.width; @@ -212,6 +215,7 @@ export default class RenderedSprite3DInstance extends Rendered3DInstance { useTransparentTexture: true, } ); + if (this._wasDestroyed) return; if (this._threeObject) { this._threeObject.material = material; } diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedSpriteInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedSpriteInstance.js index 829c548358..0ba5cb2f24 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedSpriteInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedSpriteInstance.js @@ -187,7 +187,10 @@ export default class RenderedSpriteInstance extends RenderedInstance { if (!texture.baseTexture.valid) { // Post pone texture update if texture is not loaded. - texture.once('update', () => this.updatePIXITextureAndSprite()); + texture.once('update', () => { + if (this._wasDestroyed) return; + this.updatePIXITextureAndSprite(); + }); return; } diff --git a/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js b/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js index fd84ef640c..9d08545cd1 100644 --- a/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js +++ b/newIDE/app/src/ObjectsRendering/Renderers/RenderedTextInstance.js @@ -140,6 +140,8 @@ export default class RenderedTextInstance extends RenderedInstance { textObjectConfiguration.getFontName() ) .then(fontFamily => { + if (this._wasDestroyed) return; + // Once the font is loaded, we can use the given fontFamily. this._fontFamily = fontFamily; this._styleFontDirty = true;