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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Desktop;
using Xunit;
using Xunit.Abstractions;

namespace XFESystem.Desktop.Tests;

public sealed class DesktopWebFrameCacheTests(ITestOutputHelper output)
{
    private static DesktopWebFrameKey Key(long page = 1, long revision = 2, long layout = 3, int width = 4, int height = 3,
        int scale = 200, double scrollX = 0, double scrollY = 0) => new(page, revision, layout, width, height, scale, scrollX, scrollY);

    [Fact]
    public void BackgroundRestoreCannotOverwriteProtectedForegroundMotionPixels()
    {
        var cache=new DesktopWebFrameCache();var pixels=Enumerable.Range(0,120).ToArray();var original=(int[])pixels.Clone();
        Assert.True(cache.Store(Key(),pixels,12,10,1,2,new(0,0,12,10)));Array.Fill(pixels,-1);
        Assert.True(cache.TryRestore(Key(),pixels,12,10,7,6,new(0,0,12,10),new(8,7,2,1)));
        for(int y=0;y<10;y++)for(int x=0;x<12;x++)
        {
            bool viewport=x>=7&&x<11&&y>=6&&y<9,protectedPixel=x>=8&&x<10&&y==7;
            Assert.Equal(viewport&&!protectedPixel?original[(y-6+2)*12+x-7+1]:-1,pixels[y*12+x]);
        }
        Assert.Equal(10,cache.PixelsRestored);
        Assert.True(cache.TryRestore(Key(),pixels,12,10,7,6,new(0,0,12,10),new(0,0,12,10)));
        Assert.Equal(10,cache.PixelsRestored);
    }

    [Fact]
    public void CaptureRejectsAnyForegroundPreservationIntersectionWithoutEvictingGoodFrames()
    {
        var cache=new DesktopWebFrameCache(maximumBytes:48,maximumEntries:1);var pixels=Enumerable.Range(0,48).ToArray();
        Assert.True(cache.Store(Key(),pixels,8,6,1,1,new(0,0,8,6)));
        Assert.False(cache.Store(Key(page:2),pixels,8,6,1,1,new(0,0,8,6),new(4,2,1,1)));
        Assert.Equal(1,cache.Count);Assert.Equal(0,cache.Evictions);Assert.Equal(1,cache.Captures);
        Assert.True(cache.TryRestore(Key(),pixels,8,6,1,1,new(0,0,8,6)));
        Assert.True(cache.Store(Key(page:2),pixels,8,6,1,1,new(0,0,8,6),new(5,2,1,1)));
    }

    [Fact]
    public void ProtectedRestoreKeepsExactPixelsAndAccountingForClippedAndOffscreenDestinations()
    {
        var cache=new DesktopWebFrameCache();var original=Enumerable.Range(0,120).ToArray();var pixels=new int[120];
        Assert.True(cache.Store(Key(),original,12,10,1,2,new(0,0,12,10)));
        foreach(int destinationX in new[]{-3,0,7,10})foreach(int destinationY in new[]{-2,0,6,9})
        foreach(var clip in new[]{new DesktopBounds(0,0,12,10),new DesktopBounds(2,1,7,5)})
        foreach(var preserved in new[]{new DesktopBounds(8,7,2,1),new DesktopBounds(-2,-2,4,5),new DesktopBounds(11,9,100,100),
            new DesktopBounds(0,0,0,0),new DesktopBounds(int.MinValue,0,int.MaxValue,10),new DesktopBounds(0,int.MinValue,12,int.MaxValue)})
        {
            Array.Fill(pixels,-1);long before=cache.PixelsRestored;int copied=0;
            Assert.True(cache.TryRestore(Key(),pixels,12,10,destinationX,destinationY,clip,preserved));
            for(int y=0;y<10;y++)for(int x=0;x<12;x++)
            {
                bool viewport=x>=destinationX&&x<destinationX+4&&y>=destinationY&&y<destinationY+3;
                bool inClip=x>=clip.X&&x<clip.X+clip.Width&&y>=clip.Y&&y<clip.Y+clip.Height;
                bool protect=x>=preserved.X&&(long)x<(long)preserved.X+preserved.Width&&y>=preserved.Y&&(long)y<(long)preserved.Y+preserved.Height;
                bool copy=viewport&&inClip&&!protect;if(copy)copied++;
                Assert.Equal(copy?original[(2+y-destinationY)*12+1+x-destinationX]:-1,pixels[y*12+x]);
            }
            Assert.Equal(copied,cache.PixelsRestored-before);
        }
    }

    [Fact]
    public void RestoreCopiesOnlyCurrentDamageAndReusesPixelsAtAnotherPosition()
    {
        var cache = new DesktopWebFrameCache(); var pixels = Enumerable.Range(0, 120).ToArray();
        Assert.True(cache.Store(Key(), pixels, 12, 10, 1, 2, new(1, 2, 4, 3)));
        var stored = (int[])pixels.Clone(); Array.Fill(pixels, -1);
        Assert.True(cache.TryRestore(Key(), pixels, 12, 10, 7, 6, new(8, 7, 2, 1)));
        for (int y = 0; y < 10; y++) for (int x = 0; x < 12; x++)
            Assert.Equal(y == 7 && x is >= 8 and < 10 ? stored[(2 + y - 6) * 12 + 1 + x - 7] : -1, pixels[y * 12 + x]);
        Assert.Equal(2, cache.PixelsRestored); Assert.Equal(1, cache.RestoreHits); Assert.Equal(48, cache.CachedBytes);
    }

    [Fact]
    public void RestoreClipsNegativeDestinationToTheRealFramebuffer()
    {
        var cache = new DesktopWebFrameCache(); var pixels = Enumerable.Range(0, 48).ToArray();
        Assert.True(cache.Store(Key(), pixels, 8, 6, 2, 1, new(0, 0, 8, 6))); var stored = (int[])pixels.Clone(); Array.Fill(pixels, -1);
        Assert.True(cache.TryRestore(Key(), pixels, 8, 6, -2, -1, new(0, 0, 8, 6)));
        for (int y = 0; y < 6; y++) for (int x = 0; x < 8; x++)
            Assert.Equal(y < 2 && x < 2 ? stored[(1 + y + 1) * 8 + 2 + x + 2] : -1, pixels[y * 8 + x]);
    }

    [Fact]
    public void PartialOrOffscreenCaptureCannotReplaceAnExistingFullFrame()
    {
        var cache = new DesktopWebFrameCache(); var pixels = Enumerable.Range(0, 48).ToArray();
        Assert.True(cache.Store(Key(), pixels, 8, 6, 2, 1, new(0, 0, 8, 6))); var stored = (int[])pixels.Clone(); Array.Fill(pixels, -1);
        Assert.False(cache.Store(Key(), pixels, 8, 6, 2, 1, new(2, 1, 3, 3)));
        Assert.False(cache.Store(Key(page: 2), pixels, 8, 6, -1, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(page: 3), pixels, 8, 6, 5, 4, new(0, 0, 8, 6)));
        Assert.Equal(1, cache.Count); Assert.Equal(1, cache.Captures);
        Assert.True(cache.TryRestore(Key(), pixels, 8, 6, 2, 1, new(0, 0, 8, 6)));
        for (int y = 1; y < 4; y++) for (int x = 2; x < 6; x++) Assert.Equal(stored[y * 8 + x], pixels[y * 8 + x]);
    }

