feat(runtime): define borrowed views commands and ordered events
Establish the J1 presentation-independent contract with instance-scoped clocks and generations, immutable borrowed views, typed generation-gated commands, normalized ordered diagnostics, and teardown acknowledgements. Route graphical startup plus press-time selection, movement, and combat through focused App adapters over the exact existing owners without adding a queue or mirrored world. Validated by the Release solution build, 13 Runtime tests, 3,838 App tests with three existing skips, and the complete 8,424-test Release suite with five existing skips. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
afebbe3eca
commit
854d9e9cd1
21 changed files with 2594 additions and 44 deletions
236
src/AcDream.Runtime/GameRuntimeEvents.cs
Normal file
236
src/AcDream.Runtime/GameRuntimeEvents.cs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
namespace AcDream.Runtime;
|
||||
|
||||
public readonly record struct RuntimeEventStamp(
|
||||
RuntimeGenerationToken Generation,
|
||||
ulong Sequence,
|
||||
ulong FrameNumber);
|
||||
|
||||
public enum RuntimeCommandDomain
|
||||
{
|
||||
Session,
|
||||
Selection,
|
||||
Combat,
|
||||
Movement,
|
||||
Chat,
|
||||
Portal,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeLifecycleDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeLifecycleState Previous,
|
||||
RuntimeLifecycleState Current);
|
||||
|
||||
public readonly record struct RuntimeCommandDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeCommandDomain Domain,
|
||||
int Operation,
|
||||
RuntimeCommandStatus Status,
|
||||
uint PrimaryObjectId = 0u,
|
||||
string? Text = null);
|
||||
|
||||
public enum RuntimeEntityChange
|
||||
{
|
||||
Registered,
|
||||
Updated,
|
||||
Rebucketed,
|
||||
Hidden,
|
||||
Withdrawn,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeEntityDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeEntityChange Change,
|
||||
RuntimeEntitySnapshot Entity);
|
||||
|
||||
public enum RuntimeInventoryChange
|
||||
{
|
||||
Added,
|
||||
Updated,
|
||||
Moved,
|
||||
Removed,
|
||||
Cleared,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeInventoryDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeInventoryChange Change,
|
||||
RuntimeInventoryItemSnapshot Item);
|
||||
|
||||
public readonly record struct RuntimeChatEntry(
|
||||
long Revision,
|
||||
uint SenderGuid,
|
||||
int Kind,
|
||||
string Sender,
|
||||
string Text,
|
||||
string ChannelName);
|
||||
|
||||
public readonly record struct RuntimeChatDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeChatEntry Entry);
|
||||
|
||||
public readonly record struct RuntimeMovementDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeMovementSnapshot Movement);
|
||||
|
||||
public readonly record struct RuntimePortalDelta(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimePortalSnapshot Portal);
|
||||
|
||||
public interface IRuntimeEventObserver
|
||||
{
|
||||
void OnLifecycle(in RuntimeLifecycleDelta delta);
|
||||
|
||||
void OnCommand(in RuntimeCommandDelta delta);
|
||||
|
||||
void OnEntity(in RuntimeEntityDelta delta);
|
||||
|
||||
void OnInventory(in RuntimeInventoryDelta delta);
|
||||
|
||||
void OnChat(in RuntimeChatDelta delta);
|
||||
|
||||
void OnMovement(in RuntimeMovementDelta delta);
|
||||
|
||||
void OnPortal(in RuntimePortalDelta delta);
|
||||
}
|
||||
|
||||
public interface IRuntimeEventSource
|
||||
{
|
||||
IDisposable Subscribe(IRuntimeEventObserver observer);
|
||||
}
|
||||
|
||||
public enum RuntimeTraceKind
|
||||
{
|
||||
Lifecycle,
|
||||
Command,
|
||||
Entity,
|
||||
Inventory,
|
||||
Chat,
|
||||
Movement,
|
||||
Portal,
|
||||
Checkpoint,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stable normalized trace row used to compare a direct host with an adapter
|
||||
/// host without retaining either owner's object graph.
|
||||
/// </summary>
|
||||
public readonly record struct RuntimeTraceEntry(
|
||||
RuntimeEventStamp Stamp,
|
||||
RuntimeTraceKind Kind,
|
||||
int Code,
|
||||
long Value,
|
||||
uint PrimaryObjectId,
|
||||
uint SecondaryObjectId,
|
||||
string Text);
|
||||
|
||||
public sealed class RuntimeTraceRecorder : IRuntimeEventObserver
|
||||
{
|
||||
private readonly List<RuntimeTraceEntry> _entries = [];
|
||||
|
||||
public IReadOnlyList<RuntimeTraceEntry> Entries => _entries;
|
||||
|
||||
public void AddCheckpoint(
|
||||
RuntimeEventStamp stamp,
|
||||
in RuntimeStateCheckpoint checkpoint)
|
||||
{
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
stamp,
|
||||
RuntimeTraceKind.Checkpoint,
|
||||
(int)checkpoint.Lifecycle,
|
||||
checkpoint.ChatRevision,
|
||||
(uint)checkpoint.EntityCount,
|
||||
(uint)checkpoint.InventoryObjectCount,
|
||||
string.Create(
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
$"frame={checkpoint.FrameNumber};materialized={checkpoint.MaterializedEntityCount};" +
|
||||
$"containers={checkpoint.InventoryContainerCount};chat={checkpoint.ChatCount};" +
|
||||
$"portal={checkpoint.Portal.Generation}:{(int)checkpoint.Portal.Kind}:" +
|
||||
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady}")));
|
||||
}
|
||||
|
||||
public void OnLifecycle(in RuntimeLifecycleDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Lifecycle,
|
||||
(int)delta.Current,
|
||||
(int)delta.Previous,
|
||||
0u,
|
||||
0u,
|
||||
string.Empty));
|
||||
|
||||
public void OnCommand(in RuntimeCommandDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Command,
|
||||
((int)delta.Domain << 16) | (delta.Operation & 0xFFFF),
|
||||
(int)delta.Status,
|
||||
delta.PrimaryObjectId,
|
||||
0u,
|
||||
delta.Text ?? string.Empty));
|
||||
|
||||
public void OnEntity(in RuntimeEntityDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Entity,
|
||||
(int)delta.Change,
|
||||
delta.Entity.Identity.Incarnation,
|
||||
delta.Entity.Identity.ServerGuid,
|
||||
delta.Entity.CellId,
|
||||
string.Empty));
|
||||
|
||||
public void OnInventory(in RuntimeInventoryDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Inventory,
|
||||
(int)delta.Change,
|
||||
delta.Item.StackSize,
|
||||
delta.Item.ObjectId,
|
||||
delta.Item.ContainerId,
|
||||
delta.Item.Name ?? string.Empty));
|
||||
|
||||
public void OnChat(in RuntimeChatDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Chat,
|
||||
delta.Entry.Kind,
|
||||
delta.Entry.Revision,
|
||||
delta.Entry.SenderGuid,
|
||||
0u,
|
||||
$"{delta.Entry.ChannelName}|{delta.Entry.Sender}|{delta.Entry.Text}"));
|
||||
|
||||
public void OnMovement(in RuntimeMovementDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Movement,
|
||||
delta.Movement.IsAirborne ? 1 : 0,
|
||||
BitConverter.DoubleToInt64Bits(delta.Movement.SimulationTimeSeconds),
|
||||
delta.Movement.LocalEntityId,
|
||||
delta.Movement.Position.ObjCellId,
|
||||
string.Empty));
|
||||
|
||||
public void OnPortal(in RuntimePortalDelta delta) =>
|
||||
_entries.Add(new RuntimeTraceEntry(
|
||||
delta.Stamp,
|
||||
RuntimeTraceKind.Portal,
|
||||
(int)delta.Portal.Kind,
|
||||
delta.Portal.Generation,
|
||||
delta.Portal.DestinationCell,
|
||||
0u,
|
||||
$"{delta.Portal.IsReady}:{delta.Portal.IsMaterialized}:" +
|
||||
$"{delta.Portal.IsCompleted}:{delta.Portal.IsCancelled}:" +
|
||||
$"{delta.Portal.IsWorldVisible}"));
|
||||
}
|
||||
|
||||
/// <summary>Monotonic sequence owner for one runtime instance.</summary>
|
||||
public sealed class RuntimeEventSequencer
|
||||
{
|
||||
private ulong _sequence;
|
||||
|
||||
public RuntimeEventStamp Next(
|
||||
RuntimeGenerationToken generation,
|
||||
ulong frameNumber) =>
|
||||
new(generation, checked(++_sequence), frameNumber);
|
||||
|
||||
public ulong LastSequence => _sequence;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue