fix(runtime): close Campaign LA7b review findings

This commit is contained in:
Erik 2026-08-14 18:55:48 +02:00
parent 0e82cbf700
commit 1b9e7e41f9
11 changed files with 700 additions and 28 deletions

View file

@ -178,8 +178,12 @@ public sealed class GameRuntime
faultInjection);
context.Session = dependencies.SessionOperations is null
? new LiveSessionController()
: new LiveSessionController(dependencies.SessionOperations);
? new LiveSessionController(
ProductionLiveSessionOperations.Instance,
dependencies.TimeProvider)
: new LiveSessionController(
dependencies.SessionOperations,
dependencies.TimeProvider);
construction.Own(context.Session);
Fault(
GameRuntimeConstructionPoint.SessionCreated,

View file

@ -376,14 +376,17 @@ public sealed class LiveSessionController
private Action<WorldSession>? _preLogoffFlushHook;
public LiveSessionController()
: this(ProductionLiveSessionOperations.Instance)
: this(ProductionLiveSessionOperations.Instance, null)
{
}
public LiveSessionController(ILiveSessionOperations operations)
public LiveSessionController(
ILiveSessionOperations operations,
TimeProvider? timeProvider = null)
{
_operations = operations ?? throw new ArgumentNullException(nameof(operations));
CharacterSelectionState = new RuntimeCharacterSelectionState();
CharacterSelectionState = new RuntimeCharacterSelectionState(
timeProvider);
}
public RuntimeCharacterSelectionState CharacterSelectionState { get; }
@ -583,6 +586,7 @@ public sealed class LiveSessionController
_operations.Tick(scope.Session);
if (!IsCurrent(scope, generation))
return;
CharacterSelectionState.SweepRestoreCorrelation();
}
catch (Exception tickError)
{

View file

@ -32,6 +32,7 @@ public enum RuntimeCharacterSelectionDeltaKind
DeleteAcknowledged,
RestoreRequested,
RestoreCompleted,
RestoreCorrelationExpired,
ErrorChanged,
EnteringWorld,
EnteredWorld,
@ -165,6 +166,9 @@ public interface IRuntimeCharacterSelectionCommands
/// </summary>
public sealed class RuntimeCharacterSelectionState : IDisposable
{
internal static readonly TimeSpan RestoreCorrelationTimeout =
TimeSpan.FromSeconds(5);
private sealed class ViewProjection(RuntimeCharacterSelectionState owner)
: IRuntimeCharacterSelectionView
{
@ -191,6 +195,7 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
private readonly object _gate = new();
private readonly RuntimeCharacterSelectionEventStream _events = new();
private readonly ViewProjection _view;
private readonly TimeProvider _timeProvider;
private RuntimeCharacterSelectionEntry[] _entries = [];
private RuntimeGenerationToken _generation;
private RuntimeCharacterSelectionLifecycle _lifecycle;
@ -201,12 +206,15 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
private uint _pendingDeleteCharacterId;
private uint _lastRestoreRequestedCharacterId;
private bool _restoreResponseArmed;
private long _restoreCorrelationStartedTimestamp;
private bool _flagOnlyRestoreResponseAmbiguous;
private RuntimeCharacterSelectionOperation _operation;
private RuntimeCharacterSelectionError? _error;
private bool _disposed;
public RuntimeCharacterSelectionState()
public RuntimeCharacterSelectionState(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_view = new ViewProjection(this);
}
@ -303,6 +311,8 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
_pendingDeleteCharacterId = 0u;
_lastRestoreRequestedCharacterId = 0u;
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
_flagOnlyRestoreResponseAmbiguous = false;
_operation = RuntimeCharacterSelectionOperation.None;
_error = null;
_highlightedCharacterId = Contains(previous)
@ -408,6 +418,7 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
if (_disposed
|| _lifecycle != RuntimeCharacterSelectionLifecycle.AwaitingSelection
|| HasDeleteModalOrRequest()
|| _restoreResponseArmed
|| index < 0
|| !_entries[index].IsPendingDelete)
{
@ -419,6 +430,8 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
_pendingDeleteCharacterId = 0u;
_lastRestoreRequestedCharacterId = character.CharacterId;
_restoreResponseArmed = true;
_restoreCorrelationStartedTimestamp =
_timeProvider.GetTimestamp();
_operation = RuntimeCharacterSelectionOperation.RestoreRequested;
_error = null;
_revision++;
@ -429,6 +442,39 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
return true;
}
/// <summary>
/// Releases the one outstanding restore correlation after a bounded local
/// window. ACE can intentionally send no response for an unknown GUID;
/// later flag-only responses carry no GUID, so once a request expires
/// they remain ambiguous and are never attributed to a newer request.
/// </summary>
internal bool SweepRestoreCorrelation()
{
uint characterId;
lock (_gate)
{
if (_disposed
|| !_restoreResponseArmed
|| _timeProvider.GetElapsedTime(
_restoreCorrelationStartedTimestamp)
< RestoreCorrelationTimeout)
{
return false;
}
characterId = _lastRestoreRequestedCharacterId;
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
_flagOnlyRestoreResponseAmbiguous = true;
_operation = RuntimeCharacterSelectionOperation.None;
_revision++;
}
Publish(
RuntimeCharacterSelectionDeltaKind.RestoreCorrelationExpired,
characterId);
return true;
}
internal bool Cancel()
{
RuntimeCharacterSelectionDeltaKind kind;
@ -493,12 +539,18 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
|| _lifecycle != RuntimeCharacterSelectionLifecycle.AwaitingSelection
|| !_restoreResponseArmed)
return;
if (response.Guid is null
&& _flagOnlyRestoreResponseAmbiguous)
{
return;
}
if (response.Guid is { } responseGuid
&& responseGuid != _lastRestoreRequestedCharacterId)
{
return;
}
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
characterId = response.Guid
?? _lastRestoreRequestedCharacterId;
@ -555,7 +607,10 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
or RuntimeCharacterSelectionLifecycle.EnteringWorld))
return;
_pendingDeleteCharacterId = 0u;
if (_restoreResponseArmed)
_flagOnlyRestoreResponseAmbiguous = true;
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
_operation = RuntimeCharacterSelectionOperation.None;
_error = new RuntimeCharacterSelectionError(
error.RawErrorCode,
@ -588,6 +643,8 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
_pendingDeleteCharacterId = 0u;
_lastRestoreRequestedCharacterId = 0u;
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
_flagOnlyRestoreResponseAmbiguous = false;
_operation = RuntimeCharacterSelectionOperation.None;
_error = null;
_lifecycle = RuntimeCharacterSelectionLifecycle.EnteringWorld;
@ -717,7 +774,7 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
return new RuntimeCharacterSelectionButtons(
CanEnter: false,
CanDelete: false,
CanRestore: true,
CanRestore: !_restoreResponseArmed,
DeleteVisible: false,
RestoreVisible: true);
}
@ -833,6 +890,8 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
_pendingDeleteCharacterId = 0u;
_lastRestoreRequestedCharacterId = 0u;
_restoreResponseArmed = false;
_restoreCorrelationStartedTimestamp = 0;
_flagOnlyRestoreResponseAmbiguous = false;
_operation = RuntimeCharacterSelectionOperation.None;
_error = null;
}