fix(pathfinding): stop opening every .swb twice at boot (#2548)
## Problem
Every map's `.swb` step cache was opened, indexed and logged **twice** on boot.
`MovementPath.Configure()` explicitly called `PathCacheCommands.Configure()` and `CacheEvictionTimer.Configure()`. Both are types exposing a public static parameterless `Configure()`, which `AssemblyHandler.Invoke("Configure")` already discovers and calls once each (`AssemblyHandler.cs:157`). So `PathCacheCommands.Configure()` ran twice, and `AutoLoadAtStartup()` with it. `PathCacheCommands.Configure()` called `PathfindRecorder.Configure()` the same way.
`TryOpenLazyReader` disposes the prior reader before replacing it, so there was no handle leak — but the header and full chunk index of each `.swb` were read twice (~48 MB of files across six facets). The expensive `.mul` hashing was already memoized, so it was not doubled.
## Fix
Consolidate the cache lifecycle into `Initialize`:
- `Configure()` keeps only settings and command registration.
- `Initialize()` opens the readers once, then prebakes only maps that still lack one.
- The post-bake reopen is per-map instead of a blanket `AutoLoadAtStartup()` — on a partial bake (some valid `.swb`, one stale) that would close and reopen the readers already open, a second double-open on a different path.
`Initialize` is the correct phase. `Configure` runs before `TileMatrixLoader.LoadTileMatrix()` and `World.Load()` (`Main.cs:458/460/463/465`), so opening a `.swb` there forced the lazy `Map.Tiles` property — the fingerprint hashes the map files — and built every `TileMatrix` ahead of the loader that owns it, possibly before `TileMatrix.Configure()` settled `Pre6000ClientSupport`. Both sit at the default call priority and the phase sort is unstable. Moving pathfinding out leaves nothing in `Configure` that touches `Map.Tiles`, closing that hazard; the other 22 `.Tiles` users in UOContent are all runtime paths.
Multis stay out of the bake by design — houses and boats are player data that moves, handled by the multi-aware path at query time.
## Logging
The per-map `StepCache: opened ... chunks indexed` line drops to `Debug`. Opening is the expected case; `BakeMap` already logs a rebuild at `Information`, and `Initialize` still emits `PathBake: pre-bake complete (N map(s) written)`.
## Verification
- `dotnet build Projects/UOContent` — 0 errors, 0 warnings.
- `dotnet test --filter FullyQualifiedName~Pathfinding` — **123 passed, 0 failed**.
Boot logs should now show one `opened` line per map at `Debug`, none at `Information`.
This commit is contained in:
parent
c39454137e
commit
9c11ccdb80
4 changed files with 28 additions and 21 deletions
|
|
@ -139,13 +139,17 @@ several-minutes cost. Wiring:
|
|||
after assemblies load (so content can register prompts) but **before Serilog starts**, so the
|
||||
console prompt is not interleaved with the async console sink. Any class can participate by
|
||||
defining `public static void ConfigurePrompts()` and self-gating on first-boot state.
|
||||
- The bake runs in the later `Invoke("Initialize")` phase (after the tile matrix + world load,
|
||||
which the bake walks).
|
||||
- Both the reader open and the bake run in `Invoke("Initialize")`, after the tile matrix and world
|
||||
load. Neither belongs in `Configure`, which runs *before* both: the fingerprint hashes the map
|
||||
files, so opening a `.swb` there would force the lazy `Map.Tiles` property and build every
|
||||
`TileMatrix` ahead of `TileMatrixLoader` — possibly before `TileMatrix.Configure()` settles
|
||||
`Pre6000ClientSupport`, since both sit at the default call priority and the phase sort is
|
||||
unstable. `PathCacheCommands.Configure` is limited to settings and command registration.
|
||||
- Staleness is decided by the `.swb` fingerprint, which `StepCacheFile.OpenForLazy` validates at
|
||||
open time (hash of `tiledata.mul` + the per-map `.mul`/`.uop` files — never the in-memory
|
||||
`TileData` tables, which the server patches at runtime). `Configure` opens a reader for every
|
||||
up-to-date file; the bake in `Initialize` then skips any map where `StepCache.HasLazyReader` is
|
||||
already true, so the fingerprint is computed once per boot, not twice.
|
||||
`TileData` tables, which the server patches at runtime). `Initialize` opens a reader for every
|
||||
up-to-date file, then skips any map where `StepCache.HasLazyReader` is already true, so the
|
||||
fingerprint is computed once per boot. Each newly baked map reopens only itself.
|
||||
|
||||
## Configuration levers
|
||||
|
||||
|
|
@ -154,7 +158,7 @@ several-minutes cost. Wiring:
|
|||
| `pathfinding.enable` | `PathFollower.Configure` | `true` | Master switch for `PathFollower` pathfinding. Off → greedy/auto-turn only, no A* at all. |
|
||||
| `bitmap_pathfinding_cache` feature flag (`ContentFeatureFlags.BitmapPathfindingCache`, `Server.Systems.FeatureFlags`) | `FeatureFlagManager` | `true` | Off → `BitmapAStar` routes straight to the slow path with **no cache probe and no warming memory**. ≈ old FastAStar at ~1×. |
|
||||
| `pathfinding.maxResidentChunks` | `PathCacheCommands.Configure` | 8192 (~40 MB) | LRU cap on resident chunks = the warming-memory ceiling. Lower it (e.g. 512–1024 ≈ 2.5–5 MB) on small shards. |
|
||||
| `pathfinding.maxSearchNodes` | `PathCacheCommands.Configure` → `BitmapAStarAlgorithm.MaxSearchNodes` | 1000 | A* per-Find node-expansion budget. See limits above; ~1000 is the sweet spot. |
|
||||
| `pathfinding.maxSearchNodes` | `BitmapAStarAlgorithm.Configure` → `BitmapAStarAlgorithm.MaxSearchNodes` | 1000 | A* per-Find node-expansion budget. See limits above; ~1000 is the sweet spot. |
|
||||
| `pathfinding.prebakeMaps` | `PathCacheCommands` (first-boot prompt + `Initialize`) | `false` | When set, bakes any missing/stale `.swb` for the selected maps at startup (fingerprint-gated, so a fresh cache is a no-op). Set interactively by the first-boot prompt. |
|
||||
| `PathFollower` `RepathDelay` | `PathFollower.cs` (const) | 2 s | Throttle: a moving goal re-`Find`s at most ~once per 2 s; a stationary reachable goal is pathed once and reused until arrival. Not a setting (compile-time). |
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue