namespace AcDream.App.UI.Layout;
///
/// Caching row-template resolver for
/// consumers whose row COUNT can change across a session (a live roster,
/// not a fixed authored set) — Friends, Squelch, and (Campaign FA slice
/// FA4) Fellowship all resolve their 0x64 template entries through
/// one instance of this class.
///
///
/// Originally a private local function inside
/// (Campaign FA slice FA3
/// fix-round blast SF-2): the ORIGINAL shape re-ran
/// LayoutImporter.ImportInfos — a full DAT tree walk — under the
/// shared DAT lock on EVERY row, every revision, even while the panel was
/// closed. The fix caches each template id pair's resolved
/// tree the FIRST time it is imported and never
/// re-imports for that pair again; still calls
/// (constructor parameter) on every invocation
/// because each row needs its OWN instance — only
/// the expensive per-row DAT tree WALK is memoized, not the built widget.
///
///
///
/// Extracted to its own class in FA4 (carry-forward 2, the FA3 re-review's
/// non-blocking finding: "add a production-resolver test for the new
/// template cache") so this caching behavior is unit-testable against fake
/// / delegates — no
/// live DAT access needed to prove the cache actually short-circuits a
/// repeat import. Production callers still take the shared DAT lock
/// AROUND at the call site (see
/// RetailUiRuntime.MountSocialPanel) — this class has no lock of its
/// own, matching the "controller has no DAT dependency of its own" shape
/// every other hermetically-testable page controller in this codebase
/// already follows.
///
///
public sealed class RowTemplateResolver
{
private readonly Dictionary<(uint LayoutId, uint ElementId), ElementInfo?> _cache = new();
private readonly Func _importInfos;
private readonly Func _build;
/// Number of times (the constructor
/// parameter) actually ran — i.e. cache MISSES. Exposed for the
/// production-resolver conformance test; not used by any production
/// code path.
public int ImportCount { get; private set; }
public RowTemplateResolver(
Func importInfos,
Func build)
{
_importInfos = importInfos ?? throw new ArgumentNullException(nameof(importInfos));
_build = build ?? throw new ArgumentNullException(nameof(build));
}
/// Resolves one row template. Null if the (layoutId, elementId)
/// pair does not import (cached as a miss — a permanently-unresolvable
/// template is not retried on every call; see the class doc's cache
/// contract). 's exact
/// signature.
public UiElement? Resolve(uint templateLayoutId, uint templateElementId)
{
var key = (templateLayoutId, templateElementId);
if (!_cache.TryGetValue(key, out ElementInfo? info))
{
info = _importInfos(templateLayoutId, templateElementId);
_cache[key] = info;
ImportCount++;
}
return info is null ? null : _build(info);
}
}