< Summary

Information
Class: GridForge.Grids.GridOccupantManager
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Managers/GridOccupantManager.cs
Line coverage
100%
Covered lines: 268
Uncovered lines: 0
Coverable lines: 268
Total lines: 745
Line coverage: 100%
Branch coverage
97%
Covered branches: 181
Total branches: 186
Branch coverage: 97.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
add_OnOccupantAdded(...)100%11100%
remove_OnOccupantAdded(...)100%11100%
add_OnOccupantRemoved(...)100%11100%
remove_OnOccupantRemoved(...)100%11100%
.cctor()100%11100%
.ctor(...)100%11100%
.ctor()100%11100%
.ctor(...)100%11100%
TryRegister(...)100%66100%
GetOccupiedIndices(...)100%44100%
TryGetOccupancyTicket(...)87.5%88100%
TryAddVoxelOccupant(...)100%66100%
TryAddVoxelOccupant(...)100%44100%
TryAddVoxelOccupant(...)100%44100%
TryAddVoxelOccupant(...)100%44100%
TryDeregister(...)100%66100%
TryRemoveVoxelOccupant(...)100%66100%
TryRemoveVoxelOccupant(...)100%22100%
TryRemoveVoxelOccupant(...)100%44100%
TryRemoveVoxelOccupant(...)100%44100%
ForgetTrackedOccupancies(...)100%44100%
ClearTrackedOccupancies(...)100%22100%
ReleaseTrackedOccupancies(...)100%22100%
TryGetAddContext(...)100%66100%
TrackActiveScanCell(...)100%44100%
TryGetGridOccupancies(...)100%22100%
RemoveTrackedOccupancies(...)100%44100%
TryGetCurrentTrackedVoxel(...)100%66100%
TryTrackOccupancy(...)100%66100%
ForgetTrackedOccupancy(...)83.33%66100%
ForgetTrackedOccupancyUnsafe(...)100%66100%
TryRemoveTrackedTicket(...)100%22100%
TryGetTrackedRecordUnsafe(...)75%88100%
GetTrackedOccupanciesSnapshot(...)90%1010100%
TryGetRemovalContext(...)100%1010100%
TryRemoveOccupantFromScanCell(...)100%22100%
ReleaseActiveScanCellIfEmpty(...)100%88100%
TryGetOrAddOccupancyRecord(...)100%66100%
CopyTrackedOccupancies(...)100%66100%
FinalizeTrackedOccupancySnapshot(...)100%44100%
CompareTrackedOccupancies(...)100%1212100%
NotifyOccupantAdded(...)100%66100%
NotifyOccupantRemoved(...)100%66100%
GetWorldRegistry(...)100%11100%
TryGetWorldRegistry(...)100%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// GridOccupantManager.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 GridForge.Spatial;
 9using SwiftCollections;
 10using SwiftCollections.Pool;
 11using System;
 12using System.Collections.Concurrent;
 13using System.Collections.Generic;
 14
 15namespace GridForge.Grids;
 16
 17/// <summary>
 18/// Provides utility methods for managing voxel occupants within a grid.
 19/// Supports adding, removing, and retrieving occupants with thread-safe operations.
 20/// </summary>
 21public static class GridOccupantManager
 22{
 23    #region Constants & Events
 24
 25    /// <summary>
 26    /// Maximum number of occupants allowed per voxel.
 27    /// </summary>
 28    public const byte MaxOccupantCount = byte.MaxValue;
 29
 30    /// <summary>
 31    /// Event triggered when an occupant is added.
 32    /// </summary>
 33    private static Action<OccupantEventInfo>? _onOccupantAdded;
 34
 35    /// <inheritdoc cref="_onOccupantAdded"/>
 36    public static event Action<OccupantEventInfo> OnOccupantAdded
 37    {
 1038        add => _onOccupantAdded += value;
 1039        remove => _onOccupantAdded -= value;
 40    }
 41
 42    /// <summary>
 43    /// Event triggered when an occupant is removed.
 44    /// </summary>
 45    private static Action<OccupantEventInfo>? _onOccupantRemoved;
 46
 47    /// <inheritdoc cref="_onOccupantRemoved"/>
 48    public static event Action<OccupantEventInfo> OnOccupantRemoved
 49    {
 1050        add => _onOccupantRemoved += value;
 1051        remove => _onOccupantRemoved -= value;
 52    }
 53
 54    #endregion
 55
 56    #region Private Fields
 57
 58    /// <summary>
 59    /// Tracks occupant registrations independently for each world.
 60    /// </summary>
 161    private static readonly ConcurrentDictionary<GridWorld, WorldOccupancyRegistry> _occupancyRegistries = new();
 62
 63    /// <summary>
 64    /// Tracks all voxel registrations for a single occupant.
 65    /// </summary>
 66    private sealed class OccupancyRecord
 67    {
 68        public readonly IVoxelOccupant Occupant;
 40369        public readonly SwiftDictionary<WorldVoxelIndex, int> Tickets = new();
 70
 40371        public OccupancyRecord(IVoxelOccupant occupant)
 72        {
 40373            Occupant = occupant;
 40374        }
 75    }
 76
 77    /// <summary>
 78    /// Synchronizes one world's tracked occupant registrations.
 79    /// </summary>
 80    private sealed class WorldOccupancyRegistry
 81    {
 5182        public readonly object SyncRoot = new();
 5183        public readonly SwiftDictionary<Guid, OccupancyRecord> Records = new();
 84    }
 85
 86    /// <summary>
 87    /// Immutable snapshot of one tracked occupancy.
 88    /// </summary>
 89    private readonly struct TrackedOccupancy
 90    {
 91        public readonly WorldVoxelIndex VoxelIndex;
 92        public readonly int Ticket;
 93
 94        public TrackedOccupancy(WorldVoxelIndex voxelIndex, int ticket)
 95        {
 16696            VoxelIndex = voxelIndex;
 16697            Ticket = ticket;
 16698        }
 99    }
 100
 101    #endregion
 102
 103    #region Occupant Management
 104
 105    /// <summary>
 106    /// Attempts to register the occupant with the current voxel it is on in the supplied world.
 107    /// </summary>
 108    public static bool TryRegister(GridWorld world, IVoxelOccupant occupant)
 109    {
 12110        return occupant != null
 12111            && world != null
 12112            && world.TryGetGridAndVoxel(occupant.Position, out VoxelGrid? grid, out Voxel? voxel)
 12113            && grid!.TryAddVoxelOccupant(voxel!, occupant);
 114    }
 115
 116    /// <summary>
 117    /// Returns a snapshot of the voxel indices currently tracked for the occupant in the supplied world.
 118    /// </summary>
 119    /// <param name="world">The world whose occupancy registry should be queried.</param>
 120    /// <param name="occupant">The occupant whose tracked voxel indices should be returned.</param>
 121    /// <returns>A deterministic snapshot of the occupant's currently tracked voxel indices.</returns>
 122    public static IEnumerable<WorldVoxelIndex> GetOccupiedIndices(GridWorld world, IVoxelOccupant occupant)
 123    {
 13124        TrackedOccupancy[] occupancies = GetTrackedOccupanciesSnapshot(world, occupant);
 13125        if (occupancies.Length == 0)
 8126            return Array.Empty<WorldVoxelIndex>();
 127
 5128        WorldVoxelIndex[] indices = new WorldVoxelIndex[occupancies.Length];
 24129        for (int i = 0; i < occupancies.Length; i++)
 7130            indices[i] = occupancies[i].VoxelIndex;
 131
 5132        return indices;
 133    }
 134
 135    /// <summary>
 136    /// Attempts to retrieve the scan-cell ticket for the occupant at a specific voxel in the supplied world.
 137    /// </summary>
 138    /// <param name="world">The world whose occupancy registry should be queried.</param>
 139    /// <param name="occupant">The occupant whose registration should be queried.</param>
 140    /// <param name="index">The tracked voxel index to look up.</param>
 141    /// <param name="ticket">The tracked scan-cell ticket for the occupant at that voxel.</param>
 142    /// <returns>True if the occupant is registered to the voxel, otherwise false.</returns>
 143    public static bool TryGetOccupancyTicket(
 144        GridWorld world,
 145        IVoxelOccupant occupant,
 146        WorldVoxelIndex index,
 147        out int ticket)
 148    {
 178149        ticket = -1;
 178150        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 3151            return false;
 152
 175153        lock (registry!.SyncRoot)
 175154            return TryGetTrackedRecordUnsafe(world, occupant, out OccupancyRecord? record)
 175155                && record!.Tickets.TryGetValue(index, out ticket);
 175156    }
 157
 158    /// <summary>
 159    /// Attempts to add an occupant from the given world-scoped voxel identity in the supplied world.
 160    /// </summary>
 161    public static bool TryAddVoxelOccupant(
 162        GridWorld world,
 163        WorldVoxelIndex index,
 164        IVoxelOccupant occupant)
 165    {
 3166        return occupant != null
 3167            && world != null
 3168            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 3169            && grid!.TryAddVoxelOccupant(voxel!, occupant);
 170    }
 171
 172    /// <summary>
 173    /// Attempts to add an occupant at the given world position.
 174    /// </summary>
 175    public static bool TryAddVoxelOccupant(this VoxelGrid grid, IVoxelOccupant occupant)
 176    {
 375177        return occupant != null
 375178            && grid.TryGetVoxel(occupant.Position, out Voxel? voxel)
 375179            && grid.TryAddVoxelOccupant(voxel!, occupant);
 180    }
 181
 182    /// <summary>
 183    /// Attempts to add an occupant at the specified voxel index.
 184    /// </summary>
 185    public static bool TryAddVoxelOccupant(
 186        this VoxelGrid grid,
 187        VoxelIndex voxelIndex,
 188        IVoxelOccupant occupant)
 189    {
 2190        return occupant != null
 2191            && grid.TryGetVoxel(voxelIndex, out Voxel? target)
 2192            && grid.TryAddVoxelOccupant(target!, occupant);
 193    }
 194
 195    /// <summary>
 196    /// Adds an occupant to the grid.
 197    /// </summary>
 198    public static bool TryAddVoxelOccupant(
 199        this VoxelGrid grid,
 200        Voxel targetVoxel,
 201        IVoxelOccupant occupant)
 202    {
 408203        if (!TryGetAddContext(grid, targetVoxel, occupant, out GridWorld? world, out ScanCell? scanCell))
 2204            return false;
 205
 206        int ticket;
 207        byte occupantCount;
 208
 406209        lock (grid.OccupantSyncRoot)
 210        {
 406211            ticket = scanCell!.AddOccupant(targetVoxel.WorldIndex, occupant);
 406212            if (!TryTrackOccupancy(world!, occupant, targetVoxel.WorldIndex, ticket))
 213            {
 4214                scanCell.TryRemoveOccupant(targetVoxel.WorldIndex, ticket);
 4215                return false;
 216            }
 217
 402218            TrackActiveScanCell(grid, targetVoxel.ScanCellKey);
 219
 402220            targetVoxel.OccupantCount++;
 402221            occupantCount = targetVoxel.OccupantCount;
 402222        }
 223
 402224        NotifyOccupantAdded(targetVoxel, occupant, ticket, occupantCount);
 225
 402226        return true;
 4227    }
 228
 229    /// <summary>
 230    /// Attempts to de-register the occupant from every voxel currently tracked in the supplied world.
 231    /// </summary>
 232    public static bool TryDeregister(GridWorld world, IVoxelOccupant occupant)
 233    {
 6234        TrackedOccupancy[] occupancies = GetTrackedOccupanciesSnapshot(world, occupant);
 6235        if (occupancies.Length == 0)
 1236            return false;
 237
 5238        bool removedAny = false;
 239
 22240        for (int i = 0; i < occupancies.Length; i++)
 241        {
 6242            WorldVoxelIndex voxelIndex = occupancies[i].VoxelIndex;
 6243            if (!world.TryGetGridAndVoxel(voxelIndex, out VoxelGrid? grid, out Voxel? voxel))
 244            {
 1245                removedAny |= ForgetTrackedOccupancy(world, occupant, voxelIndex);
 1246                continue;
 247            }
 248
 5249            removedAny |= grid!.TryRemoveVoxelOccupant(voxel!, occupant);
 250        }
 251
 5252        return removedAny;
 253    }
 254
 255    /// <summary>
 256    /// Attempts to remove an occupant from the given world-scoped voxel identity in the supplied world.
 257    /// </summary>
 258    public static bool TryRemoveVoxelOccupant(
 259        GridWorld world,
 260        WorldVoxelIndex index,
 261        IVoxelOccupant occupant)
 262    {
 2263        return occupant != null
 2264            && world != null
 2265            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 2266            && grid!.TryRemoveVoxelOccupant(voxel!, occupant);
 267    }
 268
 269    /// <summary>
 270    /// Attempts to remove an occupant from the given world position.
 271    /// </summary>
 272    public static bool TryRemoveVoxelOccupant(
 273        this VoxelGrid grid,
 274        IVoxelOccupant occupant)
 275    {
 141276        if (!TryGetGridOccupancies(grid, occupant, out TrackedOccupancy[] occupancies))
 4277            return false;
 278
 137279        return RemoveTrackedOccupancies(grid, occupant, occupancies);
 280    }
 281
 282    /// <summary>
 283    /// Attempts to remove an occupant at the specified voxel coordinates.
 284    /// </summary>
 285    public static bool TryRemoveVoxelOccupant(
 286        this VoxelGrid grid,
 287        VoxelIndex voxelIndex,
 288        IVoxelOccupant occupant)
 289    {
 4290        return occupant != null
 4291            && grid.TryGetVoxel(voxelIndex, out Voxel? targetVoxel)
 4292            && grid.TryRemoveVoxelOccupant(targetVoxel!, occupant);
 293    }
 294
 295    /// <summary>
 296    /// Removes an occupant from this grid.
 297    /// </summary>
 298    public static bool TryRemoveVoxelOccupant(
 299        this VoxelGrid grid,
 300        Voxel targetVoxel,
 301        IVoxelOccupant occupant)
 302    {
 160303        if (!TryGetRemovalContext(grid, targetVoxel, occupant, out ScanCell? scanCell, out int ticket))
 6304            return false;
 305
 154306        bool success = false;
 154307        byte occupantCount = targetVoxel.OccupantCount;
 308
 154309        lock (grid.OccupantSyncRoot)
 310        {
 154311            success = TryRemoveOccupantFromScanCell(
 154312                grid,
 154313                targetVoxel,
 154314                occupant,
 154315                scanCell!,
 154316                ticket,
 154317                out occupantCount);
 154318        }
 319
 154320        if (success)
 153321            NotifyOccupantRemoved(targetVoxel, occupant, ticket, occupantCount);
 322
 154323        return success;
 324    }
 325
 326    #endregion
 327
 328    #region Internal Helpers
 329
 330    internal static void ForgetTrackedOccupancies(
 331        GridWorld? world,
 332        IEnumerable<IVoxelOccupant> occupants,
 333        WorldVoxelIndex index)
 334    {
 179335        if (world == null)
 1336            return;
 337
 854338        foreach (IVoxelOccupant occupant in occupants)
 249339            ForgetTrackedOccupancy(world, occupant, index);
 178340    }
 341
 342    internal static void ClearTrackedOccupancies(GridWorld world)
 343    {
 427344        if (!TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 376345            return;
 346
 51347        lock (registry!.SyncRoot)
 51348            registry.Records.Clear();
 51349    }
 350
 351    internal static void ReleaseTrackedOccupancies(GridWorld world)
 352    {
 423353        if (!_occupancyRegistries.TryRemove(world, out WorldOccupancyRegistry? registry))
 372354            return;
 355
 51356        lock (registry.SyncRoot)
 51357            registry.Records.Clear();
 51358    }
 359
 360    #endregion
 361
 362    #region Private Methods
 363
 364    private static bool TryGetAddContext(
 365        VoxelGrid grid,
 366        Voxel targetVoxel,
 367        IVoxelOccupant occupant,
 368        out GridWorld? world,
 369        out ScanCell? scanCell)
 370    {
 408371        world = grid.World;
 408372        scanCell = null;
 408373        return occupant != null
 408374            && world != null
 408375            && targetVoxel.HasVacancy
 408376            && grid.TryGetScanCell(targetVoxel.ScanCellKey, out scanCell);
 377    }
 378
 379    private static void TrackActiveScanCell(VoxelGrid grid, int scanCellKey)
 380    {
 402381        grid.ActiveScanCells ??= SwiftHashSetPool<int>.Shared.Rent();
 402382        if (!grid.ActiveScanCells.Contains(scanCellKey))
 62383            grid.ActiveScanCells.Add(scanCellKey);
 402384    }
 385
 386    private static bool TryGetGridOccupancies(
 387        VoxelGrid grid,
 388        IVoxelOccupant occupant,
 389        out TrackedOccupancy[] occupancies)
 390    {
 141391        occupancies = Array.Empty<TrackedOccupancy>();
 141392        if (grid.World == null)
 1393            return false;
 394
 140395        occupancies = GetTrackedOccupanciesSnapshot(grid.World, occupant, grid.GridIndex);
 140396        return occupancies.Length > 0;
 397    }
 398
 399    private static bool RemoveTrackedOccupancies(
 400        VoxelGrid grid,
 401        IVoxelOccupant occupant,
 402        TrackedOccupancy[] occupancies)
 403    {
 137404        bool removedAny = false;
 405
 548406        for (int i = 0; i < occupancies.Length; i++)
 407        {
 137408            WorldVoxelIndex voxelIndex = occupancies[i].VoxelIndex;
 137409            if (!TryGetCurrentTrackedVoxel(grid, voxelIndex, out Voxel? voxel))
 410            {
 2411                removedAny |= ForgetTrackedOccupancy(grid.World!, occupant, voxelIndex);
 2412                continue;
 413            }
 414
 135415            removedAny |= TryRemoveVoxelOccupant(grid, voxel!, occupant);
 416        }
 417
 137418        return removedAny;
 419    }
 420
 421    private static bool TryGetCurrentTrackedVoxel(
 422        VoxelGrid grid,
 423        WorldVoxelIndex voxelIndex,
 424        out Voxel? voxel)
 425    {
 137426        voxel = null;
 137427        return grid.World != null
 137428            && voxelIndex.WorldSpawnToken == grid.World.SpawnToken
 137429            && voxelIndex.GridSpawnToken == grid.SpawnToken
 137430            && grid.TryGetVoxel(voxelIndex.VoxelIndex, out voxel);
 431    }
 432
 433    private static bool TryTrackOccupancy(
 434        GridWorld world,
 435        IVoxelOccupant occupant,
 436        WorldVoxelIndex index,
 437        int ticket)
 438    {
 412439        WorldOccupancyRegistry registry = GetWorldRegistry(world);
 412440        lock (registry.SyncRoot)
 441        {
 412442            if (!TryGetOrAddOccupancyRecord(registry, world, occupant, out OccupancyRecord record))
 2443                return false;
 444
 410445            if (record.Tickets.ContainsKey(index))
 446            {
 3447                GridForgeLogger.Channel.Warn($"Occupant {occupant.GlobalId} is already registered to voxel {index}.");
 3448                return false;
 449            }
 450
 407451            record.Tickets[index] = ticket;
 407452            return true;
 453        }
 412454    }
 455
 456    private static bool ForgetTrackedOccupancy(
 457        GridWorld world,
 458        IVoxelOccupant occupant,
 459        WorldVoxelIndex index)
 460    {
 410461        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 1462            return false;
 463
 409464        lock (registry!.SyncRoot)
 409465            return ForgetTrackedOccupancyUnsafe(world, occupant, index, registry);
 409466    }
 467
 468    private static bool ForgetTrackedOccupancyUnsafe(
 469        GridWorld world,
 470        IVoxelOccupant occupant,
 471        WorldVoxelIndex index,
 472        WorldOccupancyRegistry registry)
 473    {
 409474        if (!TryGetTrackedRecordUnsafe(world, occupant, out OccupancyRecord? record)
 409475            || !TryRemoveTrackedTicket(record!, index))
 476        {
 2477            return false;
 478        }
 479
 407480        if (record!.Tickets.Count == 0)
 403481            registry.Records.Remove(occupant.GlobalId);
 482
 407483        return true;
 484    }
 485
 486    private static bool TryRemoveTrackedTicket(OccupancyRecord record, WorldVoxelIndex index)
 487    {
 408488        if (!record.Tickets.ContainsKey(index))
 1489            return false;
 490
 407491        record.Tickets.Remove(index);
 407492        return true;
 493    }
 494
 495    private static bool TryGetTrackedRecordUnsafe(
 496        GridWorld world,
 497        IVoxelOccupant occupant,
 498        out OccupancyRecord? record)
 499    {
 742500        record = null;
 742501        if (world == null
 742502            || occupant == null
 742503            || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry)
 742504            || !registry!.Records.TryGetValue(occupant.GlobalId, out record))
 505        {
 15506            return false;
 507        }
 508
 727509        return ReferenceEquals(record.Occupant, occupant);
 510    }
 511
 512    private static TrackedOccupancy[] GetTrackedOccupanciesSnapshot(
 513        GridWorld world,
 514        IVoxelOccupant occupant,
 515        ushort? gridIndex = null)
 516    {
 159517        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 2518            return Array.Empty<TrackedOccupancy>();
 519
 157520        lock (registry!.SyncRoot)
 521        {
 157522            if (!TryGetTrackedRecordUnsafe(world, occupant, out OccupancyRecord? record) || record!.Tickets.Count == 0)
 9523                return Array.Empty<TrackedOccupancy>();
 524
 148525            TrackedOccupancy[] occupancies = new TrackedOccupancy[record.Tickets.Count];
 148526            int count = CopyTrackedOccupancies(record, gridIndex, occupancies);
 148527            return FinalizeTrackedOccupancySnapshot(occupancies, count);
 528        }
 157529    }
 530
 531    private static bool TryGetRemovalContext(
 532        VoxelGrid grid,
 533        Voxel targetVoxel,
 534        IVoxelOccupant occupant,
 535        out ScanCell? scanCell,
 536        out int ticket)
 537    {
 160538        scanCell = null;
 160539        ticket = -1;
 540
 160541        if (occupant == null || grid.World == null)
 1542            return false;
 543
 159544        if (!TryGetOccupancyTicket(grid.World, occupant, targetVoxel.WorldIndex, out ticket))
 545        {
 4546            GridForgeLogger.Channel.Warn($"Occupant {occupant.GlobalId} is not registered to voxel {targetVoxel.WorldInd
 4547            return false;
 548        }
 549
 155550        return targetVoxel.IsOccupied
 155551            && grid.TryGetScanCell(targetVoxel.ScanCellKey, out scanCell) == true;
 552    }
 553
 554    private static bool TryRemoveOccupantFromScanCell(
 555        VoxelGrid grid,
 556        Voxel targetVoxel,
 557        IVoxelOccupant occupant,
 558        ScanCell scanCell,
 559        int ticket,
 560        out byte occupantCount)
 561    {
 154562        occupantCount = targetVoxel.OccupantCount;
 563
 154564        if (!scanCell.TryRemoveOccupant(targetVoxel.WorldIndex, ticket))
 1565            return false;
 566
 153567        ForgetTrackedOccupancy(grid.World!, occupant, targetVoxel.WorldIndex);
 153568        ReleaseActiveScanCellIfEmpty(grid, targetVoxel, scanCell);
 569
 153570        targetVoxel.OccupantCount--;
 153571        occupantCount = targetVoxel.OccupantCount;
 153572        return true;
 573    }
 574
 575    private static void ReleaseActiveScanCellIfEmpty(
 576        VoxelGrid grid,
 577        Voxel targetVoxel,
 578        ScanCell scanCell)
 579    {
 153580        if (scanCell.IsOccupied || grid.ActiveScanCells == null)
 134581            return;
 582
 19583        grid.ActiveScanCells.Remove(targetVoxel.ScanCellKey);
 19584        if (grid.IsOccupied)
 1585            return;
 586
 18587        GridForgeLogger.Channel.Info($"Releasing unused active scan cells collection.");
 18588        SwiftHashSetPool<int>.Shared.Release(grid.ActiveScanCells);
 18589        grid.ActiveScanCells = null;
 18590    }
 591
 592    private static bool TryGetOrAddOccupancyRecord(
 593        WorldOccupancyRegistry registry,
 594        GridWorld world,
 595        IVoxelOccupant occupant,
 596        out OccupancyRecord record)
 597    {
 412598        if (registry.Records.TryGetValue(occupant.GlobalId, out record))
 599        {
 9600            if (ReferenceEquals(record.Occupant, occupant))
 7601                return true;
 602
 2603            GridForgeLogger.Channel.Warn($"Occupant id collision detected for {occupant.GlobalId} in world {world.SpawnT
 2604            return false;
 605        }
 606
 403607        record = new OccupancyRecord(occupant);
 403608        registry.Records[occupant.GlobalId] = record;
 403609        return true;
 610    }
 611
 612    private static int CopyTrackedOccupancies(
 613        OccupancyRecord record,
 614        ushort? gridIndex,
 615        TrackedOccupancy[] occupancies)
 616    {
 148617        int count = 0;
 618
 602619        foreach (WorldVoxelIndex index in record.Tickets.Keys)
 620        {
 153621            if (gridIndex.HasValue && index.GridIndex != gridIndex.Value)
 622                continue;
 623
 150624            occupancies[count++] = new TrackedOccupancy(index, record.Tickets[index]);
 625        }
 626
 148627        return count;
 628    }
 629
 630    private static TrackedOccupancy[] FinalizeTrackedOccupancySnapshot(
 631        TrackedOccupancy[] occupancies,
 632        int count)
 633    {
 148634        if (count == 0)
 1635            return Array.Empty<TrackedOccupancy>();
 636
 147637        if (count != occupancies.Length)
 1638            Array.Resize(ref occupancies, count);
 639
 147640        Array.Sort(occupancies, CompareTrackedOccupancies);
 147641        return occupancies;
 642    }
 643
 644    private static int CompareTrackedOccupancies(TrackedOccupancy left, TrackedOccupancy right)
 645    {
 12646        int result = left.VoxelIndex.WorldSpawnToken.CompareTo(right.VoxelIndex.WorldSpawnToken);
 12647        if (result != 0)
 2648            return result;
 649
 10650        result = left.VoxelIndex.GridIndex.CompareTo(right.VoxelIndex.GridIndex);
 10651        if (result != 0)
 3652            return result;
 653
 7654        result = left.VoxelIndex.VoxelIndex.x.CompareTo(right.VoxelIndex.VoxelIndex.x);
 7655        if (result != 0)
 2656            return result;
 657
 5658        result = left.VoxelIndex.VoxelIndex.y.CompareTo(right.VoxelIndex.VoxelIndex.y);
 5659        if (result != 0)
 1660            return result;
 661
 4662        result = left.VoxelIndex.VoxelIndex.z.CompareTo(right.VoxelIndex.VoxelIndex.z);
 4663        if (result != 0)
 1664            return result;
 665
 3666        result = left.VoxelIndex.GridSpawnToken.CompareTo(right.VoxelIndex.GridSpawnToken);
 3667        if (result != 0)
 1668            return result;
 669
 2670        return left.Ticket.CompareTo(right.Ticket);
 671    }
 672
 673    /// <summary>
 674    /// Notifies listeners that an occupant was added.
 675    /// </summary>
 676    private static void NotifyOccupantAdded(
 677        Voxel targetVoxel,
 678        IVoxelOccupant occupant,
 679        int ticket,
 680        byte occupantCount)
 681    {
 402682        OccupantEventInfo eventInfo = new(targetVoxel.WorldIndex, occupant, ticket, occupantCount);
 402683        Action<OccupantEventInfo>? handlers = _onOccupantAdded;
 402684        if (handlers != null)
 685        {
 5686            var handlerDelegates = handlers.GetInvocationList();
 22687            for (int i = 0; i < handlerDelegates.Length; i++)
 688            {
 689                try
 690                {
 6691                    ((Action<OccupantEventInfo>)handlerDelegates[i])(eventInfo);
 5692                }
 1693                catch (Exception ex)
 694                {
 1695                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Occupant add error: {ex.Message}");
 1696                }
 697            }
 698        }
 699
 402700        targetVoxel.NotifyOccupantAdded(eventInfo);
 402701    }
 702
 703    /// <summary>
 704    /// Notifies listeners that an occupant was removed.
 705    /// </summary>
 706    private static void NotifyOccupantRemoved(
 707        Voxel targetVoxel,
 708        IVoxelOccupant occupant,
 709        int ticket,
 710        byte occupantCount)
 711    {
 153712        OccupantEventInfo eventInfo = new(targetVoxel.WorldIndex, occupant, ticket, occupantCount);
 153713        Action<OccupantEventInfo>? handlers = _onOccupantRemoved;
 153714        if (handlers != null)
 715        {
 3716            var handlerDelegates = handlers.GetInvocationList();
 14717            for (int i = 0; i < handlerDelegates.Length; i++)
 718            {
 719                try
 720                {
 4721                    ((Action<OccupantEventInfo>)handlerDelegates[i])(eventInfo);
 3722                }
 1723                catch (Exception ex)
 724                {
 1725                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Occupant remove error: {ex.Message}
 1726                }
 727            }
 728        }
 729
 153730        targetVoxel.NotifyOccupantRemoved(eventInfo);
 153731    }
 732
 733    private static WorldOccupancyRegistry GetWorldRegistry(GridWorld world)
 734    {
 412735        return _occupancyRegistries.GetOrAdd(world, static _ => new WorldOccupancyRegistry());
 736    }
 737
 738    private static bool TryGetWorldRegistry(GridWorld world, out WorldOccupancyRegistry? registry)
 739    {
 1912740        registry = null;
 1912741        return _occupancyRegistries.TryGetValue(world, out registry);
 742    }
 743
 744    #endregion
 745}

Methods/Properties

add_OnOccupantAdded(System.Action`1<GridForge.Grids.OccupantEventInfo>)
remove_OnOccupantAdded(System.Action`1<GridForge.Grids.OccupantEventInfo>)
add_OnOccupantRemoved(System.Action`1<GridForge.Grids.OccupantEventInfo>)
remove_OnOccupantRemoved(System.Action`1<GridForge.Grids.OccupantEventInfo>)
.cctor()
.ctor(GridForge.Spatial.IVoxelOccupant)
.ctor()
.ctor(GridForge.Spatial.WorldVoxelIndex,System.Int32)
TryRegister(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant)
GetOccupiedIndices(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant)
TryGetOccupancyTicket(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex,System.Int32&)
TryAddVoxelOccupant(GridForge.Grids.GridWorld,GridForge.Spatial.WorldVoxelIndex,GridForge.Spatial.IVoxelOccupant)
TryAddVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Spatial.IVoxelOccupant)
TryAddVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Spatial.VoxelIndex,GridForge.Spatial.IVoxelOccupant)
TryAddVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant)
TryDeregister(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant)
TryRemoveVoxelOccupant(GridForge.Grids.GridWorld,GridForge.Spatial.WorldVoxelIndex,GridForge.Spatial.IVoxelOccupant)
TryRemoveVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Spatial.IVoxelOccupant)
TryRemoveVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Spatial.VoxelIndex,GridForge.Spatial.IVoxelOccupant)
TryRemoveVoxelOccupant(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant)
ForgetTrackedOccupancies(GridForge.Grids.GridWorld,System.Collections.Generic.IEnumerable`1<GridForge.Spatial.IVoxelOccupant>,GridForge.Spatial.WorldVoxelIndex)
ClearTrackedOccupancies(GridForge.Grids.GridWorld)
ReleaseTrackedOccupancies(GridForge.Grids.GridWorld)
TryGetAddContext(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.GridWorld&,GridForge.Grids.ScanCell&)
TrackActiveScanCell(GridForge.Grids.VoxelGrid,System.Int32)
TryGetGridOccupancies(GridForge.Grids.VoxelGrid,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.GridOccupantManager/TrackedOccupancy[]&)
RemoveTrackedOccupancies(GridForge.Grids.VoxelGrid,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.GridOccupantManager/TrackedOccupancy[])
TryGetCurrentTrackedVoxel(GridForge.Grids.VoxelGrid,GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.Voxel&)
TryTrackOccupancy(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex,System.Int32)
ForgetTrackedOccupancy(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex)
ForgetTrackedOccupancyUnsafe(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry)
TryRemoveTrackedTicket(GridForge.Grids.GridOccupantManager/OccupancyRecord,GridForge.Spatial.WorldVoxelIndex)
TryGetTrackedRecordUnsafe(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.GridOccupantManager/OccupancyRecord&)
GetTrackedOccupanciesSnapshot(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,System.Nullable`1<System.UInt16>)
TryGetRemovalContext(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.ScanCell&,System.Int32&)
TryRemoveOccupantFromScanCell(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.ScanCell,System.Int32,System.Byte&)
ReleaseActiveScanCellIfEmpty(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Grids.ScanCell)
TryGetOrAddOccupancyRecord(GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry,GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.GridOccupantManager/OccupancyRecord&)
CopyTrackedOccupancies(GridForge.Grids.GridOccupantManager/OccupancyRecord,System.Nullable`1<System.UInt16>,GridForge.Grids.GridOccupantManager/TrackedOccupancy[])
FinalizeTrackedOccupancySnapshot(GridForge.Grids.GridOccupantManager/TrackedOccupancy[],System.Int32)
CompareTrackedOccupancies(GridForge.Grids.GridOccupantManager/TrackedOccupancy,GridForge.Grids.GridOccupantManager/TrackedOccupancy)
NotifyOccupantAdded(GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,System.Int32,System.Byte)
NotifyOccupantRemoved(GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,System.Int32,System.Byte)
GetWorldRegistry(GridForge.Grids.GridWorld)
TryGetWorldRegistry(GridForge.Grids.GridWorld,GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry&)