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
|
|
@ -27,7 +27,6 @@ public readonly record struct PendingBackpackPlacement(
|
|||
/// </summary>
|
||||
public sealed class ItemInteractionController : IDisposable
|
||||
{
|
||||
private const long RetailUseThrottleMs = 200;
|
||||
internal const string InventoryRequestBusyMessage =
|
||||
"You can only move or use one item at a time";
|
||||
private const long RetailDoubleClickMs = 500;
|
||||
|
|
@ -62,19 +61,17 @@ public sealed class ItemInteractionController : IDisposable
|
|||
private readonly Action<string>? _systemMessage;
|
||||
private readonly AutoWieldController _autoWield;
|
||||
private readonly Action<uint, ItemUseRequestReservation>? _requestUse;
|
||||
private readonly RuntimeInteractionTransactionState _runtimeTransactions;
|
||||
private readonly InventoryTransactionState _transactions;
|
||||
|
||||
private long _lastUseMs = long.MinValue / 2;
|
||||
private uint _consumedPrimaryClickTarget;
|
||||
private long _consumedPrimaryClickMs = long.MinValue / 2;
|
||||
private uint _awaitingAppraisalId;
|
||||
private uint _currentAppraisalId;
|
||||
private PendingBackpackPlacement? _pendingBackpackPlacement;
|
||||
private bool _disposed;
|
||||
|
||||
public ItemInteractionController(
|
||||
ClientObjectTable objects,
|
||||
InventoryTransactionState transactions,
|
||||
RuntimeInteractionTransactionState runtimeTransactions,
|
||||
InteractionState interactionState,
|
||||
Func<uint> playerGuid,
|
||||
Action<uint>? sendUse,
|
||||
|
|
@ -136,13 +133,14 @@ public sealed class ItemInteractionController : IDisposable
|
|||
_requestUse = requestUse;
|
||||
_interactionState = interactionState
|
||||
?? throw new ArgumentNullException(nameof(interactionState));
|
||||
_transactions = transactions
|
||||
?? throw new ArgumentNullException(nameof(transactions));
|
||||
_runtimeTransactions = runtimeTransactions
|
||||
?? throw new ArgumentNullException(nameof(runtimeTransactions));
|
||||
_transactions = _runtimeTransactions.Inventory;
|
||||
if (!ReferenceEquals(_transactions.Objects, _objects))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The inventory transaction owner must borrow the controller's exact object table.",
|
||||
nameof(transactions));
|
||||
nameof(runtimeTransactions));
|
||||
}
|
||||
_autoWield = new AutoWieldController(
|
||||
_objects,
|
||||
|
|
@ -184,9 +182,12 @@ public sealed class ItemInteractionController : IDisposable
|
|||
public uint PlayerGuid => _playerGuid();
|
||||
|
||||
public InteractionState InteractionState => _interactionState;
|
||||
public RuntimeInteractionTransactionState RuntimeTransactions =>
|
||||
_runtimeTransactions;
|
||||
|
||||
public int BusyCount => _transactions.BusyCount;
|
||||
public uint CurrentAppraisalId => _currentAppraisalId;
|
||||
public uint CurrentAppraisalId =>
|
||||
_runtimeTransactions.CurrentAppraisalId;
|
||||
|
||||
public bool CanMakeInventoryRequest =>
|
||||
BaseCanMakeInventoryRequest && !_transactions.HasPendingRequest;
|
||||
|
|
@ -277,7 +278,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// Item__UseDone too; AttackDone belongs only to the combat attack owner.
|
||||
/// </summary>
|
||||
public void IncrementBusyCount()
|
||||
=> _transactions.IncrementBusyCount();
|
||||
=> _runtimeTransactions.IncrementBusyCount();
|
||||
|
||||
/// <summary>
|
||||
/// Raised for retail confirmation/auxiliary actions whose retained panel is
|
||||
|
|
@ -403,11 +404,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
{
|
||||
if (objectId == 0 || _sendExamine is null)
|
||||
return;
|
||||
|
||||
if (_awaitingAppraisalId == 0)
|
||||
IncrementBusyCount();
|
||||
_awaitingAppraisalId = objectId;
|
||||
_sendExamine(objectId);
|
||||
_runtimeTransactions.TryRequestAppraisal(objectId, _sendExamine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -416,25 +413,14 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public AppraisalResponseAcceptance AcceptAppraisalResponse(uint objectId)
|
||||
{
|
||||
if (objectId == 0
|
||||
|| (objectId != _awaitingAppraisalId
|
||||
&& objectId != _currentAppraisalId))
|
||||
return default;
|
||||
|
||||
bool firstResponse = objectId == _awaitingAppraisalId;
|
||||
if (firstResponse)
|
||||
{
|
||||
_awaitingAppraisalId = 0;
|
||||
_currentAppraisalId = objectId;
|
||||
bool ownedBusyReference = _transactions.BusyCount > 0;
|
||||
_transactions.CompleteUse(0u);
|
||||
if (!ownedBusyReference)
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
|
||||
bool ownedBusyReference = _transactions.BusyCount > 0;
|
||||
RuntimeAppraisalResponseAcceptance acceptance =
|
||||
_runtimeTransactions.AcceptAppraisalResponse(objectId);
|
||||
if (acceptance.FirstResponse && !ownedBusyReference)
|
||||
StateChanged?.Invoke();
|
||||
return new AppraisalResponseAcceptance(
|
||||
Accepted: true,
|
||||
FirstResponse: firstResponse);
|
||||
acceptance.Accepted,
|
||||
acceptance.FirstResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -443,10 +429,9 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public bool RefreshCurrentAppraisal()
|
||||
{
|
||||
if (_currentAppraisalId == 0 || _sendExamine is null)
|
||||
if (_sendExamine is null)
|
||||
return false;
|
||||
_sendExamine(_currentAppraisalId);
|
||||
return true;
|
||||
return _runtimeTransactions.RefreshCurrentAppraisal(_sendExamine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -460,24 +445,14 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public void CancelObjectAppraisalForSpell()
|
||||
{
|
||||
if (_awaitingAppraisalId == 0u && _currentAppraisalId == 0u)
|
||||
if (_sendExamine is null)
|
||||
return;
|
||||
|
||||
if (_awaitingAppraisalId != 0u)
|
||||
bool ownedBusyReference = _transactions.BusyCount > 0;
|
||||
if (_runtimeTransactions.CancelObjectAppraisalForSpell(_sendExamine)
|
||||
&& !ownedBusyReference)
|
||||
{
|
||||
_awaitingAppraisalId = 0u;
|
||||
_currentAppraisalId = 0u;
|
||||
bool ownedBusyReference = _transactions.BusyCount > 0;
|
||||
_transactions.CompleteUse(0u);
|
||||
if (!ownedBusyReference)
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentAppraisalId = 0u;
|
||||
StateChanged?.Invoke();
|
||||
}
|
||||
_sendExamine?.Invoke(0u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -791,8 +766,11 @@ public sealed class ItemInteractionController : IDisposable
|
|||
|
||||
if (!EnsureInventoryRequestReady())
|
||||
return false;
|
||||
_sendUseWithTarget?.Invoke(sourceGuid, targetGuid);
|
||||
_transactions.IncrementBusyCount();
|
||||
_runtimeTransactions.TryDispatchTargetedUse(
|
||||
sourceGuid,
|
||||
targetGuid,
|
||||
_sendUseWithTarget,
|
||||
incrementBusy: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -827,7 +805,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
else
|
||||
{
|
||||
_sendUse!(objectId);
|
||||
_transactions.IncrementBusyCount();
|
||||
_runtimeTransactions.IncrementBusyCount();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -926,8 +904,11 @@ public sealed class ItemInteractionController : IDisposable
|
|||
}
|
||||
break;
|
||||
case ItemPolicyActionKind.SendUseWithTarget:
|
||||
_sendUseWithTarget?.Invoke(action.ObjectId, action.TargetId);
|
||||
acted |= _sendUseWithTarget is not null;
|
||||
acted |= _runtimeTransactions.TryDispatchTargetedUse(
|
||||
action.ObjectId,
|
||||
action.TargetId,
|
||||
_sendUseWithTarget,
|
||||
incrementBusy: false);
|
||||
break;
|
||||
case ItemPolicyActionKind.SetGroundObject:
|
||||
_requestExternalContainer?.Invoke(action.ObjectId);
|
||||
|
|
@ -944,7 +925,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
_transactions.IncrementBusyCount();
|
||||
_runtimeTransactions.IncrementBusyCount();
|
||||
}
|
||||
break;
|
||||
case ItemPolicyActionKind.Reject:
|
||||
|
|
@ -965,7 +946,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
}
|
||||
|
||||
private ItemUseRequestReservation BeginUseRequestReservation()
|
||||
=> _transactions.BeginUseRequestReservation();
|
||||
=> _runtimeTransactions.BeginUseRequestReservation();
|
||||
|
||||
private void ExecutePlacementActions(System.Collections.Generic.IReadOnlyList<ItemPolicyAction> actions)
|
||||
{
|
||||
|
|
@ -1160,8 +1141,8 @@ public sealed class ItemInteractionController : IDisposable
|
|||
}
|
||||
|
||||
/// <summary>Retail UseDone (0x01C7) releases one UI busy reference.</summary>
|
||||
public void CompleteUse(uint _)
|
||||
=> _transactions.CompleteUse(0u);
|
||||
public void CompleteUse(uint error)
|
||||
=> _runtimeTransactions.CompleteUse(error);
|
||||
|
||||
/// <summary>Session teardown drops every outstanding client UI busy reference.</summary>
|
||||
public void ClearBusy()
|
||||
|
|
@ -1176,22 +1157,18 @@ public sealed class ItemInteractionController : IDisposable
|
|||
{
|
||||
PendingBackpackPlacement? pendingPlacement = _pendingBackpackPlacement;
|
||||
_pendingBackpackPlacement = null;
|
||||
// The canonical owner publishes its reset to Runtime observers, while
|
||||
// this controller preserves the pre-J4 single UI notification emitted
|
||||
// by InteractionState.ResetSession below.
|
||||
// Runtime owns the transaction reset. This controller preserves the
|
||||
// pre-J4 single UI notification emitted by InteractionState below.
|
||||
_transactions.StateChanged -= OnTransactionStateChanged;
|
||||
try
|
||||
{
|
||||
_transactions.ResetSession();
|
||||
_runtimeTransactions.ResetSession();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!_disposed)
|
||||
_transactions.StateChanged += OnTransactionStateChanged;
|
||||
}
|
||||
_awaitingAppraisalId = 0;
|
||||
_currentAppraisalId = 0;
|
||||
_lastUseMs = long.MinValue / 2;
|
||||
_consumedPrimaryClickTarget = 0u;
|
||||
_consumedPrimaryClickMs = long.MinValue / 2;
|
||||
|
||||
|
|
@ -1238,13 +1215,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
}
|
||||
|
||||
private bool ConsumeUseThrottle()
|
||||
{
|
||||
long now = _nowMs();
|
||||
if (now - _lastUseMs < RetailUseThrottleMs)
|
||||
return false;
|
||||
_lastUseMs = now;
|
||||
return true;
|
||||
}
|
||||
=> _runtimeTransactions.TryConsumeUseThrottle(_nowMs());
|
||||
|
||||
private static bool IsContainer(ClientObject item)
|
||||
=> item.ContainerTypeHint != 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue