using System.Diagnostics; using System.Threading; namespace XFESystem.Kernel; public readonly record struct NativePointerReport(int X, int Y, bool LeftButton, bool RightButton, bool MiddleButton, int Wheel, long TimestampTicks, bool LostReports = false, long Sequence = 0) { // Cosmos normalizes both PS/2 and USB reports to positive = down. // DesktopSession uses the conventional positive = up wheel contract. public int DesktopWheel => Wheel == int.MinValue ? int.MaxValue : -Wheel; } /// Ordered bounded pointer reports, independent of the renderer's frame time. public static class NativePointerCapture { public const int Capacity = 1024; private static NativePointerReport[]? reports; private static byte[]? buttonTransitions; private static long published, consumed; private static long publishedTransitions, consumedTransitions; private static int writerButtons; private static int writerGate, overflow; private static long observedReports, consumedReports, lostReports; public static long ObservedReports => Volatile.Read(ref observedReports); public static long ConsumedReports => Volatile.Read(ref consumedReports); public static long LostReports => Volatile.Read(ref lostReports); // Call before pointer delivery: preinitialize the ring and monotonic clock. public static void Prepare() { _ = Stopwatch.GetTimestamp(); if (Volatile.Read(ref buttonTransitions) is null) Interlocked.CompareExchange(ref buttonTransitions, new byte[Capacity], null); if (Volatile.Read(ref reports) is null) Interlocked.CompareExchange(ref reports, new NativePointerReport[Capacity], null); } public static long ReadTimestamp() => Volatile.Read(ref reports) is null ? 0 : Stopwatch.GetTimestamp(); // Ordinary mouse motion, including a held drag, must not starve painting. // Transition counts describe unread make/break reports without scanning or // consuming the ring. Overflow also yields for the explicit loss sentinel. public static bool HasButtonTransitionPending => Volatile.Read(ref reports) is not null && (Volatile.Read(ref overflow) != 0 || Volatile.Read(ref publishedTransitions) != Volatile.Read(ref consumedTransitions)); // Project-local IL hooks call this only after the original decoder's normal // return path has applied sensitivity, clamping and button/wheel decoding. // There is no allocation, spin, logging or desktop mutation in IRQ context. public static void Observe(int x, int y, bool leftButton, bool rightButton, bool middleButton, int wheel, long timestampTicks) { var buffer = Volatile.Read(ref reports); var transitions = Volatile.Read(ref buttonTransitions); if (buffer is null || transitions is null) return; long sequence = Interlocked.Increment(ref observedReports); if (Interlocked.CompareExchange(ref writerGate, 1, 0) != 0) { Interlocked.Increment(ref lostReports); Volatile.Write(ref overflow, 1); return; } long head = published; if (Volatile.Read(ref overflow) != 0 || unchecked(head - Volatile.Read(ref consumed)) >= Capacity) { Interlocked.Increment(ref lostReports); Volatile.Write(ref overflow, 1); Volatile.Write(ref writerGate, 0); return; } int slot=(int)(head & (Capacity - 1)),buttons=(leftButton?1:0)|(rightButton?2:0)|(middleButton?4:0); bool changed=buttons!=writerButtons; buffer[slot] = new(x, y, leftButton, rightButton, middleButton, wheel, timestampTicks, Sequence: sequence); transitions[slot]=(byte)(changed?1:0);writerButtons=buttons; if(changed)Volatile.Write(ref publishedTransitions,unchecked(publishedTransitions+1)); Volatile.Write(ref published, unchecked(head + 1)); Volatile.Write(ref writerGate, 0); } public static bool TryRead(out NativePointerReport report) { report = default; var buffer = Volatile.Read(ref reports); if (buffer is null) return false; if (Volatile.Read(ref overflow) != 0) { if (Interlocked.CompareExchange(ref writerGate, 1, 0) != 0) return false; Interlocked.Add(ref lostReports, unchecked(published - consumed)); Volatile.Write(ref consumed, published); Volatile.Write(ref consumedTransitions,publishedTransitions);writerButtons=0; Interlocked.Exchange(ref overflow, 0); Volatile.Write(ref writerGate, 0); // Loss is explicit. The consumer cancels capture and synchronizes // state; this sentinel must never be interpreted as a mouse-up/click. report = new(0, 0, false, false, false, 0, 0, LostReports: true); return true; } long head = Volatile.Read(ref published), tail = consumed; if (tail == head) return false; int slot=(int)(tail & (Capacity - 1));report = buffer[slot]; if(buttonTransitions![slot]!=0)Volatile.Write(ref consumedTransitions,unchecked(consumedTransitions+1)); Volatile.Write(ref consumed, unchecked(tail + 1)); Interlocked.Increment(ref consumedReports); return true; } }