diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 03:39:10 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 03:39:10 +0800 |
| commit | e7c19fef046ddf57e0c5e4a85c99a47261cf97b0 (patch) | |
| tree | 530972e27b2afa31c3c7b500b841dce32d0b2e68 /src | |
| parent | 847a12bd7029302d95c2b725758c7681b57235dc (diff) | |
| download | living-village-e7c19fef046ddf57e0c5e4a85c99a47261cf97b0.tar.gz | |
feat(perf): LV_UNLOCK_FPS 与 LV_PERF_SUMMARY 实机帧率核对钩子(P21)
- LV_UNLOCK_FPS=1 关闭 vsync/固定步长以测真实渲染上限;
LV_PERF_SUMMARY=1 退出时用 PerformanceSummary 打印 mean/min/max。
- LV_RIVERSCAPE_SECONDS=N 让巡游循环到 N 秒,支撑 60s 稳定采样。
Diffstat (limited to 'src')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 42 |
1 files changed, 40 insertions, 2 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index e71b280..28c86db 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -143,6 +143,16 @@ type LivingVillageGame() as this = else None let riverscapeTourMode = riverscapeTourRequested && riverscapeActive + // P21 性能核对:>0 时巡游循环到该秒数再退出(默认 0 = 跑完一遍路点)。 + let riverscapeTourSeconds = + match Environment.GetEnvironmentVariable("LV_RIVERSCAPE_SECONDS") with + | null | "" -> 0.0 + | v -> + match Double.TryParse(v, Globalization.NumberStyles.Float, Globalization.CultureInfo.InvariantCulture) with + | true, seconds when seconds > 0.0 -> seconds + | _ -> 0.0 + let riverscapeTourWatch = System.Diagnostics.Stopwatch() + let mutable riverscapeTourWatchStarted = false /// 巡游路点:第一座桥的整列(含两端外沿)+ 民居门 + 核心中心,用于桥面行走取证。 let riverscapeTourWaypoints () : (int * int) list = match riverscapeMap with @@ -194,6 +204,9 @@ type LivingVillageGame() as this = let mutable camera = Vector2.Zero let mutable avatarFacing = VillageArt.SouthFacing let mutable avatarMoving = false + let unlockFps = Environment.GetEnvironmentVariable("LV_UNLOCK_FPS") = "1" + let perfSummaryMode = Environment.GetEnvironmentVariable("LV_PERF_SUMMARY") = "1" + let mutable fpsSamples: float list = [] let mutable fpsFrames = 0 let mutable fpsSeconds = 0.0 let mutable prevKb = Unchecked.defaultof<KeyboardState> @@ -281,6 +294,10 @@ type LivingVillageGame() as this = graphics.SynchronizeWithVerticalRetrace <- true this.IsFixedTimeStep <- true this.TargetElapsedTime <- TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60L) + if unlockFps then + // P21 性能核对:放开 vsync/固定步长,测真实的渲染帧率上限。 + this.IsFixedTimeStep <- false + graphics.SynchronizeWithVerticalRetrace <- false this.Window.Title <- ChineseText.copy.SceneTitle printfn "world-mode=%s bounds=%dx%d" @@ -426,6 +443,9 @@ type LivingVillageGame() as this = /// P20 第三步取证:真走位巡游(Sim.step 驱动)沿桥/路/民居门移动,用于桥面行走帧与昼夜录制。 member private this.RunRiverscapeTour () = + if not riverscapeTourWatchStarted then + riverscapeTourWatchStarted <- true + riverscapeTourWatch.Start() let target = tourWaypoints.[tourIndex % tourWaypoints.Length] let targetPos : Vec2 = { X = float32 (fst target * Sim.tilePixels + Sim.tilePixels / 2) @@ -436,8 +456,11 @@ type LivingVillageGame() as this = if distance < 3.0f then tourIndex <- tourIndex + 1 if tourIndex >= tourWaypoints.Length then - printfn "riverscape tour=complete frames=%d" recordIndex - this.Exit() + if riverscapeTourSeconds > 0.0 && riverscapeTourWatch.Elapsed.TotalSeconds < riverscapeTourSeconds then + tourIndex <- 0 + else + printfn "riverscape tour=complete frames=%d elapsed_s=%.1f" recordIndex riverscapeTourWatch.Elapsed.TotalSeconds + this.Exit() else let inv = 1.0f / distance let input = { Input = { MoveX = dx * inv; MoveY = dy * inv } } @@ -700,9 +723,24 @@ type LivingVillageGame() as this = let fps = float fpsFrames / fpsSeconds fpsFrames <- 0 fpsSeconds <- 0.0 + if perfSummaryMode then + fpsSamples <- fps :: fpsSamples printfn $"fps={fps:F1} tick={world.Tick} pos=({world.Avatar.Pos.X:F0},{world.Avatar.Pos.Y:F0})" prevKb <- kb + override this.EndRun() = + if perfSummaryMode then + match PerformanceSummary.summarize 5 (List.rev fpsSamples) with + | Some summary -> + printfn + "perf-summary samples=%d warmup_dropped=5 mean_fps=%.2f min_fps=%.2f max_fps=%.2f" + summary.Count + summary.Mean + summary.Min + summary.Max + | None -> printfn "perf-summary samples=%d (insufficient, all warmup)" fpsSamples.Length + base.EndRun() + member private this.DrawLine (a: Vector2) (b: Vector2) (thickness: float32) (color: Color) = let d = b - a let len = d.Length() |
