namespace AcDream.App.UI.Layout; internal sealed record CharacterManagementUiMountResources( uint LayoutId, ImportedLayout Layout, Func TemplateResolver, CharacterManagementUiController.DialogStrings Strings); /// /// Retryable, idempotent composition edge for the pre-world character screen. /// DATs can become readable after the graphical runtime starts (installer copy, /// mapped-file replacement, or a transient catalog miss), so an unavailable /// dialog catalog, root, template, or string must not permanently suppress the /// screen. Once bound, later ticks are no-ops and cannot duplicate the root or /// controller lifetime. /// internal sealed class CharacterManagementUiMountCoordinator : IDisposable { private readonly UiRoot _host; private readonly CharacterSelectionRuntimeBindings _bindings; private readonly Func _ensureDialogs; private readonly Func _loadResources; private bool _disposed; public CharacterManagementUiMountCoordinator( UiRoot host, CharacterSelectionRuntimeBindings bindings, Func ensureDialogs, Func loadResources) { _host = host ?? throw new ArgumentNullException(nameof(host)); _bindings = bindings ?? throw new ArgumentNullException(nameof(bindings)); _ensureDialogs = ensureDialogs ?? throw new ArgumentNullException(nameof(ensureDialogs)); _loadResources = loadResources ?? throw new ArgumentNullException(nameof(loadResources)); } public CharacterManagementUiController? Controller { get; private set; } public void Tick() { if (_disposed || Controller is not null) return; try { RetailDialogFactory? dialogs = _ensureDialogs(); if (dialogs is null) return; CharacterManagementUiMountResources? resources = _loadResources(); if (resources is null) return; CharacterManagementUiController? candidate = CharacterManagementUiController.CreateDetached( _host, resources.Layout, resources.TemplateResolver, dialogs, _bindings, resources.Strings); if (candidate is null) return; // Take ownership before the first attach/tick. Template resolution // happens inside that tick and can throw after the root and button // handlers are live; the catch below can therefore always retire // the exact partial controller before a later retry. Controller = candidate; candidate.AttachAndTick(); Console.WriteLine( $"[UI] retail character management from enum table 5 " + $"(0x10000005 -> 0x{resources.LayoutId:X8}, " + "root 0x1000039A; flat list, no viewport)."); } catch (Exception error) { CharacterManagementUiController? partial = Controller; Controller = null; try { partial?.Dispose(); } catch (Exception cleanupError) { Console.WriteLine( "[UI] character management partial-mount cleanup failed: " + cleanupError.Message); } Console.WriteLine( "[UI] character management mount will retry after resource " + $"recovery: {error.Message}"); } } public void Dispose() { if (_disposed) return; _disposed = true; Controller?.Dispose(); Controller = null; } }