< Summary

Information
Class: Trailblazer.Navigation.MovementGroups.MovementGroupCoordinatorState
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/MovementGroups/MovementGroupCoordinatorState.cs
Line coverage
100%
Covered lines: 112
Uncovered lines: 0
Coverable lines: 112
Total lines: 265
Line coverage: 100%
Branch coverage
100%
Covered branches: 66
Total branches: 66
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_FrameCount()100%11100%
CacheOwner(...)100%66100%
Prewarm(...)100%22100%
UpdateTarget(...)100%1010100%
Remove(...)100%1212100%
IsNeighbor(...)100%88100%
Reset()100%11100%
GetOrCreateGroup(...)100%22100%
GetOrCreateMember(...)100%22100%
UpdateMemberState(...)100%22100%
UpdateMembership(...)100%66100%
TryGetFormationMetrics(...)100%66100%
UpdateFormationOffsets(...)100%88100%
IsEligibleGroupMember(...)100%22100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/MovementGroups/MovementGroupCoordinatorState.cs

#LineLine coverage
 1//=======================================================================
 2// MovementGroupCoordinatorState.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10using FixedMathSharp;
 11using SwiftCollections;
 12
 13namespace Trailblazer.Navigation.MovementGroups;
 14
 15/// <summary>
 16/// Tracks movement-group membership and resolves formation-preserving destinations for one world context.
 17/// </summary>
 18internal sealed class MovementGroupCoordinatorState
 19{
 20    private const int MinMovementGroupSize = 2;
 21
 22    private const int MovementGroupHistoryFrames = 1;
 23
 24    private readonly TrailblazerWorldContext _context;
 25
 98926    private readonly SwiftDictionary<int, MovementGroupState> _movementGroups = new();
 27
 98928    private readonly SwiftDictionary<Guid, MovementGroupMembership> _movementGroupMemberships = new();
 29
 98930    internal MovementGroupCoordinatorState(TrailblazerWorldContext context)
 31    {
 98932        _context = context;
 98933    }
 34
 13035    private int FrameCount => _context.FrameCount;
 36
 37    internal void CacheOwner(MovementGroupSession session, Guid ownerId)
 38    {
 3939        if (session.HasOwnerId && session.OwnerId == ownerId)
 440            return;
 41
 3542        if (session.HasOwnerId)
 143            _movementGroupMemberships.Remove(session.OwnerId);
 44
 3545        session.OwnerId = ownerId;
 3546        session.HasOwnerId = true;
 3547    }
 48
 49    internal void Prewarm(
 50        MovementGroupSession session,
 51        Guid ownerId,
 52        Vector3d requestedDestination,
 53        Vector3d position,
 54        Fixed64 radius)
 55    {
 2656        if (session.GroupId < 0)
 157            return;
 58
 2559        CacheOwner(session, ownerId);
 2560        UpdateTarget(session, requestedDestination, position, radius);
 2561    }
 62
 63    internal MovementGroupTarget UpdateTarget(
 64        MovementGroupSession session,
 65        Vector3d requestedDestination,
 66        Vector3d position,
 67        Fixed64 radius,
 68        bool resetFormationOffset = false)
 69    {
 6670        if (session.GroupId < 0)
 171            return new(MovementGroupTravelMode.None, requestedDestination);
 72
 6573        MovementGroupState group = GetOrCreateGroup(session.GroupId);
 6574        MovementGroupMember self = GetOrCreateMember(session, group, ref resetFormationOffset);
 75
 6576        if (self.RequestedDestination != requestedDestination)
 3877            resetFormationOffset = true;
 78
 6579        UpdateMemberState(self, requestedDestination, position, radius, resetFormationOffset);
 6580        UpdateMembership(session, self, requestedDestination);
 81
 6582        int minFrame = FrameCount - MovementGroupHistoryFrames;
 6583        if (!TryGetFormationMetrics(group, requestedDestination, minFrame, out Vector3d groupCenter, out Fixed64 average
 4784            return new(MovementGroupTravelMode.Individual, requestedDestination);
 85
 1886        Fixed64 maxSpreadSq = UpdateFormationOffsets(group, requestedDestination, minFrame, groupCenter);
 1887        Fixed64 allowedSpreadSq = averageRadius * averageRadius * (Fixed64)(groupCount * 2);
 1888        Fixed64 distanceToSharedDestinationSq = (requestedDestination - groupCenter).MagnitudeSquared;
 1889        if (maxSpreadSq > allowedSpreadSq || distanceToSharedDestinationSq <= maxSpreadSq)
 590            return new(MovementGroupTravelMode.GroupIndividual, requestedDestination);
 91
 1392        return new(MovementGroupTravelMode.Formation, requestedDestination + self.FormationOffset);
 93    }
 94
 95    internal void Remove(MovementGroupSession session)
 96    {
 59397        if (session.GroupId < 0)
 57398            return;
 99
 20100        if (_movementGroups.TryGetValue(session.GroupId, out MovementGroupState group))
 101        {
 14102            if (group.Members.TryGetValue(session.GroupIndex, out MovementGroupMember member) && member.HasOccupantId)
 12103                _movementGroupMemberships.Remove(member.OccupantId);
 104
 14105            group.Members.TryRemoveAt(session.GroupIndex);
 14106            if (group.Members.Count == 0)
 11107                _movementGroups.Remove(session.GroupId);
 108        }
 6109        else if (session.HasOwnerId)
 110        {
 1111            _movementGroupMemberships.Remove(session.OwnerId);
 112        }
 113
 20114        session.GroupIndex = -1;
 20115    }
 116
 117    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118    internal bool IsNeighbor(
 119        MovementGroupSession session,
 120        Guid otherId,
 121        Vector3d requestedDestination,
 122        int currentFrame)
 123    {
 7992124        if (session.GroupId < 0 || !_movementGroupMemberships.TryGetValue(otherId, out MovementGroupMembership membershi
 7978125            return false;
 126
 14127        return membership.GroupId == session.GroupId
 14128            && membership.RequestedDestination == requestedDestination
 14129            && membership.LastSeenFrame >= currentFrame - MovementGroupHistoryFrames;
 130    }
 131
 132    internal void Reset()
 133    {
 44134        _movementGroups.Clear();
 44135        _movementGroupMemberships.Clear();
 44136    }
 137
 138    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 139    private MovementGroupState GetOrCreateGroup(int groupId)
 140    {
 65141        if (_movementGroups.TryGetValue(groupId, out MovementGroupState group))
 37142            return group;
 143
 28144        group = new();
 28145        _movementGroups[groupId] = group;
 28146        return group;
 147    }
 148
 149    private static MovementGroupMember GetOrCreateMember(
 150        MovementGroupSession session,
 151        MovementGroupState group,
 152        ref bool resetFormationOffset)
 153    {
 65154        if (group.Members.TryGetValue(session.GroupIndex, out MovementGroupMember self))
 28155            return self;
 156
 37157        self = new();
 37158        session.GroupIndex = group.Members.Add(self);
 37159        resetFormationOffset = true;
 37160        return self;
 161    }
 162
 163    private void UpdateMemberState(
 164        MovementGroupMember self,
 165        Vector3d requestedDestination,
 166        Vector3d position,
 167        Fixed64 radius,
 168        bool resetFormationOffset)
 169    {
 65170        self.Position = position;
 65171        self.Radius = radius;
 65172        self.RequestedDestination = requestedDestination;
 65173        self.LastSeenFrame = FrameCount;
 174
 65175        if (resetFormationOffset)
 38176            self.HasFormationOffset = false;
 65177    }
 178
 179    private void UpdateMembership(
 180        MovementGroupSession session,
 181        MovementGroupMember self,
 182        Vector3d requestedDestination)
 183    {
 65184        if (!session.HasOwnerId)
 20185            return;
 186
 45187        if (self.HasOccupantId && self.OccupantId != session.OwnerId)
 1188            _movementGroupMemberships.Remove(self.OccupantId);
 189
 45190        self.OccupantId = session.OwnerId;
 45191        self.HasOccupantId = true;
 192
 45193        _movementGroupMemberships[session.OwnerId] = new MovementGroupMembership
 45194        {
 45195            GroupId = session.GroupId,
 45196            RequestedDestination = requestedDestination,
 45197            LastSeenFrame = self.LastSeenFrame
 45198        };
 45199    }
 200
 201    private bool TryGetFormationMetrics(
 202        MovementGroupState group,
 203        Vector3d requestedDestination,
 204        int minFrame,
 205        out Vector3d groupCenter,
 206        out Fixed64 averageRadius,
 207        out int groupCount)
 208    {
 65209        groupCenter = Vector3d.Zero;
 65210        averageRadius = Fixed64.Zero;
 65211        groupCount = 0;
 212
 302213        foreach (MovementGroupMember member in group.Members)
 214        {
 86215            if (!IsEligibleGroupMember(member, requestedDestination, minFrame))
 216                continue;
 217
 83218            groupCenter += member.Position;
 83219            averageRadius += member.Radius;
 83220            groupCount++;
 221        }
 222
 65223        if (groupCount < MinMovementGroupSize)
 47224            return false;
 225
 18226        groupCenter /= groupCount;
 18227        averageRadius = (averageRadius / groupCount) + _context.Settings.MovementGroupPadding;
 18228        return true;
 229    }
 230
 231    private static Fixed64 UpdateFormationOffsets(
 232        MovementGroupState group,
 233        Vector3d requestedDestination,
 234        int minFrame,
 235        Vector3d groupCenter)
 236    {
 18237        Fixed64 maxSpreadSq = Fixed64.Zero;
 238
 108239        foreach (MovementGroupMember member in group.Members)
 240        {
 36241            if (!IsEligibleGroupMember(member, requestedDestination, minFrame))
 242                continue;
 243
 36244            Vector3d formationOffset = member.Position - groupCenter;
 36245            if (!member.HasFormationOffset)
 246            {
 18247                member.FormationOffset = formationOffset;
 18248                member.HasFormationOffset = true;
 249            }
 250
 36251            Fixed64 spreadSq = formationOffset.MagnitudeSquared;
 36252            if (spreadSq > maxSpreadSq)
 14253                maxSpreadSq = spreadSq;
 254        }
 255
 18256        return maxSpreadSq;
 257    }
 258
 259    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 260    private static bool IsEligibleGroupMember(MovementGroupMember member, Vector3d requestedDestination, int minFrame)
 261    {
 122262        return member.LastSeenFrame >= minFrame
 122263            && member.RequestedDestination == requestedDestination;
 264    }
 265}

Methods/Properties

.ctor(Trailblazer.TrailblazerWorldContext)
get_FrameCount()
CacheOwner(Trailblazer.Navigation.MovementGroups.MovementGroupSession,System.Guid)
Prewarm(Trailblazer.Navigation.MovementGroups.MovementGroupSession,System.Guid,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
UpdateTarget(Trailblazer.Navigation.MovementGroups.MovementGroupSession,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
Remove(Trailblazer.Navigation.MovementGroups.MovementGroupSession)
IsNeighbor(Trailblazer.Navigation.MovementGroups.MovementGroupSession,System.Guid,FixedMathSharp.Vector3d,System.Int32)
Reset()
GetOrCreateGroup(System.Int32)
GetOrCreateMember(Trailblazer.Navigation.MovementGroups.MovementGroupSession,Trailblazer.Navigation.MovementGroups.MovementGroupState,System.Boolean&)
UpdateMemberState(Trailblazer.Navigation.MovementGroups.MovementGroupMember,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
UpdateMembership(Trailblazer.Navigation.MovementGroups.MovementGroupSession,Trailblazer.Navigation.MovementGroups.MovementGroupMember,FixedMathSharp.Vector3d)
TryGetFormationMetrics(Trailblazer.Navigation.MovementGroups.MovementGroupState,FixedMathSharp.Vector3d,System.Int32,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&,System.Int32&)
UpdateFormationOffsets(Trailblazer.Navigation.MovementGroups.MovementGroupState,FixedMathSharp.Vector3d,System.Int32,FixedMathSharp.Vector3d)
IsEligibleGroupMember(Trailblazer.Navigation.MovementGroups.MovementGroupMember,FixedMathSharp.Vector3d,System.Int32)