AuroraRuntime/Include/Aurora/Utility/DestructionWatch.hpp
2024-06-10 14:08:58 +01:00

325 lines
9.3 KiB
C++

/***
Copyright (C) 2024 Jamie Reece Wilson (a/k/a "Reece"). All rights reserved.
File: DestructionWatch.hpp
Date: 2024-01-22
Author: Reece
***/
#pragma once
namespace Aurora::Utility
{
struct ADestructionWatcher;
struct DestructionWatch
{
inline DestructionWatch();
inline ~DestructionWatch();
// If your (optionally abstract) ADestructionWatcher requires access to the parent object, it is highly recommened to call RemoveAll within the destructor of the parent class.
// Otherwise, you must maintain a strict ctor/dtor order of your classes layout.
inline void RemoveAll();
private:
friend struct ADestructionWatcher;
Threading::Waitables::FutexWaitable mutex;
ADestructionWatcher *pFirstWatcher {};
AuList<ADestructionWatcher *> *pListpWatches {};
AuUInt32 uDtorCalls {};
inline void RemoveWatcher(ADestructionWatcher *pWatcher);
inline void RemoveWatcherCall(ADestructionWatcher *pWatcher);
};
struct ADestructionWatcher
{
inline ADestructionWatcher();
inline ~ADestructionWatcher();
inline void Observe(DestructionWatch *pWatch);
inline void Observe(DestructionWatch &pWatch);
inline bool IsObservedAlive();
inline bool IsObservedDead();
inline ADestructionWatcher(ADestructionWatcher &&move);
inline ADestructionWatcher(const ADestructionWatcher &copy);
inline ADestructionWatcher &operator =(ADestructionWatcher &&move);
inline ADestructionWatcher &operator =(const ADestructionWatcher &copy);
protected:
// Note that since the parent object is in the midst of destruction; you should not be manually calling the destroy operations of it or its' children.
// C++ construction and the inverse destruction order are well defined.
// In the case of extending DestructionWatch as a base class, our constructor should be invoked first, and therefore our destructor will be invoked last.
// In the case of inserting DestructionWatch as a class member, you must guard resource access via synchronization primitives placed in proper construction/destruction order.
inline virtual void OnParentDestroy()
{
}
inline virtual void OnDestroy()
{
// You *might* be able to access the parent object at this point in time.
//
// It is legal to access the memory under both OnParentDestroy(), and OnDestroy() in the case that IsObservedAlive() return true; however, accessing the parents
// member fields shall be restricted pursuant to the inverse of C++s construction order. It is possible for multithreaded code to be in the midst of calling
// the destruction chain before we are aware of the destruction condition. Shared pointers work around this by invoking private std::enable_shared_from_this
// members at the time of construction and time of control block release. It *should* be safe to access HANDLEs, raw c pointers, etc without any special logic,
// on the account that the parent object will not have released its' memory until we're done; same applies to OnParentDestroy().
//
// ** To further harden against out of order destruction, it is advisable but not required, to call DestructionWatch::RemoveAll() in the parents destructor. **
// ** Alternatively, you must be hyper-aware of the C++ destruction order. **
}
public:
inline void Destroy()
{
if (!DoUnderLock(&ADestructionWatcher::OnDestroy, this))
{
this->OnDestroy();
}
}
private:
friend struct DestructionWatch;
friend class Threading::LockGuard<ADestructionWatcher *>;
inline void OnSelfDTOR();
inline void OnDTOR();
inline void Lock();
inline void Unlock();
public:
template <typename T, typename ...Args>
AuConditional_t<AuIsSame_v<AuResultOf_t<T, Args...>, void>, bool, AuOptional<AuResultOf_t<T, Args...>>>
DoUnderLock(const T &callable, Args && ...args)
{
{
AU_LOCK_GUARD(this->selfLock);
if (!this->pParent)
{
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
{
return false;
}
else
{
return AuOptional<AuResultOf_t<T, Args...>> {};
}
}
AuAtomicAdd(&this->pParent->uDtorCalls, 1u);
}
{
AU_LOCK_GUARD(this);
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
{
AuInvoke(callable, AuForward<Args &&>(args)...);
return true;
}
else
{
return AuInvoke(callable, AuForward<Args &&>(args)...);
}
}
}
private:
Threading::Waitables::FutexWaitable selfLock;
DestructionWatch *pParent {};
};
DestructionWatch::DestructionWatch()
{
}
DestructionWatch::~DestructionWatch()
{
this->RemoveAll();
}
void DestructionWatch::RemoveAll()
{
{
AU_LOCK_GUARD(this->mutex);
if (auto pWatcher = AuExchange(this->pFirstWatcher, nullptr))
{
this->RemoveWatcherCall(pWatcher);
}
if (auto pWatchList = AuExchange(this->pListpWatches, nullptr))
{
for (const auto pWatcher : *pWatchList)
{
this->RemoveWatcherCall(pWatcher);
}
delete pWatchList;
}
}
{
AuUInt32 uOld {};
while ((uOld = AuAtomicLoad(&this->uDtorCalls)) != 0)
{
Aurora::Threading::WaitOnAddress(&this->uDtorCalls, &uOld, sizeof(uOld), 0);
}
}
}
void DestructionWatch::RemoveWatcher(ADestructionWatcher *pWatcher)
{
AU_LOCK_GUARD(this->mutex);
if (this->pFirstWatcher == pWatcher)
{
this->pFirstWatcher = nullptr;
return;
}
if (this->pListpWatches)
{
AuTryRemove(*this->pListpWatches, pWatcher);
}
}
void DestructionWatch::RemoveWatcherCall(ADestructionWatcher *pWatcher)
{
pWatcher->OnDTOR();
}
ADestructionWatcher::ADestructionWatcher()
{
}
ADestructionWatcher::~ADestructionWatcher()
{
this->OnSelfDTOR();
}
ADestructionWatcher::ADestructionWatcher(ADestructionWatcher &&move)
{
AU_LOCK_GUARD(move.selfLock);
if (auto pParent = AuExchange(move.pParent, nullptr))
{
pParent->RemoveWatcher(&move);
this->Observe(pParent);
}
}
ADestructionWatcher::ADestructionWatcher(const ADestructionWatcher &copy)
{
AU_LOCK_GUARD(copy.selfLock);
if (auto &pParent = copy.pParent)
{
this->Observe(pParent);
}
}
ADestructionWatcher &ADestructionWatcher::operator =(ADestructionWatcher &&move)
{
AU_LOCK_GUARD(move.selfLock);
if (auto pParent = AuExchange(move.pParent, nullptr))
{
pParent->RemoveWatcher(&move);
this->Observe(pParent);
}
return *this;
}
ADestructionWatcher &ADestructionWatcher::operator =(const ADestructionWatcher &copy)
{
AU_LOCK_GUARD(copy.selfLock);
if (auto &pParent = copy.pParent)
{
this->Observe(pParent);
}
return *this;
}
void ADestructionWatcher::Observe(DestructionWatch *pWatch)
{
AU_LOCK_GUARD(pWatch->mutex);
if (!pWatch->pFirstWatcher)
{
pWatch->pFirstWatcher = this;
}
else
{
if (!pWatch->pListpWatches)
{
pWatch->pListpWatches = new AuRemovePointer_t<decltype(pWatch->pListpWatches)>();
}
pWatch->pListpWatches->push_back(this);
}
this->pParent = pWatch;
}
void ADestructionWatcher::Observe(DestructionWatch &pWatch)
{
this->Observe(&pWatch);
}
bool ADestructionWatcher::IsObservedAlive()
{
return bool(this->pParent);
}
bool ADestructionWatcher::IsObservedDead()
{
return !bool(this->pParent);
}
void ADestructionWatcher::OnSelfDTOR()
{
AU_LOCK_GUARD(this->selfLock);
if (auto pParent = AuExchange(this->pParent, nullptr))
{
this->OnDestroy();
pParent->RemoveWatcher(this);
}
else
{
this->OnDestroy();
}
}
void ADestructionWatcher::OnDTOR()
{
{
AU_LOCK_GUARD(this->selfLock);
this->pParent = nullptr;
}
this->OnParentDestroy();
}
void ADestructionWatcher::Lock()
{
}
void ADestructionWatcher::Unlock()
{
if (AuAtomicSub(&this->pParent->uDtorCalls, 1u) == 0)
{
Aurora::Threading::WakeOnAddress(&this->pParent->uDtorCalls);
}
}
}