< Summary

Line coverage
100%
Covered lines: 874
Uncovered lines: 0
Coverable lines: 874
Total lines: 1733
Line coverage: 100%
Branch coverage
100%
Covered branches: 458
Total branches: 458
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%88100%
File 1: AddContinuousCollisionCandidate(...)100%44100%
File 1: QueryPlanarContinuousCollisionCandidates(...)100%11100%
File 1: RefreshContinuousCollisionCandidate(...)100%22100%
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: CanAdmitContinuousCollisionCandidateRefresh(...)100%11100%
File 1: TryReserveContinuousCollisionCandidateRefresh(...)100%11100%
File 1: QueryMixedContinuousCollisionCandidates(...)100%22100%
File 1: MergeContinuousCollisionCandidateIds()100%1212100%
File 1: TryGetContinuousCollisionCandidate(...)100%44100%
File 1: GetContinuousCollisionCandidate(...)100%11100%
File 1: QueueContinuousCollisionHandoff(...)100%44100%
File 1: ProcessQueuedContinuousCollisionHandoffs()100%11100%
File 1: ReportContinuousCollisionIterationLimit()100%11100%
File 1: ProcessQueuedContinuousCollisionHandoffs(...)100%1414100%
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_ColliderCount()100%11100%
File 2: AssimilateBody(...)100%44100%
File 2: RefreshBodyMotionTypeRegistration(...)100%66100%
File 2: AssimilateCollider(...)100%11100%
File 2: DessimilateBody(...)100%44100%
File 2: DessimilateCollider(...)100%66100%
File 2: Simulate()100%22100%
File 2: LateSimulate()100%22100%
File 2: BeginLateSimulateBodies(...)100%66100%
File 2: CompleteLateSimulatePhysicsStep()100%11100%
File 2: PrepareCollisionPartitions()100%44100%
File 2: RunDiscreteCollisionStep()100%11100%
File 2: UpdateSleepStatesAfterPhysicsStep()100%22100%
File 2: Visualize()100%22100%
File 2: Reset()100%11100%
File 2: TryGetColliderById(...)100%11100%
File 2: TryGetColliderByServiceIndex(...)100%11100%
File 2: GetColliderByServiceIndex(...)100%11100%
File 2: PrepareReplayColliders()100%11100%
File 2: TryGetDynamicBody(...)100%11100%
File 2: RefreshColliderServiceRefreshRegistration(...)100%22100%
File 2: AddServiceRefreshCollider(...)100%11100%
File 2: RemoveServiceRefreshCollider(...)100%66100%
File 2: EnsureFrameCapacity()100%44100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: get_IsActive()100%11100%
File 3: .ctor(...)100%11100%
File 3: get_IsCurrentLifetime()100%11100%
File 3: RefreshGroundingFromDiscreteResponse(...)100%3434100%
File 3: ShouldUseDiscreteGroundingPair(...)100%44100%
File 3: ShouldUseDiscreteGroundingManifold(...)100%22100%
File 4: ProcessPartitionCandidate(...)100%1212100%
File 4: IsCanonicalSharedPartition(...)100%1010100%
File 4: Max(...)100%22100%
File 4: TryGetMinimumVoxelIndexForGrid(...)100%1212100%
File 4: ProcessCandidate(...)100%2222100%
File 4: RegisterPair(...)100%11100%
File 4: ExpandDiscreteResponsePairs(...)100%88100%
File 4: AddExistingResponsePairs(...)100%1414100%
File 4: TryAddExistingResponsePair(...)100%2020100%
File 4: AddDiscreteResponseBody(...)100%44100%
File 4: IsCurrentResponsePair(...)100%88100%
File 4: RequireCollisionPair(...)100%2020100%
File 4: CleanupUntouchedPairs(...)100%1010100%
File 4: TryKeepUntouchedPair(...)100%1212100%
File 4: CreatePair(...)100%44100%
File 4: RecyclePair(...)100%44100%
File 4: RemovePairsForCollider(...)100%1818100%
File 4: ClearWarmStartCachesForCollider(...)100%1212100%
File 5: ContributeReplayHash(...)100%1616100%
File 5: TryGetReplayHashPair(...)100%22100%
File 6: SolveDiscreteResponsePairs()100%2222100%
File 6: BuildDiscreteIslands()100%1414100%
File 6: AddJointIslandNodes()100%44100%
File 6: UnionJointIslandNodes()100%88100%
File 6: AddJointIslandConstraints()100%44100%
File 6: AddIslandNodeIfMovable(...)100%22100%
File 6: SortAndDeduplicateIslandNodes()100%11100%
File 6: FindIslandNode(...)100%22100%
File 6: UnionIslandNodes(...)100%11100%
File 6: CompressIslandRoots()100%11100%
File 6: ResolveConstraintRootKey(...)100%11100%
File 6: WakeIslandBodies(...)100%11100%
File 6: SolveDiscreteIslandRange(...)100%1010100%
File 7: HasAwakeMovableParticipant(...)100%22100%
File 7: HasAwakeResponseParticipant(...)100%22100%
File 7: IsAwakeMovable(...)100%44100%
File 7: IsMovableIslandBody(...)100%44100%
File 7: IsAwakeIslandBody(...)100%22100%
File 7: IsLayerCollisionDisabled(...)100%44100%
File 7: RemovePairReferences(...)100%11100%
File 7: CreatePairKey(...)100%22100%
File 7: GetStablePairKey(...)100%22100%
File 7: GetStableJointKey(...)100%22100%
File 7: Compare(...)100%22100%
File 7: Compare(...)100%88100%
File 7: .ctor(...)100%11100%
File 7: get_IsAwakeForCollision()100%11100%
File 7: WakeFromCollision()100%11100%
File 7: .ctor(...)100%11100%
File 7: CreatePair(...)100%11100%
File 7: CreateJoint(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.ContinuousCollision.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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 GravitasPhysics2DService
 17{
 18    internal void PrepareContinuousCollisionFrame()
 19    {
 348620        int token = _context.LateSimulateToken;
 348621        bool buildMixedIndex = _context.Settings.RuntimeMode.RunsMixedContacts();
 348622        if (_continuousCollisionPreparedToken == token
 348623            && _continuousCollisionPreparedMixedIndex == buildMixedIndex)
 24        {
 236525            return;
 26        }
 27
 112128        _planarContinuousCollisionCandidates.Clear();
 112129        _dirtyPlanarContinuousCollisionCandidates.Clear();
 112130        _dirtyPlanarContinuousCollisionBodies.FastClear();
 112131        _dirtyPlanarContinuousCollisionBodySet.Clear();
 112132        _mixedContinuousCollisionCandidates.Clear();
 112133        _dirtyMixedContinuousCollisionCandidates.Clear();
 713734        foreach (SolidBody2D body in _dynamicBodies)
 35        {
 244836            body.EnsureContinuousCollisionFramePrepared(token);
 244737            AddContinuousCollisionCandidate(body, buildMixedIndex);
 38        }
 39
 112040        _planarContinuousCollisionCandidates.Sort();
 112041        if (buildMixedIndex)
 40942            _mixedContinuousCollisionCandidates.Sort();
 43
 112044        _continuousCollisionPreparedToken = token;
 112045        _continuousCollisionPreparedMixedIndex = buildMixedIndex;
 112046    }
 47
 48    private void AddContinuousCollisionCandidate(SolidBody2D body, bool buildMixedIndex)
 49    {
 244750        Fixed64 planarRadius = body.ResolveContinuousCollisionProxyRadius();
 244751        if (planarRadius <= Fixed64.Epsilon)
 452            return;
 53
 244354        _continuousCollisionCandidateLifetimes[body.DynamicId] =
 244355            new ColliderLifetimeToken2D(body.Collider);
 244356        _planarContinuousCollisionCandidates.Add(
 244357            body.DynamicId,
 244358            DynamicCcdCandidateIndex2D.CreateBoundsBetween(
 244359                body.ContinuousCollisionFrameStart,
 244360                body.ContinuousCollisionFrameEnd,
 244361                planarRadius));
 62
 244363        if (!buildMixedIndex)
 205664            return;
 65
 38766        _mixedContinuousCollisionCandidates.Add(
 38767            body.DynamicId,
 38768            body.ResolveMixedContinuousCollisionTrajectoryBounds(planarRadius));
 38769    }
 70
 71
 72    internal SwiftList<int> QueryPlanarContinuousCollisionCandidates(DynamicCcdPlanarBounds sourceBounds)
 73    {
 108274        PrepareContinuousCollisionFrame();
 108275        _planarContinuousCollisionCandidates.Query(sourceBounds, _continuousCollisionCandidateIds);
 108276        _dirtyPlanarContinuousCollisionCandidates.Query(sourceBounds, _dirtyContinuousCollisionCandidateIds);
 108277        return MergeContinuousCollisionCandidateIds();
 78    }
 79
 80    internal void RefreshContinuousCollisionCandidate(SolidBody2D body)
 81    {
 30282        Fixed64 radius = body.ResolveContinuousCollisionProxyRadius();
 30283        TryReserveContinuousCollisionCandidateRefresh(body);
 84
 30285        _dirtyPlanarContinuousCollisionCandidates.AddOrUpdate(
 30286            body.DynamicId,
 30287            body.ResolveContinuousCollisionTrajectoryBounds(radius));
 30288        if (_continuousCollisionPreparedMixedIndex)
 89        {
 11390            _dirtyMixedContinuousCollisionCandidates.AddOrUpdate(
 11391                body.DynamicId,
 11392                body.ResolveMixedContinuousCollisionTrajectoryBounds(radius));
 93        }
 30294    }
 95
 2496    internal bool CanAdmitContinuousCollisionCandidateRefresh(SolidBody2D body) => true;
 97
 98    internal bool TryReserveContinuousCollisionCandidateRefresh(SolidBody2D body)
 99    {
 605100        if (_dirtyPlanarContinuousCollisionBodySet.Add(body))
 262101            _dirtyPlanarContinuousCollisionBodies.Add(body);
 102
 605103        return true;
 104    }
 105
 106    private void ReleaseContinuousCollisionCandidateRefresh(SolidBody2D body) =>
 119107        ReleaseContinuousCollisionCandidateRefresh(body, body.DynamicId);
 108
 109    private void ReleaseContinuousCollisionCandidateRefresh(SolidBody2D body, int dynamicId)
 110    {
 197111        if (!_dirtyPlanarContinuousCollisionBodySet.Remove(body))
 194112            return;
 113
 3114        _dirtyPlanarContinuousCollisionBodies.Remove(body);
 3115        _dirtyPlanarContinuousCollisionCandidates.Remove(dynamicId);
 3116        _dirtyMixedContinuousCollisionCandidates.Remove(dynamicId);
 3117    }
 118
 119    internal void InvalidateContinuousCollisionStateForMotionTypeChange(
 120        SolidBody2D body,
 121        int dynamicId)
 122    {
 78123        if (dynamicId >= 0)
 65124            _continuousCollisionCandidateLifetimes[dynamicId] = default;
 125
 78126        ReleaseContinuousCollisionCandidateRefresh(body, dynamicId);
 78127        _processedContinuousCollisionBodies.Remove(body);
 78128        _queuedContinuousCollisionHandoffBodies.Remove(body);
 78129        _continuousCollisionHandoffQueue.Remove(body);
 78130        body.DiscardContinuousCollisionHandoff();
 78131        _continuousCollisionPreparedToken = int.MinValue;
 78132        _continuousCollisionPreparedMixedIndex = false;
 78133    }
 134
 135    internal bool CanAdmitContinuousCollisionCandidateRefresh(
 136        SolidBody2D first,
 7137        SolidBody2D second) => true;
 138
 139    internal bool TryReserveContinuousCollisionCandidateRefresh(
 140        SolidBody2D first,
 141        SolidBody2D second)
 142    {
 44143        TryReserveContinuousCollisionCandidateRefresh(first);
 44144        TryReserveContinuousCollisionCandidateRefresh(second);
 44145        return true;
 146    }
 147
 148    internal SwiftList<int> QueryMixedContinuousCollisionCandidates(FixedBoundVolume sourceBounds)
 149    {
 292150        if (!_context.Settings.RuntimeMode.RunsMixedContacts())
 151        {
 1152            _continuousCollisionCandidateIds.FastClear();
 1153            return _continuousCollisionCandidateIds;
 154        }
 155
 291156        PrepareContinuousCollisionFrame();
 291157        _mixedContinuousCollisionCandidates.Query(sourceBounds, _continuousCollisionCandidateIds);
 291158        _dirtyMixedContinuousCollisionCandidates.Query(
 291159            sourceBounds,
 291160            _dirtyContinuousCollisionCandidateIds);
 291161        return MergeContinuousCollisionCandidateIds();
 162    }
 163
 164    private SwiftList<int> MergeContinuousCollisionCandidateIds()
 165    {
 1373166        int retainedCount = 0;
 6084167        for (int i = 0; i < _continuousCollisionCandidateIds.Count; i++)
 168        {
 1669169            int dynamicId = _continuousCollisionCandidateIds[i];
 1669170            if (TryGetContinuousCollisionCandidate(dynamicId, out SolidBody2D body)
 1669171                && !_dirtyPlanarContinuousCollisionBodySet.Contains(body))
 172            {
 1193173                _continuousCollisionCandidateIds[retainedCount++] = dynamicId;
 174            }
 175        }
 176
 1849177        while (_continuousCollisionCandidateIds.Count > retainedCount)
 476178            _continuousCollisionCandidateIds.RemoveAt(
 476179                _continuousCollisionCandidateIds.Count - 1);
 180
 3670181        for (int i = 0; i < _dirtyContinuousCollisionCandidateIds.Count; i++)
 182        {
 462183            int dynamicId = _dirtyContinuousCollisionCandidateIds[i];
 462184            if (TryGetContinuousCollisionCandidate(dynamicId, out _))
 460185                _continuousCollisionCandidateIds.Add(dynamicId);
 186        }
 187
 1373188        return _continuousCollisionCandidateIds;
 189    }
 190
 191    internal bool TryGetContinuousCollisionCandidate(int dynamicId, out SolidBody2D body)
 192    {
 2131193        ColliderLifetimeToken2D registration = _continuousCollisionCandidateLifetimes[dynamicId];
 2131194        if (registration.Collider != null && registration.IsActive)
 195        {
 2121196            body = registration.Collider.Body!;
 2121197            return true;
 198        }
 199
 10200        body = null!;
 10201        return false;
 202    }
 203
 204    internal SolidBody2D GetContinuousCollisionCandidate(int dynamicId) =>
 1633205        _dynamicBodies[dynamicId];
 206
 207    internal void QueueContinuousCollisionHandoff(SolidBody2D body)
 208    {
 175209        if (!_processedContinuousCollisionBodies.Contains(body)
 175210            || !_queuedContinuousCollisionHandoffBodies.Add(body))
 211        {
 97212            return;
 213        }
 214
 78215        _continuousCollisionHandoffQueue.Add(body);
 78216    }
 217
 218    internal bool ProcessQueuedContinuousCollisionHandoffs() =>
 3219        ProcessQueuedContinuousCollisionHandoffs(_context.Settings.ContinuousCollisionMaxToiIterations) > 0;
 220
 221    internal void ReportContinuousCollisionIterationLimit() =>
 5222        LastContinuousCollisionIslandLimitReached = true;
 223
 224    internal int ProcessQueuedContinuousCollisionHandoffs(int iterationBudget)
 225    {
 1073226        if (_continuousCollisionHandoffQueue.Count == 0)
 1052227            return 0;
 228
 21229        if (iterationBudget <= 0)
 230        {
 3231            LastContinuousCollisionIslandLimitReached = true;
 3232            DiscardContinuousCollisionHandoffQueue();
 3233            return 0;
 234        }
 235
 18236        int readIndex = 0;
 18237        int iterations = 0;
 18238        bool processed = false;
 18239        bool completed = false;
 18240        bool limitReached = false;
 241        try
 242        {
 46243            while (readIndex < _continuousCollisionHandoffQueue.Count && iterations < iterationBudget)
 244            {
 29245                SolidBody2D body = _continuousCollisionHandoffQueue[readIndex++];
 246                // Consumption can synchronously return work to this body; dequeue ends dedupe ownership first.
 29247                _queuedContinuousCollisionHandoffBodies.Remove(body);
 248
 29249                if (body.TryConsumeContinuousCollisionHandoff(updateSleepState: false, updateColliderState: false))
 250                {
 23251                    processed = true;
 23252                    iterations++;
 253                }
 254            }
 255
 17256            limitReached = readIndex < _continuousCollisionHandoffQueue.Count;
 17257            completed = true;
 17258            return iterations;
 259        }
 260        finally
 261        {
 18262            if (processed)
 13263                LastContinuousCollisionIslandCount++;
 264
 18265            LastContinuousCollisionIslandIterationCount += iterations;
 18266            LastContinuousCollisionIslandLimitReached |= completed && limitReached;
 18267            if (!completed || limitReached)
 3268                DiscardContinuousCollisionHandoffQueue();
 269            else
 15270                ClearContinuousCollisionHandoffQueue();
 18271        }
 17272    }
 273
 274    private void BeginContinuousCollisionHandoffFrame()
 275    {
 1021276        _processedContinuousCollisionBodies.Clear();
 1021277        DiscardContinuousCollisionHandoffQueue();
 1021278        LastContinuousCollisionIslandCount = 0;
 1021279        LastContinuousCollisionIslandIterationCount = 0;
 1021280        LastContinuousCollisionIslandLimitReached = false;
 1021281    }
 282
 283    internal void AbortContinuousCollisionHandoffFrame()
 284    {
 1285        _processedContinuousCollisionBodies.Clear();
 1286        DiscardContinuousCollisionHandoffQueue();
 1287    }
 288
 289    private void ClearContinuousCollisionHandoffQueue()
 290    {
 1076291        _queuedContinuousCollisionHandoffBodies.Clear();
 1076292        _continuousCollisionHandoffQueue.FastClear();
 1076293    }
 294
 295    private void DiscardContinuousCollisionHandoffQueue()
 296    {
 2220297        for (int i = 0; i < _continuousCollisionHandoffQueue.Count; i++)
 49298            _continuousCollisionHandoffQueue[i].DiscardContinuousCollisionHandoff();
 299
 1061300        ClearContinuousCollisionHandoffQueue();
 1061301    }
 302
 303}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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/// Context-owned pure 2D body, broad-phase, narrow-phase, response, and query service.
 17/// </summary>
 18public sealed partial class GravitasPhysics2DService
 19{
 120    private static readonly CollisionPair2DStableKeyComparer ResponsePairComparer = new();
 121    private static readonly IslandNodeKeyComparer<DiscreteIslandNode2D> IslandNodeComparer = new();
 122    private static readonly DiscreteIslandConstraint2DComparer IslandConstraintComparer = new();
 23
 24    private readonly GravitasWorldContext _context;
 508325    private readonly SwiftBucket<SolidBody2D> _dynamicBodies = new();
 508326    private readonly ColliderRegistry<LSCollider2D> _colliders = new();
 508327    private readonly SwiftList<LSCollider2D> _serviceRefreshColliders = new();
 508328    private readonly SwiftHashSet<ulong> _processedPairKeys = new();
 508329    private readonly SwiftDictionary<ulong, CollisionPair2D> _pairs = new();
 508330    private readonly SwiftList<ulong> _pairsToRemove = new();
 508331    private readonly SwiftList<CollisionPair2D> _pairsPendingSeparation = new();
 508332    private readonly SwiftStack<CollisionPair2D> _cachedPairs = new();
 508333    private readonly SwiftList<CollisionPair2D> _discreteResponsePairs = new();
 508334    private readonly SwiftList<CollisionPair2D> _existingResponsePairCandidates = new();
 508335    private readonly SwiftHashSet<int> _discreteResponseBodyKeys = new();
 508336    private readonly SwiftList<int> _discreteResponseBodyQueue = new();
 508337    private readonly SwiftList<DiscreteIslandNode2D> _discreteIslandNodes = new();
 508338    private readonly SwiftList<DiscreteIslandConstraint2D> _discreteIslandConstraints = new();
 508339    private readonly DynamicCcdCandidateIndex2D _planarContinuousCollisionCandidates = new();
 508340    private readonly DynamicCcdCandidateIndex2D _dirtyPlanarContinuousCollisionCandidates =
 508341        new(supportsUpdates: true);
 508342    private readonly SwiftList<SolidBody2D> _dirtyPlanarContinuousCollisionBodies = new();
 508343    private readonly SwiftHashSet<SolidBody2D> _dirtyPlanarContinuousCollisionBodySet = new();
 508344    private readonly DynamicCcdCandidateIndex _mixedContinuousCollisionCandidates = new();
 508345    private readonly DynamicCcdCandidateIndex _dirtyMixedContinuousCollisionCandidates =
 508346        new(supportsUpdates: true);
 508347    private readonly SwiftList<ColliderLifetimeToken2D> _continuousCollisionCandidateLifetimes = new();
 508348    private readonly SwiftList<int> _continuousCollisionCandidateIds = new();
 508349    private readonly SwiftList<int> _dirtyContinuousCollisionCandidateIds = new();
 508350    private readonly SwiftHashSet<SolidBody2D> _processedContinuousCollisionBodies = new();
 508351    private readonly SwiftHashSet<SolidBody2D> _queuedContinuousCollisionHandoffBodies = new();
 508352    private readonly SwiftList<SolidBody2D> _continuousCollisionHandoffQueue = new();
 508353    private int _continuousCollisionPreparedToken = int.MinValue;
 54    private bool _continuousCollisionPreparedMixedIndex;
 55
 56    /// <summary>Creates the pure 2D physics service for a world context.</summary>
 508357    public GravitasPhysics2DService(GravitasWorldContext context)
 58    {
 508359        SwiftThrowHelper.ThrowIfNull(context, nameof(context));
 508360        _context = context;
 508361    }
 62
 63    /// <summary>Gets the number of registered dynamic bodies.</summary>
 64    public int BodyCount { get; private set; }
 65
 66    /// <summary>Gets the number of registered 2D colliders.</summary>
 1040967    public int ColliderCount => _colliders.Count;
 68
 69    /// <summary>Gets or sets whether this service advances pure 2D physics.</summary>
 70    public bool SimulatePhysics { get; set; } = true;
 71
 72    internal int LastBroadPhaseCandidateCount { get; private set; }
 73
 74    /// <summary>
 75    /// Gets how many service-owned continuous-collision handoff batches were consumed during the last late step.
 76    /// </summary>
 77    public int LastContinuousCollisionIslandCount { get; private set; }
 78
 79    /// <summary>
 80    /// Gets how many service-owned continuous-collision handoff iterations were consumed during the last late step.
 81    /// </summary>
 82    public int LastContinuousCollisionIslandIterationCount { get; private set; }
 83
 84    /// <summary>
 85    /// Gets whether the service-owned continuous-collision handoff queue hit its deterministic iteration cap.
 86    /// </summary>
 87    public bool LastContinuousCollisionIslandLimitReached { get; private set; }
 88
 89    internal void AssimilateBody(SolidBody2D body, BodyMotionType motionType)
 90    {
 465291        SwiftThrowHelper.ThrowIfNull(body, nameof(body));
 465292        SwiftThrowHelper.ThrowIfArgument(
 465293            !ReferenceEquals(body.Context, _context),
 465294            nameof(body),
 465295            "2D body must belong to this physics service context.");
 96
 465297        if (motionType != BodyMotionType.Static)
 98        {
 329299            body.DynamicId = _dynamicBodies.Add(body);
 6560100            while (_continuousCollisionCandidateLifetimes.Count <= body.DynamicId)
 3268101                _continuousCollisionCandidateLifetimes.Add(default);
 3292102            BodyCount++;
 103        }
 104
 4652105        AssimilateCollider(body.Collider);
 4652106    }
 107
 108    internal void RefreshBodyMotionTypeRegistration(
 109        SolidBody2D body,
 110        BodyMotionType previousMotionType)
 111    {
 78112        if (previousMotionType == BodyMotionType.Static)
 113        {
 13114            body.DynamicId = _dynamicBodies.Add(body);
 20115            while (_continuousCollisionCandidateLifetimes.Count <= body.DynamicId)
 7116                _continuousCollisionCandidateLifetimes.Add(default);
 13117            BodyCount++;
 13118            return;
 119        }
 120
 65121        if (body.MotionType == BodyMotionType.Static)
 122        {
 14123            int previousDynamicId = body.DynamicId;
 14124            _dynamicBodies.RemoveAt(previousDynamicId);
 14125            _continuousCollisionCandidateLifetimes[previousDynamicId] = default;
 14126            ReleaseContinuousCollisionCandidateRefresh(body);
 14127            body.DynamicId = -1;
 14128            BodyCount--;
 129        }
 65130    }
 131
 132    internal void AssimilateCollider(LSCollider2D collider)
 133    {
 5162134        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 5162135        SwiftThrowHelper.ThrowIfArgument(
 5162136            !ReferenceEquals(collider.Context, _context),
 5162137            nameof(collider),
 5162138            "2D collider must belong to this physics service context.");
 139
 5162140        _colliders.Register(collider);
 5162141        RefreshColliderServiceRefreshRegistration(collider);
 5162142        _context.Collisions2D.PartitionCollider(collider);
 5162143    }
 144
 145    internal void DessimilateBody(SolidBody2D body)
 146    {
 123147        SwiftThrowHelper.ThrowIfNull(body, nameof(body));
 123148        if (body.DynamicId >= 0 && _dynamicBodies.TryRemoveAt(body.DynamicId))
 149        {
 105150            _continuousCollisionCandidateLifetimes[body.DynamicId] = default;
 105151            ReleaseContinuousCollisionCandidateRefresh(body);
 105152            BodyCount--;
 153        }
 154
 123155        LSCollider2D collider = body.Collider;
 123156        DessimilateCollider(collider);
 123157    }
 158
 159    internal void DessimilateCollider(LSCollider2D collider)
 160    {
 264161        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 264162        if (collider.Body != null)
 123163            _context.Constraints2D.RemoveJointsForBody(collider.Body);
 264164        RemovePairsForCollider(collider);
 264165        _context.MixedCollisions.RemovePairsFor2DCollider(collider);
 264166        if (collider.IsMixedPartitioned)
 18167            _context.MixedCollisions.ClearPartitioned2DCollider(collider, force: true);
 264168        if (collider.IsPartitioned)
 258169            _context.Collisions2D.ClearPartitionedCollider(collider, force: true);
 170
 264171        RemoveServiceRefreshCollider(collider);
 264172        _colliders.Remove(collider);
 264173    }
 174
 175    /// <summary>Runs the authoritative pure 2D simulation phase.</summary>
 176    public void Simulate()
 177    {
 480178        _context.EnterSimulationPhase();
 179        try
 180        {
 480181            if (!SimulatePhysics)
 5182                return;
 183
 475184            LastBroadPhaseCandidateCount = 0;
 475185        }
 186        finally
 187        {
 480188            _context.ExitSimulationPhase();
 480189        }
 480190    }
 191
 192    /// <summary>Completes deferred pure 2D collision, response, and bookkeeping work.</summary>
 193    public void LateSimulate()
 194    {
 4195        _context.EnterSimulationPhase();
 196        try
 197        {
 4198            if (!BeginLateSimulateBodies(continuousCollisionFramePrepared: false))
 1199                return;
 200
 3201            ProcessQueuedContinuousCollisionHandoffs();
 3202            CompleteLateSimulatePhysicsStep();
 3203        }
 204        finally
 205        {
 4206            _context.ExitSimulationPhase();
 4207        }
 4208    }
 209
 210    internal bool BeginLateSimulateBodies(bool continuousCollisionFramePrepared)
 211    {
 1022212        if (!SimulatePhysics)
 1213            return false;
 214
 1021215        if (!continuousCollisionFramePrepared)
 17216            _context.AdvanceLateSimulateToken();
 217
 1021218        PrepareContinuousCollisionFrame();
 1021219        BeginContinuousCollisionHandoffFrame();
 220
 6645221        foreach (SolidBody2D body in _dynamicBodies)
 222        {
 2302223            body.LateSimulate(updateSleepState: false, updateColliderState: false);
 2301224            _processedContinuousCollisionBodies.Add(body);
 225        }
 226
 1020227        return true;
 228    }
 229
 230    internal void CompleteLateSimulatePhysicsStep()
 231    {
 1003232        PrepareCollisionPartitions();
 1003233        RunDiscreteCollisionStep();
 1003234        UpdateSleepStatesAfterPhysicsStep();
 1003235    }
 236
 237    private void PrepareCollisionPartitions()
 238    {
 6548239        foreach (SolidBody2D body in _dynamicBodies)
 2271240            body.Collider.Simulate();
 241
 4128242        for (int i = 0; i < _serviceRefreshColliders.Count; i++)
 1061243            _serviceRefreshColliders[i].Simulate();
 1003244    }
 245
 246    private void RunDiscreteCollisionStep()
 247    {
 1003248        LastBroadPhaseCandidateCount = 0;
 1003249        EnsureFrameCapacity();
 1003250        _processedPairKeys.Clear();
 1003251        _discreteResponsePairs.FastClear();
 1003252        int frame = _context.FrameCount;
 1003253        _context.Collisions2D.CheckAndDistributeCollisions();
 1003254        ExpandDiscreteResponsePairs(frame);
 1003255        SolveDiscreteResponsePairs();
 1003256        RefreshGroundingFromDiscreteResponse(frame);
 1003257        CleanupUntouchedPairs(frame);
 1003258        _context.Collisions2D.RetireExpiredRetainedPartitions();
 1003259    }
 260
 261    private void UpdateSleepStatesAfterPhysicsStep()
 262    {
 6542263        foreach (SolidBody2D body in _dynamicBodies)
 2268264            body.UpdateSleepStateAfterPhysicsStep();
 1003265    }
 266
 267    /// <summary>Publishes interpolated presentation state for registered 2D bodies.</summary>
 268    public void Visualize()
 269    {
 26270        foreach (SolidBody2D body in _dynamicBodies)
 6271            body.OnVisualize();
 7272    }
 273
 274    /// <summary>Clears all context-local pure 2D registration and solver state.</summary>
 275    public void Reset()
 276    {
 33277        DiscardContinuousCollisionHandoffQueue();
 33278        _dynamicBodies.Clear();
 33279        _colliders.Clear();
 33280        _serviceRefreshColliders.FastClear();
 33281        _processedPairKeys.Clear();
 33282        _pairs.Clear();
 33283        _pairsToRemove.FastClear();
 33284        _pairsPendingSeparation.FastClear();
 33285        _cachedPairs.Clear();
 33286        _discreteResponsePairs.FastClear();
 33287        _existingResponsePairCandidates.FastClear();
 33288        _discreteResponseBodyKeys.Clear();
 33289        _discreteResponseBodyQueue.FastClear();
 33290        _discreteIslandNodes.FastClear();
 33291        _discreteIslandConstraints.FastClear();
 33292        _planarContinuousCollisionCandidates.Clear();
 33293        _dirtyPlanarContinuousCollisionCandidates.Clear();
 33294        _dirtyPlanarContinuousCollisionBodies.FastClear();
 33295        _dirtyPlanarContinuousCollisionBodySet.Clear();
 33296        _mixedContinuousCollisionCandidates.Clear();
 33297        _dirtyMixedContinuousCollisionCandidates.Clear();
 33298        _continuousCollisionCandidateLifetimes.Clear();
 33299        _continuousCollisionCandidateIds.FastClear();
 33300        _dirtyContinuousCollisionCandidateIds.FastClear();
 33301        _processedContinuousCollisionBodies.Clear();
 33302        _continuousCollisionPreparedToken = int.MinValue;
 33303        _continuousCollisionPreparedMixedIndex = false;
 33304        BodyCount = 0;
 33305        LastBroadPhaseCandidateCount = 0;
 33306        LastContinuousCollisionIslandCount = 0;
 33307        LastContinuousCollisionIslandIterationCount = 0;
 33308        LastContinuousCollisionIslandLimitReached = false;
 33309    }
 310
 311    internal bool TryGetColliderById(int colliderId, out LSCollider2D? collider) =>
 421050312        _colliders.TryGetById(colliderId, out collider);
 313
 314    internal bool TryGetColliderByServiceIndex(int serviceIndex, out LSCollider2D? collider) =>
 2315        _colliders.TryGetByServiceIndex(serviceIndex, out collider);
 316
 1219317    internal LSCollider2D GetColliderByServiceIndex(int serviceIndex) => _colliders[serviceIndex];
 318
 1124319    internal SwiftList<LSCollider2D> PrepareReplayColliders() => _colliders.PrepareReplayColliders();
 320
 321    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 322    internal bool TryGetDynamicBody(int dynamicId, out SolidBody2D body) =>
 473323        _dynamicBodies.TryGetValue(dynamicId, out body);
 324
 325    internal void RefreshColliderServiceRefreshRegistration(LSCollider2D collider)
 326    {
 5240327        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 328
 5240329        if (collider.RequiresServiceSideRefresh)
 1884330            AddServiceRefreshCollider(collider);
 331        else
 3356332            RemoveServiceRefreshCollider(collider);
 3356333    }
 334
 335    private void AddServiceRefreshCollider(LSCollider2D collider)
 336    {
 1884337        collider.SetServiceRefreshIndex(_serviceRefreshColliders.Count);
 1884338        _serviceRefreshColliders.Add(collider);
 1884339    }
 340
 341    private void RemoveServiceRefreshCollider(LSCollider2D collider)
 342    {
 3620343        int index = collider.ServiceRefreshIndex;
 3620344        if (index < 0 || !ReferenceEquals(_serviceRefreshColliders[index], collider))
 345        {
 3449346            collider.ClearServiceRefreshIndex();
 3449347            return;
 348        }
 349
 171350        int lastIndex = _serviceRefreshColliders.Count - 1;
 171351        if (index != lastIndex)
 352        {
 12353            LSCollider2D moved = _serviceRefreshColliders[lastIndex];
 12354            _serviceRefreshColliders[index] = moved;
 12355            moved.SetServiceRefreshIndex(index);
 356        }
 357
 171358        _serviceRefreshColliders.RemoveAt(lastIndex);
 171359        collider.ClearServiceRefreshIndex();
 171360    }
 361
 362    private void EnsureFrameCapacity()
 363    {
 1003364        int colliderCount = _colliders.Count;
 1003365        if (colliderCount <= 0)
 16366            return;
 367
 987368        int expectedPairKeyCapacity = System.Math.Max(colliderCount * 4, _pairs.Count);
 987369        _processedPairKeys.EnsureCapacity(expectedPairKeyCapacity);
 987370        _pairsToRemove.EnsureCapacity(expectedPairKeyCapacity);
 987371        _pairsPendingSeparation.EnsureCapacity(expectedPairKeyCapacity);
 987372        _discreteResponsePairs.EnsureCapacity(expectedPairKeyCapacity);
 987373        _existingResponsePairCandidates.EnsureCapacity(expectedPairKeyCapacity);
 987374        _discreteResponseBodyKeys.EnsureCapacity(colliderCount);
 987375        _discreteResponseBodyQueue.EnsureCapacity(colliderCount);
 987376        _discreteIslandNodes.EnsureCapacity(colliderCount);
 987377        _discreteIslandConstraints.EnsureCapacity(expectedPairKeyCapacity);
 987378        if (_context.Settings.PoolingEnabled)
 981379            _cachedPairs.EnsureCapacity(colliderCount);
 987380    }
 381
 382}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.Grounding.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.Grounding.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;
 10
 11namespace Gravitas;
 12
 13public sealed partial class GravitasPhysics2DService
 14{
 508315    private readonly SwiftCollections.SwiftList<GroundingBodySnapshotEntry> _groundingBodySnapshot = new();
 508316    private readonly SwiftCollections.SwiftList<GroundingPairSnapshotEntry> _groundingPairSnapshot = new();
 17
 18    private readonly struct GroundingBodySnapshotEntry
 19    {
 20        private readonly ColliderLifetimeToken2D _registration;
 21
 22        internal GroundingBodySnapshotEntry(SolidBody2D body)
 23        {
 227024            Body = body;
 227025            _registration = new ColliderLifetimeToken2D(body.Collider);
 227026        }
 27
 28        internal SolidBody2D Body { get; }
 29
 454030        internal bool IsActive => _registration.IsActive;
 31    }
 32
 33    private readonly struct GroundingPairSnapshotEntry
 34    {
 35        private readonly long _lifetimeVersion;
 36
 37        internal GroundingPairSnapshotEntry(CollisionPair2D pair)
 38        {
 49439            Pair = pair;
 49440            _lifetimeVersion = pair.LifetimeVersion;
 49441        }
 42
 43        internal CollisionPair2D Pair { get; }
 44
 49445        internal bool IsCurrentLifetime => Pair.LifetimeVersion == _lifetimeVersion;
 46    }
 47
 48    private void RefreshGroundingFromDiscreteResponse(int frame)
 49    {
 100350        int snapshotStart = _groundingBodySnapshot.Count;
 100351        _groundingBodySnapshot.EnsureCapacity(snapshotStart + _dynamicBodies.Count);
 654652        foreach (SolidBody2D body in _dynamicBodies)
 227053            _groundingBodySnapshot.Add(new GroundingBodySnapshotEntry(body));
 100354        int snapshotEnd = _groundingBodySnapshot.Count;
 55
 100356        int pairSnapshotStart = _groundingPairSnapshot.Count;
 100357        _groundingPairSnapshot.EnsureCapacity(pairSnapshotStart + _discreteResponsePairs.Count);
 299458        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 49459            _groundingPairSnapshot.Add(new GroundingPairSnapshotEntry(_discreteResponsePairs[i]));
 100360        int pairSnapshotEnd = _groundingPairSnapshot.Count;
 61
 62        try
 63        {
 654664            for (int i = snapshotStart; i < snapshotEnd; i++)
 65            {
 227066                GroundingBodySnapshotEntry registration = _groundingBodySnapshot[i];
 227067                if (!registration.IsActive)
 68                    continue;
 69
 227070                registration.Body.BeginAutomaticGroundingRefresh();
 71            }
 72
 299473            for (int i = pairSnapshotStart; i < pairSnapshotEnd; i++)
 74            {
 49475                GroundingPairSnapshotEntry registration = _groundingPairSnapshot[i];
 49476                if (!registration.IsCurrentLifetime)
 77                    continue;
 78
 49479                CollisionPair2D pair = registration.Pair;
 49480                if (!ShouldUseDiscreteGroundingPair(pair, frame))
 81                    continue;
 82
 49283                ContactManifold2D manifold = pair.Manifold;
 49284                if (!ShouldUseDiscreteGroundingManifold(manifold, frame))
 85                    continue;
 86
 49287                SolidBody2D? bodyA = pair.ColliderA.Body;
 49288                SolidBody2D? bodyB = pair.ColliderB.Body;
 197689                for (int contactIndex = 0; contactIndex < manifold.Count; contactIndex++)
 90                {
 49691                    ManifoldContact2D contact = manifold[contactIndex];
 49692                    bodyA?.TryAcceptContactGroundCandidate(pair.ColliderA, pair.ColliderB, contact, ownColliderIsA: true
 49693                    bodyB?.TryAcceptContactGroundCandidate(pair.ColliderB, pair.ColliderA, contact, ownColliderIsA: fals
 94                }
 95            }
 96
 654697            for (int i = snapshotStart; i < snapshotEnd; i++)
 98            {
 227099                GroundingBodySnapshotEntry registration = _groundingBodySnapshot[i];
 2270100                if (!registration.IsActive)
 101                    continue;
 102
 2269103                registration.Body.CompleteAutomaticGroundingRefresh();
 104            }
 1003105        }
 106        finally
 107        {
 1003108            if (pairSnapshotStart == 0)
 109            {
 1001110                _groundingPairSnapshot.FastClear();
 111            }
 112            else
 113            {
 5114                while (_groundingPairSnapshot.Count > pairSnapshotStart)
 3115                    _groundingPairSnapshot.RemoveAt(_groundingPairSnapshot.Count - 1);
 116            }
 117
 1003118            if (snapshotStart == 0)
 119            {
 1001120                _groundingBodySnapshot.FastClear();
 121            }
 122            else
 123            {
 7124                while (_groundingBodySnapshot.Count > snapshotStart)
 5125                    _groundingBodySnapshot.RemoveAt(_groundingBodySnapshot.Count - 1);
 126            }
 1003127        }
 1003128    }
 129
 130    internal static bool ShouldUseDiscreteGroundingPair(CollisionPair2D pair, int frame) =>
 498131        pair.LastFrame == frame && !pair.ColliderA.IsTrigger && !pair.ColliderB.IsTrigger;
 132
 133    internal static bool ShouldUseDiscreteGroundingManifold(ContactManifold2D manifold, int frame) =>
 495134        manifold.HasContact && manifold.LastUpdatedFrame == frame;
 135}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.Pairs.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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 GridForge.Spatial;
 11using SwiftCollections;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public sealed partial class GravitasPhysics2DService
 17{
 18    internal void ProcessPartitionCandidate(int firstId, int secondId, WorldVoxelIndex partitionIndex)
 19    {
 772820        if (!TryGetColliderById(firstId, out LSCollider2D? first)
 772821            || !TryGetColliderById(secondId, out LSCollider2D? second))
 22        {
 223            return;
 24        }
 25
 772626        if (!RequireCollisionPair(first!, second!)
 772627            || !CollisionDetection2D.BoundsOverlap(first!, second!)
 772628            || !IsCanonicalSharedPartition(first!, second!, partitionIndex))
 29        {
 719630            return;
 31        }
 32
 53033        ulong key = CreatePairKey(firstId, secondId);
 53034        if (!_processedPairKeys.Add(key))
 235            return;
 36
 52837        LastBroadPhaseCandidateCount++;
 52838        ProcessCandidate(first!, second!, _context.FrameCount);
 52839    }
 40
 41    internal static bool IsCanonicalSharedPartition(LSCollider2D first, LSCollider2D second, WorldVoxelIndex currentInde
 42    {
 286843        SwiftList<WorldVoxelIndex>? firstCoordinates = first.PartitionCoordinates;
 286844        SwiftList<WorldVoxelIndex>? secondCoordinates = second.PartitionCoordinates;
 286845        if (firstCoordinates == null || secondCoordinates == null)
 146            return true;
 47
 286748        if (!TryGetMinimumVoxelIndexForGrid(firstCoordinates, currentIndex, out VoxelIndex firstMin)
 286749            || !TryGetMinimumVoxelIndexForGrid(secondCoordinates, currentIndex, out VoxelIndex secondMin))
 50        {
 151            return true;
 52        }
 53
 286654        VoxelIndex current = currentIndex.VoxelIndex;
 286655        return current.x == Max(firstMin.x, secondMin.x)
 286656            && current.z == Max(firstMin.z, secondMin.z);
 57    }
 58
 59    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 427260    private static int Max(int first, int second) => first >= second ? first : second;
 61
 62    internal static bool TryGetMinimumVoxelIndexForGrid(
 63        SwiftList<WorldVoxelIndex> coordinates,
 64        WorldVoxelIndex gridIdentity,
 65        out VoxelIndex minimum)
 66    {
 573567        minimum = default;
 573568        bool found = false;
 12357669        for (int i = 0; i < coordinates.Count; i++)
 70        {
 5605371            WorldVoxelIndex candidate = coordinates[i];
 5605372            if (candidate.GridIndex != gridIdentity.GridIndex || candidate.GridSpawnToken != gridIdentity.GridSpawnToken
 73                continue;
 74
 5604175            VoxelIndex voxel = candidate.VoxelIndex;
 5604176            if (!found)
 77            {
 573378                minimum = voxel;
 573379                found = true;
 573380                continue;
 81            }
 82
 5030883            if (voxel.x < minimum.x)
 184                minimum.x = voxel.x;
 5030885            if (voxel.z < minimum.z)
 386                minimum.z = voxel.z;
 87        }
 88
 573589        return found;
 90    }
 91
 92    private void ProcessCandidate(LSCollider2D first, LSCollider2D second, int frame)
 93    {
 52894        ulong key = CreatePairKey(first.Id, second.Id);
 52895        bool hasPair = _pairs.TryGetValue(key, out CollisionPair2D pair);
 52896        bool hasAwakeMovableParticipant = HasAwakeMovableParticipant(first, second);
 97        // Partition distribution places a body-backed dynamic first; 2D triggers are bodyless static candidates.
 52898        bool triggerPair = second.IsTrigger;
 52899        if (!hasPair && !hasAwakeMovableParticipant && !triggerPair)
 2100            return;
 101
 526102        bool createdPair = false;
 526103        if (!hasPair)
 104        {
 261105            pair = CreatePair(first, second);
 261106            createdPair = true;
 107        }
 108
 526109        if (!CollisionDetection2D.TryCollide(pair!, pair!.Manifold, frame))
 110        {
 10111            if (createdPair)
 8112                RecyclePair(pair);
 10113            return;
 114        }
 115
 516116        if (triggerPair)
 117        {
 16118            if (createdPair)
 12119                RegisterPair(key, pair);
 120
 16121            pair.MarkColliding(frame);
 16122            return;
 123        }
 124
 500125        if (!hasAwakeMovableParticipant)
 126        {
 3127            pair.MarkResting(frame);
 3128            _discreteResponsePairs.Add(pair);
 3129            return;
 130        }
 131
 497132        if (createdPair)
 241133            RegisterPair(key, pair);
 134
 497135        pair.MarkCollidingDeferred(frame);
 497136        if (!IsCurrentResponsePair(key, pair))
 8137            return;
 138
 489139        _discreteResponsePairs.Add(pair);
 489140    }
 141
 142    private void RegisterPair(ulong key, CollisionPair2D pair)
 143    {
 253144        _pairs.Add(key, pair);
 253145        pair.ColliderA.TryAddCollisionPair(pair.Id2, pair);
 253146        pair.ColliderB.TryAddCollisionPairHolder(pair.Id1);
 253147    }
 148
 149    private void ExpandDiscreteResponsePairs(int frame)
 150    {
 1003151        if (_discreteResponsePairs.Count == 0)
 873152            return;
 153
 130154        _discreteResponseBodyKeys.Clear();
 130155        _discreteResponseBodyQueue.FastClear();
 156
 1244157        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 158        {
 492159            CollisionPair2D pair = _discreteResponsePairs[i];
 492160            AddDiscreteResponseBody(pair.ColliderA.Body);
 492161            AddDiscreteResponseBody(pair.ColliderB.Body);
 162        }
 163
 1206164        for (int readIndex = 0; readIndex < _discreteResponseBodyQueue.Count; readIndex++)
 165        {
 473166            int dynamicId = _discreteResponseBodyQueue[readIndex];
 473167            if (!TryGetDynamicBody(dynamicId, out SolidBody2D body))
 168                continue;
 169
 472170            AddExistingResponsePairs(body.Collider, frame);
 171        }
 130172    }
 173
 174    private void AddExistingResponsePairs(LSCollider2D collider, int frame)
 175    {
 472176        _existingResponsePairCandidates.FastClear();
 472177        SwiftDictionary<int, CollisionPair2D>? ownedPairs = collider.CollisionPairs;
 472178        if (ownedPairs != null)
 179        {
 1610180            foreach (var pairEntry in ownedPairs)
 411181                _existingResponsePairCandidates.Add(pairEntry.Value);
 182        }
 183
 472184        SwiftHashSet<int>? pairHolders = collider.CollisionPairHolders;
 472185        if (pairHolders != null)
 186        {
 382187            foreach (int holderId in pairHolders)
 188            {
 110189                if (!TryGetColliderById(holderId, out LSCollider2D? holder)
 110190                    || !holder!.TryGetCollisionPair(collider.Id, out CollisionPair2D? pair))
 191                {
 192                    continue;
 193                }
 194
 110195                _existingResponsePairCandidates.Add(pair!);
 196            }
 197        }
 198
 1986199        for (int i = 0; i < _existingResponsePairCandidates.Count; i++)
 521200            TryAddExistingResponsePair(_existingResponsePairCandidates[i], frame);
 472201    }
 202
 203    private void TryAddExistingResponsePair(CollisionPair2D pair, int frame)
 204    {
 521205        ulong key = CreatePairKey(pair.Id1, pair.Id2);
 521206        if (!_pairs.TryGetValue(key, out CollisionPair2D currentPair)
 521207            || !ReferenceEquals(currentPair, pair))
 208        {
 1209            return;
 210        }
 211
 520212        if (!_processedPairKeys.Add(key))
 513213            return;
 214
 7215        LSCollider2D first = pair.ColliderA;
 7216        LSCollider2D second = pair.ColliderB;
 7217        if (first.IsTrigger
 7218            || second.IsTrigger
 7219            || !RequireCollisionPair(first, second)
 7220            || !CollisionDetection2D.BoundsOverlap(first, second)
 7221            || !CollisionDetection2D.TryCollide(pair, pair.Manifold, frame))
 222        {
 4223            return;
 224        }
 225
 3226        if (HasAwakeMovableParticipant(first, second))
 2227            pair.MarkCollidingDeferred(frame);
 228        else
 1229            pair.MarkResting(frame);
 230
 3231        if (!IsCurrentResponsePair(key, pair))
 1232            return;
 233
 2234        _discreteResponsePairs.Add(pair);
 2235        AddDiscreteResponseBody(first.Body);
 2236        AddDiscreteResponseBody(second.Body);
 2237    }
 238
 239    private void AddDiscreteResponseBody(SolidBody2D? body)
 240    {
 988241        if (!IsMovableIslandBody(body) || !_discreteResponseBodyKeys.Add(body!.DynamicId))
 515242            return;
 243
 473244        _discreteResponseBodyQueue.Add(body.DynamicId);
 473245    }
 246
 247    private bool IsCurrentResponsePair(ulong key, CollisionPair2D pair) =>
 500248        _pairs.TryGetValue(key, out CollisionPair2D currentPair)
 500249        && ReferenceEquals(currentPair, pair)
 500250        && !pair.ColliderA.IsTrigger
 500251        && !pair.ColliderB.IsTrigger
 500252        && RequireCollisionPair(pair.ColliderA, pair.ColliderB);
 253
 254    internal bool RequireCollisionPair(LSCollider2D first, LSCollider2D second)
 255    {
 9203256        return first.IsActive && second.IsActive
 9203257            && first.Shape != ColliderType2D.None && second.Shape != ColliderType2D.None
 9203258            && (first.Body != null || second.Body != null)
 9203259            && !ReferenceEquals(first.AgentOrNull, second.AgentOrNull)
 9203260            && !IsLayerCollisionDisabled(first.Layer, second.Layer)
 9203261            && ColliderCollisionFilter.AllowsPhysicalPair(first, second)
 9203262            && !_context.Constraints2D.ShouldExcludeLinkedCollision(first, second)
 9203263            && !first.IsSibling(second);
 264    }
 265
 266    private void CleanupUntouchedPairs(int frame)
 267    {
 1003268        _pairsToRemove.FastClear();
 3032269        foreach (var pairEntry in _pairs)
 513270            _pairsToRemove.Add(pairEntry.Key);
 271
 3032272        for (int i = 0; i < _pairsToRemove.Count; i++)
 273        {
 513274            ulong key = _pairsToRemove[i];
 513275            if (!_pairs.TryGetValue(key, out CollisionPair2D pair))
 276                continue;
 277
 512278            if (pair.LastFrame == frame)
 279                continue;
 280
 14281            if (TryKeepUntouchedPair(pair, frame))
 282                continue;
 283
 10284            RemovePairReferences(pair);
 10285            _pairs.Remove(key);
 10286            pair.MarkSeparated();
 10287            RecyclePair(pair);
 288        }
 1003289    }
 290
 291    private bool TryKeepUntouchedPair(CollisionPair2D pair, int frame)
 292    {
 14293        LSCollider2D first = pair.ColliderA;
 14294        LSCollider2D second = pair.ColliderB;
 14295        bool triggerPair = first.IsTrigger || second.IsTrigger;
 14296        if ((!triggerPair && HasAwakeMovableParticipant(first, second))
 14297            || !RequireCollisionPair(first, second)
 14298            || !CollisionDetection2D.TryCollide(pair, pair.Manifold, frame))
 299        {
 10300            return false;
 301        }
 302
 4303        if (triggerPair)
 304        {
 3305            pair.MarkColliding(frame);
 3306            return true;
 307        }
 308
 1309        pair.MarkResting(frame);
 1310        return true;
 311    }
 312
 313    private CollisionPair2D CreatePair(LSCollider2D first, LSCollider2D second)
 314    {
 261315        if (_context.Settings.PoolingEnabled && _cachedPairs.Count > 0)
 316        {
 8317            CollisionPair2D pair = _cachedPairs.Pop();
 8318            pair.Initialize(first, second);
 8319            return pair;
 320        }
 321
 253322        return new CollisionPair2D(first, second);
 323    }
 324
 325    private void RecyclePair(CollisionPair2D pair)
 326    {
 82327        if (_context.Settings.PoolingEnabled && !pair.IsNotificationInProgress)
 70328            _cachedPairs.Push(pair);
 82329    }
 330
 331    private void RemovePairsForCollider(LSCollider2D collider)
 332    {
 264333        int separationStart = _pairsPendingSeparation.Count;
 264334        SwiftDictionary<int, CollisionPair2D>? collisionPairs = collider.CollisionPairs;
 264335        if (collisionPairs != null)
 336        {
 188337            foreach (var pairEntry in collisionPairs)
 47338                _pairsPendingSeparation.Add(pairEntry.Value);
 339        }
 340
 264341        SwiftHashSet<int>? collisionPairHolders = collider.CollisionPairHolders;
 264342        if (collisionPairHolders != null)
 343        {
 76344            foreach (int holderId in collisionPairHolders)
 345            {
 18346                if (!TryGetColliderById(holderId, out LSCollider2D? holder)
 18347                    || !holder!.TryGetCollisionPair(collider.Id, out CollisionPair2D? pair))
 348                {
 349                    continue;
 350                }
 351
 18352                _pairsPendingSeparation.Add(pair!);
 353            }
 354        }
 355
 264356        int separationEnd = _pairsPendingSeparation.Count;
 658357        for (int i = separationStart; i < separationEnd; i++)
 358        {
 65359            CollisionPair2D pair = _pairsPendingSeparation[i];
 65360            ulong key = CreatePairKey(pair.Id1, pair.Id2);
 65361            if (!_pairs.ContainsKey(key))
 362                continue;
 363
 64364            RemovePairReferences(pair);
 64365            _pairs.Remove(key);
 64366            pair.MarkSeparated();
 64367            RecyclePair(pair);
 368        }
 369
 264370        collider.ClearCollisionPairState();
 264371        collider.ClearRuntimeRelationships();
 372
 264373        if (separationStart == 0)
 263374            _pairsPendingSeparation.FastClear();
 264375    }
 376
 377    internal void ClearWarmStartCachesForCollider(LSCollider2D collider)
 378    {
 78379        SwiftDictionary<int, CollisionPair2D>? collisionPairs = collider.CollisionPairs;
 78380        if (collisionPairs != null)
 381        {
 4382            foreach (var pairEntry in collisionPairs)
 1383                pairEntry.Value.ClearWarmStart();
 384        }
 385
 78386        SwiftHashSet<int>? collisionPairHolders = collider.CollisionPairHolders;
 78387        if (collisionPairHolders == null)
 77388            return;
 389
 4390        foreach (int holderId in collisionPairHolders)
 391        {
 1392            if (TryGetColliderById(holderId, out LSCollider2D? holder)
 1393                && holder!.TryGetCollisionPair(collider.Id, out CollisionPair2D? pair))
 394            {
 1395                pair!.ClearWarmStart();
 396            }
 397        }
 1398    }
 399}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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 SwiftCollections;
 11
 12namespace Gravitas;
 13
 14public sealed partial class GravitasPhysics2DService
 15{
 16    internal void ContributeReplayHash(
 17        ref ChronicleHashWriter writer,
 18        GravitasReplayHashMode mode)
 19    {
 46820        SwiftList<LSCollider2D> replayColliders = PrepareReplayColliders();
 21
 46822        writer.WriteSection("physics.2d", 2);
 46823        writer.WriteBool(SimulatePhysics);
 46824        writer.WriteInt32(BodyCount);
 46825        writer.WriteInt32(ColliderCount);
 46826        writer.WriteInt32(_dynamicBodies.PeakCount);
 46827        writer.WriteInt32(replayColliders.Count);
 28
 247229        for (int i = 0; i < replayColliders.Count; i++)
 30        {
 76831            LSCollider2D collider = replayColliders[i];
 76832            collider.ContributeReplayHash(ref writer, mode);
 76833            if (collider.Body == null)
 15834                writer.WriteBool(false);
 35            else
 36            {
 61037                writer.WriteBool(true);
 61038                collider.Body.ContributeReplayHash(ref writer, mode);
 39            }
 40        }
 41
 46842        writer.WriteSection("physics.2d.pairs", 2);
 247243        for (int i = 0; i < replayColliders.Count; i++)
 44        {
 76845            LSCollider2D collider = replayColliders[i];
 46
 218047            for (int j = i + 1; j < replayColliders.Count; j++)
 48            {
 32249                LSCollider2D other = replayColliders[j];
 32250                if (!TryGetReplayHashPair(collider, other, out CollisionPair2D? pair))
 51                {
 52                    continue;
 53                }
 54
 355                pair!.ContributeReplayHash(ref writer, mode);
 56            }
 57        }
 58
 46859        if (mode != GravitasReplayHashMode.AuthoritativeWithSolverCaches)
 32960            return;
 61
 13962        writer.WriteSection("physics.2d.caches", 3);
 13963        writer.WriteInt32(_colliders.PeakCount);
 13964        writer.WriteInt32(_processedPairKeys.Count);
 13965        writer.WriteInt32(_pairs.Count);
 13966        writer.WriteInt32(_pairsToRemove.Count);
 13967        writer.WriteInt32(_cachedPairs.Count);
 13968        writer.WriteInt32(_discreteResponsePairs.Count);
 13969        writer.WriteInt32(_continuousCollisionPreparedToken);
 13970        writer.WriteBool(_continuousCollisionPreparedMixedIndex);
 13971        writer.WriteInt32(_planarContinuousCollisionCandidates.Count);
 13972        writer.WriteInt32(_dirtyPlanarContinuousCollisionCandidates.Count);
 13973        writer.WriteInt32(_dirtyPlanarContinuousCollisionBodies.Count);
 31874        for (int i = 0; i < _dirtyPlanarContinuousCollisionBodies.Count; i++)
 2075            writer.WriteInt32(_dirtyPlanarContinuousCollisionBodies[i].Collider.ReplayOrdinal);
 13976        writer.WriteInt32(_mixedContinuousCollisionCandidates.Count);
 13977        writer.WriteInt32(_dirtyMixedContinuousCollisionCandidates.Count);
 13978        writer.WriteInt32(_processedContinuousCollisionBodies.Count);
 13979        writer.WriteInt32(_queuedContinuousCollisionHandoffBodies.Count);
 13980        writer.WriteInt32(_continuousCollisionHandoffQueue.Count);
 29481        for (int i = 0; i < _continuousCollisionHandoffQueue.Count; i++)
 882            writer.WriteInt32(_continuousCollisionHandoffQueue[i].DynamicId);
 13983        writer.WriteInt32(LastBroadPhaseCandidateCount);
 13984        writer.WriteInt32(LastContinuousCollisionIslandCount);
 13985        writer.WriteInt32(LastContinuousCollisionIslandIterationCount);
 13986        writer.WriteBool(LastContinuousCollisionIslandLimitReached);
 13987    }
 88
 89    private static bool TryGetReplayHashPair(
 90        LSCollider2D first,
 91        LSCollider2D second,
 92        out CollisionPair2D? pair) =>
 32293        first.TryGetCollisionPair(second.Id, out pair)
 32294        || second.TryGetCollisionPair(first.Id, out pair);
 95}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.Response.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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 GravitasPhysics2DService
 14{
 15    private void SolveDiscreteResponsePairs()
 16    {
 100317        bool hasJoints = _context.Constraints2D.HasActiveJoints;
 100318        if (_discreteResponsePairs.Count == 0 && !hasJoints)
 71919            return;
 20
 28421        if (!hasJoints && _discreteResponsePairs.Count == 1)
 22        {
 7623            CollisionPair2D pair = _discreteResponsePairs[0];
 7624            if (!HasAwakeResponseParticipant(pair))
 125                return;
 26
 7527            pair.WakeSleepingBodiesForCollision();
 7528            CollisionResponse2D.Resolve(pair);
 7529            return;
 30        }
 31
 20832        if (_discreteResponsePairs.Count > 1)
 5233            _discreteResponsePairs.SortInPlace(ResponsePairComparer);
 20834        BuildDiscreteIslands();
 20835        if (_discreteIslandConstraints.Count == 0)
 136            return;
 37
 20738        _discreteIslandConstraints.SortInPlace(IslandConstraintComparer);
 39
 20740        int start = 0;
 73641        while (start < _discreteIslandConstraints.Count)
 42        {
 52943            int rootKey = _discreteIslandConstraints[start].RootKey;
 52944            int end = start + 1;
 61245            while (end < _discreteIslandConstraints.Count
 61246                && _discreteIslandConstraints[end].RootKey == rootKey)
 47            {
 8348                end++;
 49            }
 50
 52951            if (WakeIslandBodies(rootKey))
 52852                SolveDiscreteIslandRange(start, end);
 53
 52954            start = end;
 55        }
 20756    }
 57
 58    private void BuildDiscreteIslands()
 59    {
 20860        _discreteIslandNodes.FastClear();
 20861        _discreteIslandConstraints.FastClear();
 62
 125263        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 64        {
 41865            CollisionPair2D pair = _discreteResponsePairs[i];
 41866            AddIslandNodeIfMovable(pair.ColliderA.Body);
 41867            AddIslandNodeIfMovable(pair.ColliderB.Body);
 68        }
 69
 20870        AddJointIslandNodes();
 71
 20872        SortAndDeduplicateIslandNodes();
 20873        if (_discreteIslandNodes.Count == 0)
 174            return;
 75
 125076        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 77        {
 41878            CollisionPair2D pair = _discreteResponsePairs[i];
 41879            int nodeA = FindIslandNode(pair.ColliderA.Body);
 41880            int nodeB = FindIslandNode(pair.ColliderB.Body);
 41881            if (nodeA >= 0 && nodeB >= 0)
 782                UnionIslandNodes(nodeA, nodeB);
 83        }
 84
 20785        UnionJointIslandNodes();
 86
 20787        CompressIslandRoots();
 88
 125089        for (int i = 0; i < _discreteResponsePairs.Count; i++)
 90        {
 41891            CollisionPair2D pair = _discreteResponsePairs[i];
 41892            int nodeA = FindIslandNode(pair.ColliderA.Body);
 41893            int nodeB = FindIslandNode(pair.ColliderB.Body);
 41894            int rootKey = ResolveConstraintRootKey(nodeA, nodeB);
 41895            if (rootKey < 0)
 96                continue;
 97
 41798            GetStablePairKey(pair, out int minColliderId, out int maxColliderId);
 41799            _discreteIslandConstraints.Add(DiscreteIslandConstraint2D.CreatePair(
 417100                pair,
 417101                rootKey,
 417102                minColliderId,
 417103                maxColliderId));
 104        }
 105
 207106        AddJointIslandConstraints();
 207107    }
 108
 109    private void AddJointIslandNodes()
 110    {
 208111        GravitasConstraint2DService constraints = _context.Constraints2D;
 812112        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 113        {
 198114            if (!constraints.TryGetJointForSolver(jointId, out Joint2D? joint))
 115                continue;
 116
 195117            AddIslandNodeIfMovable(joint!.BodyA);
 195118            AddIslandNodeIfMovable(joint.BodyB);
 119        }
 208120    }
 121
 122    private void UnionJointIslandNodes()
 123    {
 207124        GravitasConstraint2DService constraints = _context.Constraints2D;
 808125        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 126        {
 197127            if (!constraints.TryGetJointForSolver(jointId, out Joint2D? joint))
 128                continue;
 129
 195130            int nodeA = FindIslandNode(joint!.BodyA);
 195131            int nodeB = FindIslandNode(joint.BodyB);
 195132            if (nodeA >= 0 && nodeB >= 0)
 161133                UnionIslandNodes(nodeA, nodeB);
 134        }
 207135    }
 136
 137    private void AddJointIslandConstraints()
 138    {
 207139        GravitasConstraint2DService constraints = _context.Constraints2D;
 808140        for (int jointId = 1; jointId <= constraints.PeakJointCount; jointId++)
 141        {
 197142            if (!constraints.TryGetJointForSolver(jointId, out Joint2D? joint))
 143                continue;
 144
 195145            int nodeA = FindIslandNode(joint!.BodyA);
 195146            int nodeB = FindIslandNode(joint.BodyB);
 195147            int rootKey = ResolveConstraintRootKey(nodeA, nodeB);
 148
 195149            GetStableJointKey(joint, out int minColliderId, out int maxColliderId);
 195150            _discreteIslandConstraints.Add(DiscreteIslandConstraint2D.CreateJoint(
 195151                joint,
 195152                rootKey,
 195153                minColliderId,
 195154                maxColliderId));
 155        }
 207156    }
 157
 158    private void AddIslandNodeIfMovable(SolidBody2D? body)
 159    {
 1226160        if (!IsMovableIslandBody(body))
 446161            return;
 162
 780163        _discreteIslandNodes.Add(new DiscreteIslandNode2D(body!.DynamicId, body));
 780164    }
 165
 166    private void SortAndDeduplicateIslandNodes() =>
 208167        IslandGraphUtility.SortAndDeduplicate(_discreteIslandNodes, IslandNodeComparer);
 168
 169    private int FindIslandNode(SolidBody2D? body)
 170    {
 2452171        if (!IsMovableIslandBody(body))
 892172            return -1;
 173
 1560174        return IslandGraphUtility.Find(_discreteIslandNodes, body!.DynamicId);
 175    }
 176
 177    private void UnionIslandNodes(int nodeA, int nodeB) =>
 168178        IslandGraphUtility.Union(_discreteIslandNodes, nodeA, nodeB);
 179
 180    private void CompressIslandRoots() =>
 207181        IslandGraphUtility.CompressRoots(_discreteIslandNodes);
 182
 183    private int ResolveConstraintRootKey(int nodeA, int nodeB) =>
 613184        IslandGraphUtility.ResolveConstraintRootKey(_discreteIslandNodes, nodeA, nodeB);
 185
 186    private bool WakeIslandBodies(int rootKey) =>
 529187        IslandGraphUtility.WakeBodies(_discreteIslandNodes, rootKey);
 188
 189    private void SolveDiscreteIslandRange(int start, int end)
 190    {
 528191        if (end - start == 1 && _discreteIslandConstraints[start].Kind == DiscreteIslandConstraint2DKind.Contact)
 192        {
 334193            CollisionResponse2D.Resolve(_discreteIslandConstraints[start].Pair);
 334194            return;
 195        }
 196
 194197        int iterations = _context.Settings.DiscreteSolverIterations;
 3308198        for (int iteration = 0; iteration < iterations; iteration++)
 199        {
 1460200            bool applyCachedImpulse = iteration == 0;
 1460201            bool applyPositionCorrection = iteration == 0;
 7004202            for (int i = start; i < end; i++)
 203            {
 2042204                DiscreteIslandConstraint2D constraint = _discreteIslandConstraints[i];
 2042205                if (constraint.Kind == DiscreteIslandConstraint2DKind.Contact)
 206                {
 502207                    CollisionResponse2D.Resolve(
 502208                        constraint.Pair,
 502209                        applyCachedImpulse,
 502210                        applyPositionCorrection);
 211                }
 212                else
 1540213                    JointSolver2D.Solve(constraint.Joint!, applyCachedImpulse);
 214            }
 215        }
 194216    }
 217
 218}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/GravitasPhysics2DService.SupportTypes.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasPhysics2DService.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.Colliders;
 9using Gravitas.Constraints;
 10using Gravitas.Support;
 11using System.Collections.Generic;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public sealed partial class GravitasPhysics2DService
 17{
 18    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 19    private static bool HasAwakeMovableParticipant(LSCollider2D first, LSCollider2D second) =>
 54020        IsAwakeMovable(first.Body) || IsAwakeMovable(second.Body);
 21
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23    private static bool HasAwakeResponseParticipant(CollisionPair2D pair) =>
 7624        IsAwakeIslandBody(pair.ColliderA.Body) || IsAwakeIslandBody(pair.ColliderB.Body);
 25
 26    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 27    private static bool IsAwakeMovable(SolidBody2D? body) =>
 55528        body != null && body.HasSolverMobility && !body.IsSleeping;
 29
 30    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 31    private static bool IsMovableIslandBody(SolidBody2D? body) =>
 476532        body != null && body.DynamicId >= 0 && body.HasSolverMobility;
 33
 34    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 35    private static bool IsAwakeIslandBody(SolidBody2D? body) =>
 9936        IsMovableIslandBody(body) && body!.IsAwakeForCollision;
 37
 38    private bool IsLayerCollisionDisabled(PhysicsLayer layer1, PhysicsLayer layer2)
 39    {
 882240        bool[,] matrix = _context.Settings.CollisionMatrix;
 882241        int layerIndex1 = layer1.Index;
 882242        int layerIndex2 = layer2.Index;
 882243        if (layerIndex1 >= matrix.GetLength(0) || layerIndex2 >= matrix.GetLength(1))
 783344            return false;
 45
 98946        return !matrix[layerIndex1, layerIndex2];
 47    }
 48
 49    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 50    private static void RemovePairReferences(CollisionPair2D pair)
 51    {
 7452        pair.ColliderA.TryRemoveCollisionPair(pair.Id2, out _);
 7453        pair.ColliderB.TryRemoveCollisionPairHolder(pair.Id1);
 7454    }
 55
 56    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 57    private static ulong CreatePairKey(int firstId, int secondId)
 58    {
 164459        if (firstId > secondId)
 12960            (firstId, secondId) = (secondId, firstId);
 61
 164462        return ((ulong)(uint)firstId << 32) | (uint)secondId;
 63    }
 64
 65    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 66    private static void GetStablePairKey(CollisionPair2D pair, out int minColliderId, out int maxColliderId)
 67    {
 423768        int idA = pair.ColliderA.Id;
 423769        int idB = pair.ColliderB.Id;
 423770        if (idA <= idB)
 71        {
 409972            minColliderId = idA;
 409973            maxColliderId = idB;
 409974            return;
 75        }
 76
 13877        minColliderId = idB;
 13878        maxColliderId = idA;
 13879    }
 80
 81    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 82    private static void GetStableJointKey(Joint2D joint, out int minColliderId, out int maxColliderId)
 83    {
 19584        int idA = joint.BodyA.Collider.Id;
 19585        int idB = joint.BodyB.Collider.Id;
 19586        if (idA <= idB)
 87        {
 19488            minColliderId = idA;
 19489            maxColliderId = idB;
 19490            return;
 91        }
 92
 193        minColliderId = idB;
 194        maxColliderId = idA;
 195    }
 96
 97    private sealed class CollisionPair2DStableKeyComparer : IComparer<CollisionPair2D>
 98    {
 99        public int Compare(CollisionPair2D? left, CollisionPair2D? right)
 100        {
 1910101            GetStablePairKey(left!, out int leftMin, out int leftMax);
 1910102            GetStablePairKey(right!, out int rightMin, out int rightMax);
 103
 1910104            int compare = leftMin.CompareTo(rightMin);
 1910105            return compare != 0 ? compare : leftMax.CompareTo(rightMax);
 106        }
 107    }
 108
 109    private sealed class DiscreteIslandConstraint2DComparer : IComparer<DiscreteIslandConstraint2D>
 110    {
 111        public int Compare(DiscreteIslandConstraint2D left, DiscreteIslandConstraint2D right)
 112        {
 994113            int compare = left.RootKey.CompareTo(right.RootKey);
 994114            if (compare != 0)
 894115                return compare;
 116
 100117            compare = left.MinColliderId.CompareTo(right.MinColliderId);
 100118            if (compare != 0)
 26119                return compare;
 120
 74121            compare = left.MaxColliderId.CompareTo(right.MaxColliderId);
 74122            if (compare != 0)
 40123                return compare;
 124
 34125            compare = left.Kind.CompareTo(right.Kind);
 34126            return compare != 0 ? compare : left.JointId.CompareTo(right.JointId);
 127        }
 128    }
 129
 130    private struct DiscreteIslandNode2D : IIslandNodeState
 131    {
 132        public DiscreteIslandNode2D(int bodyKey, SolidBody2D body)
 133        {
 780134            BodyKey = bodyKey;
 780135            Body = body;
 780136            ParentIndex = -1;
 780137            RootKey = bodyKey;
 780138        }
 139
 140        public int BodyKey { get; }
 141
 142        public SolidBody2D Body { get; }
 143
 144        public int ParentIndex { get; set; }
 145
 146        public int RootKey { get; set; }
 147
 535148        public bool IsAwakeForCollision => Body.IsAwakeForCollision;
 149
 678150        public void WakeFromCollision() => Body.WakeFromCollision();
 151    }
 152
 153    private readonly struct DiscreteIslandConstraint2D
 154    {
 155        private DiscreteIslandConstraint2D(
 156            DiscreteIslandConstraint2DKind kind,
 157            CollisionPair2D pair,
 158            Joint2D? joint,
 159            int rootKey,
 160            int minColliderId,
 161            int maxColliderId,
 162            int jointId)
 163        {
 612164            Kind = kind;
 612165            Pair = pair;
 612166            Joint = joint;
 612167            RootKey = rootKey;
 612168            MinColliderId = minColliderId;
 612169            MaxColliderId = maxColliderId;
 612170            JointId = jointId;
 612171        }
 172
 173        public static DiscreteIslandConstraint2D CreatePair(
 174            CollisionPair2D pair,
 175            int rootKey,
 176            int minColliderId,
 177            int maxColliderId) =>
 417178            new(DiscreteIslandConstraint2DKind.Contact, pair, null, rootKey, minColliderId, maxColliderId, 0);
 179
 180        public static DiscreteIslandConstraint2D CreateJoint(
 181            Joint2D joint,
 182            int rootKey,
 183            int minColliderId,
 184            int maxColliderId) =>
 195185            new(DiscreteIslandConstraint2DKind.Joint, null!, joint, rootKey, minColliderId, maxColliderId, joint.Id);
 186
 187        public DiscreteIslandConstraint2DKind Kind { get; }
 188        public CollisionPair2D Pair { get; }
 189        public Joint2D? Joint { get; }
 190        public int RootKey { get; }
 191        public int MinColliderId { get; }
 192        public int MaxColliderId { get; }
 193        public int JointId { get; }
 194    }
 195
 196    private enum DiscreteIslandConstraint2DKind : byte
 197    {
 198        Contact = 0,
 199        Joint = 1
 200    }
 201}

Methods/Properties

PrepareContinuousCollisionFrame()
AddContinuousCollisionCandidate(Gravitas.SolidBody2D,System.Boolean)
QueryPlanarContinuousCollisionCandidates(Gravitas.CollisionHandling.DynamicCcdPlanarBounds)
RefreshContinuousCollisionCandidate(Gravitas.SolidBody2D)
CanAdmitContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D)
TryReserveContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D)
ReleaseContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D)
ReleaseContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D,System.Int32)
InvalidateContinuousCollisionStateForMotionTypeChange(Gravitas.SolidBody2D,System.Int32)
CanAdmitContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D,Gravitas.SolidBody2D)
TryReserveContinuousCollisionCandidateRefresh(Gravitas.SolidBody2D,Gravitas.SolidBody2D)
QueryMixedContinuousCollisionCandidates(SwiftCollections.Query.FixedBoundVolume)
MergeContinuousCollisionCandidateIds()
TryGetContinuousCollisionCandidate(System.Int32,Gravitas.SolidBody2D&)
GetContinuousCollisionCandidate(System.Int32)
QueueContinuousCollisionHandoff(Gravitas.SolidBody2D)
ProcessQueuedContinuousCollisionHandoffs()
ReportContinuousCollisionIterationLimit()
ProcessQueuedContinuousCollisionHandoffs(System.Int32)
BeginContinuousCollisionHandoffFrame()
AbortContinuousCollisionHandoffFrame()
ClearContinuousCollisionHandoffQueue()
DiscardContinuousCollisionHandoffQueue()
.cctor()
.ctor(Gravitas.GravitasWorldContext)
get_ColliderCount()
AssimilateBody(Gravitas.SolidBody2D,Gravitas.BodyMotionType)
RefreshBodyMotionTypeRegistration(Gravitas.SolidBody2D,Gravitas.BodyMotionType)
AssimilateCollider(Gravitas.Colliders.LSCollider2D)
DessimilateBody(Gravitas.SolidBody2D)
DessimilateCollider(Gravitas.Colliders.LSCollider2D)
Simulate()
LateSimulate()
BeginLateSimulateBodies(System.Boolean)
CompleteLateSimulatePhysicsStep()
PrepareCollisionPartitions()
RunDiscreteCollisionStep()
UpdateSleepStatesAfterPhysicsStep()
Visualize()
Reset()
TryGetColliderById(System.Int32,Gravitas.Colliders.LSCollider2D&)
TryGetColliderByServiceIndex(System.Int32,Gravitas.Colliders.LSCollider2D&)
GetColliderByServiceIndex(System.Int32)
PrepareReplayColliders()
TryGetDynamicBody(System.Int32,Gravitas.SolidBody2D&)
RefreshColliderServiceRefreshRegistration(Gravitas.Colliders.LSCollider2D)
AddServiceRefreshCollider(Gravitas.Colliders.LSCollider2D)
RemoveServiceRefreshCollider(Gravitas.Colliders.LSCollider2D)
EnsureFrameCapacity()
.ctor(Gravitas.GravitasWorldContext)
.ctor(Gravitas.SolidBody2D)
get_IsActive()
.ctor(Gravitas.CollisionPair2D)
get_IsCurrentLifetime()
RefreshGroundingFromDiscreteResponse(System.Int32)
ShouldUseDiscreteGroundingPair(Gravitas.CollisionPair2D,System.Int32)
ShouldUseDiscreteGroundingManifold(Gravitas.CollisionHandling.ContactManifold2D,System.Int32)
ProcessPartitionCandidate(System.Int32,System.Int32,GridForge.Spatial.WorldVoxelIndex)
IsCanonicalSharedPartition(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,GridForge.Spatial.WorldVoxelIndex)
Max(System.Int32,System.Int32)
TryGetMinimumVoxelIndexForGrid(SwiftCollections.SwiftList`1<GridForge.Spatial.WorldVoxelIndex>,GridForge.Spatial.WorldVoxelIndex,GridForge.Spatial.VoxelIndex&)
ProcessCandidate(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,System.Int32)
RegisterPair(System.UInt64,Gravitas.CollisionPair2D)
ExpandDiscreteResponsePairs(System.Int32)
AddExistingResponsePairs(Gravitas.Colliders.LSCollider2D,System.Int32)
TryAddExistingResponsePair(Gravitas.CollisionPair2D,System.Int32)
AddDiscreteResponseBody(Gravitas.SolidBody2D)
IsCurrentResponsePair(System.UInt64,Gravitas.CollisionPair2D)
RequireCollisionPair(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
CleanupUntouchedPairs(System.Int32)
TryKeepUntouchedPair(Gravitas.CollisionPair2D,System.Int32)
CreatePair(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
RecyclePair(Gravitas.CollisionPair2D)
RemovePairsForCollider(Gravitas.Colliders.LSCollider2D)
ClearWarmStartCachesForCollider(Gravitas.Colliders.LSCollider2D)
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
TryGetReplayHashPair(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionPair2D&)
SolveDiscreteResponsePairs()
BuildDiscreteIslands()
AddJointIslandNodes()
UnionJointIslandNodes()
AddJointIslandConstraints()
AddIslandNodeIfMovable(Gravitas.SolidBody2D)
SortAndDeduplicateIslandNodes()
FindIslandNode(Gravitas.SolidBody2D)
UnionIslandNodes(System.Int32,System.Int32)
CompressIslandRoots()
ResolveConstraintRootKey(System.Int32,System.Int32)
WakeIslandBodies(System.Int32)
SolveDiscreteIslandRange(System.Int32,System.Int32)
HasAwakeMovableParticipant(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
HasAwakeResponseParticipant(Gravitas.CollisionPair2D)
IsAwakeMovable(Gravitas.SolidBody2D)
IsMovableIslandBody(Gravitas.SolidBody2D)
IsAwakeIslandBody(Gravitas.SolidBody2D)
IsLayerCollisionDisabled(Gravitas.Support.PhysicsLayer,Gravitas.Support.PhysicsLayer)
RemovePairReferences(Gravitas.CollisionPair2D)
CreatePairKey(System.Int32,System.Int32)
GetStablePairKey(Gravitas.CollisionPair2D,System.Int32&,System.Int32&)
GetStableJointKey(Gravitas.Constraints.Joint2D,System.Int32&,System.Int32&)
Compare(Gravitas.CollisionPair2D,Gravitas.CollisionPair2D)
Compare(Gravitas.GravitasPhysics2DService/DiscreteIslandConstraint2D,Gravitas.GravitasPhysics2DService/DiscreteIslandConstraint2D)
.ctor(System.Int32,Gravitas.SolidBody2D)
get_IsAwakeForCollision()
WakeFromCollision()
.ctor(Gravitas.GravitasPhysics2DService/DiscreteIslandConstraint2DKind,Gravitas.CollisionPair2D,Gravitas.Constraints.Joint2D,System.Int32,System.Int32,System.Int32,System.Int32)
CreatePair(Gravitas.CollisionPair2D,System.Int32,System.Int32,System.Int32)
CreateJoint(Gravitas.Constraints.Joint2D,System.Int32,System.Int32,System.Int32)