AuroraRuntime/Source/IO/Loop/LSTimerNoKernelScheduler.cpp

290 lines
7.2 KiB
C++
Raw Normal View History

/***
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;
}
}