/*** Copyright (C) 2021-2024 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Event.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "AuEvent.hpp" #include "SMTYield.hpp" #include "../AuWakeInternal.hpp" namespace Aurora::Threading::Primitives { EventImpl::EventImpl(bool bTriggered, bool bAtomicRelease, bool bPermitMultipleTriggers) : bTriggered_(bTriggered), bAtomicRelease_(bAtomicRelease), bPermitMultipleTriggers_(bPermitMultipleTriggers) {} EventImpl::~EventImpl() {} bool EventImpl::Init() { return true; } bool EventImpl::LockMS(AuUInt64 uTimeout /*=0*/) { return this->LockNS(AuMSToNS(uTimeout)); } bool EventImpl::LockNS(AuUInt64 uTimeout /*=0*/) { AuInt64 uStartTime {}; AuInt64 uEndTime {}; if (this->AtomicIsEventSetLogicNoSpinNoLock()) { return true; } if (uTimeout) { uStartTime = Time::SteadyClockNS(); uEndTime = uStartTime + uTimeout; } if (gPreferFutexEvent) { auto pSleepCounter = this->GetSleepCounter(); while (!this->AtomicIsEventSetLogicNoSpinNoLock()) { bool bStatus {}; EventBits bits; bits.state = AuAtomicLoad(&this->state_); if (bits.bTriggered) { continue; } AuAtomicAdd(pSleepCounter, 1u); bStatus = InternalLTSWaitOnAddressHighRes(&this->state_, &bits.state, sizeof(bits.state), uEndTime); AuAtomicSub(pSleepCounter, 1u); if (!bStatus) { return false; } } } else { AU_LOCK_GUARD(this->mutex_); while (!AtomicIsEventSetLogicNoSpinNoLock()) { AuUInt32 uTimeoutNS {}; if (uTimeout) { uStartTime = Time::SteadyClockNS(); if (uStartTime >= uEndTime) { return false; } uTimeoutNS = uEndTime - uStartTime; } if (!this->condition_.WaitForSignalNsEx(&this->mutex_, uTimeoutNS)) { continue; } } } return true; } bool EventImpl::LockAbsNS(AuUInt64 uEndTime) { if (this->AtomicIsEventSetLogicNoSpinNoLock()) { return true; } if (gPreferFutexEvent) { auto pSleepCounter = this->GetSleepCounter(); while (!this->AtomicIsEventSetLogicNoSpinNoLock()) { bool bStatus {}; EventBits bits; bits.state = AuAtomicLoad(&this->state_); if (bits.bTriggered) { continue; } AuAtomicAdd(pSleepCounter, 1u); bStatus = InternalLTSWaitOnAddressHighRes(&this->state_, &bits.state, sizeof(bits.state), uEndTime); AuAtomicSub(pSleepCounter, 1u); if (!bStatus) { return false; } } } else { AU_LOCK_GUARD(this->mutex_); while (!AtomicIsEventSetLogicNoSpinNoLock()) { AuUInt32 uTimeoutNS {}; if (uEndTime) { auto uStartTime = Time::SteadyClockNS(); if (uStartTime >= uEndTime) { return false; } uTimeoutNS = uEndTime - uStartTime; } if (!this->condition_.WaitForSignalNsEx(&this->mutex_, uTimeoutNS)) { continue; } } } return true; } bool EventImpl::TryLock() { return AtomicIsEventSetLogicNoSpinNoLock(); } bool EventImpl::AtomicIsEventSetLogicNoSpinNoLock() { EventBits bits; bits.state = AuAtomicLoad(&this->state_); if (bits.bAtomicRelease) { if (bits.bTriggered) { auto next = bits; next.bTriggered = 0; return AuAtomicCompareExchange(&this->state_, next.state, bits.state) == bits.state; } else { return false; } } else { return bits.bTriggered; } } void EventImpl::Reset() { while (true) { EventBits bits, next; bits.state = AuAtomicLoad(&this->state_); if (!bits.bTriggered) { break; } next.state = bits.state; next.bTriggered = 0; if (AuAtomicCompareExchange(&this->state_, next.state, bits.state) == bits.state) { break; } } } void EventImpl::Set() { EventBits bits, next; while (true) { bits.state = AuAtomicLoad(&this->state_); // TODO: runtime config option to turn this into a handable exception SysAssertExp((bits.bPermitMultipleTriggers) || (!bits.bTriggered), "Can not trigger an awake event object"); next.state = bits.state; next.bTriggered = 1; if (AuAtomicCompareExchange(&this->state_, next.state, bits.state) == bits.state) { break; } } this->DoSignal(bits); } bool EventImpl::TrySet() { EventBits bits, next; while (true) { bits.state = AuAtomicLoad(&this->state_); if (!bits.bPermitMultipleTriggers) { if (bits.bTriggered) { return false; } } next.state = bits.state; next.bTriggered = 1; if (AuAtomicCompareExchange(&this->state_, next.state, bits.state) == bits.state) { break; } } this->DoSignal(bits); return true; } void EventImpl::DoSignal(const EventBits &bits) { if (gPreferFutexEvent) { if (!AuAtomicLoad(GetSleepCounter())) { return; } if (bits.bAtomicRelease) { InternalLTSWakeOne(&this->state_); } else { InternalLTSWakeAll(&this->state_); } } else { this->mutex_.Lock(); this->mutex_.Unlock(); if (bits.bAtomicRelease) { this->condition_.Signal(); } else { this->condition_.Broadcast(); } } } bool EventImpl::HasOSHandle(AuMach &mach) { mach = this->bAtomicRelease_ ? 0xFF69421 : 0; return false; } bool EventImpl::HasLockImplementation() { return true; } void EventImpl::Lock() { auto ok = LockNS(0); SysAssert(ok); } void EventImpl::Unlock() { // Unlock is always a NOP; inverse of a lock/wait is nothing } AuUInt32 *EventImpl::GetSleepCounter() { return (AuUInt32 *)&this->condition_; } AUKN_SYM IEvent *EventNew(bool bTriggered, bool bAtomicRelease, bool bPermitMultipleTriggers) { auto event = _new EventImpl(bTriggered, bAtomicRelease, bPermitMultipleTriggers); if (!event) { return nullptr; } if (!event->Init()) { EventRelease(event); return nullptr; } return event; } AUKN_SYM void EventRelease(IEvent *pEvent) { AuSafeDelete(pEvent); } AUROXTL_INTERFACE_SOO_SRC_EX(AURORA_SYMBOL_EXPORT, Event, EventImpl, (bool, bTriggered), (bool, bAtomicRelease), (bool, bPermitMultipleTriggers)) }