< Summary

Information
Class: Trailblazer.Pathing.NavigationWorldGraphStore
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationWorldGraphStore.cs
Line coverage
100%
Covered lines: 141
Uncovered lines: 0
Coverable lines: 141
Total lines: 264
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%11100%
get_Current()100%11100%
get_ActiveLeaseCount()100%11100%
get_ActiveGenerationCount()100%11100%
get_RetiredBytes()100%22100%
get_RetiredGenerationCount()100%11100%
TryAcquire()100%88100%
TryAcquirePrefix(...)100%1010100%
Return(...)100%44100%
TryPublish(...)100%1010100%
get_CanPublish()100%22100%
get_IsSafetyPending()100%11100%
MarkSafetyPending()100%11100%
ClearSafetyPending()100%11100%
SetSafetyPending(...)100%11100%
Dispose()100%22100%
CollectReleased()100%66100%
RentLeaseUnderLock(...)100%22100%
WouldExceedRetiredCapacity(...)100%88100%
WouldExceedRetiredCapacityAfterLease(...)100%1010100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationWorldGraphStore.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationWorldGraphStore.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 SwiftCollections;
 11
 12namespace Trailblazer.Pathing;
 13
 14/// <summary>Publishes immutable roots and retains only generations with active leases.</summary>
 15internal sealed class NavigationWorldGraphStore : System.IDisposable
 16{
 181917    private readonly object _sync = new();
 181918    private readonly SwiftList<NavigationWorldGraph> _retired = new();
 181919    private readonly SwiftList<NavigationWorldGraphLease> _leasePool = new();
 181920    private NavigationWorldGraph _current = NavigationWorldGraph.Empty;
 21
 22    private readonly int _maxRetiredSnapshots;
 23    private readonly int _maxActiveSnapshots;
 24    private readonly long _maxRetiredBytes;
 25    private readonly long _maxActiveBytes;
 26    private readonly int _maxPersistentPages;
 27    private readonly int _maxConcurrentLeases;
 28    private int _activeLeaseCount;
 29    private bool _safetyPending;
 30    private bool _disposed;
 31
 181932    internal NavigationWorldGraphStore(
 181933        int maxActiveSnapshots,
 181934        int maxRetiredSnapshots,
 181935        long maxRetiredBytes,
 181936        long maxActiveBytes,
 181937        int maxPersistentPages,
 181938        int maxConcurrentLeases)
 39    {
 181940        _maxActiveSnapshots = maxActiveSnapshots;
 181941        _maxRetiredSnapshots = maxRetiredSnapshots;
 181942        _maxRetiredBytes = maxRetiredBytes;
 181943        _maxActiveBytes = maxActiveBytes;
 181944        _maxPersistentPages = maxPersistentPages;
 181945        _maxConcurrentLeases = maxConcurrentLeases;
 181946    }
 47
 20518448    internal NavigationWorldGraph Current => Volatile.Read(ref _current);
 49
 50    internal int ActiveLeaseCount
 51    {
 696352        get { lock (_sync) return _activeLeaseCount; }
 53    }
 54
 55    internal int ActiveGenerationCount
 56    {
 57        get
 58        {
 220059            lock (_sync)
 60            {
 220061                CollectReleased();
 220062                return 1 + _retired.Count;
 63            }
 220064        }
 65    }
 66
 67    internal long RetiredBytes
 68    {
 69        get
 70        {
 220371            lock (_sync)
 72            {
 220373                CollectReleased();
 220374                long bytes = 0;
 444275                for (int i = 0; i < _retired.Count; i++)
 1876                    bytes += _retired[i].RetainedBytes;
 220377                return bytes;
 78            }
 220379        }
 80    }
 81
 82    internal int RetiredGenerationCount
 83    {
 84        get
 85        {
 220586            lock (_sync)
 87            {
 220588                CollectReleased();
 220589                return _retired.Count;
 90            }
 220591        }
 92    }
 93
 94    internal NavigationWorldGraphLease? TryAcquire()
 95    {
 897496        lock (_sync)
 97        {
 897498            CollectReleased();
 897499            if (_disposed
 8974100                || _safetyPending
 8974101                || _activeLeaseCount >= _maxConcurrentLeases
 8974102                || WouldExceedRetiredCapacityAfterLease(Current))
 25103                return null;
 8949104            NavigationWorldGraph current = Current;
 8949105            return RentLeaseUnderLock(current);
 106        }
 8974107    }
 108
 109    internal int TryAcquirePrefix(Span<NavigationWorldGraphLease?> output)
 110    {
 466111        lock (_sync)
 112        {
 466113            output.Clear();
 466114            CollectReleased();
 466115            if (_disposed || _safetyPending || output.Length == 0)
 3116                return 0;
 463117            NavigationWorldGraph current = Current;
 463118            if (WouldExceedRetiredCapacityAfterLease(current))
 1119                return 0;
 462120            int count = System.Math.Min(
 462121                output.Length,
 462122                _maxConcurrentLeases - _activeLeaseCount);
 1868123            for (int i = 0; i < count; i++)
 472124                output[i] = RentLeaseUnderLock(current);
 462125            return count;
 126        }
 466127    }
 128
 129    internal void Return(NavigationWorldGraphLease lease)
 130    {
 9420131        NavigationWorldGraph graph = lease.DetachGraph();
 9420132        graph.Return();
 9420133        lock (_sync)
 134        {
 9420135            _activeLeaseCount--;
 9420136            CollectReleased();
 9420137            if (!_disposed && _leasePool.Count < _maxConcurrentLeases)
 9418138                _leasePool.Add(lease);
 9420139        }
 9420140    }
 141
 142    internal NavigationCandidatePublication TryPublish(NavigationWorldGraph next)
 143    {
 5029144        lock (_sync)
 145        {
 5029146            CollectReleased();
 5029147            if (_disposed)
 1148                return NavigationCandidatePublication.Deferred;
 5028149            SwiftThrowHelper.ThrowIfTrue(
 5028150                next.GraphVersion <= _current.GraphVersion,
 5028151                message: "Published navigation graph versions must increase monotonically.");
 5026152            if (next.RetainedBytes > _maxActiveBytes
 5026153                || next.PersistentPageCount > _maxPersistentPages)
 3154                return NavigationCandidatePublication.PermanentCapacity;
 5023155            if (WouldExceedRetiredCapacity(_current))
 5156                return NavigationCandidatePublication.Deferred;
 5018157            NavigationWorldGraph prior = _current;
 5018158            Volatile.Write(ref _current, next);
 5018159            if (prior.LeaseCount > 0)
 26160                _retired.Add(prior);
 5018161            CollectReleased();
 5018162            return NavigationCandidatePublication.Published;
 163        }
 5027164    }
 165
 166    internal bool CanPublish
 167    {
 168        get
 169        {
 20564170            lock (_sync)
 171            {
 20564172                CollectReleased();
 20564173                return !_disposed && !WouldExceedRetiredCapacity(_current);
 174            }
 20564175        }
 176    }
 177
 178    internal bool IsSafetyPending
 179    {
 60723180        get { lock (_sync) return _safetyPending; }
 181    }
 182
 183    internal void MarkSafetyPending()
 184    {
 8185        SetSafetyPending(true);
 8186    }
 187
 188    internal void ClearSafetyPending()
 189    {
 108190        SetSafetyPending(false);
 108191    }
 192
 193    internal void SetSafetyPending(bool pending)
 194    {
 152195        lock (_sync)
 152196            _safetyPending = pending;
 152197    }
 198
 199    public void Dispose()
 200    {
 1819201        lock (_sync)
 202        {
 1819203            if (_disposed)
 1204                return;
 1818205            _disposed = true;
 1818206            _leasePool.Clear();
 1818207            CollectReleased();
 1818208        }
 1819209    }
 210
 211    private void CollectReleased()
 212    {
 57897213        int destination = 0;
 116270214        for (int i = 0; i < _retired.Count; i++)
 215        {
 238216            NavigationWorldGraph graph = _retired[i];
 238217            if (graph.LeaseCount > 0)
 212218                _retired[destination++] = graph;
 219        }
 57923220        while (_retired.Count > destination)
 26221            _retired.RemoveAt(_retired.Count - 1);
 57897222    }
 223
 224    private NavigationWorldGraphLease RentLeaseUnderLock(
 225        NavigationWorldGraph current)
 226    {
 9421227        _activeLeaseCount++;
 9421228        if (_leasePool.Count == 0)
 1198229            return new NavigationWorldGraphLease(this, current);
 8223230        NavigationWorldGraphLease lease = _leasePool[_leasePool.Count - 1];
 8223231        _leasePool.RemoveAt(_leasePool.Count - 1);
 8223232        lease.Reinitialize(this, current);
 8223233        return lease;
 234    }
 235
 236    private bool WouldExceedRetiredCapacity(NavigationWorldGraph current)
 237    {
 25586238        if (current.LeaseCount == 0)
 25515239            return false;
 71240        if (_retired.Count >= _maxRetiredSnapshots
 71241            || _retired.Count + 2 > _maxActiveSnapshots)
 25242            return true;
 243
 46244        long bytes = current.RetainedBytes;
 94245        for (int i = 0; i < _retired.Count; i++)
 1246            bytes += _retired[i].RetainedBytes;
 46247        return bytes > _maxRetiredBytes;
 248    }
 249
 250    private bool WouldExceedRetiredCapacityAfterLease(NavigationWorldGraph current)
 251    {
 252        // A disabled retirement budget is a writer-blocking mode, not a reader ban.
 253        // Publication already refuses to retire a leased current root until all readers return.
 9413254        if (_maxRetiredSnapshots == 0 || _maxRetiredBytes == 0)
 25255            return false;
 9388256        if (_retired.Count >= _maxRetiredSnapshots
 9388257            || _retired.Count + 2 > _maxActiveSnapshots)
 2258            return true;
 9386259        long bytes = current.RetainedBytes;
 18804260        for (int i = 0; i < _retired.Count; i++)
 16261            bytes += _retired[i].RetainedBytes;
 9386262        return bytes > _maxRetiredBytes;
 263    }
 264}