< Summary

Information
Class: Trailblazer.Pathing.NavigationIncomingTraversalEdgeEnumerator
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationIncomingTraversalEdgeEnumerator.cs
Line coverage
100%
Covered lines: 476
Uncovered lines: 0
Coverable lines: 476
Total lines: 742
Line coverage: 100%
Branch coverage
100%
Covered branches: 170
Total branches: 170
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%1212100%
get_CurrentTransitionSourceAction()100%22100%
get_CurrentTransitionDestinationAction()100%22100%
AdvanceOne(...)100%2626100%
PrepareBaseCandidate(...)100%4242100%
BeginOutgoingRescan()100%44100%
MatchesCandidate()100%1414100%
MatchesSurfaceEdge(...)100%88100%
PrepareCandidate(...)100%5252100%
CompleteRuleContactScan()100%11100%
ConsiderRule(...)100%88100%
Complete()100%11100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationIncomingTraversalEdgeEnumerator.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationIncomingTraversalEdgeEnumerator.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 FixedMathSharp;
 9using GridForge.Grids;
 10
 11namespace Trailblazer.Pathing;
 12
 13/// <summary>Discovers incoming candidates and recovers their exact forward edge.</summary>
 14internal struct NavigationIncomingTraversalEdgeEnumerator
 15{
 16    private readonly GridWorld _world;
 17    private readonly NavigationWorldGraph? _graph;
 18    private readonly NavigationMediumStateRef _destination;
 19    private readonly NavigationAgentProfile _profile;
 20    private readonly NavigationAreaPolicy _areaPolicy;
 21    private readonly NavigationRayWorkspace _workspace;
 22    private readonly bool _allowTransitions;
 23    private NavigationIncomingSurfaceEdgeEnumerator _surfaceIncoming;
 24    private NavigationAutomaticSeamIndex.EndpointEnumerator _volumeSeams;
 25    private NavigationAutomaticSeamRef _volumeSeamLookahead;
 26    private NavigationTransitionPage.Enumerator _definitions;
 27    private NavigationPublishedTransition _definitionLookahead;
 28    private NavigationTraversalEdgeEnumerator _outgoing;
 29    private NavigationMediumStateRef _candidateSource;
 30    private NavigationTraversalEdgeKind _candidateTraversalKind;
 31    private NavigationGraphEdge _candidateSurfaceEdge;
 32    private TraversalTransitionType _candidateType;
 33    private NavigationTransitionIdentityKind _candidateKind;
 34    private string _candidateOwnerMapId;
 35    private string _candidateId;
 36    private NavigationCellAddress _lastRuleSourceAddress;
 37    private TraversalMedium _lastRuleSourceMedium;
 38    private TraversalTransitionType _lastRuleType;
 39    private string _lastRuleId;
 40    private TraversalTransitionRule _scannedRule;
 41    private NavigationMediumStateRef _scannedRuleSource;
 42    private NavigationCellAddress _scannedRuleAddress;
 43    private int _ruleScanIndex;
 44    private int _ruleContactDirection;
 45    private bool _started;
 46    private bool _definitionsComplete;
 47    private bool _hasDefinitionLookahead;
 48    private bool _definitionNeedsDebit;
 49    private bool _hasLastRule;
 50    private bool _rescanActive;
 51    private bool _ruleScanActive;
 52    private bool _hasScannedRule;
 53    private bool _hasVolumeSeamLookahead;
 54    private bool _volumeSeamsComplete;
 55    private bool _baseComplete;
 56    private uint _visitedVolumeDirections;
 57
 58    internal NavigationIncomingTraversalEdgeEnumerator(
 59        GridWorld world,
 60        NavigationWorldGraph graph,
 61        NavigationMediumStateRef destination,
 62        NavigationAgentProfile profile,
 63        NavigationAreaPolicy areaPolicy,
 64        NavigationRayWorkspace workspace,
 65        bool allowTransitions)
 66    {
 354967        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 354968        SwiftThrowHelper.ThrowIfNull(graph, nameof(graph));
 354969        SwiftThrowHelper.ThrowIfNull(areaPolicy, nameof(areaPolicy));
 354970        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 354971        bool foundDestinationAddress = graph.TryGetNodeAddress(
 354972            destination.Node,
 354973            out NavigationCellAddress destinationAddress);
 74        System.Diagnostics.Debug.Assert(
 75            destination.IsValid && foundDestinationAddress,
 76            "Incoming traversal enumerators own a valid destination from their immutable graph.");
 354977        _world = world;
 354978        _graph = graph;
 354979        _destination = destination;
 354980        _profile = profile;
 354981        _areaPolicy = areaPolicy;
 354982        _workspace = workspace;
 354983        _allowTransitions = allowTransitions;
 354984        _surfaceIncoming = destination.Medium == TraversalMedium.Solid
 354985            ? graph.EnumerateIncomingSurfaceEdges(destination.Node)
 354986            : default;
 354987        _volumeSeams = (destination.Medium == TraversalMedium.Gas
 354988                || destination.Medium == TraversalMedium.Liquid)
 354989                ? graph.AutomaticSeams.GetActiveEndpointEnumerator(destinationAddress)
 354990                : default;
 354991        _volumeSeamLookahead = default;
 354992        _definitions = allowTransitions
 354993            ? graph.EnumerateIncomingTransitionCandidates(destination)
 354994            : default;
 354995        _definitionLookahead = default;
 354996        _outgoing = default;
 354997        _candidateSource = default;
 354998        _candidateTraversalKind = default;
 354999        _candidateSurfaceEdge = default;
 3549100        _candidateType = default;
 3549101        _candidateKind = default;
 3549102        _candidateOwnerMapId = string.Empty;
 3549103        _candidateId = string.Empty;
 3549104        _lastRuleSourceAddress = default;
 3549105        _lastRuleSourceMedium = default;
 3549106        _lastRuleType = default;
 3549107        _lastRuleId = string.Empty;
 3549108        _scannedRule = default;
 3549109        _scannedRuleSource = default;
 3549110        _scannedRuleAddress = default;
 3549111        _ruleScanIndex = 0;
 3549112        _ruleContactDirection = -1;
 3549113        _started = false;
 3549114        _definitionsComplete = !allowTransitions;
 3549115        _hasDefinitionLookahead = false;
 3549116        _definitionNeedsDebit = false;
 3549117        _hasLastRule = false;
 3549118        _rescanActive = false;
 3549119        _ruleScanActive = false;
 3549120        _hasScannedRule = false;
 3549121        _hasVolumeSeamLookahead = false;
 3549122        _volumeSeamsComplete = destination.Medium != TraversalMedium.Gas
 3549123            && destination.Medium != TraversalMedium.Liquid;
 3549124        _baseComplete = false;
 3549125        _visitedVolumeDirections = 0;
 3549126        CurrentPredecessor = default;
 3549127        CurrentKind = default;
 3549128        CurrentOrdinal = -1;
 3549129        CurrentCost = default;
 3549130        CurrentTransitionId = string.Empty;
 3549131        CurrentTransitionOwnerMapId = string.Empty;
 3549132        CurrentTransitionType = default;
 3549133        CurrentTransitionHints = default;
 3549134        CurrentTransitionIdentityKind = default;
 3549135        RequiresWorldStamp = destination.Medium == TraversalMedium.Gas
 3549136            || destination.Medium == TraversalMedium.Liquid;
 3549137    }
 138
 139    internal NavigationMediumStateRef CurrentPredecessor { get; private set; }
 140
 141    internal NavigationTraversalEdgeKind CurrentKind { get; private set; }
 142
 143    internal int CurrentOrdinal { get; private set; }
 144
 145    internal Fixed64 CurrentCost { get; private set; }
 146
 147    internal string CurrentTransitionId { get; private set; }
 148
 149    internal string CurrentTransitionOwnerMapId { get; private set; }
 150
 151    internal TraversalTransitionType CurrentTransitionType { get; private set; }
 152
 153    internal TraversalTransitionLocomotionHints CurrentTransitionHints { get; private set; }
 154
 155    internal NavigationTransitionIdentityKind CurrentTransitionIdentityKind { get; private set; }
 156
 157    internal bool RequiresWorldStamp { get; private set; }
 158
 159    internal Vector3d CurrentTransitionSourceAction =>
 3160        CurrentKind == NavigationTraversalEdgeKind.Transition
 3161            ? _outgoing.CurrentTransitionSourceAction
 3162            : default;
 163
 164    internal Vector3d CurrentTransitionDestinationAction =>
 3165        CurrentKind == NavigationTraversalEdgeKind.Transition
 3166            ? _outgoing.CurrentTransitionDestinationAction
 3167            : default;
 168
 169    internal NavigationTraversalEdgeAdvanceStatus AdvanceOne(
 170        NavigationWorkMeter meter,
 171        NavigationDependencyWorkspace dependencies,
 172        ref int edgeStepRemaining,
 173        ref int connectionStepRemaining)
 174    {
 31847175        SwiftThrowHelper.ThrowIfNull(meter, nameof(meter));
 31847176        SwiftThrowHelper.ThrowIfNull(dependencies, nameof(dependencies));
 31847177        SwiftThrowHelper.ThrowIfNegative(edgeStepRemaining, nameof(edgeStepRemaining));
 31847178        SwiftThrowHelper.ThrowIfNegative(
 31847179            connectionStepRemaining,
 31847180            nameof(connectionStepRemaining));
 181        System.Diagnostics.Debug.Assert(_graph != null,
 182            "Incoming traversal enumerators must be initialized with an immutable graph destination.");
 31847183        if (!_started && _allowTransitions)
 184        {
 44185            dependencies.RecordTransitionDependency();
 44186            bool found = _graph.TryGetNodeAddress(
 44187                _destination.Node,
 44188                out NavigationCellAddress address);
 189            System.Diagnostics.Debug.Assert(found,
 190                "Incoming traversal starts from a published destination state.");
 44191            if (!dependencies.TryRecordPage(
 44192                    address.MapId,
 44193                    _destination.Node.CellSlot / NavigationSemanticPage.SlotCount))
 194            {
 1195                return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 196            }
 43197            _started = true;
 198        }
 199
 200        while (true)
 201        {
 35166202            if (!_rescanActive)
 203            {
 19295204                if (!_baseComplete)
 205                {
 19210206                    NavigationTraversalEdgeAdvanceStatus baseStatus = PrepareBaseCandidate(
 19210207                        meter,
 19210208                        ref edgeStepRemaining);
 19210209                    if (baseStatus != NavigationTraversalEdgeAdvanceStatus.Complete)
 15681210                        return baseStatus;
 3529211                    _baseComplete = true;
 212                }
 3614213                if (!_allowTransitions)
 3486214                    return Complete();
 128215                NavigationTraversalEdgeAdvanceStatus prepared = PrepareCandidate(
 128216                    meter,
 128217                    ref edgeStepRemaining);
 128218                if (prepared != NavigationTraversalEdgeAdvanceStatus.Pending
 128219                    || !_rescanActive)
 220                {
 94221                    return prepared;
 222                }
 34223                return NavigationTraversalEdgeAdvanceStatus.Pending;
 224            }
 225
 15871226            NavigationTraversalEdgeAdvanceStatus status = _outgoing.AdvanceOne(
 15871227                meter,
 15871228                dependencies,
 15871229                ref edgeStepRemaining,
 15871230                ref connectionStepRemaining);
 15871231            if (_outgoing.RequiresWorldStamp)
 1772232                RequiresWorldStamp = true;
 15871233            if (status == NavigationTraversalEdgeAdvanceStatus.Complete)
 234            {
 45235                _rescanActive = false;
 45236                continue;
 237            }
 15826238            if (status != NavigationTraversalEdgeAdvanceStatus.Edge)
 6364239                return status;
 9462240            if (!MatchesCandidate())
 241            {
 242                continue;
 243            }
 244
 6187245            CurrentPredecessor = _candidateSource;
 6187246            CurrentKind = _outgoing.CurrentKind;
 6187247            CurrentOrdinal = _outgoing.CurrentOrdinal;
 6187248            CurrentCost = _outgoing.CurrentCost;
 6187249            CurrentTransitionId = _outgoing.CurrentTransitionId;
 6187250            CurrentTransitionOwnerMapId = _outgoing.CurrentTransitionOwnerMapId;
 6187251            CurrentTransitionType = _outgoing.CurrentTransitionType;
 6187252            CurrentTransitionHints = _outgoing.CurrentTransitionHints;
 6187253            CurrentTransitionIdentityKind = _outgoing.CurrentTransitionIdentityKind;
 6187254            _rescanActive = false;
 6187255            return NavigationTraversalEdgeAdvanceStatus.Edge;
 256        }
 257    }
 258
 259    private NavigationTraversalEdgeAdvanceStatus PrepareBaseCandidate(
 260        NavigationWorkMeter meter,
 261        ref int edgeStepRemaining)
 262    {
 19210263        if (_destination.Medium == TraversalMedium.Solid)
 264        {
 18817265            NavigationSurfaceEdgeAdvanceStatus status = _surfaceIncoming.AdvanceOne(
 18817266                meter,
 18817267                ref edgeStepRemaining);
 18817268            if (status == NavigationSurfaceEdgeAdvanceStatus.Complete)
 3458269                return NavigationTraversalEdgeAdvanceStatus.Complete;
 15359270            if (status == NavigationSurfaceEdgeAdvanceStatus.Blocked)
 271            {
 9272                return meter.RemainingEvaluatedEdges == 0
 9273                    ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded
 9274                    : NavigationTraversalEdgeAdvanceStatus.Blocked;
 275            }
 15350276            if (status != NavigationSurfaceEdgeAdvanceStatus.Edge)
 9461277                return NavigationTraversalEdgeAdvanceStatus.Pending;
 5889278            NavigationIncomingSurfaceEdge candidate = _surfaceIncoming.Current;
 5889279            _candidateSource = new NavigationMediumStateRef(
 5889280                candidate.Predecessor,
 5889281                TraversalMedium.Solid);
 5889282            _candidateTraversalKind = NavigationTraversalEdgeKind.Surface;
 5889283            _candidateSurfaceEdge = candidate.ForwardEdge;
 5889284            BeginOutgoingRescan();
 5889285            return NavigationTraversalEdgeAdvanceStatus.Pending;
 286        }
 393287        int selectedDirection = -1;
 393288        NavigationMediumStateRef selected = default;
 393289        NavigationCellAddress selectedAddress = default;
 393290        int count = _graph!.GetCompleteDirectionCount(_destination.Node);
 19230291        for (int direction = 0; direction < count; direction++)
 292        {
 9222293            if ((_visitedVolumeDirections & (1U << direction)) != 0)
 294                continue;
 7690295            if (!_graph.TryGetCompleteNeighbor(
 7690296                    _destination.Node,
 7690297                    direction,
 7690298                    out NavigationNodeRef candidateNode,
 7690299                    out _))
 300                continue;
 1539301            bool found = _graph.TryGetNodeAddress(
 1539302                candidateNode,
 1539303                out NavigationCellAddress candidateAddress);
 304            System.Diagnostics.Debug.Assert(found,
 305                "Topology neighbors belong to the same published graph.");
 1539306            if (selectedDirection >= 0
 1539307                && selectedAddress.CompareTo(candidateAddress) <= 0)
 308                continue;
 675309            selectedDirection = direction;
 675310            selected = new NavigationMediumStateRef(
 675311                candidateNode,
 675312                _destination.Medium);
 675313            selectedAddress = candidateAddress;
 314        }
 393315        bool selectedSeam = false;
 393316        if (!_hasVolumeSeamLookahead && !_volumeSeamsComplete)
 317        {
 89318            if (_volumeSeams.MoveNext())
 319            {
 13320                _volumeSeamLookahead = _volumeSeams.Current;
 13321                _hasVolumeSeamLookahead = true;
 322            }
 323            else
 324            {
 76325                _volumeSeamsComplete = true;
 326            }
 327        }
 393328        if (_hasVolumeSeamLookahead)
 329        {
 18330            bool found = _graph.TryGetNodeRef(
 18331                _volumeSeamLookahead.Destination,
 18332                out NavigationNodeRef seamSource);
 333            System.Diagnostics.Debug.Assert(found,
 334                "Active seam rows reference published graph nodes.");
 18335            if (selectedDirection < 0
 18336                || _volumeSeamLookahead.Destination.CompareTo(selectedAddress) < 0)
 337            {
 17338                selectedDirection = 0;
 17339                selected = new NavigationMediumStateRef(
 17340                    seamSource,
 17341                    _destination.Medium);
 17342                selectedAddress = _volumeSeamLookahead.Destination;
 17343                selectedSeam = true;
 344            }
 345        }
 393346        if (selectedDirection < 0)
 71347            return NavigationTraversalEdgeAdvanceStatus.Complete;
 322348        if (edgeStepRemaining == 0)
 349        {
 7350            return meter.RemainingEvaluatedEdges == 0
 7351                ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded
 7352                : NavigationTraversalEdgeAdvanceStatus.Blocked;
 353        }
 315354        if (!meter.TryConsumeEvaluatedEdges(1))
 1355            return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 314356        edgeStepRemaining--;
 314357        if (selectedSeam)
 358        {
 13359            _volumeSeamLookahead = default;
 13360            _hasVolumeSeamLookahead = false;
 361        }
 362        else
 363        {
 301364            _visitedVolumeDirections |= 1U << selectedDirection;
 365        }
 314366        _candidateSource = selected;
 314367        _candidateTraversalKind = NavigationTraversalEdgeKind.Volume;
 314368        _candidateSurfaceEdge = default;
 314369        BeginOutgoingRescan();
 314370        return NavigationTraversalEdgeAdvanceStatus.Pending;
 371    }
 372
 373    private void BeginOutgoingRescan()
 374    {
 6237375        if (_candidateSource.Medium == TraversalMedium.Gas
 6237376            || _candidateSource.Medium == TraversalMedium.Liquid)
 377        {
 336378            RequiresWorldStamp = true;
 379        }
 6237380        _outgoing = new NavigationTraversalEdgeEnumerator(
 6237381            _world,
 6237382            _graph!,
 6237383            _candidateSource,
 6237384            _profile,
 6237385            _areaPolicy,
 6237386            _workspace,
 6237387            _allowTransitions,
 6237388            emittedSurfaceOrdinal: -1);
 6237389        _rescanActive = true;
 6237390    }
 391
 392    private bool MatchesCandidate()
 393    {
 9462394        if (_outgoing.CurrentKind != _candidateTraversalKind
 9462395            || !_outgoing.CurrentTarget.Equals(_destination))
 396        {
 3258397            return false;
 398        }
 6204399        if (_candidateTraversalKind == NavigationTraversalEdgeKind.Volume)
 288400            return true;
 5916401        if (_candidateTraversalKind == NavigationTraversalEdgeKind.Surface)
 5894402            return MatchesSurfaceEdge(
 5894403                _candidateSurfaceEdge,
 5894404                _outgoing.CurrentSurfaceEdge);
 22405        return _outgoing.CurrentTransitionIdentityKind == _candidateKind
 22406            && _outgoing.CurrentTransitionType == _candidateType
 22407            && string.Equals(
 22408                _outgoing.CurrentTransitionOwnerMapId,
 22409                _candidateOwnerMapId,
 22410                System.StringComparison.Ordinal)
 22411            && string.Equals(
 22412                _outgoing.CurrentTransitionId,
 22413                _candidateId,
 22414                System.StringComparison.Ordinal);
 415    }
 416
 417    private static bool MatchesSurfaceEdge(
 418        NavigationGraphEdge candidate,
 419        NavigationGraphEdge forward)
 420    {
 5894421        if (candidate.Kind != forward.Kind || !candidate.Target.Equals(forward.Target))
 11422            return false;
 5883423        return candidate.Kind switch
 5883424        {
 30425            NavigationGraphEdgeKind.Explicit => ReferenceEquals(
 30426                candidate.ExplicitConnection,
 30427                forward.ExplicitConnection),
 12428            NavigationGraphEdgeKind.Seam => ReferenceEquals(
 12429                candidate.AutomaticSeam.Pair,
 12430                forward.AutomaticSeam.Pair),
 5841431            _ => true
 5883432        };
 433    }
 434
 435    private NavigationTraversalEdgeAdvanceStatus PrepareCandidate(
 436        NavigationWorkMeter meter,
 437        ref int edgeStepRemaining)
 438    {
 128439        bool hasDefinition = _hasDefinitionLookahead;
 128440        NavigationPublishedTransition definition = _definitionLookahead;
 128441        NavigationMediumStateRef definitionSource = default;
 128442        if (!hasDefinition && !_definitionsComplete)
 443        {
 54444            if (_definitions.MoveNext())
 445            {
 16446                _definitionLookahead = _definitions.Current;
 16447                _hasDefinitionLookahead = true;
 16448                _definitionNeedsDebit = true;
 16449                hasDefinition = true;
 16450                definition = _definitionLookahead;
 451            }
 452            else
 453            {
 38454                _definitionsComplete = true;
 455            }
 456        }
 128457        if (_definitionNeedsDebit)
 458        {
 16459            if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate(
 16460                    meter,
 16461                    ref edgeStepRemaining,
 16462                    out NavigationTraversalEdgeAdvanceStatus blocked))
 463            {
 2464                return blocked;
 465            }
 14466            _definitionNeedsDebit = false;
 467        }
 126468        if (hasDefinition)
 469        {
 22470            bool foundDefinitionSource = _graph!.TryGetNodeRef(
 22471                definition.SourceAddress,
 22472                out NavigationNodeRef definitionSourceNode);
 473            System.Diagnostics.Debug.Assert(foundDefinitionSource,
 474                "Published incoming transition definitions retain their source map node.");
 22475            definitionSource = new NavigationMediumStateRef(
 22476                definitionSourceNode,
 22477                definition.Definition.SourceMedium);
 478        }
 479
 126480        if (!_ruleScanActive)
 481        {
 70482            _ruleScanActive = true;
 70483            _ruleScanIndex = 0;
 70484            _hasScannedRule = false;
 70485            _scannedRule = default;
 70486            _scannedRuleSource = default;
 70487            _scannedRuleAddress = default;
 70488            _ruleContactDirection = -1;
 489        }
 367490        while (_ruleScanIndex < _graph!.TransitionRules.Count)
 491        {
 299492            TraversalTransitionRule rule = _graph.TransitionRules[_ruleScanIndex];
 299493            if (_ruleContactDirection < 0)
 494            {
 60495                if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate(
 60496                        meter,
 60497                        ref edgeStepRemaining,
 60498                        out NavigationTraversalEdgeAdvanceStatus blocked))
 499                {
 6500                    return blocked;
 501                }
 54502                if (rule.DestinationMedium != _destination.Medium)
 503                {
 8504                    CompleteRuleContactScan();
 8505                    continue;
 506                }
 46507                _ruleContactDirection = 0;
 508            }
 285509            if (rule.Scope == TraversalTransitionRuleScope.SameCell)
 510            {
 24511                if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate(
 24512                        meter,
 24513                        ref edgeStepRemaining,
 24514                        out NavigationTraversalEdgeAdvanceStatus blocked))
 515                {
 6516                    return blocked;
 517                }
 18518                bool foundSource = _graph.TryGetNodeAddress(
 18519                    _destination.Node,
 18520                    out NavigationCellAddress sourceAddress);
 521                System.Diagnostics.Debug.Assert(foundSource,
 522                    "Same-cell rules reuse the published destination node.");
 18523                ConsiderRule(
 18524                    rule,
 18525                    new NavigationMediumStateRef(
 18526                        _destination.Node,
 18527                        rule.SourceMedium),
 18528                    sourceAddress,
 18529                    ref _hasScannedRule,
 18530                    ref _scannedRule,
 18531                    ref _scannedRuleSource,
 18532                    ref _scannedRuleAddress);
 18533                CompleteRuleContactScan();
 18534                continue;
 535            }
 261536            int primaryCount = _graph.GetPrimaryDirectionCount(_destination.Node);
 261537            if (_ruleContactDirection < primaryCount)
 538            {
 194539                if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate(
 194540                        meter,
 194541                        ref edgeStepRemaining,
 194542                        out NavigationTraversalEdgeAdvanceStatus blocked))
 543                {
 29544                    return blocked;
 545                }
 165546                int direction = _ruleContactDirection++;
 165547                if (_graph.TryGetPrimaryNeighbor(
 165548                        _destination.Node,
 165549                        direction,
 165550                        out NavigationNodeRef sourceNode))
 551                {
 18552                    bool foundSource = _graph.TryGetNodeAddress(
 18553                        sourceNode,
 18554                        out NavigationCellAddress sourceAddress);
 555                    System.Diagnostics.Debug.Assert(foundSource,
 556                        "Topology neighbors belong to the same published graph.");
 18557                    ConsiderRule(
 18558                        rule,
 18559                        new NavigationMediumStateRef(
 18560                            sourceNode,
 18561                            rule.SourceMedium),
 18562                        sourceAddress,
 18563                        ref _hasScannedRule,
 18564                        ref _scannedRule,
 18565                        ref _scannedRuleSource,
 18566                        ref _scannedRuleAddress);
 567                }
 18568                continue;
 569            }
 67570            if (_ruleContactDirection == primaryCount)
 571            {
 27572                bool foundDestination = _graph.TryGetNodeAddress(
 27573                    _destination.Node,
 27574                    out NavigationCellAddress seamDestinationAddress);
 575                System.Diagnostics.Debug.Assert(foundDestination,
 576                    "Volume rule scans start from a published destination node.");
 27577                _volumeSeams = _graph.AutomaticSeams.GetActiveEndpointEnumerator(
 27578                    seamDestinationAddress);
 27579                _volumeSeamLookahead = default;
 27580                _hasVolumeSeamLookahead = false;
 27581                _volumeSeamsComplete = false;
 27582                _ruleContactDirection++;
 583            }
 584            System.Diagnostics.Debug.Assert(!_volumeSeamsComplete,
 585                "Completed volume-seam contact scans advance to the next transition rule immediately.");
 67586            if (edgeStepRemaining == 0)
 16587                return NavigationTraversalEdgeAdvanceStatus.Blocked;
 51588            if (!_volumeSeams.MoveNext())
 589            {
 26590                _volumeSeamsComplete = true;
 26591                CompleteRuleContactScan();
 26592                continue;
 593            }
 25594            if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate(
 25595                    meter,
 25596                    ref edgeStepRemaining,
 25597                    out NavigationTraversalEdgeAdvanceStatus seamBlocked))
 598            {
 1599                return seamBlocked;
 600            }
 24601            NavigationAutomaticSeamRef seam = _volumeSeams.Current;
 24602            bool found = _graph.TryGetNodeRef(
 24603                seam.Destination,
 24604                out NavigationNodeRef seamSource);
 605            System.Diagnostics.Debug.Assert(found,
 606                "Active seam rows reference published graph nodes.");
 24607            ConsiderRule(
 24608                rule,
 24609                new NavigationMediumStateRef(
 24610                    seamSource,
 24611                    rule.SourceMedium),
 24612                seam.Destination,
 24613                ref _hasScannedRule,
 24614                ref _scannedRule,
 24615                ref _scannedRuleSource,
 24616                ref _scannedRuleAddress);
 617        }
 618
 68619        bool hasRule = _hasScannedRule;
 68620        TraversalTransitionRule selectedRule = _scannedRule;
 68621        NavigationMediumStateRef selectedRuleSource = _scannedRuleSource;
 68622        NavigationCellAddress selectedRuleAddress = _scannedRuleAddress;
 68623        _ruleScanActive = false;
 68624        _ruleScanIndex = 0;
 68625        _ruleContactDirection = -1;
 68626        _hasScannedRule = false;
 68627        _scannedRule = default;
 68628        _scannedRuleSource = default;
 68629        _scannedRuleAddress = default;
 630
 68631        NavigationCellAddress definitionAddress = hasDefinition
 68632            ? definition.SourceAddress
 68633            : default;
 68634        bool selectRule = hasRule && (!hasDefinition
 68635            || NavigationTraversalEdgeEnumerator.CompareTransitionKey(
 68636                selectedRuleAddress,
 68637                selectedRule.SourceMedium,
 68638                selectedRule.Type,
 68639                NavigationTransitionIdentityKind.Rule,
 68640                selectedRule.Id,
 68641                definitionAddress,
 68642                definition.Definition.SourceMedium,
 68643                definition.Definition.Type,
 68644                NavigationTransitionIdentityKind.Definition,
 68645                definition.Owner.TransitionId) < 0);
 68646        if (!hasDefinition && !hasRule)
 34647            return Complete();
 648
 34649        if (selectRule)
 650        {
 20651            _candidateSource = selectedRuleSource;
 20652            _candidateTraversalKind = NavigationTraversalEdgeKind.Transition;
 20653            _candidateType = selectedRule.Type;
 20654            _candidateKind = NavigationTransitionIdentityKind.Rule;
 20655            _candidateOwnerMapId = string.Empty;
 20656            _candidateId = selectedRule.Id;
 20657            _hasLastRule = true;
 20658            _lastRuleSourceAddress = selectedRuleAddress;
 20659            _lastRuleSourceMedium = selectedRule.SourceMedium;
 20660            _lastRuleType = selectedRule.Type;
 20661            _lastRuleId = selectedRule.Id;
 662        }
 663        else
 664        {
 14665            _candidateSource = definitionSource;
 14666            _candidateTraversalKind = NavigationTraversalEdgeKind.Transition;
 14667            _candidateType = definition.Definition.Type;
 14668            _candidateKind = NavigationTransitionIdentityKind.Definition;
 14669            _candidateOwnerMapId = definition.Owner.MapId;
 14670            _candidateId = definition.Owner.TransitionId;
 14671            _definitionLookahead = default;
 14672            _hasDefinitionLookahead = false;
 673        }
 34674        BeginOutgoingRescan();
 34675        return NavigationTraversalEdgeAdvanceStatus.Pending;
 676    }
 677
 678    private void CompleteRuleContactScan()
 679    {
 52680        _ruleScanIndex++;
 52681        _ruleContactDirection = -1;
 52682        _volumeSeamLookahead = default;
 52683        _hasVolumeSeamLookahead = false;
 52684        _volumeSeamsComplete = true;
 52685    }
 686
 687    private void ConsiderRule(
 688        TraversalTransitionRule rule,
 689        NavigationMediumStateRef source,
 690        NavigationCellAddress sourceAddress,
 691        ref bool hasRule,
 692        ref TraversalTransitionRule selectedRule,
 693        ref NavigationMediumStateRef selectedSource,
 694        ref NavigationCellAddress selectedAddress)
 695    {
 60696        if ((_hasLastRule
 60697                && NavigationTraversalEdgeEnumerator.CompareTransitionKey(
 60698                    _lastRuleSourceAddress,
 60699                    _lastRuleSourceMedium,
 60700                    _lastRuleType,
 60701                    NavigationTransitionIdentityKind.Rule,
 60702                    _lastRuleId,
 60703                    sourceAddress,
 60704                    rule.SourceMedium,
 60705                    rule.Type,
 60706                    NavigationTransitionIdentityKind.Rule,
 60707                    rule.Id) >= 0)
 60708            || (hasRule
 60709                && NavigationTraversalEdgeEnumerator.CompareTransitionKey(
 60710                    selectedAddress,
 60711                    selectedRule.SourceMedium,
 60712                    selectedRule.Type,
 60713                    NavigationTransitionIdentityKind.Rule,
 60714                    selectedRule.Id,
 60715                    sourceAddress,
 60716                    rule.SourceMedium,
 60717                    rule.Type,
 60718                    NavigationTransitionIdentityKind.Rule,
 60719                    rule.Id) <= 0))
 720        {
 38721            return;
 722        }
 22723        hasRule = true;
 22724        selectedRule = rule;
 22725        selectedSource = source;
 22726        selectedAddress = sourceAddress;
 22727    }
 728
 729    private NavigationTraversalEdgeAdvanceStatus Complete()
 730    {
 3520731        CurrentPredecessor = default;
 3520732        CurrentKind = default;
 3520733        CurrentOrdinal = -1;
 3520734        CurrentCost = default;
 3520735        CurrentTransitionId = string.Empty;
 3520736        CurrentTransitionOwnerMapId = string.Empty;
 3520737        CurrentTransitionType = default;
 3520738        CurrentTransitionHints = default;
 3520739        CurrentTransitionIdentityKind = default;
 3520740        return NavigationTraversalEdgeAdvanceStatus.Complete;
 741    }
 742}