345 lines
7.2 KiB
C++
345 lines
7.2 KiB
C++
/***
|
|
Copyright (C) 2023 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: InitOnce.hpp
|
|
Date: 2023-09-17
|
|
Author: Reece
|
|
***/
|
|
#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 bool _TryAcquireFailable();
|
|
|
|
inline void _Finish();
|
|
|
|
inline void _Wakeup();
|
|
|
|
inline void _FinishFailed();
|
|
|
|
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 __audetail::InitOnceABI
|
|
{
|
|
inline bool IsUninitialized()
|
|
{
|
|
return InitOnceABI::IsUninitialized();
|
|
}
|
|
|
|
inline bool IsReady()
|
|
{
|
|
return InitOnceABI::IsReady();
|
|
}
|
|
|
|
inline bool TrySet()
|
|
{
|
|
return InitOnceABI::TrySet();
|
|
}
|
|
|
|
template <typename Callable>
|
|
bool TryInit(const Callable &callback)
|
|
{
|
|
if (InitOnceABI::_TryAcquire())
|
|
{
|
|
callback();
|
|
InitOnceABI::_Finish();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template <typename Callable>
|
|
void Call(const Callable &callback)
|
|
{
|
|
if (this->TryInit(callback))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this->IsReady())
|
|
{
|
|
return;
|
|
}
|
|
|
|
this->Wait();
|
|
}
|
|
|
|
template <typename Callable>
|
|
bool TryCall(const Callable &callback, bool bWait = true)
|
|
{
|
|
bool bRet {};
|
|
|
|
if (InitOnceABI::_TryAcquireFailable())
|
|
{
|
|
if (callback())
|
|
{
|
|
InitOnceABI::_Finish();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
InitOnceABI::_FinishFailed();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((!(bRet = this->IsReady())) &&
|
|
(bWait))
|
|
{
|
|
this->Wait();
|
|
bRet = this->IsReady();
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
inline void Wait()
|
|
{
|
|
InitOnceABI::Wait();
|
|
}
|
|
|
|
inline IWaitable *ToBarrier()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
protected:
|
|
|
|
inline bool HasOSHandle(AuMach &mach) override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inline bool HasLockImplementation() override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
inline void Lock() override
|
|
{
|
|
this->LockAbsNS(0);
|
|
}
|
|
|
|
inline bool LockNS(AuUInt64 qwTimeoutInNs) override
|
|
{
|
|
if (this->IsReady())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return IWaitable::LockNS(qwTimeoutInNs);
|
|
}
|
|
|
|
inline bool LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/) override
|
|
{
|
|
return this->_LockAbsNS(qwAbsTimeoutInNs);
|
|
}
|
|
|
|
inline bool TryLock() override
|
|
{
|
|
return this->IsReady();
|
|
}
|
|
|
|
inline void Unlock() override
|
|
{
|
|
|
|
}
|
|
|
|
private:
|
|
friend struct InitOnceLocker;
|
|
};
|
|
|
|
struct InitOnceLocker
|
|
{
|
|
cstatic bool TryLock(InitOnce *pInitOnce, bool bFailable = false)
|
|
{
|
|
if (bFailable)
|
|
{
|
|
return pInitOnce->_TryAcquireFailable();
|
|
}
|
|
else
|
|
{
|
|
return pInitOnce->_TryAcquire();
|
|
}
|
|
}
|
|
|
|
cstatic void Finish(InitOnce *pInitOnce, bool bFailed = false)
|
|
{
|
|
if (bFailed)
|
|
{
|
|
pInitOnce->_FinishFailed();
|
|
}
|
|
else
|
|
{
|
|
pInitOnce->_Finish();
|
|
}
|
|
}
|
|
|
|
cstatic bool TryLock(__audetail::InitOnceABI *pInitOnce, bool bFailable = false)
|
|
{
|
|
if (bFailable)
|
|
{
|
|
return pInitOnce->_TryAcquireFailable();
|
|
}
|
|
else
|
|
{
|
|
return pInitOnce->_TryAcquire();
|
|
}
|
|
}
|
|
|
|
cstatic void Finish(__audetail::InitOnceABI *pInitOnce, bool bFailed = false)
|
|
{
|
|
if (bFailed)
|
|
{
|
|
pInitOnce->_FinishFailed();
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool InitOnceABI::_TryAcquireFailable()
|
|
{
|
|
if (AuAtomicTestAndSet(&this->uToken_, 0) == 0)
|
|
{
|
|
AuAtomicUnset(&this->uToken_, 2);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void InitOnceABI::_FinishFailed()
|
|
{
|
|
AuAtomicSet(&this->uToken_, 2);
|
|
AuAtomicUnset(&this->uToken_, 0);
|
|
InitOnceABI::_Wakeup();
|
|
}
|
|
|
|
bool InitOnceABI::_LockAbsNS(AuUInt64 qwAbsTimeoutInNs /* = 0, infinity*/)
|
|
{
|
|
auto uCurrent = AuAtomicLoad(&this->uToken_);
|
|
|
|
while ((uCurrent & (2 | 4)) == 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;
|
|
}
|
|
} |