refactor(runtime): own canonical entity and object lifetime

Introduce one presentation-free RuntimeEntityObjectLifetime for the exact entity directory and ClientObjectTable. Make GameWindow, graphical projections, retained UI, interaction, session routing, create/delete integration, and reset borrow that owner while preserving synchronous retail ordering, dormant retention, and retry semantics.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 05:54:46 +02:00
parent d9bf4c4960
commit 5ef8b5371d
40 changed files with 712 additions and 170 deletions

View file

@ -25,6 +25,7 @@ using AcDream.Core.Rendering;
using AcDream.Core.Selection; using AcDream.Core.Selection;
using AcDream.Core.Vfx; using AcDream.Core.Vfx;
using AcDream.Core.World; using AcDream.Core.World;
using AcDream.Runtime.Entities;
using DatReaderWriter; using DatReaderWriter;
using DatReaderWriter.DBObjs; using DatReaderWriter.DBObjs;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
@ -44,7 +45,7 @@ internal sealed record LivePresentationDependencies(
WorldGameState WorldGameState, WorldGameState WorldGameState,
WorldEvents WorldEvents, WorldEvents WorldEvents,
SelectionState Selection, SelectionState Selection,
ClientObjectTable Objects, RuntimeEntityObjectLifetime EntityObjects,
LiveEntityRuntimeSlot RuntimeSlot, LiveEntityRuntimeSlot RuntimeSlot,
DeferredLiveEntityMotionRuntimeBindings MotionBindings, DeferredLiveEntityMotionRuntimeBindings MotionBindings,
DeferredEntityEffectAdvanceSource EffectAdvance, DeferredEntityEffectAdvanceSource EffectAdvance,
@ -377,7 +378,8 @@ internal sealed class LivePresentationCompositionPhase
worldState, worldState,
new CompositeLiveEntityResourceLifecycle( new CompositeLiveEntityResourceLifecycle(
[.. resourceOwners]), [.. resourceOwners]),
componentLifecycle); componentLifecycle,
d.EntityObjects);
var liveRuntimeLease = scope.Own( var liveRuntimeLease = scope.Own(
"canonical live-entity runtime", "canonical live-entity runtime",
liveEntities, liveEntities,
@ -487,7 +489,7 @@ internal sealed class LivePresentationCompositionPhase
() => new EquippedChildRenderController( () => new EquippedChildRenderController(
content.Dats, content.Dats,
d.DatLock, d.DatLock,
d.Objects, d.EntityObjects.Objects,
liveEntities, liveEntities,
d.EffectPoses, d.EffectPoses,
parentAcceptance.TryAccept, parentAcceptance.TryAccept,
@ -675,7 +677,7 @@ internal sealed class LivePresentationCompositionPhase
static value => value.Dispose()); static value => value.Dispose());
var selectionQuery = new WorldSelectionQuery( var selectionQuery = new WorldSelectionQuery(
liveEntities, liveEntities,
d.Objects, d.EntityObjects.Objects,
selectionScene, selectionScene,
() => d.PlayerIdentity.ServerGuid, () => d.PlayerIdentity.ServerGuid,
interaction.LateBindings.SelectionCamera.Snapshot, interaction.LateBindings.SelectionCamera.Snapshot,
@ -699,7 +701,7 @@ internal sealed class LivePresentationCompositionPhase
} }
}); });
var radarSnapshotProvider = new RadarSnapshotProvider( var radarSnapshotProvider = new RadarSnapshotProvider(
d.Objects, d.EntityObjects.Objects,
liveEntities, liveEntities,
() => liveEntities.Snapshots, () => liveEntities.Snapshots,
playerGuid: () => d.PlayerIdentity.ServerGuid, playerGuid: () => d.PlayerIdentity.ServerGuid,

View file

@ -26,6 +26,7 @@ using AcDream.Core.Player;
using AcDream.Core.Social; using AcDream.Core.Social;
using AcDream.Core.Spells; using AcDream.Core.Spells;
using AcDream.Core.World; using AcDream.Core.World;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Session; using AcDream.Runtime.Session;
using Silk.NET.Windowing; using Silk.NET.Windowing;
@ -47,7 +48,7 @@ internal sealed record SessionPlayerDependencies(
WorldGameState WorldGameState, WorldGameState WorldGameState,
WorldEvents WorldEvents, WorldEvents WorldEvents,
SelectionState Selection, SelectionState Selection,
ClientObjectTable Objects, RuntimeEntityObjectLifetime EntityObjects,
EntityClassificationCache ClassificationCache, EntityClassificationCache ClassificationCache,
LiveEntityRuntimeSlot RuntimeSlot, LiveEntityRuntimeSlot RuntimeSlot,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities, LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
@ -475,14 +476,14 @@ internal sealed class SessionPlayerCompositionPhase
var dormantLiveEntities = new DormantLiveEntityStore(); var dormantLiveEntities = new DormantLiveEntityStore();
var deletion = new LiveEntityDeletionController( var deletion = new LiveEntityDeletionController(
live.LiveEntities, live.LiveEntities,
d.Objects, d.EntityObjects,
teardown, teardown,
d.PlayerIdentity, d.PlayerIdentity,
dormantLiveEntities, dormantLiveEntities,
d.Options.DumpLiveSpawns ? d.Log : null); d.Options.DumpLiveSpawns ? d.Log : null);
var hydration = new LiveEntityHydrationController( var hydration = new LiveEntityHydrationController(
live.LiveEntities, live.LiveEntities,
d.Objects, d.EntityObjects,
d.DatLock, d.DatLock,
projectionMaterializer, projectionMaterializer,
new LiveEntityRelationshipProjection(live.EquippedChildren), new LiveEntityRelationshipProjection(live.EquippedChildren),
@ -505,7 +506,7 @@ internal sealed class SessionPlayerCompositionPhase
var networkUpdates = new LiveEntityNetworkUpdateController( var networkUpdates = new LiveEntityNetworkUpdateController(
live.LiveEntities, live.LiveEntities,
d.Objects, d.EntityObjects.Objects,
hydration, hydration,
live.EntityEffects, live.EntityEffects,
live.Presentation, live.Presentation,
@ -574,7 +575,7 @@ internal sealed class SessionPlayerCompositionPhase
new CombatAttackTargetSource( new CombatAttackTargetSource(
d.Selection, d.Selection,
live.LiveEntities, live.LiveEntities,
d.Objects, d.EntityObjects.Objects,
d.PlayerIdentity), d.PlayerIdentity),
d.Settings, d.Settings,
d.PlayerController, d.PlayerController,
@ -809,7 +810,7 @@ internal sealed class SessionPlayerCompositionPhase
d.Shortcuts, d.Shortcuts,
d.DesiredComponents), d.DesiredComponents),
new LiveSessionDomainRuntime( new LiveSessionDomainRuntime(
d.Objects, d.EntityObjects,
d.LocalPlayer, d.LocalPlayer,
d.Spellbook, d.Spellbook,
d.Combat, d.Combat,
@ -873,7 +874,7 @@ internal sealed class SessionPlayerCompositionPhase
var combatCommand = new LiveCombatModeCommandController( var combatCommand = new LiveCombatModeCommandController(
new LiveSessionCombatModeAuthority(sessionHost), new LiveSessionCombatModeAuthority(sessionHost),
new LocalPlayerCombatEquipmentSource( new LocalPlayerCombatEquipmentSource(
d.Objects, d.EntityObjects.Objects,
d.PlayerIdentity), d.PlayerIdentity),
d.Combat, d.Combat,
new ItemInteractionCombatModeIntentSink( new ItemInteractionCombatModeIntentSink(
@ -890,7 +891,7 @@ internal sealed class SessionPlayerCompositionPhase
liveSessionCommands, liveSessionCommands,
d.PlayerIdentity, d.PlayerIdentity,
live.LiveEntities, live.LiveEntities,
d.Objects, d.EntityObjects.Objects,
d.Chat, d.Chat,
d.PlayerController, d.PlayerController,
worldReveal, worldReveal,

View file

@ -23,6 +23,7 @@ using AcDream.Core.Social;
using AcDream.Core.Spells; using AcDream.Core.Spells;
using AcDream.Core.World; using AcDream.Core.World;
using AcDream.Content; using AcDream.Content;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Session; using AcDream.Runtime.Session;
using AcDream.UI.Abstractions.Panels.Chat; using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Vitals; using AcDream.UI.Abstractions.Panels.Vitals;
@ -41,7 +42,7 @@ internal sealed record LiveSessionPlayerRuntime(
DesiredComponentSnapshotState DesiredComponents); DesiredComponentSnapshotState DesiredComponents);
internal sealed record LiveSessionDomainRuntime( internal sealed record LiveSessionDomainRuntime(
ClientObjectTable Objects, RuntimeEntityObjectLifetime EntityObjects,
LocalPlayerState LocalPlayer, LocalPlayerState LocalPlayer,
Spellbook Spellbook, Spellbook Spellbook,
CombatState Combat, CombatState Combat,
@ -173,7 +174,7 @@ internal sealed class LiveSessionRuntimeFactory
ExternalContainer = () => _domain.ExternalContainers.Reset(), ExternalContainer = () => _domain.ExternalContainers.Reset(),
InteractionAndSelection = _interaction.SelectionInteractions.ResetSession, InteractionAndSelection = _interaction.SelectionInteractions.ResetSession,
SelectionPresentation = _world.SelectionScene.Reset, SelectionPresentation = _world.SelectionScene.Reset,
ObjectTable = _domain.Objects.Clear, ObjectTable = _domain.EntityObjects.ClearObjects,
Spellbook = _domain.Spellbook.Clear, Spellbook = _domain.Spellbook.Clear,
MagicRuntime = () => _ui.Magic?.Reset(), MagicRuntime = () => _ui.Magic?.Reset(),
CombatAttack = _interaction.CombatAttack.ResetSession, CombatAttack = _interaction.CombatAttack.ResetSession,
@ -260,7 +261,7 @@ internal sealed class LiveSessionRuntimeFactory
} }
private LiveInventorySessionBindings CreateInventoryBindings() => new( private LiveInventorySessionBindings CreateInventoryBindings() => new(
_domain.Objects, _domain.EntityObjects.Objects,
_domain.LocalPlayer, _domain.LocalPlayer,
PlayerGuid: () => _player.Identity.ServerGuid, PlayerGuid: () => _player.Identity.ServerGuid,
OnShortcuts: list => _player.Shortcuts.Items = list, OnShortcuts: list => _player.Shortcuts.Items = list,
@ -333,7 +334,7 @@ internal sealed class LiveSessionRuntimeFactory
ShowSystemMessage: text => _domain.Chat.OnSystemMessage(text, 0u), ShowSystemMessage: text => _domain.Chat.OnSystemMessage(text, 0u),
ShowWeenieError: code => _domain.Chat.OnWeenieError(code, null), ShowWeenieError: code => _domain.Chat.OnWeenieError(code, null),
PlayerPublicWeenieBitfield: () => PlayerPublicWeenieBitfield: () =>
_domain.Objects.Get(_player.Identity.ServerGuid)? _domain.EntityObjects.Objects.Get(_player.Identity.ServerGuid)?
.PublicWeenieBitfield, .PublicWeenieBitfield,
ClientVersion: () => ClientVersion: () =>
typeof(LiveSessionRuntimeFactory).Assembly typeof(LiveSessionRuntimeFactory).Assembly
@ -351,7 +352,7 @@ internal sealed class LiveSessionRuntimeFactory
SaveAutoUi: () => _ui.RetailUi?.SaveLayout(), SaveAutoUi: () => _ui.RetailUi?.SaveLayout(),
LoadAutoUi: () => _ui.RetailUi?.RestoreLayout(), LoadAutoUi: () => _ui.RetailUi?.RestoreLayout(),
IsAway: () => IsAway: () =>
_domain.Objects.Get(_player.Identity.ServerGuid)? _domain.EntityObjects.Objects.Get(_player.Identity.ServerGuid)?
.Properties.GetBool(0x6Eu) == true, .Properties.GetBool(0x6Eu) == true,
SetAway: session.SendSetAfkMode, SetAway: session.SendSetAfkMode,
SetAwayMessage: session.SendSetAfkMessage, SetAwayMessage: session.SendSetAfkMessage,

View file

@ -6,6 +6,7 @@ using AcDream.App.Rendering.Wb;
using AcDream.App.Settings; using AcDream.App.Settings;
using AcDream.App.World; using AcDream.App.World;
using AcDream.Content; using AcDream.Content;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Session; using AcDream.Runtime.Session;
using DatReaderWriter; using DatReaderWriter;
using Silk.NET.Input; using Silk.NET.Input;
@ -340,7 +341,7 @@ public sealed class GameWindow :
// installs that immutable table once DatCollection opens in OnLoad. // installs that immutable table once DatCollection opens in OnLoad.
public AcDream.Core.Spells.SpellTable SpellTable => SpellBook.Metadata; public AcDream.Core.Spells.SpellTable SpellTable => SpellBook.Metadata;
public readonly AcDream.Core.Spells.Spellbook SpellBook = null!; public readonly AcDream.Core.Spells.Spellbook SpellBook = null!;
public readonly AcDream.Core.Items.ClientObjectTable Objects = new(); private readonly RuntimeEntityObjectLifetime _runtimeEntityObjects = new();
/// <summary>Persisted hotbar shortcuts from the last PlayerDescription (D.5.1 toolbar source).</summary> /// <summary>Persisted hotbar shortcuts from the last PlayerDescription (D.5.1 toolbar source).</summary>
private readonly ShortcutSnapshotState _shortcutSnapshots = new(); private readonly ShortcutSnapshotState _shortcutSnapshots = new();
public IReadOnlyList<AcDream.Core.Items.ShortcutEntry> Shortcuts public IReadOnlyList<AcDream.Core.Items.ShortcutEntry> Shortcuts
@ -1260,7 +1261,7 @@ public sealed class GameWindow :
_combatAttackOperations, _combatAttackOperations,
_selection, _selection,
_externalContainers, _externalContainers,
Objects, _runtimeEntityObjects.Objects,
contentEffectsAudio.MagicCatalog, contentEffectsAudio.MagicCatalog,
SpellBook, SpellBook,
Chat, Chat,
@ -1314,7 +1315,7 @@ public sealed class GameWindow :
_worldGameState, _worldGameState,
_worldEvents, _worldEvents,
_selection, _selection,
Objects, _runtimeEntityObjects,
_liveEntityRuntimeSlot, _liveEntityRuntimeSlot,
_liveEntityMotionBindings, _liveEntityMotionBindings,
_entityEffectAdvance, _entityEffectAdvance,
@ -1372,7 +1373,7 @@ public sealed class GameWindow :
_worldGameState, _worldGameState,
_worldEvents, _worldEvents,
_selection, _selection,
Objects, _runtimeEntityObjects,
_classificationCache, _classificationCache,
_liveEntityRuntimeSlot, _liveEntityRuntimeSlot,
_animatedEntities, _animatedEntities,

View file

@ -1,7 +1,7 @@
using AcDream.App.Input; using AcDream.App.Input;
using AcDream.Core.Items;
using AcDream.Core.Net; using AcDream.Core.Net;
using AcDream.Core.Net.Messages; using AcDream.Core.Net.Messages;
using AcDream.Runtime.Entities;
namespace AcDream.App.World; namespace AcDream.App.World;
@ -17,7 +17,7 @@ internal interface ILiveEntityPruneSink
internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
{ {
private readonly LiveEntityRuntime _runtime; private readonly LiveEntityRuntime _runtime;
private readonly ClientObjectTable _objects; private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly ILiveEntityTeardownCoordinator _teardown; private readonly ILiveEntityTeardownCoordinator _teardown;
private readonly ILocalPlayerIdentitySource _identity; private readonly ILocalPlayerIdentitySource _identity;
private readonly DormantLiveEntityStore _dormant; private readonly DormantLiveEntityStore _dormant;
@ -25,14 +25,15 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
public LiveEntityDeletionController( public LiveEntityDeletionController(
LiveEntityRuntime runtime, LiveEntityRuntime runtime,
ClientObjectTable objects, RuntimeEntityObjectLifetime entityObjects,
ILiveEntityTeardownCoordinator teardown, ILiveEntityTeardownCoordinator teardown,
ILocalPlayerIdentitySource identity, ILocalPlayerIdentitySource identity,
DormantLiveEntityStore? dormant = null, DormantLiveEntityStore? dormant = null,
Action<string>? diagnostic = null) Action<string>? diagnostic = null)
{ {
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_objects = objects ?? throw new ArgumentNullException(nameof(objects)); _entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown)); _teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
_identity = identity ?? throw new ArgumentNullException(nameof(identity)); _identity = identity ?? throw new ArgumentNullException(nameof(identity));
_dormant = dormant ?? new DormantLiveEntityStore(); _dormant = dormant ?? new DormantLiveEntityStore();
@ -56,11 +57,10 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
bool removed = _runtime.UnregisterLiveEntity( bool removed = _runtime.UnregisterLiveEntity(
delete, delete,
isLocalPlayer: false, isLocalPlayer: false,
beforeTeardown: () => removeRetainedObject: true);
ObjectTableWiring.ApplyEntityDelete(_objects, delete));
bool removedDormant = _dormant.RemoveExact(delete); bool removedDormant = _dormant.RemoveExact(delete);
if (!removed && removedDormant) if (!removed && removedDormant)
ObjectTableWiring.ApplyEntityDelete(_objects, delete); _entityObjects.ApplyAcceptedDormantDelete(delete);
if (removed) if (removed)
{ {
_dormant.RemoveExact(delete); _dormant.RemoveExact(delete);

View file

@ -1,6 +1,5 @@
using AcDream.App.Rendering; using AcDream.App.Rendering;
using AcDream.App.Input; using AcDream.App.Input;
using AcDream.Core.Items;
using AcDream.Core.Net; using AcDream.Core.Net;
using AcDream.Core.Net.Messages; using AcDream.Core.Net.Messages;
using AcDream.Core.World; using AcDream.Core.World;
@ -169,7 +168,7 @@ internal interface ILiveEntityWorldOriginCoordinator
internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoadedSink internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoadedSink
{ {
private readonly LiveEntityRuntime _runtime; private readonly LiveEntityRuntime _runtime;
private readonly ClientObjectTable _objects; private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly object _datLock; private readonly object _datLock;
private readonly ILiveEntityProjectionMaterializer _materializer; private readonly ILiveEntityProjectionMaterializer _materializer;
private readonly ILiveEntityRelationshipProjection _relationships; private readonly ILiveEntityRelationshipProjection _relationships;
@ -193,7 +192,7 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
public LiveEntityHydrationController( public LiveEntityHydrationController(
LiveEntityRuntime runtime, LiveEntityRuntime runtime,
ClientObjectTable objects, RuntimeEntityObjectLifetime entityObjects,
object datLock, object datLock,
ILiveEntityProjectionMaterializer materializer, ILiveEntityProjectionMaterializer materializer,
ILiveEntityRelationshipProjection relationships, ILiveEntityRelationshipProjection relationships,
@ -207,7 +206,8 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
Action<string>? diagnostic = null) Action<string>? diagnostic = null)
{ {
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_objects = objects ?? throw new ArgumentNullException(nameof(objects)); _entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_datLock = datLock ?? throw new ArgumentNullException(nameof(datLock)); _datLock = datLock ?? throw new ArgumentNullException(nameof(datLock));
_materializer = materializer ?? throw new ArgumentNullException(nameof(materializer)); _materializer = materializer ?? throw new ArgumentNullException(nameof(materializer));
_relationships = relationships ?? throw new ArgumentNullException(nameof(relationships)); _relationships = relationships ?? throw new ArgumentNullException(nameof(relationships));
@ -285,15 +285,13 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
if (_runtime.IsCurrentCreateIntegration( if (_runtime.IsCurrentCreateIntegration(
canonical, canonical,
createIntegrationVersion) createIntegrationVersion)
&& ObjectTableWiring.ApplyEntitySpawn( && _entityObjects.ApplyAcceptedSpawn(
_objects, canonical,
createIntegrationVersion,
spawn, spawn,
replaceGeneration: result.Disposition is replaceGeneration: result.Disposition is
AcDream.Core.Physics.CreateObjectTimestampDisposition.NewGeneration AcDream.Core.Physics.CreateObjectTimestampDisposition.NewGeneration
|| dormantDisposition is DormantCreateDisposition.NewGeneration, || dormantDisposition is DormantCreateDisposition.NewGeneration)
accepting: () => _runtime.IsCurrentCreateIntegration(
canonical,
createIntegrationVersion))
&& _runtime.IsCurrentCreateIntegration( && _runtime.IsCurrentCreateIntegration(
canonical, canonical,
createIntegrationVersion)) createIntegrationVersion))

View file

@ -448,6 +448,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
private readonly GpuWorldState _spatial; private readonly GpuWorldState _spatial;
private readonly ILiveEntityResourceLifecycle _resources; private readonly ILiveEntityResourceLifecycle _resources;
private readonly ILiveEntityRuntimeComponentLifecycle _runtimeComponentLifecycle; private readonly ILiveEntityRuntimeComponentLifecycle _runtimeComponentLifecycle;
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly RuntimeEntityDirectory _directory; private readonly RuntimeEntityDirectory _directory;
private readonly LiveEntityProjectionStore _projections; private readonly LiveEntityProjectionStore _projections;
// Logical components survive retail's 25-second leave-visibility lifetime. // Logical components survive retail's 25-second leave-visibility lifetime.
@ -471,12 +472,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public LiveEntityRuntime( public LiveEntityRuntime(
GpuWorldState spatial, GpuWorldState spatial,
ILiveEntityResourceLifecycle resources, ILiveEntityResourceLifecycle resources,
uint firstLocalEntityId = FirstLiveEntityId) RuntimeEntityObjectLifetime entityObjects)
: this( : this(
spatial, spatial,
resources, resources,
NullLiveEntityRuntimeComponentLifecycle.Instance, NullLiveEntityRuntimeComponentLifecycle.Instance,
firstLocalEntityId) entityObjects)
{ {
} }
@ -484,12 +485,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
GpuWorldState spatial, GpuWorldState spatial,
ILiveEntityResourceLifecycle resources, ILiveEntityResourceLifecycle resources,
Action<LiveEntityRecord> tearDownRuntimeComponents, Action<LiveEntityRecord> tearDownRuntimeComponents,
uint firstLocalEntityId = FirstLiveEntityId) RuntimeEntityObjectLifetime entityObjects)
: this( : this(
spatial, spatial,
resources, resources,
new DelegateLiveEntityRuntimeComponentLifecycle(tearDownRuntimeComponents), new DelegateLiveEntityRuntimeComponentLifecycle(tearDownRuntimeComponents),
firstLocalEntityId) entityObjects)
{ {
} }
@ -497,13 +498,15 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
GpuWorldState spatial, GpuWorldState spatial,
ILiveEntityResourceLifecycle resources, ILiveEntityResourceLifecycle resources,
ILiveEntityRuntimeComponentLifecycle? runtimeComponentLifecycle, ILiveEntityRuntimeComponentLifecycle? runtimeComponentLifecycle,
uint firstLocalEntityId = FirstLiveEntityId) RuntimeEntityObjectLifetime entityObjects)
{ {
_spatial = spatial ?? throw new ArgumentNullException(nameof(spatial)); _spatial = spatial ?? throw new ArgumentNullException(nameof(spatial));
_resources = resources ?? throw new ArgumentNullException(nameof(resources)); _resources = resources ?? throw new ArgumentNullException(nameof(resources));
_runtimeComponentLifecycle = runtimeComponentLifecycle _runtimeComponentLifecycle = runtimeComponentLifecycle
?? throw new ArgumentNullException(nameof(runtimeComponentLifecycle)); ?? throw new ArgumentNullException(nameof(runtimeComponentLifecycle));
_directory = new RuntimeEntityDirectory(firstLocalEntityId); _entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_directory = _entityObjects.Entities;
_projections = new LiveEntityProjectionStore(_directory); _projections = new LiveEntityProjectionStore(_directory);
_spatial.LiveProjectionVisibilityChanged += OnSpatialVisibilityChanged; _spatial.LiveProjectionVisibilityChanged += OnSpatialVisibilityChanged;
} }
@ -1187,6 +1190,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public bool UnregisterLiveEntity( public bool UnregisterLiveEntity(
DeleteObject.Parsed delete, DeleteObject.Parsed delete,
bool isLocalPlayer, bool isLocalPlayer,
bool removeRetainedObject = false,
Action? beforeTeardown = null) Action? beforeTeardown = null)
{ {
if (_isRegisteringResources) if (_isRegisteringResources)
@ -1208,9 +1212,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
&& (record.DeleteAcceptedForTeardown && (record.DeleteAcceptedForTeardown
|| _isClearing || _isClearing
|| _sessionClearPendingFinalization); || _sessionClearPendingFinalization);
RuntimeEntityDeleteAcceptance? acceptedDelete = null;
if (!retryingAcceptedDelete) if (!retryingAcceptedDelete)
{ {
if (!_directory.TryDelete(delete, isLocalPlayer)) if (!_entityObjects.TryAcceptDelete(
delete,
isLocalPlayer,
out acceptedDelete))
return false; return false;
AdvanceLifetimeMutation(delete.Guid); AdvanceLifetimeMutation(delete.Guid);
ParentAttachments.DeleteGeneration(delete.Guid, delete.InstanceSequence); ParentAttachments.DeleteGeneration(delete.Guid, delete.InstanceSequence);
@ -1258,6 +1266,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{ {
if (!retryingAcceptedDelete) if (!retryingAcceptedDelete)
{ {
if (removeRetainedObject)
{
try
{
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
}
catch (Exception error)
{
(failures ??= new List<Exception>()).Add(error);
}
}
try try
{ {
beforeTeardown?.Invoke(); beforeTeardown?.Invoke();

View file

@ -10,7 +10,8 @@ namespace AcDream.Core.Net;
/// 64-bit qualities (experience), SetStackSize (0x0197) = stack count, /// 64-bit qualities (experience), SetStackSize (0x0197) = stack count,
/// InventoryRemoveObject (0x0024) = inventory-view removal. /// InventoryRemoveObject (0x0024) = inventory-view removal.
/// CreateObject/DeleteObject application remains exposed as pure helpers so /// CreateObject/DeleteObject application remains exposed as pure helpers so
/// the App live-entity owner can freshness-gate the shared projections first. /// the Runtime entity/object lifetime owner can freshness-gate the canonical
/// incarnation before either graphical or retained-object projections mutate.
/// Retail: ACCObjectMaint::CreateObject / DeleteObject (the weenie_object_table side). /// Retail: ACCObjectMaint::CreateObject / DeleteObject (the weenie_object_table side).
/// </summary> /// </summary>
public static class ObjectTableWiring public static class ObjectTableWiring

View file

@ -0,0 +1,135 @@
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
namespace AcDream.Runtime.Entities;
/// <summary>
/// One exact DeleteObject acceptance issued by a
/// <see cref="RuntimeEntityObjectLifetime"/>. The graphical host retires the
/// active identity before returning this token to the Runtime owner for the
/// retained-object mutation, preserving retail callback order.
/// </summary>
public sealed class RuntimeEntityDeleteAcceptance
{
internal RuntimeEntityDeleteAcceptance(
RuntimeEntityObjectLifetime owner,
DeleteObject.Parsed delete)
{
Owner = owner;
Delete = delete;
}
internal RuntimeEntityObjectLifetime Owner { get; }
internal bool Completed { get; set; }
public DeleteObject.Parsed Delete { get; }
}
/// <summary>
/// Presentation-free ownership root for one session's canonical entity and
/// retained object lifetimes. Graphical and direct hosts borrow these exact
/// instances; they never allocate a second directory or object table.
/// </summary>
public sealed class RuntimeEntityObjectLifetime
{
public RuntimeEntityObjectLifetime(
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
{
Entities = new RuntimeEntityDirectory(firstLocalEntityId);
Objects = new ClientObjectTable();
}
public RuntimeEntityDirectory Entities { get; }
public ClientObjectTable Objects { get; }
/// <summary>
/// Applies the retained-object half of an accepted CreateObject while the
/// exact canonical incarnation and its integration version remain current.
/// Object-table callbacks are synchronous and may re-enter entity lifetime,
/// so the acceptance predicate is checked before, during, and after apply.
/// </summary>
public bool ApplyAcceptedSpawn(
RuntimeEntityRecord canonical,
ulong expectedCreateIntegrationVersion,
WorldSession.EntitySpawn spawn,
bool replaceGeneration)
{
ArgumentNullException.ThrowIfNull(canonical);
if (canonical.ServerGuid != spawn.Guid
|| canonical.Incarnation != spawn.InstanceSequence)
{
return false;
}
bool IsCurrent() =>
Entities.IsCurrent(canonical)
&& canonical.CreateIntegrationVersion
== expectedCreateIntegrationVersion;
return IsCurrent()
&& ObjectTableWiring.ApplyEntitySpawn(
Objects,
spawn,
replaceGeneration,
IsCurrent)
&& IsCurrent();
}
/// <summary>
/// Accepts the exact DeleteObject generation without yet publishing the
/// retained-object removal. This split lets the graphical host retire the
/// active identity first, matching the established reentrant callback
/// order, while Runtime remains the only freshness authority.
/// </summary>
public bool TryAcceptDelete(
DeleteObject.Parsed delete,
bool isLocalPlayer,
out RuntimeEntityDeleteAcceptance acceptance)
{
if (!Entities.TryDelete(delete, isLocalPlayer))
{
acceptance = null!;
return false;
}
acceptance = new RuntimeEntityDeleteAcceptance(this, delete);
return true;
}
/// <summary>
/// Completes a previously accepted delete exactly once. Marking the token
/// complete before synchronous callbacks preserves the current retry rule:
/// a throwing observer does not replay an already-committed removal.
/// </summary>
public void CompleteAcceptedDelete(
RuntimeEntityDeleteAcceptance acceptance)
{
ArgumentNullException.ThrowIfNull(acceptance);
if (!ReferenceEquals(acceptance.Owner, this))
{
throw new InvalidOperationException(
"A delete acceptance belongs to a different Runtime lifetime.");
}
if (acceptance.Completed)
{
throw new InvalidOperationException(
"A delete acceptance has already been completed.");
}
acceptance.Completed = true;
ObjectTableWiring.ApplyEntityDelete(Objects, acceptance.Delete);
}
/// <summary>
/// Applies a delete accepted by the dormant exact-incarnation owner after
/// the active Runtime directory correctly reports no live record.
/// </summary>
public void ApplyAcceptedDormantDelete(DeleteObject.Parsed delete) =>
ObjectTableWiring.ApplyEntityDelete(Objects, delete);
/// <summary>
/// Clears retained object state at the Runtime-owned reset stage. App
/// projection teardown remains a later acknowledged stage.
/// </summary>
public void ClearObjects() => Objects.Clear();
}

View file

@ -97,7 +97,7 @@ public sealed class CombatAttackTargetSourceTests
0x0101_FFFFu, 0x0101_FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Runtime = new LiveEntityRuntime(spatial, new Resources()); Runtime = LiveEntityRuntimeFixture.Create(spatial, new Resources());
var identity = new LocalPlayerIdentityState { ServerGuid = Player }; var identity = new LocalPlayerIdentityState { ServerGuid = Player };
Targets = new CombatAttackTargetSource( Targets = new CombatAttackTargetSource(
Selection, Selection,

View file

@ -150,7 +150,7 @@ public sealed class LivePresentationCompositionTests
Assert.Contains("d.PortalTunnelFallback.AcquirePrepared(", phase); Assert.Contains("d.PortalTunnelFallback.AcquirePrepared(", phase);
} }
private static LiveEntityRuntime Runtime() => new( private static LiveEntityRuntime Runtime() => LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(static _ => { }, static _ => { })); new DelegateLiveEntityResourceLifecycle(static _ => { }, static _ => { }));

View file

@ -50,7 +50,7 @@ public sealed class WorldSelectionQueryTests
0x0101_FFFFu, 0x0101_FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Runtime = new LiveEntityRuntime(spatial, new Resources()); Runtime = LiveEntityRuntimeFixture.Create(spatial, new Resources());
Scene = new RetailSelectionScene(new GeometrySource(Mesh())); Scene = new RetailSelectionScene(new GeometrySource(Mesh()));
Query = new WorldSelectionQuery( Query = new WorldSelectionQuery(
Runtime, Runtime,

View file

@ -0,0 +1,50 @@
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Runtime.Entities;
namespace AcDream.App.Tests;
/// <summary>
/// Explicit isolated lifetime-group seam for App projection tests. Production
/// composition supplies its session-owned group from GameWindow; focused tests
/// use a fresh group per runtime so identity never leaks between fixtures.
/// </summary>
internal static class LiveEntityRuntimeFixture
{
public static LiveEntityRuntime Create(
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
{
var lifetime = new RuntimeEntityObjectLifetime(firstLocalEntityId);
return new LiveEntityRuntime(spatial, resources, lifetime);
}
public static LiveEntityRuntime Create(
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
Action<LiveEntityRecord> tearDownRuntimeComponents,
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
{
var lifetime = new RuntimeEntityObjectLifetime(firstLocalEntityId);
return new LiveEntityRuntime(
spatial,
resources,
tearDownRuntimeComponents,
lifetime);
}
public static LiveEntityRuntime Create(
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
ILiveEntityRuntimeComponentLifecycle runtimeComponentLifecycle,
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
{
var lifetime = new RuntimeEntityObjectLifetime(firstLocalEntityId);
return new LiveEntityRuntime(
spatial,
resources,
runtimeComponentLifecycle,
lifetime);
}
}

View file

@ -210,7 +210,7 @@ public sealed class LiveSessionResetPlanTests
landblock, landblock,
new LandBlock(), new LandBlock(),
new List<WorldEntity> { staticEntity })); new List<WorldEntity> { staticEntity }));
var live = new LiveEntityRuntime(spatial, new FailingOnceResources()); var live = LiveEntityRuntimeFixture.Create(spatial, new FailingOnceResources());
WorldSession.EntitySpawn spawn = Spawn(playerA, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(playerA, 1, 1, 0x01010001u);
live.RegisterLiveEntity(spawn); live.RegisterLiveEntity(spawn);
live.MaterializeLiveEntity( live.MaterializeLiveEntity(

View file

@ -315,7 +315,7 @@ public sealed class LiveEntityInboundAuthorityGateTests
accepted.VelocityAuthorityVersion)); accepted.VelocityAuthorityVersion));
} }
private static LiveEntityRuntime Runtime() => new( private static LiveEntityRuntime Runtime() => LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));

View file

@ -23,7 +23,7 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterAndMaterializeProjection( LiveEntityRecord record = live.RegisterAndMaterializeProjection(
@ -90,7 +90,7 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
{ {
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterAndMaterializeProjection( LiveEntityRecord record = live.RegisterAndMaterializeProjection(

View file

@ -1457,7 +1457,7 @@ public sealed class ProjectileControllerTests
{ {
if (loadInitialLandblock) if (loadInitialLandblock)
Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
Live = new LiveEntityRuntime(Spatial, Resources); Live = LiveEntityRuntimeFixture.Create(Spatial, Resources);
Engine = physicsEngine ?? new PhysicsEngine(); Engine = physicsEngine ?? new PhysicsEngine();
Origin.Recenter(1, 1); Origin.Recenter(1, 1);
Controller = new ProjectileController( Controller = new ProjectileController(

View file

@ -692,7 +692,7 @@ public sealed class RemotePhysicsUpdaterTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
BindHiddenRemote(live, firstGuid); BindHiddenRemote(live, firstGuid);
@ -840,7 +840,7 @@ public sealed class RemotePhysicsUpdaterTests
0xA9B4FFFFu, 0xA9B4FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Live = new LiveEntityRuntime( Live = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
record => record =>

View file

@ -87,7 +87,7 @@ public sealed class RemoteTeleportControllerTests
const uint destinationCell = 0xAAB40001u; const uint destinationCell = 0xAAB40001u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
var live = new LiveEntityRuntime(spatial, new Resources()); var live = LiveEntityRuntimeFixture.Create(spatial, new Resources());
live.RegisterLiveEntity(Spawn(guid, sourceCell)); live.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = live.MaterializeLiveEntity( WorldEntity entity = live.MaterializeLiveEntity(
guid, guid,
@ -228,7 +228,7 @@ public sealed class RemoteTeleportControllerTests
const uint sourceCell = 0xA9B40039u; const uint sourceCell = 0xA9B40039u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
var live = new LiveEntityRuntime(spatial, new Resources()); var live = LiveEntityRuntimeFixture.Create(spatial, new Resources());
live.RegisterLiveEntity(Spawn(guid, sourceCell)); live.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = live.MaterializeLiveEntity( WorldEntity entity = live.MaterializeLiveEntity(
guid, guid,
@ -319,7 +319,7 @@ public sealed class RemoteTeleportControllerTests
Vector3 sourcePosition = new(191f, 10f, 50f); Vector3 sourcePosition = new(191f, 10f, 50f);
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
var live = new LiveEntityRuntime(spatial, new Resources()); var live = LiveEntityRuntimeFixture.Create(spatial, new Resources());
live.RegisterLiveEntity(Spawn(guid, sourceCell)); live.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = live.MaterializeLiveEntity( WorldEntity entity = live.MaterializeLiveEntity(
guid, guid,
@ -1101,7 +1101,7 @@ public sealed class RemoteTeleportControllerTests
])); ]));
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
var live = new LiveEntityRuntime(spatial, resources); var live = LiveEntityRuntimeFixture.Create(spatial, resources);
live.RegisterLiveEntity(Spawn(guid, sourceCell)); live.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = live.MaterializeLiveEntity( WorldEntity entity = live.MaterializeLiveEntity(
guid, guid,
@ -1179,7 +1179,7 @@ public sealed class RemoteTeleportControllerTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xA9B4FFFFu));
spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu)); spatial.AddLandblock(EmptyLandblock(0xAAB4FFFFu));
var live = new LiveEntityRuntime(spatial, new Resources()); var live = LiveEntityRuntimeFixture.Create(spatial, new Resources());
live.RegisterLiveEntity(Spawn(guid, sourceCell)); live.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = live.MaterializeLiveEntity( WorldEntity entity = live.MaterializeLiveEntity(
guid, guid,
@ -1383,7 +1383,7 @@ public sealed class RemoteTeleportControllerTests
internal RollbackFixture(bool celllessSource = false) internal RollbackFixture(bool celllessSource = false)
{ {
Spatial.AddLandblock(EmptyLandblock(SourceLandblock)); Spatial.AddLandblock(EmptyLandblock(SourceLandblock));
Live = new LiveEntityRuntime(Spatial, new Resources()); Live = LiveEntityRuntimeFixture.Create(Spatial, new Resources());
Live.RegisterLiveEntity(Spawn(Guid, SourceCell)); Live.RegisterLiveEntity(Spawn(Guid, SourceCell));
Entity = Live.MaterializeLiveEntity( Entity = Live.MaterializeLiveEntity(
Guid, Guid,

View file

@ -1161,7 +1161,7 @@ public sealed class EquippedChildProjectionWithdrawalTests
(Cell & 0xFFFF0000u) | 0xFFFFu, (Cell & 0xFFFF0000u) | 0xFFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Live = new LiveEntityRuntime( Live = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
_lifecycle); _lifecycle);

View file

@ -17,7 +17,7 @@ public sealed class LiveAppearanceAnimationTests
public void Capture_UsesCanonicalRecordEntityAndAnimationOwner() public void Capture_UsesCanonicalRecordEntityAndAnimationOwner()
{ {
const uint guid = 0x70000002u; const uint guid = 0x70000002u;
var runtime = new AcDream.App.World.LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
new AcDream.App.Streaming.GpuWorldState(), new AcDream.App.Streaming.GpuWorldState(),
new AcDream.App.World.DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new AcDream.App.World.DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
var spawn = new AcDream.Core.Net.WorldSession.EntitySpawn( var spawn = new AcDream.Core.Net.WorldSession.EntitySpawn(
@ -118,7 +118,7 @@ public sealed class LiveAppearanceAnimationTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
WorldSession.EntitySpawn first = Spawn(guid, cell, instance: 1); WorldSession.EntitySpawn first = Spawn(guid, cell, instance: 1);

View file

@ -440,7 +440,7 @@ public sealed class LiveEntityAnimationPresenterTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
return Add(live, guid, partCount, scale, withSequencer); return Add(live, guid, partCount, scale, withSequencer);

View file

@ -679,7 +679,7 @@ public sealed class LiveEntityAnimationSchedulerTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterAndMaterializeProjection( LiveEntityRecord record = live.RegisterAndMaterializeProjection(
@ -800,7 +800,7 @@ public sealed class LiveEntityAnimationSchedulerTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var live = new LiveEntityRuntime( var live = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
LiveEntityRecord record = live.RegisterAndMaterializeProjection( LiveEntityRecord record = live.RegisterAndMaterializeProjection(

View file

@ -286,7 +286,7 @@ public sealed class LiveEntityCreateSupersessionRecoveryTests
0x0101FFFFu, 0x0101FFFFu,
new DatReaderWriter.DBObjs.LandBlock(), new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
return new LiveEntityRuntime( return LiveEntityRuntimeFixture.Create(
spatial, spatial,
resources, resources,
new DelegateLiveEntityRuntimeComponentLifecycle(_ => { })); new DelegateLiveEntityRuntimeComponentLifecycle(_ => { }));

View file

@ -357,7 +357,7 @@ public sealed class LiveRenderProjectionJournalTests
var resources = new DelegateLiveEntityResourceLifecycle( var resources = new DelegateLiveEntityResourceLifecycle(
static _ => { }, static _ => { },
entity => sink?.OnResourceUnregister(entity)); entity => sink?.OnResourceUnregister(entity));
var runtime = new LiveEntityRuntime(state, resources); var runtime = LiveEntityRuntimeFixture.Create(state, resources);
var journal = new RenderProjectionJournal( var journal = new RenderProjectionJournal(
RenderSceneGeneration.FromRaw(1)); RenderSceneGeneration.FromRaw(1));
var projections = new LiveRenderProjectionJournal( var projections = new LiveRenderProjectionJournal(

View file

@ -55,7 +55,7 @@ public sealed class EntityEffectControllerTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Runtime = new LiveEntityRuntime( Runtime = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new NoopResources(), new NoopResources(),
record => _controller?.OnLiveEntityUnregistered(record)); record => _controller?.OnLiveEntityUnregistered(record));

View file

@ -151,7 +151,7 @@ public sealed class LiveEntityLightControllerTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Runtime = new LiveEntityRuntime(Spatial, new NoopResources(), _ => { }); Runtime = LiveEntityRuntimeFixture.Create(Spatial, new NoopResources(), _ => { });
Sink = new LightingHookSink(Lights, Poses); Sink = new LightingHookSink(Lights, Poses);
Controller = new LiveEntityLightController( Controller = new LiveEntityLightController(
Runtime, Runtime,

View file

@ -185,7 +185,7 @@ public sealed class CurrentGameRuntimeAdapterTests
public Harness() public Harness()
{ {
Identity = new LocalPlayerIdentityState(); Identity = new LocalPlayerIdentityState();
Entities = new LiveEntityRuntime( Entities = LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new NoopEntityResources()); new NoopEntityResources());
Objects = new ClientObjectTable(); Objects = new ClientObjectTable();

View file

@ -579,7 +579,7 @@ public sealed class GpuWorldStateVisibilityTests
const uint landblock = 0x0101FFFFu; const uint landblock = 0x0101FFFFu;
const uint cell = 0x01010001u; const uint cell = 0x01010001u;
var state = new GpuWorldState(); var state = new GpuWorldState();
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
state, state,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
WorldSession.EntitySpawn first = Spawn(0x70000011u, cell); WorldSession.EntitySpawn first = Spawn(0x70000011u, cell);
@ -624,7 +624,7 @@ public sealed class GpuWorldStateVisibilityTests
landblock, landblock,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
state, state,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
WorldSession.EntitySpawn spawn = Spawn(guid, cell); WorldSession.EntitySpawn spawn = Spawn(guid, cell);

View file

@ -396,7 +396,7 @@ public sealed class LocalPlayerTeleportControllerTests
0x2021FFFFu, 0x2021FFFFu,
new DatReaderWriter.DBObjs.LandBlock(), new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var runtime = new LiveEntityRuntime(world, new NullResources()); var runtime = LiveEntityRuntimeFixture.Create(world, new NullResources());
runtime.RegisterLiveEntity(Spawn(guid, cell)); runtime.RegisterLiveEntity(Spawn(guid, cell));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,
@ -479,7 +479,7 @@ public sealed class LocalPlayerTeleportControllerTests
destinationCell & 0xFFFF0000u | 0xFFFFu, destinationCell & 0xFFFF0000u | 0xFFFFu,
new DatReaderWriter.DBObjs.LandBlock(), new DatReaderWriter.DBObjs.LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var runtime = new LiveEntityRuntime(world, new NullResources()); var runtime = LiveEntityRuntimeFixture.Create(world, new NullResources());
runtime.RegisterLiveEntity(Spawn(guid, sourceCell)); runtime.RegisterLiveEntity(Spawn(guid, sourceCell));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,

View file

@ -506,7 +506,9 @@ public sealed class StreamingFrameControllerTests
} }
private static LiveEntityRuntime Runtime(GpuWorldState state) => private static LiveEntityRuntime Runtime(GpuWorldState state) =>
new(state, new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); LiveEntityRuntimeFixture.Create(
state,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
private static LoadedLandblock EmptyLandblock(uint landblock) => private static LoadedLandblock EmptyLandblock(uint landblock) =>
new(landblock, new LandBlock(), Array.Empty<WorldEntity>()); new(landblock, new LandBlock(), Array.Empty<WorldEntity>());

View file

@ -110,7 +110,7 @@ public sealed class DeferredLiveEntityRuntimeComponentLifecycleTests
{ {
const uint guid = 0x70000002u; const uint guid = 0x70000002u;
var bridge = new DeferredLiveEntityRuntimeComponentLifecycle(); var bridge = new DeferredLiveEntityRuntimeComponentLifecycle();
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
bridge); bridge);
@ -136,7 +136,7 @@ public sealed class DeferredLiveEntityRuntimeComponentLifecycleTests
private static LiveEntityRecord CreateRecord() private static LiveEntityRecord CreateRecord()
{ {
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
return runtime.RegisterAndMaterializeProjection( return runtime.RegisterAndMaterializeProjection(

View file

@ -1828,7 +1828,8 @@ public sealed class LiveEntityHydrationControllerTests
private sealed class Fixture : IDisposable private sealed class Fixture : IDisposable
{ {
public readonly List<string> Operations = []; public readonly List<string> Operations = [];
public readonly ClientObjectTable Objects = new(); public readonly RuntimeEntityObjectLifetime EntityObjects = new();
public ClientObjectTable Objects => EntityObjects.Objects;
public readonly RecordingResources Resources; public readonly RecordingResources Resources;
public readonly LiveEntityRuntime Runtime; public readonly LiveEntityRuntime Runtime;
public readonly RecordingMaterializer Materializer; public readonly RecordingMaterializer Materializer;
@ -1861,7 +1862,8 @@ public sealed class LiveEntityHydrationControllerTests
Runtime = new LiveEntityRuntime( Runtime = new LiveEntityRuntime(
spatial, spatial,
Resources, Resources,
Teardown); Teardown,
EntityObjects);
Materializer = new RecordingMaterializer(Runtime, Operations); Materializer = new RecordingMaterializer(Runtime, Operations);
Relationships = new RecordingRelationships(Operations); Relationships = new RecordingRelationships(Operations);
Ready = new RecordingReadyPublisher(Operations); Ready = new RecordingReadyPublisher(Operations);
@ -1887,13 +1889,13 @@ public sealed class LiveEntityHydrationControllerTests
}; };
var deletion = new LiveEntityDeletionController( var deletion = new LiveEntityDeletionController(
Runtime, Runtime,
Objects, EntityObjects,
Teardown, Teardown,
identity, identity,
Dormant); Dormant);
Controller = new LiveEntityHydrationController( Controller = new LiveEntityHydrationController(
Runtime, Runtime,
Objects, EntityObjects,
new object(), new object(),
Materializer, Materializer,
Relationships, Relationships,

View file

@ -328,7 +328,7 @@ public sealed class LiveEntityLifecycleStressTests
randomUnit: () => 0.5, randomUnit: () => 0.5,
canAdvanceOwner: ownerId => _effects?.CanAdvanceOwner(ownerId) ?? true); canAdvanceOwner: ownerId => _effects?.CanAdvanceOwner(ownerId) ?? true);
Runtime = new LiveEntityRuntime( Runtime = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle( new DelegateLiveEntityResourceLifecycle(
entity => entity =>
@ -489,7 +489,7 @@ public sealed class LiveEntityLifecycleStressTests
EntityEffectController? effects = null; EntityEffectController? effects = null;
LiveEntityPresentationController? presentation = null; LiveEntityPresentationController? presentation = null;
RemoteTeleportController? teleport = null; RemoteTeleportController? teleport = null;
Runtime = new LiveEntityRuntime( Runtime = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle( new DelegateLiveEntityResourceLifecycle(
entity => entity =>

View file

@ -630,10 +630,10 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
private static LiveEntityRuntime Runtime(Action<LiveEntityRecord>? teardown = null) => private static LiveEntityRuntime Runtime(Action<LiveEntityRecord>? teardown = null) =>
teardown is null teardown is null
? new LiveEntityRuntime( ? LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })) new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }))
: new LiveEntityRuntime( : LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
teardown); teardown);

View file

@ -426,7 +426,7 @@ public sealed class LiveEntityPresentationControllerTests
const uint pendingCell = 0x02020001u; const uint pendingCell = 0x02020001u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var shadows = new ShadowObjectRegistry(); var shadows = new ShadowObjectRegistry();
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
runtime.RegisterLiveEntity(Fixture.Spawn( runtime.RegisterLiveEntity(Fixture.Spawn(
@ -487,7 +487,7 @@ public sealed class LiveEntityPresentationControllerTests
const uint pendingCell = 0x02020001u; const uint pendingCell = 0x02020001u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var shadows = new ShadowObjectRegistry(); var shadows = new ShadowObjectRegistry();
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
runtime.RegisterLiveEntity(Fixture.Spawn( runtime.RegisterLiveEntity(Fixture.Spawn(
@ -667,7 +667,7 @@ public sealed class LiveEntityPresentationControllerTests
0x0101FFFFu, 0x0101FFFFu,
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
Runtime = new LiveEntityRuntime( Runtime = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }));
WorldSession.EntitySpawn spawn = Spawn(initialState); WorldSession.EntitySpawn spawn = Spawn(initialState);

View file

@ -196,7 +196,7 @@ public sealed class LiveEntityProjectionWithdrawalControllerTests
new LandBlock(), new LandBlock(),
Array.Empty<WorldEntity>())); Array.Empty<WorldEntity>()));
var lifecycle = new DeferredLiveEntityRuntimeComponentLifecycle(); var lifecycle = new DeferredLiveEntityRuntimeComponentLifecycle();
Live = new LiveEntityRuntime( Live = LiveEntityRuntimeFixture.Create(
Spatial, Spatial,
new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }), new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { }),
lifecycle); lifecycle);

View file

@ -22,7 +22,7 @@ public sealed class LiveEntityRuntimeTests
public void RuntimeSlot_BindsExactlyOnceWithoutOwningEntityState() public void RuntimeSlot_BindsExactlyOnceWithoutOwningEntityState()
{ {
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
var slot = new LiveEntityRuntimeSlot(); var slot = new LiveEntityRuntimeSlot();
Assert.Null(slot.Current); Assert.Null(slot.Current);
@ -176,7 +176,7 @@ public sealed class LiveEntityRuntimeTests
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000001u, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000001u, 1, 1, 0x01010001u);
LiveEntityRegistrationResult registered = runtime.RegisterLiveEntity(spawn); LiveEntityRegistrationResult registered = runtime.RegisterLiveEntity(spawn);
@ -213,7 +213,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000071u; const uint guid = 0x70000071u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn( WorldSession.EntitySpawn spawn = Spawn(
guid, guid,
instance: 1, instance: 1,
@ -325,7 +325,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000074u; const uint guid = 0x70000074u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn( runtime.RegisterLiveEntity(Spawn(
guid, guid,
instance: 1, instance: 1,
@ -417,7 +417,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000070u; const uint guid = 0x70000070u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -476,7 +476,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000071u; const uint guid = 0x70000071u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,
@ -513,7 +513,7 @@ public sealed class LiveEntityRuntimeTests
const uint secondGuid = 0x70000074u; const uint secondGuid = 0x70000074u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
foreach (uint guid in new[] { firstGuid, secondGuid }) foreach (uint guid in new[] { firstGuid, secondGuid })
{ {
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
@ -545,7 +545,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000075u; const uint guid = 0x70000075u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,
@ -580,7 +580,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000072u; const uint guid = 0x70000072u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity oldEntity = runtime.MaterializeLiveEntity( WorldEntity oldEntity = runtime.MaterializeLiveEntity(
guid, guid,
@ -617,7 +617,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000073u; const uint guid = 0x70000073u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -672,7 +672,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 6, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 6, 1, 0x01010001u);
LiveEntityRegistrationResult registration = LiveEntityRegistrationResult registration =
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
@ -702,7 +702,7 @@ public sealed class LiveEntityRuntimeTests
{ {
const uint parentGuid = 0x70000020u; const uint parentGuid = 0x70000020u;
const uint childGuid = 0x70000021u; const uint childGuid = 0x70000021u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
runtime.RegisterLiveEntity(Spawn(parentGuid, 9, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(parentGuid, 9, 1, 0x01010001u));
runtime.ParentAttachments.Enqueue(new ParentEvent.Parsed( runtime.ParentAttachments.Enqueue(new ParentEvent.Parsed(
parentGuid, childGuid, 1, 2, 9, 5)); parentGuid, childGuid, 1, 2, 9, 5));
@ -725,7 +725,7 @@ public sealed class LiveEntityRuntimeTests
{ {
const uint parentGuid = 0x70000022u; const uint parentGuid = 0x70000022u;
const uint childGuid = 0x70000023u; const uint childGuid = 0x70000023u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
runtime.RegisterLiveEntity(Spawn(childGuid, 3, 4, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(childGuid, 3, 4, 0x01010001u));
runtime.ParentAttachments.AcceptCreateObjectRelation(new ParentAttachmentRelation( runtime.ParentAttachments.AcceptCreateObjectRelation(new ParentAttachmentRelation(
parentGuid, childGuid, 1, 2, 9, 4)); parentGuid, childGuid, 1, 2, 9, 4));
@ -743,7 +743,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn first = Spawn(0x70000002u, 4, 10, 0x01010001u); WorldSession.EntitySpawn first = Spawn(0x70000002u, 4, 10, 0x01010001u);
runtime.RegisterLiveEntity(first); runtime.RegisterLiveEntity(first);
WorldEntity original = runtime.MaterializeLiveEntity( WorldEntity original = runtime.MaterializeLiveEntity(
@ -771,7 +771,7 @@ public sealed class LiveEntityRuntimeTests
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
int runtimeTearDowns = 0; int runtimeTearDowns = 0;
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
spatial, spatial,
resources, resources,
_ => runtimeTearDowns++); _ => runtimeTearDowns++);
@ -803,7 +803,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000004u, 2, 3, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000004u, 2, 3, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -833,7 +833,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
const uint guid = 0x70000005u; const uint guid = 0x70000005u;
runtime.RegisterLiveEntity(Spawn(guid, 3, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 3, 1, 0x01010001u));
WorldEntity first = runtime.MaterializeLiveEntity( WorldEntity first = runtime.MaterializeLiveEntity(
@ -863,7 +863,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000006u, 3, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000006u, 3, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
@ -900,7 +900,7 @@ public sealed class LiveEntityRuntimeTests
{ {
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000007u, 1, 1, 0x01020001u); WorldSession.EntitySpawn spawn = Spawn(0x70000007u, 1, 1, 0x01020001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -929,7 +929,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000025u; const uint guid = 0x70000025u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(guid, 2, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 2, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -983,7 +983,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(guid, 4, 10, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 4, 10, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -1063,7 +1063,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000027u; const uint guid = 0x70000027u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
@ -1104,7 +1104,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
LiveEntityRecord record = RegisterProjection(runtime, spawn); LiveEntityRecord record = RegisterProjection(runtime, spawn);
RetailObjectQuantumClock clock = record.ObjectClock; RetailObjectQuantumClock clock = record.ObjectClock;
@ -1143,7 +1143,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000049u; const uint guid = 0x70000049u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn( WorldSession.EntitySpawn spawn = Spawn(
guid, guid,
1, 1,
@ -1166,7 +1166,7 @@ public sealed class LiveEntityRuntimeTests
const uint staticGuid = 0x70000051u; const uint staticGuid = 0x70000051u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
LiveEntityRecord ordinary = RegisterProjection( LiveEntityRecord ordinary = RegisterProjection(
runtime, runtime,
@ -1204,7 +1204,7 @@ public sealed class LiveEntityRuntimeTests
const uint staticGuid = 0x70000054u; const uint staticGuid = 0x70000054u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
LiveEntityRecord ordinary = RegisterProjection( LiveEntityRecord ordinary = RegisterProjection(
runtime, runtime,
@ -1279,7 +1279,7 @@ public sealed class LiveEntityRuntimeTests
{ {
const uint parentGuid = 0x50000010u; const uint parentGuid = 0x50000010u;
const uint childGuid = 0x70000055u; const uint childGuid = 0x70000055u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
runtime.RegisterLiveEntity(Spawn(parentGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(parentGuid, 1, 1, 0x01010001u));
LiveEntityRecord child = RegisterProjection( LiveEntityRecord child = RegisterProjection(
runtime, runtime,
@ -1314,7 +1314,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000048u; const uint guid = 0x70000048u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn firstSpawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn firstSpawn = Spawn(guid, 1, 1, 0x01010001u);
LiveEntityRecord first = RegisterProjection(runtime, firstSpawn); LiveEntityRecord first = RegisterProjection(runtime, firstSpawn);
RetailObjectQuantumClock firstClock = first.ObjectClock; RetailObjectQuantumClock firstClock = first.ObjectClock;
@ -1341,7 +1341,7 @@ public sealed class LiveEntityRuntimeTests
{ {
const uint stateBeforeBindGuid = 0x70000037u; const uint stateBeforeBindGuid = 0x70000037u;
const uint bindBeforeStateGuid = 0x70000038u; const uint bindBeforeStateGuid = 0x70000038u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
runtime.RegisterLiveEntity(Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(stateBeforeBindGuid, 1, 1, 0x01010001u));
runtime.RegisterLiveEntity(Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(bindBeforeStateGuid, 1, 1, 0x01010001u));
@ -1381,7 +1381,7 @@ public sealed class LiveEntityRuntimeTests
public void ParentDrivenChildNoDrawKeepsBoundBodyInCanonicalState() public void ParentDrivenChildNoDrawKeepsBoundBodyInCanonicalState()
{ {
const uint guid = 0x70000039u; const uint guid = 0x70000039u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -1401,7 +1401,7 @@ public sealed class LiveEntityRuntimeTests
public void InitiallyVisibleCreate_DoesNotManufactureUnHideTransition() public void InitiallyVisibleCreate_DoesNotManufactureUnHideTransition()
{ {
const uint guid = 0x70000040u; const uint guid = 0x70000040u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
LiveEntityRecord record = RegisterProjection( LiveEntityRecord record = RegisterProjection(
runtime, runtime,
@ -1418,7 +1418,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000041u; const uint guid = 0x70000041u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn( runtime.RegisterLiveEntity(Spawn(
guid, guid,
1, 1,
@ -1448,7 +1448,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000042u; const uint guid = 0x70000042u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,
@ -1486,7 +1486,7 @@ public sealed class LiveEntityRuntimeTests
public void PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp() public void PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp()
{ {
const uint guid = 0x70000043u; const uint guid = 0x70000043u;
var runtime = new LiveEntityRuntime(new GpuWorldState(), new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(new GpuWorldState(), new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
Assert.True(runtime.TryApplyPickup( Assert.True(runtime.TryApplyPickup(
@ -1524,7 +1524,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000044u; const uint guid = 0x70000044u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
@ -1563,7 +1563,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000024u, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000024u, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -1593,7 +1593,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x70000009u, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000009u, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
@ -1620,7 +1620,7 @@ public sealed class LiveEntityRuntimeTests
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
int runtimeTearDowns = 0; int runtimeTearDowns = 0;
var runtime = new LiveEntityRuntime(spatial, resources, _ => runtimeTearDowns++); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources, _ => runtimeTearDowns++);
WorldSession.EntitySpawn spawn = Spawn(0x70000008u, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x70000008u, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
@ -1645,7 +1645,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingRegisterResources(); var resources = new FailingRegisterResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x7000000Au, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x7000000Au, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
@ -1671,7 +1671,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingRegisterAndRollbackOnceResources(); var resources = new FailingRegisterAndRollbackOnceResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity( Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity(
@ -1709,7 +1709,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingRegisterAndRollbackOnceResources(); var resources = new FailingRegisterAndRollbackOnceResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity( Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity(
guid, guid,
@ -1736,7 +1736,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingRegisterAndRollbackOnceResources(); var resources = new FailingRegisterAndRollbackOnceResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity( Assert.Throws<AggregateException>(() => runtime.MaterializeLiveEntity(
@ -1764,7 +1764,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingUnregisterResources(0x7000000Bu); var resources = new FailingUnregisterResources(0x7000000Bu);
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
foreach (uint guid in new[] { 0x7000000Bu, 0x7000000Cu }) foreach (uint guid in new[] { 0x7000000Bu, 0x7000000Cu })
{ {
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
@ -1792,7 +1792,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingOnceUnregisterResources(guid); var resources = new FailingOnceUnregisterResources(guid);
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
@ -1819,7 +1819,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingOnceUnregisterResources(guid); var resources = new FailingOnceUnregisterResources(guid);
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
@ -1852,7 +1852,7 @@ public sealed class LiveEntityRuntimeTests
if (!adapter.OnRemove(entity)) if (!adapter.OnRemove(entity))
throw new InvalidOperationException("presentation owner was not registered"); throw new InvalidOperationException("presentation owner was not registered");
}); });
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.ProjectionVisibilityChanged += (record, visible) => runtime.ProjectionVisibilityChanged += (record, visible) =>
{ {
if (record.WorldEntity is { } entity) if (record.WorldEntity is { } entity)
@ -1887,7 +1887,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingUnregisterResources(guid); var resources = new FailingUnregisterResources(guid);
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn first = Spawn(guid, 1, 1, 0x01010001u); WorldSession.EntitySpawn first = Spawn(guid, 1, 1, 0x01010001u);
runtime.RegisterLiveEntity(first); runtime.RegisterLiveEntity(first);
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
@ -1918,7 +1918,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010022u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010022u));
runtime.MaterializeLiveEntity(guid, 0x01010022u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010022u, id => Entity(id, guid));
@ -1940,7 +1940,7 @@ public sealed class LiveEntityRuntimeTests
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0102FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0102FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity attached = runtime.MaterializeLiveEntity( WorldEntity attached = runtime.MaterializeLiveEntity(
guid, guid,
@ -1965,7 +1965,7 @@ public sealed class LiveEntityRuntimeTests
{ {
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime( var runtime = LiveEntityRuntimeFixture.Create(
spatial, spatial,
new RecordingResources(), new RecordingResources(),
firstLocalEntityId: LiveEntityRuntime.LastLiveEntityId); firstLocalEntityId: LiveEntityRuntime.LastLiveEntityId);
@ -1984,7 +1984,7 @@ public sealed class LiveEntityRuntimeTests
Assert.NotEqual(0u, secondEntity.Id); Assert.NotEqual(0u, secondEntity.Id);
Assert.True(runtime.TryGetLocalEntityId(second.Guid, out uint resolved)); Assert.True(runtime.TryGetLocalEntityId(second.Guid, out uint resolved));
Assert.Equal(secondEntity.Id, resolved); Assert.Equal(secondEntity.Id, resolved);
Assert.Throws<ArgumentOutOfRangeException>(() => new LiveEntityRuntime( Assert.Throws<ArgumentOutOfRangeException>(() => LiveEntityRuntimeFixture.Create(
new GpuWorldState(), new GpuWorldState(),
new RecordingResources(), new RecordingResources(),
firstLocalEntityId: uint.MaxValue)); firstLocalEntityId: uint.MaxValue));
@ -1996,7 +1996,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x7000002Au; const uint guid = 0x7000002Au;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -2022,7 +2022,7 @@ public sealed class LiveEntityRuntimeTests
public void SessionClear_ForgetsPersistentClassificationWithoutMaterializedRecord() public void SessionClear_ForgetsPersistentClassificationWithoutMaterializedRecord()
{ {
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
spatial.MarkPersistent(0x7000002Bu); spatial.MarkPersistent(0x7000002Bu);
runtime.Clear(); runtime.Clear();
@ -2037,7 +2037,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x7000002Du; const uint guid = 0x7000002Du;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -2067,7 +2067,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000040u; const uint guid = 0x70000040u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity entity = runtime.MaterializeLiveEntity( WorldEntity entity = runtime.MaterializeLiveEntity(
guid, guid,
@ -2096,7 +2096,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000041u; const uint guid = 0x70000041u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -2127,7 +2127,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000044u; const uint guid = 0x70000044u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
bool replaced = false; bool replaced = false;
@ -2161,7 +2161,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000045u; const uint guid = 0x70000045u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity original = runtime.MaterializeLiveEntity( WorldEntity original = runtime.MaterializeLiveEntity(
guid, guid,
@ -2200,7 +2200,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x70000046u; const uint guid = 0x70000046u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
bool reprojected = false; bool reprojected = false;
@ -2228,7 +2228,7 @@ public sealed class LiveEntityRuntimeTests
{ {
const uint guid = 0x70000075u; const uint guid = 0x70000075u;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity pending = runtime.MaterializeLiveEntity( WorldEntity pending = runtime.MaterializeLiveEntity(
guid, guid,
@ -2277,7 +2277,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new FailingOnceUnregisterResources(guid); var resources = new FailingOnceUnregisterResources(guid);
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity? replacementEntity = null; WorldEntity? replacementEntity = null;
bool replaced = false; bool replaced = false;
@ -2318,7 +2318,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
spatial.AddLandblock(EmptyLandblock(0x0303FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0303FFFFu));
var runtime = new LiveEntityRuntime(spatial, new RecordingResources()); var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity original = runtime.MaterializeLiveEntity( WorldEntity original = runtime.MaterializeLiveEntity(
guid, guid,
@ -2352,7 +2352,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x7000002Cu; const uint guid = 0x7000002Cu;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -2394,7 +2394,7 @@ public sealed class LiveEntityRuntimeTests
const uint guid = 0x7000002Eu; const uint guid = 0x7000002Eu;
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(
guid, guid,
@ -2434,7 +2434,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
WorldEntity old = runtime.MaterializeLiveEntity( WorldEntity old = runtime.MaterializeLiveEntity(
guid, guid,
@ -2477,7 +2477,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2508,7 +2508,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2538,7 +2538,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2568,7 +2568,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2600,7 +2600,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2632,7 +2632,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid)); runtime.MaterializeLiveEntity(guid, 0x01010001u, id => Entity(id, guid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2660,7 +2660,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
resources.OnRegister = _ => resources.OnRegister = _ =>
runtime.RegisterLiveEntity(Spawn(guid, 2, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 2, 1, 0x01010001u));
@ -2686,7 +2686,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(guid, 1, 1, 0x01010001u));
resources.OnRegister = _ => runtime.Clear(); resources.OnRegister = _ => runtime.Clear();
@ -2711,7 +2711,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(firstGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(firstGuid, 1, 1, 0x01010001u));
runtime.RegisterLiveEntity(Spawn(secondGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(secondGuid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(firstGuid, 0x01010001u, id => Entity(id, firstGuid)); runtime.MaterializeLiveEntity(firstGuid, 0x01010001u, id => Entity(id, firstGuid));
@ -2744,7 +2744,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new CallbackResources(); var resources = new CallbackResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
runtime.RegisterLiveEntity(Spawn(oldGuid, 1, 1, 0x01010001u)); runtime.RegisterLiveEntity(Spawn(oldGuid, 1, 1, 0x01010001u));
runtime.MaterializeLiveEntity(oldGuid, 0x01010001u, id => Entity(id, oldGuid)); runtime.MaterializeLiveEntity(oldGuid, 0x01010001u, id => Entity(id, oldGuid));
resources.OnUnregister = _ => resources.OnUnregister = _ =>
@ -2785,7 +2785,7 @@ public sealed class LiveEntityRuntimeTests
var spatial = new GpuWorldState(); var spatial = new GpuWorldState();
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu)); spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
var resources = new RecordingResources(); var resources = new RecordingResources();
var runtime = new LiveEntityRuntime(spatial, resources); var runtime = LiveEntityRuntimeFixture.Create(spatial, resources);
WorldSession.EntitySpawn spawn = Spawn(0x7000000Du, 4, 1, 0x01010001u); WorldSession.EntitySpawn spawn = Spawn(0x7000000Du, 4, 1, 0x01010001u);
runtime.RegisterLiveEntity(spawn); runtime.RegisterLiveEntity(spawn);
runtime.MaterializeLiveEntity( runtime.MaterializeLiveEntity(

View file

@ -13,8 +13,19 @@ namespace AcDream.App.Tests.World;
public sealed class RuntimeEntityOwnershipTests public sealed class RuntimeEntityOwnershipTests
{ {
[Fact] [Fact]
public void LiveEntityRuntime_ComposesCanonicalRuntimeDirectory() public void LiveEntityRuntime_BorrowsCanonicalRuntimeDirectory()
{ {
var lifetime = new RuntimeEntityObjectLifetime();
var runtime = new LiveEntityRuntime(
new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(
static _ => { },
static _ => { }),
lifetime);
FieldInfo owner = typeof(LiveEntityRuntime).GetField(
"_entityObjects",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new InvalidOperationException("Missing Runtime lifetime root.");
FieldInfo directory = typeof(LiveEntityRuntime).GetField( FieldInfo directory = typeof(LiveEntityRuntime).GetField(
"_directory", "_directory",
BindingFlags.Instance | BindingFlags.NonPublic) BindingFlags.Instance | BindingFlags.NonPublic)
@ -25,9 +36,62 @@ public sealed class RuntimeEntityOwnershipTests
?? throw new InvalidOperationException("Missing App projection sidecar store."); ?? throw new InvalidOperationException("Missing App projection sidecar store.");
Assert.Equal(typeof(RuntimeEntityDirectory), directory.FieldType); Assert.Equal(typeof(RuntimeEntityDirectory), directory.FieldType);
Assert.Same(lifetime, owner.GetValue(runtime));
Assert.Same(lifetime.Entities, directory.GetValue(runtime));
Assert.Equal(typeof(LiveEntityProjectionStore), sidecars.FieldType); Assert.Equal(typeof(LiveEntityProjectionStore), sidecars.FieldType);
} }
[Fact]
public void ProductionComposition_HasOneRuntimeEntityObjectConstructionRoot()
{
string root = FindRepositoryRoot();
string appRoot = Path.Combine(root, "src", "AcDream.App");
string runtimeRoot = Path.Combine(root, "src", "AcDream.Runtime");
string[] appSources = Directory.GetFiles(
appRoot,
"*.cs",
SearchOption.AllDirectories);
string[] runtimeSources = Directory.GetFiles(
runtimeRoot,
"*.cs",
SearchOption.AllDirectories);
string[] appObjectAllocators = appSources
.Where(path => !path.Contains(
$"{Path.DirectorySeparatorChar}Studio{Path.DirectorySeparatorChar}",
StringComparison.OrdinalIgnoreCase))
.Where(path => File.ReadAllText(path).Contains(
"new ClientObjectTable",
StringComparison.Ordinal))
.ToArray();
string[] appDirectoryAllocators = appSources
.Where(path => File.ReadAllText(path).Contains(
"new RuntimeEntityDirectory",
StringComparison.Ordinal))
.ToArray();
string[] runtimeObjectAllocators = runtimeSources
.Where(path => File.ReadAllText(path).Contains(
"new ClientObjectTable",
StringComparison.Ordinal))
.Select(path => Path.GetFileName(path)!)
.ToArray();
string[] runtimeDirectoryAllocators = runtimeSources
.Where(path => File.ReadAllText(path).Contains(
"new RuntimeEntityDirectory",
StringComparison.Ordinal))
.Select(path => Path.GetFileName(path)!)
.ToArray();
Assert.Empty(appObjectAllocators);
Assert.Empty(appDirectoryAllocators);
Assert.Equal(
["RuntimeEntityObjectLifetime.cs"],
runtimeObjectAllocators);
Assert.Equal(
["RuntimeEntityObjectLifetime.cs"],
runtimeDirectoryAllocators);
}
[Fact] [Fact]
public void AppProjectionOwners_UseExactRuntimeKeysAndNoGuidDictionary() public void AppProjectionOwners_UseExactRuntimeKeysAndNoGuidDictionary()
{ {
@ -212,4 +276,18 @@ public sealed class RuntimeEntityOwnershipTests
field.FieldType.IsGenericType field.FieldType.IsGenericType
&& field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>) && field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
&& field.FieldType.GetGenericArguments()[0] == keyType; && field.FieldType.GetGenericArguments()[0] == keyType;
private static string FindRepositoryRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException(
$"Could not find AcDream.slnx above {AppContext.BaseDirectory}.");
}
} }

View file

@ -0,0 +1,251 @@
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
namespace AcDream.Runtime.Tests.Entities;
public sealed class RuntimeEntityObjectLifetimeTests
{
[Fact]
public void Owner_ConstructsOneExactDirectoryAndObjectTablePerInstance()
{
var first = new RuntimeEntityObjectLifetime();
var second = new RuntimeEntityObjectLifetime();
Assert.NotSame(first.Entities, second.Entities);
Assert.NotSame(first.Objects, second.Objects);
Assert.Same(first.Entities, first.Entities);
Assert.Same(first.Objects, first.Objects);
}
[Fact]
public void Owner_ConstructionLoadsNoPresentationOrBackendAssembly()
{
_ = new RuntimeEntityObjectLifetime();
string[] loaded = AppDomain.CurrentDomain
.GetAssemblies()
.Select(assembly => assembly.GetName().Name ?? string.Empty)
.ToArray();
Assert.DoesNotContain(loaded, name =>
name.StartsWith("AcDream.App", StringComparison.Ordinal)
|| name.StartsWith("AcDream.UI.", StringComparison.Ordinal)
|| name.StartsWith("Silk.NET", StringComparison.Ordinal)
|| name.StartsWith("OpenAL", StringComparison.Ordinal)
|| name.StartsWith("ImGui", StringComparison.Ordinal));
}
[Fact]
public void SeparateOwners_IsolateConflictingGuidsAndContents()
{
const uint guid = 0x70000001u;
var first = new RuntimeEntityObjectLifetime();
var second = new RuntimeEntityObjectLifetime();
RuntimeEntityRecord firstRecord = Accept(first, Spawn(guid, 1, "first"));
RuntimeEntityRecord secondRecord = Accept(second, Spawn(guid, 9, "second"));
Assert.True(first.ApplyAcceptedSpawn(
firstRecord,
firstRecord.CreateIntegrationVersion,
firstRecord.Snapshot,
replaceGeneration: false));
Assert.True(second.ApplyAcceptedSpawn(
secondRecord,
secondRecord.CreateIntegrationVersion,
secondRecord.Snapshot,
replaceGeneration: false));
Assert.Equal("first", first.Objects.Get(guid)!.Name);
Assert.Equal("second", second.Objects.Get(guid)!.Name);
Assert.Equal((ushort)1, firstRecord.Incarnation);
Assert.Equal((ushort)9, secondRecord.Incarnation);
}
[Fact]
public void AcceptedSpawn_RejectsWrongOrSupersededCanonicalIncarnation()
{
var lifetime = new RuntimeEntityObjectLifetime();
WorldSession.EntitySpawn spawn = Spawn(0x70000002u, 3, "current");
RuntimeEntityRecord canonical = Accept(lifetime, spawn);
var wrong = new RuntimeEntityObjectLifetime();
RuntimeEntityRecord wrongCanonical = Accept(wrong, spawn);
Assert.False(lifetime.ApplyAcceptedSpawn(
wrongCanonical,
wrongCanonical.CreateIntegrationVersion,
spawn,
replaceGeneration: false));
ulong expectedVersion = canonical.CreateIntegrationVersion;
lifetime.Entities.AdvanceCreateAuthority(canonical);
Assert.False(lifetime.ApplyAcceptedSpawn(
canonical,
expectedVersion,
spawn,
replaceGeneration: false));
Assert.Null(lifetime.Objects.Get(spawn.Guid));
}
[Fact]
public void AcceptedSpawn_RevalidatesAfterSynchronousObjectCallbacks()
{
var lifetime = new RuntimeEntityObjectLifetime();
WorldSession.EntitySpawn spawn = Spawn(0x70000003u, 4, "reentrant");
RuntimeEntityRecord canonical = Accept(lifetime, spawn);
ulong expectedVersion = canonical.CreateIntegrationVersion;
lifetime.Objects.ObjectAdded += _ =>
lifetime.Entities.AdvanceCreateAuthority(canonical);
Assert.False(lifetime.ApplyAcceptedSpawn(
canonical,
expectedVersion,
spawn,
replaceGeneration: false));
Assert.NotNull(lifetime.Objects.Get(spawn.Guid));
}
[Fact]
public void AcceptedDelete_RemovesOnlyMatchingLogicalGeneration()
{
var lifetime = new RuntimeEntityObjectLifetime();
WorldSession.EntitySpawn spawn = Spawn(0x70000004u, 6, "retained");
RuntimeEntityRecord canonical = Accept(lifetime, spawn);
Assert.True(lifetime.ApplyAcceptedSpawn(
canonical,
canonical.CreateIntegrationVersion,
spawn,
replaceGeneration: true));
Assert.False(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(spawn.Guid, 7),
isLocalPlayer: false,
out _));
Assert.NotNull(lifetime.Objects.Get(spawn.Guid));
Assert.True(lifetime.TryAcceptDelete(
new DeleteObject.Parsed(spawn.Guid, 6),
isLocalPlayer: false,
out RuntimeEntityDeleteAcceptance accepted));
lifetime.CompleteAcceptedDelete(accepted);
Assert.Null(lifetime.Objects.Get(spawn.Guid));
}
[Fact]
public void DeleteAcceptance_CannotCrossOwnersOrReplay()
{
var owner = new RuntimeEntityObjectLifetime();
var other = new RuntimeEntityObjectLifetime();
WorldSession.EntitySpawn spawn = Spawn(0x70000006u, 8, "token");
RuntimeEntityRecord canonical = Accept(owner, spawn);
Assert.True(owner.ApplyAcceptedSpawn(
canonical,
canonical.CreateIntegrationVersion,
spawn,
replaceGeneration: true));
Assert.True(owner.TryAcceptDelete(
new DeleteObject.Parsed(spawn.Guid, spawn.InstanceSequence),
isLocalPlayer: false,
out RuntimeEntityDeleteAcceptance acceptance));
Assert.Throws<InvalidOperationException>(
() => other.CompleteAcceptedDelete(acceptance));
owner.CompleteAcceptedDelete(acceptance);
Assert.Throws<InvalidOperationException>(
() => owner.CompleteAcceptedDelete(acceptance));
Assert.Null(owner.Objects.Get(spawn.Guid));
}
[Fact]
public void ClearObjects_DoesNotResetCanonicalEntityIdentity()
{
var lifetime = new RuntimeEntityObjectLifetime();
WorldSession.EntitySpawn spawn = Spawn(0x70000005u, 2, "clear");
RuntimeEntityRecord canonical = Accept(lifetime, spawn);
Assert.True(lifetime.ApplyAcceptedSpawn(
canonical,
canonical.CreateIntegrationVersion,
spawn,
replaceGeneration: false));
lifetime.ClearObjects();
Assert.Null(lifetime.Objects.Get(spawn.Guid));
Assert.True(lifetime.Entities.TryGetActive(spawn.Guid, out var retained));
Assert.Same(canonical, retained);
}
private static RuntimeEntityRecord Accept(
RuntimeEntityObjectLifetime lifetime,
WorldSession.EntitySpawn spawn)
{
InboundCreateResult accepted = lifetime.Entities.AcceptCreate(spawn);
return lifetime.Entities.AddActive(accepted.Snapshot);
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort instance,
string name)
{
var position = new CreateObject.ServerPosition(
0x0101FFFFu,
10f,
20f,
5f,
1f,
0f,
0f,
0f);
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: instance);
var physics = new PhysicsSpawnData(
RawState: 0x408u,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: 0x09000001u,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
0x02000001u,
Array.Empty<CreateObject.AnimPartChange>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.SubPaletteSwap>(),
null,
null,
name,
null,
null,
0x09000001u,
PhysicsState: 0x408u,
InstanceSequence: instance,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
}