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
|
|
@ -264,6 +264,29 @@ public sealed class CompositeTextureArrayCachePolicyTests
|
|||
Assert.Equal(2, backend.Uploads.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DestinationReveal_RaisesOnlyItemCountAndRestoresNormalProfile()
|
||||
{
|
||||
var backend = new FakeBackend(maximumLayers: 64);
|
||||
using var cache = new CompositeTextureArrayCache(
|
||||
backend,
|
||||
new DeferredRetirementQueue(),
|
||||
unownedBudgetBytes: long.MaxValue,
|
||||
physicalBudgetBytes: long.MaxValue,
|
||||
maximumUploadsPerFrame: 2,
|
||||
maximumUploadBytesPerFrame: long.MaxValue);
|
||||
|
||||
cache.BeginFrame(destinationRevealUploadPriority: true);
|
||||
Assert.True(cache.TryAddAndAcquire(1, Key(1), Texture(1, 1), out _));
|
||||
Assert.True(cache.TryAddAndAcquire(1, Key(2), Texture(1, 1), out _));
|
||||
Assert.True(cache.TryAddAndAcquire(1, Key(3), Texture(1, 1), out _));
|
||||
|
||||
cache.BeginFrame();
|
||||
Assert.True(cache.TryAddAndAcquire(1, Key(4), Texture(1, 1), out _));
|
||||
Assert.True(cache.TryAddAndAcquire(1, Key(5), Texture(1, 1), out _));
|
||||
Assert.False(cache.TryAddAndAcquire(1, Key(6), Texture(1, 1), out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OversizedTextureIsAllowedAsOnlyUploadToGuaranteeProgress()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,37 +59,100 @@ public sealed class PaperdollFramePresenterTests
|
|||
presenter.Render();
|
||||
|
||||
Assert.Equal(2, factory.BuildCount);
|
||||
Assert.Equal(2, renderer.Dolls.Count);
|
||||
Assert.Single(renderer.Dolls);
|
||||
Assert.Equal(3, renderer.RenderCount);
|
||||
Assert.False(presenter.IsDirty);
|
||||
}
|
||||
|
||||
private static WorldEntity CreateDoll() => new()
|
||||
[Fact]
|
||||
public void EquivalentPortalRefresh_KeepsPrivateDollAndTextureOwner()
|
||||
{
|
||||
WorldEntity first = CreateDoll();
|
||||
WorldEntity repeated = CreateDoll();
|
||||
var renderer = new RecordingRenderer();
|
||||
var view = new RecordingView();
|
||||
var factory = new RecordingFactory { Doll = first };
|
||||
var presenter = new PaperdollFramePresenter(renderer, view, factory);
|
||||
|
||||
presenter.Render();
|
||||
factory.Doll = repeated;
|
||||
presenter.MarkDirty();
|
||||
presenter.Render();
|
||||
|
||||
Assert.Equal(2, factory.BuildCount);
|
||||
Assert.Equal([first], renderer.Dolls);
|
||||
Assert.Equal(2, renderer.RenderCount);
|
||||
Assert.False(presenter.IsDirty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangedScale_ReplacesPrivateDoll()
|
||||
{
|
||||
WorldEntity first = CreateDoll();
|
||||
WorldEntity changed = CreateDoll(scale: 1.25f);
|
||||
var renderer = new RecordingRenderer();
|
||||
var view = new RecordingView();
|
||||
var factory = new RecordingFactory { Doll = first };
|
||||
var presenter = new PaperdollFramePresenter(renderer, view, factory);
|
||||
|
||||
presenter.Render();
|
||||
factory.Doll = changed;
|
||||
presenter.MarkDirty();
|
||||
presenter.Render();
|
||||
|
||||
Assert.Equal([first, changed], renderer.Dolls);
|
||||
}
|
||||
|
||||
private static WorldEntity CreateDoll(float scale = 1f) => new()
|
||||
{
|
||||
Id = 42u,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
Scale = scale,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void MissingPlayer_ClearsOldDollPublishesEmptyTextureAndRetries()
|
||||
public void TemporaryMissingPlayer_KeepsSuccessfulDollAndRetriesRedress()
|
||||
{
|
||||
var renderer = new RecordingRenderer { TextureHandle = 0u };
|
||||
var firstDoll = CreateDoll();
|
||||
var renderer = new RecordingRenderer { TextureHandle = 81u };
|
||||
var view = new RecordingView();
|
||||
var factory = new RecordingFactory { CanBuild = false };
|
||||
var factory = new RecordingFactory { Doll = firstDoll };
|
||||
var presenter = new PaperdollFramePresenter(renderer, view, factory);
|
||||
|
||||
presenter.Render();
|
||||
factory.CanBuild = false;
|
||||
presenter.MarkDirty();
|
||||
presenter.Render();
|
||||
presenter.Render();
|
||||
|
||||
Assert.True(presenter.IsDirty);
|
||||
Assert.Equal(3, factory.BuildCount);
|
||||
Assert.Equal([firstDoll], renderer.Dolls);
|
||||
Assert.Equal(3, renderer.RenderCount);
|
||||
Assert.Equal([81u, 81u, 81u], view.TextureHandles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetSession_ClearsPrivateDollOnceAndArmsNextCharacterBuild()
|
||||
{
|
||||
var firstDoll = CreateDoll();
|
||||
var secondDoll = CreateDoll();
|
||||
var renderer = new RecordingRenderer();
|
||||
var view = new RecordingView();
|
||||
var factory = new RecordingFactory { Doll = firstDoll };
|
||||
var presenter = new PaperdollFramePresenter(renderer, view, factory);
|
||||
|
||||
presenter.Render();
|
||||
presenter.ResetSession();
|
||||
factory.Doll = secondDoll;
|
||||
presenter.Render();
|
||||
|
||||
Assert.False(presenter.IsDirty);
|
||||
Assert.Equal(2, factory.BuildCount);
|
||||
Assert.Equal(2, renderer.Dolls.Count);
|
||||
Assert.All(renderer.Dolls, Assert.Null);
|
||||
Assert.Equal(2, renderer.RenderCount);
|
||||
Assert.Equal([0u, 0u], view.TextureHandles);
|
||||
Assert.Equal([firstDoll, null, secondDoll], renderer.Dolls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -222,8 +285,8 @@ public sealed class PaperdollFramePresenterTests
|
|||
|
||||
private sealed class RecordingFactory : IPaperdollDollFactory
|
||||
{
|
||||
public bool CanBuild { get; init; } = true;
|
||||
public WorldEntity? Doll { get; init; }
|
||||
public bool CanBuild { get; set; } = true;
|
||||
public WorldEntity? Doll { get; set; }
|
||||
public int BuildCount { get; private set; }
|
||||
|
||||
public bool TryBuild(out WorldEntity? doll)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,31 @@ using AcDream.App.Rendering.Wb;
|
|||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class PortalMeshReferenceOwnerTests
|
||||
public sealed class SyntheticEntityMeshReferenceOwnerTests
|
||||
{
|
||||
[Fact]
|
||||
public void PrivateOwnerSurvivesIndependentWorldPresentationRelease()
|
||||
{
|
||||
const ulong mesh = 0x01000001u;
|
||||
var adapter = new RecordingMeshAdapter();
|
||||
adapter.IncrementRefCount(mesh);
|
||||
var privateOwner =
|
||||
new SyntheticEntityMeshReferenceOwner(adapter, [mesh]);
|
||||
privateOwner.Acquire();
|
||||
|
||||
adapter.DecrementRefCount(mesh);
|
||||
|
||||
Assert.Equal(1, adapter.ReferenceCount(mesh));
|
||||
privateOwner.Dispose();
|
||||
Assert.Equal(0, adapter.TotalReferences);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartialAcquireRollsBackEveryCommittedSibling()
|
||||
{
|
||||
var adapter = new RecordingMeshAdapter();
|
||||
adapter.FailIncrementBeforeCommit(0x01000001u, attempts: 1);
|
||||
var owner = new PortalMeshReferenceOwner(
|
||||
var owner = new SyntheticEntityMeshReferenceOwner(
|
||||
adapter,
|
||||
[0x01000001u, 0x01000002u]);
|
||||
|
||||
|
|
@ -30,7 +47,7 @@ public sealed class PortalMeshReferenceOwnerTests
|
|||
{
|
||||
var adapter = new RecordingMeshAdapter();
|
||||
adapter.FailIncrementAfterCommit(0x01000001u, attempts: 1);
|
||||
var owner = new PortalMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
var owner = new SyntheticEntityMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
|
||||
Assert.Throws<AggregateException>(owner.Acquire);
|
||||
Assert.Equal(0, adapter.TotalReferences);
|
||||
|
|
@ -48,7 +65,7 @@ public sealed class PortalMeshReferenceOwnerTests
|
|||
var adapter = new RecordingMeshAdapter();
|
||||
adapter.FailIncrementBeforeCommit(0x01000001u, attempts: 1);
|
||||
adapter.FailDecrementBeforeCommit(0x01000002u, attempts: 1);
|
||||
var owner = new PortalMeshReferenceOwner(
|
||||
var owner = new SyntheticEntityMeshReferenceOwner(
|
||||
adapter,
|
||||
[0x01000001u, 0x01000002u]);
|
||||
|
||||
|
|
@ -69,7 +86,7 @@ public sealed class PortalMeshReferenceOwnerTests
|
|||
public void DisposeAttemptsEveryReferenceAndRetriesOnlyPendingRelease()
|
||||
{
|
||||
var adapter = new RecordingMeshAdapter();
|
||||
var owner = new PortalMeshReferenceOwner(
|
||||
var owner = new SyntheticEntityMeshReferenceOwner(
|
||||
adapter,
|
||||
[0x01000001u, 0x01000002u]);
|
||||
owner.Acquire();
|
||||
|
|
@ -92,7 +109,7 @@ public sealed class PortalMeshReferenceOwnerTests
|
|||
public void CommittedReleaseFailureCompletesOwnershipWithoutDoubleDecrement()
|
||||
{
|
||||
var adapter = new RecordingMeshAdapter();
|
||||
var owner = new PortalMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
var owner = new SyntheticEntityMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
owner.Acquire();
|
||||
adapter.FailDecrementAfterCommit(0x01000001u, attempts: 1);
|
||||
|
||||
|
|
@ -107,12 +124,12 @@ public sealed class PortalMeshReferenceOwnerTests
|
|||
[Fact]
|
||||
public void DisposeReentryDuringAcquireReconcilesBeforeReturning()
|
||||
{
|
||||
PortalMeshReferenceOwner? owner = null;
|
||||
SyntheticEntityMeshReferenceOwner? owner = null;
|
||||
var adapter = new RecordingMeshAdapter
|
||||
{
|
||||
AfterIncrement = _ => owner!.Dispose(),
|
||||
};
|
||||
owner = new PortalMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
owner = new SyntheticEntityMeshReferenceOwner(adapter, [0x01000001u]);
|
||||
|
||||
Assert.Throws<ObjectDisposedException>(owner.Acquire);
|
||||
owner.Dispose();
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using AcDream.App.Rendering;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class WorldRevealRenderResourceSchedulerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplacementGeneration_KeepsPriorityUntilExactGenerationEnds()
|
||||
{
|
||||
var first = new List<bool>();
|
||||
var second = new List<bool>();
|
||||
var scheduler = new WorldRevealRenderResourceScheduler(
|
||||
first.Add,
|
||||
second.Add);
|
||||
|
||||
scheduler.BeginDestinationReveal(3);
|
||||
scheduler.BeginDestinationReveal(4);
|
||||
scheduler.EndDestinationReveal(3);
|
||||
scheduler.EndDestinationReveal(4);
|
||||
scheduler.EndDestinationReveal(4);
|
||||
|
||||
Assert.Equal([true, true, false], first);
|
||||
Assert.Equal([true, true, false], second);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue