Skip to content

Decode bundle images on demand#2042

Open
Tutez64 wants to merge 1 commit intoopenfl:developfrom
Tutez64:lazy-bundle-image-decode
Open

Decode bundle images on demand#2042
Tutez64 wants to merge 1 commit intoopenfl:developfrom
Tutez64:lazy-bundle-image-decode

Conversation

@Tutez64
Copy link
Copy Markdown

@Tutez64 Tutez64 commented Mar 23, 2026

Summary

This changes lime.utils.AssetLibrary to decode bundle images lazily instead of decoding every IMAGE asset eagerly in __fromBundle().

Bundle images are now stored in cachedBytes first, then decoded on first getImage() / loadImage() access. isLocal(IMAGE) is also updated so synchronous access continues to work for bundle-backed images that have not been decoded yet.

Why

The previous behavior eagerly called Image.fromBytes(...) for every IMAGE asset in a bundle as soon as the library was created from the bundle.

For large bundles, this can create very large startup costs in both CPU and memory even when only a subset of the bundled images is actually used.

This change keeps bundle loading cheaper and shifts image decode cost to first real use, which is a better fit for large runtime-loaded libraries.

Behavior change

Before:

  • fromBundle() decoded all IMAGE assets immediately

After:

  • fromBundle() keeps IMAGE assets as bytes
  • getImage() / loadImage() decode on demand
  • isLocal(IMAGE) remains true for bundle images backed by cached bytes

Notes

This does change when decode cost is paid:

  • lower upfront bundle load cost
  • potential first-use cost when a specific image is accessed for the first time

Preloaded images still go through loadImage() during load(), so they are still decoded during preload as expected.

@player-03
Copy link
Copy Markdown
Contributor

I'm a little worried about reusing cachedBytes for this. On the plus side, it saves a bit of memory. On the minus side, this isn't what it's meant for.

If someone called isLocal(imagePath, BYTES), it could incorrectly return true. Then later once the image was loaded, it could return false for the same inputs. I can't think of a use case where you'd want to call that, but it's technically not how the function should behave.

My only other thought is, should this be optional? There might be some reason not to want lazy loading, in which case maybe we could add a way to mark a library as not lazy. But I can't think of a specific reason at the moment, so perhaps that can wait.

@Tutez64
Copy link
Copy Markdown
Author

Tutez64 commented Mar 24, 2026

Maybe a cleaner version of this change would be to introduce a dedicated store for bundle-backed image bytes, something like cachedImageBytes, and then:

  • keep bundled IMAGE assets there in __fromBundle()
  • decode from that store in getImage() / loadImage()
  • make isLocal(id, IMAGE) check cachedImages || cachedImageBytes
  • leave cachedBytes and isLocal(..., BINARY) semantics unchanged

On the optional question, since preload already provides a way to force up-front decode through load(), I would keep the behavior simple unless a real need appears.

@player-03
Copy link
Copy Markdown
Contributor

Whoops, I missed this reply somehow.

Agreed, a cachedImageBytes map would be the cleanest way to keep everything separate.

I also missed preload. That's basically what I'd do to make it optional, so we can count that as already done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants