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;
}
/// <summary>Ordered bounded pointer reports, independent of the renderer's frame time.</summary>
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;
}
}
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;
}
/// <summary>Ordered bounded pointer reports, independent of the renderer's frame time.</summary>
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;
}
}