mosra / magnum

Lightweight and modular C++11 graphics middleware for games and data visualization
https://magnum.graphics/
Other
4.75k stars 439 forks source link

[Feature Request] Split mainLoopIteration() methods #577

Open AndreasLrx opened 2 years ago

AndreasLrx commented 2 years ago

Currently mainLoopIteration methods (for the different platform applications) does multiple actions that could be split in 3 other methods:

This would allow a better control when making custom game loop.

mosra commented 2 years ago

Sure, I'm not opposed to this idea. But first I'd like to know more about your use case, and what exactly is hard / impossible to achieve with the current API.

AndreasLrx commented 2 years ago

I'm starting a small game engine for personnal use (mostly as a learning project) with Magnum for rendering/physics functionalities.

Looking at the game loop of Magnum applications, you handle the update going faster than expected minimalPeriod (with a delay). By doing something like this:

while (true)
{
  double start = getCurrentTime();
  processInput();
  update();
  render();

  sleep(start + MS_PER_UPDATE- getCurrentTime());
}

However you do not handle the opposite case: update taking more time than expected. This case could be handled like this:

double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  processInput();

  if (lag > MS_PER_UPDATE * 10)
    lag = MS_PER_UPDATE * 10;
  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }

  render();
}

This is a possible way to manage lagging but isn't the only one. Allowing separated functions call like I suggested above would allow users to handle it the way they want.

(For more informations see https://gameprogrammingpatterns.com/game-loop.html)

mosra commented 2 years ago

I see, that makes a lot of sense, thanks!

How about this:

AndreasLrx commented 2 years ago

Yes this sounds great ;)

mosra commented 1 year ago

Sorry for going back to the drawing board but ... while attempting to merge #580 I realized the new split workflow adds a lot of potential for errors and the set of states that need to be handled is quite hard to get right on the app side. Even documenting the process is rather complex.

So, going back to your example snippets above, why wouldn't something like this work as well, without splititng anything? Assuming you don't call setMinimalLoopPeriod() and rely just on VSync to avoid the sleep(), it's doing exactly the same thing as yours, as far as I can see:

double previous = getCurrentTime();
double lag = 0.0;

YourApplication::tickEvent() {
  if (lag > MS_PER_UPDATE * 10)
    lag = MS_PER_UPDATE * 10;
  while (lag >= MS_PER_UPDATE)
  {
    update();
    lag -= MS_PER_UPDATE;
  }
}

while(true) {
  double current = getCurrentTime();
  double elapsed = current - previous;
  previous = current;
  lag += elapsed;

  // does the following: 
  //   processInput();
  //   tickEvent();
  //   render();
  app.mainLoopIteration();
}

Or am I missing something? The point here is that if you need to perform updates at regular intervals, you do it yourself in the tickEvent().

AndreasLrx commented 1 year ago

No worries, I don't want to cause any regressions :wink:

Your idea totally works, I don't know why I haven't thought of it before. It would have saved time to both of us :sweat_smile:

mosra commented 1 year ago

Yay! I'll salvage the GLFW tickEvent() addition from #580 at least, it's not like your whole work would go to waste ;) Thanks for that!