123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- // Description : A simple, intelligent and efficient container for listeners.
- // This is designed to provide a simple & consistent interface and behavior
- // for adding, removing and iterating listeners - hopefully avoiding the
- // common pitfalls such as duplicated elements, invalid iterators and
- // dangling pointers.
- #ifndef CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H
- #define CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H
- #pragma once
- #include "Cry_Math.h"
- #include <StlUtils.h>
- /************************************************************************
- Core elements:
- * CListenerSet<T> - The collection of listeners.
- * CListenerSet<T>::Notifier - The iterator for safely calling listeners in sequence.
- [Where T is a pointer or something with pointer traits]
- Advantages:
- * Greatly reduces the complexity of managing listener collections.
- * Can safely add and remove listeners during listener iteration.
- * Automatically and safely removes NULL elements.
- * Simple interface (but different from an STL collection to avoid confusion).
- * Checks to see all listeners have been removed at destruction.
- * Low overhead implementation (cheap use of std::vector and minimal heap allocation).
- * Can add debug checks as needed - including stack tracing of Add() calls.
- * Safe for recursive notification chains.
- * Works with vanilla pointers and smart pointers.
- * Provides full support for named listeners to aid debugging.
- * Listener names tracked during notification to help resolve crashes.
- * Designed to ensure names are recorded correctly in crash dump files.
- Named listener support:
- Supplying a name for a listener provides valuable debug information in the following cases:
- * Resolving crashes during listener notification - the name will help trace what listener caused the crash.
- * Resolving which listeners are present in the CListenerSet during runtime.
- IMPORTANT: Please ensure heap allocated strings passed in as names are marked as such ie.
- m_listeners.Add(pListener, "MyListener"); // OK: Static string passed.
- m_listeners.Add(pListener2, m_myName.c_str(), false); // OK: Heap string passed and marked as non-static.
- m_listeners.Add(pListener3, m_myName.c_str(), true); // BAD: Heap string passed and marked as static (potential CRASH).
- Why store names like this?
- * 99% of use cases use static strings - so why allocate memory to make copies of static data?
- * Pointers to static strings will *always* survive crash dumps - great for debugging crashes.
- * We can enable debug support in QA builds - key for catching rare listener related crashes.
- Example:
- class CMyWorld
- {
- public:
- void AddListener(IMyWorldListener* pListener, const char* szName) { m_listeners.Add(pListener, szName); }
- void RemoveListener(IMyWorldListener* pListener) { m_listeners.Remove(pListener); }
- void NotifyListeners(CSomeEvent& event)
- {
- for (TWorldListeners::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next())
- {
- notifier->OnWorldEvent(event);
- }
- }
- private:
- typedef CListenerSet<IMyWorldListener*> TWorldListeners;
- TWorldListeners m_listeners;
- };
- // Implements IMyWorldListener
- CMyWorldUser::OnWorldEvent(CSomeEvent& event)
- {
- // OK: Removing a listeners within an event handler
- m_pWorld->RemoveListener(this);
- // OK: Notifying listeners within an event handler
- m_pWorld->NotifyListeners(CSomeEvent newEvent(USER_REMOVED, this));
- }
- *************************************************************************/
- #ifndef _RELEASE
- #define CRY_LISTENERSET_DEBUG
- #endif
- // Forward decl.
- template <typename T>
- class CListenerNotifier;
- // Main listener collection class used in conjunction with CListenerNotifier.
- template <typename T>
- class CListenerSet
- {
- public:
- // NOTE: No default constructor in favor of forcing users to provide an expectedCapacity
- inline CListenerSet(size_t expectedCapacity);
- inline /*non-virtual*/ ~CListenerSet();
- // Appends a listener to the end of the collection. Name is optional but recommended.
- inline bool Add(T pListener, const char* name = NULL, bool staticName = true);
- // Removes a listener from the collection.
- inline void Remove(T pListener);
- // Removes all listeners from the collection (NOTE: prefer informing listeners to remove themselves)
- inline void Clear(bool bFreeMemory = false);
- // Returns true if this contains pListener
- inline bool Contains(T pListener) const;
- // Returns number of valid listeners
- inline size_t ValidListenerCount() const;
- // Returns true if no valid listeners exist
- inline bool Empty() const;
- // Reserves space to help avoid runtime reallocation
- inline void Reserve(size_t capacity);
- // Returns the memory size of this object (to support CrySizer)
- inline size_t MemSize() const;
- // Returns true if currently in the process of notifying listeners.
- inline bool IsNotifying() const;
- // Allow access for Notifier for iteration
- friend class CListenerNotifier<T>;
- // Allow TListeners::Notifier style usage
- typedef class CListenerNotifier<T> Notifier;
- private: // DO NOT REMOVE - following methods only to be accessed only via CNotifier
- struct ListenerRecord
- {
- ListenerRecord()
- : m_pListener() {}
- ListenerRecord(T pListener, [[maybe_unused]] const char* szName = NULL)
- : m_pListener(pListener)
- #ifdef CRY_LISTENERSET_DEBUG
- , m_szName(szName)
- #endif
- {}
- bool operator==(const ListenerRecord& other) const { return m_pListener == other.m_pListener; }
- bool operator==(const T& other) const { return m_pListener == other; }
- T m_pListener; // The listener reference
- #ifdef CRY_LISTENERSET_DEBUG
- const char* m_szName; // Name of tracked listener (owned if pointing to data in m_allocatedNames)
- #endif
- };
- typedef std::vector<ListenerRecord> TListenerVec;
- typedef std::vector<AZStd::string> TAllocatedNameVec;
- inline void StartNotificationScope();
- inline void EndNotificationScope();
- inline void EraseNullElements();
- private:
- TListenerVec m_listeners; // Collection of unique listeners.
- size_t m_activeNotifications; // Counts current notifications in progress (cleanup cannot occur unless this is 0).
- bool m_cleanupRequired; // Indicates NULL elements in listener.
- bool m_freeMemOnCleanup; // Indicates how to clean up.
- #ifdef CRY_LISTENERSET_DEBUG
- // Used to delete heap allocated names
- inline void DeleteName(const char* name);
- TAllocatedNameVec m_allocatedNames; // Collection of strings pointing at heap allocated (i.e. copied) names (typically empty)
- #endif
- };
- // Helper class used to iterate listeners during listener notification.
- template <typename T>
- class CListenerNotifier
- {
- public:
- ILINE CListenerNotifier(CListenerSet<T>& listeners);
- ILINE /*non-virtual*/ ~CListenerNotifier();
- // True if the current element is ready for iteration
- ILINE bool IsValid();
- // Dereference current listener, MUST only be done after a call to IsValid().
- ILINE T operator->();
- // Dereference current listener, MUST only be done after a call to IsValid().
- ILINE T operator*();
- // Move to next valid listener (skipping NULL elements)
- ILINE void Next();
- // Returns the name of the listener (if available)
- inline const char* Name() const;
- private:
- CListenerSet<T>& m_listenerSet; // ListenerSet being notified
- T m_pListener; // Current listener at index (resolved by IsValid(), cleared after each dereference)
- size_t m_index; // Current index of element (incremented by next)
- #ifdef CRY_LISTENERSET_DEBUG
- const char* m_szName; // Name of the listener (if provided) to aid debugging
- #endif
- };
- /******************************************************************************************/
- template <typename T>
- inline CListenerSet<T>::CListenerSet(size_t expectedCapacity)
- : m_activeNotifications(0)
- , m_cleanupRequired(false)
- , m_freeMemOnCleanup(false)
- {
- // Reserve the expected capacity to avoid reallocations
- m_listeners.reserve(expectedCapacity);
- }
- template <typename T>
- inline CListenerSet<T>::~CListenerSet()
- {
- // Ensure no notifications are in progress
- CRY_ASSERT(m_activeNotifications == 0);
- // Ensure NULL elements were removed at end of last notification
- CRY_ASSERT(!m_cleanupRequired);
- }
- // Appends a listener to the end of the collection. Name is optional but recommended.
- template <typename T>
- inline bool CListenerSet<T>::Add(T pListener, const char* name, [[maybe_unused]] bool staticName)
- {
- bool success = false;
- // Ensure the listener exists
- CRY_ASSERT(pListener);
- if (pListener)
- {
- // Ensure the listener is only added once
- if (!Contains(pListener))
- {
- // Resolve name buffer safe for usage outside of this scope
- const char* safeName = name;
- #ifdef CRY_LISTENERSET_DEBUG
- // If a name was provided but it's not static data
- if (name && !staticName)
- {
- // Add it to the list of heap allocated names (that we need to later delete)
- m_allocatedNames.push_back(name);
- safeName = m_allocatedNames.back().c_str();
- }
- #endif
- m_listeners.push_back(ListenerRecord(pListener, safeName));
- success = true;
- }
- }
- return success;
- }
- // Removes a listener from the collection.
- template <typename T>
- inline void CListenerSet<T>::Remove(T pListener)
- {
- typename TListenerVec::iterator endIter(m_listeners.end());
- typename TListenerVec::iterator iter(std::find(m_listeners.begin(), endIter, pListener));
- if (iter != endIter)
- {
- #ifdef CRY_LISTENERSET_DEBUG
- // Delete name if it was heap allocated
- if (const char* name = iter->m_szName)
- {
- DeleteName(name);
- }
- #endif
- // If no notifications in progress
- if (m_activeNotifications == 0)
- {
- // Just delete the listener entry immediately
- m_listeners.erase(iter);
- }
- else // Notification(s) in progress, cannot re-order listeners
- {
- // Mark for cleanup
- iter->m_pListener = NULL;
- m_cleanupRequired = true;
- m_freeMemOnCleanup = false;
- }
- }
- else // The listener is not in the set
- {
- // TODO: Warn about redundant Remove()
- }
- }
- // Removes all listeners from the collection (NOTE: prefer informing listeners to remove themselves)
- template <typename T>
- inline void CListenerSet<T>::Clear(bool bFreeMemory)
- {
- // If no notifications in progress
- if (m_activeNotifications == 0)
- {
- // Simply clear the listeners immediately
- if (bFreeMemory)
- {
- stl::free_container(m_listeners);
- }
- else
- {
- m_listeners.clear();
- }
- }
- else
- {
- // Mark all listeners for cleanup
- std::fill(m_listeners.begin(), m_listeners.end(), ListenerRecord());
- m_cleanupRequired = true;
- m_freeMemOnCleanup = true;
- }
- #ifdef CRY_LISTENERSET_DEBUG
- // Safe to clear allocated names immediately (no references exist any more)
- if (bFreeMemory)
- {
- stl::free_container(m_allocatedNames);
- }
- else
- {
- m_allocatedNames.clear();
- }
- #endif
- }
- // Returns true if this contains pListener
- template <typename T>
- inline bool CListenerSet<T>::Contains(T pListener) const
- {
- return stl::find(m_listeners, pListener);
- }
- // Returns number of valid listeners
- template <typename T>
- inline size_t CListenerSet<T>::ValidListenerCount() const
- {
- size_t validCount = m_listeners.size();
- if (m_cleanupRequired)
- {
- // Remove the count of NULL elements from the result
- validCount = validCount - std::count(m_listeners.begin(), m_listeners.end(), T());
- }
- return validCount;
- }
- // Returns true if no valid listeners exist
- template <typename T>
- inline bool CListenerSet<T>::Empty() const
- {
- return ValidListenerCount() == 0;
- }
- // Reserves space to help avoid runtime reallocation
- template <typename T>
- inline void CListenerSet<T>::Reserve(size_t capacity)
- {
- m_listeners.reserve(capacity);
- }
- // Returns the memory size of this object (to support CrySizer)
- template <typename T>
- inline size_t CListenerSet<T>::MemSize() const
- {
- size_t size = sizeof(CListenerSet<T>) + sizeof(ListenerRecord) * m_listeners.size();
- #ifdef CRY_LISTENERSET_DEBUG
- size += sizeof(typename TAllocatedNameVec::value_type);
- for (typename TAllocatedNameVec::const_iterator iter(m_allocatedNames.begin()); iter != m_allocatedNames.end(); ++iter)
- {
- size += iter->capacity() * sizeof(char) + sizeof(AZStd::string);
- }
- #endif
- return size;
- }
- template <typename T>
- inline bool CListenerSet<T>::IsNotifying() const
- {
- return m_activeNotifications > 0;
- }
- template <typename T>
- inline void CListenerSet<T>::StartNotificationScope()
- {
- ++m_activeNotifications;
- }
- template <typename T>
- inline void CListenerSet<T>::EndNotificationScope()
- {
- // Ensure at least one notification scope was started
- CRY_ASSERT(m_activeNotifications > 0);
- // If this is the last notification
- if (--m_activeNotifications == 0)
- {
- EraseNullElements();
- }
- }
- template <typename T>
- inline void CListenerSet<T>::EraseNullElements()
- {
- // Ensure no modification while notification(s) are ongoing
- CRY_ASSERT(m_activeNotifications == 0);
- if (m_cleanupRequired && m_activeNotifications == 0)
- {
- stl::find_and_erase_all(m_listeners, T());
- if (m_freeMemOnCleanup && m_listeners.empty())
- {
- stl::free_container(m_listeners);
- }
- m_cleanupRequired = false;
- m_freeMemOnCleanup = false;
- }
- }
- #ifdef CRY_LISTENERSET_DEBUG
- // Used to delete heap allocated names
- template <typename T>
- inline void CListenerSet<T>::DeleteName(const char* name)
- {
- if (!m_allocatedNames.empty())
- {
- typename TAllocatedNameVec::iterator endIter(m_allocatedNames.end());
- for (typename TAllocatedNameVec::iterator iter(m_allocatedNames.begin()); iter != endIter; ++iter)
- {
- // Is this the source string?
- if (iter->c_str() == name)
- {
- // Delete it
- m_allocatedNames.erase(iter);
- break;
- }
- }
- }
- }
- #endif // defined CRY_LISTENERSET_DEBUG
- /******************************************************************************************/
- template <typename T>
- ILINE CListenerNotifier<T>::CListenerNotifier(CListenerSet<T>& listeners)
- : m_listenerSet(listeners)
- , m_pListener()
- , m_index(0)
- #ifdef CRY_LISTENERSET_DEBUG
- , m_szName()
- #endif
- {
- // Flag iteration to listener set to ensure no erase is attempted during iteration
- m_listenerSet.StartNotificationScope();
- // If first element is NULL, move to next valid element
- if (!IsValid())
- {
- Next();
- }
- }
- template <typename T>
- ILINE CListenerNotifier<T>::~CListenerNotifier()
- {
- // Erases any NULL elements from listeners
- m_listenerSet.EndNotificationScope();
- }
- // True if the current element is ready for iteration
- template <typename T>
- ILINE bool CListenerNotifier<T>::IsValid()
- {
- if (!m_pListener)
- {
- // Always check with original collection
- if (m_index < m_listenerSet.m_listeners.size())
- {
- const typename CListenerSet<T>::ListenerRecord & record(m_listenerSet.m_listeners[m_index]);
- m_pListener = record.m_pListener;
- #ifdef CRY_LISTENERSET_DEBUG
- m_szName = record.m_szName;
- #endif
- }
- }
- return m_pListener != NULL;
- }
- // Dereference current listener, MUST only be done after a call to IsReady().
- template <typename T>
- ILINE T CListenerNotifier<T>::operator->()
- {
- return operator*();
- }
- // Dereference current listener, MUST only be done after a call to IsReady().
- template <typename T>
- ILINE T CListenerNotifier<T>::operator*()
- {
- // Ensure IsReady() was called and its return value checked
- CRY_ASSERT(m_pListener);
- // Clear cached listener pointer to force a IsReady() call before this can be called again.
- // This is done as the listener could be removed during any call to its own event handlers
- // resulting in m_pListener becoming a dangling pointer.
- T pListener(m_pListener);
- m_pListener = T();
- return pListener;
- }
- // Move to next valid listener
- template <typename T>
- ILINE void CListenerNotifier<T>::Next()
- {
- size_t index = m_index;
- typename CListenerSet<T>::ListenerRecord * pNextRecord = NULL;
- m_pListener = NULL; // Always assume there's no next, let the code below prove otherwise!
- const size_t listenerCount = m_listenerSet.m_listeners.size();
- while (++index < listenerCount)
- {
- typename CListenerSet<T>::ListenerRecord & record(m_listenerSet.m_listeners[index]);
- // Is this element valid?
- if (record.m_pListener)
- {
- pNextRecord = &record;
- break;
- }
- // Else move to next element
- }
- if (pNextRecord)
- {
- m_pListener = pNextRecord->m_pListener;
- #ifdef CRY_LISTENERSET_DEBUG
- m_szName = pNextRecord->m_szName;
- #endif
- }
- m_index = index;
- }
- // Returns the name of the listener (if available)
- template <typename T>
- inline const char* CListenerNotifier<T>::Name() const
- {
- #ifdef CRY_LISTENERSET_DEBUG
- return m_szName;
- #else
- return NULL;
- #endif
- }
- #endif // CRYINCLUDE_CRYCOMMON_CRYLISTENERSET_H
|