< Summary

Information
Class: Trailblazer.Pathing.NavigationTraversalEdgeEnumerator
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationTraversalEdgeEnumerator.cs
Line coverage
100%
Covered lines: 595
Uncovered lines: 0
Coverable lines: 595
Total lines: 962
Line coverage: 100%
Branch coverage
100%
Covered branches: 232
Total branches: 232
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationTraversalEdgeEnumerator.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;
 10using NavigationTransitionEdgeStatus = Trailblazer.Pathing.NavigationTraversalEvaluationStatus;
 11using NavigationVolumeEdgeStatus = Trailblazer.Pathing.NavigationTraversalEvaluationStatus;
 12
 13namespace Trailblazer.Pathing;
 14
 15/// <summary>Identifies which existing authority produced one evaluated traversal edge.</summary>
 16internal enum NavigationTraversalEdgeKind : byte
 17{
 18    Surface = 0,
 19    Volume = 1,
 20    Transition = 2
 21}
 22
 23/// <summary>Reports bounded progress through canonical outgoing traversal edges.</summary>
 24internal enum NavigationTraversalEdgeAdvanceStatus : byte
 25{
 26    Pending = 0,
 27    Edge = 1,
 28    Complete = 2,
 29    Blocked = 3,
 30    BudgetExceeded = 4,
 31    CapacityExceeded = 5,
 32    CostOverflow = 6,
 33    Stale = 7
 34}
 35
 36/// <summary>Dispatches one medium-state's outgoing edges to the existing evaluators.</summary>
 37internal struct NavigationTraversalEdgeEnumerator
 38{
 39    private readonly NavigationWorldGraph? _graph;
 40    private readonly NavigationMediumStateRef _source;
 41    private NavigationSurfaceEdgeEnumerator _surfaceEdges;
 42    private TraversalEvaluator _surfaceEvaluator;
 43    private NavigationGraphEdge _surfaceEdge;
 44    private NavigationSurfaceEdgeRouteWork _surfaceRouteWork;
 45    private NavigationVolumeEdgeEvaluator _volumeEvaluator;
 46    private NavigationAutomaticSeamIndex.EndpointEnumerator _volumeSeams;
 47    private NavigationAutomaticSeamRef _volumeSeamLookahead;
 48    private NavigationTransitionPage.Enumerator _transitions;
 49    private NavigationTransitionEdgeEvaluator _transitionEvaluator;
 50    private NavigationPublishedTransition _definitionLookahead;
 51    private int _surfaceOrdinal;
 52    private int _volumeEdgeOrdinal;
 53    private int _transitionOrdinal;
 54    private uint _visitedVolumeDirections;
 55    private bool _surfaceRouteActive;
 56    private int _emittedSurfaceOrdinal;
 57    private bool _hasVolumeSeamLookahead;
 58    private bool _volumeSeamsComplete;
 59    private bool _allowTransitions;
 60    private bool _baseComplete;
 61    private bool _transitionStarted;
 62    private bool _hasPendingTransition;
 63    private bool _pendingIsRule;
 64    private bool _explicitTransitionsComplete;
 65    private bool _hasDefinitionLookahead;
 66    private bool _definitionNeedsDebit;
 67    private bool _hasLastRule;
 68    private NavigationCellAddress _lastRuleTargetAddress;
 69    private TraversalMedium _lastRuleTargetMedium;
 70    private TraversalTransitionType _lastRuleType;
 71    private string _lastRuleId;
 72    private TraversalTransitionRule _scannedRule;
 73    private NavigationMediumStateRef _scannedRuleTarget;
 74    private NavigationCellAddress _scannedRuleAddress;
 75    private int _ruleScanIndex;
 76    private int _ruleContactDirection;
 77    private bool _ruleScanActive;
 78    private bool _hasScannedRule;
 79
 80    internal NavigationTraversalEdgeEnumerator(
 81        GridWorld world,
 82        NavigationWorldGraph graph,
 83        NavigationMediumStateRef source,
 84        NavigationAgentProfile profile,
 85        NavigationAreaPolicy areaPolicy,
 86        NavigationRayWorkspace workspace,
 87        bool allowTransitions,
 88        int emittedSurfaceOrdinal)
 89    {
 992390        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 992391        SwiftThrowHelper.ThrowIfNull(graph, nameof(graph));
 992392        SwiftThrowHelper.ThrowIfNull(areaPolicy, nameof(areaPolicy));
 992393        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 992394        bool foundSourceAddress = graph.TryGetNodeAddress(
 992395            source.Node,
 992396            out NavigationCellAddress sourceAddress);
 97        System.Diagnostics.Debug.Assert(
 98            source.IsValid && foundSourceAddress,
 99            "Traversal enumerators own a valid source from their immutable graph.");
 9923100        _graph = graph;
 9923101        _source = source;
 9923102        _surfaceEdges = source.Medium == TraversalMedium.Solid
 9923103            ? graph.EnumerateSurfaceEdges(source.Node)
 9923104            : default;
 9923105        _surfaceEvaluator = source.Medium == TraversalMedium.Solid
 9923106            ? new TraversalEvaluator(graph, profile, areaPolicy, TraversalMedium.Solid)
 9923107            : default;
 9923108        _surfaceEdge = default;
 9923109        _surfaceRouteWork = default;
 9923110        _volumeEvaluator = source.Medium == TraversalMedium.Gas
 9923111            || source.Medium == TraversalMedium.Liquid
 9923112                ? new NavigationVolumeEdgeEvaluator(
 9923113                    world,
 9923114                    graph,
 9923115                    profile,
 9923116                    areaPolicy,
 9923117                    source.Medium,
 9923118                    workspace)
 9923119                : default;
 9923120        _volumeSeams = (source.Medium == TraversalMedium.Gas
 9923121                || source.Medium == TraversalMedium.Liquid)
 9923122                ? graph.AutomaticSeams.GetActiveEndpointEnumerator(sourceAddress)
 9923123                : default;
 9923124        _volumeSeamLookahead = default;
 9923125        _transitions = allowTransitions
 9923126            ? graph.EnumerateOutgoingTransitionCandidates(source)
 9923127            : default;
 9923128        _transitionEvaluator = allowTransitions
 9923129            ? new NavigationTransitionEdgeEvaluator(
 9923130                world,
 9923131                graph,
 9923132                profile,
 9923133                areaPolicy,
 9923134                workspace)
 9923135            : default;
 9923136        _definitionLookahead = default;
 9923137        _surfaceOrdinal = -1;
 9923138        _volumeEdgeOrdinal = -1;
 9923139        _transitionOrdinal = -1;
 9923140        _visitedVolumeDirections = 0;
 9923141        _surfaceRouteActive = false;
 9923142        _emittedSurfaceOrdinal = emittedSurfaceOrdinal;
 9923143        _hasVolumeSeamLookahead = false;
 9923144        _volumeSeamsComplete = source.Medium != TraversalMedium.Gas
 9923145            && source.Medium != TraversalMedium.Liquid;
 9923146        _allowTransitions = allowTransitions;
 9923147        _baseComplete = false;
 9923148        _transitionStarted = false;
 9923149        _hasPendingTransition = false;
 9923150        _pendingIsRule = false;
 9923151        _explicitTransitionsComplete = !allowTransitions;
 9923152        _hasDefinitionLookahead = false;
 9923153        _definitionNeedsDebit = false;
 9923154        _hasLastRule = false;
 9923155        _lastRuleTargetAddress = default;
 9923156        _lastRuleTargetMedium = default;
 9923157        _lastRuleType = default;
 9923158        _lastRuleId = string.Empty;
 9923159        _scannedRule = default;
 9923160        _scannedRuleTarget = default;
 9923161        _scannedRuleAddress = default;
 9923162        _ruleScanIndex = 0;
 9923163        _ruleContactDirection = -1;
 9923164        _ruleScanActive = false;
 9923165        _hasScannedRule = false;
 9923166        CurrentTarget = default;
 9923167        CurrentKind = default;
 9923168        CurrentOrdinal = -1;
 9923169        CurrentCost = default;
 9923170        CurrentSurfaceEdge = default;
 9923171        CurrentVolumeIsShortcut = false;
 9923172        CurrentTransitionId = string.Empty;
 9923173        CurrentTransitionOwnerMapId = string.Empty;
 9923174        CurrentTransitionType = default;
 9923175        CurrentTransitionHints = default;
 9923176        CurrentTransitionIdentityKind = default;
 9923177        CurrentTransitionSourceAction = default;
 9923178        CurrentTransitionDestinationAction = default;
 9923179        RequiresWorldStamp = source.Medium == TraversalMedium.Gas
 9923180            || source.Medium == TraversalMedium.Liquid;
 9923181    }
 182
 183    internal NavigationMediumStateRef CurrentTarget { get; private set; }
 184
 185    internal NavigationTraversalEdgeKind CurrentKind { get; private set; }
 186
 187    internal int CurrentOrdinal { get; private set; }
 188
 189    internal Fixed64 CurrentCost { get; private set; }
 190
 191    internal NavigationGraphEdge CurrentSurfaceEdge { get; private set; }
 192
 193    internal bool CurrentVolumeIsShortcut { get; private set; }
 194
 195    internal string CurrentTransitionId { get; private set; }
 196
 197    internal string CurrentTransitionOwnerMapId { get; private set; }
 198
 199    internal TraversalTransitionType CurrentTransitionType { get; private set; }
 200
 201    internal TraversalTransitionLocomotionHints CurrentTransitionHints { get; private set; }
 202
 203    internal NavigationTransitionIdentityKind CurrentTransitionIdentityKind { get; private set; }
 204
 205    internal Vector3d CurrentTransitionSourceAction { get; private set; }
 206
 207    internal Vector3d CurrentTransitionDestinationAction { get; private set; }
 208
 209    internal bool RequiresWorldStamp { get; private set; }
 210
 162211    internal bool RequiresConnectionProgress => _surfaceRouteActive
 162212        && _surfaceEdge.Kind == NavigationGraphEdgeKind.Explicit;
 213
 3842214    internal bool HasCurrentSurfacePoint => _surfaceRouteWork.HasCurrentPoint;
 215
 216    internal NavigationAStarGuidePoint CurrentSurfacePoint =>
 1815217        _surfaceRouteWork.CurrentPoint;
 218
 219    internal bool CurrentSurfacePointIsTargetFootAnchor =>
 1815220        _surfaceRouteWork.CurrentPointIsTargetFootAnchor;
 221
 1814222    internal void ConsumeCurrentSurfacePoint() => _surfaceRouteWork.ConsumePoint();
 223
 224    internal NavigationTraversalEdgeAdvanceStatus AdvanceOne(
 225        NavigationWorkMeter meter,
 226        NavigationDependencyWorkspace dependencies,
 227        ref int edgeStepRemaining,
 228        ref int connectionStepRemaining)
 229    {
 36076230        SwiftThrowHelper.ThrowIfNull(meter, nameof(meter));
 36076231        SwiftThrowHelper.ThrowIfNull(dependencies, nameof(dependencies));
 36076232        SwiftThrowHelper.ThrowIfNegative(edgeStepRemaining, nameof(edgeStepRemaining));
 36076233        SwiftThrowHelper.ThrowIfNegative(
 36076234            connectionStepRemaining,
 36076235            nameof(connectionStepRemaining));
 236        System.Diagnostics.Debug.Assert(_graph != null,
 237            "Traversal enumerators must be initialized with an immutable graph source.");
 36076238        if (_baseComplete)
 555239            return AdvanceTransition(meter, dependencies, ref edgeStepRemaining);
 35521240        if (_source.Medium == TraversalMedium.Gas
 35521241            || _source.Medium == TraversalMedium.Liquid)
 242        {
 2903243            return AdvanceVolume(meter, dependencies, ref edgeStepRemaining);
 244        }
 32618245        if (_surfaceRouteActive)
 246        {
 2306247            return AdvanceSurfaceRoute(
 2306248                meter,
 2306249                dependencies,
 2306250                ref connectionStepRemaining);
 251        }
 252
 30312253        NavigationSurfaceEdgeAdvanceStatus status = _surfaceEdges.AdvanceOne(
 30312254            meter,
 30312255            ref edgeStepRemaining);
 30312256        if (status == NavigationSurfaceEdgeAdvanceStatus.Complete)
 257        {
 2748258            _baseComplete = true;
 2748259            return AdvanceTransition(meter, dependencies, ref edgeStepRemaining);
 260        }
 27564261        if (status == NavigationSurfaceEdgeAdvanceStatus.Blocked)
 262        {
 161263            return edgeStepRemaining == 0 && meter.RemainingEvaluatedEdges > 0
 161264                ? NavigationTraversalEdgeAdvanceStatus.Blocked
 161265                : NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 266        }
 27403267        if (status != NavigationSurfaceEdgeAdvanceStatus.Edge)
 9297268            return NavigationTraversalEdgeAdvanceStatus.Pending;
 269
 18106270        _surfaceOrdinal = _surfaceEdges.CurrentOrdinal;
 18106271        _surfaceEdge = _surfaceEdges.Current;
 18106272        _graph!.TryGetNodeAddress(
 18106273            _surfaceEdge.Target,
 18106274            out NavigationCellAddress targetAddress);
 18106275        if (!dependencies.TryRecordPage(
 18106276                targetAddress.MapId,
 18106277                _surfaceEdge.Target.CellSlot / NavigationSemanticPage.SlotCount))
 278        {
 2279            return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 280        }
 18104281        if (_emittedSurfaceOrdinal >= 0)
 282        {
 980283            if (_surfaceOrdinal < _emittedSurfaceOrdinal)
 435284                return NavigationTraversalEdgeAdvanceStatus.Pending;
 285        }
 17669286        NavigationSurfaceEdgeRouteStatus begin = _surfaceRouteWork.Begin(
 17669287            _surfaceEvaluator,
 17669288            _source.Node,
 17669289            _surfaceEdge,
 17669290            emitPoints: _emittedSurfaceOrdinal >= 0);
 17669291        _surfaceRouteActive = begin == NavigationSurfaceEdgeRouteStatus.Pending;
 17669292        return _surfaceRouteActive
 17669293            ? AdvanceSurfaceRoute(
 17669294                meter,
 17669295                dependencies,
 17669296                ref connectionStepRemaining)
 17669297            : MapSurfaceRoute(begin);
 298    }
 299
 300    private NavigationTraversalEdgeAdvanceStatus AdvanceVolume(
 301        NavigationWorkMeter meter,
 302        NavigationDependencyWorkspace dependencies,
 303        ref int edgeStepRemaining)
 304    {
 3075305        while (TrySelectNextVolumeCandidate(
 3075306            out int directionOrdinal,
 3075307            out NavigationMediumStateRef target,
 3075308            out bool isPrimary,
 3075309            out NavigationAutomaticSeamRef seam,
 3075310            out bool hasSeam))
 311        {
 2673312            if (edgeStepRemaining == 0)
 313            {
 25314                return meter.RemainingEvaluatedEdges == 0
 25315                    ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded
 25316                    : NavigationTraversalEdgeAdvanceStatus.Blocked;
 317            }
 2648318            if (!meter.TryConsumeEvaluatedEdges(1))
 319            {
 1320                return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 321            }
 2647322            meter.RecordVolumeCandidate(isPrimary);
 2647323            edgeStepRemaining--;
 2647324            if (hasSeam)
 325            {
 74326                _volumeSeamLookahead = default;
 74327                _hasVolumeSeamLookahead = false;
 328            }
 329            else
 330            {
 2573331                _visitedVolumeDirections |= 1U << directionOrdinal;
 332            }
 2647333            _volumeEdgeOrdinal++;
 2647334            NavigationVolumeEdgeStatus status = _volumeEvaluator.Evaluate(
 2647335                _source,
 2647336                target,
 2647337                isPrimary,
 2647338                seam,
 2647339                hasSeam,
 2647340                meter,
 2647341                dependencies,
 2647342                out Fixed64 cost);
 2647343            if (status == NavigationVolumeEdgeStatus.BudgetExceeded)
 2344                return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 2645345            if (status == NavigationVolumeEdgeStatus.CapacityExceeded)
 2346                return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 2643347            if (status == NavigationVolumeEdgeStatus.CostOverflow)
 2348                return NavigationTraversalEdgeAdvanceStatus.CostOverflow;
 2641349            if (status == NavigationVolumeEdgeStatus.Stale)
 4350                return NavigationTraversalEdgeAdvanceStatus.Stale;
 2637351            if (status != NavigationVolumeEdgeStatus.Passable)
 352                continue;
 353
 2465354            CurrentTarget = target;
 2465355            CurrentKind = NavigationTraversalEdgeKind.Volume;
 2465356            CurrentSurfaceEdge = default;
 2465357            CurrentVolumeIsShortcut = !isPrimary;
 2465358            CurrentOrdinal = _volumeEdgeOrdinal;
 2465359            CurrentCost = cost;
 2465360            return NavigationTraversalEdgeAdvanceStatus.Edge;
 361        }
 402362        _baseComplete = true;
 402363        return AdvanceTransition(meter, dependencies, ref edgeStepRemaining);
 364    }
 365
 366    private NavigationTraversalEdgeAdvanceStatus AdvanceTransition(
 367        NavigationWorkMeter meter,
 368        NavigationDependencyWorkspace dependencies,
 369        ref int edgeStepRemaining)
 370    {
 3705371        if (!_allowTransitions)
 2939372            return Complete();
 766373        if (!_transitionStarted)
 374        {
 211375            dependencies.RecordTransitionDependency();
 211376            _graph!.TryGetNodeAddress(
 211377                _source.Node,
 211378                out NavigationCellAddress sourceAddress);
 211379            if (!dependencies.TryRecordPage(
 211380                    sourceAddress.MapId,
 211381                    _source.Node.CellSlot / NavigationSemanticPage.SlotCount))
 382            {
 1383                return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 384            }
 210385            _transitionStarted = true;
 386        }
 387
 765388        if (!_hasPendingTransition)
 389        {
 543390            NavigationTraversalEdgeAdvanceStatus prepare = PrepareNextTransition(
 543391                meter,
 543392                ref edgeStepRemaining);
 543393            if (prepare != NavigationTraversalEdgeAdvanceStatus.Pending
 543394                || !_hasPendingTransition)
 395            {
 326396                return prepare;
 397            }
 217398            return NavigationTraversalEdgeAdvanceStatus.Pending;
 399        }
 400
 222401        if (edgeStepRemaining == 0)
 402        {
 6403            return meter.RemainingTransitionPairs == 0
 6404                ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded
 6405                : NavigationTraversalEdgeAdvanceStatus.Blocked;
 406        }
 216407        if (!meter.TryConsumeTransitionPairs(1))
 1408            return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 215409        edgeStepRemaining--;
 215410        NavigationPublishedTransition transition = _definitionLookahead;
 215411        TraversalTransitionRule rule = _scannedRule;
 215412        NavigationMediumStateRef ruleTarget = _scannedRuleTarget;
 215413        bool isRule = _pendingIsRule;
 215414        _hasPendingTransition = false;
 215415        _pendingIsRule = false;
 215416        _hasScannedRule = false;
 215417        _scannedRule = default;
 215418        _scannedRuleTarget = default;
 215419        _scannedRuleAddress = default;
 215420        if (!isRule)
 421        {
 101422            _definitionLookahead = default;
 101423            _hasDefinitionLookahead = false;
 424        }
 215425        _transitionOrdinal++;
 426        NavigationTransitionEdgeStatus status;
 427        NavigationMediumStateRef target;
 428        NavigationTransitionEdgeEvidence evidence;
 215429        if (isRule)
 430        {
 114431            target = ruleTarget;
 114432            status = _transitionEvaluator.EvaluateRule(
 114433                _source,
 114434                target,
 114435                rule,
 114436                meter,
 114437                dependencies,
 114438                out evidence);
 439        }
 440        else
 441        {
 101442            status = _transitionEvaluator.EvaluateDefinition(
 101443                _source,
 101444                transition,
 101445                meter,
 101446                dependencies,
 101447                out target,
 101448                out evidence);
 449        }
 215450        if (status == NavigationTransitionEdgeStatus.BudgetExceeded)
 2451            return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 213452        if (status == NavigationTransitionEdgeStatus.CapacityExceeded)
 2453            return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 211454        if (status == NavigationTransitionEdgeStatus.CostOverflow)
 1455            return NavigationTraversalEdgeAdvanceStatus.CostOverflow;
 210456        if (status == NavigationTransitionEdgeStatus.Stale)
 1457            return NavigationTraversalEdgeAdvanceStatus.Stale;
 209458        if (status != NavigationTransitionEdgeStatus.Passable)
 62459            return NavigationTraversalEdgeAdvanceStatus.Pending;
 460
 147461        CurrentTarget = target;
 147462        CurrentKind = NavigationTraversalEdgeKind.Transition;
 147463        CurrentSurfaceEdge = default;
 147464        CurrentVolumeIsShortcut = false;
 147465        CurrentOrdinal = (_source.Medium == TraversalMedium.Solid
 147466            ? _surfaceOrdinal
 147467            : _volumeEdgeOrdinal) + 1 + _transitionOrdinal;
 147468        CurrentCost = evidence.Cost;
 147469        CurrentTransitionId = isRule ? rule.Id : transition.Owner.TransitionId;
 147470        CurrentTransitionOwnerMapId = isRule ? string.Empty : transition.Owner.MapId;
 147471        CurrentTransitionType = isRule ? rule.Type : transition.Definition.Type;
 147472        CurrentTransitionHints = isRule
 147473            ? rule.LocomotionHints
 147474            : transition.Definition.LocomotionHints;
 147475        CurrentTransitionIdentityKind = isRule
 147476            ? NavigationTransitionIdentityKind.Rule
 147477            : NavigationTransitionIdentityKind.Definition;
 147478        CurrentTransitionSourceAction = evidence.SourceAction;
 147479        CurrentTransitionDestinationAction = evidence.DestinationAction;
 147480        return NavigationTraversalEdgeAdvanceStatus.Edge;
 481    }
 482
 483    private NavigationTraversalEdgeAdvanceStatus PrepareNextTransition(
 484        NavigationWorkMeter meter,
 485        ref int edgeStepRemaining)
 486    {
 543487        bool hasDefinition = _hasDefinitionLookahead;
 543488        NavigationPublishedTransition definition = _definitionLookahead;
 543489        if (!hasDefinition && !_explicitTransitionsComplete)
 490        {
 252491            if (_transitions.MoveNext())
 492            {
 104493                _definitionLookahead = _transitions.Current;
 104494                MarkWorldDependency(
 104495                    _definitionLookahead.Definition.DestinationMedium);
 104496                _hasDefinitionLookahead = true;
 104497                _definitionNeedsDebit = true;
 104498                hasDefinition = true;
 104499                definition = _definitionLookahead;
 500            }
 501            else
 502            {
 148503                _explicitTransitionsComplete = true;
 504            }
 505        }
 543506        if (_definitionNeedsDebit)
 507        {
 105508            if (!TryConsumeTransitionCandidate(
 105509                    meter,
 105510                    ref edgeStepRemaining,
 105511                    out NavigationTraversalEdgeAdvanceStatus blocked))
 512            {
 3513                return blocked;
 514            }
 102515            _definitionNeedsDebit = false;
 516        }
 540517        if (!_ruleScanActive)
 518        {
 332519            _ruleScanActive = true;
 332520            _ruleScanIndex = 0;
 332521            _hasScannedRule = false;
 332522            _scannedRule = default;
 332523            _scannedRuleTarget = default;
 332524            _scannedRuleAddress = default;
 332525            _ruleContactDirection = -1;
 526        }
 1645527        while (_ruleScanIndex < _graph!.TransitionRules.Count)
 528        {
 1315529            TraversalTransitionRule candidate = _graph.TransitionRules[_ruleScanIndex];
 1315530            MarkWorldDependency(candidate.DestinationMedium);
 1315531            if (_ruleContactDirection < 0)
 532            {
 325533                if (!TryConsumeTransitionCandidate(
 325534                        meter,
 325535                        ref edgeStepRemaining,
 325536                        out NavigationTraversalEdgeAdvanceStatus blocked))
 537                {
 25538                    return blocked;
 539                }
 300540                if (candidate.SourceMedium != _source.Medium)
 541                {
 75542                    CompleteRuleContactScan();
 75543                    continue;
 544                }
 225545                _ruleContactDirection = 0;
 546            }
 1215547            if (candidate.Scope == TraversalTransitionRuleScope.SameCell)
 548            {
 123549                if (!TryConsumeTransitionCandidate(
 123550                        meter,
 123551                        ref edgeStepRemaining,
 123552                        out NavigationTraversalEdgeAdvanceStatus blocked))
 553                {
 16554                    return blocked;
 555                }
 107556                bool foundCandidateAddress = _graph.TryGetNodeAddress(
 107557                    _source.Node,
 107558                    out NavigationCellAddress candidateAddress);
 559                System.Diagnostics.Debug.Assert(foundCandidateAddress,
 560                    "Same-cell rules retain their immutable graph source address.");
 107561                ConsiderRuleCandidate(
 107562                    candidate,
 107563                    new NavigationMediumStateRef(
 107564                        _source.Node,
 107565                        candidate.DestinationMedium),
 107566                    candidateAddress,
 107567                    ref _hasScannedRule,
 107568                    ref _scannedRule,
 107569                    ref _scannedRuleTarget,
 107570                    ref _scannedRuleAddress);
 107571                CompleteRuleContactScan();
 107572                continue;
 573            }
 1092574            int primaryCount = _graph.GetPrimaryDirectionCount(_source.Node);
 1092575            if (_ruleContactDirection < primaryCount)
 576            {
 810577                if (!TryConsumeTransitionCandidate(
 810578                        meter,
 810579                        ref edgeStepRemaining,
 810580                        out NavigationTraversalEdgeAdvanceStatus blocked))
 581                {
 102582                    return blocked;
 583                }
 708584                int direction = _ruleContactDirection++;
 708585                if (_graph.TryGetPrimaryNeighbor(
 708586                        _source.Node,
 708587                        direction,
 708588                        out NavigationNodeRef candidateNode))
 589                {
 90590                    bool foundCandidateAddress = _graph.TryGetNodeAddress(
 90591                        candidateNode,
 90592                        out NavigationCellAddress candidateAddress);
 593                    System.Diagnostics.Debug.Assert(foundCandidateAddress,
 594                        "Primary neighbors retain an address in their immutable graph.");
 90595                    ConsiderRuleCandidate(
 90596                        candidate,
 90597                        new NavigationMediumStateRef(
 90598                            candidateNode,
 90599                            candidate.DestinationMedium),
 90600                        candidateAddress,
 90601                        ref _hasScannedRule,
 90602                        ref _scannedRule,
 90603                        ref _scannedRuleTarget,
 90604                        ref _scannedRuleAddress);
 605                }
 90606                continue;
 607            }
 282608            if (_ruleContactDirection == primaryCount)
 609            {
 118610                bool foundSeamSource = _graph.TryGetNodeAddress(
 118611                    _source.Node,
 118612                    out NavigationCellAddress seamSourceAddress);
 613                System.Diagnostics.Debug.Assert(foundSeamSource,
 614                    "Volume rule scans retain their immutable graph source address.");
 118615                _volumeSeams = _graph.AutomaticSeams.GetActiveEndpointEnumerator(
 118616                    seamSourceAddress);
 118617                _volumeSeamLookahead = default;
 118618                _hasVolumeSeamLookahead = false;
 118619                _volumeSeamsComplete = false;
 118620                _ruleContactDirection++;
 621            }
 622            System.Diagnostics.Debug.Assert(!_volumeSeamsComplete,
 623                "Completed volume-seam contact scans advance to the next transition rule immediately.");
 282624            if (edgeStepRemaining == 0)
 66625                return NavigationTraversalEdgeAdvanceStatus.Blocked;
 216626            if (!_volumeSeams.MoveNext())
 627            {
 116628                _volumeSeamsComplete = true;
 116629                CompleteRuleContactScan();
 116630                continue;
 631            }
 100632            if (!TryConsumeTransitionCandidate(
 100633                    meter,
 100634                    ref edgeStepRemaining,
 100635                    out NavigationTraversalEdgeAdvanceStatus seamBlocked))
 636            {
 1637                return seamBlocked;
 638            }
 99639            NavigationAutomaticSeamRef seam = _volumeSeams.Current;
 99640            bool foundSeamTarget = _graph.TryGetNodeRef(
 99641                seam.Destination,
 99642                out NavigationNodeRef seamTarget);
 643            System.Diagnostics.Debug.Assert(foundSeamTarget,
 644                "Active seam rows reference published graph nodes.");
 99645            ConsiderRuleCandidate(
 99646                candidate,
 99647                new NavigationMediumStateRef(
 99648                    seamTarget,
 99649                    candidate.DestinationMedium),
 99650                seam.Destination,
 99651                ref _hasScannedRule,
 99652                ref _scannedRule,
 99653                ref _scannedRuleTarget,
 99654                ref _scannedRuleAddress);
 655        }
 656
 330657        bool hasRule = _hasScannedRule;
 330658        TraversalTransitionRule selectedRule = _scannedRule;
 330659        NavigationCellAddress selectedRuleAddress = _scannedRuleAddress;
 330660        _ruleScanActive = false;
 330661        _ruleScanIndex = 0;
 330662        _ruleContactDirection = -1;
 663
 330664        if (!hasDefinition && !hasRule)
 113665            return Complete();
 217666        bool selectRule = hasRule && (!hasDefinition
 217667            || CompareTransitionKey(
 217668                selectedRuleAddress,
 217669                selectedRule.DestinationMedium,
 217670                selectedRule.Type,
 217671                NavigationTransitionIdentityKind.Rule,
 217672                selectedRule.Id,
 217673                definition.Definition.Destination,
 217674                definition.Definition.DestinationMedium,
 217675                definition.Definition.Type,
 217676                NavigationTransitionIdentityKind.Definition,
 217677                definition.Owner.TransitionId) < 0);
 217678        _hasPendingTransition = true;
 217679        _pendingIsRule = selectRule;
 217680        if (selectRule)
 681        {
 116682            _hasLastRule = true;
 116683            _lastRuleTargetAddress = selectedRuleAddress;
 116684            _lastRuleTargetMedium = selectedRule.DestinationMedium;
 116685            _lastRuleType = selectedRule.Type;
 116686            _lastRuleId = selectedRule.Id;
 687        }
 688        else
 689        {
 101690            _definitionLookahead = definition;
 691        }
 217692        return NavigationTraversalEdgeAdvanceStatus.Pending;
 693    }
 694
 695    private void CompleteRuleContactScan()
 696    {
 298697        _ruleScanIndex++;
 298698        _ruleContactDirection = -1;
 298699        _volumeSeamLookahead = default;
 298700        _hasVolumeSeamLookahead = false;
 298701        _volumeSeamsComplete = true;
 298702    }
 703
 704    private void ConsiderRuleCandidate(
 705        TraversalTransitionRule candidate,
 706        NavigationMediumStateRef candidateTarget,
 707        NavigationCellAddress candidateAddress,
 708        ref bool hasRule,
 709        ref TraversalTransitionRule selectedRule,
 710        ref NavigationMediumStateRef selectedRuleTarget,
 711        ref NavigationCellAddress selectedRuleAddress)
 712    {
 296713        if (!IsAfterLastRule(candidateAddress, candidate)
 296714            || (hasRule
 296715                && CompareTransitionKey(
 296716                    selectedRuleAddress,
 296717                    selectedRule.DestinationMedium,
 296718                    selectedRule.Type,
 296719                    NavigationTransitionIdentityKind.Rule,
 296720                    selectedRule.Id,
 296721                    candidateAddress,
 296722                    candidate.DestinationMedium,
 296723                    candidate.Type,
 296724                    NavigationTransitionIdentityKind.Rule,
 296725                    candidate.Id) <= 0))
 726        {
 161727            return;
 728        }
 135729        hasRule = true;
 135730        selectedRule = candidate;
 135731        selectedRuleTarget = candidateTarget;
 135732        selectedRuleAddress = candidateAddress;
 135733    }
 734
 735    private bool IsAfterLastRule(
 736        NavigationCellAddress target,
 296737        TraversalTransitionRule rule) => !_hasLastRule
 296738        || CompareTransitionKey(
 296739            _lastRuleTargetAddress,
 296740            _lastRuleTargetMedium,
 296741            _lastRuleType,
 296742            NavigationTransitionIdentityKind.Rule,
 296743            _lastRuleId,
 296744            target,
 296745            rule.DestinationMedium,
 296746            rule.Type,
 296747            NavigationTransitionIdentityKind.Rule,
 296748            rule.Id) < 0;
 749
 750    internal static int CompareTransitionKey(
 751        NavigationCellAddress leftAddress,
 752        TraversalMedium leftMedium,
 753        TraversalTransitionType leftType,
 754        NavigationTransitionIdentityKind leftKind,
 755        string leftId,
 756        NavigationCellAddress rightAddress,
 757        TraversalMedium rightMedium,
 758        TraversalTransitionType rightType,
 759        NavigationTransitionIdentityKind rightKind,
 760        string rightId)
 761    {
 290762        int comparison = leftAddress.CompareTo(rightAddress);
 290763        if (comparison != 0)
 162764            return comparison;
 128765        comparison = ((int)leftMedium).CompareTo((int)rightMedium);
 128766        if (comparison != 0)
 3767            return comparison;
 125768        comparison = ((int)leftType).CompareTo((int)rightType);
 125769        if (comparison != 0)
 20770            return comparison;
 105771        comparison = ((int)leftKind).CompareTo((int)rightKind);
 105772        return comparison != 0 ? comparison : string.CompareOrdinal(leftId, rightId);
 773    }
 774
 775    internal static bool TryConsumeTransitionCandidate(
 776        NavigationWorkMeter meter,
 777        ref int edgeStepRemaining,
 778        out NavigationTraversalEdgeAdvanceStatus blocked)
 779    {
 1784780        if (edgeStepRemaining == 0)
 781        {
 189782            blocked = meter.RemainingTransitionCandidates == 0
 189783                ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded
 189784                : NavigationTraversalEdgeAdvanceStatus.Blocked;
 189785            return false;
 786        }
 1595787        if (!meter.TryConsumeTransitionCandidates(1))
 788        {
 4789            blocked = NavigationTraversalEdgeAdvanceStatus.BudgetExceeded;
 4790            return false;
 791        }
 1591792        edgeStepRemaining--;
 1591793        blocked = default;
 1591794        return true;
 795    }
 796
 797    private bool TrySelectNextVolumeCandidate(
 798        out int directionOrdinal,
 799        out NavigationMediumStateRef target,
 800        out bool isPrimary,
 801        out NavigationAutomaticSeamRef seam,
 802        out bool hasSeam)
 803    {
 3075804        directionOrdinal = -1;
 3075805        target = default;
 3075806        isPrimary = false;
 3075807        seam = default;
 3075808        hasSeam = false;
 3075809        NavigationCellAddress selectedAddress = default;
 3075810        int directionCount = _graph!.GetCompleteDirectionCount(_source.Node);
 151890811        for (int candidateOrdinal = 0; candidateOrdinal < directionCount; candidateOrdinal++)
 812        {
 72870813            if ((_visitedVolumeDirections & (1U << candidateOrdinal)) != 0
 72870814                || !_graph.TryGetCompleteNeighbor(
 72870815                    _source.Node,
 72870816                    candidateOrdinal,
 72870817                    out NavigationNodeRef candidateNode,
 72870818                    out bool candidateIsPrimary))
 819            {
 820                continue;
 821            }
 16762822            bool foundCandidateAddress = _graph.TryGetNodeAddress(
 16762823                candidateNode,
 16762824                out NavigationCellAddress candidateAddress);
 825            System.Diagnostics.Debug.Assert(foundCandidateAddress,
 826                "Complete neighbors retain an address in their immutable graph.");
 16762827            if (directionOrdinal >= 0
 16762828                && selectedAddress.CompareTo(candidateAddress) <= 0)
 829            {
 830                continue;
 831            }
 832
 6267833            directionOrdinal = candidateOrdinal;
 6267834            target = new NavigationMediumStateRef(candidateNode, _source.Medium);
 6267835            isPrimary = candidateIsPrimary;
 6267836            selectedAddress = candidateAddress;
 837        }
 838
 3075839        if (!_hasVolumeSeamLookahead && !_volumeSeamsComplete)
 840        {
 799841            if (_volumeSeams.MoveNext())
 842            {
 74843                _volumeSeamLookahead = _volumeSeams.Current;
 74844                _hasVolumeSeamLookahead = true;
 845            }
 846            else
 847            {
 725848                _volumeSeamsComplete = true;
 849            }
 850        }
 3075851        if (_hasVolumeSeamLookahead)
 852        {
 124853            bool foundSeamTarget = _graph.TryGetNodeRef(
 124854                _volumeSeamLookahead.Destination,
 124855                out NavigationNodeRef seamTarget);
 856            System.Diagnostics.Debug.Assert(foundSeamTarget,
 857                "Active seam rows reference published graph nodes.");
 124858            if (directionOrdinal < 0
 124859                || _volumeSeamLookahead.Destination.CompareTo(selectedAddress) < 0)
 860            {
 96861                directionOrdinal = 0;
 96862                target = new NavigationMediumStateRef(seamTarget, _source.Medium);
 96863                isPrimary = true;
 96864                seam = _volumeSeamLookahead;
 96865                hasSeam = true;
 866            }
 867        }
 3075868        return directionOrdinal >= 0;
 869    }
 870
 871    private NavigationTraversalEdgeAdvanceStatus AdvanceSurfaceRoute(
 872        NavigationWorkMeter meter,
 873        NavigationDependencyWorkspace dependencies,
 874        ref int connectionStepRemaining)
 875    {
 19971876        NavigationSurfaceEdgeRouteStatus status = _surfaceRouteWork.Advance(
 19971877            meter,
 19971878            ref connectionStepRemaining);
 19971879        if (_surfaceRouteWork.TryTakeDependencyNode(out NavigationNodeRef dependencyNode))
 880        {
 402881            bool foundDependencyAddress = _graph!.TryGetNodeAddress(
 402882                dependencyNode,
 402883                out NavigationCellAddress dependencyAddress);
 884            System.Diagnostics.Debug.Assert(foundDependencyAddress,
 885                "Explicit route dependencies are nodes in their immutable graph.");
 402886            if ((!dependencyNode.Equals(_surfaceEdge.Target)
 402887                    && _graph.TryGetSurfaceComponent(
 402888                        dependencyAddress,
 402889                        TraversalMedium.Solid,
 402890                        out NavigationSurfaceComponentKey dependencyComponent,
 402891                        out _)
 402892                    && !dependencies.TryRecordComponent(dependencyComponent))
 402893                || !dependencies.TryRecordPage(
 402894                    dependencyAddress.MapId,
 402895                    dependencyNode.CellSlot / NavigationSemanticPage.SlotCount))
 896            {
 2897                _surfaceRouteActive = false;
 2898                return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded;
 899            }
 900        }
 19969901        if (status == NavigationSurfaceEdgeRouteStatus.Point)
 2089902            return NavigationTraversalEdgeAdvanceStatus.Pending;
 17880903        if (status == NavigationSurfaceEdgeRouteStatus.Pending)
 904        {
 218905            return connectionStepRemaining == 0
 218906                && meter.RemainingConnectionLegs > 0
 218907                    ? NavigationTraversalEdgeAdvanceStatus.Blocked
 218908                    : NavigationTraversalEdgeAdvanceStatus.Pending;
 909        }
 17662910        _surfaceRouteActive = false;
 17662911        return status == NavigationSurfaceEdgeRouteStatus.Passable
 17662912            ? SetCurrent(_surfaceRouteWork.Cost)
 17662913            : MapSurfaceRoute(status);
 914    }
 915
 916    private static NavigationTraversalEdgeAdvanceStatus MapSurfaceRoute(
 48917        NavigationSurfaceEdgeRouteStatus status) => status switch
 48918        {
 48919            NavigationSurfaceEdgeRouteStatus.BudgetExceeded =>
 7920                NavigationTraversalEdgeAdvanceStatus.BudgetExceeded,
 48921            NavigationSurfaceEdgeRouteStatus.CostOverflow =>
 3922                NavigationTraversalEdgeAdvanceStatus.CostOverflow,
 38923            _ => NavigationTraversalEdgeAdvanceStatus.Pending
 48924        };
 925
 926    private NavigationTraversalEdgeAdvanceStatus SetCurrent(Fixed64 cost)
 927    {
 17618928        CurrentTarget = new NavigationMediumStateRef(
 17618929            _surfaceEdge.Target,
 17618930            TraversalMedium.Solid);
 17618931        CurrentKind = NavigationTraversalEdgeKind.Surface;
 17618932        CurrentOrdinal = _surfaceOrdinal;
 17618933        CurrentCost = cost;
 17618934        CurrentSurfaceEdge = _surfaceEdge;
 17618935        CurrentVolumeIsShortcut = false;
 17618936        return NavigationTraversalEdgeAdvanceStatus.Edge;
 937    }
 938
 939    private void MarkWorldDependency(TraversalMedium medium)
 940    {
 1419941        if (medium == TraversalMedium.Gas || medium == TraversalMedium.Liquid)
 1386942            RequiresWorldStamp = true;
 1419943    }
 944
 945    private NavigationTraversalEdgeAdvanceStatus Complete()
 946    {
 3052947        CurrentTarget = default;
 3052948        CurrentKind = default;
 3052949        CurrentOrdinal = -1;
 3052950        CurrentCost = default;
 3052951        CurrentSurfaceEdge = default;
 3052952        CurrentVolumeIsShortcut = false;
 3052953        CurrentTransitionId = string.Empty;
 3052954        CurrentTransitionOwnerMapId = string.Empty;
 3052955        CurrentTransitionType = default;
 3052956        CurrentTransitionHints = default;
 3052957        CurrentTransitionIdentityKind = default;
 3052958        CurrentTransitionSourceAction = default;
 3052959        CurrentTransitionDestinationAction = default;
 3052960        return NavigationTraversalEdgeAdvanceStatus.Complete;
 961    }
 962}

Methods/Properties

.ctor(GridForge.Grids.GridWorld,Trailblazer.Pathing.NavigationWorldGraph,Trailblazer.Pathing.NavigationMediumStateRef,Trailblazer.Pathing.NavigationAgentProfile,Trailblazer.Pathing.NavigationAreaPolicy,Trailblazer.Pathing.NavigationRayWorkspace,System.Boolean,System.Int32)
get_RequiresConnectionProgress()
get_HasCurrentSurfacePoint()
get_CurrentSurfacePoint()
get_CurrentSurfacePointIsTargetFootAnchor()
ConsumeCurrentSurfacePoint()
AdvanceOne(Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,System.Int32&,System.Int32&)
AdvanceVolume(Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,System.Int32&)
AdvanceTransition(Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,System.Int32&)
PrepareNextTransition(Trailblazer.Pathing.NavigationWorkMeter,System.Int32&)
CompleteRuleContactScan()
ConsiderRuleCandidate(Trailblazer.Pathing.TraversalTransitionRule,Trailblazer.Pathing.NavigationMediumStateRef,Trailblazer.Pathing.NavigationCellAddress,System.Boolean&,Trailblazer.Pathing.TraversalTransitionRule&,Trailblazer.Pathing.NavigationMediumStateRef&,Trailblazer.Pathing.NavigationCellAddress&)
IsAfterLastRule(Trailblazer.Pathing.NavigationCellAddress,Trailblazer.Pathing.TraversalTransitionRule)
CompareTransitionKey(Trailblazer.Pathing.NavigationCellAddress,Trailblazer.TraversalMedium,Trailblazer.Pathing.TraversalTransitionType,Trailblazer.Pathing.NavigationTransitionIdentityKind,System.String,Trailblazer.Pathing.NavigationCellAddress,Trailblazer.TraversalMedium,Trailblazer.Pathing.TraversalTransitionType,Trailblazer.Pathing.NavigationTransitionIdentityKind,System.String)
TryConsumeTransitionCandidate(Trailblazer.Pathing.NavigationWorkMeter,System.Int32&,Trailblazer.Pathing.NavigationTraversalEdgeAdvanceStatus&)
TrySelectNextVolumeCandidate(System.Int32&,Trailblazer.Pathing.NavigationMediumStateRef&,System.Boolean&,Trailblazer.Pathing.NavigationAutomaticSeamRef&,System.Boolean&)
AdvanceSurfaceRoute(Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,System.Int32&)
MapSurfaceRoute(Trailblazer.Pathing.NavigationSurfaceEdgeRouteStatus)
SetCurrent(FixedMathSharp.Fixed64)
MarkWorldDependency(Trailblazer.TraversalMedium)
Complete()