< 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: 106
Uncovered lines: 0
Coverable lines: 106
Total lines: 366
Line coverage: 100%
Branch coverage
100%
Covered branches: 48
Total branches: 48
Branch coverage: 100%
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
 1//=======================================================================
 2// GridObstacleManager.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.Pool;
 11using System;
 12
 13namespace GridForge.Grids;
 14
 15/// <summary>
 16/// Handles the addition, removal, and tracking of obstacles within a grid.
 17/// Ensures thread safety and proper event notifications when obstacles change.
 18/// </summary>
 19public static class GridObstacleManager
 20{
 21    #region Constants & Events
 22
 23    /// <summary>
 24    /// Maximum number of obstacles that can exist on a single voxel.
 25    /// </summary>
 26    public const byte MaxObstacleCount = byte.MaxValue;
 27
 28    /// <summary>
 29    /// Event triggered when an obstacle is added.
 30    /// </summary>
 31    private static Action<ObstacleEventInfo>? _onObstacleAdded;
 32
 33    /// <inheritdoc cref="_onObstacleAdded"/>
 34    public static event Action<ObstacleEventInfo> OnObstacleAdded
 35    {
 1036        add => _onObstacleAdded += value;
 1037        remove => _onObstacleAdded -= value;
 38    }
 39
 40    /// <summary>
 41    /// Event triggered when an obstacle is removed.
 42    /// </summary>
 43    private static Action<ObstacleEventInfo>? _onObstacleRemoved;
 44
 45    /// <inheritdoc cref="_onObstacleRemoved"/>
 46    public static event Action<ObstacleEventInfo> OnObstacleRemoved
 47    {
 948        add => _onObstacleRemoved += value;
 949        remove => _onObstacleRemoved -= value;
 50    }
 51
 52    /// <summary>
 53    /// Event triggered when all obstacles on a voxel are cleared at once.
 54    /// </summary>
 55    private static Action<ObstacleClearEventInfo>? _onObstaclesCleared;
 56
 57    /// <inheritdoc cref="_onObstaclesCleared"/>
 58    public static event Action<ObstacleClearEventInfo> OnObstaclesCleared
 59    {
 1060        add => _onObstaclesCleared += value;
 1061        remove => _onObstaclesCleared -= value;
 62    }
 63
 64    #endregion
 65
 66    #region Public Methods
 67
 68    /// <summary>
 69    /// Attempts to add an obstacle at the given world-scoped voxel identity in the supplied world.
 70    /// </summary>
 71    public static bool TryAddObstacle(
 72        GridWorld world,
 73        WorldVoxelIndex index,
 74        BoundsKey obstacleSpawnToken)
 75    {
 276        return world != null
 277            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 278            && grid!.TryAddObstacle(voxel!, obstacleSpawnToken) == true;
 79    }
 80
 81    /// <summary>
 82    /// Attempts to add an obstacle at the given world position.
 83    /// </summary>
 84    public static bool TryAddObstacle(this VoxelGrid grid, Vector3d position, BoundsKey obstacleSpawnToken)
 85    {
 686        return grid.TryGetVoxel(position, out Voxel? voxel)
 687            && grid.TryAddObstacle(voxel!, obstacleSpawnToken);
 88    }
 89
 90    /// <summary>
 91    /// Attempts to add an obstacle at the given XZ-plane world position on the default world Y layer.
 92    /// </summary>
 93    /// <param name="grid">The grid to mutate.</param>
 94    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 95    /// <param name="obstacleSpawnToken">The obstacle token to attach to the resolved voxel.</param>
 96    /// <returns>True if an obstacle was added to the resolved voxel; otherwise false.</returns>
 97    public static bool TryAddObstacle(this VoxelGrid grid, Vector2d position, BoundsKey obstacleSpawnToken)
 98    {
 299        return grid.TryAddObstacle(position, default, obstacleSpawnToken);
 100    }
 101
 102    /// <summary>
 103    /// Attempts to add an obstacle at the given XZ-plane world position on the supplied world Y layer.
 104    /// </summary>
 105    /// <param name="grid">The grid to mutate.</param>
 106    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 107    /// <param name="layerY">The world Y layer to resolve.</param>
 108    /// <param name="obstacleSpawnToken">The obstacle token to attach to the resolved voxel.</param>
 109    /// <returns>True if an obstacle was added to the resolved voxel; otherwise false.</returns>
 110    public static bool TryAddObstacle(this VoxelGrid grid, Vector2d position, Fixed64 layerY, BoundsKey obstacleSpawnTok
 111    {
 3112        return grid.TryAddObstacle(GridPlane2d.ToWorld(position, layerY), obstacleSpawnToken);
 113    }
 114
 115    /// <summary>
 116    /// Adds an obstacle to this voxel.
 117    /// </summary>
 118    /// <param name="grid"></param>
 119    /// <param name="targetVoxel"></param>
 120    /// <param name="obstacleSpawnToken"></param>
 121    /// <exception cref="Exception"></exception>
 122    public static bool TryAddObstacle(this VoxelGrid grid, Voxel targetVoxel, BoundsKey obstacleSpawnToken)
 123    {
 750124        if (!targetVoxel.IsBlockable)
 3125            return false;
 126
 127        byte obstacleCount;
 128        uint gridVersion;
 129
 747130        lock (grid.ObstacleSyncRoot)
 131        {
 747132            targetVoxel.ObstacleTracker ??= SwiftHashSetPool<BoundsKey>.Shared.Rent();
 747133            if (!targetVoxel.ObstacleTracker.Add(obstacleSpawnToken))
 1134                return false;
 746135            targetVoxel.ObstacleCount++;
 136
 746137            grid.ObstacleCount++;
 746138            gridVersion = grid.IncrementVersion();
 746139            obstacleCount = targetVoxel.ObstacleCount;
 746140        }
 141
 746142        NotifyObstacleAdded(grid, targetVoxel, obstacleSpawnToken, obstacleCount, gridVersion);
 143
 746144        return true;
 1145    }
 146
 147    /// <summary>
 148    /// Attempts to remove an obstacle at the given world-scoped voxel identity in the supplied world.
 149    /// </summary>
 150    public static bool TryRemoveObstacle(
 151        GridWorld world,
 152        WorldVoxelIndex index,
 153        BoundsKey obstacleSpawnToken)
 154    {
 34155        return world != null
 34156            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 34157            && grid!.TryRemoveObstacle(voxel!, obstacleSpawnToken);
 158    }
 159
 160    /// <summary>
 161    /// Attempts to remove an obstacle from the specified world position.
 162    /// </summary>
 163    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector3d position, BoundsKey obstacleSpawnToken)
 164    {
 6165        return grid.TryGetVoxel(position, out Voxel? voxel)
 6166            && grid.TryRemoveObstacle(voxel!, obstacleSpawnToken);
 167    }
 168
 169    /// <summary>
 170    /// Attempts to remove an obstacle from the given XZ-plane world position on the default world Y layer.
 171    /// </summary>
 172    /// <param name="grid">The grid to mutate.</param>
 173    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 174    /// <param name="obstacleSpawnToken">The obstacle token to remove from the resolved voxel.</param>
 175    /// <returns>True if the obstacle was removed from the resolved voxel; otherwise false.</returns>
 176    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector2d position, BoundsKey obstacleSpawnToken)
 177    {
 1178        return grid.TryRemoveObstacle(position, default, obstacleSpawnToken);
 179    }
 180
 181    /// <summary>
 182    /// Attempts to remove an obstacle from the given XZ-plane world position on the supplied world Y layer.
 183    /// </summary>
 184    /// <param name="grid">The grid to mutate.</param>
 185    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 186    /// <param name="layerY">The world Y layer to resolve.</param>
 187    /// <param name="obstacleSpawnToken">The obstacle token to remove from the resolved voxel.</param>
 188    /// <returns>True if the obstacle was removed from the resolved voxel; otherwise false.</returns>
 189    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector2d position, Fixed64 layerY, BoundsKey obstacleSpawn
 190    {
 3191        return grid.TryRemoveObstacle(GridPlane2d.ToWorld(position, layerY), obstacleSpawnToken);
 192    }
 193
 194    /// <summary>
 195    /// Removes an obstacle from a given voxel.
 196    /// </summary>
 197    public static bool TryRemoveObstacle(this VoxelGrid grid, Voxel targetVoxel, BoundsKey obstacleSpawnToken)
 198    {
 77199        if (targetVoxel.ObstacleCount == 0)
 200        {
 5201            GridForgeLogger.Channel.Warn($"No obstacle to remove on voxel ({targetVoxel.WorldIndex})!");
 5202            return false;
 203        }
 204
 205        byte obstacleCount;
 206        uint gridVersion;
 207
 72208        lock (grid.ObstacleSyncRoot)
 209        {
 72210            if (!targetVoxel.ObstacleTracker!.Remove(obstacleSpawnToken))
 1211                return false;
 212
 71213            if (--targetVoxel.ObstacleCount <= 0)
 214            {
 65215                SwiftHashSetPool<BoundsKey>.Shared.Release(targetVoxel.ObstacleTracker);
 65216                targetVoxel.ObstacleTracker = null;
 65217                targetVoxel.ObstacleCount = 0;
 218            }
 219
 71220            grid.ObstacleCount--;
 71221            gridVersion = grid.IncrementVersion();
 71222            obstacleCount = targetVoxel.ObstacleCount;
 71223        }
 224
 71225        NotifyObstacleRemoved(grid, targetVoxel, obstacleSpawnToken, obstacleCount, gridVersion);
 226
 71227        return true;
 1228    }
 229
 230    /// <summary>
 231    /// Clears all obstacles from the specified voxel.
 232    /// </summary>
 233    /// <param name="grid"></param>
 234    /// <param name="targetVoxel"></param>
 235    public static void ClearObstacles(this VoxelGrid grid, Voxel targetVoxel)
 236    {
 561237        if (targetVoxel.ObstacleCount == 0)
 1238            return;
 239
 240        byte clearedObstacleCount;
 241        uint gridVersion;
 242
 560243        lock (grid.ObstacleSyncRoot)
 244        {
 560245            clearedObstacleCount = targetVoxel.ObstacleCount;
 560246            if (targetVoxel.ObstacleTracker != null)
 247            {
 560248                SwiftHashSetPool<BoundsKey>.Shared.Release(targetVoxel.ObstacleTracker);
 560249                targetVoxel.ObstacleTracker = null;
 250            }
 251
 560252            grid.ObstacleCount -= targetVoxel.ObstacleCount;
 560253            targetVoxel.ObstacleCount = 0;
 560254            gridVersion = grid.IncrementVersion();
 560255        }
 256
 560257        NotifyObstaclesCleared(grid, targetVoxel, clearedObstacleCount, gridVersion);
 560258    }
 259
 260    #endregion
 261
 262    #region Private Methods
 263
 264    /// <summary>
 265    /// Notifies listeners that an obstacle was added.
 266    /// </summary>
 267    private static void NotifyObstacleAdded(
 268        VoxelGrid grid,
 269        Voxel targetVoxel,
 270        BoundsKey obstacleSpawnToken,
 271        byte obstacleCount,
 272        uint gridVersion)
 273    {
 746274        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleSpawnToken, obstacleCount, gridVersion);
 746275        Action<ObstacleEventInfo>? handlers = _onObstacleAdded;
 746276        if (handlers != null)
 277        {
 7278            var handlerDelegates = handlers.GetInvocationList();
 32279            for (int i = 0; i < handlerDelegates.Length; i++)
 280            {
 281                try
 282                {
 9283                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 7284                }
 2285                catch (Exception ex)
 286                {
 2287                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle add error: {ex.Message}");
 2288                }
 289            }
 290        }
 291
 746292        targetVoxel.NotifyObstacleAdded(eventInfo);
 293
 746294        targetVoxel.CachedGridVersion = gridVersion;
 746295        grid.World!.NotifyActiveGridChange(grid);
 746296    }
 297
 298    /// <summary>
 299    /// Notifies listeners that an obstacle was removed.
 300    /// </summary>
 301    private static void NotifyObstacleRemoved(
 302        VoxelGrid grid,
 303        Voxel targetVoxel,
 304        BoundsKey obstacleSpawnToken,
 305        byte obstacleCount,
 306        uint gridVersion)
 307    {
 71308        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleSpawnToken, obstacleCount, gridVersion);
 71309        Action<ObstacleEventInfo>? handlers = _onObstacleRemoved;
 71310        if (handlers != null)
 311        {
 3312            var handlerDelegates = handlers.GetInvocationList();
 14313            for (int i = 0; i < handlerDelegates.Length; i++)
 314            {
 315                try
 316                {
 4317                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 3318                }
 1319                catch (Exception ex)
 320                {
 1321                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle remove error: {ex.Message}
 1322                }
 323            }
 324        }
 325
 71326        targetVoxel.NotifyObstacleRemoved(eventInfo);
 327
 71328        targetVoxel.CachedGridVersion = gridVersion;
 71329        grid.World!.NotifyActiveGridChange(grid);
 71330    }
 331
 332    /// <summary>
 333    /// Notifies listeners that all obstacles on a voxel were cleared.
 334    /// </summary>
 335    private static void NotifyObstaclesCleared(
 336        VoxelGrid grid,
 337        Voxel targetVoxel,
 338        byte clearedObstacleCount,
 339        uint gridVersion)
 340    {
 560341        ObstacleClearEventInfo eventInfo = new(targetVoxel.WorldIndex, clearedObstacleCount, gridVersion);
 560342        Action<ObstacleClearEventInfo>? handlers = _onObstaclesCleared;
 560343        if (handlers != null)
 344        {
 3345            var handlerDelegates = handlers.GetInvocationList();
 14346            for (int i = 0; i < handlerDelegates.Length; i++)
 347            {
 348                try
 349                {
 4350                    ((Action<ObstacleClearEventInfo>)handlerDelegates[i])(eventInfo);
 3351                }
 1352                catch (Exception ex)
 353                {
 1354                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle clear error: {ex.Message}"
 1355                }
 356            }
 357        }
 358
 560359        targetVoxel.NotifyObstaclesCleared(eventInfo);
 360
 560361        targetVoxel.CachedGridVersion = gridVersion;
 560362        grid.World!.NotifyActiveGridChange(grid);
 560363    }
 364
 365    #endregion
 366}

