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

Simulation/Game Update Loop #317

Open
julmb opened this issue May 5, 2024 · 4 comments
Open

Simulation/Game Update Loop #317

julmb opened this issue May 5, 2024 · 4 comments

Comments

@julmb
Copy link

julmb commented May 5, 2024

I would like to use Monomer to make a video game. The game is fairly UI-heavy with lots of menus and mouse-interactable screens, so a GUI library like Monomer seems like a good fit. However, the game does not only react to user interactions, it also requires a simulation to constantly update the game state at a reasonably fast rate (at least 60 Hz). How can I achieve something like this using Monomer?

From what I have seen, the easiest way would be to use a Producer (similar to the timeOfDayProducer from the tutorial) to generate Tick events that are then used to update the simulation state. However, what happens when either the simulation or the rendering cannot keep up? Will the generated Tick events just pile up in a growing queue?

I have also been thinking about using a Task to kick off a Tick event, which then both updates the model and kicks of a new Task, thus keeping the loop going. If EventResponses are handled sequentially, this should not cause a pileup of Tick events. Although this also seems kind of ugly, since each Task kicks off an asynchronous operation, which seems like overkill for a simple game loop.

Maybe there is a more elegant solution?

@fjvallarino
Copy link
Owner

Hi @julmb!

I think something similar to Example 03 - Ticker could work for your use case. The idea is that since you may receive more events than you can render in a reasonable time, you use a grouping thread that receives the messages, de-duplicates or combines them, and sends them to the main application not more frequently than desired.

To be more specific about your scenario, you could:

  • Launch a Producer (or a regular Haskell thread) that takes care of running the simulation.
  • Launch a Producer that will listen on a channel for messages from the simulation and will send a de-duplicated message to the application (i.e. Monomer related code) no more often than desired. Considering you usually want to render the latest simulation state, you can even discard older messages if you receive a new one and the minimum time frame has not passed.

That example also shows a basic way to communicate from the application to the Producer, which, again, is just a channel. Depending on your needs, an MVar/TVar could be useful.

@julmb
Copy link
Author

julmb commented May 13, 2024

Hello and thank you for the quick reply!

Decoupling the simulation loop from the UI loop sounds like a good idea. And you are right, I can easily discard stale simulation states in favor of the most recent one.

However, I am still slightly worried that the intermediate producer might send too many events. If I want the UI to update smoothly at 60Hz, this means the intermediate producer will send 60 events per second. What happens if the UI can not keep up with this?

Or maybe there is a way to process new simulation states in a way that is tied to the update/render cycle of the UI, so that a new simulation state is only used when the UI is ready for a model update?

Disclaimer: I am probably worrying too much. I have a working example and so far there seems no issue with too many updates for the UI to handle. I am just trying to make sure I use the library correctly. So if this is fine then I just need to move on.

Thank you again!

@jhrcek
Copy link
Contributor

jhrcek commented May 14, 2024

The generic solution for your problem that's usually employed is debouncing.
You can use for example this simple package https://hackage.haskell.org/package/auto-update-0.2.0/docs/Control-Debounce.html to make sure that producer doesn't overwhelm the UI by making sure that it doesn't send update more often than every 1/60th of a second.

@julmb
Copy link
Author

julmb commented May 14, 2024

I did not know about this package, thank you for mentioning it. However, my concern is not on how to implement debouncing, but rather what happens if monomer cannot perform layout and rendering at my chosen update rate of 60Hz (which might depend on the machine and background load) and whether there is a more robust way to handle this.

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

No branches or pull requests

3 participants