< Summary

Information
Class: Gravitas.PhysicsPartition
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Partitions/3D/PhysicsPartition.cs
Line coverage
100%
Covered lines: 157
Uncovered lines: 0
Coverable lines: 157
Total lines: 369
Line coverage: 100%
Branch coverage
100%
Covered branches: 114
Total branches: 114
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
get_IsAllocated()100%11100%
get_IsEmpty()100%1010100%
get_EmptySinceFrame()100%11100%
get_RetainedIndex()100%11100%
get_AwakeDynamicObjectCount()100%22100%
get_Owner()100%11100%
OnAddToVoxel(...)100%11100%
Distribute(...)100%1818100%
CopySortedStaticStyleIds(...)100%11100%
CopyAllColliderIds(...)100%11100%
CopyStaticStyleColliderIds(...)100%11100%
CopyIds(...)100%44100%
ProcessPair(...)100%44100%
AddDynamicObject(...)100%66100%
AddStaticObject(...)100%44100%
AddKinematicObject(...)100%44100%
RemoveDynamicObject(...)100%1010100%
RemoveStaticObject(...)100%66100%
RemoveKinematicObject(...)100%66100%
ContainsAwakeDynamicObject(...)100%22100%
SetDynamicObjectAwake(...)100%1010100%
IsDynamicObjectAwake(...)100%44100%
OnRemoveFromVoxel(...)100%11100%
SetOwner(...)100%22100%
IsOwnedBy(...)100%11100%
SetRetainedIndex(...)100%11100%
ClearRetainedIndex()100%11100%
ResetRetainedMembership()100%1010100%
ResetForPool()100%1010100%
MarkOccupied()100%11100%
MarkEmptyIfUnoccupied()100%22100%
MarkEmpty(...)100%11100%
SetParentIndex(...)100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_RetainedIndex()100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_IsEmpty()100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_EmptySinceFrame()100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.IsOwnedBy(...)100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.SetRetainedIndex(...)100%11100%
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.ClearRetainedIndex()100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Partitions/3D/PhysicsPartition.cs

#LineLine coverage
 1//=======================================================================
 2// PhysicsPartition.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Gravitas.CollisionHandling;
 9using GridForge.Grids;
 10using GridForge.Spatial;
 11using SwiftCollections;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16internal enum PhysicsPartitionMobilityKind
 17{
 18    Dynamic = 0,
 19    Kinematic = 1,
 20    Static = 2
 21}
 22
 23/// <summary>
 24/// Stores the 3D collider IDs assigned to one GridForge voxel.
 25/// </summary>
 26public class PhysicsPartition : IVoxelPartition, IRetainedPhysicsPartition<GravitasCollisionService>
 27{
 28    private GravitasCollisionService? _owner;
 37299429    private int _emptySinceFrame = -1;
 37299430    private int _retainedIndex = -1;
 31
 32    /// <summary>Gets or sets the world voxel occupied by this partition.</summary>
 33    public WorldVoxelIndex WorldIndex { get; set; }
 34
 35    /// <summary>Gets or sets whether this partition is attached to a voxel.</summary>
 36    public bool IsPartitioned { get; set; }
 37
 38    /// <summary>
 39    /// Stores context-local dynamic body IDs.
 40    /// </summary>
 41    public SwiftSparseSet? ContainedDynamicObjects;
 42
 43    /// <summary>
 44    /// Stores the subset of dynamic collider IDs whose bodies can currently drive collision work.
 45    /// </summary>
 46    public SwiftSparseSet? ContainedAwakeDynamicObjects;
 47
 48    /// <summary>Stores context-local kinematic collider IDs.</summary>
 49    public SwiftSparseSet? ContainedKinematicObjects;
 50
 51    /// <summary>Stores context-local static collider IDs.</summary>
 52    public SwiftSparseSet? ContainedStaticObjects;
 53
 54    /// <summary>Gets the active-partition slot, or <c>-1</c> when inactive.</summary>
 55    public int ActivationId { get; private set; }
 56
 57    /// <summary>Gets whether this partition has an active-partition slot.</summary>
 3684958    public bool IsAllocated => ActivationId != -1;
 59
 60    internal bool IsEmpty =>
 214748364761        (ContainedDynamicObjects?.Count ?? 0) == 0
 214748364762        && (ContainedKinematicObjects?.Count ?? 0) == 0
 214748364763        && (ContainedStaticObjects?.Count ?? 0) == 0;
 64
 918765    internal int EmptySinceFrame => _emptySinceFrame;
 66
 81049967    internal int RetainedIndex => _retainedIndex;
 68
 69    /// <summary>
 70    /// Gets the number of awake dynamic IDs currently in this partition.
 71    /// </summary>
 872    public int AwakeDynamicObjectCount => ContainedAwakeDynamicObjects?.Count ?? 0;
 73
 74    /// <summary>Gets the collision service that owns this partition.</summary>
 75    public GravitasCollisionService Owner
 76    {
 77        get
 78        {
 222856379            SwiftThrowHelper.ThrowIfTrue(
 222856380                _owner == null,
 222856381                nameof(PhysicsPartition),
 222856382                "PhysicsPartition is missing its owner collision service.");
 222856083            return _owner!;
 84        }
 85    }
 86
 87    /// <summary>Creates an inactive 3D physics partition.</summary>
 37299488    public PhysicsPartition()
 89    {
 37299490        ActivationId = -1;
 37299491    }
 92
 93    /// <summary>Attaches this partition to a voxel.</summary>
 94    public void OnAddToVoxel(Voxel voxel)
 95    {
 40524796        SwiftThrowHelper.ThrowIfTrue(_owner == null, nameof(PhysicsPartition), "PhysicsPartition is missing its owner co
 40524797        WorldIndex = voxel.WorldIndex;
 40524798        IsPartitioned = true;
 40524799    }
 100
 101    internal void Distribute(
 102        SwiftList<int> dynamicIds,
 103        SwiftList<int> staticIds)
 104    {
 126223105        int dynamicCount = ContainedDynamicObjects?.Count ?? 0;
 126223106        int awakeDynamicCount = ContainedAwakeDynamicObjects?.Count ?? 0;
 126223107        if (ContainedDynamicObjects == null || dynamicCount == 0 || ContainedAwakeDynamicObjects == null || awakeDynamic
 13873108            return;
 109
 112350110        ContainedDynamicObjects.CopySortedKeysTo(dynamicIds);
 112350111        CopySortedStaticStyleIds(staticIds);
 112
 113        // Sleeping bodies stay query-visible in dynamic membership, while awake membership gates partition work.
 114        // Once an active partition is selected, all local dynamic links are emitted so the discrete
 115        // island builder can wake and solve connected bodies in a stable graph instead of a flat pair pass.
 561042116        for (int j = 0; j < dynamicIds.Count; j++)
 117        {
 168171118            int id1 = dynamicIds[j];
 510000119            for (int k = j + 1; k < dynamicIds.Count; k++)
 86829120                ProcessPair(id1, dynamicIds[k]);
 121
 435354122            for (int k = 0; k < staticIds.Count; k++)
 123            {
 49506124                int id2 = staticIds[k];
 49506125                ProcessPair(id1, id2);
 126            }
 127        }
 112350128    }
 129
 130    private void CopySortedStaticStyleIds(SwiftList<int> destination)
 131    {
 112350132        destination.FastClear();
 112350133        CopyIds(ContainedKinematicObjects, destination);
 112350134        CopyIds(ContainedStaticObjects, destination);
 112350135        destination.SortInPlace();
 112350136    }
 137
 138    internal void CopyAllColliderIds(SwiftList<int> destination)
 139    {
 2021140        destination.FastClear();
 2021141        CopyIds(ContainedDynamicObjects, destination);
 2021142        CopyIds(ContainedKinematicObjects, destination);
 2021143        CopyIds(ContainedStaticObjects, destination);
 2021144        destination.SortInPlace();
 2021145    }
 146
 147    internal void CopyStaticStyleColliderIds(SwiftList<int> destination)
 148    {
 2149        destination.FastClear();
 2150        CopyIds(ContainedKinematicObjects, destination);
 2151        CopyIds(ContainedStaticObjects, destination);
 2152        destination.SortInPlace();
 2153    }
 154
 155    private static void CopyIds(SwiftSparseSet? source, SwiftList<int> destination)
 156    {
 230767157        if (source == null)
 202864158            return;
 159
 110156160        for (int i = 0; i < source.Count; i++)
 27175161            destination.Add(source.DenseKeys[i]);
 27903162    }
 163
 164    private void ProcessPair(int id1, int id2)
 165    {
 136335166        GravitasCollisionService owner = Owner;
 136335167        CollisionPair? pair = owner.Context.Physics.GetCollisionPair(id1, id2);
 168
 169        //Ensures collision pairs are not run twice
 136335170        if (pair == null || pair.PartitionVersion == owner.Version)
 132043171            return;
 172
 4292173        pair.PartitionVersion = owner.Version;
 4292174        pair.UpdateCollisionDeferred();
 4292175    }
 176
 177    /// <summary>Adds a dynamic collider ID to this partition.</summary>
 178    public void AddDynamicObject(int item)
 179    {
 306991180        ContainedDynamicObjects ??= new();
 306991181        if (!ContainedDynamicObjects.Add(item))
 2182            return;
 183
 306989184        MarkOccupied();
 306989185        SetDynamicObjectAwake(item, IsDynamicObjectAwake(item));
 186
 306989187        if (ContainedDynamicObjects.Count == 1)
 220726188            ActivationId = Owner.ActivatePartition(this);
 306989189    }
 190
 191    /// <summary>Adds a static collider ID to this partition.</summary>
 192    public void AddStaticObject(int item)
 193    {
 308360194        ContainedStaticObjects ??= new();
 308360195        if (ContainedStaticObjects.Add(item))
 308358196            MarkOccupied();
 308360197    }
 198
 199    /// <summary>Adds a kinematic collider ID to this partition.</summary>
 200    public void AddKinematicObject(int item)
 201    {
 358940202        ContainedKinematicObjects ??= new();
 358940203        if (ContainedKinematicObjects.Add(item))
 358938204            MarkOccupied();
 358940205    }
 206
 207    /// <summary>Removes a dynamic collider ID from this partition.</summary>
 208    public void RemoveDynamicObject(int item)
 209    {
 158850210        if (ContainedDynamicObjects?.Remove(item) != true)
 211        {
 4212            GravitasLogger.DebugChannel.Info($"Dynamic item not removed - {item}");
 4213            return;
 214        }
 215
 158846216        ContainedAwakeDynamicObjects?.Remove(item);
 217
 158846218        if (ContainedDynamicObjects.Count > 0)
 75138219            return;
 220
 221        // If there are no more dynamic objects, we can deactivate the partition to save on future checks until it's nee
 83708222        Owner.DeactivatePartition(ActivationId);
 83708223        ActivationId = -1;
 83708224        MarkEmptyIfUnoccupied();
 83708225    }
 226
 227    /// <summary>Removes a static collider ID from this partition.</summary>
 228    public void RemoveStaticObject(int item)
 229    {
 6158230        if (ContainedStaticObjects?.Remove(item) != true)
 231        {
 5232            GravitasLogger.DebugChannel.Info($"Static item not removed - {item}");
 5233            return;
 234        }
 235
 6153236        MarkEmptyIfUnoccupied();
 6153237    }
 238
 239    /// <summary>Removes a kinematic collider ID from this partition.</summary>
 240    public void RemoveKinematicObject(int item)
 241    {
 342285242        if (ContainedKinematicObjects?.Remove(item) != true)
 243        {
 5244            GravitasLogger.DebugChannel.Info($"Kinematic item not removed - {item}");
 5245            return;
 246        }
 247
 342280248        MarkEmptyIfUnoccupied();
 342280249    }
 250
 251    /// <summary>
 252    /// Returns true when the supplied dynamic collider ID is marked awake in this partition.
 253    /// </summary>
 9254    public bool ContainsAwakeDynamicObject(int item) => ContainedAwakeDynamicObjects?.Contains(item) == true;
 255
 256    /// <summary>
 257    /// Updates the awake dynamic subset for a collider already present in dynamic membership.
 258    /// </summary>
 259    public void SetDynamicObjectAwake(int item, bool awake)
 260    {
 315644261        if (ContainedDynamicObjects?.Contains(item) != true)
 2262            return;
 263
 315642264        if (awake)
 265        {
 309850266            ContainedAwakeDynamicObjects ??= new();
 309850267            ContainedAwakeDynamicObjects.Add(item);
 309850268            return;
 269        }
 270
 5792271        ContainedAwakeDynamicObjects?.Remove(item);
 5575272    }
 273
 274    private bool IsDynamicObjectAwake(int item)
 275    {
 306989276        if (!Owner.Context.Physics.TryGetColliderById(item, out Gravitas.Colliders.LSCollider? collider))
 12277            return true;
 278
 306977279        SolidBody? body = collider!.Body;
 306977280        return body == null || body.IsAwakeForCollision;
 281    }
 282
 283    /// <summary>Releases this partition when it is removed from a voxel.</summary>
 284    public void OnRemoveFromVoxel(Voxel voxel)
 285    {
 405248286        Owner.ReleasePartition(this);
 405247287    }
 288
 289    internal void SetOwner(GravitasCollisionService owner)
 290    {
 405263291        SwiftThrowHelper.ThrowIfNull(owner, nameof(owner));
 405263292        SwiftThrowHelper.ThrowIfArgument(
 405263293            _owner != null && !ReferenceEquals(_owner, owner),
 405263294            nameof(owner),
 405263295            "PhysicsPartition is already owned by a different collision service.");
 296
 405262297        _owner = owner;
 405262298    }
 299
 2147483647300    internal bool IsOwnedBy(GravitasCollisionService owner) => ReferenceEquals(_owner, owner);
 301
 302    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 303    internal void SetRetainedIndex(int index)
 304    {
 661810305        SwiftThrowHelper.ThrowIfNegative(index, nameof(index));
 661810306        _retainedIndex = index;
 661810307    }
 308
 309    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 405252310    internal void ClearRetainedIndex() => _retainedIndex = -1;
 311
 312    internal void ResetRetainedMembership()
 313    {
 3314        ContainedDynamicObjects?.Clear();
 3315        ContainedAwakeDynamicObjects?.Clear();
 3316        ContainedKinematicObjects?.Clear();
 3317        ContainedStaticObjects?.Clear();
 3318        ActivationId = -1;
 3319        MarkEmpty(_owner?.Context.FrameCount ?? 0);
 3320    }
 321
 322    internal void ResetForPool()
 323    {
 405254324        ContainedDynamicObjects?.Clear();
 405254325        ContainedAwakeDynamicObjects?.Clear();
 405254326        ContainedKinematicObjects?.Clear();
 405254327        ContainedStaticObjects?.Clear();
 328
 405254329        if (ActivationId != -1)
 137016330            Owner.DeactivatePartition(ActivationId);
 331
 405254332        _owner = null;
 405254333        ActivationId = -1;
 405254334        IsPartitioned = false;
 405254335        _emptySinceFrame = -1;
 405254336        _retainedIndex = -1;
 405254337    }
 338
 339    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 974285340    private void MarkOccupied() => _emptySinceFrame = -1;
 341
 342    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 343    private void MarkEmptyIfUnoccupied()
 344    {
 432141345        if (IsEmpty)
 312558346            MarkEmpty(Owner.Context.FrameCount);
 432141347    }
 348
 349    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 312561350    private void MarkEmpty(int frame) => _emptySinceFrame = frame;
 351
 352    /// <summary>
 353    /// Sets the parent index for the current voxel in the world.
 354    /// </summary>
 355    /// <param name="parentIndex">The index to assign as the parent of the current voxel.</param>
 405247356    public void SetParentIndex(WorldVoxelIndex parentIndex) => WorldIndex = parentIndex;
 357
 810499358    int IRetainedPhysicsPartition<GravitasCollisionService>.RetainedIndex => RetainedIndex;
 359
 2147483647360    bool IRetainedPhysicsPartition<GravitasCollisionService>.IsEmpty => IsEmpty;
 361
 9180362    int IRetainedPhysicsPartition<GravitasCollisionService>.EmptySinceFrame => EmptySinceFrame;
 363
 2147483647364    bool IRetainedPhysicsPartition<GravitasCollisionService>.IsOwnedBy(GravitasCollisionService owner) => IsOwnedBy(owne
 365
 661810366    void IRetainedPhysicsPartition<GravitasCollisionService>.SetRetainedIndex(int index) => SetRetainedIndex(index);
 367
 405252368    void IRetainedPhysicsPartition<GravitasCollisionService>.ClearRetainedIndex() => ClearRetainedIndex();
 369}

Methods/Properties

.ctor()
get_IsAllocated()
get_IsEmpty()
get_EmptySinceFrame()
get_RetainedIndex()
get_AwakeDynamicObjectCount()
get_Owner()
OnAddToVoxel(GridForge.Grids.Voxel)
Distribute(SwiftCollections.SwiftList`1<System.Int32>,SwiftCollections.SwiftList`1<System.Int32>)
CopySortedStaticStyleIds(SwiftCollections.SwiftList`1<System.Int32>)
CopyAllColliderIds(SwiftCollections.SwiftList`1<System.Int32>)
CopyStaticStyleColliderIds(SwiftCollections.SwiftList`1<System.Int32>)
CopyIds(SwiftCollections.SwiftSparseSet,SwiftCollections.SwiftList`1<System.Int32>)
ProcessPair(System.Int32,System.Int32)
AddDynamicObject(System.Int32)
AddStaticObject(System.Int32)
AddKinematicObject(System.Int32)
RemoveDynamicObject(System.Int32)
RemoveStaticObject(System.Int32)
RemoveKinematicObject(System.Int32)
ContainsAwakeDynamicObject(System.Int32)
SetDynamicObjectAwake(System.Int32,System.Boolean)
IsDynamicObjectAwake(System.Int32)
OnRemoveFromVoxel(GridForge.Grids.Voxel)
SetOwner(Gravitas.GravitasCollisionService)
IsOwnedBy(Gravitas.GravitasCollisionService)
SetRetainedIndex(System.Int32)
ClearRetainedIndex()
ResetRetainedMembership()
ResetForPool()
MarkOccupied()
MarkEmptyIfUnoccupied()
MarkEmpty(System.Int32)
SetParentIndex(GridForge.Spatial.WorldVoxelIndex)
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_RetainedIndex()
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_IsEmpty()
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.get_EmptySinceFrame()
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.IsOwnedBy(Gravitas.GravitasCollisionService)
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.SetRetainedIndex(System.Int32)
Gravitas.IRetainedPhysicsPartition<Gravitas.GravitasCollisionService>.ClearRetainedIndex()