refactor(runtime): own communication and social state

Construct chat history, negotiated channels, friends, squelch, and reply targets in one presentation-independent Runtime owner. Make live routing, retained UI, devtools, and current-runtime projections borrow the exact instances, preserve reconnect reset semantics, and publish failure-isolated reentrant-safe chat commits.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 07:50:16 +02:00
parent e321410770
commit c9d25ade50
19 changed files with 684 additions and 117 deletions

View file

@ -18,6 +18,7 @@ using AcDream.Core.Items;
using AcDream.Core.Player;
using AcDream.Core.Selection;
using AcDream.Core.Spells;
using AcDream.Runtime.Gameplay;
using AcDream.UI.Abstractions.Input;
using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Vitals;
@ -49,7 +50,7 @@ internal sealed record InteractionRetainedUiDependencies(
ClientObjectTable Objects,
MagicCatalog MagicCatalog,
Spellbook Spellbook,
ChatLog Chat,
RuntimeCommunicationState Communication,
LocalPlayerState LocalPlayer,
ItemManaState ItemMana,
StackSplitQuantityState StackSplitQuantity,
@ -317,7 +318,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
session.CurrentSession?.SendStackableSplitTo3D(item, amount),
selectedObjectId: () => d.Selection.SelectedObjectId ?? 0u,
stackSplitQuantity: d.StackSplitQuantity,
systemMessage: text => d.Chat.OnSystemMessage(text, 0x1Au),
systemMessage:
text => d.Communication.Chat.OnSystemMessage(text, 0x1Au),
sendPutItemInContainer: (item, container, placement) =>
session.CurrentSession?.SendPutItemInContainer(
item,
@ -400,7 +402,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
late.Session.CurrentSession?.SendCastUntargetedSpell(spellId),
sendTargeted: (target, spellId) =>
late.Session.CurrentSession?.SendCastTargetedSpell(target, spellId),
displayMessage: text => d.Chat.OnSystemMessage(text, 0x1Au),
displayMessage:
text => d.Communication.Chat.OnSystemMessage(text, 0x1Au),
incrementBusy: itemInteraction.IncrementBusyCount,
canSend: () => late.Session.IsInWorld);
checkpoint(InteractionRetainedUiCompositionPoint.MagicRuntimeCreated);
@ -477,7 +480,10 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
host.Root.Width = d.Window.Size.X;
host.Root.Height = d.Window.Size.Y;
var chat = new ChatVM(d.Chat, displayLimit: 200);
var chat = new ChatVM(
d.Communication.Chat,
displayLimit: 200,
commandTargets: d.Communication.CommandTargets);
AcDream.UI.Abstractions.Panels.Settings.SettingsStore? layoutStore =
d.Settings.LayoutStore;
RetailUiPersistenceBindings? persistence = layoutStore is null
@ -663,7 +669,8 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
late.Session.CurrentSession?.SendSetInscription(
item,
inscription),
text => d.Chat.OnSystemMessage(text, 0x1Au)),
text =>
d.Communication.Chat.OnSystemMessage(text, 0x1Au)),
StackSplitQuantity: d.StackSplitQuantity,
Plugins: d.UiRegistry,
Persistence: persistence,

View file

@ -27,6 +27,7 @@ using AcDream.Core.Social;
using AcDream.Core.Spells;
using AcDream.Core.World;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using Silk.NET.Windowing;
@ -84,13 +85,10 @@ internal sealed record SessionPlayerDependencies(
CombatAttackOperationsSlot CombatAttackOperations,
CombatState Combat,
CombatFeedbackSlot CombatFeedback,
ChatLog Chat,
RuntimeCommunicationState Communication,
LocalPlayerState LocalPlayer,
Spellbook Spellbook,
ItemManaState ItemMana,
FriendsState Friends,
SquelchState Squelch,
TurbineChatState TurbineChat,
ExternalContainerState ExternalContainers,
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
Action<string> Log);
@ -798,8 +796,6 @@ internal sealed class SessionPlayerCompositionPhase
AcDream.UI.Abstractions.Panels.Vitals.VitalsVM? vitals =
d.SettingsDevTools.DevTools?.Vitals
?? interaction.RetainedUi?.Vitals;
AcDream.UI.Abstractions.Panels.Chat.ChatVM? retailChat =
interaction.RetainedUi?.Chat;
var sessionRuntimeFactory = new LiveSessionRuntimeFactory(
new LiveSessionPlayerRuntime(
d.PlayerIdentity,
@ -815,15 +811,11 @@ internal sealed class SessionPlayerCompositionPhase
d.Spellbook,
d.Combat,
d.ItemMana,
d.Friends,
d.Squelch,
d.TurbineChat,
d.ExternalContainers,
d.Chat),
d.Communication),
new LiveSessionUiRuntime(
interaction.RetainedUi?.Runtime,
vitals,
retailChat,
interaction.RetainedUi?.CharacterSheet,
interaction.RetainedUi?.Magic,
live.PaperdollPresenter),
@ -881,7 +873,7 @@ internal sealed class SessionPlayerCompositionPhase
interaction.ItemInteraction),
d.Log,
debugToast,
text => d.Chat.OnSystemMessage(text, 0x1Au));
text => d.Communication.Chat.OnSystemMessage(text, 0x1Au));
bindings.Adopt(
"live combat-mode commands",
d.CombatModeCommands.BindOwned(combatCommand));
@ -892,7 +884,7 @@ internal sealed class SessionPlayerCompositionPhase
d.PlayerIdentity,
live.LiveEntities,
d.EntityObjects,
d.Chat,
d.Communication,
d.PlayerController,
worldReveal,
d.UpdateClock,

View file

@ -6,6 +6,7 @@ using AcDream.App.Settings;
using AcDream.Core.Chat;
using AcDream.Core.Combat;
using AcDream.Core.Player;
using AcDream.Runtime.Gameplay;
using AcDream.UI.Abstractions.Input;
using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Debug;
@ -34,7 +35,7 @@ internal sealed record SettingsDevToolsDependencies(
RuntimeSettingsController Settings,
IRuntimeSettingsStartupTarget StartupTarget,
HostQuiescenceGate HostQuiescence,
ChatLog Chat,
RuntimeCommunicationState Communication,
CombatState Combat,
LocalPlayerState LocalPlayer,
SettingsDevToolsOptionalDependencies? DevTools,
@ -418,7 +419,10 @@ internal sealed class SettingsDevToolsCompositionPhase :
var chatLease = scope.Acquire(
"developer chat view model",
() => new ChatVM(_dependencies.Chat)
() => new ChatVM(
_dependencies.Communication.Chat,
commandTargets:
_dependencies.Communication.CommandTargets)
{
FpsProvider = () => optional.Facts.Fps,
PositionProvider = () => optional.Facts.PlayerPosition,

View file

@ -24,6 +24,7 @@ using AcDream.Core.Spells;
using AcDream.Core.World;
using AcDream.Content;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Vitals;
@ -47,16 +48,12 @@ internal sealed record LiveSessionDomainRuntime(
Spellbook Spellbook,
CombatState Combat,
ItemManaState ItemMana,
FriendsState Friends,
SquelchState Squelch,
TurbineChatState TurbineChat,
ExternalContainerState ExternalContainers,
ChatLog Chat);
RuntimeCommunicationState Communication);
internal sealed record LiveSessionUiRuntime(
RetailUiRuntime? RetailUi,
VitalsVM? Vitals,
ChatVM? RetailChat,
CharacterSheetProvider? CharacterSheet,
MagicRuntime? Magic,
PaperdollFramePresenter? Paperdoll);
@ -140,7 +137,7 @@ internal sealed class LiveSessionRuntimeFactory
Selection: new(
SetPlayerIdentity: id => _player.Identity.ServerGuid = id,
SetVitalsIdentity: id => _ui.Vitals?.SetLocalPlayerGuid(id),
SetChatIdentity: _domain.Chat.SetLocalPlayerGuid,
SetChatIdentity: _domain.Communication.Chat.SetLocalPlayerGuid,
MarkPersistent: _world.WorldState.MarkPersistent,
SetVanishProbeIdentity: id => EntityVanishProbe.PlayerGuid = id,
ClearCombat: _domain.Combat.Clear),
@ -151,11 +148,11 @@ internal sealed class LiveSessionRuntimeFactory
LoadCharacterSettings: _interaction.Settings.LoadCharacterContext,
ArmPlayerModeAutoEntry: _interaction.PlayerModeAutoEntry.Arm),
Connecting: (host, port, user) =>
_domain.Chat.OnSystemMessage(
_domain.Communication.Chat.OnSystemMessage(
$"connecting to {host}:{port} as {user}",
chatType: 1),
Connected: () =>
_domain.Chat.OnSystemMessage(
_domain.Communication.Chat.OnSystemMessage(
"connected — character list received",
chatType: 1)),
connectOptions);
@ -167,7 +164,7 @@ internal sealed class LiveSessionRuntimeFactory
PlayerPresentation = ResetPlayerPresentation,
TeleportTransit = _world.Teleport.ResetSession,
SessionDialogs = () => _ui.RetailUi?.ResetSessionTransientUi(),
ChatCommandTargets = () => _ui.RetailChat?.ResetSessionTargets(),
ChatCommandTargets = _domain.Communication.ResetCommandTargets,
SettingsCharacterContext =
_interaction.Settings.RestoreDefaultCharacterContext,
EquippedChildren = _world.EquippedChildren.Clear,
@ -181,9 +178,9 @@ internal sealed class LiveSessionRuntimeFactory
CombatState = _domain.Combat.Clear,
ItemMana = _domain.ItemMana.Clear,
LocalPlayer = _domain.LocalPlayer.Clear,
Friends = _domain.Friends.Clear,
Squelch = _domain.Squelch.Clear,
TurbineChat = _domain.TurbineChat.Reset,
Friends = _domain.Communication.ResetFriends,
Squelch = _domain.Communication.ResetSquelch,
TurbineChat = _domain.Communication.ResetNegotiatedChannels,
ParticleVisibility = _world.ParticleVisibility.Reset,
InboundEventFifo = _world.InboundEvents.Clear,
LiveLiveness = _world.Liveness.Clear,
@ -218,7 +215,7 @@ internal sealed class LiveSessionRuntimeFactory
// default comes from PlayerModule::PlayerModule @ 0x005D51F0.
_player.Identity.ServerGuid = 0u;
_ui.Vitals?.SetLocalPlayerGuid(0u);
_domain.Chat.ResetSessionIdentity();
_domain.Communication.ResetChatIdentity();
EntityVanishProbe.PlayerGuid = 0u;
_interaction.Settings.ResetActiveCharacterKey();
_player.CharacterOptions.Reset();
@ -254,10 +251,10 @@ internal sealed class LiveSessionRuntimeFactory
CreateInventoryBindings(),
CreateCharacterBindings(skillTable),
new LiveSocialSessionBindings(
_domain.Chat,
_domain.TurbineChat,
_domain.Friends,
_domain.Squelch));
_domain.Communication.Chat,
_domain.Communication.TurbineChat,
_domain.Communication.Friends,
_domain.Communication.Squelch));
}
private LiveInventorySessionBindings CreateInventoryBindings() => new(
@ -331,8 +328,10 @@ internal sealed class LiveSessionRuntimeFactory
ToggleUiLock: () =>
_interaction.Settings.SetUiLocked(
!_interaction.Settings.Gameplay.LockUI),
ShowSystemMessage: text => _domain.Chat.OnSystemMessage(text, 0u),
ShowWeenieError: code => _domain.Chat.OnWeenieError(code, null),
ShowSystemMessage:
text => _domain.Communication.Chat.OnSystemMessage(text, 0u),
ShowWeenieError:
code => _domain.Communication.Chat.OnWeenieError(code, null),
PlayerPublicWeenieBitfield: () =>
_domain.EntityObjects.Objects.Get(_player.Identity.ServerGuid)?
.PublicWeenieBitfield,
@ -346,7 +345,7 @@ internal sealed class LiveSessionRuntimeFactory
ShowConfirmation: (message, completed) =>
_ui.RetailUi?.ShowConfirmation(message, completed),
Suicide: session.SendSuicide,
ClearChat: _ => _domain.Chat.Clear(),
ClearChat: _ => _domain.Communication.Chat.Clear(),
SaveUi: name => _ui.RetailUi?.SaveNamedLayout(name),
LoadUi: name => _ui.RetailUi?.RestoreNamedLayout(name),
SaveAutoUi: () => _ui.RetailUi?.SaveLayout(),
@ -363,16 +362,17 @@ internal sealed class LiveSessionRuntimeFactory
ClearConsent: session.SendClearConsent,
RemoveConsent: session.SendRemoveConsent,
SendEmote: session.SendEmote,
_domain.Friends,
_domain.Communication.Friends,
AddFriend: session.SendAddFriend,
RemoveFriend: session.SendRemoveFriend,
ClearFriends: session.SendClearFriends,
RequestLegacyFriends: session.SendLegacyFriendsListRequest,
_domain.Squelch,
_domain.Communication.Squelch,
ModifyCharacterSquelch: session.SendModifyCharacterSquelch,
ModifyAccountSquelch: session.SendModifyAccountSquelch,
ModifyGlobalSquelch: session.SendModifyGlobalSquelch,
LastTeller: () => _ui.RetailChat?.LastIncomingTellSender,
LastTeller: () =>
_domain.Communication.CommandTargets.LastIncomingTellSender,
ClearDesiredComponents: () =>
{
session.SendClearDesiredComponents();
@ -380,8 +380,8 @@ internal sealed class LiveSessionRuntimeFactory
},
HasOpenVendor: () => false,
FillComponentBuyList: (_, _) => { }),
_domain.Chat,
_domain.TurbineChat,
_domain.Communication.Chat,
_domain.Communication.TurbineChat,
PlayerGuid: () => _player.Identity.ServerGuid,
SendTalk: session.SendTalk,
SendTell: session.SendTell,

View file

@ -7,6 +7,7 @@ using AcDream.App.Settings;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using DatReaderWriter;
using Silk.NET.Input;
@ -323,17 +324,18 @@ public sealed class GameWindow :
private AcDream.App.World.LiveEntityRuntime? _liveEntities;
private AcDream.App.World.LiveEntityLivenessController? _liveEntityLiveness;
// Phase F.1-H.1 — client-side state classes fed by GameEventWiring.
// Exposed publicly so plugins + UI panels can bind directly.
public readonly AcDream.Core.Chat.ChatLog Chat = new();
// Phase I.6 — runtime state for retail's TurbineChat (0xF7DE) global
// chat rooms. Empty/disabled until the server fires
// SetTurbineChatChannels (0x0295) shortly after EnterWorld.
public readonly AcDream.Core.Chat.TurbineChatState TurbineChat = new();
// J4.1: Runtime owns communication/social state. App, UI, plugins, and
// live-session routing borrow these exact instances.
private readonly RuntimeCommunicationState _runtimeCommunication = new();
public AcDream.Core.Chat.ChatLog Chat => _runtimeCommunication.Chat;
public AcDream.Core.Chat.TurbineChatState TurbineChat =>
_runtimeCommunication.TurbineChat;
public AcDream.Core.Social.FriendsState Friends =>
_runtimeCommunication.Friends;
public AcDream.Core.Social.SquelchState Squelch =>
_runtimeCommunication.Squelch;
public readonly AcDream.Core.Combat.CombatState Combat = new();
public readonly AcDream.Core.Items.ItemManaState ItemMana = new();
public readonly AcDream.Core.Social.FriendsState Friends = new();
public readonly AcDream.Core.Social.SquelchState Squelch = new();
private readonly DesiredComponentSnapshotState _desiredComponents = new();
public IReadOnlyList<(uint Id, uint Amount)> DesiredComponents =>
_desiredComponents.Items;
@ -1208,7 +1210,7 @@ public sealed class GameWindow :
hostInputCamera.CameraController,
contentEffectsAudio.Audio?.Engine),
_hostQuiescence,
Chat,
_runtimeCommunication,
Combat,
LocalPlayer,
optionalDevTools,
@ -1264,7 +1266,7 @@ public sealed class GameWindow :
_runtimeEntityObjects.Objects,
contentEffectsAudio.MagicCatalog,
SpellBook,
Chat,
_runtimeCommunication,
LocalPlayer,
ItemMana,
_stackSplitQuantity,
@ -1409,13 +1411,10 @@ public sealed class GameWindow :
_combatAttackOperations,
Combat,
_combatFeedback,
Chat,
_runtimeCommunication,
LocalPlayer,
SpellBook,
ItemMana,
Friends,
Squelch,
TurbineChat,
_externalContainers,
_portalTunnelFallback,
Console.WriteLine),
@ -1591,6 +1590,7 @@ public sealed class GameWindow :
_equippedChildRenderer,
_liveEntities,
_runtimeEntityObjects,
_runtimeCommunication,
_renderSceneShadow,
_livePresentationBindings,
_entityEffectAdvance,

View file

@ -17,6 +17,7 @@ using AcDream.Content;
using AcDream.Core.Audio;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions.Input;
using DatReaderWriter;
@ -83,6 +84,7 @@ internal sealed record LiveShutdownRoots(
EquippedChildRenderController? EquippedChildren,
LiveEntityRuntime? LiveEntities,
RuntimeEntityObjectLifetime EntityObjects,
RuntimeCommunicationState Communication,
RenderSceneShadowRuntime? RenderSceneShadow,
LivePresentationRuntimeBindings? PresentationBindings,
DeferredEntityEffectAdvanceSource EffectAdvance,
@ -439,6 +441,10 @@ internal static class GameWindowShutdownManifest
Hard("sky", () => render.Sky?.Dispose()),
Hard("particles", () => render.Particles?.Dispose()),
]),
new ResourceShutdownStage("runtime communication state",
[
Hard("communication owner", live.Communication.Dispose),
]),
new ResourceShutdownStage("shared texture owners",
[
Hard("sampler cache", () => render.Samplers?.Dispose()),

View file

@ -8,6 +8,7 @@ using AcDream.Core.Chat;
using AcDream.Core.Selection;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using AcDream.UI.Abstractions;
@ -36,7 +37,7 @@ internal sealed class CurrentGameRuntimeAdapter
LocalPlayerIdentityState playerIdentity,
LiveEntityRuntime entities,
RuntimeEntityObjectLifetime entityObjects,
ChatLog chat,
RuntimeCommunicationState communication,
ILocalPlayerControllerSource playerController,
WorldRevealCoordinator worldReveal,
IGameRuntimeClock clock,
@ -50,7 +51,7 @@ internal sealed class CurrentGameRuntimeAdapter
playerIdentity,
entities,
entityObjects,
chat,
communication,
playerController,
worldReveal,
clock);
@ -60,7 +61,7 @@ internal sealed class CurrentGameRuntimeAdapter
_events = new CurrentGameRuntimeEventAdapter(
_view,
entityObjects,
chat);
communication);
_commands = new CurrentGameRuntimeCommandAdapter(
session,
sessionHost,

View file

@ -1,6 +1,6 @@
using AcDream.Core.Chat;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
namespace AcDream.App.Runtime;
@ -25,14 +25,16 @@ internal sealed class CurrentGameRuntimeEventAdapter
: IRuntimeEventSource,
ICurrentGameRuntimeEventSink,
IRuntimeEntityObjectObserver,
IRuntimeCommunicationObserver,
IDisposable
{
private readonly CurrentGameRuntimeViewAdapter _view;
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly ChatLog _chat;
private readonly RuntimeCommunicationState _communication;
private readonly object _observerGate = new();
private IRuntimeEventObserver[] _observers = [];
private IDisposable? _entityObjectSubscription;
private IDisposable? _communicationSubscription;
private bool _ownerEventsAttached;
private bool _disposed;
@ -42,12 +44,13 @@ internal sealed class CurrentGameRuntimeEventAdapter
public CurrentGameRuntimeEventAdapter(
CurrentGameRuntimeViewAdapter view,
RuntimeEntityObjectLifetime entityObjects,
ChatLog chat)
RuntimeCommunicationState communication)
{
_view = view ?? throw new ArgumentNullException(nameof(view));
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
_communication = communication
?? throw new ArgumentNullException(nameof(communication));
}
public IDisposable Subscribe(IRuntimeEventObserver observer)
@ -175,7 +178,7 @@ internal sealed class CurrentGameRuntimeEventAdapter
private void AttachOwnerEvents()
{
_entityObjectSubscription = _entityObjects.Events.Subscribe(this);
_chat.EntryAppended += OnChatEntry;
_communicationSubscription = _communication.Events.Subscribe(this);
_ownerEventsAttached = true;
}
@ -183,7 +186,8 @@ internal sealed class CurrentGameRuntimeEventAdapter
{
if (!_ownerEventsAttached)
return;
_chat.EntryAppended -= OnChatEntry;
_communicationSubscription?.Dispose();
_communicationSubscription = null;
_entityObjectSubscription?.Dispose();
_entityObjectSubscription = null;
_ownerEventsAttached = false;
@ -223,19 +227,13 @@ internal sealed class CurrentGameRuntimeEventAdapter
}
}
private void OnChatEntry(ChatEntry entry)
public void OnChat(in RuntimeCommunicationEvent committed)
{
if (!TryObservers(out IRuntimeEventObserver[] observers))
return;
var delta = new RuntimeChatDelta(
NextStamp(),
new RuntimeChatEntry(
_chat.Revision,
entry.SenderGuid,
(int)entry.Kind,
entry.Sender,
entry.Text,
entry.ChannelName));
committed.Entry);
foreach (IRuntimeEventObserver observer in observers)
{
try

View file

@ -6,6 +6,7 @@ using AcDream.Core.Chat;
using AcDream.Core.Items;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
namespace AcDream.App.Runtime;
@ -20,11 +21,10 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
private readonly LocalPlayerIdentityState _playerIdentity;
private readonly LiveEntityRuntime _entities;
private readonly ClientObjectTable _objects;
private readonly ChatLog _chat;
private readonly IGameRuntimeClock _clock;
private readonly IRuntimeEntityView _entityView;
private readonly IRuntimeInventoryView _inventoryView;
private readonly ChatView _chatView;
private readonly IRuntimeChatView _chatView;
private readonly MovementView _movementView;
private readonly PortalView _portalView;
private bool _active = true;
@ -34,7 +34,7 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
LocalPlayerIdentityState playerIdentity,
LiveEntityRuntime entities,
RuntimeEntityObjectLifetime entityObjects,
ChatLog chat,
RuntimeCommunicationState communication,
ILocalPlayerControllerSource playerController,
WorldRevealCoordinator worldReveal,
IGameRuntimeClock clock)
@ -45,12 +45,12 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
ArgumentNullException.ThrowIfNull(entityObjects);
_objects = entityObjects.Objects;
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
ArgumentNullException.ThrowIfNull(communication);
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_entityView = entityObjects.EntityView;
_inventoryView = entityObjects.InventoryView;
_chatView = new ChatView(_chat);
_chatView = communication.View;
_movementView = new MovementView(
playerController
?? throw new ArgumentNullException(nameof(playerController)),
@ -103,19 +103,13 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
_entities.MaterializedCount,
_objects.ObjectCount,
_objects.ContainerCount,
_chat.Revision,
_chat.Count,
_chatView.Revision,
_chatView.Count,
_movementView.Snapshot,
_portalView.Snapshot);
internal void Deactivate() => _active = false;
private sealed class ChatView(ChatLog owner) : IRuntimeChatView
{
public long Revision => owner.Revision;
public int Count => owner.Count;
}
private sealed class MovementView(
ILocalPlayerControllerSource owner,
IGameRuntimeClock clock)