/*** 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::Loop { // This is like a CF RunLoop with the input parameters of NT's WaitMultipleObjects // This shouldn't be too much heavier than CF's libevent/NT abstraction in the style of a kevent interface with objects 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 */ virtual bool SourceAdd(const AuSPtr &source) = 0; /** * @brief Same behaviour as SourceAdd * @param source * @param timeoutMS * @return */ virtual bool SourceAddWithTimeout(const AuSPtr &source, AuUInt32 timeoutMS) = 0; /** * @brief Remove operations include commit. You do not need to follow a remove with a commit. * Sources are defacto poped unless subscriber returns false indicating repeated lock attempts are wanted. * Should no subscriber be registered, the loop source will not be automatically removed * @param source */ virtual bool SourceRemove(const AuSPtr &source) = 0; /** * @brief Updates the OS watchdog list cache concept after Source[Remove/Add[WithTimeout]] * @return */ virtual bool Commit() = 0; /** * @brief * @return the amount of loop sources added to the queue */ 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 * Returning false from a ILoopSourceSubscriber will prevent eviction, resulting in continued ILoopSourceSubscriber callbacks to all subscribers * * @param source * @param subscriber * @return */ virtual bool AddCallback(const AuSPtr &source, const AuSPtr &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 * @param source * @param subscriber * @return */ virtual bool AddCallbackEx(const AuSPtr &source, const AuSPtr &subscriber) = 0; /** * @brief Near identical behaviour of the extended AddCallback method. * Registers a callback to handle all loop source signaled events. * @param subscriber * @return */ virtual bool AddCallback(const AuSPtr &subscriber) = 0; /** * @brief Nonblocking wait-any for all objects in the loop queue * @warning (may yield to ILoopSourceSubscriber delegate on the current context) * @return */ virtual bool IsSignaled() = 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 * @return * @warning (may yield to ILoopSourceSubscriber delegate on the current context) */ virtual bool WaitAll (AuUInt32 timeout = 0) = 0; /** * @brief Waits on all the loop sources until at least one is signaled * @param timeout * @return * @warning (may yield to ILoopSourceSubscriber delegate on the current context) */ virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0; /** * @brief * @param timeout * @return * @warning (may yield to ILoopSourceSubscriber delegate on the current context) */ virtual AuList> 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 NewLoopQueue(); }