diff --git a/StarterNG/Classes/GameData.cs b/StarterNG/Classes/GameData.cs index 5813ecc..f846389 100644 --- a/StarterNG/Classes/GameData.cs +++ b/StarterNG/Classes/GameData.cs @@ -35,7 +35,8 @@ public sealed class GameData /// public void Load(IProgress? progress = null, string vehiclesDir = "databases/vehicles/", - string sceneryDir = "scenery/") + string sceneryDir = "scenery/", + string miniDir = "textures/mini/") { if (Loaded) return; @@ -57,6 +58,10 @@ public sealed class GameData } Vehicles.EndLoad(); + // Preload the miniature .bmp index now (behind the splash) so the depot's + // first thumbnail render doesn't scan textures/mini/ on the UI thread. + VehicleDatabase.PreloadMiniIndex(miniDir); + // Phase 2 - sceneries foreach (string file in scnFiles) { diff --git a/StarterNG/Classes/VehicleDatabase.cs b/StarterNG/Classes/VehicleDatabase.cs index 5f3f16e..3ea04b2 100644 --- a/StarterNG/Classes/VehicleDatabase.cs +++ b/StarterNG/Classes/VehicleDatabase.cs @@ -55,6 +55,16 @@ public class VehicleTexture /// Full skin path = directory + skinfile. [JsonIgnore] public string FullPath => Directory + Skinfile; + + // ── cached lookups, populated once after load (VehicleDatabase.BuildTextureIndex) ── + // These mirror the depot's ClassOf / CategoryOf so the search/filter hot path + // never recomputes them (no per-keystroke dictionary lookups over every texture). + + /// Group's "mini" (or this texture's mini_ref) — the vehicle class. Never null. + [JsonIgnore] public string ResolvedClass { get; internal set; } = ""; + + /// Group's "category" letter, or null when the group is unknown. + [JsonIgnore] public string? ResolvedCategory { get; internal set; } } /// Alternate mapping from a malformed multi-= legacy line. @@ -132,7 +142,11 @@ public class VehicleDatabase } /// Finalises an incremental load (builds lookup indexes). - public void EndLoad() => BuildSetIndex(); + public void EndLoad() + { + BuildSetIndex(); + BuildTextureIndex(); + } /// /// Files that make up the database: the merged vehicles.json if present, @@ -246,6 +260,24 @@ public class VehicleDatabase } } + // Resolves each texture's class (group mini / mini_ref) and category letter + // once, after all groups are loaded, so the depot reads cached fields instead + // of doing a GroupsById lookup per texture on every search keystroke. + private void BuildTextureIndex() + { + foreach (var t in Textures) + { + VehicleGroup? g = null; + if (t.Group != null) + GroupsById.TryGetValue(t.Group, out g); + + t.ResolvedCategory = g?.Category; + t.ResolvedClass = g != null && !string.IsNullOrEmpty(g.Mini) + ? g.Mini! + : t.MiniRef ?? ""; + } + } + /// /// Miniature name for a texture: the texture_mini property, but if no .bmp /// for it exists, falls back to the mini of the group it belongs to. @@ -292,6 +324,14 @@ public class VehicleDatabase // Case-insensitive index of mini .bmp files (built once). private static Dictionary? _miniIndex; + /// + /// Builds the miniature .bmp index up front (called from the startup load, + /// behind the splash) so the first thumbnail render never stalls the UI + /// thread scanning textures/mini/. No-op if it was already built. + /// + public static void PreloadMiniIndex(string miniDir = "textures/mini/") => + _miniIndex ??= BuildMiniIndex(miniDir); + /// /// Resolves a miniature .bmp path under textures/mini/ case-insensitively, /// or null if missing. The directory is indexed once and reused. diff --git a/StarterNG/Views/Depot.axaml.cs b/StarterNG/Views/Depot.axaml.cs index 8306e45..41e2573 100644 --- a/StarterNG/Views/Depot.axaml.cs +++ b/StarterNG/Views/Depot.axaml.cs @@ -238,16 +238,12 @@ public partial class Depot : UserControl private static bool IsUnitCar(Dynamic d) => UnitKey(d) != Base(d.SkinFile); // Vehicle class = the "mini" of the group the texture belongs to (e.g. EU07). - private string ClassOf(VehicleTexture t) - { - if (t.Group != null && _db.GroupsById.TryGetValue(t.Group, out var g) && !string.IsNullOrEmpty(g.Mini)) - return g.Mini!; - return t.MiniRef ?? ""; - } + // Resolved once at load (see VehicleDatabase.BuildTextureIndex); read the cache + // here so search/filter never repeats the group lookup per keystroke. + private static string ClassOf(VehicleTexture t) => t.ResolvedClass; - // Vehicle type = the group's "category" letter. - private string? CategoryOf(VehicleTexture t) => - t.Group != null && _db.GroupsById.TryGetValue(t.Group, out var g) ? g.Category : null; + // Vehicle type = the group's "category" letter (also resolved once at load). + private static string? CategoryOf(VehicleTexture t) => t.ResolvedCategory; // ------------------------------------------------------------ category combo