AuroraRuntime/Include/Aurora/Utility/DestructionWatch.hpp

230 lines
5.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();
inline void RemoveAll();
private:
friend struct ADestructionWatcher;
Threading::Waitables::FutexWaitable mutex;
Threading::Waitables::FutexWaitable dtorSignal;
ADestructionWatcher *pFirstWatcher {};
AuList<ADestructionWatcher *> listpWatches;
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();
protected:
inline virtual void OnDestroyedChild()
{
}
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);
if (this->pOwnsLock)
{
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
{
callable(AuForward<Args &&>(args)...);
return true;
}
else
{
return callable(AuForward<Args &&>(args)...);
}
}
else
{
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
{
return false;
}
else
{
return AuOptional<AuResultOf_t<T, Args...>> {};
}
}
}
private:
Threading::Waitables::FutexWaitable selfLock;
DestructionWatch *pParent {};
Threading::Waitables::FutexWaitable *pOwnsLock {};
};
DestructionWatch::DestructionWatch()
{
}
DestructionWatch::~DestructionWatch()
{
this->RemoveAll();
}
void DestructionWatch::RemoveAll()
{
{
AU_LOCK_GUARD(this->mutex);
if (auto pWatcher = AuExchange(this->pFirstWatcher, nullptr))
{
this->RemoveWatcherCall(pWatcher);
}
for (const auto pWatcher : AuExchange(this->listpWatches, {}))
{
this->RemoveWatcherCall(pWatcher);
}
}
{
AU_LOCK_GUARD(this->dtorSignal);
}
}
void DestructionWatch::RemoveWatcher(ADestructionWatcher *pWatcher)
{
AU_LOCK_GUARD(this->mutex);
if (this->pFirstWatcher == pWatcher)
{
this->pFirstWatcher = nullptr;
return;
}
AuTryRemove(this->listpWatches, pWatcher);
}
void DestructionWatch::RemoveWatcherCall(ADestructionWatcher *pWatcher)
{
pWatcher->OnDTOR();
}
ADestructionWatcher::ADestructionWatcher()
{
}
ADestructionWatcher::~ADestructionWatcher()
{
this->OnSelfDTOR();
}
void ADestructionWatcher::Observe(DestructionWatch *pWatch)
{
AU_LOCK_GUARD(pWatch->mutex);
if (!pWatch->pFirstWatcher)
{
pWatch->pFirstWatcher = this;
}
else
{
pWatch->listpWatches.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))
{
pParent->RemoveWatcher(this);
}
}
void ADestructionWatcher::OnDTOR()
{
{
AU_LOCK_GUARD(this->selfLock);
this->pParent = nullptr;
}
this->OnDestroyedChild();
}
void ADestructionWatcher::Lock()
{
AU_LOCK_GUARD(this->selfLock);
this->pOwnsLock = nullptr;
if (!this->pParent)
{
return;
}
this->pParent->dtorSignal.Lock();
// noting that once we leave this scope, we could have our parent stolen from us (this->selfLock), but its' destructor will still be blocked until we finish
// this is why we end up using a raw pointer here.
this->pOwnsLock = &this->pParent->dtorSignal;
}
void ADestructionWatcher::Unlock()
{
if (this->pOwnsLock)
{
this->pOwnsLock->Unlock();
}
}
}