Harden retail character selection recovery
This commit is contained in:
parent
6cfab727f1
commit
aeac874dab
8 changed files with 736 additions and 81 deletions
|
|
@ -24,6 +24,7 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
private readonly Dictionary<uint, DialogInfo> _activeQueued = new();
|
||||
private readonly Dictionary<uint, DialogInfo> _activeNonQueued = new();
|
||||
private readonly Dictionary<uint, LinkedList<DialogInfo>> _pending = new();
|
||||
private readonly LinkedList<DialogInfo> _retryable = new();
|
||||
private readonly List<DialogInfo> _openOrder = new();
|
||||
private uint _globalContext;
|
||||
private bool _resetting;
|
||||
|
|
@ -51,6 +52,8 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
|
||||
public int PendingCount => _pending.Values.Sum(static queue => queue.Count);
|
||||
|
||||
internal int RetryCount => _retryable.Count;
|
||||
|
||||
/// <summary>Exact root-element switch from <c>CreateDialog_ @ 0x00477AD0</c>.</summary>
|
||||
public static uint RootElementId(RetailDialogType type)
|
||||
=> type switch
|
||||
|
|
@ -93,14 +96,28 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
if (queueKey == NonQueuedKey)
|
||||
{
|
||||
_activeNonQueued.Add(context, info);
|
||||
CreateDialog(info);
|
||||
if (!TryCreateDialog(info))
|
||||
{
|
||||
_activeNonQueued.Remove(context);
|
||||
QueueRetry(info);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
if (!_activeQueued.TryGetValue(queueKey, out DialogInfo? current))
|
||||
{
|
||||
if (HasRetry(queueKey))
|
||||
{
|
||||
PendingQueue(queueKey).AddLast(info);
|
||||
return context;
|
||||
}
|
||||
|
||||
_activeQueued.Add(queueKey, info);
|
||||
CreateDialog(info);
|
||||
if (!TryCreateDialog(info))
|
||||
{
|
||||
_activeQueued.Remove(queueKey);
|
||||
QueueRetry(info);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +135,15 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
Suspend(current);
|
||||
queue.AddFirst(current);
|
||||
_activeQueued[queueKey] = info;
|
||||
CreateDialog(info);
|
||||
if (!TryCreateDialog(info))
|
||||
{
|
||||
_activeQueued.Remove(queueKey);
|
||||
queue.Remove(current);
|
||||
if (queue.Count == 0)
|
||||
_pending.Remove(queueKey);
|
||||
OpenSpecificDialog(current);
|
||||
QueueRetry(info);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -211,11 +236,25 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
LinkedListNode<DialogInfo>? retry = _retryable.First;
|
||||
while (retry is not null && retry.Value.Context != context)
|
||||
retry = retry.Next;
|
||||
if (retry is not null)
|
||||
{
|
||||
DialogInfo failed = retry.Value;
|
||||
_retryable.Remove(retry);
|
||||
DialogDone(failed);
|
||||
if (failed.QueueKey != NonQueuedKey)
|
||||
OpenNextDialog(failed.QueueKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
RetryFailedDialogs();
|
||||
foreach (DialogInfo info in _openOrder.ToArray())
|
||||
info.View?.Tick();
|
||||
}
|
||||
|
|
@ -238,6 +277,7 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
DialogInfo[] infos = _activeNonQueued.Values
|
||||
.Concat(_activeQueued.Values)
|
||||
.Concat(_pending.Values.SelectMany(static queue => queue))
|
||||
.Concat(_retryable)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
if (infos.Length == 0)
|
||||
|
|
@ -249,6 +289,7 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
_activeNonQueued.Clear();
|
||||
_activeQueued.Clear();
|
||||
_pending.Clear();
|
||||
_retryable.Clear();
|
||||
foreach (DialogInfo info in infos)
|
||||
{
|
||||
try { DialogDone(info); }
|
||||
|
|
@ -292,41 +333,57 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
return queue;
|
||||
}
|
||||
|
||||
private void CreateDialog(DialogInfo info)
|
||||
private bool TryCreateDialog(DialogInfo info)
|
||||
{
|
||||
RetailDialogType type = (RetailDialogType)info.Data.GetUInt32(RetailDialogProperty.Type);
|
||||
if (type is not (RetailDialogType.Confirmation
|
||||
or RetailDialogType.Wait
|
||||
or RetailDialogType.Message
|
||||
or RetailDialogType.ConfirmationTextInput))
|
||||
throw new NotSupportedException(
|
||||
$"Retail dialog type {(uint)type} does not have a ported presenter yet.");
|
||||
|
||||
ImportedLayout layout = _createLayout(type)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Retail dialog catalog could not create type {(uint)type}.");
|
||||
IRetailDialogView view = type switch
|
||||
RetailDialogType type = (RetailDialogType)info.Data.GetUInt32(
|
||||
RetailDialogProperty.Type);
|
||||
try
|
||||
{
|
||||
RetailDialogType.Wait => new RetailWaitDialogView(_host, layout, info.Data),
|
||||
RetailDialogType.Message => new RetailMessageDialogView(
|
||||
_host, layout, info.Data, info.Context,
|
||||
context => CloseDialog(context)),
|
||||
RetailDialogType.ConfirmationTextInput =>
|
||||
new RetailConfirmationTextInputDialogView(
|
||||
if (type is not (RetailDialogType.Confirmation
|
||||
or RetailDialogType.Wait
|
||||
or RetailDialogType.Message
|
||||
or RetailDialogType.ConfirmationTextInput))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Retail dialog type {(uint)type} does not have a ported presenter yet.");
|
||||
}
|
||||
|
||||
ImportedLayout layout = _createLayout(type)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Retail dialog catalog could not create type {(uint)type}.");
|
||||
IRetailDialogView view = type switch
|
||||
{
|
||||
RetailDialogType.Wait => new RetailWaitDialogView(
|
||||
_host, layout, info.Data),
|
||||
RetailDialogType.Message => new RetailMessageDialogView(
|
||||
_host, layout, info.Data, info.Context,
|
||||
context => CloseDialog(context)),
|
||||
_ => new RetailConfirmationDialogView(
|
||||
_host, layout, info.Data, info.Context,
|
||||
context => CloseDialog(context)),
|
||||
};
|
||||
info.View = view;
|
||||
_host.AddChild(view.Root);
|
||||
_host.BringToFront(view.Root);
|
||||
_openOrder.Add(info);
|
||||
_host.Modal = view.Root;
|
||||
view.Tick();
|
||||
UpdatePendingDialogDisplays();
|
||||
DialogOpened?.Invoke(info.Context);
|
||||
RetailDialogType.ConfirmationTextInput =>
|
||||
new RetailConfirmationTextInputDialogView(
|
||||
_host, layout, info.Data, info.Context,
|
||||
context => CloseDialog(context)),
|
||||
_ => new RetailConfirmationDialogView(
|
||||
_host, layout, info.Data, info.Context,
|
||||
context => CloseDialog(context)),
|
||||
};
|
||||
info.View = view;
|
||||
_host.AddChild(view.Root);
|
||||
_host.BringToFront(view.Root);
|
||||
_openOrder.Add(info);
|
||||
_host.Modal = view.Root;
|
||||
view.Tick();
|
||||
UpdatePendingDialogDisplays();
|
||||
DialogOpened?.Invoke(info.Context);
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
RemoveView(info);
|
||||
Console.WriteLine(
|
||||
$"[UI] retail dialog type {(uint)type} context {info.Context} "
|
||||
+ $"will retry after catalog recovery: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Suspend(DialogInfo info)
|
||||
|
|
@ -380,6 +437,9 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
if (_activeQueued.ContainsKey(queueKey))
|
||||
return;
|
||||
|
||||
if (TryActivateRetry(queueKey))
|
||||
return;
|
||||
|
||||
if (!_pending.TryGetValue(queueKey, out LinkedList<DialogInfo>? queue)
|
||||
|| queue.First is null)
|
||||
return;
|
||||
|
|
@ -389,7 +449,73 @@ public sealed class RetailDialogFactory : IDisposable
|
|||
if (queue.Count == 0)
|
||||
_pending.Remove(queueKey);
|
||||
_activeQueued.Add(queueKey, next);
|
||||
CreateDialog(next);
|
||||
if (!TryCreateDialog(next))
|
||||
{
|
||||
_activeQueued.Remove(queueKey);
|
||||
QueueRetry(next);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSpecificDialog(DialogInfo info)
|
||||
{
|
||||
_activeQueued.Add(info.QueueKey, info);
|
||||
if (!TryCreateDialog(info))
|
||||
{
|
||||
_activeQueued.Remove(info.QueueKey);
|
||||
QueueRetry(info);
|
||||
}
|
||||
}
|
||||
|
||||
private void RetryFailedDialogs()
|
||||
{
|
||||
foreach (DialogInfo info in _retryable.ToArray())
|
||||
{
|
||||
if (info.QueueKey == NonQueuedKey)
|
||||
{
|
||||
_activeNonQueued.Add(info.Context, info);
|
||||
if (TryCreateDialog(info))
|
||||
_retryable.Remove(info);
|
||||
else
|
||||
_activeNonQueued.Remove(info.Context);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_activeQueued.ContainsKey(info.QueueKey)
|
||||
&& ReferenceEquals(FirstRetry(info.QueueKey), info))
|
||||
{
|
||||
TryActivateRetry(info.QueueKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryActivateRetry(uint queueKey)
|
||||
{
|
||||
DialogInfo? info = FirstRetry(queueKey);
|
||||
if (info is null)
|
||||
return false;
|
||||
|
||||
_activeQueued.Add(queueKey, info);
|
||||
if (TryCreateDialog(info))
|
||||
_retryable.Remove(info);
|
||||
else
|
||||
_activeQueued.Remove(queueKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
private DialogInfo? FirstRetry(uint queueKey)
|
||||
{
|
||||
foreach (DialogInfo info in _retryable)
|
||||
if (info.QueueKey == queueKey)
|
||||
return info;
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasRetry(uint queueKey) => FirstRetry(queueKey) is not null;
|
||||
|
||||
private void QueueRetry(DialogInfo info)
|
||||
{
|
||||
if (!_retryable.Contains(info))
|
||||
_retryable.AddLast(info);
|
||||
}
|
||||
|
||||
private void UpdatePendingDialogDisplays()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue