refactor(runtime): own interaction transactions
Move use throttling, appraisal identity, queued interactions, and exact post-arrival pickup state beneath RuntimeActionState. Keep App as the picker, movement, transport, and retained-presentation adapter while preserving retail send and UseDone ordering. Add reset, disposal, GUID-reuse, callback-reentrancy, and transport-failure coverage. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
be73bccf5a
commit
f5f7b4177f
31 changed files with 1365 additions and 432 deletions
|
|
@ -13,7 +13,8 @@ public readonly record struct RuntimeActionSnapshot(
|
|||
int TrackedTargetHealthCount,
|
||||
long InteractionRevision,
|
||||
InteractionModeKind InteractionMode,
|
||||
uint InteractionSourceObjectId);
|
||||
uint InteractionSourceObjectId,
|
||||
RuntimeInteractionTransactionSnapshot InteractionTransactions);
|
||||
|
||||
public interface IRuntimeActionView
|
||||
{
|
||||
|
|
|
|||
|
|
@ -169,7 +169,14 @@ public sealed class RuntimeTraceRecorder : IRuntimeEventObserver
|
|||
$"{checkpoint.Actions.TrackedTargetHealthCount}:" +
|
||||
$"{checkpoint.Actions.InteractionRevision}:" +
|
||||
$"{(int)checkpoint.Actions.InteractionMode}:" +
|
||||
$"{checkpoint.Actions.InteractionSourceObjectId:X8};" +
|
||||
$"{checkpoint.Actions.InteractionSourceObjectId:X8}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.Revision}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.LastUseSourceId:X8}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.LastUseTargetId:X8}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.AwaitingAppraisalId:X8}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.CurrentAppraisalId:X8}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.OutboundCount}:" +
|
||||
$"{checkpoint.Actions.InteractionTransactions.PendingPickupToken};" +
|
||||
$"portal={checkpoint.Portal.Generation}:{(int)checkpoint.Portal.Kind}:" +
|
||||
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady}")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Selection;
|
||||
|
||||
namespace AcDream.Runtime.Gameplay;
|
||||
|
|
@ -6,12 +7,14 @@ namespace AcDream.Runtime.Gameplay;
|
|||
public readonly record struct RuntimeActionOwnershipSnapshot(
|
||||
bool IsDisposed,
|
||||
bool InternalSubscriptionsAttached,
|
||||
bool InteractionTransactionsDisposed,
|
||||
uint SelectedObjectId,
|
||||
uint PreviousObjectId,
|
||||
uint PreviousValidObjectId,
|
||||
CombatMode CombatMode,
|
||||
int TrackedTargetHealthCount,
|
||||
InteractionMode InteractionMode,
|
||||
RuntimeInteractionTransactionSnapshot InteractionTransactions,
|
||||
long SelectionRevision,
|
||||
long CombatRevision,
|
||||
long InteractionRevision)
|
||||
|
|
@ -19,12 +22,14 @@ public readonly record struct RuntimeActionOwnershipSnapshot(
|
|||
public bool IsConverged =>
|
||||
IsDisposed
|
||||
&& !InternalSubscriptionsAttached
|
||||
&& InteractionTransactionsDisposed
|
||||
&& SelectedObjectId == 0u
|
||||
&& PreviousObjectId == 0u
|
||||
&& PreviousValidObjectId == 0u
|
||||
&& CombatMode == CombatMode.NonCombat
|
||||
&& TrackedTargetHealthCount == 0
|
||||
&& InteractionMode == InteractionMode.None;
|
||||
&& InteractionMode == InteractionMode.None
|
||||
&& InteractionTransactions.IsConverged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -40,11 +45,14 @@ public sealed class RuntimeActionState : IDisposable
|
|||
private long _combatRevision;
|
||||
private long _interactionRevision;
|
||||
|
||||
public RuntimeActionState()
|
||||
public RuntimeActionState(InventoryTransactionState inventoryTransactions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(inventoryTransactions);
|
||||
Selection = new SelectionState();
|
||||
Combat = new CombatState();
|
||||
Interaction = new InteractionState();
|
||||
Transactions = new RuntimeInteractionTransactionState(
|
||||
inventoryTransactions);
|
||||
View = new ActionView(this);
|
||||
|
||||
Selection.Changed += OnSelectionChanged;
|
||||
|
|
@ -57,18 +65,21 @@ public sealed class RuntimeActionState : IDisposable
|
|||
public SelectionState Selection { get; }
|
||||
public CombatState Combat { get; }
|
||||
public InteractionState Interaction { get; }
|
||||
public RuntimeInteractionTransactionState Transactions { get; }
|
||||
public IRuntimeActionView View { get; }
|
||||
public bool IsDisposed => _disposed;
|
||||
|
||||
public RuntimeActionOwnershipSnapshot CaptureOwnership() => new(
|
||||
_disposed,
|
||||
_internalSubscriptionsAttached,
|
||||
Transactions.IsDisposed,
|
||||
Selection.SelectedObjectId ?? 0u,
|
||||
Selection.PreviousObjectId ?? 0u,
|
||||
Selection.PreviousValidObjectId ?? 0u,
|
||||
Combat.CurrentMode,
|
||||
Combat.TrackedTargetCount,
|
||||
Interaction.Current,
|
||||
Transactions.CaptureOwnership(),
|
||||
Interlocked.Read(ref _selectionRevision),
|
||||
Interlocked.Read(ref _combatRevision),
|
||||
Interlocked.Read(ref _interactionRevision));
|
||||
|
|
@ -82,6 +93,7 @@ public sealed class RuntimeActionState : IDisposable
|
|||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
List<Exception>? failures = null;
|
||||
Try(Transactions.ResetSession, ref failures);
|
||||
Try(Interaction.ResetSession, ref failures);
|
||||
Try(() => Selection.Reset(), ref failures);
|
||||
Try(Combat.Clear, ref failures);
|
||||
|
|
@ -101,6 +113,7 @@ public sealed class RuntimeActionState : IDisposable
|
|||
List<Exception>? failures = null;
|
||||
try
|
||||
{
|
||||
Try(Transactions.Dispose, ref failures);
|
||||
Try(Interaction.ResetSession, ref failures);
|
||||
Try(() => Selection.Reset(), ref failures);
|
||||
Try(Combat.Clear, ref failures);
|
||||
|
|
@ -160,7 +173,8 @@ public sealed class RuntimeActionState : IDisposable
|
|||
owner.Combat.TrackedTargetCount,
|
||||
Interlocked.Read(ref owner._interactionRevision),
|
||||
owner.Interaction.Current.Kind,
|
||||
owner.Interaction.Current.SourceObjectId);
|
||||
owner.Interaction.Current.SourceObjectId,
|
||||
owner.Transactions.CaptureOwnership());
|
||||
|
||||
public bool TryGetHealth(uint objectId, out float healthPercent)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,572 @@
|
|||
using AcDream.Core.Items;
|
||||
|
||||
namespace AcDream.Runtime.Gameplay;
|
||||
|
||||
public enum RuntimeQueuedInteractionKind
|
||||
{
|
||||
Activate,
|
||||
Use,
|
||||
Pickup,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeInteractionIdentity(
|
||||
uint ServerGuid,
|
||||
uint? LocalEntityId,
|
||||
ClientObject? ClientObject);
|
||||
|
||||
public readonly record struct RuntimeQueuedInteraction(
|
||||
RuntimeQueuedInteractionKind Kind,
|
||||
RuntimeInteractionIdentity Identity);
|
||||
|
||||
public readonly record struct RuntimeInteractionApproachToken(
|
||||
ulong ControllerLifetime,
|
||||
ulong ApproachGeneration);
|
||||
|
||||
public readonly record struct RuntimePendingPickup(
|
||||
ulong Token,
|
||||
uint ServerGuid,
|
||||
uint LocalEntityId,
|
||||
uint DestinationContainerId,
|
||||
int Placement,
|
||||
ulong PendingPlacementToken,
|
||||
RuntimeInteractionApproachToken ApproachToken);
|
||||
|
||||
public readonly record struct RuntimeAppraisalResponseAcceptance(
|
||||
bool Accepted,
|
||||
bool FirstResponse);
|
||||
|
||||
public enum RuntimeInteractionDispatchResult
|
||||
{
|
||||
Rejected,
|
||||
NotInWorld,
|
||||
NotUseable,
|
||||
Dispatched,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeInteractionTransactionSnapshot(
|
||||
bool IsDisposed,
|
||||
long Revision,
|
||||
uint LastUseSourceId,
|
||||
uint LastUseTargetId,
|
||||
uint AwaitingAppraisalId,
|
||||
uint CurrentAppraisalId,
|
||||
int OutboundCount,
|
||||
bool HasPendingPickup,
|
||||
ulong PendingPickupToken,
|
||||
long DispatchFailureCount)
|
||||
{
|
||||
public bool IsConverged =>
|
||||
IsDisposed
|
||||
&& LastUseSourceId == 0u
|
||||
&& LastUseTargetId == 0u
|
||||
&& AwaitingAppraisalId == 0u
|
||||
&& CurrentAppraisalId == 0u
|
||||
&& OutboundCount == 0
|
||||
&& !HasPendingPickup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Canonical presentation-independent owner for retail interaction
|
||||
/// transactions. The shared inventory gate remains owned by
|
||||
/// <see cref="InventoryTransactionState"/> and is borrowed exactly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ordinary Use follows <c>ItemHolder::UseObject @ 0x00588A80</c>: consume the
|
||||
/// strict 0.2-second gate, send immediately, then transfer the busy reference
|
||||
/// to <c>ClientUISystem::Handle_Item__UseDone @ 0x00564900</c>. Pickup keeps
|
||||
/// the existing local approach transaction and exact post-arrival token.
|
||||
/// </remarks>
|
||||
public sealed class RuntimeInteractionTransactionState : IDisposable
|
||||
{
|
||||
public const long RetailUseThrottleMs = 200;
|
||||
|
||||
private readonly InventoryTransactionState _inventory;
|
||||
private readonly Queue<RuntimeQueuedInteraction> _outbound = new();
|
||||
private long _lastUseMs = long.MinValue / 2;
|
||||
private uint _lastUseSourceId;
|
||||
private uint _lastUseTargetId;
|
||||
private uint _awaitingAppraisalId;
|
||||
private uint _currentAppraisalId;
|
||||
private RuntimePendingPickup? _pendingPickup;
|
||||
private ulong _nextPickupToken;
|
||||
private uint _clearEpoch;
|
||||
private long _revision;
|
||||
private long _dispatchFailureCount;
|
||||
private bool _disposed;
|
||||
|
||||
public RuntimeInteractionTransactionState(
|
||||
InventoryTransactionState inventory)
|
||||
{
|
||||
_inventory = inventory
|
||||
?? throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
public InventoryTransactionState Inventory => _inventory;
|
||||
public uint AwaitingAppraisalId => _awaitingAppraisalId;
|
||||
public uint CurrentAppraisalId => _currentAppraisalId;
|
||||
public int OutboundCount => _outbound.Count;
|
||||
public bool HasPendingPickup => _pendingPickup is not null;
|
||||
public bool IsDisposed => _disposed;
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
public long DispatchFailureCount =>
|
||||
Interlocked.Read(ref _dispatchFailureCount);
|
||||
public Exception? LastDispatchFailure { get; private set; }
|
||||
|
||||
public RuntimeInteractionTransactionSnapshot CaptureOwnership() => new(
|
||||
_disposed,
|
||||
Revision,
|
||||
_lastUseSourceId,
|
||||
_lastUseTargetId,
|
||||
_awaitingAppraisalId,
|
||||
_currentAppraisalId,
|
||||
_outbound.Count,
|
||||
_pendingPickup is not null,
|
||||
_pendingPickup?.Token ?? 0u,
|
||||
DispatchFailureCount);
|
||||
|
||||
public bool TryConsumeUseThrottle(long nowMs)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (nowMs - _lastUseMs < RetailUseThrottleMs)
|
||||
return false;
|
||||
_lastUseMs = nowMs;
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
public ItemUseRequestReservation BeginUseRequestReservation()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _inventory.BeginUseRequestReservation();
|
||||
}
|
||||
|
||||
public RuntimeInteractionDispatchResult TryDispatchUse(
|
||||
uint serverGuid,
|
||||
bool ownedByPlayer,
|
||||
bool useable,
|
||||
ItemUseRequestReservation? reservation,
|
||||
IRuntimeInteractionTransport transport,
|
||||
out uint sequence)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(transport);
|
||||
sequence = 0u;
|
||||
|
||||
if (serverGuid == 0u)
|
||||
{
|
||||
reservation?.CancelBeforeDispatch();
|
||||
return RuntimeInteractionDispatchResult.Rejected;
|
||||
}
|
||||
if (!transport.IsInWorld)
|
||||
{
|
||||
reservation?.CancelBeforeDispatch();
|
||||
return RuntimeInteractionDispatchResult.NotInWorld;
|
||||
}
|
||||
if (!ownedByPlayer && !useable)
|
||||
{
|
||||
reservation?.CancelBeforeDispatch();
|
||||
return RuntimeInteractionDispatchResult.NotUseable;
|
||||
}
|
||||
if (!transport.TrySendUse(serverGuid, out sequence))
|
||||
{
|
||||
reservation?.CancelBeforeDispatch();
|
||||
return RuntimeInteractionDispatchResult.Rejected;
|
||||
}
|
||||
|
||||
reservation?.MarkDispatched();
|
||||
_lastUseSourceId = serverGuid;
|
||||
_lastUseTargetId = 0u;
|
||||
IncrementRevision();
|
||||
return RuntimeInteractionDispatchResult.Dispatched;
|
||||
}
|
||||
|
||||
public bool TryDispatchTargetedUse(
|
||||
uint sourceObjectId,
|
||||
uint targetObjectId,
|
||||
Action<uint, uint>? dispatch,
|
||||
bool incrementBusy)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (sourceObjectId == 0u
|
||||
|| targetObjectId == 0u
|
||||
|| dispatch is null)
|
||||
return false;
|
||||
|
||||
uint epoch = _clearEpoch;
|
||||
dispatch(sourceObjectId, targetObjectId);
|
||||
if (_disposed || epoch != _clearEpoch)
|
||||
return false;
|
||||
|
||||
_lastUseSourceId = sourceObjectId;
|
||||
_lastUseTargetId = targetObjectId;
|
||||
if (incrementBusy)
|
||||
_inventory.IncrementBusyCount();
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void IncrementBusyCount()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_inventory.IncrementBusyCount();
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
public void CompleteUse(uint error)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
int before = _inventory.BusyCount;
|
||||
_inventory.CompleteUse(error);
|
||||
if (_inventory.BusyCount != before)
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
public bool TryRequestAppraisal(
|
||||
uint objectId,
|
||||
Action<uint> sendAppraisal)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(sendAppraisal);
|
||||
if (objectId == 0u)
|
||||
return false;
|
||||
|
||||
uint epoch = _clearEpoch;
|
||||
bool acquiredBusy = _awaitingAppraisalId == 0u;
|
||||
if (acquiredBusy)
|
||||
{
|
||||
_inventory.IncrementBusyCount();
|
||||
if (_disposed || epoch != _clearEpoch)
|
||||
return false;
|
||||
}
|
||||
|
||||
uint previousAwaiting = _awaitingAppraisalId;
|
||||
_awaitingAppraisalId = objectId;
|
||||
IncrementRevision();
|
||||
try
|
||||
{
|
||||
sendAppraisal(objectId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// A synchronous reset/disposal or newer request owns the result.
|
||||
// Roll back only the exact request installed by this call.
|
||||
if (!_disposed
|
||||
&& epoch == _clearEpoch
|
||||
&& _awaitingAppraisalId == objectId)
|
||||
{
|
||||
_awaitingAppraisalId = previousAwaiting;
|
||||
if (acquiredBusy)
|
||||
_inventory.CompleteUse(0u);
|
||||
IncrementRevision();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public RuntimeAppraisalResponseAcceptance AcceptAppraisalResponse(
|
||||
uint objectId)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (objectId == 0u
|
||||
|| (objectId != _awaitingAppraisalId
|
||||
&& objectId != _currentAppraisalId))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
bool firstResponse = objectId == _awaitingAppraisalId;
|
||||
if (firstResponse)
|
||||
{
|
||||
_awaitingAppraisalId = 0u;
|
||||
_currentAppraisalId = objectId;
|
||||
_inventory.CompleteUse(0u);
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
return new RuntimeAppraisalResponseAcceptance(
|
||||
Accepted: true,
|
||||
FirstResponse: firstResponse);
|
||||
}
|
||||
|
||||
public bool RefreshCurrentAppraisal(Action<uint> sendAppraisal)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(sendAppraisal);
|
||||
if (_currentAppraisalId == 0u)
|
||||
return false;
|
||||
sendAppraisal(_currentAppraisalId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CancelObjectAppraisalForSpell(Action<uint> sendAppraisal)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(sendAppraisal);
|
||||
if (_awaitingAppraisalId == 0u && _currentAppraisalId == 0u)
|
||||
return false;
|
||||
|
||||
if (_awaitingAppraisalId != 0u)
|
||||
_inventory.CompleteUse(0u);
|
||||
_awaitingAppraisalId = 0u;
|
||||
_currentAppraisalId = 0u;
|
||||
IncrementRevision();
|
||||
sendAppraisal(0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Enqueue(RuntimeQueuedInteraction interaction)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (interaction.Identity.ServerGuid == 0u)
|
||||
return;
|
||||
_outbound.Enqueue(interaction);
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
public int CancelQueuedInteractions(
|
||||
uint serverGuid,
|
||||
uint? localEntityId = null)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (serverGuid == 0u || _outbound.Count == 0)
|
||||
return 0;
|
||||
|
||||
int original = _outbound.Count;
|
||||
int removed = 0;
|
||||
for (int i = 0; i < original; i++)
|
||||
{
|
||||
RuntimeQueuedInteraction interaction = _outbound.Dequeue();
|
||||
RuntimeInteractionIdentity identity = interaction.Identity;
|
||||
bool matches =
|
||||
identity.ServerGuid == serverGuid
|
||||
&& (localEntityId is not uint exact
|
||||
|| identity.LocalEntityId == exact);
|
||||
if (matches)
|
||||
removed++;
|
||||
else
|
||||
_outbound.Enqueue(interaction);
|
||||
}
|
||||
if (removed != 0)
|
||||
IncrementRevision();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drains only work present at the frame boundary. Re-entrant additions
|
||||
/// remain for the next frame, matching the pre-J5 ordered interaction
|
||||
/// queue without retaining App delegates.
|
||||
/// </summary>
|
||||
public void DrainOutbound(Action<RuntimeQueuedInteraction> dispatch)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(dispatch);
|
||||
|
||||
int count = _outbound.Count;
|
||||
uint epoch = _clearEpoch;
|
||||
bool drained = false;
|
||||
while (count-- > 0 && epoch == _clearEpoch && _outbound.Count > 0)
|
||||
{
|
||||
RuntimeQueuedInteraction interaction = _outbound.Dequeue();
|
||||
drained = true;
|
||||
try
|
||||
{
|
||||
dispatch(interaction);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Interlocked.Increment(ref _dispatchFailureCount);
|
||||
LastDispatchFailure = error;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
if (drained)
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
public bool TryArmPostArrivalPickup(
|
||||
uint serverGuid,
|
||||
uint localEntityId,
|
||||
uint destinationContainerId,
|
||||
int placement,
|
||||
ulong pendingPlacementToken,
|
||||
RuntimeInteractionApproachToken approachToken,
|
||||
out RuntimePendingPickup pending)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (serverGuid == 0u
|
||||
|| localEntityId == 0u
|
||||
|| destinationContainerId == 0u
|
||||
|| pendingPlacementToken == 0u
|
||||
|| approachToken.ControllerLifetime == 0u
|
||||
|| approachToken.ApproachGeneration == 0u)
|
||||
{
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
if (_pendingPickup is not null)
|
||||
{
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong token = ++_nextPickupToken;
|
||||
if (token == 0u)
|
||||
token = ++_nextPickupToken;
|
||||
pending = new RuntimePendingPickup(
|
||||
token,
|
||||
serverGuid,
|
||||
localEntityId,
|
||||
destinationContainerId,
|
||||
placement,
|
||||
pendingPlacementToken,
|
||||
approachToken);
|
||||
_pendingPickup = pending;
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryResolveApproachCompletion(
|
||||
RuntimeInteractionApproachToken approachToken,
|
||||
bool natural,
|
||||
out RuntimePendingPickup pending)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_pendingPickup is not { } current
|
||||
|| current.ApproachToken != approachToken)
|
||||
{
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
_pendingPickup = null;
|
||||
pending = current;
|
||||
IncrementRevision();
|
||||
return natural;
|
||||
}
|
||||
|
||||
public bool TryGetPendingPickup(out RuntimePendingPickup pending)
|
||||
{
|
||||
if (_pendingPickup is { } current)
|
||||
{
|
||||
pending = current;
|
||||
return true;
|
||||
}
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryCancelPendingPickup(out RuntimePendingPickup pending)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_pendingPickup is not { } current)
|
||||
{
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
_pendingPickup = null;
|
||||
pending = current;
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCancelPendingPickup(
|
||||
uint serverGuid,
|
||||
uint? localEntityId,
|
||||
out RuntimePendingPickup pending)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_pendingPickup is not { } current
|
||||
|| current.ServerGuid != serverGuid
|
||||
|| (localEntityId is uint exact
|
||||
&& current.LocalEntityId != exact))
|
||||
{
|
||||
pending = default;
|
||||
return false;
|
||||
}
|
||||
_pendingPickup = null;
|
||||
pending = current;
|
||||
IncrementRevision();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryDispatchPickup(
|
||||
RuntimePendingPickup pickup,
|
||||
IRuntimeInteractionTransport transport,
|
||||
out uint sequence)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ArgumentNullException.ThrowIfNull(transport);
|
||||
sequence = 0u;
|
||||
if (!transport.IsInWorld)
|
||||
return false;
|
||||
|
||||
uint sentSequence = 0u;
|
||||
bool dispatched = _inventory.TryDispatch(
|
||||
InventoryRequestKind.Pickup,
|
||||
pickup.ServerGuid,
|
||||
() => transport.TrySendPickup(
|
||||
pickup.ServerGuid,
|
||||
pickup.DestinationContainerId,
|
||||
pickup.Placement,
|
||||
out sentSequence),
|
||||
pickup.PendingPlacementToken);
|
||||
sequence = sentSequence;
|
||||
if (dispatched)
|
||||
IncrementRevision();
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
public void ResetSession()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
ResetCore(resetInventory: true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
ResetCore(resetInventory: true);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void ResetCore(bool resetInventory)
|
||||
{
|
||||
bool changed =
|
||||
_awaitingAppraisalId != 0u
|
||||
|| _currentAppraisalId != 0u
|
||||
|| _outbound.Count != 0
|
||||
|| _pendingPickup is not null
|
||||
|| _lastUseSourceId != 0u
|
||||
|| _lastUseTargetId != 0u
|
||||
|| _lastUseMs != long.MinValue / 2;
|
||||
|
||||
_lastUseSourceId = 0u;
|
||||
_lastUseTargetId = 0u;
|
||||
_awaitingAppraisalId = 0u;
|
||||
_currentAppraisalId = 0u;
|
||||
_outbound.Clear();
|
||||
_pendingPickup = null;
|
||||
_lastUseMs = long.MinValue / 2;
|
||||
_clearEpoch++;
|
||||
if (resetInventory)
|
||||
_inventory.ResetSession();
|
||||
if (changed)
|
||||
IncrementRevision();
|
||||
}
|
||||
|
||||
private void IncrementRevision() =>
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public interface IRuntimeInteractionTransport
|
||||
{
|
||||
bool IsInWorld { get; }
|
||||
bool TrySendUse(uint serverGuid, out uint sequence);
|
||||
bool TrySendPickup(
|
||||
uint itemGuid,
|
||||
uint destinationContainerId,
|
||||
int placement,
|
||||
out uint sequence);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue