290 lines
7.2 KiB
C++
290 lines
7.2 KiB
C++
|
/***
|
||
|
Copyright (C) 2021-2024 Jamie Reece Wilson (a/k/a "Reece"). All rights reserved.
|
||
|
|
||
|
File: LSTimerNoKernelScheduler.cpp
|
||
|
Date: 2024-09-08
|
||
|
Author: Reece
|
||
|
***/
|
||
|
#include <Source/RuntimeInternal.hpp>
|
||
|
#include <Source/Async/IAsyncRunnable.hpp>
|
||
|
#include <Source/Async/AuSchedular.hpp>
|
||
|
#include "LSTimerNoKernelScheduler.hpp"
|
||
|
#include <Source/Threading/Primitives/SMTYield.hpp>
|
||
|
|
||
|
namespace Aurora::IO::Loop
|
||
|
{
|
||
|
void LSTimerIPDispatcher::RunAsync()
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->mutex);
|
||
|
if (this->pParent)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->pParent->cs);
|
||
|
this->pParent->Set();
|
||
|
this->pParent = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LSTimerIP::LSTimerIP(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot) :
|
||
|
bSingleshot(bSingleshot),
|
||
|
reschedStepNsOrZero_(AuMSToNS<AuUInt64>(reschedStepMsOrZero)),
|
||
|
maxIterationsOrZero_(maxIterationsOrZero)
|
||
|
{
|
||
|
this->targetTime_ = AuTime::CurrentClockNS();
|
||
|
}
|
||
|
|
||
|
LSTimerIP::~LSTimerIP()
|
||
|
{
|
||
|
if (this->pDispatcher)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->pDispatcher->mutex);
|
||
|
this->pDispatcher->pParent = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTimeWall(AuUInt64 absTimeMs)
|
||
|
{
|
||
|
this->UpdateTimeWallNs(AuMSToNS<AuUInt64>(absTimeMs));
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTimeWallNs(AuUInt64 absTimeNs)
|
||
|
{
|
||
|
this->UpdateTimeSteadyNs(absTimeNs - AuTime::CurrentClockNS() + AuTime::SteadyClockNS());
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTimeSteady(AuUInt64 absTimeMs)
|
||
|
{
|
||
|
this->UpdateTimeSteadyNs(AuMSToNS<AuUInt64>(absTimeMs));
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero)
|
||
|
{
|
||
|
this->UpdateTickRateIfAnyNs(AuMSToNS<AuUInt64>(reschedStepMsOrZero), maxIterationsOrZero);
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->cs);
|
||
|
|
||
|
this->Stop();
|
||
|
|
||
|
this->reschedStepNsOrZero_ = reschedStepNsOrZero;
|
||
|
this->maxIterationsOrZero_ = maxIterationsOrZero;
|
||
|
this->count_ = 0;
|
||
|
|
||
|
if (this->targetTime_)
|
||
|
{
|
||
|
this->UpdateTimeSteadyNs(this->targetTime_);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::UpdateTimeSteadyNs(AuUInt64 absTimeNs)
|
||
|
{
|
||
|
AU_DEBUG_MEMCRUNCH;
|
||
|
AU_LOCK_GUARD(this->cs);
|
||
|
|
||
|
AuSPtr<LSTimerIPDispatcher> pNext;
|
||
|
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->dispatcherMutex);
|
||
|
|
||
|
if (this->pDispatcher)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->pDispatcher->mutex);
|
||
|
|
||
|
if (this->pDispatcher->pParent)
|
||
|
{
|
||
|
// abort
|
||
|
this->pDispatcher->pParent = nullptr;
|
||
|
pNext = this->pDispatcher = AuMakeShared<LSTimerIPDispatcher>();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pNext = this->pDispatcher;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pNext = this->pDispatcher = AuMakeShared<LSTimerIPDispatcher>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SysAssert(pNext, "impossible OOM during timer op");
|
||
|
|
||
|
pNext->pParent = this;
|
||
|
AuAsync::Schedule(absTimeNs, nullptr, {}, pNext);
|
||
|
this->targetTime_ = absTimeNs;
|
||
|
}
|
||
|
|
||
|
void LSTimerIP::Stop()
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->cs);
|
||
|
|
||
|
AU_LOCK_GUARD(this->dispatcherMutex);
|
||
|
if (this->pDispatcher)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->pDispatcher->mutex);
|
||
|
this->pDispatcher->pParent = nullptr;
|
||
|
this->pDispatcher = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::IsSignaledNonblocking()
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->cs);
|
||
|
|
||
|
auto uCurrentTime = AuTime::SteadyClockNS();
|
||
|
auto uNextTickTarget = AuAtomicLoad(&this->targetTime_);
|
||
|
|
||
|
if (uCurrentTime < uNextTickTarget)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto uNextTick = uNextTickTarget + this->reschedStepNsOrZero_;
|
||
|
|
||
|
if (uCurrentTime > uNextTick)
|
||
|
{
|
||
|
this->targetTime_ = uCurrentTime;
|
||
|
uNextTick = uCurrentTime + this->reschedStepNsOrZero_;
|
||
|
}
|
||
|
|
||
|
if (!this->maxIterationsOrZero_)
|
||
|
{
|
||
|
this->UpdateTimeSteadyNs(uNextTick);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (AuAtomicAdd<AuUInt32>(&this->count_, 1) <= this->maxIterationsOrZero_)
|
||
|
{
|
||
|
this->UpdateTimeSteadyNs(uNextTick);
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Stop();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::OnTrigger(AuUInt handle)
|
||
|
{
|
||
|
AU_LOCK_GUARD(this->cs);
|
||
|
|
||
|
if (IsSignaledNoSpinIfUserland())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LSEvent::Reset();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::TryTakeWaitNS(AuUInt64 uTimeoutAbs)
|
||
|
{
|
||
|
auto uNextTime = AuAtomicLoad(&this->targetTime_);
|
||
|
|
||
|
if (!uNextTime)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (this->IsSignaledNonblocking())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto uNow = AuTime::CurrentClockNS();
|
||
|
if (uNow >= uNextTime)
|
||
|
{
|
||
|
return this->IsSignaledNonblocking();
|
||
|
}
|
||
|
|
||
|
// Win32 note: there is quite a bit of hackery behind SleepNs
|
||
|
// Win 7 and lower may spin for a bit
|
||
|
// Various CPUs may enter a user-space montior condition
|
||
|
// Dumb CPUs will SMT yield
|
||
|
// Alderlake et al can tpause
|
||
|
if (uTimeoutAbs)
|
||
|
{
|
||
|
AuThreading::SleepNs(AuMin(uNextTime, uTimeoutAbs) - uNow);
|
||
|
return this->IsSignaledNonblocking();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AuThreading::SleepNs(uNextTime - uNow);
|
||
|
return this->IsSignaledNonblocking();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ELoopSource LSTimerIP::GetType()
|
||
|
{
|
||
|
return ELoopSource::eSourceTimer;
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::TryInit()
|
||
|
{
|
||
|
return LSEvent::TryInit(false, false, true);
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::IsSignaled()
|
||
|
{
|
||
|
return this->TryTakeSpin();
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::IsSignaledNoSpinIfUserland()
|
||
|
{
|
||
|
return this->IsSignaledNonblocking();
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::TryTakeNoSpin()
|
||
|
{
|
||
|
return this->IsSignaledNonblocking();
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::TryTakeSpin()
|
||
|
{
|
||
|
return Threading::Primitives::DoTryIf([&]
|
||
|
{
|
||
|
return this->TryTakeNoSpin();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bool LSTimerIP::TryTake()
|
||
|
{
|
||
|
return this->TryTakeSpin();
|
||
|
}
|
||
|
|
||
|
AuSPtr<ITimer> NewLSTimerIP(AuUInt64 qwAbsStartTimeMs, AuUInt32 dwReschedStepMsOrZero, AuUInt32 dwMaxIterationsOrZero, bool bSingleshot, bool bIsStartTimeSteady)
|
||
|
{
|
||
|
AuAsync::StartSched2();
|
||
|
|
||
|
auto pTimer = AuMakeShared<LSTimerIP>(dwReschedStepMsOrZero, dwMaxIterationsOrZero, bSingleshot);
|
||
|
if (!pTimer)
|
||
|
{
|
||
|
SysPushErrorMem();
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
if (!pTimer->TryInit())
|
||
|
{
|
||
|
SysPushErrorMem();
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
if (qwAbsStartTimeMs)
|
||
|
{
|
||
|
if (bIsStartTimeSteady)
|
||
|
{
|
||
|
pTimer->UpdateTimeSteady(qwAbsStartTimeMs);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTimer->UpdateTimeWall(qwAbsStartTimeMs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return pTimer;
|
||
|
}
|
||
|
}
|