A game loop idea.. Stack..

Started by
13 comments, last by Zahlman 16 years, 2 months ago
The idea of a function stack isn't as absurd as it sounds... and it's not a new idea either, having been used extensively in the pre-windows days. One of my favourite implementations was the function ring-buffer; essentially a closed-ring stack. The program iterated continuously around the ring buffer until there were no more elements, in which case the program terminated. Functions were added to or removed from the ring as dictated by functions already in the ring.

This architecture was particularly useful for GUI design and for handling user inputs in real time systems... which perfectly describe current games! ;)

Of course, when you think about it, the ring-buffer is very much like an adaptive game loop.

Cheers,

Timkin
Advertisement
Thanks for the input Timkin i never claimed i came up with it first :p or that it was orgianly mine it was just a though about wether or not it would be useful enough for me actualy try and implement one in my New game im doing.

Yet again thanks.
Regards Jouei.
This approach is the basis of Pro-actor design.

Actors post commands to be executed to dispatcher. Dispatcher maintains a thread pool (can be single thread) and whenever a thread is a available, it takes the next command from the queue and passes it to thread.

For a solid implementation of multiplexer you can use Boost::ASIO. While primarly intended for network programming, it has a fully functional standalone dispatcher.

Primary benefit of this is that threads will be used invoked optimally, trying to keep CPU busy as much as possible.

Basic programming model is simple. Create resources, post desired action with callback to dispatcher. Once the dispatcher is ready it'll invoke your callback. At that point, you can release the allocated resources.

This results in fine-grained multiplexed logic. This in turn guarantees responsiveness, good CPU utilization and remains scalable.

Downsides are inverse programming model (everything is done exactly "the other way round" as with traditional programming) which is somewhat hard to explain. A more difficult problem is lack of "sequentiality". Since everything is event driven, it's hard to maintain consistent global resources, such as world timer. Another downside is considerable overhead, which means that it's not practical to execute really small actions (increase int value, for example). Typically, you should aim to execute on the order of 10,000 - 100,000 actions per second. Less means you could use a conventional model, more means you'll have too much overhead. This last figure varies greatly, and it will scale well upwards - on multi-core processors you can assume 1 million or more.

This type of programming model is excellent for concurrent programming since it naturally minimizes shared state, possibly removing need for locks completely.
Just to add my 2 cents -

I've spent a lot of time thinking about this over my career, and like most design decisions, I've come to the conclusion that there isn't really one "right" answer to it. Like everything else, it depends on what your goals are for the game you're working on.

What Antheus said is totally true. Decentralized models of execution for handling computation for your game are really useful for parallel architectures. But they come at some high costs. On my first game, I used some function pointer mechanisms to control logic flow, and it started to get really hairy. Debugging and tracing program flow became far more difficult, and we ended up regretting the decision in the end.

For my second game, we went in the exact opposite direction, and just coded everything linearly. Of course, this was really inflexible, and by the time we were adding triggers and more complex effects, the design didn't hold up very well.

3 more projects later, and I'm using a hybrid approach. I have a simple game loop function plus a special "ProcessManager" class which supports a more flexible way of registering new things to happen on each frame. That way, I can control the order in which the big nasty subsystems need to be updated, and my little one off effects can be dynamically added to the update path with very little hassle, if I'm willing to pay a small performance cost for them. It's working pretty well for us so far...

Basically, the ProcessManager just maintains a list of "BaseProcess" pointers, where BaseProcess has a virtual Update() function and an "ImDone" boolean which the ProcessManager checks every frame to kill the Processes off. Child classes to BaseProcess just need to know to set ImDone to true to have the ProcessManager clean them up. Any subsystem that wants to register new processes can take the ProcessManager in as a parameter and add processes to it. Then the hardcoded Update loop can decide when the process manager will actually get Updated, thereby controlling when all the Processes will be Updated. The solution isn't rock solid (handling parameters to the BaseProcess Update() function is a bit of a dirty Hack) but with some care, we're getting most of the benefits of both worlds - easily traceability on the big complex subsystems, and dynamism on small simple event-type processes. A huge benefit is integration with our level tool - designers can place level-specific processes using our level building tool, and out ProcessManager can create them when the level is loaded, but we still get to code most of the "techy" parts of the game in a straight procedural fashion.

Uh... sorry if that got longwinded. Hope it helps, though! :)
...
There are good ideas here, but beware making things too complicated. This approach can become like obsessing over making the best possible glue, nails, etc. without caring about your building materials. The important thing is to make sure that the functionality of your game is properly organized (factored) so that you can drop it into either a plain or a fancy game loop depending.

In particular, pay attention to making sure that updating and rendering logic are separated. You should be able to redraw everything, or any given thing, on the screen without changing the "state" of any of the objects. (In C++, the compiler can help you a little here, if you take advantage of the 'const' keyword.)

This topic is closed to new replies.

Advertisement