You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

558 lines
16 KiB

#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) {}
};
}