1+ /* !
2+ \file
3+ \brief Implementation of simple thread-safe event processing and notifying system
4+
5+ \details Will be useful when you need a system which allows you to handle events with calling the subscribed callbacks.
6+ */
7+
18#pragma once
29
310#include " xrCommon/xr_smart_pointers.h"
613#include " xrCore/Threading/Lock.hpp"
714#include " xrCore/Threading/ScopeLock.hpp"
815
16+ /* !
17+ \brief Base abstract class for implementing event handling callbacks
18+
19+ \details Derive it and override @ref ProcessEvent method, then register the instanciated callback into the event
20+ notifier. The @ref ProcessEvent method will be called by the notifier every time when the selected event happens
21+ */
922class CEventNotifierCallback
1023{
1124public:
25+ /* ! Callback ID, type for identifying registered callbacks in the notifier */
1226 using CID = size_t ;
27+
28+ /* ! Invalid callback ID, will be never returned after successful subscription */
1329 static const CID INVALID_CID = std::numeric_limits<CID>::max();
1430
31+ /* ! This method will be automatically called by the notifier for processing the event */
1532 virtual void ProcessEvent () = 0;
33+
1634 virtual ~CEventNotifierCallback (){};
1735};
1836
37+ /* !
38+ \brief Abstract class for event processing callback which stores own callback ID
39+
40+ \details Designed to use primarily in pair with @ref CEventNotifier::CreateRegisteredCallback method,
41+ very useful for self-unsubscribing callbacks
42+ */
43+ class CEventNotifierCallbackWithCid : public CEventNotifierCallback
44+ {
45+ private:
46+ const CID m_cid;
47+
48+ public:
49+ /* ! Constructor, takes callback ID which was generated by the notifier
50+ /param[in] cid callback ID, should be generated by the notifier in subscription process only
51+ */
52+ CEventNotifierCallbackWithCid (CID cid) : m_cid(cid), CEventNotifierCallback(){};
53+
54+ /* ! Returns the callback ID which was generated by the notifier and passed to the constructor */
55+ CID GetCid () const { return m_cid; }
56+ };
57+
58+ /* ! \brief Template class for the event processing dispatcher
59+ \details Manages with subscribing, calling and unsubscribing handlers. Template parameter CNT is a count of events
60+ which we are going to process using the dispatcher. Owns the event's handling callbacks and controls its lifetime.
61+ */
1962template <unsigned int CNT>
2063class CEventNotifier
2164{
@@ -41,6 +84,14 @@ class CEventNotifier
4184 xr_vector<CCallbackWrapper> m_callbacks;
4285 Lock m_lock;
4386
87+ CEventNotifierCallback::CID FindFreeCid ()
88+ {
89+ ScopeLock lock (&m_lock);
90+ auto it = std::find (m_callbacks.begin (), m_callbacks.end (), nullptr );
91+ return (it == m_callbacks.end ()) ? CEventNotifierCallback::INVALID_CID :
92+ std::distance (m_callbacks.begin (), it);
93+ }
94+
4495 public:
4596 CEventNotifierCallback::CID RegisterCallback (CEventNotifierCallback* cb)
4697 {
@@ -50,6 +101,28 @@ class CEventNotifier
50101 (it->callback .reset (cb), std::distance (m_callbacks.begin (), it));
51102 }
52103
104+ template <class CB , class ... Args>
105+ CEventNotifierCallback::CID CreateRegisteredCallback (Args&&... args)
106+ {
107+ static_assert (std::is_base_of<CEventNotifierCallbackWithCid, CB>::value);
108+
109+ ScopeLock lock (&m_lock);
110+
111+ auto cid = FindFreeCid ();
112+ CB* cb = new CB ((cid == CEventNotifierCallback::INVALID_CID) ? m_callbacks.size () : cid, args...);
113+
114+ if (cid == CEventNotifierCallback::INVALID_CID)
115+ {
116+ m_callbacks.emplace_back (cb);
117+ }
118+ else
119+ {
120+ m_callbacks[cid].callback .reset (cb);
121+ }
122+
123+ return cb->GetCid ();
124+ }
125+
53126 bool UnregisterCallback (CEventNotifierCallback::CID cid)
54127 {
55128 bool result = false ;
@@ -94,18 +167,67 @@ class CEventNotifier
94167 xr_array<CCallbackStorage, CNT> m_callbacks;
95168
96169public:
170+ /* ! \brief Method for registering an existing event handler.
171+
172+ \details Use it to register in the notifier previously created event handling callback.
173+ \warning Please consider using @ref CreateRegisteredCallback method instead. When using the @ref
174+ RegisterCallback method you should register only the callbacks which were created by the other methods of the
175+ notifier. This restriction happens because the notifier owns the callbacks and destroyes it internally, so you
176+ can get some problems when creating the callbacks outside of the notifier (notifier will call improper
177+ destruction methods).
178+ \todo Provide the method for creating callbacks without the registration
179+ \param[in] cb pointer to
180+ the callback to register. After registration the notifier becomes an owner of the registered callback
181+ \param[in] event_id Index of the event type which the callback should process, must be lesser than the template
182+ parameter CNT
183+ \return Callback ID of the registered callback (unique for each event type)
184+ */
97185 CEventNotifierCallback::CID RegisterCallback (CEventNotifierCallback* cb, unsigned int event_id)
98186 {
99187 R_ASSERT (event_id < CNT);
100188 return m_callbacks[event_id].RegisterCallback (cb);
101189 }
102190
191+ /* ! \brief Template method which creates a new event handling callback and registers it in the notifier
192+
193+ \details Provides a convenient way for registering the event handlers. It's a preferred method for setting up
194+ the event handlers. The template parameter CB is a type of the event handling callback which we are going to
195+ create
196+
197+ \param[in] event_id Index of the event type which the callback should process, must be lesser than template
198+ parameter template parameter CNT
199+ \param[in] args Arguments which should be passed to the constructor of the callback
200+ \return Callback ID of the registered callback (unique for each event type)
201+ */
202+ template <class CB , class ... Args>
203+ CEventNotifierCallback::CID CreateRegisteredCallback (unsigned int event_id, Args&&... args)
204+ {
205+ R_ASSERT (event_id < CNT);
206+ return m_callbacks[event_id].CreateRegisteredCallback <CB>(args...);
207+ }
208+
209+ /* ! \brief Provides the way to unsubscribe and delete the callback
210+
211+ \details The callback will be automatically destroyed by the notifier after unsubscribing. However, the
212+ unsubscribing logic allows to unsubscribe the callback directly while processing the event. Callback won't be
213+ destroyed while its event handling method is executing.
214+
215+ \param[in] cid Callback ID which was returning by the subscribing function
216+ \param[in] event_id Index of the event type which the callback is subscribed to
217+ \return True if unsubscribing is successful, false otherwise
218+ */
103219 bool UnregisterCallback (CEventNotifierCallback::CID cid, unsigned int event_id)
104220 {
105221 R_ASSERT (event_id < CNT);
106222 return m_callbacks[event_id].UnregisterCallback (cid);
107223 }
108224
225+ /* ! \brief The method to notify the subscribed callbacks about the event
226+
227+ \details Call this method when the event happens. All the subscribed callbacks would be called one-by-one in the
228+ caller's thread.
229+ \param[in] event_id Index of the event type
230+ */
109231 void FireEvent (unsigned int event_id)
110232 {
111233 R_ASSERT (event_id < CNT);
0 commit comments