diff --git a/Core/GDCore/Extensions/Builtin/SceneExtension.cpp b/Core/GDCore/Extensions/Builtin/SceneExtension.cpp index 58247fe9d398..e11eec3b0da2 100644 --- a/Core/GDCore/Extensions/Builtin/SceneExtension.cpp +++ b/Core/GDCore/Extensions/Builtin/SceneExtension.cpp @@ -222,13 +222,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( "LoadObjectAssets", _("Preload object"), _("Preload an object resources in background."), - _("Preload object _PARAM1_ in background"), + _("Preload object _PARAM1_ in background (scene: _PARAM2_)"), "", "res/actions/hourglass_black.svg", "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("string", _("Object name")) + .AddParameter("sceneName", _("Object scene"), "", true) .MarkAsAdvanced(); extension @@ -236,13 +237,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( "UnloadObjectAssets", _("Unload object"), _("Unload an object resources. The \"resource preloading\" property must be set to \"preload with an action\" for this action to actually unload resources."), - _("Unload object _PARAM1_"), + _("Unload object _PARAM1_ (scene: _PARAM2_)"), "", "res/actions/hourglass_black.svg", "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("string", _("Object name")) + .AddParameter("sceneName", _("Object scene"), "", true) .MarkAsAdvanced(); extension @@ -250,13 +252,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( "AreObjectAssetsLoaded", _("Object preloaded"), _("Check if object resources have finished to load in background."), - _("Object _PARAM1_ was preloaded in background"), + _("Object _PARAM1_ was preloaded in background (scene: _PARAM2_)"), "", "res/actions/hourglass_black.svg", "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("string", _("Object name")) + .AddParameter("sceneName", _("Object scene"), "", true) .MarkAsAdvanced(); } diff --git a/GDJS/Runtime/ResourceLoader.ts b/GDJS/Runtime/ResourceLoader.ts index 66d8ed777492..9db9eaee25b6 100644 --- a/GDJS/Runtime/ResourceLoader.ts +++ b/GDJS/Runtime/ResourceLoader.ts @@ -6,7 +6,7 @@ namespace gdjs { const logger = new gdjs.Logger('ResourceLoader'); // TODO add a condition before each log to avoid building the message for nothing. - const debugLogger = new gdjs.Logger('ResourceLoader - debug').enable(true); + const debugLogger = new gdjs.Logger('ResourceLoader - debug').enable(false); const addSearchParameterToUrl = ( url: string, @@ -773,8 +773,8 @@ namespace gdjs { ); // Also add the resources manually loaded for objects during the current scene. // TODO Abort loading task to avoid to leave resources from an object that is currently loading. - const unloadedSceneObjectResourceLoadingQueue = newSceneName - ? this.getObjectResourceLoadingQueue(newSceneName) + const unloadedSceneObjectResourceLoadingQueue = unloadedSceneName + ? this.getObjectResourceLoadingQueue(unloadedSceneName) : null; if (unloadedSceneObjectResourceLoadingQueue) { for (const objectLoadingState of unloadedSceneObjectResourceLoadingQueue.loadingStates.values()) { diff --git a/GDJS/Runtime/events-tools/runtimescenetools.ts b/GDJS/Runtime/events-tools/runtimescenetools.ts index 6380d2468d3b..f98734bcf448 100644 --- a/GDJS/Runtime/events-tools/runtimescenetools.ts +++ b/GDJS/Runtime/events-tools/runtimescenetools.ts @@ -369,9 +369,12 @@ namespace gdjs { */ export const loadObjectOrGroupAssets = ( runtimeScene: gdjs.RuntimeScene, - objectOrGroupName: string + objectOrGroupName: string, + sceneName: string ): void => { - runtimeScene.getGame().loadObjectOrGroupAssets(objectOrGroupName); + runtimeScene + .getGame() + .loadObjectOrGroupAssets(objectOrGroupName, sceneName); }; /** @@ -379,9 +382,12 @@ namespace gdjs { */ export const unloadObjectOrGroupAssets = ( runtimeScene: gdjs.RuntimeScene, - objectOrGroupName: string + objectOrGroupName: string, + sceneName: string ): void => { - runtimeScene.getGame().unloadObjectOrGroupAssets(objectOrGroupName); + runtimeScene + .getGame() + .unloadObjectOrGroupAssets(objectOrGroupName, sceneName); }; /** @@ -389,11 +395,12 @@ namespace gdjs { */ export const areObjectOrGroupAssetsLoaded = ( runtimeScene: gdjs.RuntimeScene, - objectOrGroupName: string + objectOrGroupName: string, + sceneName: string ): boolean => { return runtimeScene .getGame() - .areObjectOrGroupAssetsLoaded(objectOrGroupName); + .areObjectOrGroupAssetsLoaded(objectOrGroupName, sceneName); }; } } diff --git a/GDJS/Runtime/runtimegame.ts b/GDJS/Runtime/runtimegame.ts index 169137fcd596..fca4c4a298e9 100644 --- a/GDJS/Runtime/runtimegame.ts +++ b/GDJS/Runtime/runtimegame.ts @@ -917,26 +917,32 @@ namespace gdjs { /** * Preload an object assets in background. */ - loadObjectOrGroupAssets(objectOrGroupName: string): void { + loadObjectOrGroupAssets( + objectOrGroupName: string, + sceneName?: string + ): void { const currentScene = this._sceneStack.getCurrentScene(); if (!currentScene) { return; } + if (!sceneName) { + sceneName = currentScene.getName(); + } const objectGroupData = this.getObjectGroupData( - currentScene.getName(), + sceneName, objectOrGroupName ); if (objectGroupData) { for (const object of objectGroupData.objects) { - this._loadObjectAssets(currentScene, object.name); + this._loadObjectAssets(sceneName, object.name); } } else { - this._loadObjectAssets(currentScene, objectOrGroupName); + this._loadObjectAssets(sceneName, objectOrGroupName); } } - private _loadObjectAssets(currentScene: RuntimeScene, objectName: string) { - const objectData = currentScene._objects.get(objectName); + private _loadObjectAssets(sceneName: string, objectName: string) { + const objectData = this.getObjectData(sceneName, objectName); if (!objectData) { return; } @@ -945,7 +951,7 @@ namespace gdjs { return; } this._resourcesLoader.loadObjectResources( - currentScene.getName(), + sceneName, objectName, usedResources ); @@ -954,22 +960,25 @@ namespace gdjs { /** * @returns true when all the resources of the given object are loaded. */ - areObjectOrGroupAssetsLoaded(objectOrGroupName: string): boolean { + areObjectOrGroupAssetsLoaded( + objectOrGroupName: string, + sceneName?: string + ): boolean { const currentScene = this._sceneStack.getCurrentScene(); if (!currentScene) { return false; } + if (!sceneName) { + sceneName = currentScene.getName(); + } const objectGroupData = this.getObjectGroupData( - currentScene.getName(), + sceneName, objectOrGroupName ); if (objectGroupData) { for (const object of objectGroupData.objects) { if ( - !this._resourcesLoader.areObjectAssetsReady( - currentScene.getName(), - object.name - ) + !this._resourcesLoader.areObjectAssetsReady(sceneName, object.name) ) { return false; } @@ -977,7 +986,7 @@ namespace gdjs { return true; } return this._resourcesLoader.areObjectAssetsReady( - currentScene.getName(), + sceneName, objectOrGroupName ); } @@ -985,39 +994,57 @@ namespace gdjs { /** * Unload an object assets. */ - unloadObjectOrGroupAssets(objectOrGroupName: string): void { + unloadObjectOrGroupAssets( + objectOrGroupName: string, + sceneName?: string + ): void { const currentScene = this._sceneStack.getCurrentScene(); if (!currentScene) { return; } + if (!sceneName) { + sceneName = currentScene.getName(); + } const objectGroupData = this.getObjectGroupData( - currentScene.getName(), + sceneName, objectOrGroupName ); if (objectGroupData) { for (const object of objectGroupData.objects) { - this._resourcesLoader.unloadObjectResources( - currentScene.getName(), - object.name - ); + this._resourcesLoader.unloadObjectResources(sceneName, object.name); } } else { this._resourcesLoader.unloadObjectResources( - currentScene.getName(), + sceneName, objectOrGroupName ); } } + private getObjectData( + sceneName: string, + objectName: string + ): ObjectData | null { + const sceneData = this.getSceneData(sceneName); + if (sceneData) { + for (const objectData of sceneData.objects) { + if (objectData.name === objectName) { + return objectData; + } + } + } + return null; + } + private getObjectGroupData( sceneName: string, objectGroupName: string ): ObjectGroupData | null { const sceneData = this.getSceneData(sceneName); if (sceneData) { - for (const objectGroup of sceneData.objectsGroups) { - if (objectGroup.name === objectGroupName) { - return objectGroup; + for (const objectGroupData of sceneData.objectsGroups) { + if (objectGroupData.name === objectGroupName) { + return objectGroupData; } } } diff --git a/GDJS/tests/tests/ResourceLoader.js b/GDJS/tests/tests/ResourceLoader.js index f1d99d5f70b8..50150b91d0e4 100644 --- a/GDJS/tests/tests/ResourceLoader.js +++ b/GDJS/tests/tests/ResourceLoader.js @@ -434,7 +434,7 @@ describe('gdjs.ResourceLoader', () => { * @param {gdjs.MockedResourceManager} mockedResourceManager */ const loadObject1AndCheck = async (runtimeGame, mockedResourceManager) => { - runtimeGame.loadObjectOrGroupAssets('Object1'); + runtimeGame.loadObjectOrGroupAssets('Object1', 'Scene1'); // Object1 resources should be pending download expect( @@ -447,7 +447,7 @@ describe('gdjs.ResourceLoader', () => { 'scene1-object1-resource2.png' ) ).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(false); // Mark Object1 resources as loaded mockedResourceManager.markPendingResourcesAsLoaded( @@ -459,7 +459,7 @@ describe('gdjs.ResourceLoader', () => { await delay(10); // Object1 should now be ready - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(true); }; it('can load object resources with an action', async () => { @@ -485,12 +485,60 @@ describe('gdjs.ResourceLoader', () => { runtimeGame._sceneStack.push('Scene1'); await loadObject1AndCheck(runtimeGame, mockedResourceManager); - runtimeGame.unloadObjectOrGroupAssets('Object1'); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(false); + runtimeGame.unloadObjectOrGroupAssets('Object1', 'Scene1'); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(false); await loadObject1AndCheck(runtimeGame, mockedResourceManager); }); + it('can unload a scene with its objects that were manually loaded', async () => { + const mockedResourceManager = new gdjs.MockedResourceManager(); + const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes); + const resourceLoader = runtimeGame.getResourceLoader(); + resourceLoader.injectMockResourceManagerForTesting( + 'fake-resource-kind-for-testing-only', + mockedResourceManager + ); + + // Start loading first scene and background loading + runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene1'); + // Mark Scene1 resources as loaded + mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource1.png'); + mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource2.png'); + await delay(10); + + // Scene1 should now be ready + expect(runtimeGame.areSceneAssetsLoaded('Scene1')).to.be(true); + expect(runtimeGame.areSceneAssetsReady('Scene1')).to.be(true); + + runtimeGame._sceneStack.push('Scene1'); + await loadObject1AndCheck(runtimeGame, mockedResourceManager); + + // First, unload Scene2 (which shares resources with Scene3) + resourceLoader.unloadSceneResources({ + unloadedSceneName: 'Scene1', + newSceneName: 'Scene2', + }); + + // Scene resources are unloaded. + expect( + mockedResourceManager.isResourceDisposed('scene1-resource1.png') + ).to.be(true); + expect( + mockedResourceManager.isResourceDisposed('scene1-resource2.png') + ).to.be(true); + expect(runtimeGame.areSceneAssetsLoaded('Scene1')).to.be(false); + expect(runtimeGame.areSceneAssetsReady('Scene1')).to.be(false); + // Scene objects are unloaded too. + expect( + mockedResourceManager.isResourceDisposed('scene1-object1-resource1.png') + ).to.be(true); + expect( + mockedResourceManager.isResourceDisposed('scene1-object1-resource2.png') + ).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(false); + }); + it('can load object group resources with an action', async () => { const mockedResourceManager = new gdjs.MockedResourceManager(); const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes); @@ -525,8 +573,8 @@ describe('gdjs.ResourceLoader', () => { 'scene1-object1-resource2.png' ) ).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(false); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup', 'Scene1')).to.be(false); // Mark Object1 resources as loaded mockedResourceManager.markPendingResourcesAsLoaded( @@ -538,8 +586,8 @@ describe('gdjs.ResourceLoader', () => { await delay(10); // Object1 should now be ready - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup', 'Scene1')).to.be(false); // Object2 resources should be pending download expect( @@ -552,8 +600,8 @@ describe('gdjs.ResourceLoader', () => { 'scene1-object2-resource2.png' ) ).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object2')).to.be(false); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object2', 'Scene1')).to.be(false); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup', 'Scene1')).to.be(false); mockedResourceManager.markPendingResourcesAsLoaded( 'scene1-object2-resource1.png' @@ -564,8 +612,8 @@ describe('gdjs.ResourceLoader', () => { await delay(10); // Object1 and Object2 should now be ready - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1')).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object2')).to.be(true); - expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup')).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object1', 'Scene1')).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('Object2', 'Scene1')).to.be(true); + expect(runtimeGame.areObjectOrGroupAssetsLoaded('MyGroup', 'Scene1')).to.be(true); }); });