I needed to add a smiley logic for the agents I have been developing at work. I planned smiley logic to manage itself, like a part of our brains. It was still going to be a part of AI, but the AI was not going to monitor and manage it closely as it does for the agent movement for example; an autonomous system in other words…
So, I designed an event-driven subsystem for the smiley logic. I added a SmileyManager to handle the events thrown by the game manager (someone scored, end of the game, beginning of the game, etc.), manage the time for the smileys by controlling SmileyScheduler, and forward the smiley requirements to the renderer via the game manager.
But there was one important point that I had to consider: Having a realistic agent behaviour. And you know, if you want to have a realistic agent, you have to be careful what your agent does. Which means that you cannot have an agent that can send smileys under unrealistic conditions, such as while jumping or shooting, or it should not send one smiley another, and so on.
And actually this is the story how SmileyScheduler and SmileyTelegram (or messaging system in general) became a part of the story. In the end, SmileyScheduler became a layer between SmileyManager and SmileyLogic.
So, when the logic finds out that the agent should send a smiley, it creates a telegram (SmileyTelegram), and sends it to SmileyScheduler. SmileyScheduler keeps all the smiley telegrams (smiley requirements in a sense) in the order of priority, be sure that all telegrams in the list is still valid, and when the agent is available and the time is right, the scheduler makes the request from SmileyManager.
So, all these lines are basically motivation behind and the summary of how the system works. You can find more detail below, or on my GitHub page.
C++ Callbacks
There are many ways to handle callbacks in C++, but I prefer to use std::function polymorphic function wrapper with the help of std::bind. Because, the function wrapper makes the things very straight forward. You just need to keep a std::vector of the callbacks and when the time arrives, iterate through the vector and call the callbacks.
Here are some code pieces that handle adding the callbacks into the vectors and call them when the event is sent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class SmileyManager { public: // OMITTED CODE // The method that is called every frame (i.e. update() in Unity) void callOnFrame(const float pDeltaTime); // Add a SmileyEvent callback void addSmileyEventCallback( SmileyLogic* const object, void(SmileyLogic::* const func)(SmileyEvent, int) ); // Add a callOnFrame() callback void addFrameCallback( SmileyLogic* const object, void(SmileyLogic::* const func)(const float) ); // Call for the SmileyEvent void callForSmileyEvents(SmileyEvent pEvent, int pIndex = -1); private: // OMITTED CODE // Vector of callbacks for handling SmileyEvent std::vector<std::function<void(SmileyEvent, int)>> mSmileyCallbacks; // Vector of callbacks for handling the new frame std::vector<std::function<void(const float)>> mFrameCallbacks; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// Meanwhile in SmileyManager.cpp // Call the registered callbacks to let them it is time for the new frame! void SmileyManager::callOnFrame(const float pDeltaTime) { for(const auto& cb : mFrameCallbacks) { cb(pDeltaTime); } } // Add a callback for SmileyEvent handling void SmileyManager::addSmileyEventCallback(SmileyLogic *const object, void (SmileyLogic::*const func)(SmileyEvent, int)) { using namespace std::placeholders; mSmileyCallbacks.emplace_back( std::bind(func, object, _1, _2) ); } // Add a callback for handling the callOnFrame() void SmileyManager::addFrameCallback(SmileyLogic *const object, void (SmileyLogic::*const func)(const float)) { using namespace std::placeholders; mFrameCallbacks.emplace_back( std::bind(func, object, _1) ); } // Call the registered callbacks to let them SmileyEvent happened void SmileyManager::callForSmileyEvents(SmileyEvent pEvent, int pIndex) { for(const auto& cb : mSmileyCallbacks) { cb(pEvent, pIndex); } } |
You can also check SmileyLogic to see how it registers itself to SmileyManager for receiving callOnFrame() calls.
Messaging System
When SmileyLogic feels that it needs to send a smiley, it prepares a SmileyTelegram and asks SmileyScheduler to handle it. The situation is like giving a package to a shipping company now. After you give the package, it is out of your control. The company makes a schedule, ships it, makes the package arrive to the desired destination, or magically get lost on the road. It is same for SmileyScheduler too. After it receives a SmileyTelegram, it puts the telegram into a priority list (std::set in our case), and handles the list in its callOnFrame() method. It removes the invalid telegrams from the list, or makes the request if the agent is in a situation that it can send a smiley.
I am not sharing any piece of code in here since it will be long. But, you can see it on my GitHub page: GitHub/mcihanozer/C++_Callbacks_N_Messaging_System