< 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: 110
Uncovered lines: 0
Coverable lines: 110
Total lines: 372
Line coverage: 100%
Branch coverage
100%
Covered branches: 54
Total branches: 54
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 System;
 9using FixedMathSharp;
 10using GridForge.Spatial;
 11using SwiftCollections.Pool;
 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    {
 1836        add => _onObstacleAdded += value;
 1837        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    {
 1648        add => _onObstacleRemoved += value;
 1649        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    {
 1760        add => _onObstaclesCleared += value;
 1761        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        ObstacleToken obstacleToken)
 75    {
 376        return world != null
 377            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 378            && grid!.TryAddObstacle(voxel!, obstacleToken) == 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, ObstacleToken obstacleToken)
 85    {
 686        return grid.TryGetVoxel(position, out Voxel? voxel)
 687            && grid.TryAddObstacle(voxel!, obstacleToken);
 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="obstacleToken">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, ObstacleToken obstacleToken)
 98    {
 299        return grid.TryAddObstacle(position, default, obstacleToken);
 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="obstacleToken">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, ObstacleToken obstacleToke
 111    {
 3112        return grid.TryAddObstacle(GridPlane2d.ToWorld(position, layerY), obstacleToken);
 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="obstacleToken">The process-unique obstacle registration token.</param>
 121    /// <exception cref="Exception"></exception>
 122    public static bool TryAddObstacle(this VoxelGrid grid, Voxel targetVoxel, ObstacleToken obstacleToken)
 123    {
 1081124        if (!obstacleToken.IsValid || !targetVoxel.IsBlockable)
 67125            return false;
 126
 127        byte obstacleCount;
 128        uint gridVersion;
 129
 1014130        lock (grid.ObstacleSyncRoot)
 131        {
 1014132            if (targetVoxel.ObstacleCount >= MaxObstacleCount)
 1133                return false;
 134
 1013135            targetVoxel.ObstacleTracker ??= SwiftHashSetPool<ObstacleToken>.Shared.Rent();
 1013136            if (!targetVoxel.ObstacleTracker.Add(obstacleToken))
 1137                return false;
 1012138            targetVoxel.ObstacleCount++;
 139
 1012140            grid.ObstacleCount++;
 1012141            gridVersion = grid.IncrementVersion();
 1012142            obstacleCount = targetVoxel.ObstacleCount;
 1012143        }
 144
 1012145        NotifyObstacleAdded(grid, targetVoxel, obstacleToken, obstacleCount, gridVersion);
 146
 1012147        return true;
 2148    }
 149
 150    /// <summary>
 151    /// Attempts to remove an obstacle at the given world-scoped voxel identity in the supplied world.
 152    /// </summary>
 153    public static bool TryRemoveObstacle(
 154        GridWorld world,
 155        WorldVoxelIndex index,
 156        ObstacleToken obstacleToken)
 157    {
 33158        return world != null
 33159            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 33160            && grid!.TryRemoveObstacle(voxel!, obstacleToken);
 161    }
 162
 163    /// <summary>
 164    /// Attempts to remove an obstacle from the specified world position.
 165    /// </summary>
 166    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector3d position, ObstacleToken obstacleToken)
 167    {
 6168        return grid.TryGetVoxel(position, out Voxel? voxel)
 6169            && grid.TryRemoveObstacle(voxel!, obstacleToken);
 170    }
 171
 172    /// <summary>
 173    /// Attempts to remove an obstacle from the given XZ-plane world position on the default world Y layer.
 174    /// </summary>
 175    /// <param name="grid">The grid to mutate.</param>
 176    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 177    /// <param name="obstacleToken">The obstacle token to remove from the resolved voxel.</param>
 178    /// <returns>True if the obstacle was removed from the resolved voxel; otherwise false.</returns>
 179    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector2d position, ObstacleToken obstacleToken)
 180    {
 1181        return grid.TryRemoveObstacle(position, default, obstacleToken);
 182    }
 183
 184    /// <summary>
 185    /// Attempts to remove an obstacle from the given XZ-plane world position on the supplied world Y layer.
 186    /// </summary>
 187    /// <param name="grid">The grid to mutate.</param>
 188    /// <param name="position">The 2D position whose X component maps to world X and Y component maps to world Z.</param
 189    /// <param name="layerY">The world Y layer to resolve.</param>
 190    /// <param name="obstacleToken">The obstacle token to remove from the resolved voxel.</param>
 191    /// <returns>True if the obstacle was removed from the resolved voxel; otherwise false.</returns>
 192    public static bool TryRemoveObstacle(this VoxelGrid grid, Vector2d position, Fixed64 layerY, ObstacleToken obstacleT
 193    {
 3194        return grid.TryRemoveObstacle(GridPlane2d.ToWorld(position, layerY), obstacleToken);
 195    }
 196
 197    /// <summary>
 198    /// Removes an obstacle from a given voxel.
 199    /// </summary>
 200    public static bool TryRemoveObstacle(this VoxelGrid grid, Voxel targetVoxel, ObstacleToken obstacleToken)
 201    {
 96202        if (!obstacleToken.IsValid)
 1203            return false;
 204
 95205        if (targetVoxel.ObstacleCount == 0)
 206        {
 5207            GridForgeLogger.Channel.Warn($"No obstacle to remove on voxel ({targetVoxel.WorldIndex})!");
 5208            return false;
 209        }
 210
 211        byte obstacleCount;
 212        uint gridVersion;
 213
 90214        lock (grid.ObstacleSyncRoot)
 215        {
 90216            if (!targetVoxel.ObstacleTracker!.Remove(obstacleToken))
 2217                return false;
 218
 88219            if (--targetVoxel.ObstacleCount <= 0)
 220            {
 78221                SwiftHashSetPool<ObstacleToken>.Shared.Release(targetVoxel.ObstacleTracker);
 78222                targetVoxel.ObstacleTracker = null;
 78223                targetVoxel.ObstacleCount = 0;
 224            }
 225
 88226            grid.ObstacleCount--;
 88227            gridVersion = grid.IncrementVersion();
 88228            obstacleCount = targetVoxel.ObstacleCount;
 88229        }
 230
 88231        NotifyObstacleRemoved(grid, targetVoxel, obstacleToken, obstacleCount, gridVersion);
 232
 88233        return true;
 2234    }
 235
 236    /// <summary>
 237    /// Clears all obstacles from the specified voxel.
 238    /// </summary>
 239    /// <param name="grid"></param>
 240    /// <param name="targetVoxel"></param>
 241    public static void ClearObstacles(this VoxelGrid grid, Voxel targetVoxel)
 242    {
 556243        if (targetVoxel.ObstacleCount == 0)
 1244            return;
 245
 246        byte clearedObstacleCount;
 247        uint gridVersion;
 248
 555249        lock (grid.ObstacleSyncRoot)
 250        {
 555251            clearedObstacleCount = targetVoxel.ObstacleCount;
 555252            if (targetVoxel.ObstacleTracker != null)
 253            {
 555254                SwiftHashSetPool<ObstacleToken>.Shared.Release(targetVoxel.ObstacleTracker);
 555255                targetVoxel.ObstacleTracker = null;
 256            }
 257
 555258            grid.ObstacleCount -= targetVoxel.ObstacleCount;
 555259            targetVoxel.ObstacleCount = 0;
 555260            gridVersion = grid.IncrementVersion();
 555261        }
 262
 555263        NotifyObstaclesCleared(grid, targetVoxel, clearedObstacleCount, gridVersion);
 555264    }
 265
 266    #endregion
 267
 268    #region Private Methods
 269
 270    /// <summary>
 271    /// Notifies listeners that an obstacle was added.
 272    /// </summary>
 273    private static void NotifyObstacleAdded(
 274        VoxelGrid grid,
 275        Voxel targetVoxel,
 276        ObstacleToken obstacleToken,
 277        byte obstacleCount,
 278        uint gridVersion)
 279    {
 1012280        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleToken, obstacleCount, gridVersion);
 1012281        Action<ObstacleEventInfo>? handlers = _onObstacleAdded;
 1012282        if (handlers != null)
 283        {
 9284            var handlerDelegates = handlers.GetInvocationList();
 42285            for (int i = 0; i < handlerDelegates.Length; i++)
 286            {
 287                try
 288                {
 12289                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 10290                }
 2291                catch (Exception ex)
 292                {
 2293                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle add error: {ex.Message}");
 2294                }
 295            }
 296        }
 297
 1012298        targetVoxel.NotifyObstacleAdded(eventInfo);
 299
 1012300        targetVoxel.CachedGridVersion = gridVersion;
 1012301        grid.World!.NotifyActiveGridChange(grid);
 1012302    }
 303
 304    /// <summary>
 305    /// Notifies listeners that an obstacle was removed.
 306    /// </summary>
 307    private static void NotifyObstacleRemoved(
 308        VoxelGrid grid,
 309        Voxel targetVoxel,
 310        ObstacleToken obstacleToken,
 311        byte obstacleCount,
 312        uint gridVersion)
 313    {
 88314        ObstacleEventInfo eventInfo = new(targetVoxel.WorldIndex, obstacleToken, obstacleCount, gridVersion);
 88315        Action<ObstacleEventInfo>? handlers = _onObstacleRemoved;
 88316        if (handlers != null)
 317        {
 3318            var handlerDelegates = handlers.GetInvocationList();
 14319            for (int i = 0; i < handlerDelegates.Length; i++)
 320            {
 321                try
 322                {
 4323                    ((Action<ObstacleEventInfo>)handlerDelegates[i])(eventInfo);
 3324                }
 1325                catch (Exception ex)
 326                {
 1327                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle remove error: {ex.Message}
 1328                }
 329            }
 330        }
 331
 88332        targetVoxel.NotifyObstacleRemoved(eventInfo);
 333
 88334        targetVoxel.CachedGridVersion = gridVersion;
 88335        grid.World!.NotifyActiveGridChange(grid);
 88336    }
 337
 338    /// <summary>
 339    /// Notifies listeners that all obstacles on a voxel were cleared.
 340    /// </summary>
 341    private static void NotifyObstaclesCleared(
 342        VoxelGrid grid,
 343        Voxel targetVoxel,
 344        byte clearedObstacleCount,
 345        uint gridVersion)
 346    {
 555347        ObstacleClearEventInfo eventInfo = new(targetVoxel.WorldIndex, clearedObstacleCount, gridVersion);
 555348        Action<ObstacleClearEventInfo>? handlers = _onObstaclesCleared;
 555349        if (handlers != null)
 350        {
 4351            var handlerDelegates = handlers.GetInvocationList();
 18352            for (int i = 0; i < handlerDelegates.Length; i++)
 353            {
 354                try
 355                {
 5356                    ((Action<ObstacleClearEventInfo>)handlerDelegates[i])(eventInfo);
 4357                }
 1358                catch (Exception ex)
 359                {
 1360                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Obstacle clear error: {ex.Message}"
 1361                }
 362            }
 363        }
 364
 555365        targetVoxel.NotifyObstaclesCleared(eventInfo);
 366
 555367        targetVoxel.CachedGridVersion = gridVersion;
 555368        grid.World!.NotifyActiveGridChange(grid);
 555369    }
 370
 371    #endregion
 372}

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.ObstacleToken)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector3d,GridForge.ObstacleToken)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,GridForge.ObstacleToken)
TryAddObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,GridForge.ObstacleToken)
TryAddObstacle(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.ObstacleToken)
TryRemoveObstacle(GridForge.Grids.GridWorld,GridForge.Spatial.WorldVoxelIndex,GridForge.ObstacleToken)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector3d,GridForge.ObstacleToken)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,GridForge.ObstacleToken)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,GridForge.ObstacleToken)
TryRemoveObstacle(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.ObstacleToken)
ClearObstacles(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel)
NotifyObstacleAdded(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.ObstacleToken,System.Byte,System.UInt32)
NotifyObstacleRemoved(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.ObstacleToken,System.Byte,System.UInt32)
NotifyObstaclesCleared(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,System.Byte,System.UInt32)