From 6c79abfb00a151cd48dfa8fa9017fb890ab4c37b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 30 Mar 2026 12:38:15 +0000 Subject: [PATCH] Deduplicate concurrent reloadResource calls for the same resource When multiple SceneEditors are open, each one registers a callback for resource changes. All callbacks fire concurrently (forEach without await), so reloadResource is called multiple times for the same resource in parallel. The second call would unload the texture the first call just loaded, leaving the first SceneEditor with a destroyed texture and causing null-baseTexture crashes (width/containsPoint errors). Track in-flight reload promises per resource name. If a reload is already in progress, subsequent callers await the same promise instead of starting a new unload/load cycle. https://claude.ai/code/session_012hFLUQDBXEuzxCEDUGVvew --- .../ObjectsRendering/PixiResourcesLoader.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js b/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js index 6e22788618..39ee11157f 100644 --- a/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js +++ b/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js @@ -45,6 +45,12 @@ type ResourcePromise = { [resourceName: string]: Promise }; let loadedBitmapFonts = {}; let loadedFontFamilies = {}; let loadedTextures = {}; +// Tracks in-flight reload promises so that concurrent reloadResource calls +// for the same resource name are deduplicated. Without this, when multiple +// SceneEditors are open, each one fires its own reloadResource — the second +// call would unload the texture that the first call just loaded, causing +// crashes (null baseTexture / width). +let pendingReloads: { [resourceName: string]: Promise } = {}; const invalidTexture = PIXI.Texture.from('res/invalid_texture.png'); const loadingTexture = PIXI.Texture.from( 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAA1BMVEXX19f5cgrAAAAAAXRSTlMz/za5cAAAAApJREFUCNdjQAMAABAAAbSqgB8AAAAASUVORK5CYII=' @@ -284,6 +290,24 @@ export default class PixiResourcesLoader { } static async reloadResource(project: gdProject, resourceName: string) { + // Deduplicate concurrent calls for the same resource. When multiple + // SceneEditors are open, each one calls reloadResource for the same + // resource. Without deduplication, the second call would unload the + // texture that the first call just loaded, causing null-baseTexture crashes. + if (pendingReloads[resourceName]) { + return pendingReloads[resourceName]; + } + + const promise = this._doReloadResource(project, resourceName); + pendingReloads[resourceName] = promise; + try { + await promise; + } finally { + delete pendingReloads[resourceName]; + } + } + + static async _doReloadResource(project: gdProject, resourceName: string) { // $FlowFixMe[invalid-computed-prop] const loadedTexture = loadedTextures[resourceName]; if (loadedTexture === invalidTexture) {