< 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: 264
Uncovered lines: 0
Coverable lines: 264
Total lines: 732
Line coverage: 100%
Branch coverage
100%
Covered branches: 178
Total branches: 178
Branch coverage: 100%
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(...)100%88100%
TryAddVoxelOccupant(...)100%66100%
TryAddVoxelOccupant(...)100%44100%
TryAddVoxelOccupant(...)100%44100%
TryAddVoxelOccupant(...)100%66100%
TryDeregister(...)100%66100%
TryRemoveVoxelOccupant(...)100%66100%
TryRemoveVoxelOccupant(...)100%22100%
TryRemoveVoxelOccupant(...)100%44100%
TryRemoveVoxelOccupant(...)100%44100%
ForgetTrackedOccupancy(...)100%66100%
ClearTrackedOccupancies(...)100%22100%
ReleaseTrackedOccupancies(...)100%22100%
TryGetAddContext(...)100%44100%
TrackActiveScanCell(...)100%44100%
TryGetGridOccupancies(...)100%22100%
RemoveTrackedOccupancies(...)100%44100%
TryGetCurrentTrackedVoxel(...)100%66100%
TryTrackOccupancy(...)100%66100%
ForgetTrackedOccupancyUnsafe(...)100%66100%
TryRemoveTrackedTicket(...)100%22100%
TryGetTrackedRecordUnsafe(...)100%22100%
GetTrackedOccupanciesSnapshot(...)100%1010100%
TryGetRemovalContext(...)100%1010100%
TryRemoveOccupantFromScanCell(...)100%22100%
ReleaseActiveScanCellIfEmpty(...)100%88100%
TryGetOrAddOccupancyRecord(...)100%66100%
CopyTrackedOccupancies(...)100%66100%
FinalizeTrackedOccupancySnapshot(...)100%44100%
CompareTrackedOccupancies(...)100%1414100%
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 System;
 9using System.Collections.Concurrent;
 10using System.Collections.Generic;
 11using GridForge.Spatial;
 12using SwiftCollections;
 13using SwiftCollections.Pool;
 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    {
 1738        add => _onOccupantAdded += value;
 1739        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    {
 1750        add => _onOccupantRemoved += value;
 1751        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;
 67069        public readonly SwiftDictionary<WorldVoxelIndex, OccupantTicket> Tickets = new();
 70
 67071        public OccupancyRecord(IVoxelOccupant occupant)
 72        {
 67073            Occupant = occupant;
 67074        }
 75    }
 76
 77    /// <summary>
 78    /// Synchronizes one world's tracked occupant registrations.
 79    /// </summary>
 80    private sealed class WorldOccupancyRegistry
 81    {
 6082        public readonly object SyncRoot = new();
 6083        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 OccupantTicket Ticket;
 93
 94        public TrackedOccupancy(WorldVoxelIndex voxelIndex, OccupantTicket ticket)
 95        {
 17296            VoxelIndex = voxelIndex;
 17297            Ticket = ticket;
 17298        }
 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    {
 17124        TrackedOccupancy[] occupancies = GetTrackedOccupanciesSnapshot(world, occupant);
 17125        if (occupancies.Length == 0)
 10126            return Array.Empty<WorldVoxelIndex>();
 127
 7128        WorldVoxelIndex[] indices = new WorldVoxelIndex[occupancies.Length];
 32129        for (int i = 0; i < occupancies.Length; i++)
 9130            indices[i] = occupancies[i].VoxelIndex;
 131
 7132        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 OccupantTicket ticket)
 148    {
 196149        ticket = default;
 196150        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 6151            return false;
 152
 190153        lock (registry!.SyncRoot)
 190154            return TryGetTrackedRecordUnsafe(registry, occupant, out OccupancyRecord? record)
 190155                && record!.Tickets.TryGetValue(index, out ticket);
 190156    }
 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    {
 388177        return occupant != null
 388178            && grid.TryGetVoxel(occupant.Position, out Voxel? voxel)
 388179            && 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    {
 740203        if (!TryGetAddContext(grid, targetVoxel, occupant, out GridWorld? world, out ScanCell? scanCell))
 1204            return false;
 205
 206        OccupantTicket ticket;
 207        byte occupantCount;
 208
 739209        lock (grid.OccupantSyncRoot)
 210        {
 739211            if (!targetVoxel.HasVacancy)
 65212                return false;
 213
 674214            ticket = scanCell!.AddOccupant(targetVoxel.WorldIndex, occupant);
 673215            if (!TryTrackOccupancy(world!, occupant, targetVoxel.WorldIndex, ticket))
 216            {
 4217                scanCell.TryRemoveOccupant(targetVoxel.WorldIndex, ticket);
 4218                return false;
 219            }
 220
 669221            TrackActiveScanCell(grid, targetVoxel.ScanCellKey);
 222
 669223            targetVoxel.OccupantCount++;
 669224            occupantCount = targetVoxel.OccupantCount;
 669225        }
 226
 669227        NotifyOccupantAdded(targetVoxel, occupant, ticket, occupantCount);
 228
 669229        return true;
 69230    }
 231
 232    /// <summary>
 233    /// Attempts to de-register the occupant from every voxel currently tracked in the supplied world.
 234    /// </summary>
 235    public static bool TryDeregister(GridWorld world, IVoxelOccupant occupant)
 236    {
 6237        TrackedOccupancy[] occupancies = GetTrackedOccupanciesSnapshot(world, occupant);
 6238        if (occupancies.Length == 0)
 1239            return false;
 240
 5241        bool removedAny = false;
 242
 22243        for (int i = 0; i < occupancies.Length; i++)
 244        {
 6245            WorldVoxelIndex voxelIndex = occupancies[i].VoxelIndex;
 6246            if (!world.TryGetGridAndVoxel(voxelIndex, out VoxelGrid? grid, out Voxel? voxel))
 247            {
 1248                removedAny |= ForgetTrackedOccupancy(world, occupant, voxelIndex);
 1249                continue;
 250            }
 251
 5252            removedAny |= grid!.TryRemoveVoxelOccupant(voxel!, occupant);
 253        }
 254
 5255        return removedAny;
 256    }
 257
 258    /// <summary>
 259    /// Attempts to remove an occupant from the given world-scoped voxel identity in the supplied world.
 260    /// </summary>
 261    public static bool TryRemoveVoxelOccupant(
 262        GridWorld world,
 263        WorldVoxelIndex index,
 264        IVoxelOccupant occupant)
 265    {
 2266        return occupant != null
 2267            && world != null
 2268            && world.TryGetGridAndVoxel(index, out VoxelGrid? grid, out Voxel? voxel)
 2269            && grid!.TryRemoveVoxelOccupant(voxel!, occupant);
 270    }
 271
 272    /// <summary>
 273    /// Attempts to remove an occupant from the given world position.
 274    /// </summary>
 275    public static bool TryRemoveVoxelOccupant(
 276        this VoxelGrid grid,
 277        IVoxelOccupant occupant)
 278    {
 143279        if (!TryGetGridOccupancies(grid, occupant, out TrackedOccupancy[] occupancies))
 4280            return false;
 281
 139282        return RemoveTrackedOccupancies(grid, occupant, occupancies);
 283    }
 284
 285    /// <summary>
 286    /// Attempts to remove an occupant at the specified voxel coordinates.
 287    /// </summary>
 288    public static bool TryRemoveVoxelOccupant(
 289        this VoxelGrid grid,
 290        VoxelIndex voxelIndex,
 291        IVoxelOccupant occupant)
 292    {
 4293        return occupant != null
 4294            && grid.TryGetVoxel(voxelIndex, out Voxel? targetVoxel)
 4295            && grid.TryRemoveVoxelOccupant(targetVoxel!, occupant);
 296    }
 297
 298    /// <summary>
 299    /// Removes an occupant from this grid.
 300    /// </summary>
 301    public static bool TryRemoveVoxelOccupant(
 302        this VoxelGrid grid,
 303        Voxel targetVoxel,
 304        IVoxelOccupant occupant)
 305    {
 162306        if (!TryGetRemovalContext(grid, targetVoxel, occupant, out ScanCell? scanCell, out OccupantTicket ticket))
 6307            return false;
 308
 156309        bool success = false;
 156310        byte occupantCount = targetVoxel.OccupantCount;
 311
 156312        lock (grid.OccupantSyncRoot)
 313        {
 156314            success = TryRemoveOccupantFromScanCell(
 156315                grid,
 156316                targetVoxel,
 156317                occupant,
 156318                scanCell!,
 156319                ticket,
 156320                out occupantCount);
 156321        }
 322
 156323        if (success)
 155324            NotifyOccupantRemoved(targetVoxel, occupant, ticket, occupantCount);
 325
 156326        return success;
 327    }
 328
 329    #endregion
 330
 331    #region Internal Helpers
 332
 333    internal static bool ForgetTrackedOccupancy(
 334        GridWorld? world,
 335        IVoxelOccupant occupant,
 336        WorldVoxelIndex index)
 337    {
 677338        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 2339            return false;
 340
 675341        lock (registry!.SyncRoot)
 675342            return ForgetTrackedOccupancyUnsafe(occupant, index, registry);
 675343    }
 344
 345    internal static void ClearTrackedOccupancies(GridWorld world)
 346    {
 542347        if (!TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 481348            return;
 349
 61350        lock (registry!.SyncRoot)
 61351            registry.Records.Clear();
 61352    }
 353
 354    internal static void ReleaseTrackedOccupancies(GridWorld world)
 355    {
 533356        if (!_occupancyRegistries.TryRemove(world, out WorldOccupancyRegistry? registry))
 473357            return;
 358
 60359        lock (registry.SyncRoot)
 60360            registry.Records.Clear();
 60361    }
 362
 363    #endregion
 364
 365    #region Private Methods
 366
 367    private static bool TryGetAddContext(
 368        VoxelGrid grid,
 369        Voxel targetVoxel,
 370        IVoxelOccupant occupant,
 371        out GridWorld? world,
 372        out ScanCell? scanCell)
 373    {
 740374        world = grid.World;
 740375        scanCell = null;
 740376        return occupant != null
 740377            && world != null
 740378            && grid.TryGetScanCell(targetVoxel.ScanCellKey, out scanCell);
 379    }
 380
 381    private static void TrackActiveScanCell(VoxelGrid grid, int scanCellKey)
 382    {
 669383        grid.ActiveScanCells ??= SwiftHashSetPool<int>.Shared.Rent();
 669384        if (!grid.ActiveScanCells.Contains(scanCellKey))
 75385            grid.ActiveScanCells.Add(scanCellKey);
 669386    }
 387
 388    private static bool TryGetGridOccupancies(
 389        VoxelGrid grid,
 390        IVoxelOccupant occupant,
 391        out TrackedOccupancy[] occupancies)
 392    {
 143393        occupancies = Array.Empty<TrackedOccupancy>();
 143394        if (grid.World == null)
 1395            return false;
 396
 142397        occupancies = GetTrackedOccupanciesSnapshot(grid.World, occupant, grid.GridIndex);
 142398        return occupancies.Length > 0;
 399    }
 400
 401    private static bool RemoveTrackedOccupancies(
 402        VoxelGrid grid,
 403        IVoxelOccupant occupant,
 404        TrackedOccupancy[] occupancies)
 405    {
 139406        bool removedAny = false;
 407
 556408        for (int i = 0; i < occupancies.Length; i++)
 409        {
 139410            WorldVoxelIndex voxelIndex = occupancies[i].VoxelIndex;
 139411            if (!TryGetCurrentTrackedVoxel(grid, voxelIndex, out Voxel? voxel))
 412            {
 2413                removedAny |= ForgetTrackedOccupancy(grid.World!, occupant, voxelIndex);
 2414                continue;
 415            }
 416
 137417            removedAny |= TryRemoveVoxelOccupant(grid, voxel!, occupant);
 418        }
 419
 139420        return removedAny;
 421    }
 422
 423    private static bool TryGetCurrentTrackedVoxel(
 424        VoxelGrid grid,
 425        WorldVoxelIndex voxelIndex,
 426        out Voxel? voxel)
 427    {
 139428        voxel = null;
 139429        return grid.World != null
 139430            && voxelIndex.WorldSpawnToken == grid.World.SpawnToken
 139431            && voxelIndex.GridSpawnToken == grid.SpawnToken
 139432            && grid.TryGetVoxel(voxelIndex.VoxelIndex, out voxel);
 433    }
 434
 435    private static bool TryTrackOccupancy(
 436        GridWorld world,
 437        IVoxelOccupant occupant,
 438        WorldVoxelIndex index,
 439        OccupantTicket ticket)
 440    {
 679441        WorldOccupancyRegistry registry = GetWorldRegistry(world);
 679442        lock (registry.SyncRoot)
 443        {
 679444            if (!TryGetOrAddOccupancyRecord(registry, world, occupant, out OccupancyRecord record))
 2445                return false;
 446
 677447            if (record.Tickets.ContainsKey(index))
 448            {
 3449                GridForgeLogger.Channel.Warn($"Occupant {occupant.GlobalId} is already registered to voxel {index}.");
 3450                return false;
 451            }
 452
 674453            record.Tickets[index] = ticket;
 674454            return true;
 455        }
 679456    }
 457
 458    private static bool ForgetTrackedOccupancyUnsafe(
 459        IVoxelOccupant occupant,
 460        WorldVoxelIndex index,
 461        WorldOccupancyRegistry registry)
 462    {
 675463        if (!TryGetTrackedRecordUnsafe(registry, occupant, out OccupancyRecord? record)
 675464            || !TryRemoveTrackedTicket(record!, index))
 465        {
 2466            return false;
 467        }
 468
 673469        if (record!.Tickets.Count == 0)
 669470            registry.Records.Remove(occupant.GlobalId);
 471
 673472        return true;
 473    }
 474
 475    private static bool TryRemoveTrackedTicket(OccupancyRecord record, WorldVoxelIndex index)
 476    {
 674477        if (!record.Tickets.ContainsKey(index))
 1478            return false;
 479
 673480        record.Tickets.Remove(index);
 673481        return true;
 482    }
 483
 484    private static bool TryGetTrackedRecordUnsafe(
 485        WorldOccupancyRegistry registry,
 486        IVoxelOccupant occupant,
 487        out OccupancyRecord? record)
 488    {
 1026489        record = null;
 1026490        if (!registry.Records.TryGetValue(occupant.GlobalId, out record))
 16491            return false;
 492
 1010493        return ReferenceEquals(record.Occupant, occupant);
 494    }
 495
 496    private static TrackedOccupancy[] GetTrackedOccupanciesSnapshot(
 497        GridWorld world,
 498        IVoxelOccupant occupant,
 499        ushort? gridIndex = null)
 500    {
 165501        if (world == null || occupant == null || !TryGetWorldRegistry(world, out WorldOccupancyRegistry? registry))
 4502            return Array.Empty<TrackedOccupancy>();
 503
 161504        lock (registry!.SyncRoot)
 505        {
 161506            if (!TryGetTrackedRecordUnsafe(registry, occupant, out OccupancyRecord? record) || record!.Tickets.Count == 
 9507                return Array.Empty<TrackedOccupancy>();
 508
 152509            TrackedOccupancy[] occupancies = new TrackedOccupancy[record.Tickets.Count];
 152510            int count = CopyTrackedOccupancies(record, gridIndex, occupancies);
 152511            return FinalizeTrackedOccupancySnapshot(occupancies, count);
 512        }
 161513    }
 514
 515    private static bool TryGetRemovalContext(
 516        VoxelGrid grid,
 517        Voxel targetVoxel,
 518        IVoxelOccupant occupant,
 519        out ScanCell? scanCell,
 520        out OccupantTicket ticket)
 521    {
 162522        scanCell = null;
 162523        ticket = default;
 524
 162525        if (occupant == null || grid.World == null)
 1526            return false;
 527
 161528        if (!TryGetOccupancyTicket(grid.World, occupant, targetVoxel.WorldIndex, out ticket))
 529        {
 4530            GridForgeLogger.Channel.Warn($"Occupant {occupant.GlobalId} is not registered to voxel {targetVoxel.WorldInd
 4531            return false;
 532        }
 533
 157534        return targetVoxel.IsOccupied
 157535            && grid.TryGetScanCell(targetVoxel.ScanCellKey, out scanCell) == true;
 536    }
 537
 538    private static bool TryRemoveOccupantFromScanCell(
 539        VoxelGrid grid,
 540        Voxel targetVoxel,
 541        IVoxelOccupant occupant,
 542        ScanCell scanCell,
 543        OccupantTicket ticket,
 544        out byte occupantCount)
 545    {
 156546        occupantCount = targetVoxel.OccupantCount;
 547
 156548        if (!scanCell.TryRemoveOccupant(targetVoxel.WorldIndex, ticket))
 1549            return false;
 550
 155551        ForgetTrackedOccupancy(grid.World!, occupant, targetVoxel.WorldIndex);
 155552        ReleaseActiveScanCellIfEmpty(grid, targetVoxel, scanCell);
 553
 155554        targetVoxel.OccupantCount--;
 155555        occupantCount = targetVoxel.OccupantCount;
 155556        return true;
 557    }
 558
 559    private static void ReleaseActiveScanCellIfEmpty(
 560        VoxelGrid grid,
 561        Voxel targetVoxel,
 562        ScanCell scanCell)
 563    {
 155564        if (scanCell.IsOccupied || grid.ActiveScanCells == null)
 134565            return;
 566
 21567        grid.ActiveScanCells.Remove(targetVoxel.ScanCellKey);
 21568        if (grid.IsOccupied)
 1569            return;
 570
 20571        GridForgeLogger.Channel.Info($"Releasing unused active scan cells collection.");
 20572        SwiftHashSetPool<int>.Shared.Release(grid.ActiveScanCells);
 20573        grid.ActiveScanCells = null;
 20574    }
 575
 576    private static bool TryGetOrAddOccupancyRecord(
 577        WorldOccupancyRegistry registry,
 578        GridWorld world,
 579        IVoxelOccupant occupant,
 580        out OccupancyRecord record)
 581    {
 679582        if (registry.Records.TryGetValue(occupant.GlobalId, out record))
 583        {
 9584            if (ReferenceEquals(record.Occupant, occupant))
 7585                return true;
 586
 2587            GridForgeLogger.Channel.Warn($"Occupant id collision detected for {occupant.GlobalId} in world {world.SpawnT
 2588            return false;
 589        }
 590
 670591        record = new OccupancyRecord(occupant);
 670592        registry.Records[occupant.GlobalId] = record;
 670593        return true;
 594    }
 595
 596    private static int CopyTrackedOccupancies(
 597        OccupancyRecord record,
 598        ushort? gridIndex,
 599        TrackedOccupancy[] occupancies)
 600    {
 152601        int count = 0;
 602
 618603        foreach (WorldVoxelIndex index in record.Tickets.Keys)
 604        {
 157605            if (gridIndex.HasValue && index.GridIndex != gridIndex.Value)
 606                continue;
 607
 154608            occupancies[count++] = new TrackedOccupancy(index, record.Tickets[index]);
 609        }
 610
 152611        return count;
 612    }
 613
 614    private static TrackedOccupancy[] FinalizeTrackedOccupancySnapshot(
 615        TrackedOccupancy[] occupancies,
 616        int count)
 617    {
 152618        if (count == 0)
 1619            return Array.Empty<TrackedOccupancy>();
 620
 151621        if (count != occupancies.Length)
 1622            Array.Resize(ref occupancies, count);
 623
 151624        Array.Sort(occupancies, CompareTrackedOccupancies);
 151625        return occupancies;
 626    }
 627
 628    private static int CompareTrackedOccupancies(TrackedOccupancy left, TrackedOccupancy right)
 629    {
 13630        int result = left.VoxelIndex.WorldSpawnToken.CompareTo(right.VoxelIndex.WorldSpawnToken);
 13631        if (result != 0)
 2632            return result;
 633
 11634        result = left.VoxelIndex.GridIndex.CompareTo(right.VoxelIndex.GridIndex);
 11635        if (result != 0)
 3636            return result;
 637
 8638        result = left.VoxelIndex.VoxelIndex.x.CompareTo(right.VoxelIndex.VoxelIndex.x);
 8639        if (result != 0)
 2640            return result;
 641
 6642        result = left.VoxelIndex.VoxelIndex.y.CompareTo(right.VoxelIndex.VoxelIndex.y);
 6643        if (result != 0)
 1644            return result;
 645
 5646        result = left.VoxelIndex.VoxelIndex.z.CompareTo(right.VoxelIndex.VoxelIndex.z);
 5647        if (result != 0)
 1648            return result;
 649
 4650        result = left.VoxelIndex.GridSpawnToken.CompareTo(right.VoxelIndex.GridSpawnToken);
 4651        if (result != 0)
 1652            return result;
 653
 3654        result = left.Ticket.Slot.CompareTo(right.Ticket.Slot);
 3655        return result != 0
 3656            ? result
 3657            : left.Ticket.Generation.CompareTo(right.Ticket.Generation);
 658    }
 659
 660    /// <summary>
 661    /// Notifies listeners that an occupant was added.
 662    /// </summary>
 663    private static void NotifyOccupantAdded(
 664        Voxel targetVoxel,
 665        IVoxelOccupant occupant,
 666        OccupantTicket ticket,
 667        byte occupantCount)
 668    {
 669669        OccupantEventInfo eventInfo = new(targetVoxel.WorldIndex, occupant, ticket, occupantCount);
 669670        Action<OccupantEventInfo>? handlers = _onOccupantAdded;
 669671        if (handlers != null)
 672        {
 5673            var handlerDelegates = handlers.GetInvocationList();
 22674            for (int i = 0; i < handlerDelegates.Length; i++)
 675            {
 676                try
 677                {
 6678                    ((Action<OccupantEventInfo>)handlerDelegates[i])(eventInfo);
 5679                }
 1680                catch (Exception ex)
 681                {
 1682                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Occupant add error: {ex.Message}");
 1683                }
 684            }
 685        }
 686
 669687        targetVoxel.NotifyOccupantAdded(eventInfo);
 669688    }
 689
 690    /// <summary>
 691    /// Notifies listeners that an occupant was removed.
 692    /// </summary>
 693    private static void NotifyOccupantRemoved(
 694        Voxel targetVoxel,
 695        IVoxelOccupant occupant,
 696        OccupantTicket ticket,
 697        byte occupantCount)
 698    {
 155699        OccupantEventInfo eventInfo = new(targetVoxel.WorldIndex, occupant, ticket, occupantCount);
 155700        Action<OccupantEventInfo>? handlers = _onOccupantRemoved;
 155701        if (handlers != null)
 702        {
 3703            var handlerDelegates = handlers.GetInvocationList();
 14704            for (int i = 0; i < handlerDelegates.Length; i++)
 705            {
 706                try
 707                {
 4708                    ((Action<OccupantEventInfo>)handlerDelegates[i])(eventInfo);
 3709                }
 1710                catch (Exception ex)
 711                {
 1712                    GridForgeLogger.Channel.Error($"[Voxel {targetVoxel.WorldIndex}] Occupant remove error: {ex.Message}
 1713                }
 714            }
 715        }
 716
 155717        targetVoxel.NotifyOccupantRemoved(eventInfo);
 155718    }
 719
 720    private static WorldOccupancyRegistry GetWorldRegistry(GridWorld world)
 721    {
 679722        return _occupancyRegistries.GetOrAdd(world, static _ => new WorldOccupancyRegistry());
 723    }
 724
 725    private static bool TryGetWorldRegistry(GridWorld world, out WorldOccupancyRegistry? registry)
 726    {
 1574727        registry = null;
 1574728        return _occupancyRegistries.TryGetValue(world, out registry);
 729    }
 730
 731    #endregion
 732}

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,GridForge.OccupantTicket)
TryRegister(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant)
GetOccupiedIndices(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant)
TryGetOccupancyTicket(GridForge.Grids.GridWorld,GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex,GridForge.OccupantTicket&)
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)
ForgetTrackedOccupancy(GridForge.Grids.GridWorld,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,GridForge.OccupantTicket)
ForgetTrackedOccupancyUnsafe(GridForge.Spatial.IVoxelOccupant,GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry)
TryRemoveTrackedTicket(GridForge.Grids.GridOccupantManager/OccupancyRecord,GridForge.Spatial.WorldVoxelIndex)
TryGetTrackedRecordUnsafe(GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry,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&,GridForge.OccupantTicket&)
TryRemoveOccupantFromScanCell(GridForge.Grids.VoxelGrid,GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,GridForge.Grids.ScanCell,GridForge.OccupantTicket,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,GridForge.OccupantTicket,System.Byte)
NotifyOccupantRemoved(GridForge.Grids.Voxel,GridForge.Spatial.IVoxelOccupant,GridForge.OccupantTicket,System.Byte)
GetWorldRegistry(GridForge.Grids.GridWorld)
TryGetWorldRegistry(GridForge.Grids.GridWorld,GridForge.Grids.GridOccupantManager/WorldOccupancyRegistry&)