< Summary

Information
Class: Trailblazer.Pathing.NavigationFlowQueryWork
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowQueryWork.cs
Line coverage
100%
Covered lines: 206
Uncovered lines: 0
Coverable lines: 206
Total lines: 355
Line coverage: 100%
Branch coverage
100%
Covered branches: 70
Total branches: 70
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%
Begin(...)100%22100%
PrepareSearchOrCheckout(...)100%2020100%
AdvanceSearch(...)100%88100%
Publish()100%1616100%
TakeResult()100%22100%
Dispose()100%66100%
MarkReady(...)100%11100%
FinishCached(...)100%88100%
FinishSuccess(...)100%11100%
Finish(...)100%22100%
DisposeAdmission()100%22100%
MapAdmissionStatus(...)100%11100%
MapFlowStatus(...)100%22100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowQueryWork.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationFlowQueryWork.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.Threading;
 10
 11namespace Trailblazer.Pathing;
 12
 13/// <summary>Reports the complete internal lifecycle of one bounded flow query.</summary>
 14internal enum NavigationFlowQueryStatus : 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>Owns one successful flow payload lease and its exact resolved origin.</summary>
 31internal readonly struct NavigationFlowQueryResult : IDisposable
 32{
 33    internal NavigationFlowQueryResult(
 34        NavigationCellAddress resolvedOrigin,
 35        NavigationFlowFieldPayloadLease payloadLease)
 36    {
 37        ResolvedOrigin = resolvedOrigin;
 38        PayloadLease = payloadLease;
 39    }
 40
 41    internal NavigationCellAddress ResolvedOrigin { get; }
 42
 43    internal NavigationFlowFieldPayloadLease PayloadLease { get; }
 44
 45    public void Dispose() => PayloadLease.Dispose();
 46}
 47
 48/// <summary>Owns flow admission, cache reservation, search, and result publication.</summary>
 49internal sealed class NavigationFlowQueryWork : IDisposable
 50{
 51    private readonly NavigationWorldGraphStore _store;
 52    private readonly NavigationFlowFieldPayloadCache _cache;
 53    private readonly NavigationFlowFieldWorkspace _workspace;
 54    private readonly NavigationQueryAdmissionWork _admission;
 55    private NavigationFlowFieldWork? _search;
 56    private NavigationFlowFieldPayloadLease _pendingLease;
 57    private NavigationFlowFieldPayload? _pendingProof;
 58    private NavigationFlowQueryResult _result;
 59    private NavigationFlowFieldReservation _payloadReservation;
 60    private NavigationCellAddress _resolvedOrigin;
 61    private NavigationFlowQueryStatus _readyStatus;
 62    private int _status;
 63    private bool _hasPendingLease;
 64    private bool _hasResult;
 65    private bool _readyToPublish;
 66    private bool _started;
 67    private bool _admissionActive;
 68
 780569    internal NavigationFlowQueryWork(
 780570        NavigationWorldGraphStore store,
 780571        NavigationFlowFieldWorkspace workspace,
 780572        NavigationFlowFieldPayloadCache cache)
 73    {
 780574        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 780575        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 780576        SwiftThrowHelper.ThrowIfNull(cache, nameof(cache));
 780577        _store = store;
 780578        _cache = cache;
 780579        _workspace = workspace;
 780580        _admission = new NavigationQueryAdmissionWork(
 780581            cache.World,
 780582            store,
 780583            workspace.EndpointWorkspace,
 780584            workspace.RayWorkspace,
 780585            PathAlgorithm.FlowField);
 780586    }
 87
 88    internal NavigationFlowQueryStatus Status
 89    {
 38690        get => (NavigationFlowQueryStatus)Volatile.Read(ref _status);
 13491        private set => Volatile.Write(ref _status, (int)value);
 92    }
 93
 20994    internal bool IsPrepared => _search != null || IsReadyToPublish;
 95
 43596    internal bool IsReadyToPublish => Volatile.Read(ref _readyToPublish);
 97
 98    internal bool ReservationRejected { get; private set; }
 99
 100    internal void Begin(PathQuery query, NavigationWorldGraphLease lease)
 101    {
 75102        SwiftThrowHelper.ThrowIfNull(lease, nameof(lease));
 75103        SwiftThrowHelper.ThrowIfTrue(
 75104            _started,
 75105            message: "The flow query work is already active.");
 74106        _started = true;
 74107        ReservationRejected = false;
 74108        _resolvedOrigin = default;
 74109        _readyStatus = NavigationFlowQueryStatus.Pending;
 74110        Volatile.Write(ref _readyToPublish, false);
 74111        Status = NavigationFlowQueryStatus.Pending;
 74112        _admission.Begin(
 74113            lease,
 74114            query,
 74115            query.Traversal.StartMedium,
 74116            query.Traversal.TargetMedia);
 74117        _admissionActive = true;
 74118        if (_admission.Status != NavigationQueryAdmissionStatus.Pending)
 2119            MarkReady(MapAdmissionStatus(_admission.Status));
 74120    }
 121
 122    internal NavigationFlowQueryStatus PrepareSearchOrCheckout(
 123        int lookupStepLimit,
 124        int endpointCandidateStepLimit)
 125    {
 107126        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 107127        SwiftThrowHelper.ThrowIfNegative(
 107128            endpointCandidateStepLimit,
 107129            nameof(endpointCandidateStepLimit));
 107130        if (!_started || Status != NavigationFlowQueryStatus.Pending || IsPrepared)
 2131            return Status;
 132
 105133        NavigationQueryAdmissionStatus admissionStatus = _admission.Advance(
 105134            lookupStepLimit,
 105135            endpointCandidateStepLimit);
 105136        if (admissionStatus == NavigationQueryAdmissionStatus.Pending)
 41137            return Status;
 64138        if (admissionStatus != NavigationQueryAdmissionStatus.Success)
 139        {
 6140            MarkReady(MapAdmissionStatus(admissionStatus));
 6141            return Status;
 142        }
 143
 58144        NavigationResolvedPathQuery resolved = _admission.Result;
 58145        _resolvedOrigin = resolved.Start.Address;
 58146        var key = new NavigationFlowFieldPayloadKey(
 58147            resolved.Query,
 58148            resolved.End.Address,
 58149            resolved.StartMedium,
 58150            resolved.TargetMedia);
 58151        NavigationFlowFieldStatus checkout = _cache.TryCheckout(
 58152            _store,
 58153            resolved.Graph,
 58154            key,
 58155            _resolvedOrigin,
 58156            out _pendingLease,
 58157            out _pendingProof);
 58158        if (checkout != NavigationFlowFieldStatus.Pending)
 159        {
 16160            _hasPendingLease = checkout == NavigationFlowFieldStatus.Success;
 16161            resolved.Dispose();
 16162            DisposeAdmission();
 16163            if (checkout == NavigationFlowFieldStatus.CapacityExceeded)
 1164                ReservationRejected = true;
 16165            MarkReady(MapFlowStatus(checkout));
 16166            return Status;
 167        }
 168
 42169        int maximumNodes = Math.Min(
 42170            _workspace.NodeCapacity,
 42171            resolved.Query.Budget.MaxExpandedNodes);
 42172        int maximumTransitions = resolved.Query.AllowTransitions
 42173            ? Math.Max(0, maximumNodes - 1)
 42174            : 0;
 42175        long maximumBytes = NavigationFlowFieldPayload.GetMaximumRetainedBytes(
 42176            maximumNodes,
 42177            maximumTransitions,
 42178            _workspace.DependencyComponentCapacity,
 42179            _workspace.DependencyPageCapacity);
 42180        if (!_cache.TryReservePayload(maximumBytes, out _payloadReservation))
 181        {
 3182            ReservationRejected = true;
 3183            resolved.Dispose();
 3184            DisposeAdmission();
 3185            MarkReady(NavigationFlowQueryStatus.CapacityExceeded);
 3186            return Status;
 187        }
 39188        _search = new NavigationFlowFieldWork(
 39189            _cache.World,
 39190            resolved,
 39191            _workspace,
 39192            _cache.MaximumSinglePayloadBytes);
 39193        DisposeAdmission();
 39194        if (_search.Status != NavigationFlowFieldStatus.Pending)
 1195            MarkReady(MapFlowStatus(_search.Status));
 39196        return Status;
 197    }
 198
 199    internal NavigationFlowQueryStatus AdvanceSearch(
 200        int lookupStepLimit,
 201        int nodeStepLimit,
 202        int edgeStepLimit,
 203        int connectionStepLimit)
 204    {
 41205        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 41206        SwiftThrowHelper.ThrowIfNegative(nodeStepLimit, nameof(nodeStepLimit));
 41207        SwiftThrowHelper.ThrowIfNegative(edgeStepLimit, nameof(edgeStepLimit));
 41208        SwiftThrowHelper.ThrowIfNegative(connectionStepLimit, nameof(connectionStepLimit));
 41209        if (Status != NavigationFlowQueryStatus.Pending || IsReadyToPublish)
 1210            return Status;
 40211        if (_search == null)
 1212            return Status;
 213
 39214        NavigationFlowFieldStatus searchStatus = _search.Advance(
 39215            lookupStepLimit,
 39216            nodeStepLimit,
 39217            edgeStepLimit,
 39218            connectionStepLimit);
 39219        if (searchStatus != NavigationFlowFieldStatus.Pending)
 35220            MarkReady(MapFlowStatus(searchStatus));
 39221        return Status;
 222    }
 223
 224    internal NavigationFlowQueryStatus Publish()
 225    {
 61226        if (!IsReadyToPublish)
 1227            return Status;
 60228        if (_hasPendingLease)
 229        {
 9230            NavigationFlowFieldPayloadLease lease = _pendingLease;
 9231            _pendingLease = default;
 9232            _hasPendingLease = false;
 9233            return FinishCached(lease);
 234        }
 51235        if (_pendingProof != null)
 236        {
 4237            NavigationFlowFieldStatus expected =
 4238                _readyStatus == NavigationFlowQueryStatus.NoPath
 4239                    ? NavigationFlowFieldStatus.NoPath
 4240                    : NavigationFlowFieldStatus.CostOverflow;
 4241            if (!_cache.IsExactProofCurrent(
 4242                    _store,
 4243                    _pendingProof,
 4244                    _resolvedOrigin,
 4245                    expected))
 246            {
 3247                return Finish(NavigationFlowQueryStatus.Stale);
 248            }
 1249            _pendingProof = null;
 250        }
 48251        if (_search?.Result == null)
 14252            return Finish(_readyStatus);
 253
 34254        NavigationFlowFieldStatus publishedStatus = _cache.TryPublishOrPromote(
 34255            _store,
 34256            _search.Result,
 34257            _resolvedOrigin,
 34258            ref _payloadReservation,
 34259            out NavigationFlowFieldPayloadLease published);
 34260        if (publishedStatus == NavigationFlowFieldStatus.Success)
 32261            return FinishSuccess(published);
 2262        published.Dispose();
 2263        return Finish(MapFlowStatus(publishedStatus));
 264    }
 265
 266    internal NavigationFlowQueryResult TakeResult()
 267    {
 39268        SwiftThrowHelper.ThrowIfTrue(
 39269            Status != NavigationFlowQueryStatus.Success || !_hasResult,
 39270            message: "The flow query has no successful payload lease.");
 38271        NavigationFlowQueryResult result = _result;
 38272        _result = default;
 38273        _hasResult = false;
 38274        return result;
 275    }
 276
 277    public void Dispose()
 278    {
 76279        DisposeAdmission();
 76280        _search?.Dispose();
 76281        _search = null;
 76282        if (_hasPendingLease)
 1283            _pendingLease.Dispose();
 76284        _pendingLease = default;
 76285        _hasPendingLease = false;
 76286        _pendingProof = null;
 76287        if (_hasResult)
 1288            _result.Dispose();
 76289        _result = default;
 76290        _hasResult = false;
 76291        _cache.ReleasePayloadReservation(ref _payloadReservation);
 76292        ReservationRejected = false;
 76293        Volatile.Write(ref _readyToPublish, false);
 76294        _started = false;
 76295    }
 296
 297    private void MarkReady(NavigationFlowQueryStatus status)
 298    {
 63299        _readyStatus = status;
 63300        Volatile.Write(ref _readyToPublish, true);
 63301    }
 302
 303    private NavigationFlowQueryStatus FinishCached(
 304        NavigationFlowFieldPayloadLease lease)
 305    {
 9306        NavigationFlowFieldStatus leaseStatus = lease.TryGetPayload(
 9307            out NavigationFlowFieldPayload payload);
 9308        if (leaseStatus != NavigationFlowFieldStatus.Success
 9309            || (!_store.Current.IsDependencyCurrent(payload.Dependencies)
 9310                || !_cache.IsWorldCurrent(payload)))
 311        {
 2312            if (leaseStatus == NavigationFlowFieldStatus.Success)
 1313                _cache.RemoveExact(payload);
 2314            lease.Dispose();
 2315            return Finish(NavigationFlowQueryStatus.Stale);
 316        }
 7317        return FinishSuccess(lease);
 318    }
 319
 320    private NavigationFlowQueryStatus FinishSuccess(
 321        NavigationFlowFieldPayloadLease lease)
 322    {
 39323        _result = new NavigationFlowQueryResult(_resolvedOrigin, lease);
 39324        _hasResult = true;
 39325        return Finish(NavigationFlowQueryStatus.Success);
 326    }
 327
 328    private NavigationFlowQueryStatus Finish(NavigationFlowQueryStatus status)
 329    {
 60330        _pendingProof = null;
 60331        Status = status;
 60332        Volatile.Write(ref _readyToPublish, false);
 60333        DisposeAdmission();
 60334        _search?.Dispose();
 60335        _search = null;
 60336        _cache.ReleasePayloadReservation(ref _payloadReservation);
 60337        return status;
 338    }
 339
 340    private void DisposeAdmission()
 341    {
 194342        if (!_admissionActive)
 120343            return;
 74344        _admission.Dispose();
 74345        _admissionActive = false;
 74346    }
 347
 348    private static NavigationFlowQueryStatus MapAdmissionStatus(
 8349        NavigationQueryAdmissionStatus status) => (NavigationFlowQueryStatus)status;
 350
 351    private static NavigationFlowQueryStatus MapFlowStatus(
 54352        NavigationFlowFieldStatus status) => status <= NavigationFlowFieldStatus.Success
 54353            ? (NavigationFlowQueryStatus)status
 54354            : (NavigationFlowQueryStatus)((byte)status + 5);
 355}