Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-stage asset loading #1833

Open
ndarilek opened this issue Apr 6, 2021 · 13 comments
Open

Multi-stage asset loading #1833

ndarilek opened this issue Apr 6, 2021 · 13 comments
Labels
A-Assets Load files from disk to use for things like images, models, and sounds C-Feature A new feature, making something new possible

Comments

@ndarilek
Copy link
Contributor

ndarilek commented Apr 6, 2021

What problem does this solve or what need does it fill?

Often, the result of an asset load that I'd like to pass around isn't just raw bytes, or some struct created by another library. For instance, I have a plugin that loads byes from the array and does some calculation--determines how many channels an audio file contains. My plugin then creates some sort of audio context, which in turn will load the bytes and create a buffer which my systems will later use directly.

Right now, my asset system loads the bytes, does any processing, and marks the asset as loaded. Then, a system listens to events, using the context resource and modifying the created struct with the type introduced by the context.

Unfortunately, this doesn't interact well with the asset loading system. Assets are marked as loaded, even though the real data I want hasn't been created yet. The startup system rushes to instantiate everything, and usually succeeds. But sometimes it fails, and while I can avoid a crash, I can't access the asset in the way I'd like. There's no easy way to tell whether an entire group of assets is fully loaded, in the same way I can with regular asset loaders.

What usually ends up happening is that the bytes of my game's background audio fully load, and the asset is marked complete. My startup OpenAL system detects the new asset and creates an OpenAL buffer. This almost always succeeds before the background audio starts playing, but occasionally it doesn't, and the game starts but my background audio doesn't play. I'm currently rolling my own custom Web Audio backend, and I expect this problem to be even worse given the general latency of loading things from the web.

What solution would you like?

Two possibilities:

  1. Make resources available to asset loaders somehow, so the loader can instantiate the asset in its final desired form. Alternately, create a different asset loader that has access to resources.
  2. Create some sort of partial-loaded state which the asset loader can use to signal that it's done with the asset, but which doesn't report that the asset is fully loaded when groups are queried. Add an event for partial loads, and let systems indicate that they've either partially or completely finished an asset load in a way that integrates with the current load checks. Then the loader marks the asset as partially loaded, and systems can detect PartialLoad events and either finish the load or perform another step and re-emit the event to even more systems for processing.

What alternative(s) have you considered?

I imagine I'll have to emit a custom event when I've loaded all my assets, but this further complicates the already non-straight-forward asset-loading process by requiring me to manually track when my custom processing step finishes. Given that I'm doing this in a plugin, whatever mechanism I create will be specific to that plugin. I'm open to other possibilities that I may be missing.

Thanks.

@alice-i-cecile alice-i-cecile added A-Assets Load files from disk to use for things like images, models, and sounds C-Feature A new feature, making something new possible labels Apr 7, 2021
@alice-i-cecile
Copy link
Member

Does #1665 solve your use case?

@ndarilek
Copy link
Contributor Author

ndarilek commented Apr 7, 2021 via email

@alice-i-cecile
Copy link
Member

Those are good questions, and beyond what I'm fully confident in my answers for. Tagging in @mockersf <3

@mockersf
Copy link
Member

mockersf commented Apr 7, 2021

The PR looks like it'd make me catch the initial load completion, then somehow construct a new Vec of assets that were dynamically added by the new creation systems and check those for completion.

You wouldn't need to keep the initial handles and wait on them.

Roughly and not checking it would compile exactly like that (probably need more typing), you could do:

let my_handles = ["file1.ext", "file2.ext", "file3.ext"].iter()
    .map(|file| asset_server.load(file))
    .map(|handle| asset_server.create_from(handle, my_asset_transform_fn))
    .collect::<Vec<_>>();

Then you can wait on that vec of handles of transformed assets. The asset_server.create_from method takes care for you of waiting for the initial handle to be ready before actually creating the new asset from it.

@ndarilek
Copy link
Contributor Author

ndarilek commented Apr 7, 2021 via email

@mockersf
Copy link
Member

mockersf commented Apr 7, 2021

Your plugin can provide its own method that will do asset loading and transform, for example in an extension trait on AssetServer. Then your plugin users will be able to do asset_server.load_my_audio_file(file) and get directly the transformed handle

@zicklag
Copy link
Member

zicklag commented Apr 7, 2021

I've needed this feature before, for something as simple as just setting the texture filtering mode and to me it looks like @mockersf 's PR will work great for it.

@ndarilek
Copy link
Contributor Author

ndarilek commented Apr 7, 2021 via email

@mockersf
Copy link
Member

mockersf commented Apr 7, 2021

The flow when loading an asset:

  1. call asset_server.load(file_path)
  2. create an handle
  3. start a task in IoTaskPool, giving it a clone of itself, the handle and the path, and detach, this continue in another thread
  4. returns the handle

From there, the task:

  1. find the asset loader based on file extension
  2. read the file as bytes
  3. give the bytes to the asset loader
  4. the asset loader builds a map of handles to new assets (so that it can load several assets from one file)
  5. for each asset in this map, the asset server will send it through a channel specific to the asset type

Finally:

  • a system, one for each asset type, is running every frame and listening to new assets coming from the channel, adding them to the assets resource

@ndarilek
Copy link
Contributor Author

ndarilek commented Apr 7, 2021 via email

@alice-i-cecile
Copy link
Member

Not sure whether or not to leave this issue open pending closure of the PR

We can leave it open and let GitHub do the work for us when it's merged. Closed by #1665.

@zicklag
Copy link
Member

zicklag commented Apr 7, 2021

@mockersf the asset post-processor can't borrow world resources though, can it?

// new texture with one pixel every two removed
let texture_handle_1 =
    asset_server.create_from(texture_handle, |texture: &Texture /* can't put resource here 👈 */| {
      // Can't use resource to create new asset
    });

In a situation I just ran into today I have an audio manager resource it would be very nice to be able to do something like this:

// load sound from disk
let sound_data_handle = asset_server.load("blink.mp3");
let sound_handle =
    asset_server.create_from(sound_data_handle, |sound_data: &SoundData, sound_manager: Res<SoundManager>| {
        sound_manager.add_sound(sound_data_handle);
    });

I have a feeling this would be much more difficult to accomplish and that I am going to need to use a system to watch asset events to accomplish this.

@mockersf
Copy link
Member

mockersf commented Apr 8, 2021

no, taking a closure with dynamic parameters would be hard... it should be possible to change it to run in a system with world access and give it to the closure? Or you can try and move your ressource from out of the closure to in instead of taking it as a parameter

let sound_manager = ...;
let sound_handle =
    asset_server.create_from(sound_data_handle, move |sound_data: &SoundData| {
        sound_manager.add_sound(sound_data_handle);
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Assets Load files from disk to use for things like images, models, and sounds C-Feature A new feature, making something new possible
Projects
Status: Wishlist
Development

No branches or pull requests

4 participants