XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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;
    }
}