/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: LSTimer.NT.cpp Date: 2021-10-1 Author: Reece ***/ #include #include "LSTimer.hpp" #include namespace Aurora::Loop { struct LSTimer : ITimer, LSHandle { LSTimer(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot, HANDLE handle); ~LSTimer(); virtual void UpdateTime(AuUInt64 absTimeMs) override; virtual void UpdateTimeNs(AuUInt64 absTimeNs) override; virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) override; virtual void Stop() override; virtual bool OnTrigger(AuUInt handle) override; virtual bool IsSignaled() override; virtual bool WaitOn(AuUInt32 timeout) override; virtual ELoopSource GetType() override; void UpdateTimeInternal(AuUInt64 absTimeNs); private: AuUInt64 reschedStepNsOrZero_ {0}; AuUInt32 maxIterationsOrZero_ {0}; AuUInt64 targetTime_ {0}; AuUInt32 count_ {0}; bool bSingleshot {}; }; LSTimer::LSTimer(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot, HANDLE handle) : LSHandle(AuReinterpretCast(handle)), reschedStepNsOrZero_(AuMSToNS(reschedStepMsOrZero)), bSingleshot(bSingleshot), maxIterationsOrZero_(maxIterationsOrZero) { this->targetTime_ = AuTime::ConvertTimestamp(AuTime::CurrentClockMS()); } LSTimer::~LSTimer() { auto handle = AuReinterpretCast(this->handle); AuWin32CloseHandle(handle); this->handle = kInvalidHandle; } void LSTimer::UpdateTime(AuUInt64 absTimeMs) { this->targetTime_ = AuTime::ConvertTimestamp(absTimeMs); UpdateTimeInternal(this->targetTime_); } void LSTimer::UpdateTimeNs(AuUInt64 absTimeNs) { this->targetTime_ = AuTime::ConvertTimestampNs(absTimeNs); UpdateTimeInternal(this->targetTime_); } void LSTimer::UpdateTimeInternal(AuUInt64 absTimeNs) { LARGE_INTEGER i; this->targetTime_ = absTimeNs; i.QuadPart = absTimeNs; SysAssert(::SetWaitableTimer(AuReinterpretCast(this->handle), &i, 0, nullptr, nullptr, false)); } void LSTimer::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) { this->reschedStepNsOrZero_ = AuMSToNS(reschedStepMsOrZero); this->maxIterationsOrZero_ = maxIterationsOrZero; this->count_ = 0; } void LSTimer::UpdateTickRateIfAnyNs(AuUInt64 reschedStepNsOrZero, AuUInt32 maxIterationsOrZero) { this->reschedStepNsOrZero_ = reschedStepNsOrZero; this->maxIterationsOrZero_ = maxIterationsOrZero; this->count_ = 0; } bool LSTimer::OnTrigger(AuUInt handle) { SysAssert(this->targetTime_ <= AuTime::ConvertTimestampNs(AuTime::CurrentClockNS())); if (!this->reschedStepNsOrZero_) { return true; } if (!this->maxIterationsOrZero_) { this->UpdateTimeInternal(this->targetTime_ + (AuUInt64(this->reschedStepNsOrZero_) / 100ULL)); return true; } bool ok = AuAtomicAdd(&this->count_, 1) <= this->maxIterationsOrZero_; if (ok) { this->UpdateTimeInternal(this->targetTime_ + (AuUInt64(this->reschedStepNsOrZero_) / 100ULL)); return true; } else { Stop(); return false; } } void LSTimer::Stop() { bool bSuccess = ::CancelWaitableTimer(AuReinterpretCast(handle)); SysAssertDbg(bSuccess); LARGE_INTEGER i; i.QuadPart = AuNumericLimits::max(); SysAssert(::SetWaitableTimer(AuReinterpretCast(this->handle), &i, 0, nullptr, nullptr, false)); } bool LSTimer::IsSignaled() { return LSHandle::IsSignaled(); } bool LSTimer::WaitOn(AuUInt32 timeout) { return LSHandle::WaitOn(timeout); } ELoopSource LSTimer::GetType() { return ELoopSource::eSourceTimer; } AUKN_SYM AuSPtr NewLSTimer(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, bool bSingleshot) { // https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-setthreadpoolwait // TODO: Is that any better with an event? auto handle = CreateWaitableTimerW(nullptr, !bSingleshot, nullptr); if (!handle) { SysPushErrorIO(); return {}; } auto object = AuMakeShared(reschedStepMsOrZero, maxIterationsOrZero, bSingleshot, handle); if (!object) { SysPushErrorMem(); AuWin32CloseHandle(handle); return {}; } if (absStartTimeMs) { object->UpdateTime(absStartTimeMs); } return object; } }