Skip to content

Delete subassets; only asset processing! #21223

@andriyDev

Description

@andriyDev

Motivation

Subassets cause all sorts of crazy issues (e.g., #12756) and a never-ending stream of edge cases (e.g., #21222). Loading a single subasset can result in the entire asset being reloaded even if other subassets from the same root asset are still around. A lot of complexity in the asset server is due to juggling subassets.

Asset processing is only barely used by Bevy and AFAIK users. Part of the reason for this is that asset processing has very confusing functionality. It currently behaves as though it should be totally transparent (turning on/off asset processing should make no difference), but that means users are unlikely to want to make their game "dependent" on asset processing. By making it so transparent, it becomes confusing to have an asset processor change the type of an asset/subasset (since now callers might loading the asset as a different type than they'd otherwise expect). Asset processing is also primarily demonstrated as an optimization for build time.

We expect in the future that formats like GLTF should be preprocessed into BSN and several other files (known as "one to many" asset processing, see #9714). However, this is difficult to implement since it's unclear which files to write each subasset to. How do we load this up at runtime? Will our file paths include #?

The Scheme

Delete subassets, and make asset processing "mandatory". Let's see how this would work. I will be using GLTF as an example.

You take your .gltf/.glb file and place it into your assets folder (for this example "path/to/my.gltf"). If you were to attempt to load this in Bevy without asset processing, the GltfLoader will load it as a Gltf asset. There are no subassets, so all the meshes, textures, materials, scenes, etc must be stored inside this one big Gltf asset. So far this is only a downgrade.

But now, we bring in the GltfProcessor. This will take that one Gltf asset, split it into a bunch of smaller objects (e.g., each mesh, each scene, etc) and call something like ProcessContext::write_subasset for each of them. This would write the assets as "path/to/my.gltf/Mesh0" perhaps. We now have a directory full of all the "subassets" that the Gltf asset contained (note: it doesn't have to be all subassets, discussed later).

Now at runtime, users just load "path/to/my.gltf/Mesh0". They load that singular mesh. That's it. There are no subassets, so either the asset is loaded or it's not loaded. All the "useful" parts of the Gltf have been extracted out through the GltfProcessor. The subasset can be hot reloaded easily since we know there's a one-to-one mapping between files and assets. The asset processor can also be hot reloaded easily since there's also a one-to-one mapping between original files and assets that need to be processed.

This effectively turns the asset processor into an import process! It now has a very clear purpose - we are turning raw source assets into just the "useful" parts of the asset. This also makes the asset processor easier to wrap. For example, you can just take the Bevy default GltfProcessor, wrap it in your own processor that extracts some other data (e.g., GLTF extras), and writes them to some other asset - while keeping all the original processing. This means that we do not need to expose every piece of data in the GLTF in our GltfProcessor. For example, the default GltfProcessor should not do anything with the GLTF extras. If a user wants that data, they will need to wrap the default processor, or write their own entirely (usually wrapping should be sufficient though).

Risks/Regressions

  1. We're turning one big file like .glb into many small files. Reading many small files is usually less efficient than one big file, so it could be a performance concern.
  2. Loading UGC is harder. You can't just accept a GLTF file path, load it, and try to spawn it as a scene. You must first load it as a Gltf asset, and then decompose that file manually (e.g., through a system). This is effectively running the asset processor at runtime.
    • We can probably add a manual convenience function for applying a processor at runtime. Unclear if it's necessary.
  3. "Archive assets" can't exist anymore Supporting archive assets #12279.
    • Archive assets shouldn't exist anyway IMO. If it's truly an archive, it should be an asset source that we "mount" at runtime. This means supporting changing the set of asset sources at runtime though.
  4. Users are now forced to use the asset processor.
    • We need to make this easy to run.
    • I don't see this as a big loss. We expect this to be the future anyway with BSN coming.
  5. The asset processor would only really make sense at publish time.
    • This is kinda the case today. It would be weird to be running the asset processor after publish time during game time.
  6. The asset processor currently has an issue to do with async-fs, resulting in corrupted files. Dropped writes resulting in corrupted files smol-rs/async-fs#45
    • This is presumably a temporary issue though.

Positive Side-effects

  1. Since the asset processor only makes sense at publish time, we should lean into that and remove as much of the runtime logic as possible. Yay for simpler code!
  2. Our AssetPath format becomes simpler - it's basically all paths!
  3. We need to "guess the type" of an asset less often, since it's whatever the user asked for, since users can't request subassets.
  4. Hot reloading is simpler! Just find the assets for that one file - no subassets!
  5. One to many asset processing is easier to implement. Since asset processing is necessary, the processor can write whatever file paths it wants (with some fixed prefix), and the user just loads those assets. There's no confusion whether the subasset exists in the original asset or just the processed asset - it has to be the processed asset!

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-AssetsLoad files from disk to use for things like images, models, and soundsC-Code-QualityA section of code that is hard to understand or changeD-ComplexQuite challenging from either a design or technical perspective. Ask for help!S-Needs-DesignThis issue requires design work to think about how it would best be accomplished

    Type

    No type

    Projects

    Status

    Respond (With Priority)

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions