feat(ui): importer Fix C — per-element dat FontDid resolver (studio path); character level uses its retail font
DatWidgetFactory.Create now accepts an optional fontResolve: Func<uint,UiDatFont?> parameter. When supplied and the element has a non-zero FontDid, the element receives its own dat font instead of the shared global datFont fallback. Null = original single-font behavior (the live GameWindow path passes null — provably unchanged). LayoutImporter.Build/BuildFromInfos/Import all thread the optional resolver down to the factory. RenderStack gains a lazy font cache (ConcurrentDictionary, pre-seeded with VitalsDatFont + LargeDatFont) and a ResolveDatFont(uint) method. StudioWindow wires stack.ResolveDatFont into LayoutSource so every studio import gets per-element fonts. GameWindow import calls left passing null (follow-up todo). CharacterStatController font-hack cleanup (diagnosed via one-shot console dump then removed): - Name (0x10000231): dat FontDid = 18px font — remove datFont override (null) - Heritage/PkStatus: dat FontDid = 14px fonts — remove override - LevelCaption: dat FontDid = 16px — remove override (same font, no visual change) - Level (0x1000023B): dat FontDid = 36px (the big retail gold font) — was forced to rowDatFont/LargeDatFont (18px); now drops to null so the dat 36px font drives - TotalXpLabel/TotalXp: dat FontDid = 16px — remove override - FooterTitle (0x1000024E): dat FontDid = 20px — remove datFont override - KEEP: synthesized elements (XP meter overlays, 9 attribute rows, tab sprites) still use datFont directly since they have no dat origin All Label/LabelTwoLine/LabelLeft/LabelProvider helpers updated: null = keep build-time dat font; non-null = controller explicit override (backward-compat). 8 new tests in DatWidgetFactoryFontResolveTests: - null resolver → DatFont == global datFont - FontDid=0 → resolver not called - resolver returns null → fallback to global datFont - resolver called with element's FontDid - controller DatFont override wins after build - LayoutImporter.Build threads fontResolve to factory - meter element fires resolver for non-zero FontDid - BuildFromInfos without fontResolve param = original behavior Build + all 710 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6e0be4bd34
commit
a0d33956f6
7 changed files with 360 additions and 66 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Concurrent;
|
||||
using DatReaderWriter;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -48,6 +49,46 @@ public sealed record RenderStack(
|
|||
uint t = TextureCache.GetOrUploadRenderSurface(spriteId, out int w, out int h);
|
||||
return (t, w, h);
|
||||
}
|
||||
|
||||
// ── Font cache (per-stack, keyed by FontDid) ─────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Cache of loaded dat fonts keyed by FontDid (0x40000000-range).
|
||||
/// Populated lazily by <see cref="ResolveDatFont"/>. Thread-safe for
|
||||
/// concurrent reads from the studio render loop; writes happen only
|
||||
/// during the first load of each distinct FontDid.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<uint, AcDream.App.UI.UiDatFont?> _fontCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lazily load and cache a dat font by its FontDid. Returns null (and
|
||||
/// caches null) when the Font DBObj is absent or has no foreground surface —
|
||||
/// callers fall back to the global font in that case.
|
||||
///
|
||||
/// <para>Pre-seeds <see cref="VitalsDatFont"/> (0x40000000) and
|
||||
/// <see cref="LargeDatFont"/> (0x40000001) from the already-loaded instances
|
||||
/// to avoid a redundant upload on those two ids.</para>
|
||||
/// </summary>
|
||||
public AcDream.App.UI.UiDatFont? ResolveDatFont(uint fontDid)
|
||||
{
|
||||
return _fontCache.GetOrAdd(fontDid, id =>
|
||||
AcDream.App.UI.UiDatFont.Load(Dats, TextureCache, id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-seeds the font cache from the two already-loaded font instances
|
||||
/// (VitalsDatFont = 0x40000000, LargeDatFont = 0x40000001) so that
|
||||
/// <see cref="ResolveDatFont"/> returns them without a redundant GL upload.
|
||||
/// Called once by <see cref="RenderBootstrap.Create"/> after the stack is
|
||||
/// fully constructed.
|
||||
/// </summary>
|
||||
internal void SeedFontCache()
|
||||
{
|
||||
if (VitalsDatFont is not null)
|
||||
_fontCache.TryAdd(AcDream.App.UI.UiDatFont.DefaultFontId, VitalsDatFont);
|
||||
if (LargeDatFont is not null)
|
||||
_fontCache.TryAdd(0x40000001u, LargeDatFont);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Options for <see cref="RenderBootstrap.Create"/>.</summary>
|
||||
|
|
@ -159,7 +200,7 @@ public static class RenderBootstrap
|
|||
// UI Studio, and BitmapFont requires a system font byte array) ---
|
||||
var uiHost = new AcDream.App.UI.UiHost(gl, shaderDir, defaultFont: null);
|
||||
|
||||
return new RenderStack(
|
||||
var stack = new RenderStack(
|
||||
Gl: gl,
|
||||
Dats: dats,
|
||||
ShaderDir: shaderDir,
|
||||
|
|
@ -173,6 +214,13 @@ public static class RenderBootstrap
|
|||
UiHost: uiHost,
|
||||
VitalsDatFont: vitalsDatFont,
|
||||
LargeDatFont: largeDatFont);
|
||||
|
||||
// Pre-seed the font cache with the two already-uploaded atlas instances
|
||||
// so ResolveDatFont(0x40000000) and ResolveDatFont(0x40000001) hit the cache
|
||||
// rather than re-uploading the same GL texture a second time.
|
||||
stack.SeedFontCache();
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
// NullAnimLoader mirrors GameWindow's private NullAnimLoader (GameWindow ~13327-13330).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue