From d79de798d3680694d38f4224aded9a242a1bdaf6 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 2 Jul 2026 13:58:32 +0200 Subject: [PATCH] docs: document the event loop (EventLoopService, RegisterEventLoop, wheel-driver exclusivity) --- docs/articles/scheduler.md | 45 ++++++++++++++++++++++++ docs/tutorials/events-jobs-scheduling.md | 5 +++ src/SquidStd.Services.Core/README.md | 1 + 3 files changed, 51 insertions(+) diff --git a/docs/articles/scheduler.md b/docs/articles/scheduler.md index 1d30db98..1ee13abb 100644 --- a/docs/articles/scheduler.md +++ b/docs/articles/scheduler.md @@ -27,3 +27,48 @@ scheduler.Schedule("cleanup", "0 3 * * *", async ct => await DoCleanupAsync(ct); }); ``` + +## Event loop + +For applications that need a tight, frame-driven loop (game servers, simulations, real-time +processing), `SquidStd.Services.Core` provides `EventLoopService` — a dedicated background thread +(`SquidStd-EventLoop`) that, every frame: + +1. drains the `IMainThreadDispatcher` (deferred callbacks posted with `Post`), and +2. advances the timer wheel (`ITimerService.UpdateTicksDelta`), + +then sleeps `IdleSleepMs` (default 1 ms) when a frame produced no work. It exposes tick metrics +(`tick_count`, `tick_avg_ms`, `tick_max_ms`, `idle_sleeps_total`) under the `eventloop` provider and +logs a warning when a tick takes longer than `SlowTickThresholdMs` (default 250 ms). + +Register it (after `RegisterCoreServices`) with `RegisterEventLoop()`: + +```csharp +using DryIoc; +using SquidStd.Core.Interfaces.Threading; +using SquidStd.Services.Core.Extensions; + +container.RegisterEventLoop(); + +var loop = container.Resolve(); +Console.WriteLine($"{loop.TickCount} ticks, avg {loop.AverageTickMs:0.###} ms"); +``` + +Configure it via the `eventLoop` section (section keys are matched as registered, property names are +PascalCase): + +```yaml +eventLoop: + IdleCpuEnabled: true # sleep when a tick produced no work + IdleSleepMs: 1 # how long to sleep when idle + SlowTickThresholdMs: 250 # warn above this per-tick time +``` + +### Event loop vs. timer-wheel pump + +Both `EventLoopService` and `TimerWheelPumpService` advance the timer wheel, so they are **mutually +exclusive** — register exactly one. The exclusivity is structural: both implement the +`ITimerWheelDriver` marker, `RegisterEventLoop()` throws if a driver is already registered, and modules +that need the wheel (the worker manager, the mail poller) auto-register the pump only when no driver is +present. Use the pump for ordinary apps where a coarse periodic pump is enough; use the event loop when +you want the wheel advanced at frame-rate alongside dispatcher draining. diff --git a/docs/tutorials/events-jobs-scheduling.md b/docs/tutorials/events-jobs-scheduling.md index 80f3a594..c73fefa8 100644 --- a/docs/tutorials/events-jobs-scheduling.md +++ b/docs/tutorials/events-jobs-scheduling.md @@ -51,6 +51,11 @@ You'll see `received: hello`, `job ran on a worker thread`, and—if you let it The event bus dispatches to sync and async listeners; the job system is a fixed-size worker-thread pool; the cron scheduler is driven by the timer wheel (registered by `RegisterSchedulerServices`). +The timer wheel is advanced by a *driver*. `RegisterSchedulerServices()` uses `TimerWheelPumpService`, a +periodic background pump. Apps that need a frame-rate loop can instead call `RegisterEventLoop()`, which advances +the wheel and drains the main-thread dispatcher on a dedicated thread — see +[Scheduler → Event loop](../articles/scheduler.md#event-loop). The two are mutually exclusive: register exactly one. + ## See also - [SquidStd.Services.Core reference](../articles/services-core.md) diff --git a/src/SquidStd.Services.Core/README.md b/src/SquidStd.Services.Core/README.md index ac26180b..b2fe0f75 100644 --- a/src/SquidStd.Services.Core/README.md +++ b/src/SquidStd.Services.Core/README.md @@ -61,6 +61,7 @@ await seeded.DispatchAsync(command, connection); // factory maps connection -> | `TimerWheelService` | Timer-wheel scheduling. | | `MainThreadDispatcherService` | Main-thread work dispatch. | | `MetricsCollectionService` | Metric sample aggregation. | +| `EventLoopService` | Frame-driven loop: drains the dispatcher and advances the timer wheel. | ## Related