acdream/src/AcDream.App/UI/Layout/CharacterManagementUiMountCoordinator.cs

106 lines
3.8 KiB
C#

namespace AcDream.App.UI.Layout;
internal sealed record CharacterManagementUiMountResources(
uint LayoutId,
ImportedLayout Layout,
Func<uint, uint, UiElement?> TemplateResolver,
CharacterManagementUiController.DialogStrings Strings);
/// <summary>
/// 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.
/// </summary>
internal sealed class CharacterManagementUiMountCoordinator : IDisposable
{
private readonly UiRoot _host;
private readonly CharacterSelectionRuntimeBindings _bindings;
private readonly Func<RetailDialogFactory?> _ensureDialogs;
private readonly Func<CharacterManagementUiMountResources?> _loadResources;
private bool _disposed;
public CharacterManagementUiMountCoordinator(
UiRoot host,
CharacterSelectionRuntimeBindings bindings,
Func<RetailDialogFactory?> ensureDialogs,
Func<CharacterManagementUiMountResources?> 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;
}
}