< Summary

Information
Class: Trailblazer.Pathing.NavigationEndpointResolutionWork
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/AStar/NavigationEndpointResolutionWork.cs
Line coverage
100%
Covered lines: 453
Uncovered lines: 0
Coverable lines: 453
Total lines: 722
Line coverage: 100%
Branch coverage
100%
Covered branches: 208
Total branches: 208
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Begin(...)100%1212100%
Reset()100%11100%
get_RequiresWorldStamp()100%11100%
Advance(...)100%5252100%
DiscoverGeneration()100%44100%
ConsiderCandidate(...)100%2626100%
AdvanceCandidateRay()100%88100%
TryMergeRayDependencies()100%88100%
TryAcceptPendingVolumeFallback()100%22100%
SelectPreferredCandidate(...)100%88100%
AcceptCandidate(...)100%11100%
TryGetBounds(...)100%1414100%
Finish(...)100%1616100%
AreDependenciesCurrent()100%1818100%
TryQualifyCandidate(...)100%1818100%
ConsiderMedium(...)100%1414100%
RecordResultComponents()100%66100%
FirstVolumeMedium(...)100%22100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/AStar/NavigationEndpointResolutionWork.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationEndpointResolutionWork.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 FixedMathSharp;
 10using GridForge.Grids;
 11using GridForge.Grids.Topology;
 12
 13namespace Trailblazer.Pathing;
 14
 15/// <summary>Reports bounded endpoint-resolution progress.</summary>
 16internal enum NavigationEndpointResolutionStatus : byte
 17{
 18    Pending = 0,
 19    Success = 1,
 20    NoMap = 2,
 21    InvalidEndpoint = 3,
 22    BudgetExceeded = 4,
 23    CostOverflow = 5,
 24    CapacityExceeded = 6,
 25    Stale = 7
 26}
 27
 28/// <summary>Identifies the directed query endpoint being resolved.</summary>
 29internal enum NavigationEndpointRole : byte
 30{
 31    Start,
 32    Destination
 33}
 34
 35/// <summary>Identifies one exact resolved graph endpoint.</summary>
 36internal readonly struct NavigationResolvedEndpoint
 37{
 38    internal NavigationResolvedEndpoint(
 39        NavigationNodeRef node,
 40        NavigationCellAddress address,
 41        TraversalMedia media,
 42        TraversalMedium resolutionMedium,
 43        Vector3d footAnchor,
 44        Fixed64 resolutionDistance)
 45    {
 46        Node = node;
 47        Address = address;
 48        Media = media;
 49        ResolutionMedium = resolutionMedium;
 50        FootAnchor = footAnchor;
 51        ResolutionDistance = resolutionDistance;
 52    }
 53
 54    internal NavigationNodeRef Node { get; }
 55    internal NavigationCellAddress Address { get; }
 56    internal TraversalMedia Media { get; }
 57    internal TraversalMedium ResolutionMedium { get; }
 58    internal Vector3d FootAnchor { get; }
 59    internal Fixed64 ResolutionDistance { get; }
 60}
 61
 62/// <summary>Resolves one endpoint through bounded GridForge coverage and immutable graph filtering.</summary>
 63internal sealed class NavigationEndpointResolutionWork
 64{
 65    private readonly GridWorld _world;
 66    private readonly NavigationWorldGraphStore _store;
 67    private readonly NavigationWorkMeter _meter;
 68    private readonly NavigationEndpointWorkspace _workspace;
 69    private readonly NavigationRayWorkspace _rayWorkspace;
 70    private readonly NavigationRayWork _rayWork;
 71    private NavigationWorldGraph _graph = null!;
 72    private NavigationEndpoint _endpoint;
 73    private TraversalEvaluator _evaluator;
 74    private NavigationVolumeAnchorEvaluator _volumeEvaluator;
 75    private TraversalMedia _media;
 76    private NavigationEndpointRole _role;
 77    private NavigationResolvedEndpoint _pendingCandidate;
 78    private int _mapOrdinal;
 79    private int _generationInputOrdinal;
 80    private ulong _worldChangeSequence;
 81    private bool _discoveryComplete;
 82    private bool _cursorBegun;
 83    private bool _cursorComplete;
 84    private bool _hasResult;
 85    private bool _requiresWorldStamp;
 86
 1586587    internal NavigationEndpointResolutionWork(
 1586588        GridWorld world,
 1586589        NavigationWorldGraphStore store,
 1586590        NavigationWorkMeter meter,
 1586591        NavigationEndpointWorkspace workspace,
 1586592        NavigationRayWorkspace rayWorkspace,
 1586593        NavigationRayWork rayWork)
 94    {
 1586595        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 1586596        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 1586597        SwiftThrowHelper.ThrowIfNull(meter, nameof(meter));
 1586598        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 1586599        SwiftThrowHelper.ThrowIfNull(rayWorkspace, nameof(rayWorkspace));
 15865100        SwiftThrowHelper.ThrowIfNull(rayWork, nameof(rayWork));
 15865101        _world = world;
 15865102        _store = store;
 15865103        _meter = meter;
 15865104        _workspace = workspace;
 15865105        _rayWorkspace = rayWorkspace;
 15865106        _rayWork = rayWork;
 15865107    }
 108
 109    internal void Begin(
 110        NavigationWorldGraph graph,
 111        NavigationEndpoint endpoint,
 112        NavigationEndpointRole role,
 113        NavigationAgentProfile profile,
 114        NavigationAreaPolicy areaPolicy,
 115        TraversalMedia media)
 116    {
 1334117        SwiftThrowHelper.ThrowIfNull(graph, nameof(graph));
 1334118        SwiftThrowHelper.ThrowIfNull(areaPolicy, nameof(areaPolicy));
 1334119        SwiftThrowHelper.ThrowIfArgument(
 1334120            role is not NavigationEndpointRole.Start
 1334121                and not NavigationEndpointRole.Destination,
 1334122            nameof(role),
 1334123            "Endpoint role must be start or destination.");
 1334124        _graph = graph;
 1334125        _endpoint = endpoint;
 1334126        _role = role;
 1334127        _media = media;
 1334128        _evaluator = new TraversalEvaluator(
 1334129            graph,
 1334130            profile,
 1334131            areaPolicy,
 1334132            TraversalMedium.Solid);
 1334133        _volumeEvaluator = new NavigationVolumeAnchorEvaluator(
 1334134            _world,
 1334135            graph,
 1334136            profile,
 1334137            areaPolicy,
 1334138            _rayWorkspace);
 1334139        _pendingCandidate = default;
 1334140        _mapOrdinal = 0;
 1334141        _generationInputOrdinal = 0;
 1334142        _worldChangeSequence = _world.ChangeSequence;
 1334143        _cursorBegun = false;
 1334144        _cursorComplete = false;
 1334145        _hasResult = false;
 1334146        _requiresWorldStamp = false;
 1334147        Result = default;
 1334148        _rayWork.Reset();
 1334149        _workspace.ResetResolution();
 1334150        _discoveryComplete = endpoint.MapId == null && graph.MapCount == 0;
 1334151        Status = (endpoint.MapId == null
 1334152                && graph.MapCount > _workspace.CoveredAddressGenerations.Length)
 1334153            || (endpoint.MapId != null
 1334154                && _workspace.CoveredAddressGenerations.Length == 0)
 1334155                ? NavigationEndpointResolutionStatus.CapacityExceeded
 1334156                : NavigationEndpointResolutionStatus.Pending;
 1334157    }
 158
 159    internal void Reset()
 160    {
 1351161        _graph = null!;
 1351162        _endpoint = default;
 1351163        _evaluator = default;
 1351164        _volumeEvaluator = default;
 1351165        _media = default;
 1351166        _role = default;
 1351167        _pendingCandidate = default;
 1351168        _mapOrdinal = 0;
 1351169        _generationInputOrdinal = 0;
 1351170        _worldChangeSequence = 0;
 1351171        _discoveryComplete = false;
 1351172        _cursorBegun = false;
 1351173        _cursorComplete = false;
 1351174        _hasResult = false;
 1351175        _requiresWorldStamp = false;
 1351176        _rayWork.Reset();
 1351177        Status = default;
 1351178        Result = default;
 1351179    }
 180
 181    internal NavigationEndpointResolutionStatus Status { get; private set; }
 182
 183    internal NavigationResolvedEndpoint Result { get; private set; }
 184
 1220185    internal bool RequiresWorldStamp => _requiresWorldStamp;
 186
 187    internal NavigationEndpointResolutionStatus Advance(
 188        int lookupStepLimit,
 189        int endpointCandidateStepLimit)
 190    {
 2059191        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 2059192        SwiftThrowHelper.ThrowIfNegative(
 2059193            endpointCandidateStepLimit,
 2059194            nameof(endpointCandidateStepLimit));
 2059195        if (Status != NavigationEndpointResolutionStatus.Pending)
 3196            return Status;
 197
 2056198        if (_pendingCandidate.Node.IsValid)
 199        {
 43200            NavigationEndpointResolutionStatus candidateStatus = AdvanceCandidateRay();
 43201            if (candidateStatus != NavigationEndpointResolutionStatus.Pending)
 8202                return candidateStatus;
 35203            return Status;
 204        }
 2013205        if (_cursorComplete)
 206        {
 136207            return Finish(NavigationSearchFinalizationRules.ResolveEndpointCursorStatus(
 136208                _hasResult));
 209        }
 210
 1877211        int lookupRemaining = Math.Min(lookupStepLimit, _meter.RemainingLookupProbes);
 1877212        int candidateRemaining = Math.Min(
 1877213            endpointCandidateStepLimit,
 1877214            _meter.RemainingEndpointCandidates);
 1877215        if (!_discoveryComplete)
 216        {
 1442217            if (lookupRemaining == 0)
 218            {
 97219                return _meter.RemainingLookupProbes == 0
 97220                    ? Finish(NavigationEndpointResolutionStatus.BudgetExceeded)
 97221                    : Status;
 222            }
 1345223            DiscoverGeneration();
 1345224            _meter.TryConsumeLookupProbes(1);
 1345225            lookupRemaining--;
 1345226            if (!_discoveryComplete || lookupRemaining == 0)
 143227                return Status;
 228        }
 229
 1637230        if (!_cursorBegun)
 231        {
 1331232            if (_workspace.CoveredAddressGenerationCount == 0)
 3233                return Finish(NavigationEndpointResolutionStatus.NoMap);
 1328234            if (!TryGetBounds(out Vector3d minimum, out Vector3d maximum))
 1235                return Finish(NavigationEndpointResolutionStatus.CostOverflow);
 236            // Generation storage and the cursor are constructed from the same map capacity,
 237            // and Begin rejects a graph whose count exceeds that storage before discovery.
 1327238            _world.TryBeginCoveredAddresses(
 1327239                _workspace.CoveredAddressCursor,
 1327240                minimum,
 1327241                maximum,
 1327242                _workspace.CoveredAddressGenerationCount);
 1327243            _cursorBegun = true;
 244        }
 245
 4934246        while (lookupRemaining > 0 || candidateRemaining > 0)
 247        {
 4810248            bool binding = _generationInputOrdinal
 4810249                < _workspace.CoveredAddressGenerationCount;
 4810250            ReadOnlySpan<GridCoveredAddressGeneration> input = binding
 4810251                ? _workspace.CoveredAddressGenerations.AsSpan(
 4810252                    _generationInputOrdinal,
 4810253                    _workspace.CoveredAddressGenerationCount - _generationInputOrdinal)
 4810254                : ReadOnlySpan<GridCoveredAddressGeneration>.Empty;
 4810255            GridCoveredAddressCursorStatus cursorStatus = _world.AdvanceCoveredAddresses(
 4810256                _workspace.CoveredAddressCursor,
 4810257                input,
 4810258                _workspace.CoveredAddressOutput,
 4810259                lookupProbeLimit: binding ? lookupRemaining : 0,
 4810260                addressProbeLimit: binding ? 0 : lookupRemaining,
 4810261                outputLimit: candidateRemaining > 0 ? 1 : 0,
 4810262                out int lookupProbes,
 4810263                out int addressProbes,
 4810264                out int inputsConsumed,
 4810265                out int outputCount);
 4810266            _cursorComplete = cursorStatus == GridCoveredAddressCursorStatus.Complete;
 4810267            int consumedLookup = checked(lookupProbes + addressProbes);
 4810268            _meter.TryConsumeLookupProbes(consumedLookup);
 4810269            lookupRemaining -= consumedLookup;
 4810270            _generationInputOrdinal += inputsConsumed;
 4810271            if (cursorStatus == GridCoveredAddressCursorStatus.Stale)
 1272                return Finish(NavigationEndpointResolutionStatus.Stale);
 4809273            if (outputCount != 0)
 274            {
 3253275                _meter.TryConsumeEndpointCandidates(1);
 3253276                candidateRemaining--;
 3253277                if (!ConsiderCandidate(_workspace.CoveredAddressOutput[0]))
 192278                    return Status;
 279            }
 4617280            if (cursorStatus == GridCoveredAddressCursorStatus.Complete)
 281            {
 1177282                return Finish(NavigationSearchFinalizationRules.ResolveEndpointCursorStatus(
 1177283                    _hasResult));
 284            }
 3440285            if (consumedLookup == 0 && outputCount == 0)
 286                break;
 287        }
 288
 263289        if (_meter.RemainingLookupProbes == 0
 263290            || (_generationInputOrdinal >= _workspace.CoveredAddressGenerationCount
 263291                && _meter.RemainingEndpointCandidates == 0))
 292        {
 1293            return Finish(NavigationEndpointResolutionStatus.BudgetExceeded);
 294        }
 262295        return Status;
 296    }
 297
 298    private void DiscoverGeneration()
 299    {
 1345300        if (_endpoint.MapId != null)
 301        {
 1246302            if (_graph.TryGetCoveredAddressGeneration(
 1246303                    _endpoint.MapId,
 1246304                    out GridCoveredAddressGeneration generation))
 305            {
 1245306                _workspace.CoveredAddressGenerations[0] = generation;
 1245307                _workspace.CoveredAddressGenerationCount = 1;
 308            }
 1246309            _mapOrdinal = 1;
 1246310            _discoveryComplete = true;
 1246311            return;
 312        }
 313
 314        // This method is called only while discovery is incomplete, so the ordinal is in range.
 99315        _graph.TryGetCoveredAddressGeneration(
 99316            _mapOrdinal,
 99317            out _,
 99318            out GridCoveredAddressGeneration generationAtOrdinal);
 99319        _workspace.CoveredAddressGenerations[
 99320            _workspace.CoveredAddressGenerationCount++] = generationAtOrdinal;
 99321        _mapOrdinal++;
 99322        _discoveryComplete = _mapOrdinal >= _graph.MapCount;
 99323    }
 324
 325    private bool ConsiderCandidate(GridCoveredAddress candidate)
 326    {
 327        // Covered generations originate from this graph's configuration index.
 3253328        _graph.TryGetMapId(candidate.ConfigurationKey, out string mapId);
 3253329        if (!_graph.TryGetNodeRef(
 3253330                new NavigationCellAddress(mapId, candidate.VoxelIndex),
 3253331                out NavigationNodeRef node))
 332        {
 1648333            return true;
 334        }
 1605335        if (!_workspace.TryRecordPage(
 1605336                mapId,
 1605337                node.CellSlot / NavigationSemanticPage.SlotCount))
 338        {
 1339            Finish(NavigationEndpointResolutionStatus.CapacityExceeded);
 1340            return false;
 341        }
 1604342        bool consumedVolumeWork = false;
 1604343        _graph.TryGetNodeState(node, out NavigationNodeState state);
 1604344        bool qualified = TryQualifyCandidate(
 1604345                node,
 1604346                state,
 1604347                out TraversalMedia qualifyingMedia,
 1604348                out TraversalMedium resolutionMedium,
 1604349                out Vector3d footAnchor,
 1604350                out Fixed64 distance,
 1604351                out consumedVolumeWork);
 1604352        if (consumedVolumeWork)
 153353            _requiresWorldStamp = true;
 1604354        if (!qualified)
 5355            return Status == NavigationEndpointResolutionStatus.Pending
 5356                && !consumedVolumeWork;
 1599357        var address = new NavigationCellAddress(mapId, candidate.VoxelIndex);
 1599358        var resolved = new NavigationResolvedEndpoint(
 1599359            node,
 1599360            address,
 1599361            qualifyingMedia,
 1599362            resolutionMedium,
 1599363            footAnchor,
 1599364            distance);
 1599365        if (!SelectPreferredCandidate(
 1599366                _hasResult,
 1599367                Result,
 1599368                resolved,
 1599369                out _))
 370        {
 273371            return true;
 372        }
 1326373        if (_endpoint.Resolution == EndpointResolutionPolicy.Strict
 1326374            || (qualifyingMedia & TraversalMedia.Solid) == 0)
 375        {
 1283376            AcceptCandidate(resolved);
 1283377            return _endpoint.Resolution == EndpointResolutionPolicy.Strict
 1283378                && resolutionMedium == TraversalMedium.Solid;
 379        }
 380
 43381        _pendingCandidate = resolved;
 43382        Vector3d start = _role == NavigationEndpointRole.Start
 43383            ? _endpoint.Position
 43384            : state.FootAnchor;
 43385        Vector3d end = _role == NavigationEndpointRole.Start
 43386            ? state.FootAnchor
 43387            : _endpoint.Position;
 43388        NavigationRayChainConstraint constraint = _role == NavigationEndpointRole.Start
 43389            ? NavigationRayChainConstraint.FinishAt(address)
 43390            : NavigationRayChainConstraint.SeedAt(address);
 43391        _rayWork.Begin(new NavigationRayRequest(
 43392            _world,
 43393            _store,
 43394            _graph,
 43395            _evaluator.Profile,
 43396            _evaluator.AreaPolicy,
 43397            TraversalMedium.Solid,
 43398            start,
 43399            end,
 43400            _role == NavigationEndpointRole.Start
 43401                ? NavigationRayEndpointAllowance.StartPrefix
 43402                : NavigationRayEndpointAllowance.DestinationSuffix,
 43403            constraint));
 43404        return false;
 405    }
 406
 407    private NavigationEndpointResolutionStatus AdvanceCandidateRay()
 408    {
 43409        NavigationRayStatus rayStatus = _rayWork.Advance(_meter);
 43410        if (rayStatus is NavigationRayStatus.Success or NavigationRayStatus.Blocked)
 411        {
 37412            if (!TryMergeRayDependencies())
 2413                return Finish(NavigationEndpointResolutionStatus.CapacityExceeded);
 35414            if (rayStatus == NavigationRayStatus.Success)
 28415                AcceptCandidate(_pendingCandidate);
 416            else
 7417                TryAcceptPendingVolumeFallback();
 35418            _pendingCandidate = default;
 35419            _rayWork.Reset();
 35420            return Status;
 421        }
 6422        return Finish((NavigationEndpointResolutionStatus)((byte)rayStatus + 1));
 423    }
 424
 425    private bool TryMergeRayDependencies()
 426    {
 37427        NavigationDependencyWorkspace dependencies = _rayWorkspace.Dependencies;
 194428        for (int i = 0; i < dependencies.ComponentCount; i++)
 429        {
 61430            if (!_workspace.TryRecordComponent(dependencies.Components[i]))
 1431                return false;
 432        }
 198433        for (int i = 0; i < dependencies.PageCount; i++)
 434        {
 64435            GraphPageDependencyAddress page = dependencies.Pages[i];
 64436            if (!_workspace.TryRecordPage(page.MapId, page.PageIndex))
 1437                return false;
 438        }
 35439        return true;
 440    }
 441
 442    private void TryAcceptPendingVolumeFallback()
 443    {
 7444        TraversalMedia volumeMedia = _pendingCandidate.Media & TraversalMedia.AnyVolume;
 7445        if (volumeMedia == TraversalMedia.None)
 3446            return;
 4447        _graph.TryGetNodeState(
 4448            _pendingCandidate.Node,
 4449            out NavigationNodeState state);
 4450        state.TryGetCenteredVolumeFootAnchor(
 4451            _evaluator.Profile.Shape.Height,
 4452            out Vector3d footAnchor);
 4453        _ = Vector3d.TryGetDistance(
 4454            _endpoint.Position,
 4455            footAnchor,
 4456            out Fixed64 distance);
 457
 4458        TraversalMedium medium = FirstVolumeMedium(volumeMedia);
 4459        var candidate = new NavigationResolvedEndpoint(
 4460            _pendingCandidate.Node,
 4461            _pendingCandidate.Address,
 4462            volumeMedia,
 4463            medium,
 4464            footAnchor,
 4465            distance);
 4466        SelectPreferredCandidate(
 4467            _hasResult,
 4468            Result,
 4469            candidate,
 4470            out NavigationResolvedEndpoint preferred);
 4471        AcceptCandidate(preferred);
 4472    }
 473
 474    internal static bool SelectPreferredCandidate(
 475        bool hasCurrent,
 476        NavigationResolvedEndpoint current,
 477        NavigationResolvedEndpoint candidate,
 478        out NavigationResolvedEndpoint preferred)
 479    {
 1609480        bool selectCandidate = !hasCurrent
 1609481            || candidate.ResolutionDistance < current.ResolutionDistance
 1609482            || (candidate.ResolutionDistance == current.ResolutionDistance
 1609483                && candidate.Address.CompareTo(current.Address) < 0);
 1609484        preferred = selectCandidate ? candidate : current;
 1609485        return selectCandidate;
 486    }
 487
 488    private void AcceptCandidate(NavigationResolvedEndpoint candidate)
 489    {
 1315490        Result = candidate;
 1315491        _hasResult = true;
 1315492    }
 493
 494    private bool TryGetBounds(out Vector3d minimum, out Vector3d maximum)
 495    {
 1328496        Fixed64 distance = _endpoint.Resolution == EndpointResolutionPolicy.Strict
 1328497            ? Fixed64.Zero
 1328498            : _endpoint.MaxResolutionDistance;
 1328499        Fixed64 minX = default;
 1328500        Fixed64 minY = default;
 1328501        Fixed64 minZ = default;
 1328502        Fixed64 maxX = default;
 1328503        Fixed64 maxY = default;
 1328504        Fixed64 maxZ = default;
 1328505        bool boundsValid = Fixed64.TrySubtract(
 1328506                _endpoint.Position.X,
 1328507                distance,
 1328508                out minX)
 1328509            && Fixed64.TrySubtract(_endpoint.Position.Y, distance, out minY)
 1328510            && Fixed64.TrySubtract(_endpoint.Position.Z, distance, out minZ)
 1328511            && Fixed64.TryAdd(_endpoint.Position.X, distance, out maxX)
 1328512            && Fixed64.TryAdd(_endpoint.Position.Y, distance, out maxY)
 1328513            && Fixed64.TryAdd(_endpoint.Position.Z, distance, out maxZ);
 1328514        if (!boundsValid)
 515        {
 1516            minimum = default;
 1517            maximum = default;
 1518            return false;
 519        }
 1327520        minimum = new Vector3d(minX, minY, minZ);
 1327521        maximum = new Vector3d(maxX, maxY, maxZ);
 1327522        return true;
 523    }
 524
 525    private NavigationEndpointResolutionStatus Finish(
 526        NavigationEndpointResolutionStatus status)
 527    {
 1332528        _pendingCandidate = default;
 1332529        _rayWork.Reset();
 1332530        if (status == NavigationEndpointResolutionStatus.Success)
 531        {
 1302532            status = RecordResultComponents();
 533        }
 1332534        bool dependenciesWereRead = status == NavigationEndpointResolutionStatus.Success
 1332535            || (status == NavigationEndpointResolutionStatus.InvalidEndpoint
 1332536                && (_workspace.PageCount != 0 || _workspace.ComponentCount != 0));
 1332537        bool epochWasCurrent = NavigationSearchFinalizationRules.IsEpochCurrent(
 1332538            epochRequired: true,
 1332539            expectedEpoch: _worldChangeSequence,
 1332540            currentEpoch: _world.ChangeSequence);
 1332541        bool dependenciesAreCurrent = !dependenciesWereRead
 1332542            || (epochWasCurrent && AreDependenciesCurrent());
 543        // Dependency validation can observe several immutable records, so recheck the
 544        // world epoch before publishing the terminal result.
 1332545        bool epochIsCurrent = epochWasCurrent
 1332546            && NavigationSearchFinalizationRules.IsEpochCurrent(
 1332547                epochRequired: true,
 1332548                expectedEpoch: _worldChangeSequence,
 1332549                currentEpoch: _world.ChangeSequence);
 1332550        status = NavigationSearchFinalizationRules.ResolveEndpointStatus(
 1332551            status,
 1332552            dependenciesWereRead,
 1332553            dependenciesAreCurrent,
 1332554            epochIsCurrent);
 1332555        Status = status;
 1332556        if (status != NavigationEndpointResolutionStatus.Success)
 37557            Result = default;
 1332558        return Status;
 559    }
 560
 561    private bool AreDependenciesCurrent()
 562    {
 1309563        NavigationWorldGraph current = _store.Current;
 1309564        NavigationAreaPolicy areaPolicy = _evaluator.AreaPolicy;
 1309565        if (_graph.AreaCatalog.TryGet(
 1309566                areaPolicy.Key,
 1309567                out NavigationAreaPolicy expectedPolicy)
 1309568            && (!current.AreaCatalog.TryGet(
 1309569                    areaPolicy.Key,
 1309570                    out NavigationAreaPolicy currentPolicy)
 1309571                || !currentPolicy.ContentEquals(expectedPolicy)))
 572        {
 1573            return false;
 574        }
 1308575        NavigationDependencyWorkspace dependencies = _workspace.Dependencies;
 5404576        for (int i = 0; i < dependencies.ComponentCount; i++)
 577        {
 1395578            NavigationSurfaceComponentKey key = dependencies.Components[i];
 1395579            _graph.TryGetComponentDependency(key, out GraphComponentDependency prior);
 1395580            if (!current.TryGetComponentDependency(key, out GraphComponentDependency next)
 1395581                || !prior.Equals(next))
 582            {
 1583                return false;
 584            }
 585        }
 5332586        for (int i = 0; i < dependencies.PageCount; i++)
 587        {
 1362588            GraphPageDependencyAddress address = dependencies.Pages[i];
 1362589            _graph.TryGetPageDependency(address, out GraphPageDependency prior);
 1362590            if (!current.TryGetPageDependency(address, out GraphPageDependency next)
 1362591                || !prior.Equals(next))
 592            {
 3593                return false;
 594            }
 595        }
 1304596        return true;
 597    }
 598
 599    private bool TryQualifyCandidate(
 600        NavigationNodeRef node,
 601        NavigationNodeState state,
 602        out TraversalMedia qualifyingMedia,
 603        out TraversalMedium resolutionMedium,
 604        out Vector3d footAnchor,
 605        out Fixed64 distance,
 606        out bool consumedVolumeWork)
 607    {
 1604608        qualifyingMedia = TraversalMedia.None;
 1604609        resolutionMedium = default;
 1604610        footAnchor = default;
 1604611        distance = default;
 1604612        consumedVolumeWork = false;
 1604613        if ((_media & TraversalMedia.Solid) != 0
 1604614            && _evaluator.TryGetPassableNodeState(node, out _))
 615        {
 1455616            if (!ConsiderMedium(
 1455617                    TraversalMedium.Solid,
 1455618                    state.FootAnchor,
 1455619                    ref qualifyingMedia,
 1455620                    ref resolutionMedium,
 1455621                    ref footAnchor,
 1455622                    ref distance))
 623            {
 1624                Finish(NavigationEndpointResolutionStatus.CostOverflow);
 1625                return false;
 626            }
 627        }
 628
 1603629        TraversalMedia requestedVolume = _media & TraversalMedia.AnyVolume;
 1603630        if (requestedVolume != TraversalMedia.None)
 631        {
 153632            consumedVolumeWork = true;
 153633            NavigationVolumeAnchorStatus volumeStatus = _volumeEvaluator.Evaluate(
 153634                node,
 153635                requestedVolume,
 153636                _meter,
 153637                _workspace.Dependencies,
 153638                out Vector3d volumeFoot,
 153639                out TraversalMedia volumeMedia);
 153640            if (volumeStatus is NavigationVolumeAnchorStatus.BudgetExceeded
 153641                or NavigationVolumeAnchorStatus.CostOverflow
 153642                or NavigationVolumeAnchorStatus.CapacityExceeded
 153643                or NavigationVolumeAnchorStatus.Stale)
 644            {
 1645                Finish((NavigationEndpointResolutionStatus)((byte)volumeStatus + 2));
 1646                return false;
 647            }
 152648            for (TraversalMedium medium = TraversalMedium.Gas;
 454649                 medium <= TraversalMedium.Liquid;
 302650                 medium++)
 651            {
 303652                if ((volumeMedia & NavigationCell.ToMedia(medium)) != 0
 303653                    && !ConsiderMedium(
 303654                        medium,
 303655                        volumeFoot,
 303656                        ref qualifyingMedia,
 303657                        ref resolutionMedium,
 303658                        ref footAnchor,
 303659                        ref distance))
 660                {
 1661                    Finish(NavigationEndpointResolutionStatus.CostOverflow);
 1662                    return false;
 663                }
 664            }
 665        }
 1601666        return qualifyingMedia != TraversalMedia.None;
 667    }
 668
 669    private bool ConsiderMedium(
 670        TraversalMedium medium,
 671        Vector3d anchor,
 672        ref TraversalMedia qualifyingMedia,
 673        ref TraversalMedium resolutionMedium,
 674        ref Vector3d footAnchor,
 675        ref Fixed64 distance)
 676    {
 1608677        if (!Vector3d.TryGetDistance(_endpoint.Position, anchor, out Fixed64 candidateDistance))
 2678            return false;
 1606679        if (_endpoint.Resolution == EndpointResolutionPolicy.NearestNavigable
 1606680            && candidateDistance > _endpoint.MaxResolutionDistance)
 681        {
 1682            return true;
 683        }
 1605684        qualifyingMedia |= NavigationCell.ToMedia(medium);
 1605685        if (resolutionMedium == TraversalMedium.Unknown
 1605686            || candidateDistance < distance
 1605687            || (candidateDistance == distance && (int)medium < (int)resolutionMedium))
 688        {
 1600689            resolutionMedium = medium;
 1600690            footAnchor = anchor;
 1600691            distance = candidateDistance;
 692        }
 1605693        return true;
 694    }
 695
 696    private NavigationEndpointResolutionStatus RecordResultComponents()
 697    {
 1302698        for (TraversalMedium medium = TraversalMedium.Solid;
 5205699             medium <= TraversalMedium.Liquid;
 3903700             medium++)
 701        {
 3904702            if ((Result.Media & NavigationCell.ToMedia(medium)) == 0)
 703                continue;
 1304704            _graph.TryGetSurfaceComponent(
 1304705                Result.Address,
 1304706                medium,
 1304707                out NavigationSurfaceComponentKey componentKey,
 1304708                out _);
 1304709            if (!_workspace.TryRecordComponent(componentKey))
 1710                return NavigationEndpointResolutionStatus.CapacityExceeded;
 711        }
 1301712        return NavigationEndpointResolutionStatus.Success;
 713    }
 714
 715    private static TraversalMedium FirstVolumeMedium(TraversalMedia media)
 716    {
 4717        if ((media & TraversalMedia.Gas) != 0)
 3718            return TraversalMedium.Gas;
 1719        return TraversalMedium.Liquid;
 720    }
 721
 722}

Methods/Properties

.ctor(GridForge.Grids.GridWorld,Trailblazer.Pathing.NavigationWorldGraphStore,Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationEndpointWorkspace,Trailblazer.Pathing.NavigationRayWorkspace,Trailblazer.Pathing.NavigationRayWork)
Begin(Trailblazer.Pathing.NavigationWorldGraph,Trailblazer.Pathing.NavigationEndpoint,Trailblazer.Pathing.NavigationEndpointRole,Trailblazer.Pathing.NavigationAgentProfile,Trailblazer.Pathing.NavigationAreaPolicy,Trailblazer.Pathing.TraversalMedia)
Reset()
get_RequiresWorldStamp()
Advance(System.Int32,System.Int32)
DiscoverGeneration()
ConsiderCandidate(GridForge.Grids.Topology.GridCoveredAddress)
AdvanceCandidateRay()
TryMergeRayDependencies()
TryAcceptPendingVolumeFallback()
SelectPreferredCandidate(System.Boolean,Trailblazer.Pathing.NavigationResolvedEndpoint,Trailblazer.Pathing.NavigationResolvedEndpoint,Trailblazer.Pathing.NavigationResolvedEndpoint&)
AcceptCandidate(Trailblazer.Pathing.NavigationResolvedEndpoint)
TryGetBounds(FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
Finish(Trailblazer.Pathing.NavigationEndpointResolutionStatus)
AreDependenciesCurrent()
TryQualifyCandidate(Trailblazer.Pathing.NavigationNodeRef,Trailblazer.Pathing.NavigationNodeState,Trailblazer.Pathing.TraversalMedia&,Trailblazer.TraversalMedium&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&,System.Boolean&)
ConsiderMedium(Trailblazer.TraversalMedium,FixedMathSharp.Vector3d,Trailblazer.Pathing.TraversalMedia&,Trailblazer.TraversalMedium&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
RecordResultComponents()
FirstVolumeMedium(Trailblazer.Pathing.TraversalMedia)