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

@ -20,6 +20,7 @@ internal sealed class CurrentGameRuntimeAdapter
{
private readonly GameRuntime _runtime;
private readonly CurrentGameRuntimeCommandAdapter _commands;
private readonly CharacterSelectionProjection _characterSelection;
private readonly IDisposable _hostLease;
private readonly object _subscriptionGate = new();
private readonly HashSet<AdapterSubscription> _subscriptions = [];
@ -40,6 +41,7 @@ internal sealed class CurrentGameRuntimeAdapter
"graphical game-runtime command adapter");
try
{
_characterSelection = new CharacterSelectionProjection(this);
_commands = new CurrentGameRuntimeCommandAdapter(
runtime.Session,
sessionHost,
@ -61,7 +63,7 @@ internal sealed class CurrentGameRuntimeAdapter
}
private bool IsActive =>
!_disposed
!Volatile.Read(ref _disposed)
&& !_runtime.Session.IsDisposalComplete;
public RuntimeGenerationToken Generation => _runtime.Generation;
@ -90,7 +92,7 @@ internal sealed class CurrentGameRuntimeAdapter
public IRuntimeSocialView Social => _runtime.Social;
public IRuntimeChatView Chat => _runtime.Chat;
public IRuntimeCharacterSelectionView CharacterSelection =>
_runtime.CharacterSelection;
_characterSelection;
public IRuntimeFellowshipView Fellowship => _runtime.Fellowship;
public IRuntimeAllegianceView Allegiance => _runtime.Allegiance;
public IRuntimeActionView Actions => _runtime.Actions;
@ -100,9 +102,9 @@ internal sealed class CurrentGameRuntimeAdapter
public IRuntimeSessionCommands Session => _commands;
public IRuntimeCharacterSelectionCommands CharacterSelectionCommands =>
_runtime.Session;
_characterSelection;
IRuntimeCharacterSelectionCommands IGameRuntimeCommands.CharacterSelection =>
_runtime.Session;
_characterSelection;
public IRuntimeSelectionCommands Selection => _commands;
public IRuntimeCombatCommands Combat => _commands;
public IRuntimeMagicCommands Magic => _commands;
@ -166,6 +168,187 @@ internal sealed class CurrentGameRuntimeAdapter
_subscriptions.Remove(subscription);
}
private RuntimeCharacterSelectionSnapshot CharacterSelectionSnapshot()
{
lock (_subscriptionGate)
{
if (IsActive)
return _runtime.CharacterSelection.Snapshot;
return new RuntimeCharacterSelectionSnapshot(
_runtime.Generation,
RuntimeCharacterSelectionLifecycle.Inactive,
Revision: 0,
AccountName: string.Empty,
SlotCount: 0,
RosterCount: 0,
HighlightedCharacterId: 0u,
HighlightedDisplayIndex: -1,
PendingDeleteCharacterId: 0u,
LastRestoreRequestedCharacterId: 0u,
Operation: RuntimeCharacterSelectionOperation.None,
Error: null,
Buttons: RuntimeCharacterSelectionButtons.None);
}
}
private bool TryGetCharacterSelectionAt(
int displayIndex,
out RuntimeCharacterSelectionEntry character)
{
lock (_subscriptionGate)
{
if (IsActive)
{
return _runtime.CharacterSelection.TryGetAt(
displayIndex,
out character);
}
character = default;
return false;
}
}
private bool TryGetCharacterSelection(
uint characterId,
out RuntimeCharacterSelectionEntry character)
{
lock (_subscriptionGate)
{
if (IsActive)
{
return _runtime.CharacterSelection.TryGet(
characterId,
out character);
}
character = default;
return false;
}
}
private void VisitCharacterSelection(
IRuntimeCharacterSelectionVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
lock (_subscriptionGate)
{
if (IsActive)
_runtime.CharacterSelection.Visit(visitor);
}
}
private IDisposable SubscribeCharacterSelection(
IRuntimeCharacterSelectionObserver observer)
{
ArgumentNullException.ThrowIfNull(observer);
lock (_subscriptionGate)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var gated = new AdapterCharacterSelectionObserver(this, observer);
IDisposable runtimeSubscription =
_runtime.CharacterSelection.Subscribe(gated);
var subscription = new AdapterSubscription(
this,
runtimeSubscription);
_subscriptions.Add(subscription);
return subscription;
}
}
private RuntimeCommandResult ExecuteCharacterSelection(
Func<IRuntimeCharacterSelectionCommands, RuntimeCommandResult> execute)
{
lock (_subscriptionGate)
{
if (!IsActive)
{
return new RuntimeCommandResult(
RuntimeCommandStatus.Inactive,
_runtime.Generation);
}
return execute(_runtime.Session);
}
}
private void ForwardCharacterSelection(
IRuntimeCharacterSelectionObserver observer,
in RuntimeCharacterSelectionDelta delta)
{
lock (_subscriptionGate)
{
if (IsActive)
observer.OnCharacterSelectionChanged(in delta);
}
}
private sealed class CharacterSelectionProjection(
CurrentGameRuntimeAdapter owner)
: IRuntimeCharacterSelectionView,
IRuntimeCharacterSelectionCommands
{
public RuntimeCharacterSelectionSnapshot Snapshot =>
owner.CharacterSelectionSnapshot();
public bool TryGetAt(
int displayIndex,
out RuntimeCharacterSelectionEntry character) =>
owner.TryGetCharacterSelectionAt(displayIndex, out character);
public bool TryGet(
uint characterId,
out RuntimeCharacterSelectionEntry character) =>
owner.TryGetCharacterSelection(characterId, out character);
public void Visit(IRuntimeCharacterSelectionVisitor visitor) =>
owner.VisitCharacterSelection(visitor);
public IDisposable Subscribe(
IRuntimeCharacterSelectionObserver observer) =>
owner.SubscribeCharacterSelection(observer);
public RuntimeCommandResult Highlight(
RuntimeGenerationToken expectedGeneration,
uint characterId) =>
owner.ExecuteCharacterSelection(
commands => commands.Highlight(
expectedGeneration,
characterId));
public RuntimeCommandResult Enter(
RuntimeGenerationToken expectedGeneration) =>
owner.ExecuteCharacterSelection(
commands => commands.Enter(expectedGeneration));
public RuntimeCommandResult RequestDelete(
RuntimeGenerationToken expectedGeneration) =>
owner.ExecuteCharacterSelection(
commands => commands.RequestDelete(expectedGeneration));
public RuntimeCommandResult ConfirmDelete(
RuntimeGenerationToken expectedGeneration) =>
owner.ExecuteCharacterSelection(
commands => commands.ConfirmDelete(expectedGeneration));
public RuntimeCommandResult Restore(
RuntimeGenerationToken expectedGeneration) =>
owner.ExecuteCharacterSelection(
commands => commands.Restore(expectedGeneration));
public RuntimeCommandResult Cancel(
RuntimeGenerationToken expectedGeneration) =>
owner.ExecuteCharacterSelection(
commands => commands.Cancel(expectedGeneration));
}
private sealed class AdapterCharacterSelectionObserver(
CurrentGameRuntimeAdapter owner,
IRuntimeCharacterSelectionObserver observer)
: IRuntimeCharacterSelectionObserver
{
public void OnCharacterSelectionChanged(
in RuntimeCharacterSelectionDelta delta) =>
owner.ForwardCharacterSelection(observer, in delta);
}
private sealed class AdapterSubscription(
CurrentGameRuntimeAdapter owner,
IDisposable runtimeSubscription) : IDisposable