< Summary

Information
Class: GridForge.Grids.ScanCell
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Nodes/ScanCell.cs
Line coverage
97%
Covered lines: 90
Uncovered lines: 2
Coverable lines: 92
Total lines: 309
Line coverage: 97.8%
Branch coverage
94%
Covered branches: 83
Total branches: 88
Branch coverage: 94.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_GridIndex()100%11100%
get_World()100%11100%
get_CellKey()100%11100%
get_SpawnToken()100%11100%
get_CellOccupantCount()100%11100%
get_IsAllocated()100%11100%
get_IsOccupied()100%22100%
Initialize(...)100%11100%
Reset()100%66100%
AddOccupant(...)100%44100%
TryRemoveOccupant(...)90%1010100%
GetOccupants()100%66100%
GetConditionalOccupants()100%1414100%
AddOccupantsWithinRadiusTo(...)93.75%161690%
AddOccupantsWithinRadiusTo(...)83.33%181890.9%
GetOccupantsFor()100%66100%
TryGetOccupantAt(...)100%66100%
GetHashCode()100%11100%

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Grids/Nodes/ScanCell.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Spatial;
 3using SwiftCollections;
 4using System;
 5using System.Collections.Generic;
 6
 7namespace GridForge.Grids;
 8
 9/// <summary>
 10/// Represents a spatial partition within a grid, managing occupants at a finer granularity than grid voxels.
 11/// Handles efficient tracking, retrieval, and removal of occupants within a designated scan cell area.
 12/// </summary>
 13public class ScanCell
 14{
 15    #region Properties
 16
 17    /// <summary>
 18    /// The world-local index of the grid this scan cell belongs to.
 19    /// </summary>
 483720    public ushort GridIndex { get; private set; }
 21
 22    /// <summary>
 23    /// The world that owns this scan cell through its parent grid.
 24    /// </summary>
 260825    public GridWorld? World { get; private set; }
 26
 27    /// <summary>
 28    /// A unique identifier for this scan cell in the grid.
 29    /// </summary>
 484530    public int CellKey { get; private set; }
 31
 32
 33    /// <summary>
 34    /// Unique token identifying this scan cell instance.
 35    /// </summary>
 125536    public int SpawnToken { get; private set; }
 37
 38    /// <summary>
 39    /// Maps a <see cref="Voxel.WorldIndex"/> to a bucket of associated <see cref="IVoxelOccupant"/> instances.
 40    /// </summary>
 41    private SwiftDictionary<WorldVoxelIndex, SwiftBucket<IVoxelOccupant>>? _voxelOccupants;
 42
 43    /// <summary>
 44    /// The total number of occupants in this scan cell.
 45    /// </summary>
 358046    public int CellOccupantCount { get; private set; }
 47
 48    /// <summary>
 49    /// Indicates whether this scan cell is currently allocated in the grid.
 50    /// </summary>
 516051    public bool IsAllocated { get; private set; }
 52
 53    /// <summary>
 54    /// Determines whether this scan cell is occupied by any occupants.
 55    /// A scan cell is only considered occupied if it is allocated and contains at least one occupant.
 56    /// </summary>
 139357    public bool IsOccupied => IsAllocated && CellOccupantCount > 0;
 58
 59    #endregion
 60
 61    #region Initialization & Reset
 62
 63    /// <summary>
 64    /// Initializes the scan cell with the specified grid index and unique cell key.
 65    /// </summary>
 66    internal void Initialize(GridWorld world, ushort gridIndex, int cellKey)
 67    {
 125568        World = world;
 125569        GridIndex = gridIndex;
 125570        CellKey = cellKey;
 125571        SpawnToken = GetHashCode();
 125572        IsAllocated = true;
 125573    }
 74
 75    /// <summary>
 76    /// Resets the scan cell, clearing all occupants and returning memory to object pools.
 77    /// This effectively marks the scan cell as deallocated and removes all references.
 78    /// </summary>
 79    internal void Reset()
 80    {
 125681        if (!IsAllocated)
 182            return;
 83
 125584        if (_voxelOccupants != null)
 85        {
 28086            foreach (var kvp in _voxelOccupants)
 87            {
 9888                SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 9889                GridOccupantManager.ForgetTrackedOccupancies(World, bucket, kvp.Key);
 9890                Pools.VoxelOccupantBucketPool.Release(bucket);
 91            }
 92
 4293            Pools.VoxelOccupantDictionaryPool.Release(_voxelOccupants);
 4294            _voxelOccupants = null;
 95        }
 96
 125597        CellOccupantCount = 0;
 98
 125599        World = null;
 1255100        GridIndex = ushort.MaxValue;
 1255101        CellKey = byte.MaxValue;
 102
 1255103        IsAllocated = false;
 1255104    }
 105
 106    #endregion
 107
 108    #region Occupant Management
 109
 110    /// <summary>
 111    /// Adds an occupant to this scan cell and tracks its presence.
 112    /// </summary>
 113    /// <param name="index">The global index of the voxel where the occupant resides.</param>
 114    /// <param name="occupant">The occupant instance to add.</param>
 115    /// <returns>An integer ticket representing the occupant's position in the data structure.</returns>
 116    internal int AddOccupant(WorldVoxelIndex index, IVoxelOccupant occupant)
 117    {
 316118        _voxelOccupants ??= Pools.VoxelOccupantDictionaryPool.Rent();
 316119        if (!_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> bucket))
 120        {
 112121            bucket = Pools.VoxelOccupantBucketPool.Rent();
 112122            _voxelOccupants[index] = bucket;
 123        }
 124
 316125        int ticket = bucket.Add(occupant);
 316126        CellOccupantCount++;
 316127        return ticket;
 128    }
 129
 130    /// <summary>
 131    /// Removes an occupant from this scan cell.
 132    /// </summary>
 133    /// <param name="index">The global index of the voxel the occupant was assigned to.</param>
 134    /// <param name="ticket">The ticket assigned to the occupant instance from this scancell.</param>
 135    /// <returns>True if the occupant was successfully removed; otherwise, false.</returns>
 136    internal bool TryRemoveOccupant(
 137        WorldVoxelIndex index,
 138        int ticket)
 139    {
 151140        if (!IsOccupied || _voxelOccupants?.TryGetValue(index, out var bucket) != true)
 2141            return false;
 142
 149143        if (!bucket.TryRemoveAt(ticket))
 1144            return false;
 145
 146        // If the occupant was the last in its bucket, remove the entire bucket
 148147        if (bucket.Count == 0)
 148        {
 14149            _voxelOccupants.Remove(index);
 14150            Pools.VoxelOccupantBucketPool.Release(bucket);
 151        }
 152
 148153        CellOccupantCount--;
 154
 148155        return true;
 156    }
 157
 158    #endregion
 159
 160    #region Occupant Retrieval
 161
 162    /// <summary>
 163    /// Retrieves all occupants associated with this ScanCell.
 164    /// </summary>
 165    /// <returns>An enumerable of occupants within this scan cell.</returns>
 166    public IEnumerable<IVoxelOccupant> GetOccupants()
 167    {
 9168        if (_voxelOccupants == null)
 1169            yield break;
 170
 30171        foreach (SwiftBucket<IVoxelOccupant> bucket in _voxelOccupants.Values)
 172        {
 162173            foreach (IVoxelOccupant voxelOccupant in bucket)
 74174                yield return voxelOccupant;
 175        }
 6176    }
 177
 178    /// <summary>
 179    /// Retrieves occupants whose group Ids match a given condition.
 180    /// </summary>
 181    public IEnumerable<IVoxelOccupant> GetConditionalOccupants(
 182        Func<IVoxelOccupant, bool>? occupantCondition = null,
 183        Func<byte, bool>? groupConditional = null)
 184    {
 9185        if (_voxelOccupants == null)
 1186            yield break;
 187
 188        // Loop through each voxel's bucket and filter by the cluster condition
 31189        foreach (var bucket in _voxelOccupants.Values)
 190        {
 43191            foreach (var occupant in bucket)
 192            {
 14193                if (occupantCondition != null && !occupantCondition(occupant))
 194                    continue;
 195
 11196                if (groupConditional != null && !groupConditional(occupant.OccupantGroupId))
 197                    continue;
 198
 7199                yield return occupant;
 200            }
 201        }
 7202    }
 203
 204    /// <summary>
 205    /// Appends occupants within the squared radius to caller-owned storage without allocating an iterator.
 206    /// </summary>
 207    internal void AddOccupantsWithinRadiusTo(
 208        SwiftList<IVoxelOccupant> results,
 209        Vector3d position,
 210        Fixed64 squaredRadius,
 211        Func<IVoxelOccupant, bool>? occupantCondition = null,
 212        Func<byte, bool>? groupCondition = null)
 213    {
 266214        if (_voxelOccupants == null)
 0215            return;
 216
 33456217        foreach (var kvp in _voxelOccupants)
 218        {
 16462219            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 65848220            foreach (IVoxelOccupant occupant in bucket)
 221            {
 16462222                if (occupantCondition != null && !occupantCondition(occupant))
 223                    continue;
 224
 16461225                if (groupCondition != null && !groupCondition(occupant.OccupantGroupId))
 226                    continue;
 227
 16459228                if ((occupant.Position - position).SqrMagnitude <= squaredRadius)
 16456229                    results.Add(occupant);
 230            }
 231        }
 266232    }
 233
 234    /// <summary>
 235    /// Appends typed occupants within the squared radius to caller-owned storage without LINQ.
 236    /// </summary>
 237    internal void AddOccupantsWithinRadiusTo<T>(
 238        SwiftList<T> results,
 239        Vector3d position,
 240        Fixed64 squaredRadius,
 241        Func<IVoxelOccupant, bool>? occupantCondition = null,
 242        Func<byte, bool>? groupCondition = null) where T : IVoxelOccupant
 243    {
 1244        if (_voxelOccupants == null)
 0245            return;
 246
 4247        foreach (var kvp in _voxelOccupants)
 248        {
 1249            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 4250            foreach (IVoxelOccupant occupant in bucket)
 251            {
 1252                if (occupantCondition != null && !occupantCondition(occupant))
 253                    continue;
 254
 1255                if (groupCondition != null && !groupCondition(occupant.OccupantGroupId))
 256                    continue;
 257
 1258                if (occupant is T typedOccupant
 1259                    && (occupant.Position - position).SqrMagnitude <= squaredRadius)
 260                {
 1261                    results.Add(typedOccupant);
 262                }
 263            }
 264        }
 1265    }
 266
 267    /// <summary>
 268    /// Retrieves all occupants associated with a specific voxel spawn token within this scan cell.
 269    /// </summary>
 270    /// <param name="index">The global index of the voxel.</param>
 271    /// <returns>An enumerable collection of occupants assigned to the voxel.</returns>
 272    public IEnumerable<IVoxelOccupant> GetOccupantsFor(WorldVoxelIndex index)
 273    {
 5274        if (_voxelOccupants == null || !_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> voxelOccupant
 2275            yield break;
 276
 14277        foreach (IVoxelOccupant voxelOccupant in voxelOccupants)
 4278            yield return voxelOccupant;
 3279    }
 280
 281    /// <summary>
 282    /// Attempts to retrieve a specific occupant in this scan cell using a voxel's spawn key and occupant ticket.
 283    /// </summary>
 284    /// <param name="index">The global index of the voxel the occupant belongs to.</param>
 285    /// <param name="occupantTicket">The unique ticket identifying the occupant.</param>
 286    /// <param name="voxelOccupant">The retrieved occupant if found.</param>
 287    /// <returns>True if the occupant was found, otherwise false.</returns>
 288    public bool TryGetOccupantAt(
 289        WorldVoxelIndex index,
 290        int occupantTicket,
 291        out IVoxelOccupant? voxelOccupant)
 292    {
 12293        voxelOccupant = null;
 12294        if (_voxelOccupants == null
 12295            || !_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> voxelOccupants)
 12296            || !voxelOccupants.IsAllocated(occupantTicket))
 297        {
 4298            return false;
 299        }
 300
 8301        voxelOccupant = voxelOccupants[occupantTicket];
 8302        return true;
 303    }
 304
 305    #endregion
 306
 307    /// <inheritdoc/>
 2327308    public override int GetHashCode() => SwiftHashTools.CombineHashCodes(GridIndex, CellKey);
 309}