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

Load resources on a seperate thread #815

Closed
Dieff opened this issue Aug 28, 2020 · 6 comments
Closed

Load resources on a seperate thread #815

Dieff opened this issue Aug 28, 2020 · 6 comments

Comments

@Dieff
Copy link

Dieff commented Aug 28, 2020

I am trying to load images to draw on a separate thread which I show a loading progress bar on the main thread. It is currently hard to do this with ggez because all of the functions for load data, both in the filesystem module and the graphics::Image type require access to the Context, which I cannot pass to a different thread nor duplicate. None of the examples or docs seem to explain how this could be accomplished.

Some alternatives I have considered

  • Do not use ggez to interact with the filesystem. This makes me lose out on a bunch of ggez features, and I still need to get spirtes into a type that ggez::graphics can draw.
  • Load images into memory and then use the Image::from_rgba8 function to load them. I think this requires an entirely new dependency to handle converting a png to the rgba8 format. I would need to use the same process for audio.

Since loading on a different thread is very common, I think an example of how to do this would be helpful for ggez users. If anyone has suggestions for a good way to load assets on another thread I would be happy to write up a guide and example to help future users of the library.

@haselkern
Copy link

This has (kind of) been discussed in #251. The problem is, as you correctly stated, that everything wants a &mut Context which prevents background loading. Issues that are related to this are #570 and probably the more relevant #480.

A (not great) workaround I did was to load resources one after another, a single resource each frame in order to not freeze the UI completely, but still show a progress bar on a loading screen.

@Dieff
Copy link
Author

Dieff commented Aug 31, 2020

What if this problem were solved by adding something new to the ggez API?

One idea I had is that this functionality could be implemented on a type AssetLoader. When you create a new AssetLoader, it clones whatever information it needs from a &mut Content (mainly it needs a copy of FileSystem I think). The loader can be sent to a new thread, or used in the current one. Having a more complex IO API here would also set the stage for running ggez games in more unusual environments, like a web browser or more operating systems. The API of AssetLoader could be extended as needed to fit new environments.

struct AssetLoader

impl AssetLoader {
  fn new(&mut Content) -> Self;
  fn load_image(&self, path: impl AsRef<Path>) -> Result<Image, io::Error>;
  fn load_sound(&self, path: impl AsRef<Path>) -> Result<SoundData, io::Error>;
  fn load_font(&self, path: impl AsRef<Path>) -> Result<Font, io::Error>;
  // for saving games
  fn write_data(&self, &[u8]) -> Result<(), io::Error>;
}

If we want to get fancy, we could add async asset loading right to AssetLoader. Since ggez already supports the concept of resource directories and automatic saving of engine configuration, it seems like more features around resources loading would fit with the scope of the library. Users could poll futures during the game update function to see if their assets are done loading without worrying about any sort of executor or async/await. Behind the scenes, AssetLoader::async_load... just spawns a new thread or hands off a task to a thread pool.

impl AssetLoader {
  fn async_load_image(&self, path) -> Future<Output = GameResult<Image>>;
  ...
}

impl EventHandler for Game {
  fn update(&mut self, &mut ctx) {
    if let Poll::Ready(Ok(image)) = self.loading_image.poll(&mut self.loading_image_context) {
      // draw my new image
    } else {
      // draw loading indicator
    }
  }

}

@icefoxen
Copy link
Contributor

icefoxen commented Sep 1, 2020

The async loader concept is one I've considered, note that the coffee lib already does something like that and it's pretty cool. I would probably want synchronous loading to at least be the default so new users don't have to understand async to get things working, but being able to load assets in the background is pretty darn nice.

@Ratysz
Copy link
Contributor

Ratysz commented Sep 2, 2020

The library goods is easy enough to integrate and will do most of this for you.

@icefoxen
Copy link
Contributor

Oooh, the goods library looks pretty neat. Thanks!

@icefoxen
Copy link
Contributor

I'm closing this basically because I'm not going to do this without a major redesign, which is not on my to-do list any time soon. Both OpenGL and rodio are pretty opinionated about threads, so it's hard to get past their design decisions and make something with ggez that would let you hook a loading callback or such into.

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

No branches or pull requests

4 participants