fix(streaming): preserve portal destination ownership
Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
This commit is contained in:
parent
2c848d4167
commit
823936ec31
57 changed files with 2551 additions and 324 deletions
|
|
@ -254,6 +254,119 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Assert.Empty(reloaded.Entities);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OriginRecenterSpatialSwap_RetainsLiveIdentityAndReturnsExactReceipts()
|
||||
{
|
||||
const uint firstLandblock = 0x1010FFFFu;
|
||||
const uint secondLandblock = 0x1011FFFFu;
|
||||
const uint pendingLandblock = 0x2020FFFFu;
|
||||
const uint playerGuid = 0x50000001u;
|
||||
var firstStatic = Entity(100u, 0u);
|
||||
var secondStatic = Entity(101u, 0u);
|
||||
var remote = Entity(1u, 0x70000001u);
|
||||
var pending = Entity(2u, 0x70000002u);
|
||||
var player = Entity(3u, playerGuid);
|
||||
var state = new GpuWorldState();
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
firstLandblock,
|
||||
new LandBlock(),
|
||||
[firstStatic]));
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
secondLandblock,
|
||||
new LandBlock(),
|
||||
[secondStatic]));
|
||||
state.PlaceLiveEntityProjection(firstLandblock, remote);
|
||||
state.MarkPersistent(playerGuid);
|
||||
state.PlaceLiveEntityProjection(secondLandblock, player);
|
||||
state.PlaceLiveEntityProjection(pendingLandblock, pending);
|
||||
var edges = new List<(uint Guid, bool Visible)>();
|
||||
state.LiveProjectionVisibilityChanged += (guid, visible) =>
|
||||
edges.Add((guid, visible));
|
||||
|
||||
GpuWorldRecenterRetirement result =
|
||||
state.DetachAllForOriginRecenter();
|
||||
|
||||
Assert.Null(result.ObserverFailure);
|
||||
Assert.Equal(
|
||||
[firstLandblock, secondLandblock, pendingLandblock],
|
||||
result.Landblocks.Select(retirement => retirement.LandblockId));
|
||||
Assert.Contains(
|
||||
result.Landblocks.Single(
|
||||
retirement => retirement.LandblockId == firstLandblock).Entities,
|
||||
entity => ReferenceEquals(entity, firstStatic));
|
||||
Assert.Contains(
|
||||
result.Landblocks.Single(
|
||||
retirement => retirement.LandblockId == firstLandblock).Entities,
|
||||
entity => ReferenceEquals(entity, remote));
|
||||
Assert.Contains(
|
||||
result.Landblocks.Single(
|
||||
retirement => retirement.LandblockId == secondLandblock).Entities,
|
||||
entity => ReferenceEquals(entity, secondStatic));
|
||||
Assert.Contains(
|
||||
result.Landblocks.Single(
|
||||
retirement => retirement.LandblockId == secondLandblock).Entities,
|
||||
entity => ReferenceEquals(entity, player));
|
||||
Assert.Same(
|
||||
pending,
|
||||
Assert.Single(result.Landblocks.Single(
|
||||
retirement => retirement.LandblockId == pendingLandblock).Entities));
|
||||
|
||||
Assert.Empty(state.LoadedLandblockIds);
|
||||
Assert.Empty(state.Entities);
|
||||
Assert.Equal(2, state.PendingLiveEntityCount);
|
||||
Assert.Same(player, Assert.Single(state.DrainRescued()));
|
||||
Assert.Equal(
|
||||
[(remote.ServerGuid, false), (player.ServerGuid, false)],
|
||||
edges);
|
||||
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
firstLandblock,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
Assert.Same(remote, Assert.Single(state.Entities));
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
pendingLandblock,
|
||||
new LandBlock(),
|
||||
Array.Empty<WorldEntity>()));
|
||||
Assert.Contains(state.Entities, entity => ReferenceEquals(entity, pending));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OriginRecenterSpatialSwap_PreservesCurrentLiveBucketOrder()
|
||||
{
|
||||
const uint landblock = 0x3030FFFFu;
|
||||
var staticEntity = Entity(100u, 0u);
|
||||
var removed = Entity(1u, 0x70000001u);
|
||||
var middle = Entity(2u, 0x70000002u);
|
||||
var movedTail = Entity(3u, 0x70000003u);
|
||||
var state = new GpuWorldState();
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
landblock,
|
||||
new LandBlock(),
|
||||
[staticEntity]));
|
||||
state.PlaceLiveEntityProjection(landblock, removed);
|
||||
state.PlaceLiveEntityProjection(landblock, middle);
|
||||
state.PlaceLiveEntityProjection(landblock, movedTail);
|
||||
|
||||
// Removing the first live projection swap-moves the tail into its
|
||||
// bucket slot. Dictionary insertion order is now intentionally
|
||||
// different from the canonical bucket order.
|
||||
state.RemoveLiveEntityProjection(removed);
|
||||
Assert.Equal(
|
||||
[staticEntity, movedTail, middle],
|
||||
state.Entities);
|
||||
|
||||
state.DetachAllForOriginRecenter();
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
landblock,
|
||||
new LandBlock(),
|
||||
[staticEntity]));
|
||||
|
||||
Assert.Equal(
|
||||
[staticEntity, movedTail, middle],
|
||||
state.Entities);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchedVisibilityObserver_SeesCommittedFlatAndResidentViews()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue