Okay, this is going to get very technical.
I've been working for several days on a new Event Manager. Event manager is very important because it helps me decouple stuff and optimize perfomance a lot.
Previously I had a simple subject/observer system which let observers to subscribe to subjects and provide a callback function which was called if the subject sent an event. But it had some issues: messages could only be sent immediately and for some object to become observer/subject its class should inherit IObserver/ISubject classes. This caused lots of recompilation each time I realized that some object needed to become an observer or a subject and adding new events when they were already sent caused some troubles too.
So I've decided to completely remake event system and add some cool features.
I've made an event queue and got rid of IObserver/ISubject classes.
Each time something needs to send an event, it adds it to the global event queue.
If some entity subscribes to an event of this type, its callback is called during Event Manager sendMessages() function
Here's how objects can subscribe to events:
SomeClass obj;
engine_system.eventM->addListener<SomeEvent>(&obj, &SomeClass::onSomeEvent);
And here's how they can send them:
auto event = std::make_shared<SomeEvent>();
event->someParam = someValue;
engine_system.eventM->queueEvent(event);
I can later get event's parameters by casting them in a callback which is called during EventManager's sendMessages() function:
void SomeObj::onSomeEvent(const std::shared_ptr<Event>& e) {
auto event = std::dynamic_pointer_cast<SomeEvent>(e); // this is totally a SomeEvent, because this function is called
// only when e has this type
std::cout << event->someParam;
}
Callbacks are implemented using std::function and std::bind and here's how they should look:
void Class::CallbackFunc(const std::shared_ptr<Event>& e)
Objects can also subscribe to certain entities to receive events only from them. (Objects receive messages from everyone by default)
For example, GUIPlayingState only needs to know when player's hp changes, so it adds a playerEntity as a subject:
engine_system.eventM->addListener<HpChangedEvent>(&gui, &GUIPlayerState::onPlayerHpChanged);
engine_system.eventM->addSubjectToListener<HpChangedEvent>(&gui, playerEntity);
But it doesn't stop here!
I've implemented
Lua/C++ event manager too!
Previously, if I needed to observe another entity's state, I called update() entity function in every frame. For example, if a bridge is lowered when a button is pressed, this function was called:
bridge.update = function(bridgeEntity)
local button = getEntityByTag("BRIDGE_BUTTON")
if(getScriptState(button) == "ButtonPressedState") then
setScriptState(bridgeEntity, "BridgeLoweredState")
end
end
This is obviously not a great solution, because calling this function 60 times per second may be expensive. It also sucks that I need to check button's state every frame and this may be very expensive too. Especially if lots of entities have their own update functions.
Event manager fixes that problem by calling a callback function only when the button's state changes.
Entities can add callbacks in Lua this way:
addLuaListener(entity, "SomeEvent", func) -- func is some Lua function
They can also subscribe to entities:
subscribeToSubject(entity, "SomeEvent", subjectEntity)
Each time an event is queued in C++ or Lua, both C++ and Lua callbacks are called. And this it very cool, because it makes communication between Lua and C++ even simpler than before.
Lua callbacks get an event table which can be used to get parameters:
f = function(event)
print(event.someParam)
end
So, here's how the bridge can subscribe to the button:
addLuaListener(bridge, "ScriptStateChangedEvent", bridge.onBridgeButtonPressed)
subscribeToSubject(bridge, "ScriptStateChangedEvent", button)
And here's how a callback looks like:
onBridgeButtonPressed = function(this, event) -- this function is called every time button entity changes its state
if (event.currentStateName == "ButtonPressedState") then
setScriptState(bridge, "BridgeLoweredState")
end
end
In C++ ScriptStateComponent emits ScriptStateChangedEvent everytime a state is changed. Then a callback Lua function gets called. This works!
The implementation is pretty difficult and required lots of template magic, error checking and optimization, so it would take me far too long to write about everything. Ask me any questions and I'll try to answer them. Maybe you have your own cool Event Manager: tell me about it!