refactor(runtime): own inventory transaction state
Move the retail one-request-at-a-time gate, shared use busy references, external-container state, item mana, shortcuts, and desired-component snapshots into one Runtime-owned graph over J3's exact ClientObjectTable. Retained UI and session routing now borrow that owner; reset and shutdown preserve the existing order while failure/reentrancy tests protect the transaction boundary. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
595d5e6b01
commit
011efbeaa7
18 changed files with 1337 additions and 406 deletions
114
src/AcDream.Runtime/Gameplay/RuntimeInventoryState.cs
Normal file
114
src/AcDream.Runtime/Gameplay/RuntimeInventoryState.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
using AcDream.Core.Items;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.Runtime.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical presentation-independent owner for inventory gameplay state.
|
||||
/// The object collection is borrowed directly from J3's entity/object
|
||||
/// lifetime; this owner creates no second inventory model.
|
||||
/// </summary>
|
||||
public sealed class RuntimeInventoryState : IDisposable
|
||||
{
|
||||
private readonly RuntimeEntityObjectLifetime _entityObjects;
|
||||
private bool _disposed;
|
||||
|
||||
public RuntimeInventoryState(RuntimeEntityObjectLifetime entityObjects)
|
||||
{
|
||||
_entityObjects = entityObjects
|
||||
?? throw new ArgumentNullException(nameof(entityObjects));
|
||||
ExternalContainers = new ExternalContainerState();
|
||||
ItemMana = new ItemManaState();
|
||||
Shortcuts = new ShortcutState();
|
||||
DesiredComponents = new DesiredComponentState();
|
||||
Transactions = new InventoryTransactionState(_entityObjects.Objects);
|
||||
}
|
||||
|
||||
public ClientObjectTable Objects => _entityObjects.Objects;
|
||||
public ExternalContainerState ExternalContainers { get; }
|
||||
public ItemManaState ItemMana { get; }
|
||||
public ShortcutState Shortcuts { get; }
|
||||
public DesiredComponentState DesiredComponents { get; }
|
||||
public InventoryTransactionState Transactions { get; }
|
||||
public bool IsDisposed => _disposed;
|
||||
|
||||
public void ResetExternalContainer() => ExternalContainers.Reset();
|
||||
public void ResetTransactions() => Transactions.ResetSession();
|
||||
public void ResetItemMana() => ItemMana.Clear();
|
||||
|
||||
public void ResetPlayerSnapshots()
|
||||
{
|
||||
Shortcuts.Clear();
|
||||
DesiredComponents.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
List<Exception>? failures = null;
|
||||
Try(() => ExternalContainers.Reset(), ref failures);
|
||||
Try(ItemMana.Clear, ref failures);
|
||||
Try(Shortcuts.Clear, ref failures);
|
||||
Try(DesiredComponents.Clear, ref failures);
|
||||
Try(Transactions.Dispose, ref failures);
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
"Runtime inventory state did not converge during disposal.",
|
||||
failures);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static void Try(Action action, ref List<Exception>? failures)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ShortcutState
|
||||
{
|
||||
private IReadOnlyList<ShortcutEntry> _items = Array.Empty<ShortcutEntry>();
|
||||
private long _revision;
|
||||
|
||||
public IReadOnlyList<ShortcutEntry> Items => Volatile.Read(ref _items);
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
|
||||
public void Replace(IReadOnlyList<ShortcutEntry>? items)
|
||||
{
|
||||
Volatile.Write(
|
||||
ref _items,
|
||||
items ?? Array.Empty<ShortcutEntry>());
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public void Clear() => Replace(Array.Empty<ShortcutEntry>());
|
||||
}
|
||||
|
||||
public sealed class DesiredComponentState
|
||||
{
|
||||
private IReadOnlyList<(uint Id, uint Amount)> _items =
|
||||
Array.Empty<(uint Id, uint Amount)>();
|
||||
private long _revision;
|
||||
|
||||
public IReadOnlyList<(uint Id, uint Amount)> Items =>
|
||||
Volatile.Read(ref _items);
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
|
||||
public void Replace(IReadOnlyList<(uint Id, uint Amount)>? items)
|
||||
{
|
||||
Volatile.Write(
|
||||
ref _items,
|
||||
items ?? Array.Empty<(uint Id, uint Amount)>());
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public void Clear() =>
|
||||
Replace(Array.Empty<(uint Id, uint Amount)>());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue