feat(A.5 T4): StreamingRegion ComputeFirstTickDiff
Adds the first-tick bootstrap diff: ToLoadNear for the (2*near+1)^2 inner window, ToLoadFar for the outer annulus up to FarRadius. Uses Chebyshev distance, matching existing Recenter convention. Also renames the single-tier RecenterTo → RecenterToSingleTier to free the canonical name for the upcoming two-tier overload (T5). Updates StreamingRegionTests and StreamingController to call the renamed method. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
378f32ac7a
commit
7bcababf82
4 changed files with 54 additions and 6 deletions
|
|
@ -79,7 +79,7 @@ public sealed class StreamingController
|
||||||
}
|
}
|
||||||
else if (_region.CenterX != observerCx || _region.CenterY != observerCy)
|
else if (_region.CenterX != observerCx || _region.CenterY != observerCy)
|
||||||
{
|
{
|
||||||
var diff = _region.RecenterTo(observerCx, observerCy);
|
var diff = _region.RecenterToSingleTier(observerCx, observerCy);
|
||||||
foreach (var id in diff.ToLoad) _enqueueLoad(id);
|
foreach (var id in diff.ToLoad) _enqueueLoad(id);
|
||||||
foreach (var id in diff.ToUnload) _enqueueUnload(id);
|
foreach (var id in diff.ToUnload) _enqueueUnload(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,47 @@ public sealed class StreamingRegion
|
||||||
internal static uint EncodeLandblockId(int lbX, int lbY)
|
internal static uint EncodeLandblockId(int lbX, int lbY)
|
||||||
=> ((uint)lbX << 24) | ((uint)lbY << 16) | 0xFFFFu;
|
=> ((uint)lbX << 24) | ((uint)lbY << 16) | 0xFFFFu;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First-tick bootstrap: emit ToLoadNear for every LB in the inner ring,
|
||||||
|
/// ToLoadFar for every LB in the outer ring (between near and far). Used
|
||||||
|
/// by <see cref="StreamingController.Tick"/> on the first call before any
|
||||||
|
/// RecenterTo.
|
||||||
|
/// </summary>
|
||||||
|
public TwoTierDiff ComputeFirstTickDiff()
|
||||||
|
{
|
||||||
|
var near = new List<uint>();
|
||||||
|
var far = new List<uint>();
|
||||||
|
for (int dx = -FarRadius; dx <= FarRadius; dx++)
|
||||||
|
{
|
||||||
|
for (int dy = -FarRadius; dy <= FarRadius; dy++)
|
||||||
|
{
|
||||||
|
int nx = CenterX + dx;
|
||||||
|
int ny = CenterY + dy;
|
||||||
|
if (nx < 0 || nx > 0xFF || ny < 0 || ny > 0xFF) continue;
|
||||||
|
int absDx = System.Math.Abs(dx);
|
||||||
|
int absDy = System.Math.Abs(dy);
|
||||||
|
var id = EncodeLandblockId(nx, ny);
|
||||||
|
if (absDx <= NearRadius && absDy <= NearRadius)
|
||||||
|
near.Add(id);
|
||||||
|
else
|
||||||
|
far.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new TwoTierDiff(
|
||||||
|
ToLoadFar: far,
|
||||||
|
ToLoadNear: near,
|
||||||
|
ToPromote: System.Array.Empty<uint>(),
|
||||||
|
ToDemote: System.Array.Empty<uint>(),
|
||||||
|
ToUnload: System.Array.Empty<uint>());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recompute the visible window around a new center and return the
|
/// Recompute the visible window around a new center and return the
|
||||||
/// delta vs. the previous state. Hysteresis: landblocks aren't unloaded
|
/// delta vs. the previous state. Hysteresis: landblocks aren't unloaded
|
||||||
/// until they're further than <c>Radius + 2</c> from the new center,
|
/// until they're further than <c>Radius + 2</c> from the new center,
|
||||||
/// so boundary crossings don't thrash.
|
/// so boundary crossings don't thrash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RegionDiff RecenterTo(int newCx, int newCy)
|
public RegionDiff RecenterToSingleTier(int newCx, int newCy)
|
||||||
{
|
{
|
||||||
// Snapshot the old resident set so we can diff against it.
|
// Snapshot the old resident set so we can diff against it.
|
||||||
var oldResident = new HashSet<uint>(_resident);
|
var oldResident = new HashSet<uint>(_resident);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public class StreamingRegionTests
|
||||||
{
|
{
|
||||||
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
||||||
|
|
||||||
var diff = region.RecenterTo(50, 50);
|
var diff = region.RecenterToSingleTier(50, 50);
|
||||||
|
|
||||||
Assert.Empty(diff.ToLoad);
|
Assert.Empty(diff.ToLoad);
|
||||||
Assert.Empty(diff.ToUnload);
|
Assert.Empty(diff.ToUnload);
|
||||||
|
|
@ -52,7 +52,7 @@ public class StreamingRegionTests
|
||||||
// the radius+2 threshold, so it stays loaded (hysteresis keeps radius+2).
|
// the radius+2 threshold, so it stays loaded (hysteresis keeps radius+2).
|
||||||
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
||||||
|
|
||||||
var diff = region.RecenterTo(51, 50);
|
var diff = region.RecenterToSingleTier(51, 50);
|
||||||
|
|
||||||
Assert.Equal(5, diff.ToLoad.Count);
|
Assert.Equal(5, diff.ToLoad.Count);
|
||||||
Assert.Empty(diff.ToUnload);
|
Assert.Empty(diff.ToUnload);
|
||||||
|
|
@ -71,7 +71,7 @@ public class StreamingRegionTests
|
||||||
// x=48 is now 5 away, > radius+2 = 4 → unload. x=49 is 4 away, not > 4 → keep. x=50 is 3 away, not > 4 → keep.
|
// x=48 is now 5 away, > radius+2 = 4 → unload. x=49 is 4 away, not > 4 → keep. x=50 is 3 away, not > 4 → keep.
|
||||||
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
||||||
|
|
||||||
var diff = region.RecenterTo(53, 50);
|
var diff = region.RecenterToSingleTier(53, 50);
|
||||||
|
|
||||||
Assert.Equal(15, diff.ToLoad.Count);
|
Assert.Equal(15, diff.ToLoad.Count);
|
||||||
Assert.Equal(5, diff.ToUnload.Count);
|
Assert.Equal(5, diff.ToUnload.Count);
|
||||||
|
|
@ -82,7 +82,7 @@ public class StreamingRegionTests
|
||||||
{
|
{
|
||||||
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
|
||||||
|
|
||||||
var diff = region.RecenterTo(200, 200);
|
var diff = region.RecenterToSingleTier(200, 200);
|
||||||
|
|
||||||
Assert.Equal(25, diff.ToLoad.Count);
|
Assert.Equal(25, diff.ToLoad.Count);
|
||||||
Assert.Equal(25, diff.ToUnload.Count);
|
Assert.Equal(25, diff.ToUnload.Count);
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,18 @@ public class StreamingRegionTwoTierTests
|
||||||
// becomes (NearRadius+2) for the far-tier unload, which is wrong.
|
// becomes (NearRadius+2) for the far-tier unload, which is wrong.
|
||||||
Assert.Equal(region.FarRadius, region.Radius);
|
Assert.Equal(region.FarRadius, region.Radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RecenterTo_FirstTick_SplitsLoadIntoNearAndFar()
|
||||||
|
{
|
||||||
|
// near=1, far=3 → near window is 3×3=9, far window is 7×7-3×3=40 LBs.
|
||||||
|
var region = new StreamingRegion(centerX: 100, centerY: 100, nearRadius: 1, farRadius: 3);
|
||||||
|
var diff = region.ComputeFirstTickDiff();
|
||||||
|
|
||||||
|
Assert.Equal(9, diff.ToLoadNear.Count);
|
||||||
|
Assert.Equal(40, diff.ToLoadFar.Count);
|
||||||
|
Assert.Empty(diff.ToPromote);
|
||||||
|
Assert.Empty(diff.ToDemote);
|
||||||
|
Assert.Empty(diff.ToUnload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue