< Summary

Information
Class: Trailblazer.Pathing.NavigationQueryAdmissionWork
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/AStar/NavigationQueryAdmissionWork.cs
Line coverage
100%
Covered lines: 172
Uncovered lines: 0
Coverable lines: 172
Total lines: 300
Line coverage: 100%
Branch coverage
100%
Covered branches: 60
Total branches: 60
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%1818100%
get_Result()100%11100%
get_Meter()100%11100%
get_RayWork()100%11100%
Advance(...)100%3030100%
Dispose()100%22100%
BeginEndpointWork(...)100%44100%
Finish(...)100%11100%
MapEndpointFailure(...)100%66100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationQueryAdmissionWork.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 GridForge.Grids;
 10
 11namespace Trailblazer.Pathing;
 12
 13/// <summary>Reports bounded internal query-admission progress.</summary>
 14internal enum NavigationQueryAdmissionStatus : byte
 15{
 16    Pending = 0,
 17    Success = 1,
 18    Unsupported = 2,
 19    NoMap = 3,
 20    InvalidProfile = 4,
 21    InvalidStart = 5,
 22    InvalidEnd = 6,
 23    NoPath = 7,
 24    BudgetExceeded = 8,
 25    CostOverflow = 9,
 26    CapacityExceeded = 10,
 27    Stale = 11
 28}
 29
 30/// <summary>Resolves one surface A* query against an exact leased graph generation.</summary>
 31internal sealed class NavigationQueryAdmissionWork : IDisposable
 32{
 33    private enum Stage : byte
 34    {
 35        ResolvePolicy = 0,
 36        ResolveStart = 1,
 37        ResolveEnd = 2
 38    }
 39
 40    private readonly NavigationEndpointWorkspace _workspace;
 41    private readonly GridWorld _world;
 42    private readonly PathAlgorithm _expectedAlgorithm;
 43    private readonly NavigationWorkMeter _meter;
 44    private readonly NavigationRayWork _rayWork;
 45    private readonly NavigationEndpointResolutionWork _endpointWork;
 46    private readonly NavigationResolvedPathQuery _result;
 47    private NavigationWorldGraphLease? _lease;
 48    private NavigationAreaPolicy? _areaPolicy;
 49    private PathQuery _query;
 50    private TraversalMedium _startMedium;
 51    private TraversalMedia _targetMedia;
 52    private NavigationResolvedEndpoint _start;
 53    private NavigationResolvedEndpoint _end;
 54    private Stage _stage;
 55    private bool _endpointActive;
 56    private bool _active;
 57    private ulong _worldChangeSequence;
 58    private bool _requiresWorldStamp;
 59
 1581660    internal NavigationQueryAdmissionWork(
 1581661        GridWorld world,
 1581662        NavigationWorldGraphStore store,
 1581663        NavigationEndpointWorkspace workspace,
 1581664        NavigationRayWorkspace rayWorkspace,
 1581665        PathAlgorithm expectedAlgorithm)
 66    {
 1581667        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 1581668        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 1581669        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 1581670        SwiftThrowHelper.ThrowIfNull(rayWorkspace, nameof(rayWorkspace));
 1581671        _workspace = workspace;
 1581672        _world = world;
 1581673        _expectedAlgorithm = expectedAlgorithm;
 1581674        _meter = new NavigationWorkMeter(default);
 1581675        _rayWork = new NavigationRayWork(rayWorkspace);
 1581676        _endpointWork = new NavigationEndpointResolutionWork(
 1581677            world,
 1581678            store,
 1581679            _meter,
 1581680            workspace,
 1581681            rayWorkspace,
 1581682            _rayWork);
 1581683        _result = new NavigationResolvedPathQuery();
 1581684    }
 85
 86    internal void Begin(
 87        NavigationWorldGraphLease lease,
 88        PathQuery query,
 89        TraversalMedium startMedium,
 90        TraversalMedia targetMedia)
 91    {
 68992        SwiftThrowHelper.ThrowIfNull(lease, nameof(lease));
 68993        SwiftThrowHelper.ThrowIfTrue(
 68994            _active,
 68995            message: "The query admission work is already active.");
 68896        _active = true;
 68897        _lease = lease;
 68898        _query = query;
 68899        _workspace.Reset();
 688100        _meter.Reset(query.Budget);
 688101        _areaPolicy = null;
 688102        _start = default;
 688103        _end = default;
 688104        _worldChangeSequence = _world.ChangeSequence;
 688105        _startMedium = startMedium;
 688106        _targetMedia = targetMedia;
 688107        _stage = Stage.ResolvePolicy;
 688108        _endpointActive = false;
 688109        _requiresWorldStamp = false;
 688110        Status = NavigationQueryAdmissionStatus.Pending;
 688111        if (!query.Agent.IsValid)
 2112            Finish(NavigationQueryAdmissionStatus.InvalidProfile);
 686113        else if (query.Algorithm != _expectedAlgorithm)
 4114            Finish(NavigationQueryAdmissionStatus.Unsupported);
 682115        else if (!NavigationCell.IsKnownMedium(startMedium))
 1116            Finish(NavigationQueryAdmissionStatus.InvalidStart);
 681117        else if (targetMedia == TraversalMedia.None
 681118            || (targetMedia & ~NavigationCell.KnownMedia) != 0)
 119        {
 2120            Finish(NavigationQueryAdmissionStatus.InvalidEnd);
 121        }
 679122        else if ((query.Agent.AllowedMedia & NavigationCell.ToMedia(startMedium)) == 0
 679123            || (targetMedia & ~query.Agent.AllowedMedia) != 0)
 124        {
 2125            Finish(NavigationQueryAdmissionStatus.InvalidProfile);
 126        }
 677127        else if (!query.AllowTransitions
 677128            && (targetMedia & NavigationCell.ToMedia(startMedium)) == 0)
 129        {
 2130            Finish(NavigationQueryAdmissionStatus.NoPath);
 131        }
 677132    }
 133
 134    internal NavigationQueryAdmissionStatus Status { get; private set; }
 135
 721136    internal NavigationResolvedPathQuery Result => _result;
 137
 161138    internal NavigationWorkMeter Meter => _meter;
 139
 250140    internal NavigationRayWork RayWork => _rayWork;
 141
 142    internal NavigationQueryAdmissionStatus Advance(
 143        int lookupStepLimit,
 144        int endpointCandidateStepLimit)
 145    {
 1112146        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 1112147        SwiftThrowHelper.ThrowIfNegative(
 1112148            endpointCandidateStepLimit,
 1112149            nameof(endpointCandidateStepLimit));
 1112150        if (Status != NavigationQueryAdmissionStatus.Pending)
 6151            return Status;
 152
 1106153        int lookupRemaining = lookupStepLimit;
 1106154        int candidateRemaining = endpointCandidateStepLimit;
 3021155        while (Status == NavigationQueryAdmissionStatus.Pending)
 156        {
 2390157            if (_stage == Stage.ResolvePolicy)
 158            {
 651159                if (lookupRemaining == 0)
 160                {
 4161                    return _meter.RemainingLookupProbes == 0
 4162                        ? Finish(NavigationQueryAdmissionStatus.BudgetExceeded)
 4163                        : Status;
 164                }
 647165                if (!_meter.TryConsumeLookupProbes(1))
 1166                    return Finish(NavigationQueryAdmissionStatus.BudgetExceeded);
 646167                lookupRemaining--;
 646168                if (!_lease!.Graph.AreaCatalog.TryGet(
 646169                        _query.AreaPolicy,
 646170                        out NavigationAreaPolicy policy))
 171                {
 1172                    return Finish(NavigationQueryAdmissionStatus.Stale);
 173                }
 645174                _areaPolicy = policy;
 645175                _stage = Stage.ResolveStart;
 645176                continue;
 177            }
 178
 179            // ResolvePolicy always continues after selecting ResolveStart. Those are the only
 180            // remaining stages while status is pending, so endpoint work is unconditional here.
 1739181            if (!_endpointActive)
 182            {
 1284183                BeginEndpointWork(
 1284184                    _stage == Stage.ResolveStart ? _query.Start : _query.End,
 1284185                    _stage == Stage.ResolveStart
 1284186                        ? NavigationEndpointRole.Start
 1284187                        : NavigationEndpointRole.Destination);
 1284188                _endpointActive = true;
 189            }
 1739190            int lookupBefore = _meter.LookupProbes;
 1739191            int candidatesBefore = _meter.EndpointCandidates;
 1739192            NavigationEndpointResolutionStatus endpointStatus = _endpointWork.Advance(
 1739193                lookupRemaining,
 1739194                candidateRemaining);
 1739195            lookupRemaining = Math.Max(
 1739196                0,
 1739197                lookupRemaining - (_meter.LookupProbes - lookupBefore));
 1739198            candidateRemaining = Math.Max(
 1739199                0,
 1739200                candidateRemaining - (_meter.EndpointCandidates - candidatesBefore));
 1739201            if (endpointStatus == NavigationEndpointResolutionStatus.Pending)
 455202                return Status;
 1284203            if (endpointStatus != NavigationEndpointResolutionStatus.Success)
 204            {
 13205                return Finish(MapEndpointFailure(endpointStatus, _stage));
 206            }
 1271207            _requiresWorldStamp = _requiresWorldStamp
 1271208                || _endpointWork.RequiresWorldStamp;
 1271209            if (_stage == Stage.ResolveStart)
 210            {
 639211                _start = _endpointWork.Result;
 639212                _stage = Stage.ResolveEnd;
 639213                _endpointActive = false;
 214            }
 215            else
 216            {
 632217                if (_world.ChangeSequence != _worldChangeSequence)
 1218                    return Finish(NavigationQueryAdmissionStatus.Stale);
 631219                _end = _endpointWork.Result;
 631220                _endpointWork.Reset();
 631221                NavigationWorldGraphLease lease = _lease!;
 631222                _result.Bind(
 631223                    lease,
 631224                    _query,
 631225                    _start,
 631226                    _end,
 631227                    _areaPolicy!,
 631228                    _startMedium,
 631229                    _targetMedia,
 631230                    _meter,
 631231                    _worldChangeSequence,
 631232                    _requiresWorldStamp);
 631233                _lease = null;
 631234                Status = NavigationQueryAdmissionStatus.Success;
 631235                _endpointActive = false;
 236            }
 237        }
 631238        return Status;
 239    }
 240
 241    public void Dispose()
 242    {
 720243        NavigationWorldGraphLease? lease = _lease;
 720244        _lease = null;
 720245        lease?.Dispose();
 720246        _endpointWork.Reset();
 720247        _areaPolicy = null;
 720248        _query = default;
 720249        _startMedium = default;
 720250        _targetMedia = default;
 720251        _start = default;
 720252        _end = default;
 720253        _worldChangeSequence = 0;
 720254        _requiresWorldStamp = false;
 720255        _endpointActive = false;
 720256        _active = false;
 720257    }
 258
 259    private void BeginEndpointWork(
 260        NavigationEndpoint endpoint,
 261        NavigationEndpointRole role)
 262    {
 1284263        NavigationWorldGraph graph = _lease!.Graph;
 1284264        TraversalMedia effectiveTargetMedia = _query.AllowTransitions
 1284265            ? _targetMedia
 1284266            : NavigationCell.ToMedia(_startMedium);
 1284267        _endpointWork.Begin(
 1284268            graph,
 1284269            endpoint,
 1284270            role,
 1284271            _query.Agent,
 1284272            _areaPolicy!,
 1284273            role == NavigationEndpointRole.Start
 1284274                ? NavigationCell.ToMedia(_startMedium)
 1284275                : effectiveTargetMedia);
 1284276    }
 277
 278    private NavigationQueryAdmissionStatus Finish(
 279        NavigationQueryAdmissionStatus status)
 280    {
 30281        Status = status;
 30282        Dispose();
 30283        return Status;
 284    }
 285
 286    private static NavigationQueryAdmissionStatus MapEndpointFailure(
 287        NavigationEndpointResolutionStatus endpointStatus,
 288        Stage stage)
 289    {
 13290        if (endpointStatus == NavigationEndpointResolutionStatus.InvalidEndpoint)
 291        {
 8292            return stage == Stage.ResolveStart
 8293                ? NavigationQueryAdmissionStatus.InvalidStart
 8294                : NavigationQueryAdmissionStatus.InvalidEnd;
 295        }
 5296        int offset = endpointStatus == NavigationEndpointResolutionStatus.NoMap ? 1 : 4;
 5297        return (NavigationQueryAdmissionStatus)((byte)endpointStatus + offset);
 298    }
 299
 300}