< Summary

Information
Class: Trailblazer.Pathing.NavigationFlowFieldGuideLease
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowFieldGuideLease.cs
Line coverage
100%
Covered lines: 248
Uncovered lines: 0
Coverable lines: 248
Total lines: 419
Line coverage: 100%
Branch coverage
100%
Covered branches: 74
Total branches: 74
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%22100%
get_Generation()100%11100%
Bind(...)100%11100%
GetStatus(...)100%66100%
GetOriginIntegrationCost(...)100%22100%
TrySample(...)100%2424100%
CompletePendingTransition(...)100%1010100%
ResolveTransitionSampleStatus(...)100%22100%
AdvanceSampleOrdinal(...)100%11100%
IsSampleEpochCurrent(...)100%22100%
ResolveTransitionCompletion(...)100%11100%
TryDetach(...)100%22100%
Dispose(...)100%22100%
IsGenerationActiveUnderLock(...)100%88100%
TryGetCurrentPayloadUnderLock(...)100%88100%
MarkStaleUnderLock()100%11100%
TrySampleTransitionUnderLock(...)100%66100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowFieldGuideLease.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationFlowFieldGuideLease.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.Diagnostics;
 10using FixedMathSharp;
 11using GridForge.Grids;
 12using GridForge.Grids.Topology;
 13
 14namespace Trailblazer.Pathing;
 15
 16/// <summary>Owns the generation-linearized state of one flow field sampler.</summary>
 17internal sealed class NavigationFlowFieldGuideLease
 18{
 822319    private readonly object _sync = new();
 20    private readonly GridCoveredAddressCursor _coveredAddressCursor;
 21    private readonly GridCoveredAddressGeneration[] _coveredAddressGenerations;
 22    private readonly GridCoveredAddress[] _coveredAddressOutput;
 23    private NavigationFlowFieldPayloadCache? _owner;
 24    private NavigationWorldGraphStore? _store;
 25    private NavigationCellAddress _currentSource;
 26    private TraversalMedium _currentMedium;
 27    private Fixed64 _originIntegrationCost;
 28    private NavigationGuideStatus _status;
 29    private int _payloadSlot;
 30    private ulong _payloadGeneration;
 31    private ulong _generation;
 32    private long _sampleOrdinal;
 33    private bool _hasPendingTransition;
 34
 822335    internal NavigationFlowFieldGuideLease(int coveredAddressGenerationCapacity)
 36    {
 822337        _coveredAddressCursor = new GridCoveredAddressCursor(
 822338            coveredAddressGenerationCapacity);
 822339        _coveredAddressGenerations = coveredAddressGenerationCapacity == 0
 822340            ? Array.Empty<GridCoveredAddressGeneration>()
 822341            : new GridCoveredAddressGeneration[coveredAddressGenerationCapacity];
 822342        _coveredAddressOutput = new GridCoveredAddress[1];
 822343        _payloadSlot = -1;
 822344    }
 45
 46    internal ulong Generation
 47    {
 380748        get { lock (_sync) return _generation; }
 49    }
 50
 51    internal void Bind(
 52        NavigationFlowFieldPayloadCache owner,
 53        NavigationWorldGraphStore store,
 54        int payloadSlot,
 55        ulong payloadGeneration,
 56        NavigationCellAddress resolvedOrigin,
 57        TraversalMedium startMedium,
 58        Fixed64 originIntegrationCost)
 59    {
 117960        lock (_sync)
 61        {
 62            // The cache rents only detached guides and does not return one to its pool
 63            // until TryDetach has cleared this complete ownership tuple.
 64            Debug.Assert(_owner == null);
 117965            _generation = NavigationGenerationCounter.Advance(
 117966                _generation,
 117967                "The flow guide generation is exhausted.");
 117968            _owner = owner;
 117969            _store = store;
 117970            _payloadSlot = payloadSlot;
 117971            _payloadGeneration = payloadGeneration;
 117972            _currentSource = resolvedOrigin;
 117973            _currentMedium = startMedium;
 117974            _originIntegrationCost = originIntegrationCost;
 117975            _sampleOrdinal = 0;
 117976            _hasPendingTransition = false;
 117977            _status = NavigationGuideStatus.Success;
 117978        }
 117979    }
 80
 81    internal NavigationGuideStatus GetStatus(ulong generation)
 82    {
 3483        lock (_sync)
 84        {
 3485            if (!IsGenerationActiveUnderLock(generation))
 386                return NavigationGuideStatus.Stale;
 3187            if (_status != NavigationGuideStatus.Success)
 688                return _status;
 2589            return TryGetCurrentPayloadUnderLock(out _)
 2590                ? NavigationGuideStatus.Success
 2591                : MarkStaleUnderLock();
 92        }
 3493    }
 94
 95    internal Fixed64 GetOriginIntegrationCost(ulong generation)
 96    {
 497        lock (_sync)
 98        {
 499            return IsGenerationActiveUnderLock(generation)
 4100                ? _originIntegrationCost
 4101                : Fixed64.Zero;
 102        }
 4103    }
 104
 105    internal NavigationGuideStatus TrySample(
 106        ulong generation,
 107        Vector3d actualFootPosition,
 108        ref GuideSampleWorkMeter meter,
 109        out NavigationFlowSample sample)
 110    {
 1363111        lock (_sync)
 112        {
 1363113            sample = default;
 1363114            if (!IsGenerationActiveUnderLock(generation))
 1115                return NavigationGuideStatus.Stale;
 1362116            if (_status != NavigationGuideStatus.Success)
 2117                return _status;
 1360118            if (!TryGetCurrentPayloadUnderLock(out NavigationFlowFieldPayload payload))
 2119                return MarkStaleUnderLock();
 1358120            NavigationFlowFieldPayloadCache owner = _owner!;
 1358121            GridWorld world = owner.World;
 1358122            ulong worldSequence = world.ChangeSequence;
 1358123            NavigationWorldGraphStore store = _store!;
 1358124            NavigationWorldGraphLease? graphLease = store.TryAcquire();
 1358125            if (graphLease == null)
 2126                return NavigationGuideStatus.CapacityExceeded;
 1356127            using (graphLease)
 128            {
 1356129                NavigationWorldGraph graph = graphLease.Graph;
 1356130                bool dependencyCurrent = graph.IsDependencyCurrent(payload.Dependencies);
 1356131                NavigationCellAddress candidateSource = _currentSource;
 1356132                NavigationFlowFieldNode currentNode = default;
 1356133                Vector3d target = default;
 1356134                Vector3d candidateHeading = default;
 1356135                NavigationGuideStatus status =
 1356136                    NavigationSelectedEdgeProgressWork.TrySample(
 1356137                        dependencyCurrent,
 1356138                        world,
 1356139                        store,
 1356140                        graph,
 1356141                        payload,
 1356142                        _currentSource,
 1356143                        _currentMedium,
 1356144                        actualFootPosition,
 1356145                        ref meter,
 1356146                        _coveredAddressCursor,
 1356147                        _coveredAddressGenerations,
 1356148                        _coveredAddressOutput,
 1356149                        owner.ImmediateRayWorkspace,
 1356150                        out currentNode,
 1356151                        out candidateSource,
 1356152                        out target,
 1356153                        out candidateHeading);
 1356154                if (!dependencyCurrent
 1356155                    || !TryGetCurrentPayloadUnderLock(out _)
 1356156                    || world.ChangeSequence != worldSequence)
 157                {
 1158                    return MarkStaleUnderLock();
 159                }
 1355160                if (status == NavigationGuideStatus.Stale)
 1161                    return MarkStaleUnderLock();
 1354162                if (status == NavigationGuideStatus.Success)
 163                {
 1282164                    bool sourceChanged = candidateSource != _currentSource;
 165                    // Selected edges target earlier-settled nodes in this int-bounded
 166                    // payload, so one lease cannot exhaust a long source identity.
 167                    Debug.Assert(_sampleOrdinal < payload.Nodes.Length);
 1282168                    long candidateSampleOrdinal = AdvanceSampleOrdinal(
 1282169                        _sampleOrdinal,
 1282170                        sourceChanged);
 1282171                    if (currentNode.TransitionInstructionOrdinal >= 0)
 172                    {
 22173                        NavigationGuideStatus transitionStatus =
 22174                            TrySampleTransitionUnderLock(
 22175                            generation,
 22176                            world,
 22177                            store,
 22178                            graph,
 22179                            owner,
 22180                            payload,
 22181                            currentNode,
 22182                            candidateSource,
 22183                            candidateSampleOrdinal,
 22184                            actualFootPosition,
 22185                            ref meter,
 22186                            out sample);
 22187                        bool transitionEpochCurrent = IsSampleEpochCurrent(
 22188                            TryGetCurrentPayloadUnderLock(out _),
 22189                            world.ChangeSequence,
 22190                            worldSequence);
 22191                        transitionStatus = ResolveTransitionSampleStatus(
 22192                            transitionEpochCurrent,
 22193                            transitionStatus,
 22194                            ref sample);
 22195                        if (transitionStatus == NavigationGuideStatus.Stale)
 1196                            return MarkStaleUnderLock();
 21197                        if (transitionStatus != NavigationGuideStatus.Success)
 4198                            return transitionStatus;
 17199                        _currentSource = candidateSource;
 17200                        _sampleOrdinal = candidateSampleOrdinal;
 17201                        _hasPendingTransition = sample.HasTransition;
 17202                        return NavigationGuideStatus.Success;
 203                    }
 1260204                    _currentSource = candidateSource;
 1260205                    _sampleOrdinal = candidateSampleOrdinal;
 1260206                    sample = new NavigationFlowSample(
 1260207                        candidateHeading,
 1260208                        target,
 1260209                        _currentMedium,
 1260210                        default,
 1260211                        hasTransition: false);
 212                }
 1332213                return status;
 214            }
 215        }
 1363216    }
 217
 218    internal NavigationGuideStatus CompletePendingTransition(
 219        ulong generation,
 220        in NavigationTransitionInstruction instruction)
 221    {
 12222        lock (_sync)
 223        {
 12224            if (!IsGenerationActiveUnderLock(generation)
 12225                || !_hasPendingTransition
 12226                || !TryGetCurrentPayloadUnderLock(out NavigationFlowFieldPayload payload)
 12227                || !payload.TryGetNode(
 12228                    _currentSource,
 12229                    _currentMedium,
 12230                    out NavigationFlowFieldNode current)
 12231                || !instruction.MatchesCompletion(
 12232                    this,
 12233                    generation,
 12234                    _sampleOrdinal))
 235            {
 4236                return NavigationGuideStatus.Stale;
 237            }
 8238            NavigationTransitionInstruction expected =
 8239                payload.TransitionInstructions[current.TransitionInstructionOrdinal];
 240            Debug.Assert(_sampleOrdinal < payload.Nodes.Length);
 8241            _status = ResolveTransitionCompletion(
 8242                expected.DestinationAddress,
 8243                expected.DestinationMedium,
 8244                AdvanceSampleOrdinal(_sampleOrdinal, advance: true),
 8245                ref _currentSource,
 8246                ref _currentMedium,
 8247                ref _hasPendingTransition,
 8248                ref _sampleOrdinal);
 8249            return _status;
 250        }
 12251    }
 252
 253    internal static NavigationGuideStatus ResolveTransitionSampleStatus(
 254        bool epochCurrent,
 255        NavigationGuideStatus status,
 256        ref NavigationFlowSample sample)
 257    {
 26258        if (status != NavigationGuideStatus.Success || epochCurrent)
 25259            return status;
 1260        sample = default;
 1261        return NavigationGuideStatus.Stale;
 262    }
 263
 264    internal static long AdvanceSampleOrdinal(long current, bool advance)
 265    {
 266        Debug.Assert(!advance || current < long.MaxValue);
 1292267        return current + (advance ? 1L : 0L);
 268    }
 269
 270    internal static bool IsSampleEpochCurrent(
 271        bool payloadCurrent,
 272        ulong currentWorldSequence,
 273        ulong expectedWorldSequence) =>
 25274        payloadCurrent && currentWorldSequence == expectedWorldSequence;
 275
 276    internal static NavigationGuideStatus ResolveTransitionCompletion(
 277        NavigationCellAddress destination,
 278        TraversalMedium destinationMedium,
 279        long nextSampleOrdinal,
 280        ref NavigationCellAddress currentSource,
 281        ref TraversalMedium currentMedium,
 282        ref bool hasPendingTransition,
 283        ref long sampleOrdinal)
 284    {
 9285        currentSource = destination;
 9286        currentMedium = destinationMedium;
 9287        hasPendingTransition = false;
 9288        sampleOrdinal = nextSampleOrdinal;
 9289        return NavigationGuideStatus.Success;
 290    }
 291
 292    internal bool TryDetach(
 293        ulong generation,
 294        out NavigationFlowFieldPayloadCache owner,
 295        out int payloadSlot)
 296    {
 1183297        lock (_sync)
 298        {
 1183299            owner = null!;
 1183300            payloadSlot = -1;
 1183301            if (!IsGenerationActiveUnderLock(generation))
 5302                return false;
 1178303            owner = _owner!;
 1178304            payloadSlot = _payloadSlot;
 1178305            _owner = null;
 1178306            _store = null;
 1178307            _currentSource = default;
 1178308            _currentMedium = TraversalMedium.Unknown;
 1178309            _originIntegrationCost = Fixed64.Zero;
 1178310            _sampleOrdinal = 0;
 1178311            _hasPendingTransition = false;
 1178312            _status = NavigationGuideStatus.Stale;
 1178313            _payloadSlot = -1;
 1178314            _payloadGeneration = 0;
 1178315            return true;
 316        }
 1183317    }
 318
 319    internal void Dispose(ulong generation)
 320    {
 1183321        if (!TryDetach(
 1183322                generation,
 1183323                out NavigationFlowFieldPayloadCache owner,
 1183324                out int payloadSlot))
 325        {
 5326            return;
 327        }
 1178328        owner.ReturnDetachedGuide(this, generation, payloadSlot);
 1178329    }
 330
 331    private bool IsGenerationActiveUnderLock(ulong generation) =>
 2596332        generation != 0
 2596333        && generation == _generation
 2596334        && _owner != null
 2596335        && _store != null
 2596336        && _payloadSlot >= 0;
 337
 338    private bool TryGetCurrentPayloadUnderLock(
 339        out NavigationFlowFieldPayload payload)
 340    {
 2773341        NavigationFlowFieldPayloadCache? owner = _owner;
 2773342        NavigationWorldGraphStore? store = _store;
 2773343        if (owner != null
 2773344            && store != null
 2773345            && owner.TryGetGuidePayload(
 2773346                    _payloadSlot,
 2773347                    _payloadGeneration,
 2773348                    out payload) == NavigationFlowFieldStatus.Success
 2773349            && owner.IsPayloadCurrent(store.Current, payload))
 350        {
 2767351            return true;
 352        }
 6353        payload = null!;
 6354        return false;
 355    }
 356
 357    private NavigationGuideStatus MarkStaleUnderLock()
 358    {
 8359        _status = NavigationGuideStatus.Stale;
 8360        return _status;
 361    }
 362
 363    private NavigationGuideStatus TrySampleTransitionUnderLock(
 364        ulong generation,
 365        GridWorld world,
 366        NavigationWorldGraphStore store,
 367        NavigationWorldGraph graph,
 368        NavigationFlowFieldPayloadCache owner,
 369        NavigationFlowFieldPayload payload,
 370        in NavigationFlowFieldNode node,
 371        NavigationCellAddress candidateSource,
 372        long candidateSampleOrdinal,
 373        Vector3d actualFootPosition,
 374        ref GuideSampleWorkMeter meter,
 375        out NavigationFlowSample sample)
 376    {
 22377        sample = default;
 22378        NavigationTransitionInstruction instruction =
 22379            payload.TransitionInstructions[node.TransitionInstructionOrdinal];
 380        NavigationGuideStatus headingStatus;
 381        Vector3d heading;
 22382        if (_hasPendingTransition)
 383        {
 1384            headingStatus = NavigationGuideStatus.Success;
 1385            heading = Vector3d.Zero;
 386        }
 387        else
 388        {
 21389            headingStatus = NavigationSelectedEdgeProgressWork.TrySampleTransitionApproach(
 21390                world,
 21391                store,
 21392                graph,
 21393                payload,
 21394                candidateSource,
 21395                _currentMedium,
 21396                actualFootPosition,
 21397                instruction.SourcePosition,
 21398                ref meter,
 21399                owner.ImmediateRayWorkspace,
 21400                out heading);
 401        }
 22402        if (headingStatus != NavigationGuideStatus.Success)
 5403            return headingStatus;
 17404        bool hasTransition = heading == Vector3d.Zero;
 405        Debug.Assert(!hasTransition || candidateSampleOrdinal < payload.Nodes.Length);
 17406        sample = new NavigationFlowSample(
 17407            heading,
 17408            instruction.SourcePosition,
 17409            _currentMedium,
 17410            hasTransition
 17411                ? instruction.WithCompletionStamp(
 17412                    this,
 17413                    generation,
 17414                    candidateSampleOrdinal)
 17415                : default,
 17416            hasTransition);
 17417        return NavigationGuideStatus.Success;
 418    }
 419}

Methods/Properties

.ctor(System.Int32)
get_Generation()
Bind(Trailblazer.Pathing.NavigationFlowFieldPayloadCache,Trailblazer.Pathing.NavigationWorldGraphStore,System.Int32,System.UInt64,Trailblazer.Pathing.NavigationCellAddress,Trailblazer.TraversalMedium,FixedMathSharp.Fixed64)
GetStatus(System.UInt64)
GetOriginIntegrationCost(System.UInt64)
TrySample(System.UInt64,FixedMathSharp.Vector3d,Trailblazer.Pathing.GuideSampleWorkMeter&,Trailblazer.Pathing.NavigationFlowSample&)
CompletePendingTransition(System.UInt64,Trailblazer.Pathing.NavigationTransitionInstruction&)
ResolveTransitionSampleStatus(System.Boolean,Trailblazer.Pathing.NavigationGuideStatus,Trailblazer.Pathing.NavigationFlowSample&)
AdvanceSampleOrdinal(System.Int64,System.Boolean)
IsSampleEpochCurrent(System.Boolean,System.UInt64,System.UInt64)
ResolveTransitionCompletion(Trailblazer.Pathing.NavigationCellAddress,Trailblazer.TraversalMedium,System.Int64,Trailblazer.Pathing.NavigationCellAddress&,Trailblazer.TraversalMedium&,System.Boolean&,System.Int64&)
TryDetach(System.UInt64,Trailblazer.Pathing.NavigationFlowFieldPayloadCache&,System.Int32&)
Dispose(System.UInt64)
IsGenerationActiveUnderLock(System.UInt64)
TryGetCurrentPayloadUnderLock(Trailblazer.Pathing.NavigationFlowFieldPayload&)
MarkStaleUnderLock()
TrySampleTransitionUnderLock(System.UInt64,GridForge.Grids.GridWorld,Trailblazer.Pathing.NavigationWorldGraphStore,Trailblazer.Pathing.NavigationWorldGraph,Trailblazer.Pathing.NavigationFlowFieldPayloadCache,Trailblazer.Pathing.NavigationFlowFieldPayload,Trailblazer.Pathing.NavigationFlowFieldNode&,Trailblazer.Pathing.NavigationCellAddress,System.Int64,FixedMathSharp.Vector3d,Trailblazer.Pathing.GuideSampleWorkMeter&,Trailblazer.Pathing.NavigationFlowSample&)