Skip to content

Asset Bundles - improvements and making it public #5626

@Maksims

Description

@Maksims

Asset Bundles - are a great way to reduce number of network requests by bundling assets into single .tar file.
Currently engine support for it usable, but it is not public and needs improvements. It also relies on Editor bundling functionality and would be great if described well, so that engine-only developers can create .tar files themself.

Current "issue" with bundles:

If asset is in a bundle which is not loaded, and we try to load that asset, it will fire an error. This has couple of issues:

  1. Developers who implements lazy-loading, now should check not only if asset is loaded, but also if asset is referenced in a bundle before trying to load it.
  2. When we reference an asset by components, engine will load an asset for you when its needed for rendering - this is very powerful, and simplifies lazy-loading big way. But if asset is referenced by a bundle then this fails and will fire errors unless bundle is loaded in advance, which for non-preloading cases makes it hard to manage.
  3. Because asset can be in multiple bundles, it makes it somewhat non-obvious what to load here.
  4. Bundles do throw blob requests in a network tab, this preferably should be avoided, to improve network debugging.
  5. .tar potentially can be streamed, instead of loading whole file first, this will spread assets parsing CPU impact when lazy-loading bundles, as well as allow parsing while network is still downloading, so improve speed at which bundled assets will load in overall.

The potential solution to this problem:

Make AssetRegistry.load to take bundles into the account, and load bundles when trying to load asset which is referenced by a bundle. Instead of throwing an error (current behavior).

Add optional options argument to AssetRegistry.load method, that can assist in picking the right bundle with two properties bundlesIgnore and bundlesFilter:

app.assets.load(asset, {
    bundlesFilter: (bundles) => {
        return bundles[0];
    }
});

When asset registry tries to load an asset, that is referenced in a bundle, developer has an option to ignore bundles by providing bundlesIgnore flag, which by default is false so assets will load from bundles.
And then within list of referenced bundles, if any of the bundles are already loading/loaded, we ensure asset is marked as loading and then don't do anything else, as it will trigger load event of an asset once bundle is loaded. If none of the referenced bundles are loading/loaded, by default the smallest bundle will be called for loading.
If developer provides bundlesFilter callback, then instead of smallest bundle by default, the developer can pick a specific bundle from a list of referenced bundles. If nothing is returned, default behavior prevails.

This solution will not require any change in engine lazy-loading behaviors across components. It will load non-preloaded assets which are in bundles as expected without throwing an error (current behavior). And provide control to the developer if such needed.

Use Cases:

Currently to take bundles into account:

const asset = app.assets.get(42);

asset.ready(() => {
    // asset is loaded
});

if (!asset.loading && !asset.loaded) {
    const bundles = app.bundles.listBundlesForAsset(asset);
    if (bundles) {
        asset.loading = true;
        let bundle;
        
        for(let i = 0; i < bundles.length; i++) {
            // if any of bundles is already loading
            // then do not try to load any other bundle
            if (bundles[i].loading) {
                bundle = null;
                break;
            }
            bundle = bundles[i];
        }
        
        if (bundle)
            app.assets.load(bundle); // start loading bundle
    } else {
        app.assets.load(asset);
    }
}

With new behavior, basic lazy-loading will work regardless of in-bundle or not:

const asset = app.assets.get(42);

asset.ready(() => {
    // asset is loaded
});

app.assets.load(asset);

If a developer need to force load asset not from bundle:

app.assets.load(asset, { bundlesIgnore: true });

If a developer need to pick which bundle to load asset from:

app.assets.load(asset, {
    bundlesFilter: (bundles) => {
        const ind = Math.floor(bundles.length * Math.random()); // random bundle just for example
        return bundles[ind];
    }
});

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions