/*** 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 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; inline void OnSelfDTOR(); inline void OnDTOR(); inline void Lock(); inline void Unlock(); public: template AuConditional_t, void>, bool, AuOptional>> DoUnderLock(const T &callable, Args && ...args) { AU_LOCK_GUARD(this); if (this->pOwnsLock) { if constexpr (AuIsSame_v, void>) { callable(AuForward(args)...); return true; } else { return callable(AuForward(args)...); } } else { if constexpr (AuIsSame_v, void>) { return false; } else { return AuOptional> {}; } } } 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(); } } }