fix(rendering): reconcile shadow roots from live runtime
Recover ordinary live roots from LiveEntityRuntime's canonical active spatial workset instead of relying on already-retained derived records. Keep attached children callback-authoritative, cache immutable selection geometry fingerprints, and pin zero-allocation retained update paths with regression tests. Release gate: 3,733 App tests / 3 skips; 8,217 complete-solution tests / 5 skips. Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
056bbd4efd
commit
91463db551
7 changed files with 272 additions and 15 deletions
|
|
@ -211,6 +211,8 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
new(ReferenceEqualityComparer.Instance);
|
||||
private readonly List<CurrentRenderSelectionFingerprint>
|
||||
_selectionParts = [];
|
||||
private readonly Dictionary<uint, SelectionGeometryCacheEntry>
|
||||
_selectionGeometryByGfxObj = [];
|
||||
private ulong _frameSequence;
|
||||
private int _abortedFrames;
|
||||
private int _outdoorStaticCount;
|
||||
|
|
@ -637,16 +639,15 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
}
|
||||
ArgumentNullException.ThrowIfNull(mesh);
|
||||
|
||||
StableRenderHash128 geometry = StableRenderHash128.Create();
|
||||
geometry.Add(mesh.SphereCenter);
|
||||
geometry.Add(mesh.SphereRadius);
|
||||
geometry.Add(mesh.Polygons.Count);
|
||||
foreach (RetailSelectionPolygon polygon in mesh.Polygons)
|
||||
if (!_selectionGeometryByGfxObj.TryGetValue(
|
||||
gfxObjId,
|
||||
out SelectionGeometryCacheEntry cachedGeometry)
|
||||
|| !ReferenceEquals(cachedGeometry.Mesh, mesh))
|
||||
{
|
||||
geometry.Add(polygon.SingleSided);
|
||||
geometry.Add(polygon.Vertices.Count);
|
||||
foreach (Vector3 vertex in polygon.Vertices)
|
||||
geometry.Add(vertex);
|
||||
cachedGeometry = new SelectionGeometryCacheEntry(
|
||||
mesh,
|
||||
FingerprintSelectionGeometry(mesh));
|
||||
_selectionGeometryByGfxObj[gfxObjId] = cachedGeometry;
|
||||
}
|
||||
|
||||
var fingerprint = new CurrentRenderSelectionFingerprint(
|
||||
|
|
@ -656,7 +657,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
PartIndex: partIndex,
|
||||
GfxObjId: gfxObjId,
|
||||
LocalToWorld: localToWorld,
|
||||
Geometry: geometry.Finish());
|
||||
Geometry: cachedGeometry.Fingerprint);
|
||||
_selectionParts.Add(fingerprint);
|
||||
_selectionHash.Add(fingerprint.Sequence);
|
||||
_selectionHash.Add(fingerprint.ServerGuid);
|
||||
|
|
@ -722,8 +723,11 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
|
||||
StableRenderHash128 geometry = StableRenderHash128.Create();
|
||||
geometry.Add(entity.MeshRefs.Count);
|
||||
foreach (MeshRef mesh in entity.MeshRefs)
|
||||
for (int meshIndex = 0;
|
||||
meshIndex < entity.MeshRefs.Count;
|
||||
meshIndex++)
|
||||
{
|
||||
MeshRef mesh = entity.MeshRefs[meshIndex];
|
||||
geometry.Add(mesh.GfxObjId);
|
||||
geometry.Add(mesh.PartTransform);
|
||||
}
|
||||
|
|
@ -734,8 +738,12 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
appearance.Add(true);
|
||||
appearance.Add(palette.BasePaletteId);
|
||||
appearance.Add(palette.SubPalettes.Count);
|
||||
foreach (PaletteOverride.SubPaletteRange range in palette.SubPalettes)
|
||||
for (int rangeIndex = 0;
|
||||
rangeIndex < palette.SubPalettes.Count;
|
||||
rangeIndex++)
|
||||
{
|
||||
PaletteOverride.SubPaletteRange range =
|
||||
palette.SubPalettes[rangeIndex];
|
||||
appearance.Add(range.SubPaletteId);
|
||||
appearance.Add(range.Offset);
|
||||
appearance.Add(range.Length);
|
||||
|
|
@ -747,8 +755,11 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
}
|
||||
|
||||
appearance.Add(entity.PartOverrides.Count);
|
||||
foreach (PartOverride part in entity.PartOverrides)
|
||||
for (int partIndex = 0;
|
||||
partIndex < entity.PartOverrides.Count;
|
||||
partIndex++)
|
||||
{
|
||||
PartOverride part = entity.PartOverrides[partIndex];
|
||||
appearance.Add(part.PartIndex);
|
||||
appearance.Add(part.GfxObjId);
|
||||
}
|
||||
|
|
@ -784,6 +795,31 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
Appearance: appearance.Finish());
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 FingerprintSelectionGeometry(
|
||||
RetailSelectionMesh mesh)
|
||||
{
|
||||
StableRenderHash128 geometry = StableRenderHash128.Create();
|
||||
geometry.Add(mesh.SphereCenter);
|
||||
geometry.Add(mesh.SphereRadius);
|
||||
geometry.Add(mesh.Polygons.Count);
|
||||
for (int polygonIndex = 0;
|
||||
polygonIndex < mesh.Polygons.Count;
|
||||
polygonIndex++)
|
||||
{
|
||||
RetailSelectionPolygon polygon = mesh.Polygons[polygonIndex];
|
||||
geometry.Add(polygon.SingleSided);
|
||||
geometry.Add(polygon.Vertices.Count);
|
||||
for (int vertexIndex = 0;
|
||||
vertexIndex < polygon.Vertices.Count;
|
||||
vertexIndex++)
|
||||
{
|
||||
geometry.Add(polygon.Vertices[vertexIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
return geometry.Finish();
|
||||
}
|
||||
|
||||
private static InteriorEntityPartition.ProjectionClass ProjectionClassOf(
|
||||
WorldEntity entity) =>
|
||||
entity.ServerGuid != 0
|
||||
|
|
@ -792,6 +828,10 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
? InteriorEntityPartition.ProjectionClass.CellStatic
|
||||
: InteriorEntityPartition.ProjectionClass.OutdoorStatic;
|
||||
|
||||
private readonly record struct SelectionGeometryCacheEntry(
|
||||
RetailSelectionMesh Mesh,
|
||||
RenderSceneHash128 Fingerprint);
|
||||
|
||||
private static void AddPViewCandidate(
|
||||
ref StableRenderHash128 hash,
|
||||
in CurrentRenderPViewCandidateFingerprint candidate)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
private readonly LiveEntityRuntime _runtime;
|
||||
private readonly RenderProjectionJournal _journal;
|
||||
private readonly Dictionary<uint, TrackedProjection> _byLocalId = [];
|
||||
private readonly List<LiveEntityRecord> _activeRootScratch = [];
|
||||
private readonly List<TrackedProjection> _activeScratch = [];
|
||||
|
||||
public LiveRenderProjectionJournal(
|
||||
|
|
@ -107,10 +108,34 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
/// </summary>
|
||||
public void SynchronizeActiveSources()
|
||||
{
|
||||
// The canonical live-runtime workset is the source of truth for world
|
||||
// roots. Walking only this journal's already-tracked values would make
|
||||
// a missed/reordered derived callback permanent and prevent shadow
|
||||
// comparison from converging after a same-location revisit.
|
||||
_runtime.CopySpatialRootObjectRecordsTo(_activeRootScratch);
|
||||
for (int i = 0; i < _activeRootScratch.Count; i++)
|
||||
{
|
||||
LiveEntityRecord record = _activeRootScratch[i];
|
||||
if (!record.ResourcesRegistered
|
||||
|| record.WorldEntity is not { } entity
|
||||
|| !_runtime.IsCurrentSpatialRootObject(record))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Upsert(record, entity, record.IsSpatiallyVisible);
|
||||
}
|
||||
|
||||
// Attached children are not members of the ordinary root workset.
|
||||
// Their exact EntityReady/pose/removal callbacks remain authoritative;
|
||||
// synchronization only refreshes children whose attachment projection
|
||||
// is still retained by this derived scene.
|
||||
_activeScratch.Clear();
|
||||
foreach (TrackedProjection tracked in _byLocalId.Values)
|
||||
{
|
||||
if (tracked.Record.IsSpatiallyProjected)
|
||||
if (tracked.Record.IsSpatiallyProjected
|
||||
&& tracked.Record.ProjectionKind is
|
||||
LiveEntityProjectionKind.Attached)
|
||||
_activeScratch.Add(tracked);
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +162,7 @@ internal sealed class LiveRenderProjectionJournal : ILiveRenderProjectionSink
|
|||
public void ResetTracking()
|
||||
{
|
||||
_byLocalId.Clear();
|
||||
_activeRootScratch.Clear();
|
||||
_activeScratch.Clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue