< Summary

Information
Class: Gravitas.Constraints.ConstraintEndpointJointIndex<T>
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Constraints/ConstraintEndpointJointIndex.cs
Line coverage
100%
Covered lines: 86
Uncovered lines: 0
Coverable lines: 86
Total lines: 183
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
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%
Add(...)100%11100%
Remove(...)100%11100%
TryGetLast(...)100%22100%
TryGetPrevious(...)100%11100%
Clear()100%11100%
Append(...)100%22100%
RemoveEndpoint(...)100%66100%
GetEndpoint(...)100%22100%
EnsureJointCapacity(...)100%44100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Constraints/ConstraintEndpointJointIndex.cs

#LineLine coverage
 1//=======================================================================
 2// ConstraintEndpointJointIndex.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 SwiftCollections;
 9using System;
 10
 11namespace Gravitas.Constraints;
 12
 13/// <summary>
 14/// Tracks registered joint IDs in stable endpoint order without scanning a context's peak joint range.
 15/// </summary>
 16internal sealed class ConstraintEndpointJointIndex<TBody>
 17    where TBody : class
 18{
 19    private const int DefaultJointCapacity = 64;
 20
 1016621    private readonly SwiftDictionary<TBody, EndpointChain> _chainsByBody = new();
 1016622    private JointEndpoints[] _endpointsByJoint = new JointEndpoints[DefaultJointCapacity];
 23
 24    internal void Add(TBody bodyA, TBody bodyB, int jointId)
 25    {
 50726        SwiftThrowHelper.ThrowIfTrue(
 50727            ReferenceEquals(bodyA, bodyB),
 50728            nameof(bodyB),
 50729            "A joint endpoint index requires two distinct bodies.");
 50730        EnsureJointCapacity(jointId + 1);
 50731        ref JointEndpoints endpoints = ref _endpointsByJoint[jointId];
 50732        SwiftThrowHelper.ThrowIfTrue(
 50733            endpoints.IsRegistered,
 50734            nameof(jointId),
 50735            "Joint endpoint ownership is already registered.");
 36
 50737        endpoints = new JointEndpoints(bodyA, bodyB);
 50738        Append(bodyA, jointId, ref endpoints.EndpointA);
 50739        Append(bodyB, jointId, ref endpoints.EndpointB);
 50740        endpoints.IsRegistered = true;
 50741    }
 42
 43    internal void Remove(int jointId)
 44    {
 5745        ref JointEndpoints endpoints = ref _endpointsByJoint[jointId];
 5746        RemoveEndpoint(jointId, ref endpoints.EndpointA);
 5747        RemoveEndpoint(jointId, ref endpoints.EndpointB);
 5748        endpoints = default;
 5749    }
 50
 51    internal bool TryGetLast(TBody body, out int jointId)
 52    {
 38153        if (_chainsByBody.TryGetValue(body, out EndpointChain chain))
 54        {
 5555            jointId = chain.LastJointId;
 5556            return true;
 57        }
 58
 32659        jointId = -1;
 32660        return false;
 61    }
 62
 63    internal bool TryGetPrevious(TBody body, int jointId, out int previousJointId)
 64    {
 3365        previousJointId = GetEndpoint(jointId, body).PreviousJointId;
 3366        return previousJointId != 0;
 67    }
 68
 69    internal void Clear()
 70    {
 1022871        _chainsByBody.Clear();
 1022872        Array.Clear(_endpointsByJoint, 0, _endpointsByJoint.Length);
 1022873    }
 74
 75    private void Append(TBody body, int jointId, ref EndpointLink endpoint)
 76    {
 101477        endpoint.Body = body;
 101478        if (!_chainsByBody.TryGetValue(body, out EndpointChain chain))
 79        {
 53880            _chainsByBody.Add(body, new EndpointChain(jointId, jointId, 1));
 53881            return;
 82        }
 83
 47684        endpoint.PreviousJointId = chain.LastJointId;
 47685        ref EndpointLink previous = ref GetEndpoint(chain.LastJointId, body);
 47686        previous.NextJointId = jointId;
 47687        chain.LastJointId = jointId;
 47688        chain.Count++;
 47689        _chainsByBody[body] = chain;
 47690    }
 91
 92    private void RemoveEndpoint(int jointId, ref EndpointLink endpoint)
 93    {
 11494        TBody body = endpoint.Body!;
 11495        SwiftThrowHelper.ThrowIfTrue(
 11496            !_chainsByBody.TryGetValue(body, out EndpointChain chain),
 11497            nameof(jointId),
 11498            "Joint endpoint chain is missing its body.");
 99
 114100        if (endpoint.PreviousJointId == 0)
 98101            chain.FirstJointId = endpoint.NextJointId;
 102        else
 16103            GetEndpoint(endpoint.PreviousJointId, body).NextJointId = endpoint.NextJointId;
 104
 114105        if (endpoint.NextJointId == 0)
 99106            chain.LastJointId = endpoint.PreviousJointId;
 107        else
 15108            GetEndpoint(endpoint.NextJointId, body).PreviousJointId = endpoint.PreviousJointId;
 109
 114110        chain.Count--;
 114111        if (chain.Count == 0)
 87112            _chainsByBody.Remove(body);
 113        else
 27114            _chainsByBody[body] = chain;
 27115    }
 116
 117    private ref EndpointLink GetEndpoint(int jointId, TBody body)
 118    {
 540119        ref JointEndpoints endpoints = ref _endpointsByJoint[jointId];
 540120        if (ReferenceEquals(endpoints.EndpointA.Body, body))
 237121            return ref endpoints.EndpointA;
 122
 303123        SwiftThrowHelper.ThrowIfTrue(
 303124            !ReferenceEquals(endpoints.EndpointB.Body, body),
 303125            nameof(body),
 303126            "Joint endpoint chain references an unrelated body.");
 303127        return ref endpoints.EndpointB;
 128    }
 129
 130    private void EnsureJointCapacity(int required)
 131    {
 507132        if (required <= _endpointsByJoint.Length)
 505133            return;
 134
 2135        int newSize = _endpointsByJoint.Length;
 4136        while (newSize < required)
 2137            newSize *= 2;
 138
 2139        Array.Resize(ref _endpointsByJoint, newSize);
 2140    }
 141
 142    private struct EndpointChain
 143    {
 144        internal EndpointChain(int firstJointId, int lastJointId, int count)
 145        {
 538146            FirstJointId = firstJointId;
 538147            LastJointId = lastJointId;
 538148            Count = count;
 538149        }
 150
 151        internal int FirstJointId;
 152        internal int LastJointId;
 153        internal int Count;
 154    }
 155
 156    private struct JointEndpoints
 157    {
 158        internal JointEndpoints(TBody bodyA, TBody bodyB)
 159        {
 507160            EndpointA = new EndpointLink(bodyA);
 507161            EndpointB = new EndpointLink(bodyB);
 507162            IsRegistered = false;
 507163        }
 164
 165        internal EndpointLink EndpointA;
 166        internal EndpointLink EndpointB;
 167        internal bool IsRegistered;
 168    }
 169
 170    private struct EndpointLink
 171    {
 172        internal EndpointLink(TBody body)
 173        {
 1014174            Body = body;
 1014175            PreviousJointId = 0;
 1014176            NextJointId = 0;
 1014177        }
 178
 179        internal TBody? Body;
 180        internal int PreviousJointId;
 181        internal int NextJointId;
 182    }
 183}