0
\$\begingroup\$

I'm working on my game engine, and was faced with the fact, that i don't know how properly design an event system. As a pattern i choose Pub/Sub with an event bus, but i don't know where i should 'place' bus. I have the main engine class, called RubyApp, which connect all engine systems. I was thinking of placing an event bus inside a Window class, which is responsible(unexpectedly!:)) for the main game window, and in Window constructor pass pointer to bus to glfwSetWindowUserPointer(). But I have doubts about this idea, as well as about making EventBus fields and methods static

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Is the event bus for internal use in the engine, is it a service provided to the games, or both? \$\endgroup\$
    – Theraot
    Commented Oct 5, 2023 at 18:33
  • \$\begingroup\$ @Theraot it's for internal use only \$\endgroup\$
    – qulop
    Commented Oct 6, 2023 at 0:06

1 Answer 1

0
\$\begingroup\$

i don't know where i should 'place' bus

Being fully internal, it should be part of the core of the engine, and no particular place beyond that.

You could have one single event bus with multiple events, in which case it can be static/singleton/global. It is also possible to have multiple buses, for different purposes, which does not preclude an static/singleton/global façade from where you can get the different instances.

It is more interesting to consider who is responsible for connecting it.

We have three options:

  • Subsystems do not know about the event bus: For receive messages, the code that construct the subsystems, can also make the connections so the subsystems do not even need to be aware of them. To send messages, they are going to need some handle to the event bus that you pass on construction, so the subsystems can use it to send messages.
  • Subsystems do about the event bus: so they can connect themselves, and the code that constructs them is simpler. Plus they can directly call into the event bus to send messages.
  • A mix of both.

I'm aware that the common advice against static/singletons/global is that changes to them propagate to everything (they are a hidden dependency). So, if we are worried about changes in the event bus, the former approach makes some sense.

However, being internal, the event bus would only be changing because a change to its interface is necessary. Plus I do not expect that update the code that uses it to be too complicated.

Furthermore, the purpose of the event bus is to allow subsystems to communicate using it, so that they not need to worry if there is code handling the messages on the other side. Which means that as long as the subsystems use the event bus, they can change independently of each other. That it, they would be fully decoupled.

Thus, my suggestion is to let subsystems access the event bus directly.

Yet please notice that by using the event bus for communication between subsystems, they lose the ability to give a result right away. Thus, I do not believe all your subsystems need to be fully decoupled. If it make sense to couple subsystems, couple them.


I was thinking of placing an event bus inside a Window class

I would make the event bus separate from the Window, avoiding to make the Window code more complex than necessary.

The fact that you want it in the Window class makes me think that you are thinking of the message loop. You, of course, want a layer of abstraction on top of it... Which, GLFW already gives you, and it allows you to connect callbacks for multiple events. The callbacks might send messages over the event bus if that is necessary.

You might also wonder if you need to protect your code from changes in GLFW. However, GLFW will not change beneath you. If it changes, it is because you have decided to update it. However, it makes sense to limit the propagation of those changes by keeping all the GLFW specific code in a single place, so if you need to update GLFW, you know you only need to change code there.


how properly design an event system

An event bus should allow to:

  • Add and remove listeners for a particular "event". Which means there will be a list of connections (which you might pool).

  • Send messages for a particular "event" which all listeners of that "event" would receive.

    For your consideration: A feature that you might find useful is the ability to schedule a message for an specific frame. Which also requires a method to tell the event bus which is the current frame (which you would call from the game loop), so it dispatched scheduled messages.

    Even if you do not schedule messages, it might still be a good idea to not dispatch them right away, but dispatch them at a dedicated time using a call from the game loop.

I'm confident you can find plenty of examples.

However, given that this is for internal use, I suspect the primary usage would be commands. Thus, so I submit to your consideration: channels. A subsystem would create a channel, and only the subsystem that created will be listening on it. Other subsystems can then get the channel (so they must be accesible from anywhere), and use it to send messages to that particular subsystem. Thus you would not have API to add and removing listeners. It also means that the subsystem might poll messages, instead of being called.

\$\endgroup\$
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .