325 lines
9.3 KiB
C++
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 ©);
|
|
|
|
inline ADestructionWatcher &operator =(ADestructionWatcher &&move);
|
|
inline ADestructionWatcher &operator =(const ADestructionWatcher ©);
|
|
|
|
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 ©)
|
|
{
|
|
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 ©)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|