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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Desktop;

/// <summary>
/// A canvas-local cache of actual opaque web viewport pixels. Keys contain only
/// values, never a page/runtime reference. Position is deliberately excluded so
/// an unchanged viewport can be restored while its window moves.
/// </summary>
public interface IDesktopWebFrameCache
{
    DesktopBounds WebPaintClip { get; }
    bool TryRestoreWebFrame(long pageId, long revision, long layoutVersion, DesktopBounds bounds, double scrollX, double scrollY);
    void StoreWebFrame(long pageId, long revision, long layoutVersion, DesktopBounds bounds, double scrollX, double scrollY);
}

public readonly record struct DesktopWebFrameKey(long PageId, long Revision, long LayoutVersion,
    int NativeWidth, int NativeHeight, int ScalePercent, double ScrollX, double ScrollY);

/// <summary>
/// Bounded LRU framebuffer storage used by native canvases and host pixel tests.
/// Capture requires the entire viewport to be painted; restore copies only the
/// intersection of the requested destination, framebuffer and current damage.
/// All coordinates and dimensions in this class are native pixels.
/// </summary>
public sealed class DesktopWebFrameCache
{
    public const int DefaultMaximumEntries = 6;
    public const long DefaultMaximumBytes = 32L * 1024 * 1024;
    private sealed record Entry(DesktopWebFrameKey Key, int[] Pixels);
    private readonly List<Entry> entries = [];
    public DesktopWebFrameCache(long maximumBytes = DefaultMaximumBytes, int maximumEntries = DefaultMaximumEntries)
    {
        if (maximumBytes <= 0 || maximumBytes > DefaultMaximumBytes) throw new ArgumentOutOfRangeException(nameof(maximumBytes));
        if (maximumEntries <= 0 || maximumEntries > DefaultMaximumEntries) throw new ArgumentOutOfRangeException(nameof(maximumEntries));
        MaximumBytes = maximumBytes; MaximumEntries = maximumEntries;
    }
    public long MaximumBytes { get; }
    public int MaximumEntries { get; }
    public int Count => entries.Count;
    public long CachedBytes { get; private set; }
    public long RestoreHits { get; private set; }
    public long PixelsRestored { get; private set; }
    public long Captures { get; private set; }
    public long Evictions { get; private set; }

    public bool TryRestore(DesktopWebFrameKey key, Span<int> framebuffer, int framebufferWidth, int framebufferHeight,
        int destinationX, int destinationY, DesktopBounds clip, DesktopBounds preserved = default)
    {
        ValidateFramebuffer(framebuffer.Length, framebufferWidth, framebufferHeight);
        if (!ValidKey(key)) return false;
        int index = Find(key); if (index < 0) return false;
        var entry = entries[index]; Touch(index); RestoreHits++;
        long left = Math.Max(0L, Math.Max((long)destinationX, clip.X));
        long top = Math.Max(0L, Math.Max((long)destinationY, clip.Y));
        long right = Math.Min(framebufferWidth, Math.Min((long)destinationX + key.NativeWidth, (long)clip.X + Math.Max(0, clip.Width)));
        long bottom = Math.Min(framebufferHeight, Math.Min((long)destinationY + key.NativeHeight, (long)clip.Y + Math.Max(0, clip.Height)));
        if (right <= left || bottom <= top) return true;
        int width = (int)(right - left);
        int protectedX=Math.Clamp(preserved.X,0,framebufferWidth),protectedY=Math.Clamp(preserved.Y,0,framebufferHeight);
        int protectedRight=(int)Math.Clamp((long)preserved.X+Math.Max(0,preserved.Width),0,framebufferWidth);
        int protectedBottom=(int)Math.Clamp((long)preserved.Y+Math.Max(0,preserved.Height),0,framebufferHeight);
        preserved=new(protectedX,protectedY,Math.Max(0,protectedRight-protectedX),Math.Max(0,protectedBottom-protectedY));
        for (int y = (int)top; y < bottom; y++)
        {
            int source = checked((int)(((long)y - destinationY) * key.NativeWidth + left - destinationX));
            DesktopSceneMotionBuffer.RowSegments(preserved,(int)left,width,y,out int leftEnd,out int rightStart);
            int leftCount=leftEnd-(int)left,rightCount=(int)right-rightStart,frameOffset=checked(y*framebufferWidth);
            entry.Pixels.AsSpan(source,leftCount).CopyTo(framebuffer.Slice(frameOffset+(int)left,leftCount));
            entry.Pixels.AsSpan(source+rightStart-(int)left,rightCount).CopyTo(framebuffer.Slice(frameOffset+rightStart,rightCount));
            PixelsRestored+=leftCount+rightCount;
        }
        return true;
    }

    public bool Store(DesktopWebFrameKey key, ReadOnlySpan<int> framebuffer, int framebufferWidth, int framebufferHeight,
        int sourceX, int sourceY, DesktopBounds clip, DesktopBounds preserved = default)
    {
        ValidateFramebuffer(framebuffer.Length, framebufferWidth, framebufferHeight);
        if (!ValidKey(key)) return false;
        long bytes = (long)key.NativeWidth * key.NativeHeight * sizeof(int);
        if (bytes > MaximumBytes || sourceX < 0 || sourceY < 0 ||
            (long)sourceX + key.NativeWidth > framebufferWidth || (long)sourceY + key.NativeHeight > framebufferHeight ||
            clip.Width <= 0 || clip.Height <= 0 || clip.X > sourceX || clip.Y > sourceY ||
            (long)clip.X + clip.Width < (long)sourceX + key.NativeWidth || (long)clip.Y + clip.Height < (long)sourceY + key.NativeHeight) return false;
        // The protected pixels may belong to a foreground window. A lower
        // browser's paint deliberately skipped them, so they are not its image.
        if(preserved.Width>0&&preserved.Height>0&&(long)sourceX+key.NativeWidth>preserved.X&&
            (long)preserved.X+preserved.Width>sourceX&&(long)sourceY+key.NativeHeight>preserved.Y&&
            (long)preserved.Y+preserved.Height>sourceY)return false;

        int index = Find(key); Entry entry;
        if (index >= 0) { entry = entries[index]; Touch(index); }
        else
        {
            while (entries.Count > 0 && (entries.Count >= MaximumEntries || CachedBytes + bytes > MaximumBytes))
            {
                CachedBytes -= (long)entries[0].Pixels.Length * sizeof(int); entries.RemoveAt(0); Evictions++;
            }
            entry = new(key, new int[checked(key.NativeWidth * key.NativeHeight)]);
            entries.Add(entry); CachedBytes += bytes;
        }
        for (int y = 0; y < key.NativeHeight; y++)
            framebuffer.Slice(checked((sourceY + y) * framebufferWidth + sourceX), key.NativeWidth)
                .CopyTo(entry.Pixels.AsSpan(y * key.NativeWidth, key.NativeWidth));
        Captures++; return true;
    }

    private static bool ValidKey(DesktopWebFrameKey key) => key.NativeWidth > 0 && key.NativeHeight > 0 && key.ScalePercent > 0 &&
        (long)key.NativeWidth * key.NativeHeight <= int.MaxValue / sizeof(int) && double.IsFinite(key.ScrollX) && double.IsFinite(key.ScrollY);
    private static void ValidateFramebuffer(int length, int width, int height)
    {
        if (width <= 0 || height <= 0 || (long)width * height > length) throw new ArgumentException("Invalid framebuffer dimensions.");
    }
    private int Find(DesktopWebFrameKey key)
    { for (int i = 0; i < entries.Count; i++) if (entries[i].Key == key) return i; return -1; }
    private void Touch(int index)
    { if (index == entries.Count - 1) return; var entry = entries[index]; entries.RemoveAt(index); entries.Add(entry); }
}