summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 03:39:10 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 03:39:10 +0800
commite7c19fef046ddf57e0c5e4a85c99a47261cf97b0 (patch)
tree530972e27b2afa31c3c7b500b841dce32d0b2e68 /src
parent847a12bd7029302d95c2b725758c7681b57235dc (diff)
downloadliving-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.fs42
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()