< Summary

Line coverage
100%
Covered lines: 656
Uncovered lines: 0
Coverable lines: 656
Total lines: 1381
Line coverage: 100%
Branch coverage
100%
Covered branches: 316
Total branches: 316
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: PrepareContinuousCollisionFrame()100%66100%
File 1: AddContinuousCollisionCandidate(...)100%22100%
File 1: QueryContinuousCollisionCandidates(...)100%1212100%
File 1: TryGetContinuousCollisionCandidate(...)100%44100%
File 1: GetContinuousCollisionCandidate(...)100%11100%
File 1: CanAdmitContinuousCollisionCandidateRefresh(...)100%11100%
File 1: CanAdmitContinuousCollisionCandidateRefresh(...)100%11100%
File 1: TryReserveContinuousCollisionCandidateRefresh(...)100%22100%
File 1: ReleaseContinuousCollisionCandidateRefresh(...)100%11100%
File 1: ReleaseContinuousCollisionCandidateRefresh(...)100%22100%
File 1: InvalidateContinuousCollisionStateForMotionTypeChange(...)100%22100%
File 1: TryReserveContinuousCollisionCandidateRefresh(...)100%11100%
File 1: RefreshContinuousCollisionCandidate(...)100%11100%
File 1: QueueContinuousCollisionHandoff(...)100%44100%
File 1: ProcessQueuedContinuousCollisionHandoffs()100%11100%
File 1: ReportContinuousCollisionIterationLimit()100%11100%
File 1: ProcessQueuedContinuousCollisionHandoffs(...)100%1616100%
File 1: BeginContinuousCollisionHandoffFrame()100%11100%
File 1: AbortContinuousCollisionHandoffFrame()100%11100%
File 1: ClearContinuousCollisionHandoffQueue()100%11100%
File 1: DiscardContinuousCollisionHandoffQueue()100%22100%
File 2: .cctor()100%11100%
File 2: .ctor(...)100%11100%
File 2: get_PeakColliderCount()100%11100%
File 2: get_ColliderCount()100%11100%
File 2: Simulate()100%11100%
File 2: PrepareCollisionPartitions()100%44100%
File 2: LateSimulate()100%22100%
File 2: BeginLateSimulateBodies(...)100%88100%
File 2: CompleteLateSimulatePhysicsStep()100%11100%
File 2: RunDiscreteCollisionStep()100%11100%
File 2: UpdateSleepStatesAfterPhysicsStep()100%44100%
File 2: Visualize()100%44100%
File 2: Reset()100%11100%
File 2: AssimilateBody(...)100%44100%
File 2: RefreshBodyMotionTypeRegistration(...)100%66100%
File 2: AssimilateCollider(...)100%11100%
File 2: DessimilateBody(...)100%22100%
File 2: DessimilateCollider(...)100%1212100%
File 2: RefreshColliderServiceRefreshRegistration(...)100%22100%
File 2: TryGetColliderById(...)100%22100%
File 2: TryGetColliderByServiceIndex(...)100%11100%
File 2: GetColliderByServiceIndex(...)100%11100%
File 2: PrepareReplayColliders()100%11100%
File 2: AddServiceRefreshCollider(...)100%11100%
File 2: RemoveServiceRefreshCollider(...)100%66100%
File 3: GetCollisionPair(...)100%1212100%
File 3: RequireCollisionPair(...)100%2222100%
File 3: IsLayerCollisionDisabled(...)100%44100%
File 3: PoolForDeactivation(...)100%11100%
File 3: FullDeactivateCollisionPair(...)100%11100%
File 3: DeactivateAndPoolPair(...)100%66100%
File 3: RemovePairReferences(...)100%11100%
File 3: RemovePairsForCollider(...)100%1818100%
File 3: ClearWarmStartCachesForCollider(...)100%1212100%
File 3: CreatePair(...)100%44100%
File 3: ProcessActiveCollisionPairs()100%2828100%
File 4: ContributeReplayHash(...)100%1616100%
File 4: TryGetReplayHashPair(...)100%22100%
File 5: QueueDiscreteResponsePair(...)100%11100%
File 5: SolveDiscreteResponsePairs()100%2222100%
File 5: BuildDiscreteIslands()100%1414100%
File 5: AddJointIslandNodes()100%44100%
File 5: UnionJointIslandNodes()100%88100%
File 5: AddJointIslandConstraints()100%44100%
File 5: AddIslandNodeIfMovable(...)100%22100%
File 5: SortAndDeduplicateIslandNodes()100%11100%
File 5: FindIslandNode(...)100%22100%
File 5: UnionIslandNodes(...)100%11100%
File 5: CompressIslandRoots()100%11100%
File 5: ResolveConstraintRootKey(...)100%11100%
File 5: WakeIslandBodies(...)100%11100%
File 5: SolveDiscreteIslandRange(...)100%1010100%
File 6: IsMovableIslandBody(...)100%11100%
File 6: HasAwakeResponseParticipant(...)100%22100%
File 6: IsAwakeIslandBody(...)100%22100%
File 6: GetStablePairKey(...)100%22100%
File 6: GetStableJointKey(...)100%22100%
File 6: Compare(...)100%22100%
File 6: Compare(...)100%88100%
File 6: .ctor(...)100%11100%
File 6: get_IsAwakeForCollision()100%11100%
File 6: WakeFromCollision()100%11100%
File 6: .ctor(...)100%11100%
File 6: CreatePair(...)100%11100%
File 6: CreateJoint(...)100%11100%
File 6: get_InactiveFrameThreshold()100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.ContinuousCollision.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.ContinuousCollision.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 FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using SwiftCollections;
 12using SwiftCollections.Query;
 13
 14namespace Gravitas;
 15
 16public sealed partial class GravitasPhysicsService
 17{
 18    internal void PrepareContinuousCollisionFrame()
 19    {
 1854620        int token = _context.LateSimulateToken;
 1854621        if (_continuousCollisionPreparedToken == token)
 1624622            return;
 23
 230024        _continuousCollisionCandidates.Clear();
 230025        _dirtyContinuousCollisionCandidates.Clear();
 230026        _dirtyContinuousCollisionBodyIds.Clear();
 230027        _dirtyContinuousCollisionBodies.FastClear();
 230028        int peak = _dynamicBodies.PeakCount;
 3197229        for (int i = 0; i < peak; i++)
 30        {
 1368731            if (_dynamicBodies.TryGetValue(i, out SolidBody body))
 32            {
 1367633                body.EnsureContinuousCollisionFramePrepared(token);
 1367534                AddContinuousCollisionCandidate(body);
 35            }
 36        }
 37
 229938        _continuousCollisionCandidates.Sort();
 229939        _continuousCollisionPreparedToken = token;
 229940    }
 41
 42    private void AddContinuousCollisionCandidate(SolidBody body)
 43    {
 1367544        Fixed64 radius = body.ResolveContinuousCollisionProxyRadius();
 1367545        if (radius <= Fixed64.Epsilon)
 346            return;
 47
 1367248        _continuousCollisionCandidateLifetimes[body.DynamicId] =
 1367249            new ColliderLifetimeToken(body.Collider);
 1367250        _continuousCollisionCandidates.Add(
 1367251            body.DynamicId,
 1367252            DynamicCcdCandidateIndex.CreateBoundsBetween(
 1367253                body.ContinuousCollisionFrameStart,
 1367254                body.ContinuousCollisionFrameEnd,
 1367255                Vector3d.One * radius));
 1367256    }
 57
 58    /// <summary>
 59    /// Runs this context's visual interpolation step for dynamic bodies.
 60    /// </summary>
 61
 62    internal SwiftList<int> QueryContinuousCollisionCandidates(FixedBoundVolume sourceBounds)
 63    {
 1407764        PrepareContinuousCollisionFrame();
 1407765        _continuousCollisionCandidates.Query(sourceBounds, _continuousCollisionCandidateIds);
 1407766        _dirtyContinuousCollisionCandidates.Query(sourceBounds, _dirtyContinuousCollisionCandidateIds);
 1407767        int retainedCount = 0;
 5743468        for (int i = 0; i < _continuousCollisionCandidateIds.Count; i++)
 69        {
 1464070            int dynamicId = _continuousCollisionCandidateIds[i];
 1464071            if (!_dirtyContinuousCollisionBodyIds.Contains(dynamicId)
 1464072                && TryGetContinuousCollisionCandidate(dynamicId, out _))
 73            {
 1383974                _continuousCollisionCandidateIds[retainedCount++] = dynamicId;
 75            }
 76        }
 77
 1487878        while (_continuousCollisionCandidateIds.Count > retainedCount)
 80179            _continuousCollisionCandidateIds.RemoveAt(
 80180                _continuousCollisionCandidateIds.Count - 1);
 81
 2972682        for (int i = 0; i < _dirtyContinuousCollisionCandidateIds.Count; i++)
 83        {
 78684            int dynamicId = _dirtyContinuousCollisionCandidateIds[i];
 78685            if (TryGetContinuousCollisionCandidate(dynamicId, out _))
 78486                _continuousCollisionCandidateIds.Add(dynamicId);
 87        }
 88
 1407789        return _continuousCollisionCandidateIds;
 90    }
 91
 92    internal bool TryGetContinuousCollisionCandidate(int dynamicId, out SolidBody body)
 93    {
 1463394        ColliderLifetimeToken registration = _continuousCollisionCandidateLifetimes[dynamicId];
 1463395        if (registration.Collider != null && registration.IsActive)
 96        {
 1462397            body = registration.Collider.Body!;
 1462398            return true;
 99        }
 100
 10101        body = null!;
 10102        return false;
 103    }
 104
 105    internal SolidBody GetContinuousCollisionCandidate(int dynamicId) =>
 14401106        _dynamicBodies[dynamicId];
 107
 1108    internal bool CanAdmitContinuousCollisionCandidateRefresh(SolidBody body) => true;
 109
 110    internal bool CanAdmitContinuousCollisionCandidateRefresh(
 111        SolidBody first,
 1112        SolidBody second) => true;
 113
 114    internal bool TryReserveContinuousCollisionCandidateRefresh(SolidBody body)
 115    {
 386116        if (_dirtyContinuousCollisionBodyIds.Add(body.DynamicId))
 339117            _dirtyContinuousCollisionBodies.Add(body);
 118
 386119        return true;
 120    }
 121
 122    private void ReleaseContinuousCollisionCandidateRefresh(SolidBody body) =>
 87123        ReleaseContinuousCollisionCandidateRefresh(body, body.DynamicId);
 124
 125    private void ReleaseContinuousCollisionCandidateRefresh(SolidBody body, int dynamicId)
 126    {
 151127        if (!_dirtyContinuousCollisionBodyIds.Remove(dynamicId))
 148128            return;
 129
 3130        _dirtyContinuousCollisionBodies.Remove(body);
 3131        _dirtyContinuousCollisionCandidates.Remove(dynamicId);
 3132    }
 133
 134    internal void InvalidateContinuousCollisionStateForMotionTypeChange(
 135        SolidBody body,
 136        int dynamicId)
 137    {
 64138        if (dynamicId >= 0)
 49139            _continuousCollisionCandidateLifetimes[dynamicId] = default;
 140
 64141        ReleaseContinuousCollisionCandidateRefresh(body, dynamicId);
 64142        _processedContinuousCollisionBodies.Remove(body);
 64143        _queuedContinuousCollisionHandoffBodies.Remove(body);
 64144        _continuousCollisionHandoffQueue.Remove(body);
 64145        body.DiscardContinuousCollisionHandoff();
 64146        _continuousCollisionPreparedToken = int.MinValue;
 64147    }
 148
 149    internal bool TryReserveContinuousCollisionCandidateRefresh(
 150        SolidBody first,
 151        SolidBody second)
 152    {
 55153        TryReserveContinuousCollisionCandidateRefresh(first);
 55154        TryReserveContinuousCollisionCandidateRefresh(second);
 55155        return true;
 156    }
 157
 158    internal void RefreshContinuousCollisionCandidate(SolidBody body)
 159    {
 584160        Fixed64 radius = body.ResolveContinuousCollisionProxyRadius();
 584161        _dirtyContinuousCollisionCandidates.AddOrUpdate(
 584162            body.DynamicId,
 584163            body.ResolveContinuousCollisionTrajectoryBounds(radius));
 584164    }
 165
 166    internal void QueueContinuousCollisionHandoff(SolidBody body)
 167    {
 211168        if (!_processedContinuousCollisionBodies.Contains(body)
 211169            || !_queuedContinuousCollisionHandoffBodies.Add(body))
 170        {
 110171            return;
 172        }
 173
 101174        _continuousCollisionHandoffQueue.Add(body);
 101175    }
 176
 177    internal bool ProcessQueuedContinuousCollisionHandoffs() =>
 2178        ProcessQueuedContinuousCollisionHandoffs(_context.Settings.ContinuousCollisionMaxToiIterations) > 0;
 179
 180    internal void ReportContinuousCollisionIterationLimit() =>
 6181        LastContinuousCollisionIslandLimitReached = true;
 182
 183    internal int ProcessQueuedContinuousCollisionHandoffs(int iterationBudget)
 184    {
 2258185        if (_continuousCollisionHandoffQueue.Count == 0)
 2181186            return 0;
 187
 77188        if (iterationBudget <= 0)
 189        {
 1190            LastContinuousCollisionIslandLimitReached = true;
 1191            DiscardContinuousCollisionHandoffQueue();
 1192            return 0;
 193        }
 194
 76195        int readIndex = 0;
 76196        int iterations = 0;
 76197        bool processed = false;
 76198        bool completed = false;
 76199        bool limitReached = false;
 200        try
 201        {
 162202            while (readIndex < _continuousCollisionHandoffQueue.Count && iterations < iterationBudget)
 203            {
 88204                SolidBody body = _continuousCollisionHandoffQueue[readIndex++];
 205                // Consumption can synchronously return work to this body; dequeue ends dedupe ownership first.
 88206                _queuedContinuousCollisionHandoffBodies.Remove(body);
 207
 88208                if (body.TryConsumeQueuedContinuousCollisionHandoff(
 88209                        updateSleepState: false,
 88210                        updateColliderState: false,
 88211                        out bool shouldNotifyMovement))
 212                {
 83213                    processed = true;
 83214                    iterations++;
 83215                    if (shouldNotifyMovement)
 80216                        body.NotifyAuthoritativeMovement();
 217                }
 218            }
 219
 74220            limitReached = readIndex < _continuousCollisionHandoffQueue.Count;
 74221            completed = true;
 74222            return iterations;
 223        }
 224        finally
 225        {
 76226            if (processed)
 72227                LastContinuousCollisionIslandCount++;
 228
 76229            LastContinuousCollisionIslandIterationCount += iterations;
 76230            LastContinuousCollisionIslandLimitReached |= completed && limitReached;
 76231            if (!completed || limitReached)
 4232                DiscardContinuousCollisionHandoffQueue();
 233            else
 72234                ClearContinuousCollisionHandoffQueue();
 76235        }
 74236    }
 237
 238    private void BeginContinuousCollisionHandoffFrame()
 239    {
 2197240        _processedContinuousCollisionBodies.Clear();
 2197241        DiscardContinuousCollisionHandoffQueue();
 2197242        LastContinuousCollisionIslandCount = 0;
 2197243        LastContinuousCollisionIslandIterationCount = 0;
 2197244        LastContinuousCollisionIslandLimitReached = false;
 2197245    }
 246
 247    internal void AbortContinuousCollisionHandoffFrame()
 248    {
 1249        _processedContinuousCollisionBodies.Clear();
 1250        DiscardContinuousCollisionHandoffQueue();
 1251    }
 252
 253    private void ClearContinuousCollisionHandoffQueue()
 254    {
 2309255        _queuedContinuousCollisionHandoffBodies.Clear();
 2309256        _continuousCollisionHandoffQueue.FastClear();
 2309257    }
 258
 259    private void DiscardContinuousCollisionHandoffQueue()
 260    {
 4502261        for (int i = 0; i < _continuousCollisionHandoffQueue.Count; i++)
 14262            _continuousCollisionHandoffQueue[i].DiscardContinuousCollisionHandoff();
 263
 2237264        ClearContinuousCollisionHandoffQueue();
 2237265    }
 266
 267}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.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.Colliders;
 9using Gravitas.CollisionHandling;
 10using SwiftCollections;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas;
 14
 15/// <summary>
 16/// Owns physics registration and collision-pair state for one <see cref="GravitasWorldContext"/>.
 17/// </summary>
 18public sealed partial class GravitasPhysicsService
 19{
 20    private const int DefaultColliderSize = 2048;
 21    private const int DefaultBodySize = DefaultColliderSize / 4;
 22    private const int DefaultColliderIdSize = DefaultColliderSize / 8;
 123    private static readonly CollisionPairStableKeyComparer ResponsePairComparer = new();
 124    private static readonly IslandNodeKeyComparer<DiscreteIslandNode> IslandNodeComparer = new();
 125    private static readonly DiscreteIslandConstraintComparer IslandConstraintComparer = new();
 26
 27    private readonly GravitasWorldContext _context;
 28
 508329    private SwiftBucket<SolidBody> _dynamicBodies = new(DefaultBodySize);
 508330    private readonly ColliderRegistry<LSCollider> _colliders = new(DefaultColliderSize);
 508331    private SwiftList<LSCollider> _serviceRefreshColliders = new(DefaultColliderIdSize);
 508332    private SwiftStack<CollisionPair> _cachedCollisionPairs = new();
 508333    private SwiftQueue<CollisionPairLifetimeToken> _activeCollisionPairs = new();
 508334    private readonly SwiftList<CollisionPairLifetimeToken> _activeCollisionPairSnapshot = new();
 508335    private readonly SwiftList<CollisionPairLifetimeToken> _pairsPendingDeactivation = new();
 508336    private readonly SwiftList<CollisionPair> _discreteResponsePairs = new();
 508337    private readonly SwiftList<DiscreteIslandNode> _discreteIslandNodes = new();
 508338    private readonly SwiftList<DiscreteIslandConstraint> _discreteIslandConstraints = new();
 508339    private readonly DynamicCcdCandidateIndex _continuousCollisionCandidates = new(DefaultBodySize);
 508340    private readonly DynamicCcdCandidateIndex _dirtyContinuousCollisionCandidates =
 508341        new(DefaultBodySize, supportsUpdates: true);
 508342    private readonly SwiftList<ColliderLifetimeToken> _continuousCollisionCandidateLifetimes =
 508343        new(DefaultBodySize);
 508344    private readonly SwiftList<int> _continuousCollisionCandidateIds = new(DefaultBodySize);
 508345    private readonly SwiftList<int> _dirtyContinuousCollisionCandidateIds = new(DefaultBodySize);
 508346    private readonly SwiftHashSet<int> _dirtyContinuousCollisionBodyIds = new(DefaultBodySize);
 508347    private readonly SwiftList<SolidBody> _dirtyContinuousCollisionBodies = new(DefaultBodySize);
 508348    private readonly SwiftHashSet<SolidBody> _processedContinuousCollisionBodies = new(DefaultBodySize);
 508349    private readonly SwiftHashSet<SolidBody> _queuedContinuousCollisionHandoffBodies = new(DefaultBodySize);
 508350    private readonly SwiftList<SolidBody> _continuousCollisionHandoffQueue = new(DefaultBodySize);
 508351    private int _continuousCollisionPreparedToken = int.MinValue;
 52
 53    /// <summary>
 54    /// Initializes a new physics service for the supplied context.
 55    /// </summary>
 56    /// <param name="context">The owning world context.</param>
 508357    public GravitasPhysicsService(GravitasWorldContext context)
 58    {
 508359        SwiftThrowHelper.ThrowIfNull(context, nameof(context));
 508360        _context = context;
 508361    }
 62
 63    /// <summary>
 64    /// Gets or sets whether this context should simulate physics.
 65    /// </summary>
 66    public bool SimulatePhysics { get; set; } = true;
 67
 68    /// <summary>
 69    /// Gets the highest collider ID allocated in this context.
 70    /// </summary>
 31071    public int PeakColliderCount => _colliders.PeakCount;
 72
 73    /// <summary>
 74    /// Gets the number of dynamic bodies currently registered in this context.
 75    /// </summary>
 76    public int BodyCount { get; private set; }
 77
 78    /// <summary>
 79    /// Gets the number of colliders currently registered in this context.
 80    /// </summary>
 193681    public int ColliderCount => _colliders.Count;
 82
 83    /// <summary>
 84    /// Gets how many service-owned continuous-collision handoff batches were consumed during the last late step.
 85    /// </summary>
 86    public int LastContinuousCollisionIslandCount { get; private set; }
 87
 88    /// <summary>
 89    /// Gets how many service-owned continuous-collision handoff iterations were consumed during the last late step.
 90    /// </summary>
 91    public int LastContinuousCollisionIslandIterationCount { get; private set; }
 92
 93    /// <summary>
 94    /// Gets whether the service-owned continuous-collision handoff queue hit its deterministic iteration cap.
 95    /// </summary>
 96    public bool LastContinuousCollisionIslandLimitReached { get; private set; }
 97
 98    /// <summary>
 99    /// Runs this context's physics simulation step.
 100    /// </summary>
 101    public void Simulate()
 102    {
 2001103        _context.EnterSimulationPhase();
 104        try
 105        {
 2001106            if (!SimulatePhysics)
 107                return;
 2001108        }
 109        finally
 110        {
 2001111            _context.ExitSimulationPhase();
 2001112        }
 2001113    }
 114
 115    private void PrepareCollisionPartitions()
 116    {
 31366117        foreach (SolidBody body in _dynamicBodies)
 13502118            body.Collider.Simulate();
 119
 18918120        for (int i = 0; i < _serviceRefreshColliders.Count; i++)
 7278121            _serviceRefreshColliders[i].Simulate();
 2181122    }
 123
 124    /// <summary>
 125    /// Runs this context's late physics step.
 126    /// </summary>
 127    public void LateSimulate()
 128    {
 3129        _context.EnterSimulationPhase();
 130        try
 131        {
 3132            if (!BeginLateSimulateBodies(continuousCollisionFramePrepared: false))
 1133                return;
 134
 2135            ProcessQueuedContinuousCollisionHandoffs();
 2136            CompleteLateSimulatePhysicsStep();
 2137        }
 138        finally
 139        {
 3140            _context.ExitSimulationPhase();
 3141        }
 3142    }
 143
 144    internal bool BeginLateSimulateBodies(bool continuousCollisionFramePrepared)
 145    {
 2198146        if (!SimulatePhysics)
 1147            return false;
 148
 2197149        if (!continuousCollisionFramePrepared)
 17150            _context.AdvanceLateSimulateToken();
 151
 2197152        PrepareContinuousCollisionFrame();
 2197153        BeginContinuousCollisionHandoffFrame();
 154
 2197155        int peak = _dynamicBodies.PeakCount;
 31480156        for (int i = 0; i < peak; i++)
 157        {
 13544158            if (_dynamicBodies.TryGetValue(i, out SolidBody body))
 159            {
 13530160                body.LateSimulate(updateSleepState: false, updateColliderState: false);
 13529161                _processedContinuousCollisionBodies.Add(body);
 162            }
 163        }
 164
 2196165        return true;
 166    }
 167
 168    internal void CompleteLateSimulatePhysicsStep()
 169    {
 2181170        PrepareCollisionPartitions();
 2181171        RunDiscreteCollisionStep();
 2181172        ProcessActiveCollisionPairs();
 2179173        UpdateSleepStatesAfterPhysicsStep();
 2179174    }
 175
 176    private void RunDiscreteCollisionStep()
 177    {
 2181178        _discreteResponsePairs.FastClear();
 2181179        _context.Collisions.CheckAndDistributeCollisions();
 2181180        SolveDiscreteResponsePairs();
 2181181        _context.Collisions.RetireExpiredRetainedPartitions();
 2181182    }
 183
 184    private void UpdateSleepStatesAfterPhysicsStep()
 185    {
 2179186        int peak = _dynamicBodies.PeakCount;
 31394187        for (int i = 0; i < peak; i++)
 188        {
 13518189            if (_dynamicBodies.TryGetValue(i, out SolidBody body))
 13501190                body.UpdateSleepStateAfterPhysicsStep();
 191        }
 2179192    }
 193
 194    /// <summary>Publishes interpolated presentation state for registered 3D bodies.</summary>
 195    public void Visualize()
 196    {
 23197        int peak = _dynamicBodies.PeakCount;
 110198        for (int i = 0; i < peak; i++)
 199        {
 32200            if (_dynamicBodies.TryGetValue(i, out SolidBody body))
 31201                body.OnVisualize();
 202        }
 23203    }
 204
 205    /// <summary>
 206    /// Clears all context-local physics registration state.
 207    /// </summary>
 208    public void Reset()
 209    {
 34210        DiscardContinuousCollisionHandoffQueue();
 34211        _dynamicBodies.Clear();
 34212        _colliders.Clear();
 34213        _serviceRefreshColliders.FastClear();
 34214        _cachedCollisionPairs.FastClear();
 34215        _activeCollisionPairs.FastClear();
 34216        _activeCollisionPairSnapshot.FastClear();
 34217        _pairsPendingDeactivation.FastClear();
 34218        _discreteResponsePairs.FastClear();
 34219        _discreteIslandNodes.FastClear();
 34220        _discreteIslandConstraints.FastClear();
 34221        _continuousCollisionCandidates.Clear();
 34222        _dirtyContinuousCollisionCandidates.Clear();
 34223        _continuousCollisionCandidateLifetimes.Clear();
 34224        _continuousCollisionCandidateIds.FastClear();
 34225        _dirtyContinuousCollisionCandidateIds.FastClear();
 34226        _dirtyContinuousCollisionBodyIds.Clear();
 34227        _dirtyContinuousCollisionBodies.FastClear();
 34228        _processedContinuousCollisionBodies.Clear();
 34229        _continuousCollisionPreparedToken = int.MinValue;
 34230        LastContinuousCollisionIslandCount = 0;
 34231        LastContinuousCollisionIslandIterationCount = 0;
 34232        LastContinuousCollisionIslandLimitReached = false;
 233
 34234        BodyCount = 0;
 34235    }
 236
 237    internal int AssimilateBody(SolidBody body, BodyMotionType motionType)
 238    {
 5082239        SwiftThrowHelper.ThrowIfNull(body, nameof(body));
 5082240        SwiftThrowHelper.ThrowIfArgument(
 5082241            !ReferenceEquals(body.Context, _context),
 5082242            nameof(body),
 5082243            "Body must belong to this physics service context.");
 244
 5082245        int dynamicId = -1;
 5082246        if (motionType != BodyMotionType.Static)
 247        {
 4523248            dynamicId = _dynamicBodies.Add(body);
 9024249            while (_continuousCollisionCandidateLifetimes.Count <= dynamicId)
 4501250                _continuousCollisionCandidateLifetimes.Add(default);
 4523251            BodyCount++;
 252        }
 253
 5082254        return dynamicId;
 255    }
 256
 257    internal void RefreshBodyMotionTypeRegistration(
 258        SolidBody body,
 259        BodyMotionType previousMotionType)
 260    {
 64261        if (previousMotionType == BodyMotionType.Static)
 262        {
 15263            int dynamicId = _dynamicBodies.Add(body);
 24264            while (_continuousCollisionCandidateLifetimes.Count <= dynamicId)
 9265                _continuousCollisionCandidateLifetimes.Add(default);
 15266            body.SetDynamicId(dynamicId);
 15267            BodyCount++;
 15268            return;
 269        }
 270
 49271        if (body.MotionType == BodyMotionType.Static)
 272        {
 14273            int previousDynamicId = body.DynamicId;
 14274            _dynamicBodies.RemoveAt(previousDynamicId);
 14275            _continuousCollisionCandidateLifetimes[previousDynamicId] = default;
 14276            ReleaseContinuousCollisionCandidateRefresh(body);
 14277            body.SetDynamicId(-1);
 14278            BodyCount--;
 279        }
 49280    }
 281
 282    internal int AssimilateCollider(LSCollider collider)
 283    {
 5685284        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 5685285        collider.BindContext(_context);
 286
 5685287        int id = _colliders.Register(collider);
 5685288        RefreshColliderServiceRefreshRegistration(collider);
 5685289        return id;
 290    }
 291
 292    internal void DessimilateBody(SolidBody body)
 293    {
 91294        SwiftThrowHelper.ThrowIfNull(body, nameof(body));
 91295        int dynamicId = body.DynamicId;
 91296        if (!_dynamicBodies.TryRemoveAt(dynamicId))
 18297            return;
 298
 73299        _continuousCollisionCandidateLifetimes[dynamicId] = default;
 73300        ReleaseContinuousCollisionCandidateRefresh(body);
 73301        BodyCount--;
 73302    }
 303
 304    internal void DessimilateCollider(LSCollider collider)
 305    {
 224306        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 224307        int id = collider.Id;
 308
 224309        if (!_colliders.TryGetById(id, out LSCollider? registered) || !ReferenceEquals(registered, collider))
 310        {
 4311            GravitasLogger.Channel.Warn($"Object with ID {collider.Id} cannot be dessimilated because it is not assimila
 4312            return;
 313        }
 314
 220315        if (collider.Body != null)
 92316            _context.Constraints3D.RemoveJointsForBody(collider.Body);
 220317        RemovePairsForCollider(collider);
 219318        if (collider.IsPartitioned)
 215319            _context.Collisions.ClearPartitionedObject(collider, true);
 320
 219321        _context.MixedCollisions.RemovePairsFor3DCollider(collider);
 219322        if (collider.IsMixedPartitioned)
 18323            _context.MixedCollisions.ClearPartitioned3DCollider(collider, force: true);
 324
 219325        RemoveServiceRefreshCollider(collider);
 219326        _colliders.Remove(collider);
 219327    }
 328
 329    internal void RefreshColliderServiceRefreshRegistration(LSCollider collider)
 330    {
 5749331        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 332
 5749333        if (collider.RequiresServiceSideRefresh)
 1175334            AddServiceRefreshCollider(collider);
 335        else
 4574336            RemoveServiceRefreshCollider(collider);
 4574337    }
 338
 339    /// <summary>
 340    /// Resolves a context-local collider ID.
 341    /// </summary>
 342    /// <param name="id">The context-local collider ID.</param>
 343    /// <param name="collider">The resolved collider, if present.</param>
 344    /// <returns>True when the ID resolves in this context.</returns>
 345    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 346    public bool TryGetColliderById(int id, out LSCollider? collider)
 347    {
 1241703348        if (id < 0)
 349        {
 14350            collider = null;
 14351            return false;
 352        }
 353
 1241689354        return _colliders.TryGetById(id, out collider);
 355    }
 356
 357    internal bool TryGetColliderByServiceIndex(int serviceIndex, out LSCollider? collider) =>
 8358        _colliders.TryGetByServiceIndex(serviceIndex, out collider);
 359
 1401360    internal LSCollider GetColliderByServiceIndex(int serviceIndex) => _colliders[serviceIndex];
 361
 1629362    internal SwiftList<LSCollider> PrepareReplayColliders() => _colliders.PrepareReplayColliders();
 363
 364    private void AddServiceRefreshCollider(LSCollider collider)
 365    {
 1175366        collider.SetServiceRefreshIndex(_serviceRefreshColliders.Count);
 1175367        _serviceRefreshColliders.Add(collider);
 1175368    }
 369
 370    private void RemoveServiceRefreshCollider(LSCollider collider)
 371    {
 4793372        int index = collider.ServiceRefreshIndex;
 4793373        if (index < 0 || !ReferenceEquals(_serviceRefreshColliders[index], collider))
 374        {
 4633375            collider.ClearServiceRefreshIndex();
 4633376            return;
 377        }
 378
 160379        int lastIndex = _serviceRefreshColliders.Count - 1;
 160380        if (index != lastIndex)
 381        {
 6382            LSCollider moved = _serviceRefreshColliders[lastIndex];
 6383            _serviceRefreshColliders[index] = moved;
 6384            moved.SetServiceRefreshIndex(index);
 385        }
 386
 160387        _serviceRefreshColliders.RemoveAt(lastIndex);
 160388        collider.ClearServiceRefreshIndex();
 160389    }
 390
 391}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.Pairs.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.Pairs.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.Colliders;
 9using Gravitas.CollisionHandling;
 10using Gravitas.Support;
 11using SwiftCollections;
 12
 13namespace Gravitas;
 14
 15internal readonly struct CollisionPairLifetimeToken
 16{
 17    internal CollisionPairLifetimeToken(CollisionPair pair)
 18    {
 19        Pair = pair;
 20        LifetimeVersion = pair.LifetimeVersion;
 21        ColliderA = new ColliderLifetimeToken(pair.ColliderA);
 22        ColliderB = new ColliderLifetimeToken(pair.ColliderB);
 23    }
 24
 25    internal CollisionPair Pair { get; }
 26
 27    internal long LifetimeVersion { get; }
 28
 29    private ColliderLifetimeToken ColliderA { get; }
 30
 31    private ColliderLifetimeToken ColliderB { get; }
 32
 33    internal bool IsCurrentLifetime => Pair.LifetimeVersion == LifetimeVersion
 34        && ColliderA.IsCurrentLifetime
 35        && ColliderB.IsCurrentLifetime;
 36}
 37
 38public sealed partial class GravitasPhysicsService
 39{
 40    internal CollisionPair? GetCollisionPair(int id1, int id2)
 41    {
 13634642        if (!TryGetColliderById(id1, out LSCollider? collider1)
 13634643            || !TryGetColliderById(id2, out LSCollider? collider2))
 44        {
 245            return null;
 46        }
 47
 13634448        if (collider1!.Id > collider2!.Id)
 4854749            (collider2, collider1) = (collider1, collider2);
 50
 13634451        if (!RequireCollisionPair(collider1, collider2))
 5130752            return null;
 53
 8503754        if (!collider1.TryGetCollisionPair(collider2.Id, out CollisionPair? pair)
 8503755            && !collider2.TryGetCollisionPair(collider1.Id, out pair))
 56        {
 25857            pair = CreatePair(collider1, collider2);
 25858            pair.ColliderA.TryAddCollisionPair(pair.ColliderB.Id, pair);
 25859            pair.ColliderB.TryAddCollisionPairHolder(pair.ColliderA.Id);
 60        }
 61
 8503762        return pair;
 63    }
 64
 65    internal bool RequireCollisionPair(LSCollider collider1, LSCollider collider2)
 66    {
 15422567        return collider1.IsActive && collider2.IsActive
 15422568            && !collider1.IsDeactivationInProgress && !collider2.IsDeactivationInProgress
 15422569            && collider1.Shape != ColliderType.None && collider2.Shape != ColliderType.None
 15422570            && (collider1.Body != null || collider2.Body != null)
 15422571            && !IsLayerCollisionDisabled(collider1.Layer, collider2.Layer)
 15422572            && ColliderCollisionFilter.AllowsPhysicalPair(collider1, collider2)
 15422573            && !_context.Constraints3D.ShouldExcludeLinkedCollision(collider1, collider2)
 15422574            && !collider1.IsSibling(collider2);
 75    }
 76
 77    internal bool IsLayerCollisionDisabled(PhysicsLayer layer1, PhysicsLayer layer2)
 78    {
 15927379        bool[,] matrix = _context.Settings.CollisionMatrix;
 15927380        int layerIndex1 = layer1.Index;
 15927381        int layerIndex2 = layer2.Index;
 15927382        if (layerIndex1 >= matrix.GetLength(0) || layerIndex2 >= matrix.GetLength(1))
 1991883            return false;
 84
 13935585        return !matrix[layerIndex1, layerIndex2];
 86    }
 87
 88    internal void PoolForDeactivation(CollisionPair pair)
 89    {
 27390        SwiftThrowHelper.ThrowIfNull(pair, nameof(pair));
 27391        lock (_activeCollisionPairs)
 27392            _activeCollisionPairs.Enqueue(new CollisionPairLifetimeToken(pair));
 27393    }
 94
 95    internal void FullDeactivateCollisionPair(CollisionPair pair)
 96    {
 3897        RemovePairReferences(pair);
 3898        DeactivateAndPoolPair(pair);
 3799    }
 100
 101    internal void DeactivateAndPoolPair(CollisionPair pair)
 102    {
 38103        if (!pair.Active)
 2104            return;
 105
 36106        pair.Deactivate();
 35107        if (_context.Settings.PoolingEnabled && !pair.IsNotificationInProgress)
 23108            _cachedCollisionPairs.Push(pair);
 35109    }
 110
 111    internal void RemovePairReferences(CollisionPair pair)
 112    {
 38113        pair.ColliderA.TryRemoveCollisionPair(pair.Id2);
 38114        pair.ColliderB.TryRemoveCollisionPairHolder(pair.Id1);
 38115    }
 116
 117    internal void RemovePairsForCollider(LSCollider collider)
 118    {
 220119        int deactivationStart = _pairsPendingDeactivation.Count;
 220120        SwiftDictionary<int, CollisionPair>? collisionPairs = collider.CollisionPairs;
 220121        if (collisionPairs != null)
 122        {
 50123            foreach (var pairEntry in collisionPairs)
 14124                _pairsPendingDeactivation.Add(new CollisionPairLifetimeToken(pairEntry.Value));
 125        }
 126
 220127        SwiftHashSet<int>? collisionPairHolders = collider.CollisionPairHolders;
 220128        if (collisionPairHolders != null)
 129        {
 70130            foreach (int holderId in collisionPairHolders)
 131            {
 18132                if (!TryGetColliderById(holderId, out LSCollider? holder))
 133                    continue;
 134
 18135                if (holder!.TryGetCollisionPair(collider.Id, out CollisionPair? pair))
 18136                    _pairsPendingDeactivation.Add(new CollisionPairLifetimeToken(pair!));
 137            }
 138        }
 139
 220140        int deactivationEnd = _pairsPendingDeactivation.Count;
 141        try
 142        {
 500143            for (int i = deactivationStart; i < deactivationEnd; i++)
 144            {
 31145                CollisionPairLifetimeToken token = _pairsPendingDeactivation[i];
 31146                if (token.IsCurrentLifetime)
 30147                    FullDeactivateCollisionPair(token.Pair);
 148            }
 149
 219150            collider.ClearCollisionPairState();
 219151            collider.ClearRuntimeRelationships();
 219152        }
 153        finally
 154        {
 220155            if (deactivationStart == 0)
 217156                _pairsPendingDeactivation.FastClear();
 220157        }
 219158    }
 159
 160    internal void ClearWarmStartCachesForCollider(LSCollider collider)
 161    {
 64162        SwiftDictionary<int, CollisionPair>? collisionPairs = collider.CollisionPairs;
 64163        if (collisionPairs != null)
 164        {
 4165            foreach (var pairEntry in collisionPairs)
 1166                pairEntry.Value.ClearWarmStart();
 167        }
 168
 64169        SwiftHashSet<int>? collisionPairHolders = collider.CollisionPairHolders;
 64170        if (collisionPairHolders == null)
 63171            return;
 172
 4173        foreach (int holderId in collisionPairHolders)
 174        {
 1175            if (TryGetColliderById(holderId, out LSCollider? holder)
 1176                && holder!.TryGetCollisionPair(collider.Id, out CollisionPair? pair))
 177            {
 1178                pair!.ClearWarmStart();
 179            }
 180        }
 1181    }
 182
 183    private CollisionPair CreatePair(LSCollider collider1, LSCollider collider2)
 184    {
 258185        if (!_context.Settings.PoolingEnabled || _cachedCollisionPairs.Count <= 0)
 257186            return new CollisionPair(collider1, collider2);
 187
 1188        CollisionPair pair = _cachedCollisionPairs.Pop();
 1189        pair.Initialize(collider1, collider2);
 1190        return pair;
 191    }
 192
 193    private void ProcessActiveCollisionPairs()
 194    {
 2181195        int snapshotStart = _activeCollisionPairSnapshot.Count;
 2181196        int collisionCounter = _activeCollisionPairs.Count;
 13832197        for (int i = 0; i < collisionCounter; i++)
 4735198            _activeCollisionPairSnapshot.Add(_activeCollisionPairs.Dequeue());
 199
 2181200        int snapshotEnd = _activeCollisionPairSnapshot.Count;
 2181201        int processingIndex = snapshotStart;
 202        try
 203        {
 11645204            for (; processingIndex < snapshotEnd; processingIndex++)
 205            {
 4734206                CollisionPairLifetimeToken token = _activeCollisionPairSnapshot[processingIndex];
 4734207                CollisionPair instancePair = token.Pair;
 4734208                if (!token.IsCurrentLifetime || !instancePair.Active)
 209                    continue;
 210
 4730211                if (!RequireCollisionPair(instancePair.ColliderA, instancePair.ColliderB))
 212                {
 2213                    FullDeactivateCollisionPair(instancePair);
 2214                    continue;
 215                }
 216
 4728217                bool preservedSleepingContact = instancePair.TryPreserveSleepingRestingContact();
 4728218                int passedFrames = _context.FrameCount - instancePair.LastCollidedFrame;
 4728219                if (!preservedSleepingContact && passedFrames >= InactiveFrameThreshold)
 1220                    FullDeactivateCollisionPair(instancePair);
 221                else
 222                {
 4727223                    if (instancePair.CullCounter <= 0)
 3828224                        instancePair.NotifyCollidersOfContact();
 4725225                    if (token.IsCurrentLifetime && instancePair.Active)
 4716226                        _activeCollisionPairs.Enqueue(token);
 227                }
 228            }
 2179229        }
 230        finally
 231        {
 2187232            for (; processingIndex < snapshotEnd; processingIndex++)
 233            {
 3234                CollisionPairLifetimeToken token = _activeCollisionPairSnapshot[processingIndex];
 3235                if (token.IsCurrentLifetime && token.Pair.Active)
 3236                    _activeCollisionPairs.Enqueue(token);
 237            }
 238
 2181239            if (snapshotStart == 0)
 2179240                _activeCollisionPairSnapshot.FastClear();
 2181241        }
 2179242    }
 243
 244}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.ReplayHash.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 Chronicler;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using SwiftCollections;
 12
 13namespace Gravitas;
 14
 15public sealed partial class GravitasPhysicsService
 16{
 17    internal void ContributeReplayHash(
 18        ref ChronicleHashWriter writer,
 19        GravitasReplayHashMode mode)
 20    {
 72421        SwiftList<LSCollider> replayColliders = PrepareReplayColliders();
 22
 72423        writer.WriteSection("physics.3d", 2);
 72424        writer.WriteBool(SimulatePhysics);
 72425        writer.WriteInt32(BodyCount);
 72426        writer.WriteInt32(ColliderCount);
 72427        writer.WriteInt32(_dynamicBodies.PeakCount);
 72428        writer.WriteInt32(replayColliders.Count);
 29
 592630        for (int i = 0; i < replayColliders.Count; i++)
 31        {
 223932            LSCollider collider = replayColliders[i];
 223933            collider.ContributeReplayHash(ref writer, mode);
 223934            if (collider.Body == null)
 20635                writer.WriteBool(false);
 36            else
 37            {
 203338                writer.WriteBool(true);
 203339                collider.Body.ContributeReplayHash(ref writer, mode);
 40            }
 41        }
 42
 72443        writer.WriteSection("physics.3d.pairs", 2);
 592644        for (int i = 0; i < replayColliders.Count; i++)
 45        {
 223946            LSCollider collider = replayColliders[i];
 47
 1921648            for (int j = i + 1; j < replayColliders.Count; j++)
 49            {
 736950                LSCollider other = replayColliders[j];
 736951                if (!TryGetReplayHashPair(collider, other, out CollisionPair? pair))
 52                {
 53                    continue;
 54                }
 55
 3556                pair!.ContributeReplayHash(ref writer, mode);
 57            }
 58        }
 59
 72460        if (mode != GravitasReplayHashMode.AuthoritativeWithSolverCaches)
 41561            return;
 62
 30963        writer.WriteSection("physics.3d.caches", 4);
 30964        writer.WriteInt32(PeakColliderCount);
 30965        writer.WriteInt32(_cachedCollisionPairs.Count);
 30966        writer.WriteInt32(_activeCollisionPairs.Count);
 30967        writer.WriteInt32(_continuousCollisionPreparedToken);
 30968        writer.WriteInt32(_continuousCollisionCandidates.Count);
 30969        writer.WriteInt32(_dirtyContinuousCollisionCandidates.Count);
 30970        writer.WriteInt32(_dirtyContinuousCollisionBodies.Count);
 66671        for (int i = 0; i < _dirtyContinuousCollisionBodies.Count; i++)
 2472            writer.WriteInt32(_dirtyContinuousCollisionBodies[i].Collider.ReplayOrdinal);
 30973        writer.WriteInt32(_processedContinuousCollisionBodies.Count);
 30974        writer.WriteInt32(_queuedContinuousCollisionHandoffBodies.Count);
 30975        writer.WriteInt32(_continuousCollisionHandoffQueue.Count);
 63476        for (int i = 0; i < _continuousCollisionHandoffQueue.Count; i++)
 877            writer.WriteInt32(_continuousCollisionHandoffQueue[i].DynamicId);
 30978        writer.WriteInt32(LastContinuousCollisionIslandCount);
 30979        writer.WriteInt32(LastContinuousCollisionIslandIterationCount);
 30980        writer.WriteBool(LastContinuousCollisionIslandLimitReached);
 30981    }
 82
 83    private static bool TryGetReplayHashPair(
 84        LSCollider first,
 85        LSCollider second,
 86        out CollisionPair? pair) =>
 736987        first.TryGetCollisionPair(second.Id, out pair)
 736988        || second.TryGetCollisionPair(first.Id, out pair);
 89}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.Response.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.Response.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 Gravitas.Constraints;
 10
 11namespace Gravitas;
 12
 13public sealed partial class GravitasPhysicsService
 14{
 15    internal void QueueDiscreteResponsePair(CollisionPair pair)
 16    {
 82417        SwiftThrowHelper.ThrowIfNull(pair, nameof(pair));
 82418        _discreteResponsePairs.Add(pair);
 82419    }
 20
 21    private void SolveDiscreteResponsePairs()
 22    {
 218123        bool hasJoints = _context.Constraints3D.HasActiveJoints;
 218124        if (_discreteResponsePairs.Count == 0 && !hasJoints)
 122525            return;
 26
 95627        if (!hasJoints && _discreteResponsePairs.Count == 1)
 28        {
 6529            CollisionPair pair = _discreteResponsePairs[0];
 6530            if (!HasAwakeResponseParticipant(pair))
 231                return;
 32
 6333            pair.WakeSleepingBodiesForCollision();
 6334            CollisionResponse.CalculateImpulse(pair);
 6335            return;
 36        }
 37
 89138        if (_discreteResponsePairs.Count > 1)
 22739            _discreteResponsePairs.SortInPlace(ResponsePairComparer);
 89140        BuildDiscreteIslands();
 89141        if (_discreteIslandConstraints.Count == 0)
 142            return;
 43
 89044        _discreteIslandConstraints.SortInPlace(IslandConstraintComparer);
 45
 89046        int start = 0;
 178747        while (start < _discreteIslandConstraints.Count)
 48        {
 89749            int rootKey = _discreteIslandConstraints[start].RootKey;
 89750            int end = start + 1;
 574951            while (end < _discreteIslandConstraints.Count
 574952                && _discreteIslandConstraints[end].RootKey == rootKey)
 53            {
 485254                end++;
 55            }
 56
 89757            if (WakeIslandBodies(rootKey))
 89658                SolveDiscreteIslandRange(start, end);
 59
 89760            start = end;
 61        }
 89062    }
 63
 64    private void BuildDiscreteIslands()
 65    {
 89166        _discreteIslandNodes.FastClear();
 89167        _discreteIslandConstraints.FastClear();
 68
 326669        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 70        {
 74271            CollisionPair pair = _discreteResponsePairs[i];
 74272            AddIslandNodeIfMovable(pair.ColliderA.Body!);
 74273            AddIslandNodeIfMovable(pair.ColliderB.Body!);
 74        }
 75
 89176        AddJointIslandNodes();
 77
 89178        SortAndDeduplicateIslandNodes();
 89179        if (_discreteIslandNodes.Count == 0)
 180            return;
 81
 326482        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 83        {
 74284            CollisionPair pair = _discreteResponsePairs[i];
 74285            int nodeA = FindIslandNode(pair.ColliderA.Body!);
 74286            int nodeB = FindIslandNode(pair.ColliderB.Body!);
 74287            if (nodeA >= 0 && nodeB >= 0)
 71988                UnionIslandNodes(nodeA, nodeB);
 89        }
 90
 89091        UnionJointIslandNodes();
 92
 89093        CompressIslandRoots();
 94
 326495        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 96        {
 74297            CollisionPair pair = _discreteResponsePairs[i];
 74298            int nodeA = FindIslandNode(pair.ColliderA.Body!);
 74299            int nodeB = FindIslandNode(pair.ColliderB.Body!);
 742100            int rootKey = ResolveConstraintRootKey(nodeA, nodeB);
 742101            if (rootKey < 0)
 102                continue;
 103
 741104            GetStablePairKey(pair, out int minColliderId, out int maxColliderId);
 741105            _discreteIslandConstraints.Add(DiscreteIslandConstraint.CreatePair(
 741106                pair,
 741107                rootKey,
 741108                minColliderId,
 741109                maxColliderId));
 110        }
 111
 890112        AddJointIslandConstraints();
 890113    }
 114
 115    private void AddJointIslandNodes()
 116    {
 891117        GravitasConstraint3DService constraints = _context.Constraints3D;
 11804118        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 119        {
 5011120            if (!constraints.TryGetJointForSolver(jointId, out Joint3D? joint))
 121                continue;
 122
 5008123            AddIslandNodeIfMovable(joint!.BodyA);
 5008124            AddIslandNodeIfMovable(joint.BodyB);
 125        }
 891126    }
 127
 128    private void UnionJointIslandNodes()
 129    {
 890130        GravitasConstraint3DService constraints = _context.Constraints3D;
 11800131        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 132        {
 5010133            if (!constraints.TryGetJointForSolver(jointId, out Joint3D? joint))
 134                continue;
 135
 5008136            int nodeA = FindIslandNode(joint!.BodyA);
 5008137            int nodeB = FindIslandNode(joint.BodyB);
 5008138            if (nodeA >= 0 && nodeB >= 0)
 4674139                UnionIslandNodes(nodeA, nodeB);
 140        }
 890141    }
 142
 143    private void AddJointIslandConstraints()
 144    {
 890145        GravitasConstraint3DService constraints = _context.Constraints3D;
 11800146        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 147        {
 5010148            if (!constraints.TryGetJointForSolver(jointId, out Joint3D? joint))
 149                continue;
 150
 5008151            int nodeA = FindIslandNode(joint!.BodyA);
 5008152            int nodeB = FindIslandNode(joint.BodyB);
 5008153            int rootKey = ResolveConstraintRootKey(nodeA, nodeB);
 154
 5008155            GetStableJointKey(joint, out int minColliderId, out int maxColliderId);
 5008156            _discreteIslandConstraints.Add(DiscreteIslandConstraint.CreateJoint(
 5008157                joint,
 5008158                rootKey,
 5008159                minColliderId,
 5008160                maxColliderId));
 161        }
 890162    }
 163
 164    private void AddIslandNodeIfMovable(SolidBody body)
 165    {
 11500166        if (!IsMovableIslandBody(body))
 358167            return;
 168
 11142169        _discreteIslandNodes.Add(new DiscreteIslandNode(body.DynamicId, body));
 11142170    }
 171
 172    private void SortAndDeduplicateIslandNodes() =>
 891173        IslandGraphUtility.SortAndDeduplicate(_discreteIslandNodes, IslandNodeComparer);
 174
 175    private int FindIslandNode(SolidBody body)
 176    {
 23000177        if (!IsMovableIslandBody(body))
 716178            return -1;
 179
 22284180        return IslandGraphUtility.Find(_discreteIslandNodes, body.DynamicId);
 181    }
 182
 183    private void UnionIslandNodes(int nodeA, int nodeB) =>
 5393184        IslandGraphUtility.Union(_discreteIslandNodes, nodeA, nodeB);
 185
 186    private void CompressIslandRoots() =>
 890187        IslandGraphUtility.CompressRoots(_discreteIslandNodes);
 188
 189    private int ResolveConstraintRootKey(int nodeA, int nodeB) =>
 5750190        IslandGraphUtility.ResolveConstraintRootKey(_discreteIslandNodes, nodeA, nodeB);
 191
 192    private bool WakeIslandBodies(int rootKey) =>
 897193        IslandGraphUtility.WakeBodies(_discreteIslandNodes, rootKey);
 194
 195    private void SolveDiscreteIslandRange(int start, int end)
 196    {
 896197        if (end - start == 1 && _discreteIslandConstraints[start].Kind == DiscreteIslandConstraintKind.Contact)
 198        {
 13199            CollisionResponse.CalculateImpulse(_discreteIslandConstraints[start].Pair);
 13200            return;
 201        }
 202
 883203        int iterations = _context.Settings.DiscreteSolverIterations;
 19066204        for (int iteration = 0; iteration < iterations; iteration++)
 205        {
 8650206            bool applyCachedImpulse = iteration == 0;
 8650207            bool applyPositionCorrection = iteration == 0;
 134004208            for (int i = start; i < end; i++)
 209            {
 58352210                DiscreteIslandConstraint constraint = _discreteIslandConstraints[i];
 58352211                if (constraint.Kind == DiscreteIslandConstraintKind.Contact)
 212                {
 8622213                    CollisionResponse.CalculateImpulse(
 8622214                        constraint.Pair,
 8622215                        applyCachedImpulse,
 8622216                        applyPositionCorrection);
 217                }
 218                else
 49730219                    JointSolver3D.Solve(constraint.Joint!, applyCachedImpulse);
 220            }
 221        }
 883222    }
 223
 224}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/GravitasPhysicsService.SupportTypes.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysicsService.SupportTypes.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 Gravitas.Constraints;
 10using System.Collections.Generic;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas;
 14
 15public sealed partial class GravitasPhysicsService
 16{
 17    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3457718    private static bool IsMovableIslandBody(SolidBody body) => body.HasSolverMobility;
 19
 20    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 21    private static bool HasAwakeResponseParticipant(CollisionPair pair) =>
 6522        IsAwakeIslandBody(pair.ColliderA.Body!) || IsAwakeIslandBody(pair.ColliderB.Body!);
 23
 24    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 25    private static bool IsAwakeIslandBody(SolidBody body) =>
 7726        IsMovableIslandBody(body) && body.IsAwakeForCollision;
 27
 28    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 29    private static void GetStablePairKey(CollisionPair pair, out int minColliderId, out int maxColliderId)
 30    {
 198531        int idA = pair.ColliderA.Id;
 198532        int idB = pair.ColliderB.Id;
 198533        if (idA <= idB)
 34        {
 196535            minColliderId = idA;
 196536            maxColliderId = idB;
 196537            return;
 38        }
 39
 2040        minColliderId = idB;
 2041        maxColliderId = idA;
 2042    }
 43
 44    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 45    private static void GetStableJointKey(Joint3D joint, out int minColliderId, out int maxColliderId)
 46    {
 500847        int idA = joint.BodyA.Collider.Id;
 500848        int idB = joint.BodyB.Collider.Id;
 500849        if (idA <= idB)
 50        {
 500651            minColliderId = idA;
 500652            maxColliderId = idB;
 500653            return;
 54        }
 55
 256        minColliderId = idB;
 257        maxColliderId = idA;
 258    }
 59
 60    private sealed class CollisionPairStableKeyComparer : IComparer<CollisionPair>
 61    {
 62        public int Compare(CollisionPair? left, CollisionPair? right)
 63        {
 62264            GetStablePairKey(left!, out int leftMin, out int leftMax);
 62265            GetStablePairKey(right!, out int rightMin, out int rightMax);
 66
 62267            int compare = leftMin.CompareTo(rightMin);
 62268            return compare != 0 ? compare : leftMax.CompareTo(rightMax);
 69        }
 70    }
 71
 72    private sealed class DiscreteIslandConstraintComparer : IComparer<DiscreteIslandConstraint>
 73    {
 74        public int Compare(DiscreteIslandConstraint left, DiscreteIslandConstraint right)
 75        {
 817876            int compare = left.RootKey.CompareTo(right.RootKey);
 817877            if (compare != 0)
 778                return compare;
 79
 817180            compare = left.MinColliderId.CompareTo(right.MinColliderId);
 817181            if (compare != 0)
 662182                return compare;
 83
 155084            compare = left.MaxColliderId.CompareTo(right.MaxColliderId);
 155085            if (compare != 0)
 154586                return compare;
 87
 588            compare = left.Kind.CompareTo(right.Kind);
 589            return compare != 0 ? compare : left.JointId.CompareTo(right.JointId);
 90        }
 91    }
 92
 93    private struct DiscreteIslandNode : IIslandNodeState
 94    {
 95        public DiscreteIslandNode(int bodyKey, SolidBody body)
 96        {
 1114297            BodyKey = bodyKey;
 1114298            Body = body;
 1114299            ParentIndex = -1;
 11142100            RootKey = bodyKey;
 11142101        }
 102
 103        public int BodyKey { get; }
 104
 105        public SolidBody Body { get; }
 106
 107        public int ParentIndex { get; set; }
 108
 109        public int RootKey { get; set; }
 110
 902111        public bool IsAwakeForCollision => Body.IsAwakeForCollision;
 112
 5574113        public void WakeFromCollision() => Body.WakeFromCollision();
 114    }
 115
 116    private readonly struct DiscreteIslandConstraint
 117    {
 118        private DiscreteIslandConstraint(
 119            DiscreteIslandConstraintKind kind,
 120            CollisionPair pair,
 121            Joint3D? joint,
 122            int rootKey,
 123            int minColliderId,
 124            int maxColliderId,
 125            int jointId)
 126        {
 5749127            Kind = kind;
 5749128            Pair = pair;
 5749129            Joint = joint;
 5749130            RootKey = rootKey;
 5749131            MinColliderId = minColliderId;
 5749132            MaxColliderId = maxColliderId;
 5749133            JointId = jointId;
 5749134        }
 135
 136        public static DiscreteIslandConstraint CreatePair(
 137            CollisionPair pair,
 138            int rootKey,
 139            int minColliderId,
 140            int maxColliderId) =>
 741141            new(DiscreteIslandConstraintKind.Contact, pair, null, rootKey, minColliderId, maxColliderId, 0);
 142
 143        public static DiscreteIslandConstraint CreateJoint(
 144            Joint3D joint,
 145            int rootKey,
 146            int minColliderId,
 147            int maxColliderId) =>
 5008148            new(DiscreteIslandConstraintKind.Joint, null!, joint, rootKey, minColliderId, maxColliderId, joint.Id);
 149
 150        public DiscreteIslandConstraintKind Kind { get; }
 151        public CollisionPair Pair { get; }
 152        public Joint3D? Joint { get; }
 153        public int RootKey { get; }
 154        public int MinColliderId { get; }
 155        public int MaxColliderId { get; }
 156        public int JointId { get; }
 157    }
 158
 159    private enum DiscreteIslandConstraintKind : byte
 160    {
 161        Contact = 0,
 162        Joint = 1
 163    }
 164
 4338165    private int InactiveFrameThreshold => _context.FrameRate * 8;
 166}

Methods/Properties

PrepareContinuousCollisionFrame()
AddContinuousCollisionCandidate(Gravitas.SolidBody)
QueryContinuousCollisionCandidates(SwiftCollections.Query.FixedBoundVolume)
TryGetContinuousCollisionCandidate(System.Int32,Gravitas.SolidBody&)
GetContinuousCollisionCandidate(System.Int32)
CanAdmitContinuousCollisionCandidateRefresh(Gravitas.SolidBody)
CanAdmitContinuousCollisionCandidateRefresh(Gravitas.SolidBody,Gravitas.SolidBody)
TryReserveContinuousCollisionCandidateRefresh(Gravitas.SolidBody)
ReleaseContinuousCollisionCandidateRefresh(Gravitas.SolidBody)
ReleaseContinuousCollisionCandidateRefresh(Gravitas.SolidBody,System.Int32)
InvalidateContinuousCollisionStateForMotionTypeChange(Gravitas.SolidBody,System.Int32)
TryReserveContinuousCollisionCandidateRefresh(Gravitas.SolidBody,Gravitas.SolidBody)
RefreshContinuousCollisionCandidate(Gravitas.SolidBody)
QueueContinuousCollisionHandoff(Gravitas.SolidBody)
ProcessQueuedContinuousCollisionHandoffs()
ReportContinuousCollisionIterationLimit()
ProcessQueuedContinuousCollisionHandoffs(System.Int32)
BeginContinuousCollisionHandoffFrame()
AbortContinuousCollisionHandoffFrame()
ClearContinuousCollisionHandoffQueue()
DiscardContinuousCollisionHandoffQueue()
.cctor()
.ctor(Gravitas.GravitasWorldContext)
get_PeakColliderCount()
get_ColliderCount()
Simulate()
PrepareCollisionPartitions()
LateSimulate()
BeginLateSimulateBodies(System.Boolean)
CompleteLateSimulatePhysicsStep()
RunDiscreteCollisionStep()
UpdateSleepStatesAfterPhysicsStep()
Visualize()
Reset()
AssimilateBody(Gravitas.SolidBody,Gravitas.BodyMotionType)
RefreshBodyMotionTypeRegistration(Gravitas.SolidBody,Gravitas.BodyMotionType)
AssimilateCollider(Gravitas.Colliders.LSCollider)
DessimilateBody(Gravitas.SolidBody)
DessimilateCollider(Gravitas.Colliders.LSCollider)
RefreshColliderServiceRefreshRegistration(Gravitas.Colliders.LSCollider)
TryGetColliderById(System.Int32,Gravitas.Colliders.LSCollider&)
TryGetColliderByServiceIndex(System.Int32,Gravitas.Colliders.LSCollider&)
GetColliderByServiceIndex(System.Int32)
PrepareReplayColliders()
AddServiceRefreshCollider(Gravitas.Colliders.LSCollider)
RemoveServiceRefreshCollider(Gravitas.Colliders.LSCollider)
GetCollisionPair(System.Int32,System.Int32)
RequireCollisionPair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
IsLayerCollisionDisabled(Gravitas.Support.PhysicsLayer,Gravitas.Support.PhysicsLayer)
PoolForDeactivation(Gravitas.CollisionHandling.CollisionPair)
FullDeactivateCollisionPair(Gravitas.CollisionHandling.CollisionPair)
DeactivateAndPoolPair(Gravitas.CollisionHandling.CollisionPair)
RemovePairReferences(Gravitas.CollisionHandling.CollisionPair)
RemovePairsForCollider(Gravitas.Colliders.LSCollider)
ClearWarmStartCachesForCollider(Gravitas.Colliders.LSCollider)
CreatePair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
ProcessActiveCollisionPairs()
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
TryGetReplayHashPair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.CollisionPair&)
QueueDiscreteResponsePair(Gravitas.CollisionHandling.CollisionPair)
SolveDiscreteResponsePairs()
BuildDiscreteIslands()
AddJointIslandNodes()
UnionJointIslandNodes()
AddJointIslandConstraints()
AddIslandNodeIfMovable(Gravitas.SolidBody)
SortAndDeduplicateIslandNodes()
FindIslandNode(Gravitas.SolidBody)
UnionIslandNodes(System.Int32,System.Int32)
CompressIslandRoots()
ResolveConstraintRootKey(System.Int32,System.Int32)
WakeIslandBodies(System.Int32)
SolveDiscreteIslandRange(System.Int32,System.Int32)
IsMovableIslandBody(Gravitas.SolidBody)
HasAwakeResponseParticipant(Gravitas.CollisionHandling.CollisionPair)
IsAwakeIslandBody(Gravitas.SolidBody)
GetStablePairKey(Gravitas.CollisionHandling.CollisionPair,System.Int32&,System.Int32&)
GetStableJointKey(Gravitas.Constraints.Joint3D,System.Int32&,System.Int32&)
Compare(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.CollisionPair)
Compare(Gravitas.GravitasPhysicsService/DiscreteIslandConstraint,Gravitas.GravitasPhysicsService/DiscreteIslandConstraint)
.ctor(System.Int32,Gravitas.SolidBody)
get_IsAwakeForCollision()
WakeFromCollision()
.ctor(Gravitas.GravitasPhysicsService/DiscreteIslandConstraintKind,Gravitas.CollisionHandling.CollisionPair,Gravitas.Constraints.Joint3D,System.Int32,System.Int32,System.Int32,System.Int32)
CreatePair(Gravitas.CollisionHandling.CollisionPair,System.Int32,System.Int32,System.Int32)
CreateJoint(Gravitas.Constraints.Joint3D,System.Int32,System.Int32,System.Int32)
get_InactiveFrameThreshold()