Methods/Properties

add_OnObstacleAdded(System.Action`1<GridForge.Grids.ObstacleEventInfo>)
remove_OnObstacleAdded(System.Action`1<GridForge.Grids.ObstacleEventInfo>)
add_OnObstacleRemoved(System.Action`1<GridForge.Grids.ObstacleEventInfo>)
remove_OnObstacleRemoved(System.Action`1<GridForge.Grids.ObstacleEventInfo>)
add_OnObstaclesCleared(System.Action`1<GridForge.Grids.ObstacleClearEventInfo>)
remove_OnObstaclesCleared(System.Action`1<GridForge.Grids.ObstacleClearEventInfo>)
TryAddObstacle(GridForge.Grids.GridWorld,GridForge.Spatial.WorldVoxelIndex,GridForge.BoundsKey)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector3d,GridForge.BoundsKey)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,GridForge.BoundsKey)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,GridForge.BoundsKey)
TryAddObstacle(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.BoundsKey)
TryRemoveObstacle(GridForge.Grids.GridWorld,GridForge.Spatial.WorldVoxelIndex,GridForge.BoundsKey)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector3d,GridForge.BoundsKey)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,GridForge.BoundsKey)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,GridForge.BoundsKey)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.BoundsKey)
ClearObstacles(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel)
NotifyObstacleAdded(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.BoundsKey,System.Byte,System.UInt32)
NotifyObstacleRemoved(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.BoundsKey,System.Byte,System.UInt32)
NotifyObstaclesCleared(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,System.Byte,System.UInt32)