using System; using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Threading.Tasks; using WhiteMagic.Hooking; namespace WhiteMagic.Execution; /// /// A crash-safe work queue drained on the target's own thread via a detour on a /// per-frame function. Callers queue work and receive the result (or exception) /// on their own thread through a completion handle. /// /// /// The pump assumes the frame function is parameterless and returns an . /// This matches common per-frame functions such as D3D9 EndScene. /// public sealed class MainThreadPump : IDisposable { private readonly DetourManager _detours; private readonly IntPtr _frameAddress; private readonly ConcurrentQueue _queue = new(); private readonly object _gate = new(); private Detour? _detour; private bool _installed; private bool _disposed; /// /// Creates a pump that will hook the frame function at . /// public MainThreadPump(DetourManager detours, IntPtr frameAddress) { _detours = detours; _frameAddress = frameAddress; } /// Returns after the frame hook has been applied. public bool IsInstalled => _installed; /// Installs the frame-function detour. public void Install() { if (_installed) return; _detour = _detours.Create("MainThreadPump", _frameAddress, (FrameDelegate)PumpHook); _detour.Apply(); _installed = true; } /// /// Queues work to run on the hooked thread and blocks until it completes. /// public TResult Execute(Func work) { var tcs = new TaskCompletionSource(); lock (_gate) { if (_disposed) ThrowDisposed(); if (!_installed) throw new InvalidOperationException("The main-thread pump is not installed. Call Install() first."); _queue.Enqueue(new WorkItem(() => work()!, tcs)); } object? result = tcs.Task.GetAwaiter().GetResult(); return (TResult)result!; } /// /// Queues work to run on the hooked thread and returns a . /// public Task ExecuteAsync(Func work) { var tcs = new TaskCompletionSource(); lock (_gate) { if (_disposed) ThrowDisposed(); if (!_installed) throw new InvalidOperationException("The main-thread pump is not installed. Call Install() first."); object? Box() => work()!; _queue.Enqueue(new WorkItem(Box, r => tcs.TrySetResult((TResult)r!), ex => tcs.TrySetException(ex))); } return tcs.Task; } /// Removes the frame-function detour if it is installed. public void Dispose() { lock (_gate) { if (_disposed) return; _disposed = true; if (_installed && _detour is not null) _detour.Remove(); _installed = false; } // Fault any caller still blocked on queued work so Execute cannot hang forever. while (_queue.TryDequeue(out WorkItem? item)) { item.SetException(new ObjectDisposedException(nameof(MainThreadPump))); } } private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(MainThreadPump)); private int PumpHook() { try { while (_queue.TryDequeue(out WorkItem? item)) { try { object? result = item.Work(); item.SetResult(result); } catch (Exception ex) { item.SetException(ex); } } // Call the original frame function so rendering/game logic continues. return _detour is null ? 0 : (int?)_detour.CallOriginal() ?? 0; } catch { // Never let an exception escape back into the native frame caller. return 0; } } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int FrameDelegate(); private sealed class WorkItem { private readonly Action? _setResult; private readonly Action? _setException; public WorkItem(Func work, Action setResult, Action setException) { Work = work; _setResult = setResult; _setException = setException; } public WorkItem(Func work, TaskCompletionSource tcs) { Work = work; _setResult = r => tcs.TrySetResult(r); _setException = ex => tcs.TrySetException(ex); } public Func Work { get; } public void SetResult(object? result) { try { _setResult?.Invoke(result); } catch (InvalidOperationException) { // Already completed, e.g. concurrent Dispose/PumpHook race. } } public void SetException(Exception exception) { try { _setException?.Invoke(exception); } catch (InvalidOperationException) { // Already completed, e.g. concurrent Dispose/PumpHook race. } } } }