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

Should Game::update receive &mut Window? #103

Open
dlight opened this issue Oct 11, 2019 · 5 comments
Open

Should Game::update receive &mut Window? #103

dlight opened this issue Oct 11, 2019 · 5 comments
Labels
question Further information is requested
Milestone

Comments

@dlight
Copy link
Contributor

dlight commented Oct 11, 2019

I note that Game has the following methods:

fn interact(&mut self, _input: &mut Self::Input, _window: &mut Window)

fn update(&mut self, _window: &Window)

But load::Task::run can only be called if I have &mut Window, so I can only call it on interact, not on update. Is this by design? Is the rationale on that written somewhere?

I suppose that, since Game::update can potentially be called more times than Game::interact, it might not allow calling Task::run because it would be expensive. However, what if I'm procedurally generating GPU assets on Game::update and want to send it to the GPU ASAP? Should I store it somewhere and wait for the next Game::interact? (this looks ugly)

@hecrj
Copy link
Owner

hecrj commented Oct 11, 2019

Game::update receives an immutable Window by design.

The idea here is that update should stay pure and have no side-effects other than changing game state. Ideally, your update logic should be completely decoupled from your rendering strategy. This makes your game way easier to test and lets you change your renderer freely (you could get a headless mode for free, even).

The current design in Coffee gets us halfway there. You can't draw or talk to the GPU in update, but you can still rely on graphical assets. This is definitely something to improve, either we go all the way and we split game state entirely from assets, or we simply allow side-effects on update. I personally think an opinionated approach is worth exploring in this case. I am open to any suggestions!

The current Task::run should not be public (I forgot to make it private 😅), as it is only meant to be used by loading screens. In master it has been replaced by a method that takes a Gpu instead of a Window. If you want to load resources at runtime I recommend you to use methods that do not return a Task for now. We will explore async asset loading once async/await reaches stable.

For your particular use case, I would probably generate and upload the game assets during draw instead. Is there any reason why this may not work?

In any case, thank you for sharing your use case! I will keep it in mind!

@hecrj hecrj added the question Further information is requested label Oct 11, 2019
@dlight
Copy link
Contributor Author

dlight commented Oct 11, 2019

For your particular use case, I would probably generate and upload the game assets during draw instead. Is there any reason why this may not work?

I don't know yet, I'm just beginning with this (amazing) crate!

But here's my guess: my map will not be static and actually depend on game logic, so its generation should be done on update (the map is also not used only for display but also for physics etc). If I move its generation to graphics, I will probably need to do there a lot of game logic (and I will lose the fixed timestep of update)

However, map generation can be done completely on the CPU. On update, I can generate an image::RgbImage (from image crate) and store it on my game state, and then load it on the next draw (that's what I referred as "ugly"). But once it's generated I would like to push to the GPU as soon as possible, to hide the latency. So, should I really block my rendering on draw to wait the CPU upload the buffer to the GPU? This might be slow. (btw I actually don't know whether this will matter in practice).

And indeed, I would be happy with async/await support for asset loading (in my head it should work like this: I start uploading to the GPU asynchronously on update, and draw will start using the new asset as soon as it is ready).

The idea here is that update should stay pure and have no side-effects other than changing game state. Ideally, your update logic should be completely decoupled from your rendering strategy. This makes your game way easier to test and lets you change your renderer freely (you could get a headless mode for free, even).

But, more philosophically.. I'm thinking about this. What I really wanted to do is to eventually move some game logic to the gpu by writing custom shaders. But yeah, this would make it harder to test (I don't know any CI with GPU support).

In master it has been replaced by a method that takes a Gpu instead of a Window

Oh yes I'm actually using Task::using_gpu (I moved to master because I wanted to use the new coffee::ui::Image)

If you want to load resources at runtime I recommend you to use methods that do not return a Task for now.

And what method could do this?

You mean something like graphics::Image::from_image(window.gpu(), myimage)? This will immediately block until it uploads the image to the GPU, right?

@hecrj
Copy link
Owner

hecrj commented Oct 11, 2019

However, map generation can be done completely on the CPU. On update, I can generate an image::RgbImage (from image crate) and store it on my game state, and then load it on the next draw (that's what I referred as "ugly").

Will you procedurally generate every pixel of the map? This sounds very expensive. Normally, you generate a map as a bunch of game entities or terrain.

I have a prototype game that does procedural generation and lazy chunk loading. In update, when the player moves to an unexplored area, I procedurally generate the tiles of a new chunk of my map (grass, water, sand, etc.) and its entities (trees, ores, enemies, etc.). I do not represent all this using pixels, but an actual data structure that makes sense for my update logic (most likely arrays and vectors of enums).

When it comes to rendering, all I do is simply query the chunks of my map that are visible, assign sprites to each of the terrain tiles and entities, and draw them all at once using a Batch. Then, if you want to keep the static part of your map loaded in the GPU so rendering is faster you could use a Canvas (terrain tends to be pretty static, for instance).

You mean something like graphics::Image::from_image(window.gpu(), myimage)? This will immediately block until it uploads the image to the GPU, right?

Yes. Keep in mind that Task::run will also block immediately, there is no async support for it yet! Loading screens have access to the engine internals and are able to draw between each different Task, but there is no actual loading happening in another thread.

@dlight
Copy link
Contributor Author

dlight commented Oct 11, 2019

My current plan is to first generate a map skeleton using petgraph, then lay it out on a lower resolution image, then do a bunch of stuff on top of it like maze generation (each pixel would roughly correspond to what a tile would be in size), then scale it to a higher resolution image and create finer detail (similar to this - the previous steps of the pipelines would provide rough shapes for it to work).

Yes, this is supposed to be expensive - that's why it's fun! I'm inspired by some stunning stuff produced by contemporary demoscene (which is surprisingly fast even on modest hardware). If this project grows I might need to make heavy use of rayon and simd on the CPU side, and move a lot of stuff to shaders on the GPU side, and at this point it might outgrow coffee (but knowing me, I'm much more likely to abandon the project much before this, so I'm fine, 😅)

@hecrj
Copy link
Owner

hecrj commented Oct 12, 2019

That looks great! Be aware that Coffee does not support custom shaders (see #57), so it may not be a good fit!

In any case, I still think you should be able to keep a logic representation of the masks, and then map to actual colors (images) in draw when your map changes. You can split your map into multiple logical units or chunks and use a Canvas, so you do not have to redraw all of it.

This is what I personally do to implement my minimap, where I draw a LOT of individual pixels. You could also distribute the CPU load in multiple frames with an animation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants