perf(physics): pool collision cell snapshots
Preserve the fixed per-cell collision walk across nested registry mutations while replacing the repeated List allocation with an explicitly owned pooled snapshot. Capture cardinality once, return storage on every exit, and pin live-list mutation behavior with a focused regression test. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
f9736ece6c
commit
4f1c067a21
3 changed files with 77 additions and 3 deletions
56
src/AcDream.Core/Physics/ShadowEntrySnapshot.cs
Normal file
56
src/AcDream.Core/Physics/ShadowEntrySnapshot.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Buffers;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Pooled, mutation-stable snapshot of a cell's live shadow entries.
|
||||
/// Nested collision resolution can update the registry while the outer
|
||||
/// transition is still walking the cell, so the captured count and entries
|
||||
/// must remain fixed for the lifetime of that walk.
|
||||
/// </summary>
|
||||
internal ref struct ShadowEntrySnapshot
|
||||
{
|
||||
private ShadowEntry[]? _buffer;
|
||||
private readonly int _count;
|
||||
|
||||
private ShadowEntrySnapshot(ShadowEntry[] buffer, int count)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_count = count;
|
||||
}
|
||||
|
||||
public readonly ReadOnlySpan<ShadowEntry> Entries
|
||||
=> _buffer.AsSpan(0, _count);
|
||||
|
||||
public static ShadowEntrySnapshot Capture(IReadOnlyList<ShadowEntry> source)
|
||||
{
|
||||
int count = source.Count;
|
||||
ShadowEntry[] buffer = ArrayPool<ShadowEntry>.Shared.Rent(count);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
buffer[i] = source[i];
|
||||
|
||||
return new ShadowEntrySnapshot(buffer, count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ArrayPool<ShadowEntry>.Shared.Return(buffer, clearArray: false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ShadowEntry[]? buffer = _buffer;
|
||||
_buffer = null;
|
||||
|
||||
if (buffer is not null)
|
||||
{
|
||||
// ShadowEntry is a value-only record struct, so the pooled buffer
|
||||
// cannot retain references to live entities or renderer state.
|
||||
ArrayPool<ShadowEntry>.Shared.Return(buffer, clearArray: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2752,9 +2752,8 @@ public sealed class Transition
|
|||
// Snapshot the LIVE per-cell list: a nested step-up
|
||||
// (DoStepUp → TransitionalInsert → this) must not observe
|
||||
// registry mutations through the same reference mid-iteration.
|
||||
var nearbyObjs = new List<ShadowEntry>(objsInCell);
|
||||
|
||||
foreach (var obj in nearbyObjs)
|
||||
using var nearbyObjs = ShadowEntrySnapshot.Capture(objsInCell);
|
||||
foreach (ShadowEntry obj in nearbyObjs.Entries)
|
||||
{
|
||||
// Self-skip — fix #42 (2026-05-05). Mirrors retail
|
||||
// CObjCell::find_obj_collisions at acclient_2013_pseudo_c.txt
|
||||
|
|
|
|||
|
|
@ -16,6 +16,25 @@ public class ShadowObjectRegistryTests
|
|||
private const float OffX = 0f;
|
||||
private const float OffY = 0f;
|
||||
|
||||
[Fact]
|
||||
public void ShadowEntrySnapshot_LiveListMutation_DoesNotChangeCapturedWalk()
|
||||
{
|
||||
var first = new ShadowEntry(1u, 0u, Vector3.Zero, Quaternion.Identity, 1f);
|
||||
var second = new ShadowEntry(2u, 0u, Vector3.One, Quaternion.Identity, 1f);
|
||||
var replacement = new ShadowEntry(3u, 0u, Vector3.UnitX, Quaternion.Identity, 1f);
|
||||
var liveEntries = new List<ShadowEntry> { first, second };
|
||||
|
||||
using var snapshot = ShadowEntrySnapshot.Capture(liveEntries);
|
||||
|
||||
liveEntries.RemoveAt(0);
|
||||
liveEntries.Add(replacement);
|
||||
liveEntries.Add(first);
|
||||
|
||||
Assert.Equal(
|
||||
new uint[] { first.EntityId, second.EntityId },
|
||||
snapshot.Entries.ToArray().Select(entry => entry.EntityId));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Register / TotalRegistered
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue