CryListenerSet.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. // Description : A simple, intelligent and efficient container for listeners.
  9. // This is designed to provide a simple & consistent interface and behavior
  10. // for adding, removing and iterating listeners - hopefully avoiding the
  11. // common pitfalls such as duplicated elements, invalid iterators and
  12. // dangling pointers.
  13. #ifndef CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H
  14. #define CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H
  15. #pragma once
  16. #include "Cry_Math.h"
  17. #include <StlUtils.h>
  18. /************************************************************************
  19. Core elements:
  20. * CListenerSet<T> - The collection of listeners.
  21. * CListenerSet<T>::Notifier - The iterator for safely calling listeners in sequence.
  22. [Where T is a pointer or something with pointer traits]
  23. Advantages:
  24. * Greatly reduces the complexity of managing listener collections.
  25. * Can safely add and remove listeners during listener iteration.
  26. * Automatically and safely removes NULL elements.
  27. * Simple interface (but different from an STL collection to avoid confusion).
  28. * Checks to see all listeners have been removed at destruction.
  29. * Low overhead implementation (cheap use of std::vector and minimal heap allocation).
  30. * Can add debug checks as needed - including stack tracing of Add() calls.
  31. * Safe for recursive notification chains.
  32. * Works with vanilla pointers and smart pointers.
  33. * Provides full support for named listeners to aid debugging.
  34. * Listener names tracked during notification to help resolve crashes.
  35. * Designed to ensure names are recorded correctly in crash dump files.
  36. Named listener support:
  37. Supplying a name for a listener provides valuable debug information in the following cases:
  38. * Resolving crashes during listener notification - the name will help trace what listener caused the crash.
  39. * Resolving which listeners are present in the CListenerSet during runtime.
  40. IMPORTANT: Please ensure heap allocated strings passed in as names are marked as such ie.
  41. m_listeners.Add(pListener, "MyListener"); // OK: Static string passed.
  42. m_listeners.Add(pListener2, m_myName.c_str(), false); // OK: Heap string passed and marked as non-static.
  43. m_listeners.Add(pListener3, m_myName.c_str(), true); // BAD: Heap string passed and marked as static (potential CRASH).
  44. Why store names like this?
  45. * 99% of use cases use static strings - so why allocate memory to make copies of static data?
  46. * Pointers to static strings will *always* survive crash dumps - great for debugging crashes.
  47. * We can enable debug support in QA builds - key for catching rare listener related crashes.
  48. Example:
  49. class CMyWorld
  50. {
  51. public:
  52. void AddListener(IMyWorldListener* pListener, const char* szName) { m_listeners.Add(pListener, szName); }
  53. void RemoveListener(IMyWorldListener* pListener) { m_listeners.Remove(pListener); }
  54. void NotifyListeners(CSomeEvent& event)
  55. {
  56. for (TWorldListeners::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next())
  57. {
  58. notifier->OnWorldEvent(event);
  59. }
  60. }
  61. private:
  62. typedef CListenerSet<IMyWorldListener*> TWorldListeners;
  63. TWorldListeners m_listeners;
  64. };
  65. // Implements IMyWorldListener
  66. CMyWorldUser::OnWorldEvent(CSomeEvent& event)
  67. {
  68. // OK: Removing a listeners within an event handler
  69. m_pWorld->RemoveListener(this);
  70. // OK: Notifying listeners within an event handler
  71. m_pWorld->NotifyListeners(CSomeEvent newEvent(USER_REMOVED, this));
  72. }
  73. *************************************************************************/
  74. #ifndef _RELEASE
  75. #define CRY_LISTENERSET_DEBUG
  76. #endif
  77. // Forward decl.
  78. template <typename T>
  79. class CListenerNotifier;
  80. // Main listener collection class used in conjunction with CListenerNotifier.
  81. template <typename T>
  82. class CListenerSet
  83. {
  84. public:
  85. // NOTE: No default constructor in favor of forcing users to provide an expectedCapacity
  86. inline CListenerSet(size_t expectedCapacity);
  87. inline /*non-virtual*/ ~CListenerSet();
  88. // Appends a listener to the end of the collection. Name is optional but recommended.
  89. inline bool Add(T pListener, const char* name = NULL, bool staticName = true);
  90. // Removes a listener from the collection.
  91. inline void Remove(T pListener);
  92. // Removes all listeners from the collection (NOTE: prefer informing listeners to remove themselves)
  93. inline void Clear(bool bFreeMemory = false);
  94. // Returns true if this contains pListener
  95. inline bool Contains(T pListener) const;
  96. // Returns number of valid listeners
  97. inline size_t ValidListenerCount() const;
  98. // Returns true if no valid listeners exist
  99. inline bool Empty() const;
  100. // Reserves space to help avoid runtime reallocation
  101. inline void Reserve(size_t capacity);
  102. // Returns the memory size of this object (to support CrySizer)
  103. inline size_t MemSize() const;
  104. // Returns true if currently in the process of notifying listeners.
  105. inline bool IsNotifying() const;
  106. // Allow access for Notifier for iteration
  107. friend class CListenerNotifier<T>;
  108. // Allow TListeners::Notifier style usage
  109. typedef class CListenerNotifier<T> Notifier;
  110. private: // DO NOT REMOVE - following methods only to be accessed only via CNotifier
  111. struct ListenerRecord
  112. {
  113. ListenerRecord()
  114. : m_pListener() {}
  115. ListenerRecord(T pListener, [[maybe_unused]] const char* szName = NULL)
  116. : m_pListener(pListener)
  117. #ifdef CRY_LISTENERSET_DEBUG
  118. , m_szName(szName)
  119. #endif
  120. {}
  121. bool operator==(const ListenerRecord& other) const { return m_pListener == other.m_pListener; }
  122. bool operator==(const T& other) const { return m_pListener == other; }
  123. T m_pListener; // The listener reference
  124. #ifdef CRY_LISTENERSET_DEBUG
  125. const char* m_szName; // Name of tracked listener (owned if pointing to data in m_allocatedNames)
  126. #endif
  127. };
  128. typedef std::vector<ListenerRecord> TListenerVec;
  129. typedef std::vector<AZStd::string> TAllocatedNameVec;
  130. inline void StartNotificationScope();
  131. inline void EndNotificationScope();
  132. inline void EraseNullElements();
  133. private:
  134. TListenerVec m_listeners; // Collection of unique listeners.
  135. size_t m_activeNotifications; // Counts current notifications in progress (cleanup cannot occur unless this is 0).
  136. bool m_cleanupRequired; // Indicates NULL elements in listener.
  137. bool m_freeMemOnCleanup; // Indicates how to clean up.
  138. #ifdef CRY_LISTENERSET_DEBUG
  139. // Used to delete heap allocated names
  140. inline void DeleteName(const char* name);
  141. TAllocatedNameVec m_allocatedNames; // Collection of strings pointing at heap allocated (i.e. copied) names (typically empty)
  142. #endif
  143. };
  144. // Helper class used to iterate listeners during listener notification.
  145. template <typename T>
  146. class CListenerNotifier
  147. {
  148. public:
  149. ILINE CListenerNotifier(CListenerSet<T>& listeners);
  150. ILINE /*non-virtual*/ ~CListenerNotifier();
  151. // True if the current element is ready for iteration
  152. ILINE bool IsValid();
  153. // Dereference current listener, MUST only be done after a call to IsValid().
  154. ILINE T operator->();
  155. // Dereference current listener, MUST only be done after a call to IsValid().
  156. ILINE T operator*();
  157. // Move to next valid listener (skipping NULL elements)
  158. ILINE void Next();
  159. // Returns the name of the listener (if available)
  160. inline const char* Name() const;
  161. private:
  162. CListenerSet<T>& m_listenerSet; // ListenerSet being notified
  163. T m_pListener; // Current listener at index (resolved by IsValid(), cleared after each dereference)
  164. size_t m_index; // Current index of element (incremented by next)
  165. #ifdef CRY_LISTENERSET_DEBUG
  166. const char* m_szName; // Name of the listener (if provided) to aid debugging
  167. #endif
  168. };
  169. /******************************************************************************************/
  170. template <typename T>
  171. inline CListenerSet<T>::CListenerSet(size_t expectedCapacity)
  172. : m_activeNotifications(0)
  173. , m_cleanupRequired(false)
  174. , m_freeMemOnCleanup(false)
  175. {
  176. // Reserve the expected capacity to avoid reallocations
  177. m_listeners.reserve(expectedCapacity);
  178. }
  179. template <typename T>
  180. inline CListenerSet<T>::~CListenerSet()
  181. {
  182. // Ensure no notifications are in progress
  183. CRY_ASSERT(m_activeNotifications == 0);
  184. // Ensure NULL elements were removed at end of last notification
  185. CRY_ASSERT(!m_cleanupRequired);
  186. }
  187. // Appends a listener to the end of the collection. Name is optional but recommended.
  188. template <typename T>
  189. inline bool CListenerSet<T>::Add(T pListener, const char* name, [[maybe_unused]] bool staticName)
  190. {
  191. bool success = false;
  192. // Ensure the listener exists
  193. CRY_ASSERT(pListener);
  194. if (pListener)
  195. {
  196. // Ensure the listener is only added once
  197. if (!Contains(pListener))
  198. {
  199. // Resolve name buffer safe for usage outside of this scope
  200. const char* safeName = name;
  201. #ifdef CRY_LISTENERSET_DEBUG
  202. // If a name was provided but it's not static data
  203. if (name && !staticName)
  204. {
  205. // Add it to the list of heap allocated names (that we need to later delete)
  206. m_allocatedNames.push_back(name);
  207. safeName = m_allocatedNames.back().c_str();
  208. }
  209. #endif
  210. m_listeners.push_back(ListenerRecord(pListener, safeName));
  211. success = true;
  212. }
  213. }
  214. return success;
  215. }
  216. // Removes a listener from the collection.
  217. template <typename T>
  218. inline void CListenerSet<T>::Remove(T pListener)
  219. {
  220. typename TListenerVec::iterator endIter(m_listeners.end());
  221. typename TListenerVec::iterator iter(std::find(m_listeners.begin(), endIter, pListener));
  222. if (iter != endIter)
  223. {
  224. #ifdef CRY_LISTENERSET_DEBUG
  225. // Delete name if it was heap allocated
  226. if (const char* name = iter->m_szName)
  227. {
  228. DeleteName(name);
  229. }
  230. #endif
  231. // If no notifications in progress
  232. if (m_activeNotifications == 0)
  233. {
  234. // Just delete the listener entry immediately
  235. m_listeners.erase(iter);
  236. }
  237. else // Notification(s) in progress, cannot re-order listeners
  238. {
  239. // Mark for cleanup
  240. iter->m_pListener = NULL;
  241. m_cleanupRequired = true;
  242. m_freeMemOnCleanup = false;
  243. }
  244. }
  245. else // The listener is not in the set
  246. {
  247. // TODO: Warn about redundant Remove()
  248. }
  249. }
  250. // Removes all listeners from the collection (NOTE: prefer informing listeners to remove themselves)
  251. template <typename T>
  252. inline void CListenerSet<T>::Clear(bool bFreeMemory)
  253. {
  254. // If no notifications in progress
  255. if (m_activeNotifications == 0)
  256. {
  257. // Simply clear the listeners immediately
  258. if (bFreeMemory)
  259. {
  260. stl::free_container(m_listeners);
  261. }
  262. else
  263. {
  264. m_listeners.clear();
  265. }
  266. }
  267. else
  268. {
  269. // Mark all listeners for cleanup
  270. std::fill(m_listeners.begin(), m_listeners.end(), ListenerRecord());
  271. m_cleanupRequired = true;
  272. m_freeMemOnCleanup = true;
  273. }
  274. #ifdef CRY_LISTENERSET_DEBUG
  275. // Safe to clear allocated names immediately (no references exist any more)
  276. if (bFreeMemory)
  277. {
  278. stl::free_container(m_allocatedNames);
  279. }
  280. else
  281. {
  282. m_allocatedNames.clear();
  283. }
  284. #endif
  285. }
  286. // Returns true if this contains pListener
  287. template <typename T>
  288. inline bool CListenerSet<T>::Contains(T pListener) const
  289. {
  290. return stl::find(m_listeners, pListener);
  291. }
  292. // Returns number of valid listeners
  293. template <typename T>
  294. inline size_t CListenerSet<T>::ValidListenerCount() const
  295. {
  296. size_t validCount = m_listeners.size();
  297. if (m_cleanupRequired)
  298. {
  299. // Remove the count of NULL elements from the result
  300. validCount = validCount - std::count(m_listeners.begin(), m_listeners.end(), T());
  301. }
  302. return validCount;
  303. }
  304. // Returns true if no valid listeners exist
  305. template <typename T>
  306. inline bool CListenerSet<T>::Empty() const
  307. {
  308. return ValidListenerCount() == 0;
  309. }
  310. // Reserves space to help avoid runtime reallocation
  311. template <typename T>
  312. inline void CListenerSet<T>::Reserve(size_t capacity)
  313. {
  314. m_listeners.reserve(capacity);
  315. }
  316. // Returns the memory size of this object (to support CrySizer)
  317. template <typename T>
  318. inline size_t CListenerSet<T>::MemSize() const
  319. {
  320. size_t size = sizeof(CListenerSet<T>) + sizeof(ListenerRecord) * m_listeners.size();
  321. #ifdef CRY_LISTENERSET_DEBUG
  322. size += sizeof(typename TAllocatedNameVec::value_type);
  323. for (typename TAllocatedNameVec::const_iterator iter(m_allocatedNames.begin()); iter != m_allocatedNames.end(); ++iter)
  324. {
  325. size += iter->capacity() * sizeof(char) + sizeof(AZStd::string);
  326. }
  327. #endif
  328. return size;
  329. }
  330. template <typename T>
  331. inline bool CListenerSet<T>::IsNotifying() const
  332. {
  333. return m_activeNotifications > 0;
  334. }
  335. template <typename T>
  336. inline void CListenerSet<T>::StartNotificationScope()
  337. {
  338. ++m_activeNotifications;
  339. }
  340. template <typename T>
  341. inline void CListenerSet<T>::EndNotificationScope()
  342. {
  343. // Ensure at least one notification scope was started
  344. CRY_ASSERT(m_activeNotifications > 0);
  345. // If this is the last notification
  346. if (--m_activeNotifications == 0)
  347. {
  348. EraseNullElements();
  349. }
  350. }
  351. template <typename T>
  352. inline void CListenerSet<T>::EraseNullElements()
  353. {
  354. // Ensure no modification while notification(s) are ongoing
  355. CRY_ASSERT(m_activeNotifications == 0);
  356. if (m_cleanupRequired && m_activeNotifications == 0)
  357. {
  358. stl::find_and_erase_all(m_listeners, T());
  359. if (m_freeMemOnCleanup && m_listeners.empty())
  360. {
  361. stl::free_container(m_listeners);
  362. }
  363. m_cleanupRequired = false;
  364. m_freeMemOnCleanup = false;
  365. }
  366. }
  367. #ifdef CRY_LISTENERSET_DEBUG
  368. // Used to delete heap allocated names
  369. template <typename T>
  370. inline void CListenerSet<T>::DeleteName(const char* name)
  371. {
  372. if (!m_allocatedNames.empty())
  373. {
  374. typename TAllocatedNameVec::iterator endIter(m_allocatedNames.end());
  375. for (typename TAllocatedNameVec::iterator iter(m_allocatedNames.begin()); iter != endIter; ++iter)
  376. {
  377. // Is this the source string?
  378. if (iter->c_str() == name)
  379. {
  380. // Delete it
  381. m_allocatedNames.erase(iter);
  382. break;
  383. }
  384. }
  385. }
  386. }
  387. #endif // defined CRY_LISTENERSET_DEBUG
  388. /******************************************************************************************/
  389. template <typename T>
  390. ILINE CListenerNotifier<T>::CListenerNotifier(CListenerSet<T>& listeners)
  391. : m_listenerSet(listeners)
  392. , m_pListener()
  393. , m_index(0)
  394. #ifdef CRY_LISTENERSET_DEBUG
  395. , m_szName()
  396. #endif
  397. {
  398. // Flag iteration to listener set to ensure no erase is attempted during iteration
  399. m_listenerSet.StartNotificationScope();
  400. // If first element is NULL, move to next valid element
  401. if (!IsValid())
  402. {
  403. Next();
  404. }
  405. }
  406. template <typename T>
  407. ILINE CListenerNotifier<T>::~CListenerNotifier()
  408. {
  409. // Erases any NULL elements from listeners
  410. m_listenerSet.EndNotificationScope();
  411. }
  412. // True if the current element is ready for iteration
  413. template <typename T>
  414. ILINE bool CListenerNotifier<T>::IsValid()
  415. {
  416. if (!m_pListener)
  417. {
  418. // Always check with original collection
  419. if (m_index < m_listenerSet.m_listeners.size())
  420. {
  421. const typename CListenerSet<T>::ListenerRecord & record(m_listenerSet.m_listeners[m_index]);
  422. m_pListener = record.m_pListener;
  423. #ifdef CRY_LISTENERSET_DEBUG
  424. m_szName = record.m_szName;
  425. #endif
  426. }
  427. }
  428. return m_pListener != NULL;
  429. }
  430. // Dereference current listener, MUST only be done after a call to IsReady().
  431. template <typename T>
  432. ILINE T CListenerNotifier<T>::operator->()
  433. {
  434. return operator*();
  435. }
  436. // Dereference current listener, MUST only be done after a call to IsReady().
  437. template <typename T>
  438. ILINE T CListenerNotifier<T>::operator*()
  439. {
  440. // Ensure IsReady() was called and its return value checked
  441. CRY_ASSERT(m_pListener);
  442. // Clear cached listener pointer to force a IsReady() call before this can be called again.
  443. // This is done as the listener could be removed during any call to its own event handlers
  444. // resulting in m_pListener becoming a dangling pointer.
  445. T pListener(m_pListener);
  446. m_pListener = T();
  447. return pListener;
  448. }
  449. // Move to next valid listener
  450. template <typename T>
  451. ILINE void CListenerNotifier<T>::Next()
  452. {
  453. size_t index = m_index;
  454. typename CListenerSet<T>::ListenerRecord * pNextRecord = NULL;
  455. m_pListener = NULL; // Always assume there's no next, let the code below prove otherwise!
  456. const size_t listenerCount = m_listenerSet.m_listeners.size();
  457. while (++index < listenerCount)
  458. {
  459. typename CListenerSet<T>::ListenerRecord & record(m_listenerSet.m_listeners[index]);
  460. // Is this element valid?
  461. if (record.m_pListener)
  462. {
  463. pNextRecord = &record;
  464. break;
  465. }
  466. // Else move to next element
  467. }
  468. if (pNextRecord)
  469. {
  470. m_pListener = pNextRecord->m_pListener;
  471. #ifdef CRY_LISTENERSET_DEBUG
  472. m_szName = pNextRecord->m_szName;
  473. #endif
  474. }
  475. m_index = index;
  476. }
  477. // Returns the name of the listener (if available)
  478. template <typename T>
  479. inline const char* CListenerNotifier<T>::Name() const
  480. {
  481. #ifdef CRY_LISTENERSET_DEBUG
  482. return m_szName;
  483. #else
  484. return NULL;
  485. #endif
  486. }
  487. #endif // CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H