refactor(rendering): prepare retained particle routing
Derive attached particle owners from the exact retained PView route stream with a zero-allocation warm path. Correct G4 gate provenance, record the current production-only RDP sample, and keep G5 old-route deletion explicitly blocked until the reapplied retained cutover passes its connected visual gate.
This commit is contained in:
parent
20f9fadb12
commit
c730632075
7 changed files with 374 additions and 20 deletions
|
|
@ -0,0 +1,178 @@
|
|||
using AcDream.App.Rendering.Scene;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class RenderFrameRouteOwnerSelectorTests
|
||||
{
|
||||
private static readonly RenderSceneGeneration Generation =
|
||||
RenderSceneGeneration.FromRaw(1);
|
||||
|
||||
[Fact]
|
||||
public void ReplaceAndUnionUseExactRouteIdentityAndDeduplicateOwners()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint> { 999 };
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.Union(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
|
||||
Assert.Equal([10u, 20u, 30u], owners.Order());
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptRouteRemovesOwnersAcrossEveryRangeOfThatRoute()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint>();
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||
|
||||
Assert.Equal([50u], owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingRouteClearsOnReplaceAndDoesNotMutateOnUnion()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint> { 10, 20 };
|
||||
|
||||
RenderFrameRouteOwnerSelector.Union(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex: 9,
|
||||
cellId: 0x01010009);
|
||||
Assert.Equal([10u, 20u], owners.Order());
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex: 9,
|
||||
cellId: 0x01010009);
|
||||
Assert.Empty(owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmSelectionAllocatesNothing()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint>();
|
||||
owners.EnsureCapacity(16);
|
||||
|
||||
SelectOwners(owners, in view);
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int iteration = 0; iteration < 1_000; iteration++)
|
||||
SelectOwners(owners, in view);
|
||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
Assert.Equal([50u], owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
private static void SelectOwners(
|
||||
HashSet<uint> owners,
|
||||
in RenderFrameView view)
|
||||
{
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||
}
|
||||
|
||||
private static void PublishRoutes(
|
||||
RenderFrameExchange exchange,
|
||||
ulong sequence)
|
||||
{
|
||||
RenderProjectionRecord owner10 = Projection(1, 10);
|
||||
RenderProjectionRecord owner20 = Projection(2, 20);
|
||||
RenderProjectionRecord owner30 = Projection(3, 30);
|
||||
RenderProjectionRecord owner40 = Projection(4, 40);
|
||||
RenderProjectionRecord owner50 = Projection(5, 50);
|
||||
RenderProjectionRecord anonymous = Projection(6, 0);
|
||||
RenderFrameWriter writer =
|
||||
exchange.BeginBuild(Generation, sequence);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||
0,
|
||||
0,
|
||||
[owner10, owner20, anonymous]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
0,
|
||||
0,
|
||||
[owner20, owner30]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
1,
|
||||
0,
|
||||
[owner40]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
0,
|
||||
0,
|
||||
[owner20, owner40, owner50]);
|
||||
writer.SetSourceDigest(
|
||||
new RenderSceneDigest(
|
||||
Generation,
|
||||
default,
|
||||
default));
|
||||
writer.Publish();
|
||||
}
|
||||
|
||||
private static RenderProjectionRecord Projection(
|
||||
ulong projectionId,
|
||||
uint localEntityId) =>
|
||||
new()
|
||||
{
|
||||
Id = RenderProjectionId.FromRaw(projectionId),
|
||||
OwnerIncarnation = RenderOwnerIncarnation.FromRaw(1),
|
||||
Source = new RenderSourceMetadata(
|
||||
LocalEntityId: localEntityId,
|
||||
ServerGuid: 0,
|
||||
SourceId: 0,
|
||||
ParentCellId: 0,
|
||||
EffectCellId: 0,
|
||||
BuildingShellAnchorCellId: 0,
|
||||
TransformFingerprint: default,
|
||||
GeometryFingerprint: default,
|
||||
AppearanceFingerprint: default),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue