AuroraRuntime/Include/Aurora/Loop/ILoopQueue.hpp

178 lines
8.3 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::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<ILoopSource> &source) = 0;
/**
* @brief Same behaviour as SourceAdd
* @param source
* @param timeoutMS
* @return
*/
virtual bool SourceAddWithTimeout(const AuSPtr<ILoopSource> &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<ILoopSource> &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<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
* @param source
* @param subscriber
* @return
*/
virtual bool AddCallbackEx(const AuSPtr<ILoopSource> &source, const AuSPtr<ILoopSourceSubscriberEx> &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<ILoopSourceSubscriber> &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<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();
}