acdream/tests/AcDream.App.Tests/Streaming/LandblockPlacementPublicationCompositionTests.cs

1031 lines
40 KiB
C#

using System.Collections.Immutable;
using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.Core.Items;
using AcDream.Core.Lighting;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.Plugins;
using AcDream.Core.Rendering;
using AcDream.Core.Rendering.Wb;
using AcDream.Core.Terrain;
using AcDream.Core.Vfx;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.World;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
namespace AcDream.App.Tests.Streaming;
public sealed class LandblockPlacementPublicationCompositionTests
{
private const uint LandblockId = 0x3031FFFFu;
private const uint CellId = 0x30310014u;
private const uint PlayerGuid = 0x5000000Au;
[Fact]
public void CollisionRefresh_WithMissingGraphicalBackend_PublishesBackendBeforeExactRestoreAck()
{
using var fixture = new Fixture();
RuntimePlacementProjectionToken seeded = fixture.SeedResident(PlayerGuid);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(seeded));
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
fixture.Pipeline.PublishLoaded(result);
Assert.True(fixture.Pipeline.HasPendingPublication(result));
Assert.Equal(0, fixture.Pipeline.Diagnostics.Physics.CompleteCount);
Assert.Equal(0, fixture.Pipeline.Diagnostics.Statics.CompleteCount);
Assert.True(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restore));
Assert.Equal(RuntimePlacementProjectionKind.Place, restore.Kind);
Assert.Equal(CellId, restore.Token.ExactCellId);
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.Equal(
1,
fixture.Lifetime.Physics.CaptureOwnership()
.CommittedCollisionPrefixMutationCount);
Assert.False(fixture.Controller.IsRenderNeighborhoodResident(
LandblockId,
nearRadius: 0,
farRadius: 0));
var serviceWindow = new GraphicalRemotePlacementServiceWindow(
fixture.State,
fixture.Controller.IsLandblockPresentationReady);
Assert.False(serviceWindow.IsWithinServiceWindow(CellId));
Assert.True(subscription.RetryPending());
Assert.False(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(out _));
fixture.Pipeline.ResumePublication(result);
Assert.False(fixture.Pipeline.HasPendingPublication(result));
Assert.Equal(1, fixture.Pipeline.Diagnostics.Physics.CompleteCount);
Assert.Equal(1, fixture.Pipeline.Diagnostics.Statics.CompleteCount);
Assert.True(fixture.Controller.IsRenderNeighborhoodResident(
LandblockId,
nearRadius: 0,
farRadius: 0));
Assert.True(serviceWindow.IsWithinServiceWindow(CellId));
LiveEntityRecord record = Assert.Single(fixture.Runtime.MaterializedRecords);
Assert.True(record.IsSpatiallyProjected);
Assert.Equal(CellId, record.FullCellId);
WorldEntity projected = Assert.Single(
fixture.State.Entities,
entity => entity.ServerGuid == PlayerGuid);
Assert.Same(
Assert.Single(fixture.Runtime.MaterializedRecords).WorldEntity,
projected);
}
[Fact]
public void CollisionRefresh_TwoResidentsRetainOrderedExactReceiptsAndCompleteOnce()
{
const uint remoteGuid = 0x5000000Bu;
using var fixture = new Fixture();
RuntimePlacementProjectionToken firstSeed =
fixture.SeedResident(PlayerGuid, isLocalPlayer: true);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(
firstSeed));
RuntimePlacementProjectionToken secondSeed =
fixture.SeedResident(remoteGuid, isLocalPlayer: false);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(
secondSeed));
var observer = new PlacementRecorder();
using IDisposable observerSubscription =
fixture.Lifetime.Events.SubscribePlacement(observer);
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
fixture.Pipeline.PublishLoaded(result);
RuntimePlacementProjectionSnapshot head = AssertHead(fixture);
RuntimeEntityKey[] orderedPlaces = observer.Placements
.Where(static placement =>
placement.Kind == RuntimePlacementProjectionKind.Place)
.Select(static placement => placement.Token.Entity)
.ToArray();
Assert.Equal(2, orderedPlaces.Length);
Assert.Equal(orderedPlaces[0], head.Token.Entity);
Assert.NotEqual(orderedPlaces[0], orderedPlaces[1]);
Assert.Equal(2, fixture.Lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.True(subscription.RetryPending());
Assert.False(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(out _));
fixture.Pipeline.ResumePublication(result);
Assert.False(fixture.Pipeline.HasPendingPublication(result));
Assert.Equal(1, fixture.Pipeline.Diagnostics.Physics.CompleteCount);
Assert.Equal(1, fixture.Pipeline.Diagnostics.Statics.CompleteCount);
Assert.Equal(
[PlayerGuid, remoteGuid],
fixture.State.Entities
.Where(static entity => entity.ServerGuid != 0u)
.Select(static entity => entity.ServerGuid)
.Order()
.ToArray());
RuntimePhysicsOwnershipSnapshot ownership =
fixture.Lifetime.Physics.CaptureOwnership();
Assert.Equal(0, ownership.PendingSetPositionHostAcknowledgementCount);
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void MeterExpiryAfterEngineTransfer_YieldsBeforeBackendAndNextFrameProgresses()
{
using var fixture = new Fixture();
RuntimePlacementProjectionToken seeded = fixture.SeedResident(PlayerGuid);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(seeded));
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
long timestamp = 0;
var observer = new PlacementRecorder(
onPlacement: placement =>
{
if (placement.Kind == RuntimePlacementProjectionKind.Place)
timestamp = 2;
});
using IDisposable observerSubscription =
fixture.Lifetime.Events.SubscribePlacement(observer);
StreamingWorkBudget budget = MeterBudget();
var firstMeter = new StreamingWorkMeter(
budget,
() => timestamp,
timestampFrequency: 1_000);
LandblockPublicationAdvance first = fixture.Pipeline.PublishLoaded(
result,
LandblockStreamResultCost.Estimate(result),
firstMeter,
ensureProgress: false);
firstMeter.FinishFrame();
Assert.False(first.Completed);
Assert.True(first.Progressed);
Assert.Equal(StreamingWorkLimit.Time, firstMeter.Snapshot.LastLimit);
Assert.False(fixture.State.IsLoaded(LandblockId));
RuntimePlacementProjectionSnapshot head = AssertHead(fixture);
Assert.Equal(RuntimePlacementProjectionKind.Place, head.Kind);
Assert.Equal(1, observer.Placements.Count(static placement =>
placement.Kind == RuntimePlacementProjectionKind.Place));
timestamp = 0;
var secondMeter = new StreamingWorkMeter(
budget,
() => timestamp,
timestampFrequency: 1_000);
LandblockPublicationAdvance second = fixture.Pipeline.ResumePublication(
result,
secondMeter,
ensureProgress: false);
secondMeter.FinishFrame();
Assert.False(second.Completed);
Assert.True(second.Progressed);
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.Equal(1, fixture.Pipeline.Diagnostics.Physics.BeginCount);
Assert.Equal(0, fixture.Pipeline.Diagnostics.Physics.CompleteCount);
Assert.Equal(1, observer.Placements.Count(static placement =>
placement.Kind == RuntimePlacementProjectionKind.Place));
Assert.True(subscription.RetryPending());
var thirdMeter = new StreamingWorkMeter(budget);
LandblockPublicationAdvance third = fixture.Pipeline.ResumePublication(
result,
thirdMeter,
ensureProgress: true);
thirdMeter.FinishFrame();
Assert.True(third.Completed);
Assert.Equal(1, fixture.Pipeline.Diagnostics.Physics.CompleteCount);
Assert.Equal(1, fixture.Pipeline.Diagnostics.Statics.CompleteCount);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public void ControllerResetOrRecenter_AfterEngineTransferRetriesFailedBackendThenRetires(
bool recenter,
bool activateBeforeRequest = false)
{
var projection = new FailFirstStaticProjectionSink();
using var fixture = new Fixture(projection);
RuntimePlacementProjectionToken seeded = fixture.SeedResident(PlayerGuid);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(seeded));
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
long timestamp = 0;
var observer = new PlacementRecorder(
onPlacement: placement =>
{
if (placement.Kind == RuntimePlacementProjectionKind.Place)
timestamp = 2;
});
using IDisposable observerSubscription =
fixture.Lifetime.Events.SubscribePlacement(observer);
var firstMeter = new StreamingWorkMeter(
MeterBudget(),
() => timestamp,
timestampFrequency: 1_000);
LandblockPublicationAdvance first = fixture.Pipeline.PublishLoaded(
result,
LandblockStreamResultCost.Estimate(result),
firstMeter,
ensureProgress: false);
firstMeter.FinishFrame();
Assert.False(first.Completed);
Assert.False(fixture.State.IsLoaded(LandblockId));
Assert.Equal(0, projection.Attempts);
if (activateBeforeRequest)
{
Assert.Throws<InvalidOperationException>(() =>
fixture.Pipeline.ResumePublication(result));
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.Equal(1, projection.Attempts);
Assert.False(fixture.Controller.IsLandblockPresentationReady(
LandblockId));
}
if (recenter)
fixture.Controller.BeginOriginRecenter();
else
fixture.Controller.ForceReloadWindow();
fixture.Controller.Tick(0x30, 0x31);
Assert.True(fixture.Pipeline.HasPendingPublication(result));
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.Equal(activateBeforeRequest ? 2 : 1, projection.Attempts);
Assert.False(fixture.Controller.IsLandblockPresentationReady(LandblockId));
RuntimePhysicsOwnershipSnapshot afterFailure =
fixture.Lifetime.Physics.CaptureOwnership();
Assert.Equal(1, afterFailure.CommittedCollisionPrefixMutationCount);
Assert.Equal(1, afterFailure.PendingSetPositionHostAcknowledgementCount);
fixture.Controller.Tick(0x30, 0x31);
Assert.Equal(2, projection.Attempts);
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.False(fixture.Controller.IsLandblockPresentationReady(LandblockId));
Assert.True(subscription.RetryPending());
bool recenterCommitted = false;
for (int frame = 0;
frame < 16
&& (fixture.Pipeline.HasPendingPublication(result)
|| fixture.State.IsLoaded(LandblockId)
|| fixture.Pipeline.PendingRetirementCount != 0);
frame++)
{
fixture.Controller.Tick(0x30, 0x31);
if (recenter
&& !recenterCommitted
&& fixture.Controller.IsOriginRecenterRetirementComplete())
{
Assert.True(fixture.Controller.TryCommitOriginRecenter(
destinationX: 0x32,
destinationY: 0x31,
isSealedDungeon: false));
recenterCommitted = true;
}
}
Assert.False(fixture.Pipeline.HasPendingPublication(result));
Assert.False(fixture.State.IsLoaded(LandblockId));
Assert.Equal(recenter, recenterCommitted);
Assert.Equal(2, projection.Attempts);
Assert.Equal(0, fixture.Meshes.ReferenceCount);
RuntimePhysicsOwnershipSnapshot completed =
fixture.Lifetime.Physics.CaptureOwnership();
Assert.Equal(0, completed.CollisionPrefixMutationCount);
Assert.Equal(0, completed.CollisionAdmissionCount);
Assert.Equal(0, completed.PendingSetPositionHostAcknowledgementCount);
}
[Fact]
public void PresentationFence_IsExactLandblockAndStopsBeforeLiveRecovery()
{
const uint unrelated = 0x4040FFFFu;
using (var fixture = new Fixture())
{
RuntimePlacementProjectionToken seeded =
fixture.SeedResident(PlayerGuid);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(
seeded));
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
fixture.Pipeline.PublishLoaded(result);
fixture.State.AddLandblock(new LoadedLandblock(
unrelated,
new LandBlock(),
Array.Empty<WorldEntity>()));
var window = new GraphicalRemotePlacementServiceWindow(
fixture.State,
fixture.Controller.IsLandblockPresentationReady);
Assert.False(window.IsWithinServiceWindow(CellId));
Assert.True(window.IsWithinServiceWindow(unrelated));
}
bool failRecovery = true;
using (var fixture = new Fixture(onLandblockLoaded: _ =>
{
if (failRecovery)
{
failRecovery = false;
throw new InvalidOperationException("injected recovery failure");
}
}))
{
LandblockStreamResult.Loaded result = fixture.Result();
Assert.Throws<InvalidOperationException>(() =>
fixture.Pipeline.PublishLoaded(result));
Assert.True(fixture.Pipeline.HasPendingPublication(result));
Assert.True(fixture.Controller.IsLandblockPresentationReady(
LandblockId));
Assert.True(fixture.Controller.IsRenderNeighborhoodResident(
LandblockId,
nearRadius: 0,
farRadius: 0));
fixture.Pipeline.ResumePublication(result);
Assert.False(fixture.Pipeline.HasPendingPublication(result));
}
}
[Fact]
public void CollisionRefresh_OutdoorSeamRecordsActualPrefixAndBackend()
{
const uint adjacentLandblock = 0x3131FFFFu;
const uint seamCell = 0x3031003Cu;
var seamPosition = new Vector3(191.5f, 84f, 0f);
using var fixture = new Fixture();
fixture.AddCollisionLandblock(adjacentLandblock);
RuntimePlacementProjectionToken seeded = fixture.SeedResident(
PlayerGuid,
isLocalPlayer: true,
seamCell,
seamPosition);
Assert.True(fixture.Lifetime.Physics.SetPosition.AcknowledgeProjection(
seeded));
using var subscription = fixture.Subscribe(PlayerGuid);
LandblockStreamResult.Loaded result = fixture.Result();
fixture.Pipeline.PublishLoaded(result);
RuntimePlacementProjectionSnapshot restore = AssertHead(fixture);
uint actualPrefix = restore.Token.ExactCellId & 0xFFFF0000u;
Console.WriteLine(
$"#474 seam restoreCell=0x{restore.Token.ExactCellId:X8} "
+ $"actualPrefix=0x{actualPrefix:X8} "
+ $"targetBackend={fixture.State.IsLoaded(LandblockId)} "
+ $"adjacentBackend={fixture.State.IsLoaded(adjacentLandblock)}");
Assert.Contains(actualPrefix, new[]
{
LandblockId & 0xFFFF0000u,
adjacentLandblock & 0xFFFF0000u,
});
Assert.True(fixture.State.IsLoaded(LandblockId));
Assert.False(fixture.State.IsLoaded(adjacentLandblock));
Assert.True(
fixture.State.IsLoaded(actualPrefix | 0xFFFFu),
$"actual restore prefix 0x{actualPrefix:X8} lacked a backend");
Assert.True(subscription.RetryPending());
fixture.Pipeline.ResumePublication(result);
Assert.False(fixture.Pipeline.HasPendingPublication(result));
Assert.Equal(restore.Token.ExactCellId,
Assert.Single(fixture.Runtime.MaterializedRecords).FullCellId);
}
[Fact]
public void DebtFreeFullRetirementsDrainMultipleRealOwnersInOneBudgetedFrame()
{
uint[] landblockIds =
[
0x3031FFFFu,
0x3032FFFFu,
0x3033FFFFu,
];
var removalOrder = new List<uint>();
using var fixture = new Fixture(removeTerrain: removalOrder.Add);
foreach (uint landblockId in landblockIds)
{
fixture.AddDebtFreeLandblock(landblockId);
fixture.Pipeline.EnqueueFullRetirement(landblockId);
}
Assert.Equal(landblockIds.Length, fixture.Pipeline.PendingRetirementCount);
var meter = new StreamingWorkMeter(RetirementBudget(
maxEntityOperations: 64,
maxGlRetireOperations: 16));
fixture.Pipeline.AdvanceRetirements(meter);
meter.FinishFrame();
Assert.Equal(0, fixture.Pipeline.PendingRetirementCount);
Assert.Equal(landblockIds.Length,
fixture.PhysicsPublisher.Diagnostics.FullRemovalCount);
Assert.Equal(landblockIds, removalOrder);
Assert.Equal(removalOrder.Count, removalOrder.Distinct().Count());
Assert.Equal(33, meter.Snapshot.Operations);
Assert.Equal(33, meter.Snapshot.CompletedOperations);
Assert.Equal(0, meter.Snapshot.YieldCount);
Assert.All(landblockIds, landblockId =>
{
Assert.False(fixture.State.IsLoaded(landblockId));
Assert.False(fixture.Lifetime.Physics.Engine
.IsLandblockTerrainResident(landblockId));
Assert.False(fixture.Pipeline.IsRetirementPending(landblockId));
});
RuntimePhysicsOwnershipSnapshot ownership =
fixture.Lifetime.Physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionPrefixQuiescenceCount);
Assert.Equal(0, ownership.PendingCollisionPrefixProjectionCount);
}
[Fact]
public void DebtFreeFullRetirementsRespectSmallMeterAndResumeExactlyOnce()
{
uint[] landblockIds =
[
0x3031FFFFu,
0x3032FFFFu,
0x3033FFFFu,
];
using var fixture = new Fixture();
foreach (uint landblockId in landblockIds)
{
fixture.AddDebtFreeLandblock(landblockId);
fixture.Pipeline.EnqueueFullRetirement(landblockId);
}
int frames = 0;
while (fixture.Pipeline.PendingRetirementCount != 0 && frames++ < 8)
{
var meter = new StreamingWorkMeter(RetirementBudget(
maxEntityOperations: 7,
maxGlRetireOperations: 4));
fixture.Pipeline.AdvanceRetirements(meter);
meter.FinishFrame();
Assert.InRange(meter.Snapshot.Used.EntityOperations, 1, 7);
Assert.InRange(meter.Snapshot.Used.GlRetireOperations, 0, 4);
Assert.Equal(meter.Snapshot.Operations,
meter.Snapshot.CompletedOperations);
}
Assert.Equal(5, frames);
Assert.Equal(0, fixture.Pipeline.PendingRetirementCount);
Assert.Equal(landblockIds.Length,
fixture.PhysicsPublisher.Diagnostics.FullRemovalCount);
Assert.All(landblockIds, landblockId =>
Assert.False(fixture.Lifetime.Physics.Engine
.IsLandblockTerrainResident(landblockId)));
}
[Fact]
public void HeldAndThrowingPlaceKeepsExactRetirementFifoHeadUntilProductionSinkAck()
{
uint[] retiringLandblockIds =
[
0x3032FFFFu,
0x3033FFFFu,
];
var removalOrder = new List<uint>();
using var fixture = new Fixture(removeTerrain: removalOrder.Add);
fixture.AddDebtFreeLandblock(LandblockId);
RuntimePlacementProjectionToken seeded = fixture.SeedResident(PlayerGuid);
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(seeded));
using RuntimePlacementProjectionSubscription subscription =
fixture.SubscribeWithThrowThenHold(PlayerGuid, out var sink);
RuntimePlacementProjectionToken exact = fixture.MoveResident(PlayerGuid);
Assert.Equal(exact, Assert.Single(sink.Attempts));
Assert.Equal(RuntimePlacementProjectionKind.Place,
AssertHead(fixture).Kind);
foreach (uint landblockId in retiringLandblockIds)
{
fixture.AddDebtFreeLandblock(landblockId);
fixture.Pipeline.EnqueueFullRetirement(landblockId);
}
var blockedMeter = new StreamingWorkMeter(RetirementBudget(
maxEntityOperations: 64,
maxGlRetireOperations: 16));
fixture.Pipeline.AdvanceRetirements(blockedMeter);
blockedMeter.FinishFrame();
Assert.Equal(2, fixture.Pipeline.PendingRetirementCount);
Assert.Equal(0,
fixture.PhysicsPublisher.Diagnostics.FullRemovalCount);
Assert.Empty(removalOrder);
Assert.True(fixture.Lifetime.Physics.Engine
.IsLandblockTerrainResident(retiringLandblockIds[0]));
Assert.True(subscription.RetryPending());
Assert.Equal([exact, exact], sink.Attempts);
Assert.Equal(2, fixture.Pipeline.PendingRetirementCount);
Assert.True(subscription.RetryPending());
Assert.Equal([exact, exact, exact], sink.Attempts);
Assert.False(fixture.Lifetime.Physics.SetPosition
.TryPeekProjection(out _));
var completedMeter = new StreamingWorkMeter(RetirementBudget(
maxEntityOperations: 64,
maxGlRetireOperations: 16));
fixture.Pipeline.AdvanceRetirements(completedMeter);
completedMeter.FinishFrame();
Assert.Equal(0, fixture.Pipeline.PendingRetirementCount);
Assert.Equal(2,
fixture.PhysicsPublisher.Diagnostics.FullRemovalCount);
Assert.Equal(
retiringLandblockIds,
removalOrder);
Assert.Equal(removalOrder.Count, removalOrder.Distinct().Count());
}
private static RuntimePlacementProjectionSnapshot AssertHead(Fixture fixture)
{
Assert.True(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot head));
return head;
}
private static StreamingWorkBudget MeterBudget() => new(
TimeSpan.FromMilliseconds(1),
maxCompletionAdmissions: 64,
maxAdoptedCpuBytes: 1_000_000,
maxEntityOperations: 32,
maxGpuUploadBytes: 1_000_000,
maxGlRetireOperations: 64,
destinationReserveFraction: 0.75f);
private static StreamingWorkBudget RetirementBudget(
int maxEntityOperations,
int maxGlRetireOperations) => new(
TimeSpan.FromSeconds(10),
maxCompletionAdmissions: 64,
maxAdoptedCpuBytes: 1_000_000,
maxEntityOperations,
maxGpuUploadBytes: 1_000_000,
maxGlRetireOperations,
destinationReserveFraction: 0.75f);
private sealed class Fixture : IDisposable
{
internal Fixture(
IRenderStaticProjectionJournalSink? staticProjectionSink = null,
Action<uint>? onLandblockLoaded = null,
Action<uint>? removeTerrain = null)
{
Lifetime = new RuntimeEntityObjectLifetime(new PhysicsDataCache());
Lifetime.BindEventContext(
static () => new RuntimeGenerationToken(1UL),
static () => 1UL);
RuntimePhysicsState physics = Lifetime.Physics;
physics.SetPosition.BeginCollisionGeneration(LandblockId, 1UL);
physics.Engine.AddLandblock(
LandblockId,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
physics.SetPosition.CommitCollisionGeneration(
LandblockId,
1UL,
ready: true);
physics.ObserveLocalWorldFrame(CellId, teleportAdvanced: false);
Meshes = new CountingMeshAdapter();
State = new GpuWorldState(new LandblockSpawnAdapter(Meshes));
Runtime = new LiveEntityRuntime(State, new NoopResources(), Lifetime);
var render = new LandblockRenderPublisher(
static (_, _, _) => { },
removeTerrain ?? (static _ => { }),
new CellVisibility(),
State);
PhysicsPublisher = new LandblockPhysicsPublisher(
physics,
new float[256]);
var lighting = new LightingHookSink(
new LightManager(),
new NullPoseSource());
var translucency = new TranslucencyFadeManager();
var staticPublisher = new LandblockStaticPresentationPublisher(
lighting,
translucency,
new WorldGameState(),
new WorldEvents());
var retirement = new LandblockPresentationRetirementOwner(
render,
PhysicsPublisher,
staticPublisher,
lighting,
translucency);
Pipeline = new LandblockPresentationPipeline(
render,
PhysicsPublisher,
staticPublisher,
State,
retirement,
onLandblockLoaded,
ensureEnvCellMeshes: null,
staticProjectionSink: staticProjectionSink);
Controller = new StreamingController(
enqueueLoad: static (_, _, _) => { },
enqueueUnload: static (_, _) => { },
drainCompletions: static _ =>
Array.Empty<LandblockStreamResult>(),
State,
nearRadius: 0,
farRadius: 0,
Pipeline);
}
internal RuntimeEntityObjectLifetime Lifetime { get; }
internal CountingMeshAdapter Meshes { get; }
internal GpuWorldState State { get; }
internal LiveEntityRuntime Runtime { get; }
internal LandblockPhysicsPublisher PhysicsPublisher { get; }
internal LandblockPresentationPipeline Pipeline { get; }
internal StreamingController Controller { get; }
internal void AddCollisionLandblock(uint landblockId)
{
RuntimePhysicsState physics = Lifetime.Physics;
physics.SetPosition.BeginCollisionGeneration(landblockId, 1UL);
physics.Engine.AddLandblock(
landblockId,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 192f,
worldOffsetY: 0f);
physics.SetPosition.CommitCollisionGeneration(
landblockId,
1UL,
ready: true);
}
internal void AddDebtFreeLandblock(uint landblockId)
{
if (!Lifetime.Physics.Engine
.IsLandblockTerrainResident(landblockId))
{
AddCollisionLandblock(landblockId);
}
if (!State.IsLoaded(landblockId))
{
State.AddLandblock(new LoadedLandblock(
landblockId,
new LandBlock
{
Terrain = new TerrainInfo[81],
Height = new byte[81],
},
Array.Empty<WorldEntity>()));
}
}
internal RuntimePlacementProjectionToken SeedResident(
uint guid,
bool isLocalPlayer = true,
uint cellId = CellId,
Vector3? position = null)
{
Vector3 resolvedPosition = position ?? new Vector3(60f, 84f, 0f);
LiveEntityRecord record = Runtime.RegisterAndMaterializeProjection(
Spawn(guid, cellId, resolvedPosition),
isLocalPlayer: isLocalPlayer);
Assert.False(Runtime.HasActiveInitialCreateResidence(record.Canonical));
var body = new PhysicsBody
{
Position = resolvedPosition,
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cellId, body.Position, body.Position);
Lifetime.Entities.SetFinalPhysicsState(
record.Canonical,
PhysicsStateFlags.Gravity);
Lifetime.Entities.SetFullCell(
record.Canonical,
cellId,
(cellId & 0xFFFF0000u) | 0xFFFFu);
Lifetime.Entities.SetPhysicsBody(record.Canonical, body);
record.Canonical.ObjectClock.Activate();
Lifetime.Physics.AcknowledgeSpatialProjection(
record.Canonical,
spatial: true);
var request = new PhysicsSetPositionRequest(
body.Position,
body.Orientation,
cellId,
body.CellPosition.Frame.Origin,
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
1f,
0.4f,
0.4f,
Flags: PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
CurrentCellId: cellId);
RuntimeSetPositionOutcome placed = Lifetime.Physics.SetPosition.Apply(
record.Canonical,
record.Canonical.PositionAuthorityVersion,
new RuntimeSetPositionCommand(
request,
RuntimeSetPositionOperationKind.LocalAuthoritative,
1d,
record.Canonical.VelocityAuthorityVersion));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
placed.Status);
return placed.Projection;
}
internal RuntimePlacementProjectionToken MoveResident(uint guid)
{
Assert.True(Lifetime.Entities.TryGetActive(
guid,
out RuntimeEntityRecord record));
PhysicsBody body = Assert.IsType<PhysicsBody>(record.PhysicsBody);
var next = body.Position + new Vector3(0.25f, 0f, 0f);
var request = new PhysicsSetPositionRequest(
next,
body.Orientation,
record.FullCellId,
next,
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
1f,
0.4f,
0.4f,
Flags: PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
CurrentCellId: record.FullCellId);
RuntimeSetPositionOutcome placed = Lifetime.Physics.SetPosition.Apply(
record,
record.PositionAuthorityVersion,
new RuntimeSetPositionCommand(
request,
RuntimeSetPositionOperationKind.LocalAuthoritative,
2d,
record.VelocityAuthorityVersion));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
placed.Status);
return placed.Projection;
}
internal RuntimePlacementProjectionSubscription Subscribe(uint guid)
{
return new RuntimePlacementProjectionSubscription(
Lifetime.Placements,
static () => new RuntimeGenerationToken(1UL),
CreatePlacementSink(guid));
}
internal RuntimePlacementProjectionSubscription SubscribeWithThrowThenHold(
uint guid,
out ThrowThenHoldPlacementSink sink)
{
sink = new ThrowThenHoldPlacementSink(CreatePlacementSink(guid));
return new RuntimePlacementProjectionSubscription(
Lifetime.Placements,
static () => new RuntimeGenerationToken(1UL),
sink);
}
private IRuntimePlacementProjectionSink CreatePlacementSink(uint guid)
{
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
var origin = new LiveWorldOriginState();
origin.SetPlaceholder(0, 0);
return new RuntimePlacementPresentationSink(
Runtime,
new RuntimeWorldTransitState(),
new WorldGameState(),
new WorldEvents(),
new EntityEffectPoseRegistry(),
new LocalPlayerShadowSynchronizer(
Lifetime.Physics.Engine,
Runtime,
identity,
origin,
new LocalPlayerShadowState()),
() => guid,
static _ => { });
}
internal LandblockStreamResult.Loaded Result()
{
var landblock = new LoadedLandblock(
LandblockId,
new LandBlock
{
Terrain = new TerrainInfo[81],
Height = new byte[81],
},
Array.Empty<WorldEntity>(),
PhysicsDatBundle.Empty);
var shell = new EnvCellShellPlacement(
CellId: 0x30310100u,
GeometryId: 0x2_0000_0474UL,
EnvironmentId: 0x0D000001u,
CellStructure: 1,
Surfaces: ImmutableArray<ushort>.Empty,
WorldPosition: Vector3.Zero,
Rotation: Quaternion.Identity,
Transform: Matrix4x4.Identity,
LocalBounds: new WbBoundingBox(Vector3.Zero, Vector3.One),
WorldBounds: new WbBoundingBox(Vector3.Zero, Vector3.One));
var build = new LandblockBuild(
landblock,
new EnvCellLandblockBuild(
LandblockId,
Array.Empty<LoadedCell>(),
[shell]),
Origin: new LandblockBuildOrigin(0x30, 0x31));
return new LandblockStreamResult.Loaded(
LandblockId,
LandblockStreamTier.Near,
build,
new LandblockMeshData(
Array.Empty<TerrainVertex>(),
Array.Empty<uint>()));
}
public void Dispose()
{
Runtime.Clear();
Lifetime.Dispose();
}
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
uint cellId = CellId,
Vector3? worldPosition = null)
{
Vector3 positionValue = worldPosition ?? new Vector3(60f, 84f, 0f);
var position = new CreateObject.ServerPosition(
cellId,
positionValue.X,
positionValue.Y,
positionValue.Z,
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: 1);
var physics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.Gravity,
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,
[],
[],
[],
null,
null,
"#474 resident",
(uint)ItemType.Creature,
null,
0x09000001u,
PhysicsState: (uint)PhysicsStateFlags.Gravity,
InstanceSequence: 1,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: physics);
}
private sealed class NoopResources : ILiveEntityResourceLifecycle
{
public void Register(WorldEntity entity) { }
public void Unregister(WorldEntity entity) { }
}
private sealed class PlacementRecorder(
Action<RuntimePlacementProjectionSnapshot>? onPlacement = null)
: IRuntimePlacementObserver
{
internal List<RuntimePlacementProjectionSnapshot> Placements { get; } = [];
public void OnPlacement(in RuntimePlacementDelta delta)
{
Placements.Add(delta.Placement);
onPlacement?.Invoke(delta.Placement);
}
}
internal sealed class ThrowThenHoldPlacementSink(
IRuntimePlacementProjectionSink inner)
: IRuntimePlacementProjectionSink
{
internal List<RuntimePlacementProjectionToken> Attempts { get; } = [];
public bool TryApply(in RuntimePlacementProjectionSnapshot projection)
{
Attempts.Add(projection.Token);
return Attempts.Count switch
{
1 => throw new InvalidOperationException(
"injected retirement sink failure"),
2 => false,
_ => inner.TryApply(projection),
};
}
}
private sealed class FailFirstStaticProjectionSink
: IRenderStaticProjectionJournalSink
{
internal int Attempts { get; private set; }
public void Reconcile(
LandblockBuild build,
GpuLandblockSpatialPublication publication)
{
Attempts++;
if (Attempts == 1)
throw new InvalidOperationException("injected projection failure");
}
public void Retire(GpuLandblockRetirement retirement) { }
}
internal sealed class CountingMeshAdapter : IWbMeshAdapter
{
internal int ReferenceCount { get; private set; }
public void IncrementRefCount(ulong id) => ReferenceCount++;
public void PinPreparedRenderData(ulong id) => ReferenceCount++;
public void DecrementRefCount(ulong id) => ReferenceCount--;
}
private sealed class NullPoseSource : IEntityEffectPoseSource
{
public bool TryGetRootPose(uint localEntityId, out Matrix4x4 rootWorld)
{
rootWorld = default;
return false;
}
public bool TryGetPartPose(
uint localEntityId,
int partIndex,
out Matrix4x4 partLocal)
{
partLocal = default;
return false;
}
}
}