< Summary

Information
Class: GridForge.Grids.ScanCell
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Nodes/ScanCell.cs
Line coverage
100%
Covered lines: 114
Uncovered lines: 0
Coverable lines: 114
Total lines: 407
Line coverage: 100%
Branch coverage
100%
Covered branches: 108
Total branches: 108
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_IsOccupied()100%22100%
Initialize(...)100%11100%
Reset()100%66100%
AddOccupant(...)100%44100%
TryRemoveOccupant(...)100%88100%
GetOccupants()100%66100%
GetConditionalOccupants()100%1414100%
AddOccupantsWithinRadiusTo(...)100%1010100%
AddOccupantsWithinRadius2dTo(...)100%1212100%
AddOccupantsWithinRadiusTo(...)100%1010100%
AddOccupantsWithinRadius2dTo(...)100%1414100%
OccupantPassesFilters(...)100%66100%
IsWithinSquaredRadius(...)100%11100%
TryGetTypedOccupantWithinRadius(...)100%44100%
GetOccupantsFor()100%66100%
TryGetOccupantAt(...)100%66100%
GetHashCode()100%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// ScanCell.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using GridForge.Spatial;
 10using SwiftCollections;
 11using SwiftCollections.Utility;
 12using System;
 13using System.Collections.Generic;
 14
 15namespace GridForge.Grids;
 16
 17/// <summary>
 18/// Represents a spatial partition within a grid, managing occupants at a finer granularity than grid voxels.
 19/// Handles efficient tracking, retrieval, and removal of occupants within a designated scan cell area.
 20/// </summary>
 21public class ScanCell
 22{
 23    #region Properties
 24
 25    /// <summary>
 26    /// The world-local index of the grid this scan cell belongs to.
 27    /// </summary>
 28    public ushort GridIndex { get; private set; }
 29
 30    /// <summary>
 31    /// The world that owns this scan cell through its parent grid.
 32    /// </summary>
 33    public GridWorld? World { get; private set; }
 34
 35    /// <summary>
 36    /// A unique identifier for this scan cell in the grid.
 37    /// </summary>
 38    public int CellKey { get; private set; }
 39
 40
 41    /// <summary>
 42    /// Unique token identifying this scan cell instance.
 43    /// </summary>
 44    public int SpawnToken { get; private set; }
 45
 46    /// <summary>
 47    /// Maps a <see cref="Voxel.WorldIndex"/> to a bucket of associated <see cref="IVoxelOccupant"/> instances.
 48    /// </summary>
 49    private SwiftDictionary<WorldVoxelIndex, SwiftBucket<IVoxelOccupant>>? _voxelOccupants;
 50
 51    /// <summary>
 52    /// The total number of occupants in this scan cell.
 53    /// </summary>
 54    public int CellOccupantCount { get; private set; }
 55
 56    /// <summary>
 57    /// Indicates whether this scan cell is currently allocated in the grid.
 58    /// </summary>
 59    public bool IsAllocated { get; private set; }
 60
 61    /// <summary>
 62    /// Determines whether this scan cell is occupied by any occupants.
 63    /// A scan cell is only considered occupied if it is allocated and contains at least one occupant.
 64    /// </summary>
 248165    public bool IsOccupied => IsAllocated && CellOccupantCount > 0;
 66
 67    #endregion
 68
 69    #region Initialization & Reset
 70
 71    /// <summary>
 72    /// Initializes the scan cell with the specified grid index and unique cell key.
 73    /// </summary>
 74    internal void Initialize(GridWorld world, ushort gridIndex, int cellKey)
 75    {
 183776        World = world;
 183777        GridIndex = gridIndex;
 183778        CellKey = cellKey;
 183779        SpawnToken = GetHashCode();
 183780        IsAllocated = true;
 183781    }
 82
 83    /// <summary>
 84    /// Resets the scan cell, clearing all occupants and returning memory to object pools.
 85    /// This effectively marks the scan cell as deallocated and removes all references.
 86    /// </summary>
 87    internal void Reset()
 88    {
 183889        if (!IsAllocated)
 190            return;
 91
 183792        if (_voxelOccupants != null)
 93        {
 47694            foreach (var kvp in _voxelOccupants)
 95            {
 17896                SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 17897                GridOccupantManager.ForgetTrackedOccupancies(World, bucket, kvp.Key);
 17898                Pools.VoxelOccupantBucketPool.Release(bucket);
 99            }
 100
 60101            Pools.VoxelOccupantDictionaryPool.Release(_voxelOccupants);
 60102            _voxelOccupants = null;
 103        }
 104
 1837105        CellOccupantCount = 0;
 106
 1837107        World = null;
 1837108        GridIndex = ushort.MaxValue;
 1837109        CellKey = byte.MaxValue;
 110
 1837111        IsAllocated = false;
 1837112    }
 113
 114    #endregion
 115
 116    #region Occupant Management
 117
 118    /// <summary>
 119    /// Adds an occupant to this scan cell and tracks its presence.
 120    /// </summary>
 121    /// <param name="index">The global index of the voxel where the occupant resides.</param>
 122    /// <param name="occupant">The occupant instance to add.</param>
 123    /// <returns>An integer ticket representing the occupant's position in the data structure.</returns>
 124    internal int AddOccupant(WorldVoxelIndex index, IVoxelOccupant occupant)
 125    {
 406126        _voxelOccupants ??= Pools.VoxelOccupantDictionaryPool.Rent();
 406127        if (!_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> bucket))
 128        {
 199129            bucket = Pools.VoxelOccupantBucketPool.Rent();
 199130            _voxelOccupants[index] = bucket;
 131        }
 132
 406133        int ticket = bucket.Add(occupant);
 406134        CellOccupantCount++;
 406135        return ticket;
 136    }
 137
 138    /// <summary>
 139    /// Removes an occupant from this scan cell.
 140    /// </summary>
 141    /// <param name="index">The global index of the voxel the occupant was assigned to.</param>
 142    /// <param name="ticket">The ticket assigned to the occupant instance from this scancell.</param>
 143    /// <returns>True if the occupant was successfully removed; otherwise, false.</returns>
 144    internal bool TryRemoveOccupant(
 145        WorldVoxelIndex index,
 146        int ticket)
 147    {
 161148        if (!IsOccupied)
 1149            return false;
 150
 160151        if (!_voxelOccupants!.TryGetValue(index, out var bucket))
 1152            return false;
 153
 159154        if (!bucket.TryRemoveAt(ticket))
 2155            return false;
 156
 157        // If the occupant was the last in its bucket, remove the entire bucket
 157158        if (bucket.Count == 0)
 159        {
 21160            _voxelOccupants.Remove(index);
 21161            Pools.VoxelOccupantBucketPool.Release(bucket);
 162        }
 163
 157164        CellOccupantCount--;
 165
 157166        return true;
 167    }
 168
 169    #endregion
 170
 171    #region Occupant Retrieval
 172
 173    /// <summary>
 174    /// Retrieves all occupants associated with this ScanCell.
 175    /// </summary>
 176    /// <returns>An enumerable of occupants within this scan cell.</returns>
 177    public IEnumerable<IVoxelOccupant> GetOccupants()
 178    {
 9179        if (_voxelOccupants == null)
 1180            yield break;
 181
 30182        foreach (SwiftBucket<IVoxelOccupant> bucket in _voxelOccupants.Values)
 183        {
 162184            foreach (IVoxelOccupant voxelOccupant in bucket)
 74185                yield return voxelOccupant;
 186        }
 6187    }
 188
 189    /// <summary>
 190    /// Retrieves occupants whose group Ids match a given condition.
 191    /// </summary>
 192    public IEnumerable<IVoxelOccupant> GetConditionalOccupants(
 193        Func<IVoxelOccupant, bool>? occupantCondition = null,
 194        Func<byte, bool>? groupConditional = null)
 195    {
 9196        if (_voxelOccupants == null)
 1197            yield break;
 198
 199        // Loop through each voxel's bucket and filter by the cluster condition
 31200        foreach (var bucket in _voxelOccupants.Values)
 201        {
 43202            foreach (var occupant in bucket)
 203            {
 14204                if (occupantCondition != null && !occupantCondition(occupant))
 205                    continue;
 206
 11207                if (groupConditional != null && !groupConditional(occupant.OccupantGroupId))
 208                    continue;
 209
 7210                yield return occupant;
 211            }
 212        }
 7213    }
 214
 215    /// <summary>
 216    /// Appends occupants within the squared radius to caller-owned storage without allocating an iterator.
 217    /// </summary>
 218    internal void AddOccupantsWithinRadiusTo(
 219        SwiftList<IVoxelOccupant> results,
 220        Vector3d position,
 221        Fixed64 squaredRadius,
 222        Func<IVoxelOccupant, bool>? occupantCondition = null,
 223        Func<byte, bool>? groupCondition = null)
 224    {
 273225        if (_voxelOccupants == null)
 1226            return;
 227
 33484228        foreach (var kvp in _voxelOccupants)
 229        {
 16470230            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 65882231            foreach (IVoxelOccupant occupant in bucket)
 232            {
 16471233                if (OccupantPassesFilters(occupant, occupantCondition, groupCondition)
 16471234                    && IsWithinSquaredRadius(occupant, position, squaredRadius))
 235                {
 16461236                    results.Add(occupant);
 237                }
 238            }
 239        }
 272240    }
 241
 242    /// <summary>
 243    /// Appends occupants within the XZ squared radius on the selected local Y voxel layer.
 244    /// </summary>
 245    internal void AddOccupantsWithinRadius2dTo(
 246        SwiftList<IVoxelOccupant> results,
 247        Vector3d position,
 248        int localLayerY,
 249        Fixed64 squaredRadius,
 250        Func<IVoxelOccupant, bool>? occupantCondition = null,
 251        Func<byte, bool>? groupCondition = null)
 252    {
 264253        if (_voxelOccupants == null)
 1254            return;
 255
 33440256        foreach (var kvp in _voxelOccupants)
 257        {
 16457258            if (kvp.Key.VoxelIndex.y != localLayerY)
 259                continue;
 260
 16453261            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 65816262            foreach (IVoxelOccupant occupant in bucket)
 263            {
 16455264                if (OccupantPassesFilters(occupant, occupantCondition, groupCondition)
 16455265                    && GridPlane2d.DistanceSquaredXZ(occupant.Position, position) <= squaredRadius)
 266                {
 16452267                    results.Add(occupant);
 268                }
 269            }
 270        }
 263271    }
 272
 273    /// <summary>
 274    /// Appends typed occupants within the squared radius to caller-owned storage without LINQ.
 275    /// </summary>
 276    internal void AddOccupantsWithinRadiusTo<T>(
 277        SwiftList<T> results,
 278        Vector3d position,
 279        Fixed64 squaredRadius,
 280        Func<IVoxelOccupant, bool>? occupantCondition = null,
 281        Func<byte, bool>? groupCondition = null) where T : IVoxelOccupant
 282    {
 4283        if (_voxelOccupants == null)
 1284            return;
 285
 12286        foreach (var kvp in _voxelOccupants)
 287        {
 3288            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 14289            foreach (IVoxelOccupant occupant in bucket)
 290            {
 4291                if (!OccupantPassesFilters(occupant, occupantCondition, groupCondition))
 292                    continue;
 293
 3294                if (TryGetTypedOccupantWithinRadius(occupant, position, squaredRadius, out T typedOccupant))
 2295                    results.Add(typedOccupant);
 296            }
 297        }
 3298    }
 299
 300    /// <summary>
 301    /// Appends typed occupants within the XZ squared radius on the selected local Y voxel layer.
 302    /// </summary>
 303    internal void AddOccupantsWithinRadius2dTo<T>(
 304        SwiftList<T> results,
 305        Vector3d position,
 306        int localLayerY,
 307        Fixed64 squaredRadius,
 308        Func<IVoxelOccupant, bool>? occupantCondition = null,
 309        Func<byte, bool>? groupCondition = null) where T : IVoxelOccupant
 310    {
 5311        if (_voxelOccupants == null)
 1312            return;
 313
 20314        foreach (var kvp in _voxelOccupants)
 315        {
 6316            if (kvp.Key.VoxelIndex.y != localLayerY)
 317                continue;
 318
 4319            SwiftBucket<IVoxelOccupant> bucket = kvp.Value;
 20320            foreach (IVoxelOccupant occupant in bucket)
 321            {
 6322                if (!OccupantPassesFilters(occupant, occupantCondition, groupCondition))
 323                    continue;
 324
 4325                if (occupant is T typedOccupant
 4326                    && GridPlane2d.DistanceSquaredXZ(occupant.Position, position) <= squaredRadius)
 327                {
 2328                    results.Add(typedOccupant);
 329                }
 330            }
 331        }
 4332    }
 333
 334    private static bool OccupantPassesFilters(
 335        IVoxelOccupant occupant,
 336        Func<IVoxelOccupant, bool>? occupantCondition,
 337        Func<byte, bool>? groupCondition)
 338    {
 32936339        return (occupantCondition == null || occupantCondition(occupant))
 32936340            && (groupCondition == null || groupCondition(occupant.OccupantGroupId));
 341    }
 342
 343    private static bool IsWithinSquaredRadius(
 344        IVoxelOccupant occupant,
 345        Vector3d position,
 346        Fixed64 squaredRadius)
 347    {
 16468348        return (occupant.Position - position).MagnitudeSquared <= squaredRadius;
 349    }
 350
 351    private static bool TryGetTypedOccupantWithinRadius<T>(
 352        IVoxelOccupant occupant,
 353        Vector3d position,
 354        Fixed64 squaredRadius,
 355        out T typedOccupant) where T : IVoxelOccupant
 356    {
 3357        typedOccupant = default!;
 3358        if (occupant is not T candidate || !IsWithinSquaredRadius(occupant, position, squaredRadius))
 1359            return false;
 360
 2361        typedOccupant = candidate;
 2362        return true;
 363    }
 364
 365    /// <summary>
 366    /// Retrieves all occupants associated with a specific voxel spawn token within this scan cell.
 367    /// </summary>
 368    /// <param name="index">The global index of the voxel.</param>
 369    /// <returns>An enumerable collection of occupants assigned to the voxel.</returns>
 370    public IEnumerable<IVoxelOccupant> GetOccupantsFor(WorldVoxelIndex index)
 371    {
 5372        if (_voxelOccupants == null || !_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> voxelOccupant
 2373            yield break;
 374
 14375        foreach (IVoxelOccupant voxelOccupant in voxelOccupants)
 4376            yield return voxelOccupant;
 3377    }
 378
 379    /// <summary>
 380    /// Attempts to retrieve a specific occupant in this scan cell using a voxel's spawn key and occupant ticket.
 381    /// </summary>
 382    /// <param name="index">The global index of the voxel the occupant belongs to.</param>
 383    /// <param name="occupantTicket">The unique ticket identifying the occupant.</param>
 384    /// <param name="voxelOccupant">The retrieved occupant if found.</param>
 385    /// <returns>True if the occupant was found, otherwise false.</returns>
 386    public bool TryGetOccupantAt(
 387        WorldVoxelIndex index,
 388        int occupantTicket,
 389        out IVoxelOccupant? voxelOccupant)
 390    {
 14391        voxelOccupant = null;
 14392        if (_voxelOccupants == null
 14393            || !_voxelOccupants.TryGetValue(index, out SwiftBucket<IVoxelOccupant> voxelOccupants)
 14394            || !voxelOccupants.IsAllocated(occupantTicket))
 395        {
 4396            return false;
 397        }
 398
 10399        voxelOccupant = voxelOccupants[occupantTicket];
 10400        return true;
 401    }
 402
 403    #endregion
 404
 405    /// <inheritdoc/>
 4040406    public override int GetHashCode() => SwiftHashTools.CombineHashCodes(GridIndex, CellKey);
 407}

Methods/Properties

get_IsOccupied()
Initialize(GridForge.Grids.GridWorld,System.UInt16,System.Int32)
Reset()
AddOccupant(GridForge.Spatial.WorldVoxelIndex,GridForge.Spatial.IVoxelOccupant)
TryRemoveOccupant(GridForge.Spatial.WorldVoxelIndex,System.Int32)
GetOccupants()
GetConditionalOccupants()
AddOccupantsWithinRadiusTo(SwiftCollections.SwiftList`1<GridForge.Spatial.IVoxelOccupant>,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Func`2<GridForge.Spatial.IVoxelOccupant,System.Boolean>,System.Func`2<System.Byte,System.Boolean>)
AddOccupantsWithinRadius2dTo(SwiftCollections.SwiftList`1<GridForge.Spatial.IVoxelOccupant>,FixedMathSharp.Vector3d,System.Int32,FixedMathSharp.Fixed64,System.Func`2<GridForge.Spatial.IVoxelOccupant,System.Boolean>,System.Func`2<System.Byte,System.Boolean>)
AddOccupantsWithinRadiusTo(SwiftCollections.SwiftList`1<T>,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Func`2<GridForge.Spatial.IVoxelOccupant,System.Boolean>,System.Func`2<System.Byte,System.Boolean>)
AddOccupantsWithinRadius2dTo(SwiftCollections.SwiftList`1<T>,FixedMathSharp.Vector3d,System.Int32,FixedMathSharp.Fixed64,System.Func`2<GridForge.Spatial.IVoxelOccupant,System.Boolean>,System.Func`2<System.Byte,System.Boolean>)
OccupantPassesFilters(GridForge.Spatial.IVoxelOccupant,System.Func`2<GridForge.Spatial.IVoxelOccupant,System.Boolean>,System.Func`2<System.Byte,System.Boolean>)
IsWithinSquaredRadius(GridForge.Spatial.IVoxelOccupant,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
TryGetTypedOccupantWithinRadius(GridForge.Spatial.IVoxelOccupant,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,T&)
GetOccupantsFor()
TryGetOccupantAt(GridForge.Spatial.WorldVoxelIndex,System.Int32,GridForge.Spatial.IVoxelOccupant&)
GetHashCode()