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:
Erik 2026-07-26 08:20:32 +02:00
parent 595d5e6b01
commit 011efbeaa7
18 changed files with 1337 additions and 406 deletions

View file

@ -46,13 +46,11 @@ internal sealed record InteractionRetainedUiDependencies(
CombatState Combat,
ICombatAttackOperations CombatAttackOperations,
SelectionState Selection,
ExternalContainerState ExternalContainers,
ClientObjectTable Objects,
RuntimeInventoryState Inventory,
MagicCatalog MagicCatalog,
Spellbook Spellbook,
RuntimeCommunicationState Communication,
LocalPlayerState LocalPlayer,
ItemManaState ItemMana,
StackSplitQuantityState StackSplitQuantity,
BufferedUiRegistry? UiRegistry,
LiveCombatModeCommandSlot CombatModeCommands,
@ -60,7 +58,6 @@ internal sealed record InteractionRetainedUiDependencies(
ILocalPlayerControllerSource PlayerController,
ILocalPlayerModeSource PlayerMode,
PlayerCharacterOptionsState CharacterOptions,
ShortcutSnapshotState Shortcuts,
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
SelectionCameraFactory,
DeferredRenderFrameDiagnosticsSource FrameDiagnostics,
@ -275,8 +272,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
InteractionRetainedUiDependencies d,
DeferredLiveSessionUiAuthority session) =>
new(
d.ExternalContainers,
d.Objects,
d.Inventory.ExternalContainers,
d.Inventory.Objects,
guid => session.CurrentSession?.SendNoLongerViewingContents(guid));
public ItemInteractionController CreateItemInteraction(
@ -286,7 +283,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
DeferredLiveSessionUiAuthority session = late.Session;
DeferredSelectionUiAuthority selection = late.Selection;
return new ItemInteractionController(
d.Objects,
d.Inventory.Objects,
playerGuid: () => d.PlayerIdentity.ServerGuid,
sendUse: null,
sendExamine: guid => session.CurrentSession?.SendAppraise(guid),
@ -313,7 +310,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
placeInBackpack: selection.SendPickup,
backpackContainerId: () => late.InventoryContainer.Current(
d.PlayerIdentity.ServerGuid),
groundObjectId: () => d.ExternalContainers.CurrentContainerId,
groundObjectId: () =>
d.Inventory.ExternalContainers.CurrentContainerId,
sendSplitToWorld: (item, amount) =>
session.CurrentSession?.SendStackableSplitTo3D(item, amount),
selectedObjectId: () => d.Selection.SelectedObjectId ?? 0u,
@ -333,9 +331,10 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
amount),
requestExternalContainer: guid =>
{
d.ExternalContainers.RequestOpen(guid);
d.Inventory.ExternalContainers.RequestOpen(guid);
},
requestUse: selection.RequestUse);
requestUse: selection.RequestUse,
transactions: d.Inventory.Transactions);
}
public RetainedUiComposition CreateRetainedUi(
@ -372,7 +371,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
checkpoint(InteractionRetainedUiCompositionPoint.CursorAssetsCreated);
var characterSheet = new CharacterSheetProvider(
d.Objects,
d.Inventory.Objects,
d.LocalPlayer,
playerGuid: () => d.PlayerIdentity.ServerGuid,
activeToonName: () => d.Settings.ActiveToonKey,
@ -391,7 +390,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
MagicRuntime magic = MagicRuntime.Create(
d.MagicCatalog,
d.Spellbook,
d.Objects,
d.Inventory.Objects,
selectedObject: () => d.Selection.SelectedObjectId,
localPlayerId: () => d.PlayerIdentity.ServerGuid,
accountName: () => late.Session.AccountName
@ -530,7 +529,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
Magic: new MagicRuntimeBindings(
d.Spellbook,
magic.Casting,
d.Objects,
d.Inventory.Objects,
() => d.PlayerIdentity.ServerGuid,
d.MagicCatalog.Components,
iconComposer.GetIcon,
@ -571,7 +570,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
late.SelectionCamera.UiSnapshot),
Indicators: new IndicatorRuntimeBindings(
d.Spellbook,
d.Objects,
d.Inventory.Objects,
() => d.PlayerIdentity.ServerGuid,
() => d.LocalPlayer.GetAttribute(
LocalPlayerState.AttributeKind.Strength)
@ -581,13 +580,13 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
() => late.Session.CurrentSession?.RequestLinkStatusPing(),
d.Window.Close),
Toolbar: new ToolbarRuntimeBindings(
d.Objects,
() => d.Shortcuts.Items,
d.Inventory.Objects,
() => d.Inventory.Shortcuts.Items,
iconComposer.GetIcon,
iconComposer.GetDragIcon,
guid => late.Session.TryUseItem(guid, d.Log),
d.Combat,
d.ItemMana,
d.Inventory.ItemMana,
d.CombatModeCommands.Toggle,
itemInteraction,
entry => late.Session.CurrentSession?.SendAddShortcut(entry),
@ -596,10 +595,11 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
handler => d.Combat.HealthChanged += handler,
handler => d.Combat.HealthChanged -= handler,
late.Selection.ShouldShowHealth,
guid => d.Objects.Get(guid)?.GetAppropriateName(),
guid => d.Inventory.Objects.Get(guid)?.GetAppropriateName(),
d.Combat.GetHealthPercent,
d.Combat.HasHealth,
guid => (uint)(d.Objects.Get(guid)?.StackSize ?? 0),
guid =>
(uint)(d.Inventory.Objects.Get(guid)?.StackSize ?? 0),
guid => late.Session.CurrentSession?.SendQueryHealth(guid),
guid => late.Session.CurrentSession?.SendQueryItemMana(guid),
() => d.PlayerIdentity.ServerGuid,
@ -610,7 +610,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
placement)),
Character: new CharacterRuntimeBindings(characterSheet),
Inventory: new InventoryRuntimeBindings(
d.Objects,
d.Inventory.Objects,
() => d.PlayerIdentity.ServerGuid,
iconComposer.GetIcon,
iconComposer.GetDragIcon,
@ -637,8 +637,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
itemInteraction,
d.Selection),
ExternalContainer: new ExternalContainerRuntimeBindings(
d.ExternalContainers,
d.Objects,
d.Inventory.ExternalContainers,
d.Inventory.Objects,
iconComposer.GetIcon,
iconComposer.GetDragIcon,
itemInteraction,