/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: LSTimer.Linux.cpp Date: 2021-10-1 Author: Reece ***/ #include #include "LSTimer.hpp" #include "LSFromFdNonblocking.hpp" #include #include namespace Aurora::Loop { struct LSTimer : ITimer, LSHandle { LSTimer(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, int handle); ~LSTimer(); virtual void UpdateTime(AuUInt64 absTimeMs) override; virtual void UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, 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 absTimeMs); private: AuUInt32 reschedStepMsOrZero_ {}, maxIterationsOrZero_ {}; AuUInt64 targetTime_ {}; AuUInt32 count_ {}; bool IsSignaledNonblocking(); }; LSTimer::LSTimer(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero, int handle) : LSHandle(handle), reschedStepMsOrZero_(reschedStepMsOrZero), maxIterationsOrZero_(maxIterationsOrZero) { this->targetTime_ = AuTime::CurrentClockMS(); } LSTimer::~LSTimer() { if ((this->handle != 0) && (this->handle != -1)) { ::close(AuExchange(this->handle, -1)); } } void LSTimer::UpdateTime(AuUInt64 absTimeMs) { this->targetTime_ = absTimeMs; UpdateTimeInternal(this->targetTime_); } void LSTimer::UpdateTimeInternal(AuUInt64 absTimeMs) { this->targetTime_ = absTimeMs; itimerspec spec {}; AuTime::auabsms2ts(&spec.it_value, this->targetTime_); AuTime::ms2ts(&spec.it_interval, this->reschedStepMsOrZero_); SysAssert(::timerfd_settime(this->handle, TFD_TIMER_ABSTIME, &spec, nullptr) == 0); } void LSTimer::UpdateTickRateIfAny(AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) { this->reschedStepMsOrZero_ = reschedStepMsOrZero; this->maxIterationsOrZero_ = maxIterationsOrZero; this->count_ = 0; this->UpdateTime(this->targetTime_); } bool LSTimer::OnTrigger(AuUInt handle) { return IsSignaledNonblocking(); } void LSTimer::Stop() { itimerspec spec {}; SysAssert(::timerfd_settime(this->handle, 0, &spec, nullptr) == 0); } bool LSTimer::IsSignaled() { return IsSignaledFromNonblockingImpl(this, this, &LSTimer::IsSignaledNonblocking); } bool LSTimer::WaitOn(AuUInt32 timeout) { return LSHandle::WaitOn(timeout); } ELoopSource LSTimer::GetType() { return ELoopSource::eSourceTimer; } bool LSTimer::IsSignaledNonblocking() { AuUInt64 oldSemaphoreValue {}; auto ok = ::read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8; if (ok && this->maxIterationsOrZero_) { ok = AuAtomicAdd(&this->count_, 1) <= this->maxIterationsOrZero_; if (ok) { // fall through } else { Stop(); return false; } } if (ok && !this->reschedStepMsOrZero_) { // Ensure non-step timers remain signaled despite the read above us caneling them this->UpdateTime(this->targetTime_); } return ok; } AUKN_SYM AuSPtr NewLSTimer(AuUInt64 absStartTimeMs, AuUInt32 reschedStepMsOrZero, AuUInt32 maxIterationsOrZero) { int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); if (fd == -1) { SysPushErrorIO(); return {}; } auto object = AuMakeShared(reschedStepMsOrZero, maxIterationsOrZero, fd); if (!object) { SysPushErrorMem(); ::close(fd); return {}; } object->UpdateTime(absStartTimeMs); return object; } }