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

@ -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)