< Summary

Information
Class: Trailblazer.Pathing.NavigationFlowQueryResult
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowQueryWork.cs
Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 355
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Dispose()100%11100%

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    {
 119337        ResolvedOrigin = resolvedOrigin;
 119338        PayloadLease = payloadLease;
 119339    }
 40
 41    internal NavigationCellAddress ResolvedOrigin { get; }
 42
 43    internal NavigationFlowFieldPayloadLease PayloadLease { get; }
 44
 4045    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
 69    internal NavigationFlowQueryWork(
 70        NavigationWorldGraphStore store,
 71        NavigationFlowFieldWorkspace workspace,
 72        NavigationFlowFieldPayloadCache cache)
 73    {
 74        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 75        SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace));
 76        SwiftThrowHelper.ThrowIfNull(cache, nameof(cache));
 77        _store = store;
 78        _cache = cache;
 79        _workspace = workspace;
 80        _admission = new NavigationQueryAdmissionWork(
 81            cache.World,
 82            store,
 83            workspace.EndpointWorkspace,
 84            workspace.RayWorkspace,
 85            PathAlgorithm.FlowField);
 86    }
 87
 88    internal NavigationFlowQueryStatus Status
 89    {
 90        get => (NavigationFlowQueryStatus)Volatile.Read(ref _status);
 91        private set => Volatile.Write(ref _status, (int)value);
 92    }
 93
 94    internal bool IsPrepared => _search != null || IsReadyToPublish;
 95
 96    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    {
 102        SwiftThrowHelper.ThrowIfNull(lease, nameof(lease));
 103        SwiftThrowHelper.ThrowIfTrue(
 104            _started,
 105            message: "The flow query work is already active.");
 106        _started = true;
 107        ReservationRejected = false;
 108        _resolvedOrigin = default;
 109        _readyStatus = NavigationFlowQueryStatus.Pending;
 110        Volatile.Write(ref _readyToPublish, false);
 111        Status = NavigationFlowQueryStatus.Pending;
 112        _admission.Begin(
 113            lease,
 114            query,
 115            query.Traversal.StartMedium,
 116            query.Traversal.TargetMedia);
 117        _admissionActive = true;
 118        if (_admission.Status != NavigationQueryAdmissionStatus.Pending)
 119            MarkReady(MapAdmissionStatus(_admission.Status));
 120    }
 121
 122    internal NavigationFlowQueryStatus PrepareSearchOrCheckout(
 123        int lookupStepLimit,
 124        int endpointCandidateStepLimit)
 125    {
 126        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 127        SwiftThrowHelper.ThrowIfNegative(
 128            endpointCandidateStepLimit,
 129            nameof(endpointCandidateStepLimit));
 130        if (!_started || Status != NavigationFlowQueryStatus.Pending || IsPrepared)
 131            return Status;
 132
 133        NavigationQueryAdmissionStatus admissionStatus = _admission.Advance(
 134            lookupStepLimit,
 135            endpointCandidateStepLimit);
 136        if (admissionStatus == NavigationQueryAdmissionStatus.Pending)
 137            return Status;
 138        if (admissionStatus != NavigationQueryAdmissionStatus.Success)
 139        {
 140            MarkReady(MapAdmissionStatus(admissionStatus));
 141            return Status;
 142        }
 143
 144        NavigationResolvedPathQuery resolved = _admission.Result;
 145        _resolvedOrigin = resolved.Start.Address;
 146        var key = new NavigationFlowFieldPayloadKey(
 147            resolved.Query,
 148            resolved.End.Address,
 149            resolved.StartMedium,
 150            resolved.TargetMedia);
 151        NavigationFlowFieldStatus checkout = _cache.TryCheckout(
 152            _store,
 153            resolved.Graph,
 154            key,
 155            _resolvedOrigin,
 156            out _pendingLease,
 157            out _pendingProof);
 158        if (checkout != NavigationFlowFieldStatus.Pending)
 159        {
 160            _hasPendingLease = checkout == NavigationFlowFieldStatus.Success;
 161            resolved.Dispose();
 162            DisposeAdmission();
 163            if (checkout == NavigationFlowFieldStatus.CapacityExceeded)
 164                ReservationRejected = true;
 165            MarkReady(MapFlowStatus(checkout));
 166            return Status;
 167        }
 168
 169        int maximumNodes = Math.Min(
 170            _workspace.NodeCapacity,
 171            resolved.Query.Budget.MaxExpandedNodes);
 172        int maximumTransitions = resolved.Query.AllowTransitions
 173            ? Math.Max(0, maximumNodes - 1)
 174            : 0;
 175        long maximumBytes = NavigationFlowFieldPayload.GetMaximumRetainedBytes(
 176            maximumNodes,
 177            maximumTransitions,
 178            _workspace.DependencyComponentCapacity,
 179            _workspace.DependencyPageCapacity);
 180        if (!_cache.TryReservePayload(maximumBytes, out _payloadReservation))
 181        {
 182            ReservationRejected = true;
 183            resolved.Dispose();
 184            DisposeAdmission();
 185            MarkReady(NavigationFlowQueryStatus.CapacityExceeded);
 186            return Status;
 187        }
 188        _search = new NavigationFlowFieldWork(
 189            _cache.World,
 190            resolved,
 191            _workspace,
 192            _cache.MaximumSinglePayloadBytes);
 193        DisposeAdmission();
 194        if (_search.Status != NavigationFlowFieldStatus.Pending)
 195            MarkReady(MapFlowStatus(_search.Status));
 196        return Status;
 197    }
 198
 199    internal NavigationFlowQueryStatus AdvanceSearch(
 200        int lookupStepLimit,
 201        int nodeStepLimit,
 202        int edgeStepLimit,
 203        int connectionStepLimit)
 204    {
 205        SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit));
 206        SwiftThrowHelper.ThrowIfNegative(nodeStepLimit, nameof(nodeStepLimit));
 207        SwiftThrowHelper.ThrowIfNegative(edgeStepLimit, nameof(edgeStepLimit));
 208        SwiftThrowHelper.ThrowIfNegative(connectionStepLimit, nameof(connectionStepLimit));
 209        if (Status != NavigationFlowQueryStatus.Pending || IsReadyToPublish)
 210            return Status;
 211        if (_search == null)
 212            return Status;
 213
 214        NavigationFlowFieldStatus searchStatus = _search.Advance(
 215            lookupStepLimit,
 216            nodeStepLimit,
 217            edgeStepLimit,
 218            connectionStepLimit);
 219        if (searchStatus != NavigationFlowFieldStatus.Pending)
 220            MarkReady(MapFlowStatus(searchStatus));
 221        return Status;
 222    }
 223
 224    internal NavigationFlowQueryStatus Publish()
 225    {
 226        if (!IsReadyToPublish)
 227            return Status;
 228        if (_hasPendingLease)
 229        {
 230            NavigationFlowFieldPayloadLease lease = _pendingLease;
 231            _pendingLease = default;
 232            _hasPendingLease = false;
 233            return FinishCached(lease);
 234        }
 235        if (_pendingProof != null)
 236        {
 237            NavigationFlowFieldStatus expected =
 238                _readyStatus == NavigationFlowQueryStatus.NoPath
 239                    ? NavigationFlowFieldStatus.NoPath
 240                    : NavigationFlowFieldStatus.CostOverflow;
 241            if (!_cache.IsExactProofCurrent(
 242                    _store,
 243                    _pendingProof,
 244                    _resolvedOrigin,
 245                    expected))
 246            {
 247                return Finish(NavigationFlowQueryStatus.Stale);
 248            }
 249            _pendingProof = null;
 250        }
 251        if (_search?.Result == null)
 252            return Finish(_readyStatus);
 253
 254        NavigationFlowFieldStatus publishedStatus = _cache.TryPublishOrPromote(
 255            _store,
 256            _search.Result,
 257            _resolvedOrigin,
 258            ref _payloadReservation,
 259            out NavigationFlowFieldPayloadLease published);
 260        if (publishedStatus == NavigationFlowFieldStatus.Success)
 261            return FinishSuccess(published);
 262        published.Dispose();
 263        return Finish(MapFlowStatus(publishedStatus));
 264    }
 265
 266    internal NavigationFlowQueryResult TakeResult()
 267    {
 268        SwiftThrowHelper.ThrowIfTrue(
 269            Status != NavigationFlowQueryStatus.Success || !_hasResult,
 270            message: "The flow query has no successful payload lease.");
 271        NavigationFlowQueryResult result = _result;
 272        _result = default;
 273        _hasResult = false;
 274        return result;
 275    }
 276
 277    public void Dispose()
 278    {
 279        DisposeAdmission();
 280        _search?.Dispose();
 281        _search = null;
 282        if (_hasPendingLease)
 283            _pendingLease.Dispose();
 284        _pendingLease = default;
 285        _hasPendingLease = false;
 286        _pendingProof = null;
 287        if (_hasResult)
 288            _result.Dispose();
 289        _result = default;
 290        _hasResult = false;
 291        _cache.ReleasePayloadReservation(ref _payloadReservation);
 292        ReservationRejected = false;
 293        Volatile.Write(ref _readyToPublish, false);
 294        _started = false;
 295    }
 296
 297    private void MarkReady(NavigationFlowQueryStatus status)
 298    {
 299        _readyStatus = status;
 300        Volatile.Write(ref _readyToPublish, true);
 301    }
 302
 303    private NavigationFlowQueryStatus FinishCached(
 304        NavigationFlowFieldPayloadLease lease)
 305    {
 306        NavigationFlowFieldStatus leaseStatus = lease.TryGetPayload(
 307            out NavigationFlowFieldPayload payload);
 308        if (leaseStatus != NavigationFlowFieldStatus.Success
 309            || (!_store.Current.IsDependencyCurrent(payload.Dependencies)
 310                || !_cache.IsWorldCurrent(payload)))
 311        {
 312            if (leaseStatus == NavigationFlowFieldStatus.Success)
 313                _cache.RemoveExact(payload);
 314            lease.Dispose();
 315            return Finish(NavigationFlowQueryStatus.Stale);
 316        }
 317        return FinishSuccess(lease);
 318    }
 319
 320    private NavigationFlowQueryStatus FinishSuccess(
 321        NavigationFlowFieldPayloadLease lease)
 322    {
 323        _result = new NavigationFlowQueryResult(_resolvedOrigin, lease);
 324        _hasResult = true;
 325        return Finish(NavigationFlowQueryStatus.Success);
 326    }
 327
 328    private NavigationFlowQueryStatus Finish(NavigationFlowQueryStatus status)
 329    {
 330        _pendingProof = null;
 331        Status = status;
 332        Volatile.Write(ref _readyToPublish, false);
 333        DisposeAdmission();
 334        _search?.Dispose();
 335        _search = null;
 336        _cache.ReleasePayloadReservation(ref _payloadReservation);
 337        return status;
 338    }
 339
 340    private void DisposeAdmission()
 341    {
 342        if (!_admissionActive)
 343            return;
 344        _admission.Dispose();
 345        _admissionActive = false;
 346    }
 347
 348    private static NavigationFlowQueryStatus MapAdmissionStatus(
 349        NavigationQueryAdmissionStatus status) => (NavigationFlowQueryStatus)status;
 350
 351    private static NavigationFlowQueryStatus MapFlowStatus(
 352        NavigationFlowFieldStatus status) => status <= NavigationFlowFieldStatus.Success
 353            ? (NavigationFlowQueryStatus)status
 354            : (NavigationFlowQueryStatus)((byte)status + 5);
 355}