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
|
|
@ -1632,10 +1632,13 @@ port in any phase — no separate listing here.
|
||||||
> shadow referee, production cutover, and acceptance matrix. The user
|
> shadow referee, production cutover, and acceptance matrix. The user
|
||||||
> explicitly approved Slices F–L, including the F/G and J gates, on
|
> explicitly approved Slices F–L, including the F/G and J gates, on
|
||||||
> 2026-07-24. Slice F and G0–G4 are implemented; 46,599 automated G4
|
> 2026-07-24. Slice F and G0–G4 are implemented; 46,599 automated G4
|
||||||
> comparisons have zero mismatches. The 2026-07-25 connected visual gate
|
> comparisons have zero mismatches. The first retained-path visual gate exposed
|
||||||
> accepted the retained scene after independent portal scheduling/private-view/
|
> independent portal scheduling/private-view/ACE-revisit ownership regressions.
|
||||||
> ACE-revisit ownership regressions were corrected; G5 old-path deletion and
|
> Their correction passed visually while the draw cutover was temporarily
|
||||||
> production-only profiling are now active. The historical exact cutover
|
> reverted, which proves the correction but not the retained source.
|
||||||
|
> `20f9fadb` reapplies the cutover over those fixes; its connected portal/revisit
|
||||||
|
> re-gate is pending before G5 old-path deletion and production-only profiling.
|
||||||
|
> The historical exact cutover
|
||||||
> rollback remains
|
> rollback remains
|
||||||
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
||||||
> retained UI/frame work, exact bounded light selection, and
|
> retained UI/frame work, exact bounded light selection, and
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,11 @@ publication, retirement, upload, warmup, or resource backlog. Capped/uncapped
|
||||||
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
||||||
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
||||||
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
||||||
G0–G4 are implemented; G4's automated gate has zero mismatches and its
|
G0–G4 are implemented and G4's automated gate has zero mismatches. The portal
|
||||||
2026-07-25 connected visual gate passes after the portal scheduling/private
|
scheduling/private-viewport/ACE-revisit correction passed visually while the
|
||||||
viewport/ACE revisit regression correction. G5 deletion and production-only
|
retained draw cutover was temporarily reverted, so `20f9fadb` reapplies the
|
||||||
profiling are active. Slice H is
|
cutover over those fixes for the pending connected retained-path re-gate. G5
|
||||||
|
deletion and production-only profiling remain gated on that pass. Slice H is
|
||||||
complete: retained UI/frame work, exact bounded light selection, and
|
complete: retained UI/frame work, exact bounded light selection, and
|
||||||
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||||
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
**Date:** 2026-07-24
|
**Date:** 2026-07-24
|
||||||
|
|
||||||
**Status:** approved 2026-07-24; Slice F and G4 complete, G5 active
|
**Status:** approved 2026-07-24; Slice F complete, G4 retained-path re-gate
|
||||||
|
active, G5 prepared but not unblocked
|
||||||
|
|
||||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||||
|
|
||||||
|
|
@ -27,8 +28,8 @@ retail-faithful gameplay
|
||||||
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
||||||
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
||||||
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
||||||
| G4 — production cutover | complete | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The 2026-07-25 connected visual pass accepted the retained scene after its independent portal scheduling/ownership regressions were corrected. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
| G4 — production cutover | re-gate active | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The portal correction was visually accepted while the cutover was temporarily reverted by `2c848d41`; that proves the correction but cannot accept the retained draw source. Commit `20f9fadb` reapplies the cutover over the correction and is the current connected visual re-gate. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||||
| G5 | active | The accepted visual gate unblocks production-only performance/lifecycle closeout and old-route deletion. |
|
| G5 | prepared | Old-route deletion and the production-only reference matrix remain blocked only on the connected `20f9fadb` retained-path re-gate. An allocation-free retained-route particle-owner selector and steady RDP sample are prepared without changing the running gate binary. |
|
||||||
|
|
||||||
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
||||||
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
||||||
|
|
@ -646,12 +647,15 @@ the scene journal was empty, source and binary hashes matched, and every
|
||||||
process closed gracefully. The gate corrected whole-retained-world composite
|
process closed gracefully. The gate corrected whole-retained-world composite
|
||||||
warmup discovery by sourcing the exact published 3×3 destination neighborhood;
|
warmup discovery by sourcing the exact published 3×3 destination neighborhood;
|
||||||
that correction is draw-path independent. Fixed-camera artifacts are captured.
|
that correction is draw-path independent. Fixed-camera artifacts are captured.
|
||||||
The user completed the connected visual gate on 2026-07-25. The retained scene
|
The first connected retained-path gate exposed independent portal scheduling,
|
||||||
itself remained exact; the pass exposed independent portal scheduling, private
|
private-viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
||||||
viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
|
||||||
correction is recorded in
|
correction is recorded in
|
||||||
`docs/research/2026-07-25-portal-regression-closeout.md`. G4 is accepted and
|
`docs/research/2026-07-25-portal-regression-closeout.md`. The user's subsequent
|
||||||
old-route deletion plus G5 production-only profiling are unblocked.
|
pass occurred while exact cutover `ef1d2633` was temporarily reverted by
|
||||||
|
`2c848d41`, so it proves those corrections but not the retained draw source.
|
||||||
|
`20f9fadb` reapplies the cutover over the accepted corrections. G4 acceptance,
|
||||||
|
old-route deletion, and the G5 production-only matrix remain gated on the
|
||||||
|
connected retained-path recheck.
|
||||||
|
|
||||||
### G5 — Physical performance and lifecycle gate
|
### G5 — Physical performance and lifecycle gate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ The first connected retained-scene visual pass reported:
|
||||||
4. doors, signs, portals, NPCs, and other server-spawned objects disappeared
|
4. doors, signs, portals, NPCs, and other server-spawned objects disappeared
|
||||||
after repeated portal travel.
|
after repeated portal travel.
|
||||||
|
|
||||||
This note records the root causes and the accepted correction. It does not
|
This note records the root causes and the correction. It does not reduce world
|
||||||
reduce world radius, texture quality, particles, readiness, or retail
|
radius, texture quality, particles, readiness, or retail presentation. The
|
||||||
presentation.
|
correction's visual pass occurred on the temporary old-draw rollback; the
|
||||||
|
retained-source re-gate is tracked separately below.
|
||||||
|
|
||||||
## Retail and protocol anchors
|
## Retail and protocol anchors
|
||||||
|
|
||||||
|
|
@ -99,7 +100,11 @@ This ACE adaptation remains explicit in divergence AP-69.
|
||||||
- Complete App Release suite: 3,813 passed / 3 skipped.
|
- Complete App Release suite: 3,813 passed / 3 skipped.
|
||||||
- Release solution build: green with zero warnings.
|
- Release solution build: green with zero warnings.
|
||||||
- Complete Release solution suite: 8,322 passed / 5 skipped.
|
- Complete Release solution suite: 8,322 passed / 5 skipped.
|
||||||
- User connected visual gate:
|
- User connected correction gate on rollback route (`2c848d41`):
|
||||||
- paperdoll remains visible;
|
- paperdoll remains visible;
|
||||||
- portal transit is materially faster;
|
- portal transit is materially faster;
|
||||||
- server-spawned objects remain after repeated travel.
|
- server-spawned objects remain after repeated travel.
|
||||||
|
- Retained production source:
|
||||||
|
- `20f9fadb` reapplies exact cutover `ef1d2633` over the correction;
|
||||||
|
- complete Release suite: 8,326 passed / 5 skipped;
|
||||||
|
- connected portal/revisit visual re-gate pending before G4 acceptance.
|
||||||
|
|
|
||||||
43
docs/research/2026-07-25-slice-g5-production-profile.md
Normal file
43
docs/research/2026-07-25-slice-g5-production-profile.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Slice G5 production profile — 2026-07-25
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Work in progress. The retained-scene cutover is running at `20f9fadb`, but G5
|
||||||
|
old-route deletion remains blocked on its connected portal/revisit visual
|
||||||
|
re-gate. This note records production-only evidence gathered without enabling
|
||||||
|
the referee or lifecycle automation.
|
||||||
|
|
||||||
|
## First steady RDP sample
|
||||||
|
|
||||||
|
The client was idle in-world at 60 Hz with the normal capped Release
|
||||||
|
presentation, High quality, a 4/12 near/far radius, and no developer or
|
||||||
|
automation UI. The title reported 173/625 landblocks, 10,383 entities, and 51
|
||||||
|
active animations. Five seconds of process CPU plus three one-second Windows
|
||||||
|
GPU counter samples measured:
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|---|---:|
|
||||||
|
| CPU | 2.68% of 16 logical processors, about 42.9% of one logical core |
|
||||||
|
| Working set | 255.8 MiB |
|
||||||
|
| Private bytes | 1,193.9 MiB |
|
||||||
|
| Threads / handles | 26 / 663 |
|
||||||
|
| GPU 3-D engine | 55.74% average, 56.26% maximum |
|
||||||
|
| Dedicated GPU process memory | 422 MiB |
|
||||||
|
| Shared GPU process memory | 141 MiB |
|
||||||
|
|
||||||
|
This is an RDP presentation sample, not the required physical-local reference
|
||||||
|
matrix and not evidence of a plateau across travel. It establishes that the
|
||||||
|
post-cutover retained path is active and gives G5 a pre-deletion production
|
||||||
|
point. The complete capped/uncapped nine-stop, dense-Arwic, lifecycle,
|
||||||
|
allocation, GC, stage-timing, and screenshot/instance-set matrix remains
|
||||||
|
mandatory after the visual re-gate.
|
||||||
|
|
||||||
|
## Prepared old-route dependency removal
|
||||||
|
|
||||||
|
`RenderFrameRouteOwnerSelector` derives attached particle owner IDs from the
|
||||||
|
exact retained PView route ranges. It supports exact-route replace/union and
|
||||||
|
all-range exclusion without allocation after warmup. This removes the final
|
||||||
|
particle-routing reason for normal production to build
|
||||||
|
`InteriorEntityPartition`; it is not wired into the running visual-gate binary.
|
||||||
|
Four focused tests and the 3,821-pass / 3-skip App Release suite are green
|
||||||
|
through an isolated output path.
|
||||||
120
src/AcDream.App/Rendering/Scene/RenderFrameRouteOwnerSelector.cs
Normal file
120
src/AcDream.App/Rendering/Scene/RenderFrameRouteOwnerSelector.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
namespace AcDream.App.Rendering.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Projects the retained PView route stream into particle-owner identities.
|
||||||
|
/// This keeps attached-effect routing on the same visibility decision as mesh
|
||||||
|
/// submission without walking spatial/gameplay entities a second time.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RenderFrameRouteOwnerSelector
|
||||||
|
{
|
||||||
|
public static void Replace(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
in RenderFrameView view,
|
||||||
|
RenderFrameCandidateRoute route,
|
||||||
|
int routeIndex,
|
||||||
|
uint cellId)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(destination);
|
||||||
|
destination.Clear();
|
||||||
|
Union(
|
||||||
|
destination,
|
||||||
|
in view,
|
||||||
|
route,
|
||||||
|
routeIndex,
|
||||||
|
cellId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Union(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
in RenderFrameView view,
|
||||||
|
RenderFrameCandidateRoute route,
|
||||||
|
int routeIndex,
|
||||||
|
uint cellId)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(destination);
|
||||||
|
VisitExact(
|
||||||
|
destination,
|
||||||
|
in view,
|
||||||
|
route,
|
||||||
|
routeIndex,
|
||||||
|
cellId,
|
||||||
|
remove: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExceptRoute(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
in RenderFrameView view,
|
||||||
|
RenderFrameCandidateRoute route)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(destination);
|
||||||
|
|
||||||
|
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||||
|
view.RouteRanges;
|
||||||
|
ReadOnlySpan<RenderProjectionRecord> candidates =
|
||||||
|
view.RouteCandidates;
|
||||||
|
for (int index = 0; index < ranges.Length; index++)
|
||||||
|
{
|
||||||
|
RenderFrameCandidateRange range = ranges[index];
|
||||||
|
if (range.Route != route)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ApplyRange(
|
||||||
|
destination,
|
||||||
|
candidates,
|
||||||
|
in range,
|
||||||
|
remove: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VisitExact(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
in RenderFrameView view,
|
||||||
|
RenderFrameCandidateRoute route,
|
||||||
|
int routeIndex,
|
||||||
|
uint cellId,
|
||||||
|
bool remove)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||||
|
view.RouteRanges;
|
||||||
|
ReadOnlySpan<RenderProjectionRecord> candidates =
|
||||||
|
view.RouteCandidates;
|
||||||
|
for (int index = 0; index < ranges.Length; index++)
|
||||||
|
{
|
||||||
|
RenderFrameCandidateRange range = ranges[index];
|
||||||
|
if (range.Route != route
|
||||||
|
|| range.RouteIndex != routeIndex
|
||||||
|
|| range.CellId != cellId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyRange(
|
||||||
|
destination,
|
||||||
|
candidates,
|
||||||
|
in range,
|
||||||
|
remove);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyRange(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
ReadOnlySpan<RenderProjectionRecord> candidates,
|
||||||
|
in RenderFrameCandidateRange range,
|
||||||
|
bool remove)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<RenderProjectionRecord> owners =
|
||||||
|
candidates.Slice(range.Offset, range.Count);
|
||||||
|
for (int index = 0; index < owners.Length; index++)
|
||||||
|
{
|
||||||
|
uint localEntityId = owners[index].Source.LocalEntityId;
|
||||||
|
if (localEntityId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (remove)
|
||||||
|
destination.Remove(localEntityId);
|
||||||
|
else
|
||||||
|
destination.Add(localEntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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