    [Fact]
    public void EveryContentGeometryAndScaleKeyFieldInvalidatesTheFrame()
    {
        var cache = new DesktopWebFrameCache(); var pixels = new int[48];
        Assert.True(cache.Store(Key(), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        foreach (var key in new[] { Key(page: 2), Key(revision: 4), Key(layout: 5), Key(width: 5), Key(height: 4),
                     Key(scale: 100), Key(scrollX: .25), Key(scrollY: 1) })
            Assert.False(cache.TryRestore(key, pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.True(cache.TryRestore(Key(), pixels, 8, 6, 100, 100, new(0, 0, 8, 6)));
        Assert.Equal(0, cache.PixelsRestored);
    }

    [Fact]
    public void EntryBoundUsesLeastRecentlyRestoredAndStoredOrder()
    {
        var cache = new DesktopWebFrameCache(maximumBytes: 96, maximumEntries: 2); var pixels = new int[12];
        Assert.True(cache.Store(Key(page: 1), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.True(cache.Store(Key(page: 2), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.True(cache.TryRestore(Key(page: 1), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.True(cache.Store(Key(page: 3), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.False(cache.TryRestore(Key(page: 2), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.True(cache.TryRestore(Key(page: 1), pixels, 4, 3, 0, 0, new(0, 0, 4, 3)));
        Assert.Equal(2, cache.Count); Assert.Equal(96, cache.CachedBytes); Assert.Equal(1, cache.Evictions);
    }

    [Fact]
    public void RepeatedRevisionsReuseEvictedBuffersWithoutChangingPixelsOrLruOrder()
    {
        const int width = 128, height = 64;
        var cache = new DesktopWebFrameCache(maximumBytes: width * height * sizeof(int) * 3, maximumEntries: 3);
        var pixels = new int[width * height];
        var restored = new int[pixels.Length];
        var clip = new DesktopBounds(0, 0, width, height);
        DesktopWebFrameKey Revision(int revision) => Key(revision: revision, width: width, height: height);
        for (int revision = 1; revision <= 3; revision++)
        {
            Array.Fill(pixels, revision);
            Assert.True(cache.Store(Revision(revision), pixels, width, height, 0, 0, clip));
        }
        // Restoring revision 1 makes revision 2 the first normal eviction.
        Assert.True(cache.TryRestore(Revision(1), restored, width, height, 0, 0, clip));
        Assert.All(restored, pixel => Assert.Equal(1, pixel));

        long allocatedBefore = GC.GetAllocatedBytesForCurrentThread();
        bool storedAll = true;
        for (int revision = 4; revision <= 1003; revision++)
        {
            // Non-uniform values catch stale rows and partial overwrites when
            // the old revision's storage is assigned to the new key.
            for (int pixel = 0; pixel < pixels.Length; pixel++) pixels[pixel] = revision * 10000 + pixel;
            storedAll &= cache.Store(Revision(revision), pixels, width, height, 0, 0, clip);
        }
        long allocated = GC.GetAllocatedBytesForCurrentThread() - allocatedBefore;
        output.WriteLine("RepeatedRevisionUpdates=1000; PixelBufferBytes=32768; AllocatedBytes=" + allocated);
        Assert.True(storedAll);
        // Entry records are still allocated; 1,000 new 32 KiB pixel arrays
        // would exceed 32 MiB. No buffer pool or host GC is required for reuse.
        Assert.InRange(allocated, 1, 256 * 1024);
        Assert.Equal(3, cache.Count);
        Assert.Equal(3L * pixels.Length * sizeof(int), cache.CachedBytes);
        Assert.Equal(1000, cache.Evictions);
        Assert.Equal(1003, cache.Captures);
        Assert.Equal(3, cache.PixelChunkAllocations);
        Assert.Equal(1000, cache.PixelStorageReuses);
        Assert.False(cache.TryRestore(Revision(1000), restored, width, height, 0, 0, clip));
        for (int revision = 1001; revision <= 1003; revision++)
        {
            Array.Fill(restored, -1);
            Assert.True(cache.TryRestore(Revision(revision), restored, width, height, 0, 0, clip));
            for (int pixel = 0; pixel < restored.Length; pixel++) Assert.Equal(revision * 10000 + pixel, restored[pixel]);
        }
    }

    [Fact]
    public void EvictedBuffersAreReusedOnlyAtTheExactPixelLength()
    {
        var cache = new DesktopWebFrameCache(maximumBytes: 96, maximumEntries: 1);
        var pixels = Enumerable.Range(0, 48).ToArray();
        var clip = new DesktopBounds(0, 0, 8, 6);
        Assert.True(cache.Store(Key(width: 4, height: 3), pixels, 8, 6, 1, 1, clip));
        Assert.True(cache.Store(Key(page: 2, width: 6, height: 4), pixels, 8, 6, 0, 0, clip));
        var restored = Enumerable.Repeat(-1, pixels.Length).ToArray();
        Assert.True(cache.TryRestore(Key(page: 2, width: 6, height: 4), restored, 8, 6, 0, 0, clip));
        for (int row = 0; row < 6; row++) for (int column = 0; column < 8; column++)
            Assert.Equal(row < 4 && column < 6 ? pixels[row * 8 + column] : -1, restored[row * 8 + column]);
        Assert.Equal(96, cache.CachedBytes);
        Assert.Equal(1, cache.Count);
    }

    [Fact]
    public void ChunkedCaptureAndProtectedClippedRestorePreserveEveryNonUniformPixelAcrossChunkSeams()
    {
        const int frameWidth = 389, frameHeight = 150, width = 353, height = 113, sourceX = 13, sourceY = 7;
        var cache = new DesktopWebFrameCache();
        var original = Enumerable.Range(0, frameWidth * frameHeight).Select(value => unchecked(value * 7919 + 131)).ToArray();
        var restored = new int[original.Length];
        var key = Key(width: width, height: height);
        Assert.True(cache.Store(key, original, frameWidth, frameHeight, sourceX, sourceY, new(0, 0, frameWidth, frameHeight)));
        Assert.Equal(3, cache.PixelChunkAllocations);
        Assert.Equal(64 * 1024, cache.LargestPixelChunkAllocatedBytes);
        foreach (int targetX in new[] { -17, 7, 90 }) foreach (int targetY in new[] { -6, 20, 110 })
        foreach (var clip in new[] { new DesktopBounds(0, 0, frameWidth, frameHeight), new DesktopBounds(11, 13, 351, 117) })
        {
            var preserved = new DesktopBounds(141, 66, 28, 6);
            Array.Fill(restored, -1);
            long before = cache.PixelsRestored;
            Assert.True(cache.TryRestore(key, restored, frameWidth, frameHeight, targetX, targetY, clip, preserved));
            int copied = 0;
            for (int y = 0; y < frameHeight; y++) for (int x = 0; x < frameWidth; x++)
            {
                bool copy = x >= targetX && x < targetX + width && y >= targetY && y < targetY + height &&
                    clip.Contains(x, y) && !preserved.Contains(x, y);
                if (copy) copied++;
                Assert.Equal(copy ? original[(sourceY + y - targetY) * frameWidth + sourceX + x - targetX] : -1,
                    restored[y * frameWidth + x]);
            }
            Assert.Equal(copied, cache.PixelsRestored - before);
        }
    }

    [Fact]
    public void NativeFourKScaledViewportNeedsOnlyBoundedChunkAllocationsAndReusesThemForLaterRevisions()
    {
        // This exact geometry previously requested 5,145,368 bytes including
        // the native managed-array header and failed with 960 free pages in
        // the largest physical run, despite 48,830 free pages in total.
        const int width = 1592, height = 808;
        var key = Key(width: width, height: height);
        var cache = new DesktopWebFrameCache(maximumBytes: (long)width * height * sizeof(int), maximumEntries: 1);
        var pixels = Enumerable.Range(0, width * height).Select(value => unchecked(value * 1009 + 77)).ToArray();
        var restored = new int[pixels.Length];
        var clip = new DesktopBounds(0, 0, width, height);
        Assert.True(cache.Store(key, pixels, width, height, 0, 0, clip));
        Assert.Equal(79, cache.PixelChunkAllocations);
        Assert.Equal(64 * 1024, cache.LargestPixelChunkAllocatedBytes);
        Assert.Equal(5_145_344, cache.CachedBytes);
        Assert.True(cache.TryRestore(key, restored, width, height, 0, 0, clip));
        Assert.Equal(pixels, restored);

        for (int pixel = 0; pixel < pixels.Length; pixel++) pixels[pixel] = unchecked(pixels[pixel] ^ 0x13572468);
        var next = key with { Revision = key.Revision + 1 };
        long allocatedBefore = GC.GetAllocatedBytesForCurrentThread();
        Assert.True(cache.Store(next, pixels, width, height, 0, 0, clip));
        long allocated = GC.GetAllocatedBytesForCurrentThread() - allocatedBefore;
        Assert.InRange(allocated, 1, 1024);
        Assert.Equal(79, cache.PixelChunkAllocations);
        Assert.Equal(1, cache.PixelStorageReuses);
        Assert.False(cache.TryRestore(key, restored, width, height, 0, 0, clip));
        Assert.True(cache.TryRestore(next, restored, width, height, 0, 0, clip));
        Assert.Equal(pixels, restored);
        output.WriteLine("NativeViewport=1592x808; PixelBytes={0}; Chunks={1}; LargestChunkPayloadBytes={2}; RevisionAllocatedBytes={3}",
            cache.CachedBytes, cache.PixelChunkAllocations, cache.LargestPixelChunkAllocatedBytes, allocated);
    }

    [Fact]
    public void GlobalByteBoundIsThirtyTwoMiBAndEntryBoundIsSix()
    {
        var cache = new DesktopWebFrameCache(); var pixels = new int[2048 * 1024];
        for (int page = 1; page <= 7; page++)
        {
            Assert.True(cache.Store(Key(page: page, width: 2048, height: 1024), pixels, 2048, 1024, 0, 0, new(0, 0, 2048, 1024)));
            Assert.InRange(cache.Count, 1, 6); Assert.InRange(cache.CachedBytes, 1, 32L * 1024 * 1024);
        }
        Assert.Equal(4, cache.Count); Assert.Equal(32L * 1024 * 1024, cache.CachedBytes); Assert.Equal(3, cache.Evictions);
        var tiny = new DesktopWebFrameCache(); var one = new int[1];
        for (int page = 1; page <= 7; page++) Assert.True(tiny.Store(Key(page: page, width: 1, height: 1), one, 1, 1, 0, 0, new(0, 0, 1, 1)));
        Assert.Equal(6, tiny.Count); Assert.False(tiny.TryRestore(Key(page: 1, width: 1, height: 1), one, 1, 1, 0, 0, new(0, 0, 1, 1)));
    }

    [Fact]
    public void InvalidAndOversizedCapturesDoNotEvictGoodFrames()
    {
        var cache = new DesktopWebFrameCache(maximumBytes: 48); var pixels = new int[48];
        Assert.True(cache.Store(Key(), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(width: 8, height: 6), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(scrollY: double.NaN), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(scrollX: double.PositiveInfinity), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(width: int.MaxValue, height: int.MaxValue), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.False(cache.Store(Key(), pixels, 8, 6, 0, 0, new(int.MinValue, 0, int.MaxValue, 6)));
        Assert.Equal(1, cache.Count); Assert.Equal(0, cache.Evictions);
        Assert.True(cache.TryRestore(Key(), pixels, 8, 6, 0, 0, new(0, 0, 8, 6)));
        Assert.Throws<ArgumentOutOfRangeException>(() => new DesktopWebFrameCache(maximumBytes: 32L * 1024 * 1024 + 1));
        Assert.Throws<ArgumentOutOfRangeException>(() => new DesktopWebFrameCache(maximumEntries: 7));
    }
}