< Summary

Information
Class: GridForge.Grids.GridObstacleManager
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Managers/GridObstacleManager.cs
Line coverage
100%
Covered lines: 102
Uncovered lines: 0
Coverable lines: 102
Total lines: 309
Line coverage: 100%
Branch coverage
91%
Covered branches: 51
Total branches: 56
Branch coverage: 91%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Grids/Managers/GridObstacleManager.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Spatial;
 3using SwiftCollections.Pool;
 4using System;
 5
 6namespace GridForge.Grids;
 7
 8/// <summary>
 9/// Handles the addition, removal, and tracking of obstacles within a grid.
 10/// Ensures thread safety and proper event notifications when obstacles change.
 11/// </summary>
 12public static class GridObstacleManager
 13{
 14    #region Constants & Events
 15
 16    /// <summary>
 17    /// Maximum number of obstacles that can exist on a single voxel.
 18    /// </summary>
 19    public const byte MaxObstacleCount = byte.MaxValue;
 20
 21    /// <summary>
 22    /// Event triggered when an obstacle is added.
 23    /// </summary>
 24    private static Action<ObstacleEventInfo>? _onObstacleAdded;
 25
 26    /// <inheritdoc cref="_onObstacleAdded"/>
 27    public static event Action<ObstacleEventInfo> OnObstacleAdded
 28    {
 329        add => _onObstacleAdded += value;
 330        remove => _onObstacleAdded -= value;
 31    }
 32
 33    /// <summary>
 34    /// Event triggered when an obstacle is removed.
 35    /// </summary>
 36    private static Action<ObstacleEventInfo>? _onObstacleRemoved;
 37
 38    /// <inheritdoc cref="_onObstacleRemoved"/>
 39    public static event Action<ObstacleEventInfo> OnObstacleRemoved
 40    {
 241        add => _onObstacleRemoved += value;
 242        remove => _onObstacleRemoved -= value;
 43    }
 44
 45    /// <summary>
 46    /// Event triggered when all obstacles on a voxel are cleared at once.
 47    /// </summary>
 48    private static Action<ObstacleClearEventInfo>? _onObstaclesCleared;
 49
 50    /// <inheritdoc cref="_onObstaclesCleared"/>
 51    public static event Action<ObstacleClearEventInfo> OnObstaclesCleared
 52    {
 353        add => _onObstaclesCleared += value;
 354        remove => _onObstaclesCleared -= value;
 55    }
 56
 57    #endregion
 58
 59    #region Public Methods
 60
 61    /// <summary>
 62    /// Attempts to add an obstacle at the given world-scoped voxel identity in the supplied world.
 63    /// </summary>
 64    public static bool TryAddObstacle(
 65        GridWorld world,
 66        WorldVoxelIndex index,
 67        BoundsKey obstacleSpawnToken)
 68    {
 269        return world != null
 270            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 271            && grid!.TryAddObstacle(voxel!, obstacleSpawnToken) == true;
 72    }
 73
 74    /// <summary>
 75    /// Attempts to add an obstacle at the given world position.
 76    /// </summary>
 77    public static bool TryAddObstacle(this VoxelGrid grid, Vector3d position, BoundsKey obstacleSpawnToken)
 78    {
 379        return grid.TryGetVoxel(position, out Voxel? voxel)
 380            && grid.TryAddObstacle(voxel!, obstacleSpawnToken);
 81    }
 82
 83    /// <summary>
 84    /// Adds an obstacle to this voxel.
 85    /// </summary>
 86    /// <param name="grid"></param>
 87    /// <param name="targetVoxel"></param>
 88    /// <param name="obstacleSpawnToken"></param>
 89    /// <exception cref="Exception"></exception>
 90    public static bool TryAddObstacle(this VoxelGrid grid, Voxel targetVoxel, BoundsKey obstacleSpawnToken)
 91    {
 67992        if (!targetVoxel.IsBlockable)
 293            return false;
 94
 95        byte obstacleCount;
 96        uint gridVersion;
 97
 67798        lock (grid.ObstacleSyncRoot)
 99        {
 677100            targetVoxel.ObstacleTracker ??= SwiftHashSetPool<BoundsKey>.Shared.Rent();
 677101            if (!targetVoxel.ObstacleTracker.Add(obstacleSpawnToken))
 1102                return false;
 676103            targetVoxel.ObstacleCount++;
 104
 676105            grid.ObstacleCount++;
 676106            gridVersion = grid.IncrementVersion();
 676107            obstacleCount = targetVoxel.ObstacleCount;
 676108        }
 109
 676110        NotifyObstacleAdded(grid, targetVoxel, obstacleSpawnToken, obstacleCount, gridVersion);
 111
 676112        return true;
 1113    }
 114
 115    /// <summary>
 116    /// Attempts to remove an obstacle at the given world-scoped voxel identity in the supplied world.
 117    /// </summary>
 118    public static bool TryRemoveObstacle(
 119        GridWorld world,
 120        WorldVoxelIndex index,
 121        BoundsKey obstacleSpawnToken)
 122    {
 11123        return world != null
 11124            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 11125            && grid!.TryRemoveObstacle(voxel!, obstacleSpawnToken);
 126    }
 127
 128    /// <summary>
 129    /// Attempts to remove an obstacle from the specified world position.
 130    /// </summary>
 131    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector3d position, BoundsKey obstacleSpawnToken)
 132    {
 3133        return grid.TryGetVoxel(position, out Voxel? voxel)
 3134            && grid.TryRemoveObstacle(voxel!, obstacleSpawnToken);
 135    }
 136
 137    /// <summary>
 138    /// Removes an obstacle from a given voxel.
 139    /// </summary>
 140    public static bool TryRemoveObstacle(this VoxelGrid grid, Voxel targetVoxel, BoundsKey obstacleSpawnToken)
 141    {
 42142        if (targetVoxel.ObstacleCount == 0)
 143        {
 2144            GridForgeLogger.Channel.Warn($"No obstacle to remove on voxel ({targetVoxel.WorldIndex})!");
 2145            return false;
 146        }
 147
 148        byte obstacleCount;
 149        uint gridVersion;
 150
 40151        lock (grid.ObstacleSyncRoot)
 152        {
 40153            if (targetVoxel.ObstacleTracker?.Remove(obstacleSpawnToken) != true)
 1154                return false;
 155
 39156            if (--targetVoxel.ObstacleCount <= 0)
 157            {
 37158                SwiftHashSetPool<BoundsKey>.Shared.Release(targetVoxel.ObstacleTracker);
 37159                targetVoxel.ObstacleTracker = null;
 37160                targetVoxel.ObstacleCount = 0;
 161            }
 162
 39163            grid.ObstacleCount--;
 39164            gridVersion = grid.IncrementVersion();
 39165            obstacleCount = targetVoxel.ObstacleCount;
 39166        }
 167
 39168        NotifyObstacleRemoved(grid, targetVoxel, obstacleSpawnToken, obstacleCount, gridVersion);
 169
 39170        return true;
 1171    }
 172
 173    /// <summary>
 174    /// Clears all obstacles from the specified voxel.
 175    /// </summary>
 176    /// <param name="grid"></param>
 177    /// <param name="targetVoxel"></param>
 178    public static void ClearObstacles(this VoxelGrid grid, Voxel targetVoxel)
 179    {
 523180        if (targetVoxel.ObstacleCount == 0)
 1181            return;
 182
 183        byte clearedObstacleCount;
 184        uint gridVersion;
 185
 522186        lock (grid.ObstacleSyncRoot)
 187        {
 522188            clearedObstacleCount = targetVoxel.ObstacleCount;
 522189            if (targetVoxel.ObstacleTracker != null)
 190            {
 522191                SwiftHashSetPool<BoundsKey>.Shared.Release(targetVoxel.ObstacleTracker);
 522192                targetVoxel.ObstacleTracker = null;
 193            }
 194
 522195            grid.ObstacleCount -= targetVoxel.ObstacleCount;
 522196            targetVoxel.ObstacleCount = 0;
 522197            gridVersion = grid.IncrementVersion();
 522198        }
 199
 522200        NotifyObstaclesCleared(grid, targetVoxel, clearedObstacleCount, gridVersion);
 522201    }
 202
 203    #endregion
 204
 205    #region Private Methods
 206
 207    /// <summary>
 208    /// Notifies listeners that an obstacle was added.
 209    /// </summary>
 210    private static void NotifyObstacleAdded(
 211        VoxelGrid grid,
 212        Voxel targetVoxel,
 213        BoundsKey obstacleSpawnToken,
 214        byte obstacleCount,
 215        uint gridVersion)
 216    {
 676217        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleSpawnToken, obstacleCount, gridVersion);
 676218        Action<ObstacleEventInfo>? handlers = _onObstacleAdded;
 676219        if (handlers != null)
 220        {
 4221            var handlerDelegates = handlers.GetInvocationList();
 20222            for (int i = 0; i < handlerDelegates.Length; i++)
 223            {
 224                try
 225                {
 6226                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 4227                }
 2228                catch (Exception ex)
 229                {
 2230                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle add error: {ex.Message}");
 2231                }
 232            }
 233        }
 234
 676235        targetVoxel.NotifyObstacleAdded(eventInfo);
 236
 676237        targetVoxel.CachedGridVersion = gridVersion;
 676238        grid.World?.NotifyActiveGridChange(grid);
 676239    }
 240
 241    /// <summary>
 242    /// Notifies listeners that an obstacle was removed.
 243    /// </summary>
 244    private static void NotifyObstacleRemoved(
 245        VoxelGrid grid,
 246        Voxel targetVoxel,
 247        BoundsKey obstacleSpawnToken,
 248        byte obstacleCount,
 249        uint gridVersion)
 250    {
 39251        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleSpawnToken, obstacleCount, gridVersion);
 39252        Action<ObstacleEventInfo>? handlers = _onObstacleRemoved;
 39253        if (handlers != null)
 254        {
 1255            var handlerDelegates = handlers.GetInvocationList();
 6256            for (int i = 0; i < handlerDelegates.Length; i++)
 257            {
 258                try
 259                {
 2260                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 1261                }
 1262                catch (Exception ex)
 263                {
 1264                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle remove error: {ex.Message}
 1265                }
 266            }
 267        }
 268
 39269        targetVoxel.NotifyObstacleRemoved(eventInfo);
 270
 39271        targetVoxel.CachedGridVersion = gridVersion;
 39272        grid.World?.NotifyActiveGridChange(grid);
 39273    }
 274
 275    /// <summary>
 276    /// Notifies listeners that all obstacles on a voxel were cleared.
 277    /// </summary>
 278    private static void NotifyObstaclesCleared(
 279        VoxelGrid grid,
 280        Voxel targetVoxel,
 281        byte clearedObstacleCount,
 282        uint gridVersion)
 283    {
 522284        ObstacleClearEventInfo eventInfo = new(targetVoxel.WorldIndex, clearedObstacleCount, gridVersion);
 522285        Action<ObstacleClearEventInfo>? handlers = _onObstaclesCleared;
 522286        if (handlers != null)
 287        {
 2288            var handlerDelegates = handlers.GetInvocationList();
 10289            for (int i = 0; i < handlerDelegates.Length; i++)
 290            {
 291                try
 292                {
 3293                    ((Action<ObstacleClearEventInfo>)handlerDelegates[i])(eventInfo);
 2294                }
 1295                catch (Exception ex)
 296                {
 1297                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle clear error: {ex.Message}"
 1298                }
 299            }
 300        }
 301
 522302        targetVoxel.NotifyObstaclesCleared(eventInfo);
 303
 522304        targetVoxel.CachedGridVersion = gridVersion;
 522305        grid.World?.NotifyActiveGridChange(grid);
 522306    }
 307
 308    #endregion
 309}