refactor: Add BitMask256 utility for 256-bit bitmask operations (#2300)

### Summary

- Create BitMask256 struct with scalar operations (benchmarks showed AVX2 vectorization provides no benefit for this size)
- Refactor Map.cs to use BitMask256 for full Z range support (-128 to 127)
- Remove SectorSpawnCache struct, use BitMask256 directly in manager
- Update tests to use BitMask256 directly
- Remove unused test assertions for old 64-bit behavior
This commit is contained in:
Kamron Batman 2025-12-29 12:54:49 -08:00 • committed by GitHub
parent d3b43f6f0e
commit 598223703f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 482 additions and 261 deletions

View file

@ -15,130 +15,13 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using Server.Collections;
using Server.Regions;
namespace Server.Engines.Spawners;
/// <summary>
/// Cached spawn position data for a 16x16 sector.
/// Uses a bitmap to track valid spawn positions (256 bits = 4 ulongs = 32 bytes).
/// </summary>
public struct SectorSpawnCache
{
public ulong Bits0;
public ulong Bits1;
public ulong Bits2;
public ulong Bits3;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetCount() =>
BitOperations.PopCount(Bits0) +
BitOperations.PopCount(Bits1) +
BitOperations.PopCount(Bits2) +
BitOperations.PopCount(Bits3);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetBit(int bitIndex)
{
var ulongIndex = bitIndex >> 6; // / 64
var bitPosition = bitIndex & 0x3F; // % 64
switch (ulongIndex)
{
case 0: Bits0 |= 1UL << bitPosition; break;
case 1: Bits1 |= 1UL << bitPosition; break;
case 2: Bits2 |= 1UL << bitPosition; break;
case 3: Bits3 |= 1UL << bitPosition; break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool GetBit(int bitIndex)
{
var ulongIndex = bitIndex >> 6;
var bitPosition = bitIndex & 0x3F;
return ulongIndex switch
{
0 => (Bits0 & (1UL << bitPosition)) != 0,
1 => (Bits1 & (1UL << bitPosition)) != 0,
2 => (Bits2 & (1UL << bitPosition)) != 0,
3 => (Bits3 & (1UL << bitPosition)) != 0,
_ => false
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ClearBit(int bitIndex)
{
var ulongIndex = bitIndex >> 6;
var bitPosition = bitIndex & 0x3F;
switch (ulongIndex)
{
case 0: Bits0 &= ~(1UL << bitPosition); break;
case 1: Bits1 &= ~(1UL << bitPosition); break;
case 2: Bits2 &= ~(1UL << bitPosition); break;
case 3: Bits3 &= ~(1UL << bitPosition); break;
}
}
/// <summary>
/// Gets the Nth set bit position (0-indexed) from the bitmap.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetNthBitPosition(int n)
{
var count0 = BitOperations.PopCount(Bits0);
if (n < count0)
{
return GetNthBitInUlong(Bits0, n);
}
n -= count0;
var count1 = BitOperations.PopCount(Bits1);
if (n < count1)
{
return 64 + GetNthBitInUlong(Bits1, n);
}
n -= count1;
var count2 = BitOperations.PopCount(Bits2);
if (n < count2)
{
return 128 + GetNthBitInUlong(Bits2, n);
}
n -= count2;
return 192 + GetNthBitInUlong(Bits3, n);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetNthBitInUlong(ulong bits, int n)
{
// BMI2 PDEP: O(1) - deposits the nth selector bit into the position of the nth set bit
if (Bmi2.X64.IsSupported)
{
var deposited = Bmi2.X64.ParallelBitDeposit(1UL << n, bits);
return BitOperations.TrailingZeroCount(deposited);
}
// Fallback: O(popcount) - clear n set bits, then find position of next one
while (n > 0 && bits != 0)
{
bits &= bits - 1; // Clear lowest set bit
n--;
}
return bits == 0 ? -1 : BitOperations.TrailingZeroCount(bits);
}
}
/// <summary>
/// Global manager for sector-based spawn position caching.
/// Shared across all spawners for efficient memory usage and house invalidation.
@ -147,8 +30,8 @@ public struct SectorSpawnCache
/// </summary>
public static class SectorSpawnCacheManager
{
private static readonly Dictionary<(Map, int, int), SectorSpawnCache> _landCaches = [];
private static readonly Dictionary<(Map, int, int), SectorSpawnCache> _waterCaches = [];
private static readonly Dictionary<(Map, int, int), BitMask256> _landCaches = [];
private static readonly Dictionary<(Map, int, int), BitMask256> _waterCaches = [];
/// <summary>
/// Marks a position as valid for spawning in the global cache.
@ -245,7 +128,7 @@ public static class SectorSpawnCacheManager
if (caches.TryGetValue((map, sx, sy), out var cache))
{
var count = cache.GetCount();
var count = cache.PopCount();
if (count > 0)
{
sectorList.Add((sx, sy, count));
@ -281,7 +164,7 @@ public static class SectorSpawnCacheManager
}
var indexInSector = targetIndex - currentIndex;
var bitPosition = cache.GetNthBitPosition(indexInSector);
var bitPosition = cache.GetNthSetBit(indexInSector);
var localX = bitPosition & (Map.SectorSize - 1);
var localY = bitPosition >> Map.SectorShift;