/************************************************************************* * ModernUO * * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EventLoopTasks.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System; using System.Collections.Concurrent; using System.Threading; namespace Server; public sealed class EventLoopContext : SynchronizationContext { public enum Priority { Normal, High } private readonly ConcurrentQueue _queue; private readonly ConcurrentQueue _priorityQueue; private readonly Thread _mainThread; private readonly int _maxPerFrame; public EventLoopContext(int maxPerFrame = 128) { _maxPerFrame = maxPerFrame; _queue = []; _priorityQueue = []; _mainThread = Thread.CurrentThread; } public override SynchronizationContext CreateCopy() => new EventLoopContext(); /// /// True when no callbacks are waiting to run. /// /// /// drains at most _maxPerFrame callbacks, so work can /// legitimately be left over. The event loop checks this before sleeping so a backlog keeps /// it running instead. /// public bool IsEmpty => _queue.IsEmpty && _priorityQueue.IsEmpty; public void Post(Action d, Priority priority = Priority.Normal) { (priority == Priority.High ? _priorityQueue : _queue).Enqueue(d); WakeEventLoop(); } public override void Post(SendOrPostCallback d, object state) { _queue.Enqueue(() => d(state)); WakeEventLoop(); } /// /// Nudges the game loop in case it is asleep: the loop blocks on network I/O, which a queue /// push alone does not signal. /// private void WakeEventLoop() { // A post from the loop thread cannot need a wake -- the loop is executing this very call // -- and the signal is a syscall on every backend. if (Thread.CurrentThread == _mainThread) { EventLoopProfiler.WakeSignal(elided: true); return; } EventLoopProfiler.WakeSignal(elided: false); // Safe before networking is configured and after teardown; NetState.Wake does nothing. Network.NetState.Wake(); } public override void Send(SendOrPostCallback d, object state) { if (Thread.CurrentThread == _mainThread) { d(state); return; } var evt = new AutoResetEvent(false); _queue.Enqueue(() => { d(state); evt.Set(); }); WakeEventLoop(); evt.WaitOne(); } public void ExecuteTasks() { if (Thread.CurrentThread != _mainThread) { throw new Exception("Called EventLoop.ExecuteTasks on incorrect thread!"); } var count = _priorityQueue.Count; for (var i = 0; i < count; i++) { if (_priorityQueue.TryDequeue(out var a)) { a(); } } count = Math.Min(_queue.Count, _maxPerFrame); for (var i = 0; i < count; i++) { if (_queue.TryDequeue(out var a)) { a(); } } } }