From 847a12bd7029302d95c2b725758c7681b57235dc Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 03:39:05 +0800 Subject: feat(perf): 帧率采样汇总纯函数 + 测试(P21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PerformanceSummary.summarize:丢弃 warmup 后求每秒 FPS 的均值/最低/最高, 空样本返回 None;含 3 条单测(TDD)。 --- .../LivingVillage.Desktop.Tests.fsproj | 1 + .../PerformanceSummaryTests.fs | 34 ++++++++++++++++++++++ .../LivingVillage.Desktop.fsproj | 1 + src/LivingVillage.Desktop/PerformanceSummary.fs | 27 +++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 src/LivingVillage.Desktop.Tests/PerformanceSummaryTests.fs create mode 100644 src/LivingVillage.Desktop/PerformanceSummary.fs (limited to 'src') diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index 87e9e9d..a0c98b0 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -12,6 +12,7 @@ + diff --git a/src/LivingVillage.Desktop.Tests/PerformanceSummaryTests.fs b/src/LivingVillage.Desktop.Tests/PerformanceSummaryTests.fs new file mode 100644 index 0000000..cd7c3b1 --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/PerformanceSummaryTests.fs @@ -0,0 +1,34 @@ +namespace LivingVillage.Desktop.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Desktop + +[] +type PerformanceSummaryTests () = + + let close (expected: float) (actual: float) = + Assert.IsTrue(abs (expected - actual) < 0.0001, sprintf "expected %f but got %f" expected actual) + + [] + member _.SummarizeDropsWarmupAndReportsMeanMinMax () = + match PerformanceSummary.summarize 2 [ 10.0; 20.0; 30.0; 40.0 ] with + | None -> Assert.Fail("expected a summary") + | Some summary -> + Assert.AreEqual(2, summary.Count) + close 35.0 summary.Mean + close 30.0 summary.Min + close 40.0 summary.Max + + [] + member _.SummarizeReturnsNoneWhenNoRealSampleRemains () = + Assert.IsTrue((PerformanceSummary.summarize 5 [ 1.0; 2.0 ]).IsNone) + Assert.IsTrue((PerformanceSummary.summarize 0 []).IsNone) + + [] + member _.SummarizeWithZeroWarmupKeepsEverySample () = + match PerformanceSummary.summarize 0 [ 60.0; 59.5; 61.0 ] with + | None -> Assert.Fail("expected a summary") + | Some summary -> + Assert.AreEqual(3, summary.Count) + close 59.5 summary.Min diff --git a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj index 6f388f2..2901dce 100644 --- a/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj +++ b/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj @@ -19,6 +19,7 @@ + diff --git a/src/LivingVillage.Desktop/PerformanceSummary.fs b/src/LivingVillage.Desktop/PerformanceSummary.fs new file mode 100644 index 0000000..3edb4a7 --- /dev/null +++ b/src/LivingVillage.Desktop/PerformanceSummary.fs @@ -0,0 +1,27 @@ +namespace LivingVillage.Desktop + +/// P21:纯函数的帧率采样汇总(均值/最低/最高),供实机性能核对与回归测试。 +/// 不含时钟/IO,只看传入的每秒 FPS 样本序列;warmup 采样用于剔除启动抖动。 +module PerformanceSummary = + + type Summary = + { Count: int + Mean: float + Min: float + Max: float } + + /// 丢弃前 `warmupSamples` 个样本(不足则全丢),对剩余样本求均值/最低/最高。 + /// 剩余为空时返回 None(如运行过短)。 + let summarize (warmupSamples: int) (samples: float list) : Summary option = + let skip = max 0 warmupSamples + let taken = + if List.length samples <= skip then [] + else samples |> List.skip skip + match taken with + | [] -> None + | _ -> + Some + { Count = taken.Length + Mean = List.average taken + Min = List.min taken + Max = List.max taken } -- cgit v1.2.3