From ff0e32ce0251f18e701944b66f27ed31af8777e3 Mon Sep 17 00:00:00 2001 From: Jamie Reece Wilson Date: Sun, 17 Sep 2023 15:26:42 +0100 Subject: [PATCH] [*] Improve AuInitOnce API with an 8 byte variant and a locker API [+] AuInitOnceSmall --- Include/Aurora/Runtime.hpp | 1 + Include/Aurora/Threading/InitOnce.hpp | 223 +++++++++++++++++++------- 2 files changed, 166 insertions(+), 58 deletions(-) diff --git a/Include/Aurora/Runtime.hpp b/Include/Aurora/Runtime.hpp index 51b7be15..258019d5 100644 --- a/Include/Aurora/Runtime.hpp +++ b/Include/Aurora/Runtime.hpp @@ -144,6 +144,7 @@ using AuFutexSemaphore = AuThreading::Waitables::FutexSemaphoreWaitable; using AuFutexCond = AuThreading::Waitables::FutexCondWaitable; using AuInitOnce = AuThreading::InitOnce; +using AuInitOnceSmall = __audetail::InitOnceABI; template using AuTLSVariable = AuThreads::TLSVariable; diff --git a/Include/Aurora/Threading/InitOnce.hpp b/Include/Aurora/Threading/InitOnce.hpp index f530809c..110f5e85 100644 --- a/Include/Aurora/Threading/InitOnce.hpp +++ b/Include/Aurora/Threading/InitOnce.hpp @@ -7,41 +7,70 @@ ***/ #pragma once +namespace Aurora::Threading +{ + struct InitOnceLocker; +} + +namespace __audetail +{ + struct InitOnceABI + { + inline bool IsUninitialized(); + + inline bool IsReady(); + + inline bool TrySet(); + + inline void Wait(); + + protected: + + inline bool _TryAcquire(); + + inline void _Finish(); + + inline void _Wakeup(); + + inline bool _LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/); + + protected: + + AuAUInt32 uToken_ {}; + AuAUInt32 uSleepers_ {}; + + friend struct Aurora::Threading::InitOnceLocker; + }; +} + namespace Aurora::Threading { struct InitOnce final : - private IWaitable + private IWaitable, + private __audetail::InitOnceABI { inline bool IsUninitialized() { - return (AuAtomicLoad(&this->uToken_) & 1) == 0; + return InitOnceABI::IsUninitialized(); } inline bool IsReady() { - return (AuAtomicLoad(&this->uToken_) & 2) != 0; + return InitOnceABI::IsReady(); } inline bool TrySet() { - if (AuAtomicTestAndSet(&this->uToken_, 0) == 0) - { - this->Finish(); - return true; - } - else - { - return false; - } + return InitOnceABI::TrySet(); } template bool TryInit(const Callable &callback) { - if (AuAtomicTestAndSet(&this->uToken_, 0) == 0) + if (this->_TryAcquire()) { callback(); - this->Finish(); + this->_Finish(); return true; } else @@ -63,12 +92,12 @@ namespace Aurora::Threading return; } - this->Lock(); + this->Wait(); } inline void Wait() { - this->Lock(); + InitOnceABI::Wait(); } inline IWaitable *ToBarrier() @@ -77,39 +106,23 @@ namespace Aurora::Threading } protected: - - inline void Finish() - { - AuAtomicSet(&this->uToken_, 1); - this->Wakeup(); - } - - // barrier impl: - - inline void Wakeup() - { - if (auto uSleepers = AuAtomicLoad(&this->uSleepers_)) - { - WakeNOnAddress((const void *)&this->uToken_, uSleepers); - } - } - inline bool HasOSHandle(AuMach &mach) + inline bool HasOSHandle(AuMach &mach) override { return false; } - inline bool HasLockImplementation() + inline bool HasLockImplementation() override { return true; } - inline void Lock() + inline void Lock() override { this->LockAbsNS(0); } - inline bool LockNS(AuUInt64 qwTimeoutInNs) + inline bool LockNS(AuUInt64 qwTimeoutInNs) override { if (this->IsReady()) { @@ -119,39 +132,133 @@ namespace Aurora::Threading return IWaitable::LockNS(qwTimeoutInNs); } - inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) + inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) override { - auto uCurrent = AuAtomicLoad(&this->uToken_); - - while ((uCurrent & 2) == 0) - { - AuAtomicAdd(&this->uSleepers_, 1u); - bool bRet = WaitOnAddressSteady((const void *)&this->uToken_, &uCurrent, sizeof(uCurrent), qwAbsTimeoutInNs); - AuAtomicSub(&this->uSleepers_, 1u); - - if (!bRet) - { - return this->IsReady(); - } - - uCurrent = AuAtomicLoad(&this->uToken_); - } - - return true; + return this->_LockAbsNS(qwAbsTimeoutInNs); } - inline bool TryLock() + inline bool TryLock() override { return this->IsReady(); } - inline void Unlock() + inline void Unlock() override { } private: - AuAUInt32 uToken_ {}; - AuAUInt32 uSleepers_ {}; + friend struct InitOnceLocker; }; + + struct InitOnceLocker + { + cstatic bool TryLock(InitOnce *pInitOnce) + { + return pInitOnce->_TryAcquire(); + } + + cstatic void Finish(InitOnce *pInitOnce) + { + pInitOnce->_Finish(); + } + + cstatic bool TryLock(__audetail::InitOnceABI *pInitOnce) + { + return pInitOnce->_TryAcquire(); + } + + cstatic void Finish(__audetail::InitOnceABI *pInitOnce) + { + pInitOnce->_Finish(); + } + + /** + * Example usage: + * + * static AuInitOnceSmall gSmall; // or static AuInitOnce gInitOnce; + * static_assert(sizeof(gSmall) == 8); + * + * if (AuThreading::InitOnceLocker::TryLock(&gSmall)) + * { + * // My non-AuInitOnce::TryInit/Call() callback logic here + * AuThreading::InitOnceLocker::Finish(&gSmall); + * } + * else + * { + * gSmall.Wait(); + * } + */ + }; +} + +namespace __audetail +{ + bool InitOnceABI::IsUninitialized() + { + return (AuAtomicLoad(&this->uToken_) & 1) == 0; + } + + bool InitOnceABI::IsReady() + { + return (AuAtomicLoad(&this->uToken_) & 2) != 0; + } + + bool InitOnceABI::TrySet() + { + if (this->_TryAcquire()) + { + this->_Finish(); + return true; + } + else + { + return false; + } + } + + void InitOnceABI::Wait() + { + this->_LockAbsNS(0); + } + + bool InitOnceABI::_TryAcquire() + { + return AuAtomicTestAndSet(&this->uToken_, 0) == 0; + } + + void InitOnceABI::_Finish() + { + AuAtomicSet(&this->uToken_, 1); + this->_Wakeup(); + } + + void InitOnceABI::_Wakeup() + { + if (auto uSleepers = AuAtomicLoad(&this->uSleepers_)) + { + Aurora::Threading::WakeNOnAddress((const void *)&this->uToken_, uSleepers); + } + } + + bool InitOnceABI::_LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) + { + auto uCurrent = AuAtomicLoad(&this->uToken_); + + while ((uCurrent & 2) == 0) + { + AuAtomicAdd(&this->uSleepers_, 1u); + bool bRet = Aurora::Threading::WaitOnAddressSteady((const void *)&this->uToken_, &uCurrent, sizeof(uCurrent), qwAbsTimeoutInNs); + AuAtomicSub(&this->uSleepers_, 1u); + + if (!bRet) + { + return this->IsReady(); + } + + uCurrent = AuAtomicLoad(&this->uToken_); + } + + return true; + } } \ No newline at end of file