< Summary

Information
Class: Gravitas.MixedPairLifetimeToken
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/Mixed/GravitasMixedCollisionService.Pairs.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 223
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsCurrentLifetime()100%44100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/Mixed/GravitasMixedCollisionService.Pairs.cs

#LineLine coverage
 1//=======================================================================
 2// GravitasMixedCollisionService.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas;
 13
 14internal readonly struct MixedPairLifetimeToken
 15{
 16    internal MixedPairLifetimeToken(CollisionPairMixed pair)
 17    {
 20818        Pair = pair;
 20819        LifetimeVersion = pair.LifetimeVersion;
 20820        Collider3D = new ColliderLifetimeToken(pair.Collider3D);
 20821        Collider2D = new ColliderLifetimeToken2D(pair.Collider2D);
 20822    }
 23
 24    internal CollisionPairMixed Pair { get; }
 25
 26    internal long LifetimeVersion { get; }
 27
 28    private ColliderLifetimeToken Collider3D { get; }
 29
 30    private ColliderLifetimeToken2D Collider2D { get; }
 31
 21232    internal bool IsCurrentLifetime => Pair.LifetimeVersion == LifetimeVersion
 21233        && Collider3D.IsCurrentLifetime
 21234        && Collider2D.IsCurrentLifetime;
 35}
 36
 37internal sealed partial class GravitasMixedCollisionService
 38{
 39    internal void RemovePairsFor3DCollider(LSCollider collider)
 40    {
 41        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 42        int removalStart = _pairsToRemove.Count;
 43        foreach (var pairEntry in _pairs)
 44        {
 45            CollisionPairMixed pair = pairEntry.Value;
 46            if (!ReferenceEquals(pair.Collider3D, collider))
 47                continue;
 48
 49            _pairsToRemove.Add(new MixedPairLifetimeToken(pair));
 50        }
 51
 52        RemoveQueuedPairs(removalStart, _pairsToRemove.Count);
 53    }
 54
 55    internal void RemovePairsFor2DCollider(LSCollider2D collider)
 56    {
 57        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 58        int removalStart = _pairsToRemove.Count;
 59        foreach (var pairEntry in _pairs)
 60        {
 61            CollisionPairMixed pair = pairEntry.Value;
 62            if (!ReferenceEquals(pair.Collider2D, collider))
 63                continue;
 64
 65            _pairsToRemove.Add(new MixedPairLifetimeToken(pair));
 66        }
 67
 68        RemoveQueuedPairs(removalStart, _pairsToRemove.Count);
 69    }
 70
 71    internal bool RequireCollisionPair(LSCollider collider3D, LSCollider2D collider2D)
 72    {
 73        return collider3D.IsActive && !collider3D.IsDeactivationInProgress && collider2D.IsActive
 74            && collider3D.Shape != ColliderType.None && collider2D.Shape != ColliderType2D.None
 75            && (collider3D.Body != null || collider2D.Body != null)
 76            && !ReferenceEquals(collider3D.AgentOrNull, collider2D.AgentOrNull)
 77            && !collider3D.ExcludesMixedCollisionWith(collider2D)
 78            && ColliderCollisionFilter.AllowsPhysicalPair(collider3D, collider2D)
 79            && !_context.Physics.IsLayerCollisionDisabled(collider3D.Layer, collider2D.Layer);
 80    }
 81
 82    private void ProcessCandidate(MixedColliderKey candidate, int frame)
 83    {
 84        if (!_context.Physics.TryGetColliderById(candidate.Collider3DId, out LSCollider? collider3D)
 85            || !_context.Physics2D.TryGetColliderById(candidate.Collider2DId, out LSCollider2D? collider2D))
 86        {
 87            return;
 88        }
 89
 90        LSCollider resolved3D = collider3D!;
 91        LSCollider2D resolved2D = collider2D!;
 92        bool hasPair = _pairs.TryGetValue(candidate.Key, out CollisionPairMixed pair);
 93        bool triggerPair = resolved3D.IsTrigger || resolved2D.IsTrigger;
 94        bool hasAwakeMovableParticipant = HasAwakeMovableParticipant(resolved3D, resolved2D);
 95        if (!hasPair && !triggerPair && !hasAwakeMovableParticipant)
 96            return;
 97
 98        if (!CollisionDetectionMixed.TryCollide(resolved3D, resolved2D, out MixedContact contact))
 99            return;
 100
 101        if (!triggerPair && !hasAwakeMovableParticipant)
 102        {
 103            pair!.MarkResting(frame, contact);
 104            _mixedResponsePairs.Add(pair);
 105            return;
 106        }
 107
 108        if (!hasPair)
 109        {
 110            pair = CreatePair(resolved3D, resolved2D);
 111            _pairs.Add(candidate.Key, pair);
 112        }
 113
 114        pair!.MarkColliding(frame, contact);
 115        if (!triggerPair)
 116            _mixedResponsePairs.Add(pair);
 117    }
 118
 119    private void CleanupUntouchedPairs(int frame)
 120    {
 121        int removalStart = _pairsToRemove.Count;
 122        foreach (var pairEntry in _pairs)
 123            _pairsToRemove.Add(new MixedPairLifetimeToken(pairEntry.Value));
 124
 125        int removalEnd = _pairsToRemove.Count;
 126        try
 127        {
 128            for (int i = removalStart; i < removalEnd; i++)
 129            {
 130                MixedPairLifetimeToken token = _pairsToRemove[i];
 131                CollisionPairMixed pair = token.Pair;
 132                if (!IsCurrentPair(token))
 133                    continue;
 134
 135                if (pair.LastFrame == frame)
 136                    continue;
 137
 138                if (TryKeepUntouchedPair(pair, frame))
 139                    continue;
 140
 141                RemoveCurrentPair(token);
 142            }
 143        }
 144        finally
 145        {
 146            if (removalStart == 0)
 147                _pairsToRemove.FastClear();
 148        }
 149    }
 150
 151    private bool TryKeepUntouchedPair(CollisionPairMixed pair, int frame)
 152    {
 153        LSCollider collider3D = pair.Collider3D;
 154        LSCollider2D collider2D = pair.Collider2D;
 155        bool triggerPair = collider3D.IsTrigger || collider2D.IsTrigger;
 156        if ((!triggerPair && HasAwakeMovableParticipant(collider3D, collider2D))
 157            || !RequireCollisionPair(collider3D, collider2D)
 158            || !MixedBoundsOverlap(collider3D, collider2D)
 159            || !CollisionDetectionMixed.TryCollide(collider3D, collider2D, out MixedContact contact))
 160        {
 161            return false;
 162        }
 163
 164        if (triggerPair)
 165        {
 166            pair.MarkColliding(frame, contact);
 167            return true;
 168        }
 169
 170        pair.MarkResting(frame);
 171        return true;
 172    }
 173
 174    private CollisionPairMixed CreatePair(LSCollider collider3D, LSCollider2D collider2D)
 175    {
 176        if (_context.Settings.PoolingEnabled && _cachedPairs.Count > 0)
 177        {
 178            CollisionPairMixed pair = _cachedPairs.Pop();
 179            pair.Initialize(collider3D, collider2D);
 180            return pair;
 181        }
 182
 183        return new CollisionPairMixed(collider3D, collider2D);
 184    }
 185
 186    private void RemoveQueuedPairs(int removalStart, int removalEnd)
 187    {
 188        try
 189        {
 190            for (int i = removalStart; i < removalEnd; i++)
 191                RemoveCurrentPair(_pairsToRemove[i]);
 192        }
 193        finally
 194        {
 195            if (removalStart == 0)
 196                _pairsToRemove.FastClear();
 197        }
 198    }
 199
 200    private bool IsCurrentPair(in MixedPairLifetimeToken token) =>
 201        token.IsCurrentLifetime
 202        && _pairs.TryGetValue(token.Pair.Key, out CollisionPairMixed currentPair)
 203        && ReferenceEquals(currentPair, token.Pair);
 204
 205    private void RemoveCurrentPair(in MixedPairLifetimeToken token)
 206    {
 207        if (!IsCurrentPair(token))
 208            return;
 209
 210        CollisionPairMixed pair = token.Pair;
 211        _pairs.Remove(pair.Key);
 212        pair.MarkSeparated();
 213        RecyclePair(pair);
 214    }
 215
 216    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 217    private void RecyclePair(CollisionPairMixed pair)
 218    {
 219        if (_context.Settings.PoolingEnabled && !pair.IsNotificationInProgress)
 220            _cachedPairs.Push(pair);
 221    }
 222
 223}