< Summary

Information
Class: Trailblazer.Pathing.NavigationAStarQueryWork
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/AStar/NavigationAStarQueryWork.cs
Line coverage
100%
Covered lines: 173
Uncovered lines: 0
Coverable lines: 173
Total lines: 311
Line coverage: 100%
Branch coverage
100%
Covered branches: 58
Total branches: 58
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%
get_Status()100%11100%
set_Status(...)100%11100%
get_IsPrepared()100%22100%
get_IsReadyToPublish()100%11100%
BeginReserved(...)100%22100%
PrepareSearchOrCheckout(...)100%1212100%
AdvanceSearch(...)100%88100%
Publish()100%1414100%
TakeResult()100%22100%
Dispose()100%66100%
MarkReady(...)100%11100%
FinishCached(...)100%66100%
FinishSuccess(...)100%11100%
FinishNegative(...)100%11100%
Finish(...)100%22100%
DisposeAdmission()100%22100%
MapAdmissionStatus(...)100%11100%
MapSearchStatus(...)100%22100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationAStarQueryWork.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Diagnostics;
 10using System.Threading;
 11using GridForge.Grids;
 12
 13namespace Trailblazer.Pathing;
 14
 15/// <summary>Reports the complete internal lifecycle of one bounded A* query.</summary>
 16internal enum NavigationAStarQueryStatus : byte
 17{
 18    Pending = 0,
 19    Success = 1,
 20    Unsupported = 2,
 21    NoMap = 3,
 22    InvalidProfile = 4,
 23    InvalidStart = 5,
 24    InvalidEnd = 6,
 25    NoPath = 7,
 26    BudgetExceeded = 8,
 27    CostOverflow = 9,
 28    CapacityExceeded = 10,
 29    Stale = 11
 30}
 31
 32/// <summary>
 33/// Owns admission and search leases until one immutable payload lease can be returned.
 34/// </summary>
 35internal sealed class NavigationAStarQueryWork : IDisposable
 36{
 37    private readonly NavigationWorldGraphStore _store;
 38    private readonly NavigationAStarPayloadCache _cache;
 39    private readonly NavigationAStarWorkspace _workspace;
 40    private readonly NavigationQueryAdmissionWork _admission;
 41    private NavigationSurfaceAStarWork? _search;
 42    private NavigationAStarPayloadLease? _pendingLease;
 43    private NavigationAStarPayloadLease? _result;
 44    private NavigationAStarPayloadReservation _payloadReservation;
 45    private NavigationAStarQueryStatus _readyStatus;
 46    private int _status;
 47    private bool _readyToPublish;
 48    private bool _started;
 49    private bool _admissionActive;
 50
 782951    internal NavigationAStarQueryWork(
 782952        GridWorld world,
 782953        NavigationWorldGraphStore store,
 782954        NavigationAStarWorkspace workspace,
 782955        NavigationAStarPayloadCache cache)
 56    {
 782957        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 782958        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 782959        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 782960        SwiftThrowHelper.ThrowIfNull(cache, nameof(cache));
 782961        _store = store;
 782962        _workspace = workspace;
 782963        _cache = cache;
 782964        _admission = new NavigationQueryAdmissionWork(
 782965            world,
 782966            store,
 782967            workspace.EndpointWorkspace,
 782968            workspace.RayWorkspace,
 782969            PathAlgorithm.AStar);
 782970    }
 71
 72    internal NavigationAStarQueryStatus Status
 73    {
 292574        get => (NavigationAStarQueryStatus)Volatile.Read(ref _status);
 84575        private set => Volatile.Write(ref _status, (int)value);
 76    }
 77
 157978    internal bool IsPrepared => _search != null || _readyToPublish;
 79
 206280    internal bool IsReadyToPublish => Volatile.Read(ref _readyToPublish);
 81
 82    internal void BeginReserved(
 83        PathQuery query,
 84        NavigationWorldGraphLease lease,
 85        ref NavigationAStarPayloadReservation reservation)
 86    {
 43487        SwiftThrowHelper.ThrowIfNull(lease, nameof(lease));
 43488        SwiftThrowHelper.ThrowIfArgument(
 43489            !reservation.HasLeaseSlot,
 43490            paramName: nameof(reservation),
 43491            message: "A batch query requires one payload reservation.");
 43392        SwiftThrowHelper.ThrowIfTrue(
 43393            _started,
 43394            message: "The A* query work is already active.");
 43295        _started = true;
 43296        Volatile.Write(ref _readyToPublish, false);
 43297        _readyStatus = NavigationAStarQueryStatus.Pending;
 43298        Status = NavigationAStarQueryStatus.Pending;
 43299        _payloadReservation = reservation;
 432100        reservation = default;
 432101        _admission.Begin(
 432102            lease,
 432103            query,
 432104            query.Traversal.StartMedium,
 432105            query.Traversal.TargetMedia);
 432106        _admissionActive = true;
 432107        if (_admission.Status != NavigationQueryAdmissionStatus.Pending)
 4108            MarkReady(MapAdmissionStatus(_admission.Status));
 432109    }
 110
 111    internal NavigationAStarQueryStatus PrepareSearchOrCheckout(
 112        int lookupStepLimit,
 113        int endpointCandidateStepLimit)
 114    {
 653115        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 653116        SwiftThrowHelper.ThrowIfNegative(
 653117            endpointCandidateStepLimit,
 653118            nameof(endpointCandidateStepLimit));
 653119        if (!_started || Status != NavigationAStarQueryStatus.Pending || IsPrepared)
 4120            return Status;
 121
 649122        NavigationQueryAdmissionStatus admissionStatus = _admission.Advance(
 649123            lookupStepLimit,
 649124            endpointCandidateStepLimit);
 649125        if (admissionStatus == NavigationQueryAdmissionStatus.Pending)
 238126            return Status;
 411127        if (admissionStatus != NavigationQueryAdmissionStatus.Success)
 128        {
 7129            MarkReady(MapAdmissionStatus(admissionStatus));
 7130            return Status;
 131        }
 132
 404133        NavigationResolvedPathQuery resolved = _admission.Result;
 404134        var key = new NavigationAStarPayloadKey(
 404135            resolved.Query,
 404136            resolved.Start.Address,
 404137            resolved.End.Address,
 404138            resolved.StartMedium,
 404139            resolved.TargetMedia);
 404140        bool cacheHit = _cache.TryCheckoutReserved(
 404141            key,
 404142            resolved.Graph,
 404143            ref _payloadReservation,
 404144            out _pendingLease);
 404145        if (cacheHit)
 146        {
 277147            resolved.Dispose();
 277148            DisposeAdmission();
 277149            MarkReady(MapSearchStatus(_pendingLease.Payload.Status));
 277150            return Status;
 151        }
 127152        NavigationRayWork rayWork = _admission.RayWork;
 127153        DisposeAdmission();
 127154        _search = new NavigationSurfaceAStarWork(
 127155            _cache.World,
 127156            _store,
 127157            resolved,
 127158            _workspace,
 127159            rayWork,
 127160            _payloadReservation.MaximumBytes);
 127161        return Status;
 162    }
 163
 164    internal NavigationAStarQueryStatus AdvanceSearch(
 165        int lookupStepLimit,
 166        int nodeStepLimit,
 167        int edgeStepLimit,
 168        int connectionStepLimit)
 169    {
 408170        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 408171        SwiftThrowHelper.ThrowIfNegative(nodeStepLimit, nameof(nodeStepLimit));
 408172        SwiftThrowHelper.ThrowIfNegative(edgeStepLimit, nameof(edgeStepLimit));
 408173        SwiftThrowHelper.ThrowIfNegative(connectionStepLimit, nameof(connectionStepLimit));
 408174        if (Status != NavigationAStarQueryStatus.Pending || IsReadyToPublish)
 1175            return Status;
 407176        if (_search == null)
 1177            return Status;
 178
 406179        NavigationSurfaceAStarStatus searchStatus = _search.Advance(
 406180            lookupStepLimit,
 406181            nodeStepLimit,
 406182            edgeStepLimit,
 406183            connectionStepLimit);
 406184        if (searchStatus != NavigationSurfaceAStarStatus.Pending)
 126185            MarkReady(MapSearchStatus(searchStatus));
 406186        return Status;
 187    }
 188
 189    internal NavigationAStarQueryStatus Publish()
 190    {
 414191        if (!IsReadyToPublish)
 1192            return Status;
 413193        if (_pendingLease != null)
 194        {
 276195            NavigationAStarPayloadLease cached = _pendingLease;
 276196            _pendingLease = null;
 276197            return FinishCached(cached);
 198        }
 137199        if (_readyStatus is not NavigationAStarQueryStatus.Success
 137200            and not NavigationAStarQueryStatus.NoPath)
 201        {
 15202            return Finish(_readyStatus);
 203        }
 122204        if (_search == null)
 1205            return Finish(_readyStatus);
 121206        NavigationAStarPayload payload = _search!.Result;
 121207        if (!_cache.TryPublish(
 121208                payload,
 121209                _store,
 121210                ref _payloadReservation,
 121211                out NavigationAStarPayloadLease published))
 212        {
 213            Debug.Assert(
 214                !_store.Current.IsDependencyCurrent(payload.Dependencies)
 215                || !_cache.IsWorldCurrent(payload),
 216                "A gate-owned reservation must cover its worker's maximum payload.");
 3217            return Finish(NavigationAStarQueryStatus.Stale);
 218        }
 118219        return _readyStatus == NavigationAStarQueryStatus.Success
 118220            ? FinishSuccess(published)
 118221            : FinishNegative(published, _readyStatus);
 222    }
 223
 224    internal NavigationAStarPayloadLease TakeResult()
 225    {
 380226        SwiftThrowHelper.ThrowIfTrue(
 380227            Status != NavigationAStarQueryStatus.Success || _result == null,
 380228            message: "The A* query has no successful payload lease.");
 377229        NavigationAStarPayloadLease result = _result;
 377230        _result = null;
 377231        return result;
 232    }
 233
 234    public void Dispose()
 235    {
 436236        DisposeAdmission();
 436237        _search?.Dispose();
 436238        _search = null;
 436239        _pendingLease?.Dispose();
 436240        _pendingLease = null;
 436241        _result?.Dispose();
 436242        _result = null;
 436243        _cache.ReleasePayloadReservation(ref _payloadReservation);
 436244        Volatile.Write(ref _readyToPublish, false);
 436245        _started = false;
 436246    }
 247
 248    private void MarkReady(NavigationAStarQueryStatus status)
 249    {
 414250        _readyStatus = status;
 414251        Volatile.Write(ref _readyToPublish, true);
 414252    }
 253
 254    private NavigationAStarQueryStatus FinishCached(
 255        NavigationAStarPayloadLease lease)
 256    {
 276257        if (!_store.Current.IsDependencyCurrent(lease.Payload.Dependencies)
 276258            || !_cache.IsWorldCurrent(lease.Payload))
 259        {
 1260            _cache.RemoveExact(lease.Payload);
 1261            lease.Dispose();
 1262            return Finish(NavigationAStarQueryStatus.Stale);
 263        }
 275264        NavigationAStarQueryStatus status = MapSearchStatus(lease.Payload.Status);
 275265        return status == NavigationAStarQueryStatus.Success
 275266            ? FinishSuccess(lease)
 275267            : FinishNegative(lease, status);
 268    }
 269
 270    private NavigationAStarQueryStatus FinishSuccess(
 271        NavigationAStarPayloadLease lease)
 272    {
 378273        _result = lease;
 378274        return Finish(NavigationAStarQueryStatus.Success);
 275    }
 276
 277    private NavigationAStarQueryStatus FinishNegative(
 278        NavigationAStarPayloadLease lease,
 279        NavigationAStarQueryStatus status)
 280    {
 15281        lease.Dispose();
 15282        return Finish(status);
 283    }
 284
 285    private NavigationAStarQueryStatus Finish(NavigationAStarQueryStatus status)
 286    {
 413287        Status = status;
 413288        Volatile.Write(ref _readyToPublish, false);
 413289        DisposeAdmission();
 413290        _search?.Dispose();
 413291        _search = null;
 413292        _cache.ReleasePayloadReservation(ref _payloadReservation);
 413293        return status;
 294    }
 295
 296    private void DisposeAdmission()
 297    {
 1253298        if (!_admissionActive)
 821299            return;
 432300        _admission.Dispose();
 432301        _admissionActive = false;
 432302    }
 303
 304    private static NavigationAStarQueryStatus MapAdmissionStatus(
 11305        NavigationQueryAdmissionStatus status) => (NavigationAStarQueryStatus)status;
 306
 307    private static NavigationAStarQueryStatus MapSearchStatus(
 678308        NavigationSurfaceAStarStatus status) => status <= NavigationSurfaceAStarStatus.Success
 678309            ? (NavigationAStarQueryStatus)status
 678310            : (NavigationAStarQueryStatus)((byte)status + 5);
 311}