AuroraRuntime/Include/Aurora/IO/Loop/ILoopQueue.hpp
2023-12-31 22:39:31 +00:00

227 lines
12 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: ILoopQueue.hpp
Date: 2022-2-12
Author: Reece
***/
#pragma once
namespace Aurora::IO::Loop
{
// ILoopQueue's are comparable to a CoreFoundation RunLoop with the input parameters of NT's WaitMultipleObjects and RegisterWaitForSingleObject
// This shouldn't be too much heavier than CF's libevent/NT abstraction
struct ILoopQueue
{
/**
* @brief
* [*] Source add/remove operations should be followed by a Commit() to commit the changes
*
* [*] Calling Source[Add/Remove] during a Wait[Any/All[Ex]] will cause the wait function to
* return false, allowing for the caller to rewait on the updated queue, or to allow
* the thread to account for the object-was-invalidated condition
*
* @param source
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
* @warning it is unsafe to add an ILoopSource more than once.
*/
virtual bool SourceAdd(const AuSPtr<ILoopSource> &source) = 0;
/**
* @brief Same behaviour as SourceAdd
* Note: Timeout is specified in MS and polled during WaitAny/All failure or completition
* Objects awaiting timeout will not preempt the loop
* @param source
* @param timeoutMS
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
* @warning it is unsafe to add an ILoopSource more than once.
*/
virtual bool SourceAddWithTimeout(const AuSPtr<ILoopSource> &source, AuUInt32 timeoutMS) = 0;
/**
* @brief Manually request a loop source dequeue to be scheduled on next ::Commit.
* Loop queues are generally removed by registering callbacks, once upon completion,
* the internal state hits a zero reference count condition, atomically removing the source.
* See: AddCallback
* @param source
* @note thread safe / nonblocking | can be called alongside any other function marked as such
*/
virtual bool SourceRemove(const AuSPtr<ILoopSource> &source) = 0;
/**
* @brief Updates the OS watch list after Source[Remove/Add[WithTimeout]]
* Commits may occur while another thread is waiting on the loop queue.
* In those circumstances, they are internally preempted and rescheduled.
* @return
* @note thread safe
*/
virtual bool Commit() = 0;
/**
* @brief
* @return the amount of loop sources added to the queue
* @note thread safe | can be called alongside any other function marked as such
*/
virtual AuUInt32 GetSourceCount() = 0;
/**
* @brief Appends a callback handler to be paired with a loop source
*
* Once a loop source become signaled, and a Wait[Any/All] function call is made, loop source subscribers are called
* to filter the final should-remove operation. The final popped objects are returned from Wait[Any[Ex]] and forgotten.
*
* If no ILoopSourceSubscribers are registered, the edge case behaviour is that loop sources will not be automatically
* dequeued. No user code explicitly told us to do anything other than wait, so the default behaviour of Wait[Any[Ex]]
* shall be to not evict loop sources, until at least one callback is registered, or until SourceRemove is called.
*
* Returning true from a ILoopSourceSubscriber will allow the ILoopQueue to evict the loop source and callback.
* [*] A call to ::Commit is not required.
* [*] Other ILoopSourceSubscribers will be consulted on whether or not the loop source shall be evicted entirely.
*
* Returning false from a ILoopSourceSubscriber will prevent eviction, resulting in continued callbacks in the next tick.
* [*] Other ILoopSourceSubscribers will be consulted on whether or not the loop source shall be evicted entirely.
*
* Once all callbacks are evicited, loop sources are automatically dequeued; however, objects without a callback shall not be dequeued.
* Loop Sources may be kept around by a global ILoopSourceSubscriber, past this stage. See: other AddCallback function.
*
* @param source
* @param subscriber
* @return
* @note thread safe | can be called alongside any other function marked as such
*/
virtual bool AddCallback(const AuSPtr<ILoopSource> &source, const AuSPtr<ILoopSourceSubscriber> &subscriber) = 0;
/**
* @brief Identical behaviour to that of the basic method. In addition, ILoopSourceSubscriberEx::OnTimeout is called
* on violation of the timeout provided to SourceAddWithTimeout, and the OnFinished method has been extended to
* accept a position number. This is the position of the subscriber relative to the signal-state change tick.
* If the loop source is singleshot, and multiple callbacks are registered, all callbacks will be called with
* a position counter. Furthermore, extended callbacks are called __before__ non-ex variants.
* @param source
* @param subscriber
* @return
* @note thread safe | can be called alongside any other function marked as such
*/
virtual bool AddCallbackEx(const AuSPtr<ILoopSource> &source, const AuSPtr<ILoopSourceSubscriberEx> &subscriber) = 0;
/**
* @brief
* Registers a callback to be called when either, an loop source has no callbacks, or a loop source could be dequeued.
* Returning false from OnFinished shall prevent the loop queue from decommitting the loop source.
* @param subscriber
* @return
* @note thread safe | can be called alongside any other function marked as such
*/
virtual bool AddCallback(const AuSPtr<ILoopSourceSubscriber> &subscriber) = 0;
/**
* @brief Nonblocking check for alert objects in the loop queue
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
*/
virtual bool IsSignaledPeek() = 0;
/**
* @brief Nonblocking wait-any for all objects in the loop queue
* @warning may yield to ILoopSourceSubscriber delegate on the current context
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
*/
virtual AuUInt32 PumpNonblocking() = 0;
/**
* @brief Nonblocking wait-any for all objects in the loop queue
* @warning may yield to ILoopSourceSubscriber delegate on the current context
* @return
* @note thread safe / nonblocking | can be called alongside any other function marked as such
*/
virtual AuList<AuSPtr<ILoopSource>> PumpNonblockingEx() = 0;
/**
* @brief Waits on all the submitted loop sources until they are all complete or until the timeout has finished.
* Note: the completion of another Wait[All/Any[Ex]] call may result in a
* @param timeout timeout in MS, zero = indefinite
* @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @warning (thread safety might be limited to blocking callers of the object for.now. Linux might work. NT needs an APC hack.)
*/
virtual bool WaitAll (AuUInt32 timeout = 0) = 0;
/**
* @brief Waits on all the loop sources until at least one is signaled.
* @param timeout timeout in MS, zero = indefinite.
* Use IsSignaledPump for nonblocking.
* @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
* @note thread safe | can be called alongside any other function marked as such
*/
virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0;
/**
* @brief Waits on all the loop sources until at least one is signaled.
* @param timeout timeout in MS, zero = indefinite.
* Use IsSignaledPump for nonblocking.
* @return
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
* @note thread safe | can be called alongside any other function marked as such
*/
virtual AuList<AuSPtr<ILoopSource>> WaitAnyEx(AuUInt32 timeout = 0) = 0;
/**
* @brief
* Mostly relevant on Windows where the limit is 64 versus horrible UNIX-y socket
* optimized interfaces that can handle thousands of fds
*
* In large loop queues, a maximum of
* `sectionTickTime * (sectionDequeCount + clamp(platformSanity, sectionDequeCount))`
* kernel objects can be checked within a given time frame.
*
* What this effectively does is iterate over a subdivided work queue, aligned
* to platformSanity (NT: 64); [i, i+platform sanity] objects can then be waited on.
*
* On timeout of sectionTickTime,
* [i, i+sectionDequeCount] of the tick could be checked, however, we know
* [i, i+platform sanity] timeouted so, in the event of a timeout, we can
* asynchronously check [i+platform sanity, ...] to be nicer faster. Since
* we've already spent the maximum allocated time to poll on a mere subset,
* we may as well desperately check sectionDequeCount worth of objects, and
* write-off the time spent blocking. That way, we get upto platform sanity
* of is-signaled checks for free in the worst case scenario. The perspective
* that the overshoot is expensive doesn't make sense. The is-signaled
* check is basically free and the initial timeout/yield-to-kern over set 0
* was the bottleneck
*
* Otherwise on success, we know to check in the range of [i, platform sanity
* for at least one alerted object.
*
* Internal logic will dequeue platform coefficient aligned handles, and the timeout
* optimization shall check [i+platform sanity, i+platform sanity+sectionDequeCount].
* 1 handle =/= 1 loop source, tho usually 1 loop source ~= 1 handle
*
* tl'dr:
* [*] dont use if you cant stall for sectionTickTime if sectionDequeCount of your
* loop sources are unsignaled
* [*] chug paths are intended for observer threads and not for maintaining high
* performance. no sane traditional application would ever have 64+ signalable
* objects stuck in a loop. though if you do, and you dont configure anything,
* the worst case scenario is, WaitAny can take upto a millisecond to discover
* a signaled loop source.
*
* @param sectionTickTime
* @param sectionDequeCount
*/
virtual void ChugPathConfigure(AuUInt32 sectionTickTime, AuSInt sectionDequeCount) = 0;
/**
* @brief Hints that the calling program understands the kernel shouldnt schedule tne entire source list, and instead, we should long poll
*/
virtual void ChugHint(bool value) = 0;
};
AUKN_SYM AuSPtr<ILoopQueue> NewLoopQueue();
}