hecrj / coffee

An opinionated 2D game engine for Rust
https://docs.rs/coffee
MIT License
1.08k stars 55 forks source link

Should `Game::load` receive `&mut Window`? (not a duplicate of #130) #137

Open DomtronVox opened 4 years ago

DomtronVox commented 4 years ago

So this may be a dumb question as it is very similar to #130 (I copied the title and tweaked it slightly even).

I'm having some trouble understanding Tasks, so this may just be stemming from that. I have this code that does not work because Game::load receives an immutable window ref.

    fn load(_window: &Window) -> Task<MyGame> {
        let player = Image::load("assets/sara-atlas.png")
                      .map(|image| SpriteSheet::new(image, 5, 5))
                      .run(_window.gpu())
                      .expect("Error loading image.");

        let world = World::new();

        Task::succeed(|| MyGame { 
             player, 
             world,
        })
    }

The issue is, I want to load this image now then pass it into the MyGame object at the end of the load function. But Task::run function needs gpu and the Window::gpu function wants a mutable Window which load doesn't get.

In #103 you said it is by design that Game::update doesn't get a mutable window because only game logic should happen there and you don't want it to be changing the window itself, but I assume the same doesn't apply to Game::load, or am I wrong?

This is likely me just being confused about Tasks. When do Tasks actually get run? From the looks of it, it seems whatever calls Game::load runs the task(s), but if I have 100 images to load and I'm only supposed to return a Task\<Game> from load how am I supposed to load all those images? Also, down the road I want to have an AssetDatabase struct that holds lookup tables for all loaded assets (sprite sheets, dialogue trees, etc). And I want to load those assets after the main menu when the player selects one of the campaigns to play. That means it won't be happening in Game::load. But I also don't want some 'if' statement stuck in my Game::draw function being checked every tick. Should it go in Game::interact? (i've yet to mess with interact and input so I'm not sure)

Finally, thanks for making and maintaining this project. It's been very nice to work with so far.

DomtronVox commented 4 years ago

Here is a quick example of something I want to do that needs Game::load to have a mutable window.

I'm adding in ECS, specifically Specs. My rendering system needs a value put into the ECS world object that the render function can draw to. That value should be initialised in the load function. I was going to use Batch. To create a batch you need an image and to create an image you need a gpu.

fn load(_window: &Window) -> Task<MyGame> {
        let player = ... //same as above

        let world = World::new();

        //insert data into the world
        let bg_img = Image::from_image(_window.gpu(), 
                                                             DynamicImage::new_rgba8(_window.width(), _window.height()))
                             .expect("Error creating batch image.");
        world.insert(Batch::new(bg_img)); 

        Task::succeed(|| MyGame { 
             player, 
             world,
        })
    }

//....

    fn draw(&mut self, frame: &mut Frame, _timer: &Timer) {
        // Clear the current frame
        frame.clear(Color::BLACK);

        let mut screen_batch = self.world.write_resource::<Batch>();
        screen_batch.clear();

        //run render system which uses the batch object to draw to

       //something like this to draw to screen.
       screen_batch.draw(&mut frame.as_target());
    }

EDIT

Ok, I seemed to have figured out how to do this. It still feels kinda hacky to me though and would be simplified by Game::load getting a mut window. This is my new Game::load function:

    fn load(_window: &Window) -> Task<MyGame> {

        (
            Image::load("assets/sara-atlas.png"),
            Task::using_gpu(|gpu| {
                Ok(Batch::new(
                    Image::from_colors(gpu, &[Color::BLACK])
                      .expect("Failed to creat batch image.")
                ))
            })
        ).join()
         .map(|(player_atlas, render_system_batch)| {

              let player = SpriteSheet::new(player_atlas, 5, 5);

              let mut world = World::new();
              ecs::register_components(&mut world);

              //insert data into the world
              world.insert(render_system_batch); 

              MyGame { player, world }
         })

    }

Is this an abuse of the task system or more or less the intended use?

EDIT 2 I realize now I'm misusing Batch. I thought it was something to draw images to. I now realize it is for drawing the same image/image segment multiple times. However, I also cannot use Canvas for this as it needs a gpu for the as_target() and I cannot seem to get the gpu into my system. But this is getting off topic from the original ticket.