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

XFESystem

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

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

namespace XFESystem.Core.Tests;

public sealed class FramePacerTests
{
    [Fact]
    public void CleanDesktopDoesNotScheduleOrCountFrames()
    {
        var pacer = new FramePacer(10_000_000, 240000);
        Assert.False(pacer.ShouldRender(0, false));
        Assert.True(pacer.ShouldRender(0, true));
        pacer.Rendered(0, 100);
        long deadline = pacer.NextDeadlineTicks;
        Assert.False(pacer.ShouldRender(10_000_000, false));
        Assert.Equal(1, pacer.FramesRendered);
        Assert.Equal(deadline, pacer.NextDeadlineTicks);
        Assert.True(pacer.ShouldRender(10_000_000, true));
    }

    [Fact]
    public void TwoHundredFortyHzUsesTicksRatherThanTruncatedMilliseconds()
    {
        var pacer = new FramePacer(10_000_000, 240000);
        pacer.Rendered(0, 100);
        Assert.Equal(41667, pacer.NextDeadlineTicks);
        Assert.False(pacer.ShouldRender(41666, true));
        Assert.True(pacer.ShouldRender(41667, true));
        Assert.Equal(240000, pacer.TargetMilliHz);
    }

    [Theory]
    [InlineData(160000)]
    [InlineData(239760)]
    [InlineData(240000)]
    [InlineData(360000)]
    public void FractionalDeadlinesKeepExactPhaseOverTenThousandFrames(int target)
    {
        const long frequency = 10_000_000;
        var pacer = new FramePacer(frequency, target);
        long begin = 123;
        long origin = begin;
        for (int index = 1; index <= 10000; index++)
        {
            Assert.True(pacer.ShouldRender(begin, true));
            pacer.Rendered(begin, begin);
            long expected = origin + (long)(((UInt128)(uint)index * (ulong)frequency * 1000 + (uint)target - 1) / (uint)target);
            Assert.Equal(expected, pacer.NextDeadlineTicks);
            begin = pacer.NextDeadlineTicks;
        }
        Assert.Equal(0, pacer.Overruns);
        Assert.Equal(10000, pacer.FramesRendered);
    }

    [Fact]
    public void LowFrequencyClockAccumulatesFractionRatherThanRoundingEveryPeriod()
    {
        var pacer = new FramePacer(1000, 240000);
        long[] expected = [5, 9, 13, 17, 21, 25, 30, 34, 38, 42, 46, 50];
        long begin = 0;
        foreach (long deadline in expected)
        {
            pacer.Rendered(begin, begin);
            Assert.Equal(deadline, pacer.NextDeadlineTicks);
            begin = deadline;
        }
    }

    [Fact]
    public void LateRenderCanPresentImmediatelyRatherThanWaitingAnotherFullPeriod()
    {
        var pacer = new FramePacer(1000, 100000);
        pacer.Rendered(0, 15);
        Assert.Equal(1, pacer.Overruns);
        Assert.Equal(15, pacer.LastRenderDurationTicks);
        Assert.Equal(10, pacer.NextDeadlineTicks);
        Assert.True(pacer.ShouldRender(15, true));
        pacer.Rendered(15, 16);
        Assert.Equal(20, pacer.NextDeadlineTicks);
        Assert.False(pacer.ShouldRender(16, true));
    }

    [Fact]
    public void ManyMissedDeadlinesAreSkippedWithoutBurstOfObsoleteFrames()
    {
        var pacer = new FramePacer(1000, 100000);
        pacer.Rendered(0, 95);
        Assert.Equal(90, pacer.NextDeadlineTicks);
        Assert.True(pacer.ShouldRender(95, true));
        pacer.Rendered(95, 96);
        Assert.Equal(100, pacer.NextDeadlineTicks);
        Assert.False(pacer.ShouldRender(96, true));
        Assert.Equal(2, pacer.FramesRendered);
        Assert.Equal(1, pacer.Overruns);
    }

    [Fact]
    public void ResumingAfterIdleJumpsToCurrentPhase()
    {
        var pacer = new FramePacer(1000, 100000);
        pacer.Rendered(0, 1);
        Assert.True(pacer.ShouldRender(100000, true));
        pacer.Rendered(100000, 100001);
        Assert.Equal(100010, pacer.NextDeadlineTicks);
        Assert.Equal(2, pacer.FramesRendered);
        Assert.False(pacer.ShouldRender(100001, true));
    }

    [Fact]
    public void ActualRendererCadenceDoesNotClaimTargetOrMonitorScanRate()
    {
        var pacer = new FramePacer(1000, 240000);
        Assert.Equal(0, pacer.ActualRendererFps);
        pacer.Rendered(0, 1);
        Assert.Equal(0, pacer.ActualRendererFps);
        pacer.Rendered(10, 11);
        pacer.Rendered(20, 21);
        Assert.Equal(100, pacer.ActualRendererFps);
        Assert.Equal(240000, pacer.TargetMilliHz);
        Assert.Equal(1, pacer.LastRenderDurationTicks);
    }

    [Fact]
    public void ExactlyOnePeriodIsNotCountedAsAnOverrun()
    {
        var pacer = new FramePacer(1000, 100000);
        pacer.Rendered(0, 10);
        Assert.Equal(0, pacer.Overruns);
        Assert.True(pacer.ShouldRender(10, true));
    }

    [Fact]
    public void HugeFrequencyAndDeadlineAreHandledWithoutOverflow()
    {
        var pacer = new FramePacer(long.MaxValue, 1000);
        pacer.Rendered(10, 11);
        Assert.Equal(long.MaxValue, pacer.NextDeadlineTicks);
        Assert.False(pacer.ShouldRender(100, true));
    }

    [Theory]
    [InlineData(0, 60000)]
    [InlineData(-1, 60000)]
    [InlineData(1000, 0)]
    [InlineData(1000, -1)]
    [InlineData(1, 2000)]
    public void InvalidClockOrTargetCannotCreateAZeroTickLoop(long frequency, int target) =>
        Assert.Throws<ArgumentOutOfRangeException>(() => new FramePacer(frequency, target));

    [Fact]
    public void ReversedAndOverlappingTimestampsAreRejected()
    {
        var pacer = new FramePacer(1000, 60000);
        Assert.Throws<ArgumentOutOfRangeException>(() => pacer.ShouldRender(-1, true));
        Assert.Throws<ArgumentOutOfRangeException>(() => pacer.Rendered(-1, 0));
        Assert.Throws<ArgumentOutOfRangeException>(() => pacer.Rendered(10, 9));
        pacer.Rendered(10, 20);
        Assert.Throws<ArgumentOutOfRangeException>(() => pacer.Rendered(19, 21));
        Assert.Equal(1, pacer.FramesRendered);
    }
}