|
|
- #pragma once
-
- #include <fsdk/IObject.h>
-
- #include <cstdint>
- #include <cstdlib>
- #include <atomic>
- #include <new>
-
- namespace tsdk {
- /**
- * @brief Default base reference-counted object implementation.
- * RefCountedImpl derivatives must apply ot the following rules:
- * - be descendants of IRefCounted
- * - objects must be created strictly on heap with no exceptions
- * - new and delete should not be accessible directly (unless you understand good enough what you are doing).
- * RefCountedImpl provides implementation of weak reference counting to coupe with functions defined by IRefCounted.
- * Weak referencing allows to avoid circular dependencies in parent-child relationships. Weak reference count is
- * controlled via retainWeak(), releaseWeak() and getWeakRefCount(). These functions behave similar to their 'strong'
- * reference counting relatives, but control life time just of the reference counter objects instead of the counted
- * object (which occupies less memory). This way we are able to call destructor of the counting object and
- * potentially free it's resources and keep the counter alive at the same time to avoid poking a dead object.
- *
- *
- * @tparam T base interface type to propagate through inheritance tree. Must be convertible to IRefCounted.
- * @note RefCountedImpl uses a separate reference counter object which is prepended to the actual object during
- * construction so size of occupied memory is greater than generally expected sizeof(ObjectType).
- * @note this implementation of RefCountedImpl is thread safe.
- * @note to achieve lock free thread safety weak reference counter reflects actual value of strong reference
- * counter. I.e. after creation, the object has both counters set to 1. After each retain() both counters increase
- * and so on. retainWeak() / releaseWeak() in turn alter only the weak reference counter. So actual number of
- * weak references is Nact = (Nweak - Nstrong).
- * */
- template<typename T>
- struct RefCountedImpl : T {
-
- /**
- * @brief Reference counter object.
- * Keeps track of strong and weak references.
- * */
- struct RefCounter {
- std::atomic<int32_t> m_strong; //!< strong reference count.
- std::atomic<int32_t> m_weak; //!< weak reference count.
- };
-
- /**
- * @brief Acquire strong reference.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t retain() noexcept override {
- auto counter = getCounter(this);
-
- // object must be alive.
- assert(counter->m_strong > 0);
- assert(counter->m_weak > 0);
-
- return (++ counter->m_strong);
- }
-
- /**
- * @brief Acquire strong reference thread safely.
- * @note difference between retain and retainLocked is former guarantees synchronization in case another
- * thread release object in the same time we try to call WeakRef.lock()
- * This method was introduced to align behaviour of WeakRef.lock() from SDK with with weak_ptr.lock()
- * from standard library, so seek more info about thread safety requirements for std::weak_ptr.
- * @returns actual reference count.
- * */
- int32_t retainLocked() noexcept override {
- auto counter = getCounter(this);
- int32_t strongCount = counter->m_strong;
-
- do {
- // if counter hit 0 at any point during object lifetime, it means it can't be used anymore, so don't retain
- if(strongCount == 0)
- return 0;
- } while(!std::atomic_compare_exchange_weak_explicit(
- &counter->m_strong,
- &strongCount,
- strongCount + 1,
- std::memory_order_acq_rel,
- std::memory_order_relaxed));
-
- // strong count + 1 reflects actual value in any of cases, and it's faster to read non-atomic variable
- return strongCount + 1;
- }
-
- /**
- * @brief Release strong reference.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t release() noexcept override {
- auto counter = getCounter(this);
-
- // object must be alive.
- assert(counter->m_strong > 0);
- assert(counter->m_weak > 0);
-
- const int32_t strong = (-- counter->m_strong);
-
- if(strong == 0) {
- destruct();
-
- // for thread fence reasons see http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
- std::atomic_thread_fence(std::memory_order_acq_rel);
-
- if((-- counter->m_weak) == 0) {
- deallocate();
- }
- }
-
- return strong;
- }
-
- /**
- * @brief Get actual strong reference count.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t getRefCount() const noexcept override {
- auto counter = getCounter(this);
- return counter->m_strong;
- }
-
- /**
- * @brief Acquire weak reference.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t retainWeak() noexcept override {
- auto counter = getCounter(this);
-
- // object reference counter must be alive.
- assert(counter->m_weak > 0);
-
- return (++ counter->m_weak);
- }
-
- /**
- * @brief Release weak reference.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t releaseWeak() noexcept override {
- auto counter = getCounter(this);
-
- // object reference counter must be alive.
- assert(counter->m_weak > 0);
-
- const int32_t weak = (-- counter->m_weak);
-
- if(weak == 0) {
- // for thread fence reasons see http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
- std::atomic_thread_fence(std::memory_order_acq_rel);
- deallocate();
- }
-
- return weak;
- }
-
- /**
- * @brief Get weak reference count.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t getWeakRefCount() const noexcept override {
- auto counter = getCounter(this);
- return counter->m_weak;
- }
-
- /**
- * @brief Check if object is dead.
- * @note this function is thread safe.
- * @returns true if strong reference count is zero, false otherwise.
- * */
- bool isExpired() { return getRefCount() == 0; }
-
- /**
- * @brief Check if object is alive.
- * @note this function is thread safe.
- * @returns true if strong reference count is not zero, false otherwise.
- * */
- bool isAlive() { return !isExpired(); }
-
- protected:
-
- /**
- * @brief Initialize empty object.
- * @details Stores a pointer to reference counter in debug builds.
- * */
- RefCountedImpl() {
- #ifndef NDEBUG
- m_counter = getCounter(this);
- #endif
- RefCounter *counter = getCounter(this);
- counter->m_strong++;
- counter->m_weak++;
- }
-
- virtual ~RefCountedImpl() {
-
- if(getRefCount() && getWeakRefCount()) {
- RefCounter *counter = getCounter(this);
- counter->m_strong--;
- counter->m_weak--;
- }
- }
-
- /**
- * @brief Custom reference-counter aware operator new.
- * Uses default CRT implementation of malloc().
- * @param size memory size to allocate.
- * */
- void* operator new(size_t size) {
- auto counter =
- reinterpret_cast<RefCounter*>(
- malloc(size + sizeof(RefCounter)));
-
- new (counter) RefCounter();
-
- counter->m_strong = 0;
- counter->m_weak = 0;
-
- return counter + 1;
- }
-
- /**
- * @brief Custom reference-counter aware operator delete.
- * Uses default CRT implementation of free.
- * @param memory memory location to free.
- * @param memory memory size to free. (Unused).
- * */
- void operator delete(void* memory, size_t) {
- auto counter =
- reinterpret_cast<RefCounter*>(memory) - 1;
-
- assert(counter->m_strong == 0);
- assert(counter->m_weak == 0);
-
- counter->~RefCounter();
-
- free(counter);
- }
-
- /**
- * @brief Call operator delete on this object.
- * @note no safety checks performed.
- * */
- void deallocate() {
- operator delete (this, 0);
- }
-
- /**
- * @brief Call operator destructor on this object.
- * @note no safety checks performed.
- * */
- void destruct() {
- this->~RefCountedImpl();
- }
-
- /**
- * @brief Get object reference counter pointer from this pointer.
- * @param pthis this pointer.
- * */
- static RefCounter* getCounter(RefCountedImpl* pthis) {
- return reinterpret_cast<RefCounter*>(pthis) - 1;
- }
-
- /**
- * @brief Get object reference counter pointer from this pointer.
- * @param pthis this pointer.
- * */
- static const RefCounter* getCounter(const RefCountedImpl* pthis) {
- return reinterpret_cast<const RefCounter*>(pthis) - 1;
- }
-
- #ifndef NDEBUG
- RefCounter* m_counter; //!< pointer to reference counter object.
- #endif
- };
-
-
- /** @brief Plain reference counted base. */
- typedef RefCountedImpl<fsdk::IRefCounted> RefCounted;
-
-
- /**
- * @brief Default base object implementation.
- * @tparam T object interface, must be convertible to IObject.
- * @tparam ID object type id.
- * */
- template<typename T, int32_t ID>
- struct ObjectImpl : RefCountedImpl<T> {
-
- /**
- * @brief Get object type id.
- * @note this function is thread safe.
- * @returns object type id.
- * */
- int32_t getTypeId() const override {
- return ID;
- }
- };
-
-
- /**
- * @brief Default public object implementation.
- * @note this expects embedded object type id in to the interface. This is achieved with DECLARE_TYPE_ID macro.
- * @tparam T object interface, must be convertible to IObject.
- * */
- template<typename T>
- struct PublicObjectImpl : RefCountedImpl<T> {
- };
-
-
- /**
- * @brief Default base reference-counted managed object implementation.
- * This implementation assumes that implemented object has a logical parent object that holds a pool for it's
- * children or implements any other method of life time management. Thus, parent type must apply to several more
- * restrictions than the managed type.
- * @tparam T base interface type to propagate through inheritance tree. Must be convertible to IRefCounted.
- * @tparam P parent object type. Must be convertible to RefCountedImpl.
- * P must implement the following interface:
- * [code]
- * void recycle(C*);
- * [/code]
- * where C is any type that is convertible to ManagedRefCountedImpl<T, P>.
- * @note consequently it is safe to deallocate dead children in destructor since ones that are alive (if any) are
- * already aware that their parent object is dead before the actual call of destructor, so will not try to return
- * themselves to the parent's pool.
- * */
- template<
- typename T,
- typename P>
- struct ManagedRefCountedImpl : RefCountedImpl<T> {
-
- /** @brief Managed object type. */
- typedef ManagedRefCountedImpl<T, P> ManagedType;
-
- /** @brief Parent object type. */
- typedef P ParentType;
-
- /**
- * @brief Get a parent object.
- * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
- * the returned pointer(s) before they are freed or else you will have a memory leak.
- * @returns pointer to the parent object or nullptr if parent is expired.
- * */
- ParentType* getParent() const {
- // return strong reference to pointer if it is alive.
- if(auto parent = m_parent.lock()) {
- // ensure it will be alive until caller deals with it.
- // if retain returns 0 it means it has failed
- if(parent->retain() != 0)
- return parent;
- }
-
- // parent is dead, so return nullptr.
- return nullptr;
- }
-
- /**
- * @brief Release strong reference.
- * Recycles object.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t release() noexcept override {
- auto counter = this->getCounter(this);
-
- // object must be alive.
- assert(counter->m_strong > 0);
- assert(counter->m_weak > 0);
-
- const int32_t strong = (-- counter->m_strong);
-
- if(strong == 0 && (-- counter->m_weak) == 0) {
- // don't call dtor as we expect this object to be renewed later.
- this->recycle();
- }
-
- return strong;
- }
-
- /**
- * @brief Release weak reference.
- * Recycles object.
- * @note this function is thread safe.
- * @returns actual reference count.
- * */
- int32_t releaseWeak() noexcept override {
- auto counter = this->getCounter(this);
-
- // object reference counter must be alive.
- assert(counter->m_weak > 0);
-
- const int32_t weak = (-- counter->m_weak);
-
- if(weak == 0) {
- this->recycle();
- }
-
- return weak;
- }
-
- protected:
-
- // Befriend our parent so that it can call destruct() and deallocate().
- friend P;
-
- /**
- * @brief Initialize an empty object with a parent.
- * @param parent the parent.
- * */
- explicit ManagedRefCountedImpl(ParentType* parent)
- : m_parent(parent)
- { assert(parent); }
-
- /**
- * @brief Try recycle this object.
- * If parent is still alive, then return to it's cache.
- * If parent is dead, free memory.
- * */
- void recycle() noexcept {
- // return to reuse pool or free memory.
- if(auto parent = m_parent.lock()) {
-
- // remove dangling weak ref.
- m_parent.reset();
-
- // recycle may call deallocate() so no code should follow this point.
- parent->recycle(this);
- }
- else {
- // our parent is already dead, so it goes.
- this->destroy();
- }
- }
-
- /**
- * @brief Reanimate this object by resetting reference counters.
- * @details Due to implementation details parent object pointer should re-assigned.
- * @param parent parent object pointer.
- * */
- void reanimate(ParentType* parent) {
- assert(parent);
-
- auto counter = this->getCounter(this);
-
- // Make sure object is dead;
- // otherwise it's a logical issue.
- assert(counter->m_strong == 0);
- assert(counter->m_weak == 0);
-
- counter->m_weak = 1;
- counter->m_strong = 1;
-
- m_parent.assign(parent);
- }
-
- /**
- * @brief Calls both destructor and operator delete.
- */
- void destroy() noexcept {
- this->destruct();
- this->deallocate();
- }
-
- private:
-
- fsdk::WeakRef<ParentType> m_parent; //!< parent (pool) object.
- };
-
-
- /**
- * @brief Default base managed object implementation.
- * @tparam T object interface, must be convertible to IObject.
- * @tparam ID object type id.
- * @tparam P parent object type, @see ManagedRefCountedImpl.
- * */
- template<
- typename T, int32_t ID,
- typename P>
- struct ManagedObjectImpl : ManagedRefCountedImpl<T, P> {
-
- /** @brief Managed object type. */
- typedef ManagedObjectImpl<T, ID, P> ManagedType;
-
- // Fix dependent name lookup.
- using typename ManagedRefCountedImpl<T, P>::ParentType;
-
- /**
- * @brief Get object type id.
- * @note this function is thread safe.
- * @returns object type id.
- * */
- int32_t getTypeId() const override {
- return ID;
- }
-
- /**
- * @brief Get a parent object.
- * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
- * the returned pointer(s) before they are freed or else you will have a memory leak.
- * @returns pointer to the parent object or nullptr if parent is expired.
- * */
- fsdk::IRefCounted* getParentObject() const noexcept {
- return this->getParent();
- }
-
- protected:
-
- /**
- * @brief Initialize an empty object with a parent.
- * @param parent the parent.
- * */
- explicit ManagedObjectImpl(ParentType* parent)
- : ManagedRefCountedImpl<T, P>(parent) {}
- };
-
-
- /**
- * @brief Default public managed object implementation.
- * @note this expects embedded object type id in to the interface. This is achieved with DECLARE_TYPE_ID macro.
- * @tparam T object interface, must be convertible to IObject.
- * @tparam P parent object type, @see ManagedRefCountedImpl.
- * */
- template<
- typename T,
- typename P>
- struct ManagedPublicObjectImpl : ManagedRefCountedImpl<T, P> {
-
- /** @brief Managed object type. */
- typedef ManagedPublicObjectImpl<T, P> ManagedType;
-
- ///Fix dependent name lookup.
- using typename ManagedRefCountedImpl<T, P>::ParentType;
-
- /** @brief Get a parent object.
- * Any returned interfaces will have their reference count incremented by one, so be sure to call ::release() on
- * the returned pointer(s) before they are freed or else you will have a memory leak.
- * @returns pointer to the parent object or nullptr if parent is expired.
- * */
- fsdk::IRefCounted* getParentObject() const noexcept {
- return this->getParent();
- }
-
- protected:
-
- /**
- * @brief Initialize an empty object with a parent.
- * @param parent the parent.
- * */
- explicit ManagedPublicObjectImpl(ParentType* parent)
- : ManagedRefCountedImpl<T, P>(parent) {}
- };
- }
|