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>
56 lines
1.5 KiB
C#
56 lines
1.5 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|