< Summary

Information
Class: Trailblazer.Pathing.NavigationFlowAdmissionGate
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowAdmissionGate.cs
Line coverage
100%
Covered lines: 236
Uncovered lines: 0
Coverable lines: 236
Total lines: 420
Line coverage: 100%
Branch coverage
100%
Covered branches: 64
Total branches: 64
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%22100%
get_PayloadCache()100%11100%
Begin(...)100%44100%
Begin(...)100%66100%
Dispose()100%44100%
CancelActive()100%22100%
CanBegin()100%22100%
FitsBatchEnvelope(...)100%44100%
FitsSingleEnvelope(...)100%11100%
BeginCore(...)100%22100%
GetDescriptor(...)100%44100%
GetAdmittedCount(...)100%11100%
IsAdmissionComplete(...)100%11100%
GetStatus(...)100%22100%
IsReadyToPublish(...)100%22100%
AdvanceAdmission(...)100%66100%
AdvanceSearch(...)100%22100%
PublishReadyPrefix(...)100%66100%
TakeResult(...)100%11100%
Release(...)100%66100%
CutCapacitySuffix(...)100%22100%
EnsureActive(...)100%22100%
IsActive(...)100%44100%
.ctor(...)100%11100%
CompareTo(...)100%22100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationFlowAdmissionGate.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;
 10using GridForge.Grids;
 11
 12namespace Trailblazer.Pathing;
 13
 14/// <summary>Serializes context-owned flow resource admission.</summary>
 15internal sealed class NavigationFlowAdmissionGate : IDisposable
 16{
 99917    private readonly object _sync = new();
 18    private readonly NavigationWorldGraphStore _store;
 19    private readonly NavigationQueryLimits _limits;
 20    private readonly NavigationQueryAdmissionCoordinator _coordinator;
 21    private readonly NavigationFlowFieldPayloadCache _cache;
 22    private readonly NavigationFlowQueryWork[] _queries;
 23    private readonly NavigationWorldGraphLease?[] _leases;
 24    private readonly BatchDescriptor[] _descriptors;
 25    private NavigationQueryCapacityReservation _capacityReservation;
 26    private ulong _generation;
 27    private ulong _activeGeneration;
 28    private int _activeCount;
 29    private int _activeAdmittedCount;
 30    private int _nextAdmission;
 31    private int _nextPublication;
 32    private bool _active;
 33    private bool _disposed;
 34
 99935    internal NavigationFlowAdmissionGate(
 99936        GridWorld world,
 99937        NavigationWorldGraphStore store,
 99938        NavigationQueryLimits limits,
 99939        NavigationQueryAdmissionCoordinator coordinator,
 99940        NavigationImmediateRayWorkspace immediateRayWorkspace)
 41    {
 99942        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 99943        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 99944        SwiftThrowHelper.ThrowIfNull(coordinator, nameof(coordinator));
 99945        SwiftThrowHelper.ThrowIfArgument(
 99946            limits.MaxConcurrentNavigationQueries <= 0,
 99947            nameof(limits),
 99948            "Query limits must be explicitly initialized.");
 99949        _store = store;
 99950        _limits = limits;
 99951        _coordinator = coordinator;
 99952        _cache = new NavigationFlowFieldPayloadCache(
 99953            world,
 99954            limits.MaxFlowCacheEntries,
 99955            limits.MaxFlowReusablePayloadBytes,
 99956            limits.MaxFlowSinglePayloadBytes,
 99957            limits.MaxFlowActivePayloadBytes,
 99958            limits.MaxFlowActivePayloadLeases,
 99959            limits.FlowWorkspaceMapCapacity,
 99960            immediateRayWorkspace);
 99961        _queries = new NavigationFlowQueryWork[limits.MaxConcurrentNavigationQueries];
 99962        _leases = new NavigationWorldGraphLease[limits.MaxConcurrentNavigationQueries];
 1757663        for (int i = 0; i < _queries.Length; i++)
 64        {
 778965            var workspace = new NavigationFlowFieldWorkspace(
 778966                limits.FlowWorkspaceMapCapacity,
 778967                limits.FlowWorkspaceEndpointPageCapacity,
 778968                limits.FlowWorkspaceComponentCapacity,
 778969                limits.FlowWorkspaceNodeCapacity,
 778970                limits.RayWorkspaceCoveredAddressCapacity,
 778971                limits.RayWorkspaceTraceIntervalCapacity);
 778972            _queries[i] = new NavigationFlowQueryWork(store, workspace, _cache);
 73        }
 99974        _descriptors = new BatchDescriptor[limits.MaxBatchItems];
 99975    }
 76
 6477    internal NavigationFlowFieldPayloadCache PayloadCache => _cache;
 78
 79    internal NavigationFlowQueryStatus Begin(
 80        PathQuery query,
 81        out NavigationFlowBatchWork work)
 82    {
 5483        lock (_sync)
 84        {
 5485            if (!CanBegin() || !FitsSingleEnvelope(query))
 86            {
 387                work = default;
 388                return NavigationFlowQueryStatus.CapacityExceeded;
 89            }
 5190            _descriptors[0] = new BatchDescriptor(0, 0, query);
 5191            return BeginCore(1, out work);
 92        }
 5493    }
 94
 95    internal NavigationFlowQueryStatus Begin(
 96        PathQueryBatch batch,
 97        out NavigationFlowBatchWork work)
 98    {
 599        lock (_sync)
 100        {
 5101            if (!CanBegin() || !FitsBatchEnvelope(batch))
 102            {
 1103                work = default;
 1104                return NavigationFlowQueryStatus.CapacityExceeded;
 105            }
 28106            for (int i = 0; i < batch.Count; i++)
 107            {
 10108                PathQueryBatchItem item = batch.Items[i];
 10109                _descriptors[i] = new BatchDescriptor(item.StableOrdinal, i, item.Query);
 110            }
 4111            Array.Sort(_descriptors, 0, batch.Count);
 4112            return BeginCore(batch.Count, out work);
 113        }
 5114    }
 115
 116    public void Dispose()
 117    {
 999118        NavigationFlowBatchWork active = default;
 999119        lock (_sync)
 120        {
 999121            if (_disposed)
 1122                return;
 998123            _disposed = true;
 998124            if (_active)
 1125                active = new NavigationFlowBatchWork(this, _activeGeneration);
 998126        }
 998127        active.Dispose();
 998128        _cache.Dispose();
 999129    }
 130
 131    internal void CancelActive()
 132    {
 23133        NavigationFlowBatchWork active = default;
 23134        lock (_sync)
 135        {
 23136            if (_active)
 1137                active = new NavigationFlowBatchWork(this, _activeGeneration);
 23138        }
 23139        active.Dispose();
 23140    }
 141
 142    private bool CanBegin() =>
 59143        !_disposed
 59144        && !_active;
 145
 146    private bool FitsBatchEnvelope(PathQueryBatch batch) =>
 5147        batch.Count > 0
 5148        && batch.Count <= _limits.MaxBatchItems
 5149        && batch.GetLogicalRetainedBytes() <= _limits.MaxBatchDescriptorBytes;
 150
 151    private bool FitsSingleEnvelope(PathQuery query) =>
 52152        PathQueryBatchItem.GetLogicalRetainedBytes(query)
 52153        <= _limits.MaxBatchDescriptorBytes;
 154
 155    private NavigationFlowQueryStatus BeginCore(
 156        int count,
 157        out NavigationFlowBatchWork work)
 158    {
 55159        int candidateCount = _coordinator.TryReservePrefix(
 55160            PathAlgorithm.FlowField,
 55161            Math.Min(count, _queries.Length),
 55162            out _capacityReservation);
 55163        int leased = _store.TryAcquirePrefix(_leases.AsSpan(0, candidateCount));
 55164        _capacityReservation = _coordinator.Trim(_capacityReservation, leased);
 228165        for (int i = 0; i < leased; i++)
 166        {
 59167            _descriptors[i].SlotIndex = i;
 59168            _queries[i].Begin(_descriptors[i].Query, _leases[i]!);
 59169            _leases[i] = null;
 170        }
 55171        _generation = unchecked(_generation + 1UL);
 55172        _activeGeneration = _generation;
 55173        _activeCount = count;
 55174        _activeAdmittedCount = leased;
 55175        _nextAdmission = 0;
 55176        _nextPublication = 0;
 55177        _active = true;
 55178        work = new NavigationFlowBatchWork(this, _activeGeneration);
 55179        return NavigationFlowQueryStatus.Pending;
 180    }
 181
 182    private BatchDescriptor GetDescriptor(int inputIndex)
 183    {
 220184        SwiftThrowHelper.ThrowIfArgumentOutOfRange(
 220185            inputIndex < 0 || inputIndex >= _activeCount,
 220186            inputIndex,
 220187            nameof(inputIndex));
 218188        int descriptorOrdinal = 0;
 233189        while (_descriptors[descriptorOrdinal].InputIndex != inputIndex)
 15190            descriptorOrdinal++;
 218191        return _descriptors[descriptorOrdinal];
 192    }
 193
 194    internal int GetAdmittedCount(NavigationFlowBatchWork work)
 195    {
 6196        lock (_sync)
 197        {
 6198            EnsureActive(work);
 6199            return _activeAdmittedCount;
 200        }
 6201    }
 202
 203    internal bool IsAdmissionComplete(NavigationFlowBatchWork work)
 204    {
 140205        lock (_sync)
 206        {
 140207            EnsureActive(work);
 140208            return _nextAdmission >= _activeAdmittedCount;
 209        }
 140210    }
 211
 212    internal NavigationFlowQueryStatus GetStatus(
 213        NavigationFlowBatchWork work,
 214        int inputIndex)
 215    {
 61216        lock (_sync)
 217        {
 61218            EnsureActive(work);
 58219            BatchDescriptor descriptor = GetDescriptor(inputIndex);
 56220            if (descriptor.SlotIndex < 0)
 6221                return NavigationFlowQueryStatus.CapacityExceeded;
 50222            NavigationFlowQueryWork query = _queries[descriptor.SlotIndex];
 50223            lock (query)
 50224                return query.Status;
 225        }
 56226    }
 227
 228    internal bool IsReadyToPublish(
 229        NavigationFlowBatchWork work,
 230        int inputIndex)
 231    {
 87232        lock (_sync)
 233        {
 87234            EnsureActive(work);
 87235            BatchDescriptor descriptor = GetDescriptor(inputIndex);
 87236            return descriptor.SlotIndex < 0
 87237                || _queries[descriptor.SlotIndex].IsReadyToPublish;
 238        }
 87239    }
 240
 241    internal void AdvanceAdmission(
 242        NavigationFlowBatchWork work,
 243        int lookupStepLimit,
 244        int endpointCandidateStepLimit)
 245    {
 85246        lock (_sync)
 247        {
 85248            EnsureActive(work);
 134249            while (_nextAdmission < _activeAdmittedCount)
 250            {
 88251                NavigationFlowQueryWork query = _queries[_nextAdmission];
 88252                query.PrepareSearchOrCheckout(
 88253                    lookupStepLimit,
 88254                    endpointCandidateStepLimit);
 88255                if (!query.IsPrepared)
 37256                    return;
 51257                if (query.ReservationRejected)
 258                {
 2259                    CutCapacitySuffix(_nextAdmission);
 2260                    return;
 261                }
 49262                _nextAdmission++;
 263            }
 46264        }
 85265    }
 266
 267    internal NavigationFlowQueryStatus AdvanceSearch(
 268        NavigationFlowBatchWork work,
 269        int inputIndex,
 270        int lookupStepLimit,
 271        int nodeStepLimit,
 272        int edgeStepLimit,
 273        int connectionStepLimit)
 274    {
 275        BatchDescriptor descriptor;
 276        NavigationFlowQueryWork query;
 40277        lock (_sync)
 278        {
 40279            EnsureActive(work);
 40280            SwiftThrowHelper.ThrowIfTrue(
 40281                _nextAdmission < _activeAdmittedCount,
 40282                message: "The flow batch has not completed its sequential preparation barrier.");
 38283            descriptor = GetDescriptor(inputIndex);
 38284            if (descriptor.SlotIndex < 0)
 1285                return NavigationFlowQueryStatus.CapacityExceeded;
 37286            query = _queries[descriptor.SlotIndex];
 37287            Monitor.Enter(query);
 37288        }
 289        try
 290        {
 37291            return query.AdvanceSearch(
 37292                lookupStepLimit,
 37293                nodeStepLimit,
 37294                edgeStepLimit,
 37295                connectionStepLimit);
 296        }
 297        finally
 298        {
 37299            Monitor.Exit(query);
 37300        }
 38301    }
 302
 303    internal int PublishReadyPrefix(
 304        NavigationFlowBatchWork work,
 305        int maximumCount)
 306    {
 49307        SwiftThrowHelper.ThrowIfNegative(maximumCount, nameof(maximumCount));
 49308        lock (_sync)
 309        {
 49310            EnsureActive(work);
 49311            SwiftThrowHelper.ThrowIfTrue(
 49312                _nextAdmission < _activeAdmittedCount,
 49313                message: "The flow batch has not completed its sequential preparation barrier.");
 47314            int published = 0;
 94315            while (published < maximumCount
 94316                && _nextPublication < _activeAdmittedCount)
 317            {
 48318                NavigationFlowQueryWork query = _queries[_nextPublication];
 48319                lock (query)
 320                {
 48321                    if (!query.IsReadyToPublish)
 1322                        break;
 47323                    query.Publish();
 47324                }
 47325                _nextPublication++;
 47326                published++;
 327            }
 47328            return published;
 329        }
 47330    }
 331
 332    internal NavigationFlowQueryResult TakeResult(
 333        NavigationFlowBatchWork work,
 334        int inputIndex)
 335    {
 37336        lock (_sync)
 337        {
 37338            EnsureActive(work);
 37339            BatchDescriptor descriptor = GetDescriptor(inputIndex);
 37340            SwiftThrowHelper.ThrowIfTrue(
 37341                descriptor.SlotIndex < 0,
 37342                message: "The flow query was not admitted.");
 36343            return _queries[descriptor.SlotIndex].TakeResult();
 344        }
 36345    }
 346
 347    internal void Release(NavigationFlowBatchWork work)
 348    {
 56349        lock (_sync)
 350        {
 56351            if (!IsActive(work))
 1352                return;
 220353            for (int i = 0; i < _activeAdmittedCount; i++)
 354            {
 55355                lock (_queries[i])
 55356                    _queries[i].Dispose();
 357            }
 55358            _coordinator.Release(_capacityReservation);
 55359            _capacityReservation = default;
 55360            if (_activeCount > 0)
 55361                Array.Clear(_descriptors, 0, _activeCount);
 55362            _active = false;
 55363            _activeGeneration = 0;
 55364            _activeCount = 0;
 55365            _activeAdmittedCount = 0;
 55366            _nextAdmission = 0;
 55367            _nextPublication = 0;
 55368        }
 56369    }
 370
 371    private void CutCapacitySuffix(int firstRejected)
 372    {
 2373        int previousCount = _activeAdmittedCount;
 12374        for (int i = firstRejected; i < previousCount; i++)
 375        {
 4376            _queries[i].Dispose();
 4377            _descriptors[i].SlotIndex = -1;
 378        }
 2379        _activeAdmittedCount = firstRejected;
 2380        _nextAdmission = firstRejected;
 2381        _capacityReservation = _coordinator.Trim(
 2382            _capacityReservation,
 2383            firstRejected);
 2384    }
 385
 386    private void EnsureActive(NavigationFlowBatchWork work)
 387    {
 505388        if (!IsActive(work))
 3389            throw new ObjectDisposedException(nameof(NavigationFlowBatchWork));
 502390    }
 391
 392    private bool IsActive(NavigationFlowBatchWork work) =>
 561393        _active
 561394        && work.IsOwnedBy(this)
 561395        && work.Generation == _activeGeneration;
 396
 397    private struct BatchDescriptor : IComparable<BatchDescriptor>
 398    {
 399        internal BatchDescriptor(long stableOrdinal, int inputIndex, PathQuery query)
 400        {
 61401            StableOrdinal = stableOrdinal;
 61402            InputIndex = inputIndex;
 61403            Query = query;
 61404            SlotIndex = -1;
 61405        }
 406
 407        internal long StableOrdinal;
 408        internal int InputIndex;
 409        internal PathQuery Query;
 410        internal int SlotIndex;
 411
 412        public readonly int CompareTo(BatchDescriptor other)
 413        {
 8414            int ordinalComparison = StableOrdinal.CompareTo(other.StableOrdinal);
 8415            return ordinalComparison != 0
 8416                ? ordinalComparison
 8417                : InputIndex.CompareTo(other.InputIndex);
 418        }
 419    }
 420}

Methods/Properties

.ctor(GridForge.Grids.GridWorld,Trailblazer.Pathing.NavigationWorldGraphStore,Trailblazer.Pathing.NavigationQueryLimits,Trailblazer.Pathing.NavigationQueryAdmissionCoordinator,Trailblazer.Pathing.NavigationImmediateRayWorkspace)
get_PayloadCache()
Begin(Trailblazer.Pathing.PathQuery,Trailblazer.Pathing.NavigationFlowBatchWork&)
Begin(Trailblazer.Pathing.PathQueryBatch,Trailblazer.Pathing.NavigationFlowBatchWork&)
Dispose()
CancelActive()
CanBegin()
FitsBatchEnvelope(Trailblazer.Pathing.PathQueryBatch)
FitsSingleEnvelope(Trailblazer.Pathing.PathQuery)
BeginCore(System.Int32,Trailblazer.Pathing.NavigationFlowBatchWork&)
GetDescriptor(System.Int32)
GetAdmittedCount(Trailblazer.Pathing.NavigationFlowBatchWork)
IsAdmissionComplete(Trailblazer.Pathing.NavigationFlowBatchWork)
GetStatus(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32)
IsReadyToPublish(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32)
AdvanceAdmission(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32,System.Int32)
AdvanceSearch(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32)
PublishReadyPrefix(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32)
TakeResult(Trailblazer.Pathing.NavigationFlowBatchWork,System.Int32)
Release(Trailblazer.Pathing.NavigationFlowBatchWork)
CutCapacitySuffix(System.Int32)
EnsureActive(Trailblazer.Pathing.NavigationFlowBatchWork)
IsActive(Trailblazer.Pathing.NavigationFlowBatchWork)
.ctor(System.Int64,System.Int32,Trailblazer.Pathing.PathQuery)
CompareTo(Trailblazer.Pathing.NavigationFlowAdmissionGate/BatchDescriptor)