feat: Return cached assets instead of replacing them on reload#1033
feat: Return cached assets instead of replacing them on reload#1033nojaf wants to merge 6 commits intokaplayjs:masterfrom
Conversation
dragoncoder047
left a comment
There was a problem hiding this comment.
I would say hold off merging this until at least some part of #1021 is merged, since while it unregisters the sprite, the old one is still hanging around in the texture and isn't reclaimed. #1021 should in theory allow the sprite to be removed from there and the now-unused space made available to load other sprites.
dragoncoder047
left a comment
There was a problem hiding this comment.
There's two ways to do things here:
-
explicit unloading - what you are adding, where a second loadSprite() with the same ID gets blocked/discarded, and an explicit unloadSprite() is needed to free the slot before a sprite can be re-loaded.
-
implicit unloading - what I would suggest as the other asset systems also do it this way (you can loadSound() with the same ID and it will replace the existing one when it loads... however I would suggest also fixing those so that while the 2nd one is being loaded, you can still access the 1st one's data). With implicit unloading, once an asset with id X is loaded, loading another asset of the same kind with id X causes the onLoad callback of the new asset to be set up so that it will be addLoaded()'ed when it finished, but it does not replace the existing one with the pending one until then, so data will never be null.
Also, several files have only formatting-related changes, please put them back to what they were before / what they are on master, if pnpm fmt doesn't do that already.
|
|
||
| // remove a sprite from the asset cache, allowing it to be reloaded with a new URL | ||
| export function unloadSprite(name: string): void { | ||
| _k.assets.sprites.remove(name); |
There was a problem hiding this comment.
@nojaf once you merge in master, this is one place where the actual TexPacker unload code can be added.
| // If asset already exists and is loaded, return the cached version | ||
| const existing = this.assets.get(id); | ||
| if (existing && existing.loaded && existing.data) { | ||
| return existing; |
There was a problem hiding this comment.
It just completely discards the second-time loader promise here. Why not create a new asset for it, and set it up to replace the existing one once it loads?
| } | ||
| // remove an asset from the cache, allowing it to be reloaded with a new URL | ||
| remove(handle: string): void { | ||
| this.assets.delete(handle); |
There was a problem hiding this comment.
2nd place to add the TexPacker unload code. Make a subclass of AssetBucket that overrides this, and then use it for the sprites AssetBucket.
There was a problem hiding this comment.
Forgot to mention here initially, the add() and addLoaded() should probably already be calling this, so that the sprite subclass can handle when the sprite is loaded for the 2nd time, once the 2nd sprite loads it calls remove() on the old one and then addLoaded()'s the new one.
When an asset with the same ID is loaded again, the old loaded asset is now kept in the map while the new loader runs in the background. The new asset only replaces the old one once it finishes loading, ensuring data is never null during a reload.
Add SpriteAssetBucket subclass that overrides onRemove to call packer.remove() for each frame, reclaiming atlas/texture space. AssetBucket.add() and addLoaded() now call remove() on the old asset before replacing it, triggering cleanup via the hook.
|
Hey @dragoncoder047 , thanks for the review! I ran Anyway, let me know if I got everything. |
Summary
Problem
When switching scenes using
go(), callingloadSprite()with the same asset name would replace the already-loaded cached asset with a new pendingAssetobject. This caused issues when:_k.assets.loadedbecomestruego("sceneA")to restart the sceneloadSprite()is called again with the same namesAssetBucket.add()replaces cached assets with new pending onesonLoad()fires immediately (because_k.assets.loadedis stilltrue)getSprite(name).datareturnsnullbecause the new Promise hasn't resolved yetThis resulted in game objects being created with missing sprite data on scene restart.
Solution
Cache hit optimization:
AssetBucket.add()now returns the existing cached asset if it's already loaded, instead of replacing it with a new pending asset.Escape hatch: For the edge case where you actually want to replace an asset with a different URL, a new
unloadSprite(name)function is provided: