Skip to content

Commit 09a5d86

Browse files
authored
Merge pull request #272 from FreeZoneMods/xd_dev
Improve script reset notifying system
2 parents 59d9757 + fb67938 commit 09a5d86

File tree

8 files changed

+214
-25
lines changed

8 files changed

+214
-25
lines changed

src/utils/xrSE_Factory/ai_space.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,35 @@
1111
#include "xrScriptEngine/script_engine.hpp"
1212
#include "xrServerEntities/object_factory.h"
1313

14-
static CAI_Space g_ai_space;
14+
// Nifty counter required to solve initilization order problem
15+
// Please, do NOT initialize this static members directly, it breaks the initialization logic
16+
// And yes, according to the standard ($3.6.2/1): "Objects with static storage duration (3.7.1) shall be
17+
// zero-initialized (8.5) before any other initialization takes place" So, it must be automatically
18+
// initialized with zeros before executing the constructor
19+
static CAI_Space* s_ai_space;
20+
static u32 s_nifty_counter;
21+
22+
SAI_Space_Initializer::SAI_Space_Initializer()
23+
{
24+
if (s_nifty_counter++ == 0)
25+
{
26+
s_ai_space = new CAI_Space();
27+
}
28+
}
29+
30+
SAI_Space_Initializer::~SAI_Space_Initializer()
31+
{
32+
if (--s_nifty_counter == 0)
33+
{
34+
xr_delete(s_ai_space);
35+
}
36+
}
1537

1638
CAI_Space& CAI_Space::GetInstance()
1739
{
18-
auto& instance = g_ai_space;
40+
VERIFY(s_ai_space);
41+
42+
auto& instance = *s_ai_space;
1943
if (!instance.m_inited)
2044
{
2145
instance.init();

src/utils/xrSE_Factory/ai_space.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,24 @@ class CAI_Space
3434
EVENT_SCRIPT_ENGINE_RESET,
3535
EVENT_COUNT,
3636
};
37-
CEventNotifierCallback::CID Subscribe(CEventNotifierCallback* cb, EEventID event_id) { return 0; }
37+
38+
template <class CB, class... Args>
39+
CEventNotifierCallback::CID Subscribe(EEventID event_id, Args&&... args)
40+
{
41+
return 0;
42+
}
43+
3844
bool Unsubscribe(CEventNotifierCallback::CID cid, EEventID event_id) { return true; }
3945
};
4046

47+
// Static initializer for every translation unit
48+
// Need to avoid static initilization order problem
49+
static struct SAI_Space_Initializer
50+
{
51+
SAI_Space_Initializer();
52+
~SAI_Space_Initializer();
53+
} s_AISpaceInitializer;
54+
4155
IC CAI_Space& ai();
4256

4357
#include "ai_space_inline.h"

src/xrCore/Events/Notifier.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
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"
@@ -6,16 +13,52 @@
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+
*/
922
class CEventNotifierCallback
1023
{
1124
public:
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+
*/
1962
template <unsigned int CNT>
2063
class 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

96169
public:
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);

src/xrGame/MainMenu.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@ CMainMenu* MainMenu() { return (CMainMenu*)g_pGamePersistent->m_pMainMenu; };
6767

6868
CMainMenu::CMainMenu()
6969
{
70-
class CResetEventCb : public CEventNotifierCallback
70+
class CResetEventCb : public CEventNotifierCallbackWithCid
7171
{
7272
CMainMenu* m_mainmenu;
7373

7474
public:
75-
CResetEventCb(CMainMenu* mm) : m_mainmenu(mm) {}
75+
CResetEventCb(CID cid, CMainMenu* mm) : m_mainmenu(mm), CEventNotifierCallbackWithCid(cid) {}
7676
void ProcessEvent() override { m_mainmenu->DestroyInternal(true); }
7777
};
7878

79-
m_script_reset_event_cid = ai().Subscribe(new CResetEventCb(this), CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
79+
m_script_reset_event_cid = ai().Subscribe<CResetEventCb>(CAI_Space::EVENT_SCRIPT_ENGINE_RESET, this);
8080

8181
m_Flags.zero();
8282
m_startDialog = NULL;

src/xrGame/ai_space.cpp

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,35 @@
2222
#include "moving_objects.h"
2323
#include "doors_manager.h"
2424

25-
static CAI_Space g_ai_space;
25+
// Nifty counter required to solve initilization order problem
26+
// Please, do NOT initialize this static members directly, it breaks the initialization logic
27+
// And yes, according to the standard ($3.6.2/1): "Objects with static storage duration (3.7.1) shall be
28+
// zero-initialized (8.5) before any other initialization takes place" So, it must be automatically
29+
// initialized with zeros before executing the constructor
30+
static CAI_Space* s_ai_space;
31+
static u32 s_nifty_counter;
32+
33+
SAI_Space_Initializer::SAI_Space_Initializer()
34+
{
35+
if (s_nifty_counter++ == 0)
36+
{
37+
s_ai_space = new CAI_Space();
38+
}
39+
}
40+
41+
SAI_Space_Initializer::~SAI_Space_Initializer()
42+
{
43+
if (--s_nifty_counter == 0)
44+
{
45+
xr_delete(s_ai_space);
46+
}
47+
}
2648

2749
CAI_Space& CAI_Space::GetInstance()
2850
{
29-
auto& instance = g_ai_space;
51+
VERIFY(s_ai_space);
52+
53+
auto& instance = *s_ai_space;
3054
if (!instance.m_inited)
3155
{
3256
instance.init();
@@ -197,11 +221,6 @@ void CAI_Space::set_alife(CALifeSimulator* alife_simulator)
197221
SetGameGraph(nullptr);
198222
}
199223

200-
CEventNotifierCallback::CID CAI_Space::Subscribe(CEventNotifierCallback* cb, EEventID event_id)
201-
{
202-
return m_events_notifier.RegisterCallback(cb, event_id);
203-
}
204-
205224
bool CAI_Space::Unsubscribe(CEventNotifierCallback::CID cid, EEventID event_id)
206225
{
207226
return m_events_notifier.UnregisterCallback(cid, event_id);

src/xrGame/ai_space.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ class CAI_Space : public AISpaceBase
7373
virtual ~CAI_Space();
7474
static CAI_Space& GetInstance();
7575

76-
CEventNotifierCallback::CID Subscribe(CEventNotifierCallback* cb, EEventID event_id);
76+
template <class CB, class... Args>
77+
CEventNotifierCallback::CID Subscribe(EEventID event_id, Args&&... args)
78+
{
79+
return m_events_notifier.CreateRegisteredCallback<CB>(event_id, args...);
80+
}
81+
7782
bool Unsubscribe(CEventNotifierCallback::CID cid, EEventID event_id);
7883
void RestartScriptEngine();
7984

@@ -86,6 +91,14 @@ class CAI_Space : public AISpaceBase
8691
IC doors::manager& doors() const;
8792
};
8893

94+
// Static initializer for every translation unit
95+
// Need to avoid static initilization order problem
96+
static struct SAI_Space_Initializer
97+
{
98+
SAI_Space_Initializer();
99+
~SAI_Space_Initializer();
100+
} s_AISpaceInitializer;
101+
89102
IC CAI_Space& ai();
90103

91104
#include "ai_space_inline.h"

src/xrGame/ui/UIWpnParams.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,19 @@ void CUIWpnParams::SetInfo(CInventoryItem* slot_wpn, CInventoryItem& cur_wpn)
108108
{
109109
if (!g_lua_wpn_params)
110110
{
111-
class CResetEventCb : public CEventNotifierCallback
111+
class CResetEventCb : public CEventNotifierCallbackWithCid
112112
{
113113
public:
114-
CID m_cid = INVALID_CID;
115-
114+
CResetEventCb(CID cid) : CEventNotifierCallbackWithCid(cid) {}
116115
void ProcessEvent() override
117116
{
118117
xr_delete(g_lua_wpn_params);
119-
ai().Unsubscribe(m_cid, CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
118+
ai().Unsubscribe(GetCid(), CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
120119
}
121120
};
122121

123122
g_lua_wpn_params = new SLuaWpnParams();
124-
auto cb = new CResetEventCb();
125-
cb->m_cid = ai().Subscribe(cb, CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
123+
ai().Subscribe<CResetEventCb>(CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
126124
}
127125

128126
LPCSTR cur_section = cur_wpn.object().cNameSect().c_str();

src/xrServerEntities/object_factory_inline.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,19 @@ IC const CObjectFactory& object_factory()
1818
g_object_factory = new CObjectFactory();
1919
g_object_factory->init();
2020

21-
class CResetEventCb : public CEventNotifierCallback
21+
class CResetEventCb : public CEventNotifierCallbackWithCid
2222
{
2323
public:
24-
CID m_cid;
24+
CResetEventCb(CID cid) : CEventNotifierCallbackWithCid(cid) {}
2525

2626
void ProcessEvent() override
2727
{
2828
xr_delete(g_object_factory);
29-
ai().Unsubscribe(m_cid, CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
29+
ai().Unsubscribe(GetCid(), CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
3030
}
3131
};
3232

33-
CResetEventCb* e = new CResetEventCb();
34-
e->m_cid = ai().Subscribe(e, CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
33+
ai().Subscribe<CResetEventCb>(CAI_Space::EVENT_SCRIPT_ENGINE_RESET);
3534
}
3635
return (*g_object_factory);
3736
}

0 commit comments

Comments
 (0)