< Summary

Information
Class: Trailblazer.Pathing.NavigationFlowFieldReservation
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Search/Flow/NavigationFlowFieldPayloadCache.cs
Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 1245
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%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationFlowFieldPayloadCache.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 GridForge.Grids;
 10
 11namespace Trailblazer.Pathing;
 12
 13internal readonly struct NavigationFlowFieldReservation
 14{
 15    internal NavigationFlowFieldReservation(
 16        NavigationFlowFieldPayloadCache owner,
 17        int slot,
 18        ulong generation)
 19    {
 54720        Owner = owner;
 54721        Slot = slot;
 54722        Generation = generation;
 54723    }
 24
 25    internal readonly NavigationFlowFieldPayloadCache? Owner;
 26    internal readonly int Slot;
 27    internal readonly ulong Generation;
 28}
 29
 30/// <summary>Stores bounded destination-centric canonical flow-field prefixes.</summary>
 31internal sealed class NavigationFlowFieldPayloadCache : IDisposable
 32{
 33    internal enum LeaseSlotState : byte
 34    {
 35        Free = 0,
 36        Reserved = 1,
 37        ActivePayload = 2,
 38        ActiveGuide = 3,
 39        Retired = 4
 40    }
 41
 42    private enum PrefixRelation : byte
 43    {
 44        Equal = 0,
 45        CandidateLonger = 1,
 46        ExistingLonger = 2
 47    }
 48
 49    private readonly object _sync = new();
 50    private readonly GridWorld _world;
 51    private readonly NavigationFlowFieldPayloadKey[] _keys;
 52    private readonly CacheEntry?[] _entries;
 53    private readonly byte[] _states;
 54    private readonly int[] _previous;
 55    private readonly int[] _next;
 56    private readonly LeaseSlot[] _leaseSlots;
 57    private readonly int[] _freeLeaseSlots;
 58    private readonly int _mask;
 59    private readonly int _maximumEntries;
 60    private readonly long _maximumReusableBytes;
 61    private readonly long _maximumSinglePayloadBytes;
 62    private readonly long _maximumActivePayloadBytes;
 63    private int _freeLeaseCount;
 64    private int _leastRecent = -1;
 65    private int _mostRecent = -1;
 66    private int _count;
 67    private int _activeLeaseCount;
 68    private int _reservedLeaseCount;
 69    private long _cachedBytes;
 70    private long _leasedBytes;
 71    private long _detachedBytes;
 72    private long _reservedPayloadBytes;
 73    private bool _disposed;
 74    private NavigationFlowFieldGuideLease?[] _freeGuides;
 75    private int _freeGuideCount;
 76    private readonly int _guideMapCapacity;
 77    private readonly NavigationImmediateRayWorkspace _immediateRayWorkspace;
 78
 79    internal NavigationFlowFieldPayloadCache(
 80        GridWorld world,
 81        int maxEntries,
 82        long maxReusableBytes,
 83        long maxSinglePayloadBytes,
 84        long maxActivePayloadBytes,
 85        int maxActiveLeases,
 86        int guideMapCapacity,
 87        NavigationImmediateRayWorkspace immediateRayWorkspace)
 88    {
 89        SwiftThrowHelper.ThrowIfNull(world, nameof(world));
 90        SwiftThrowHelper.ThrowIfNegative(maxEntries, nameof(maxEntries));
 91        if (maxReusableBytes < 0)
 92            throw new ArgumentOutOfRangeException(nameof(maxReusableBytes));
 93        if (maxSinglePayloadBytes < 0)
 94            throw new ArgumentOutOfRangeException(nameof(maxSinglePayloadBytes));
 95        if (maxActivePayloadBytes < 0)
 96            throw new ArgumentOutOfRangeException(nameof(maxActivePayloadBytes));
 97        SwiftThrowHelper.ThrowIfNegative(maxActiveLeases, nameof(maxActiveLeases));
 98        SwiftThrowHelper.ThrowIfNegative(guideMapCapacity, nameof(guideMapCapacity));
 99        SwiftThrowHelper.ThrowIfNull(immediateRayWorkspace, nameof(immediateRayWorkspace));
 100        SwiftThrowHelper.ThrowIfArgument(
 101            maxSinglePayloadBytes > maxActivePayloadBytes,
 102            nameof(maxSinglePayloadBytes),
 103            "A single payload cannot exceed the complete active-payload byte ceiling.");
 104
 105        int tableSize = 1;
 106        int required = checked(Math.Max(1, maxEntries * 2));
 107        while (tableSize < required)
 108            tableSize = checked(tableSize * 2);
 109        _world = world;
 110        _keys = new NavigationFlowFieldPayloadKey[tableSize];
 111        _entries = new CacheEntry[tableSize];
 112        _states = new byte[tableSize];
 113        _previous = new int[tableSize];
 114        _next = new int[tableSize];
 115        Array.Fill(_previous, -1);
 116        Array.Fill(_next, -1);
 117        _leaseSlots = maxActiveLeases == 0
 118            ? Array.Empty<LeaseSlot>()
 119            : new LeaseSlot[maxActiveLeases];
 120        _freeLeaseSlots = maxActiveLeases == 0
 121            ? Array.Empty<int>()
 122            : new int[maxActiveLeases];
 123        for (int i = 0; i < _freeLeaseSlots.Length; i++)
 124            _freeLeaseSlots[i] = _freeLeaseSlots.Length - i - 1;
 125        _freeLeaseCount = _freeLeaseSlots.Length;
 126        _freeGuides = maxActiveLeases == 0
 127            ? Array.Empty<NavigationFlowFieldGuideLease?>()
 128            : new NavigationFlowFieldGuideLease[maxActiveLeases];
 129        for (int i = 0; i < _freeGuides.Length; i++)
 130            _freeGuides[i] = new NavigationFlowFieldGuideLease(guideMapCapacity);
 131        _freeGuideCount = _freeGuides.Length;
 132        _guideMapCapacity = guideMapCapacity;
 133        _immediateRayWorkspace = immediateRayWorkspace;
 134        _mask = tableSize - 1;
 135        _maximumEntries = maxEntries;
 136        _maximumReusableBytes = maxReusableBytes;
 137        _maximumSinglePayloadBytes = maxSinglePayloadBytes;
 138        _maximumActivePayloadBytes = maxActivePayloadBytes;
 139    }
 140
 141    internal int Count
 142    {
 143        get { lock (_sync) return _count; }
 144    }
 145
 146    internal NavigationImmediateRayWorkspace ImmediateRayWorkspace =>
 147        _immediateRayWorkspace;
 148
 149    internal GridWorld World => _world;
 150
 151    internal long CachedBytes
 152    {
 153        get { lock (_sync) return _cachedBytes; }
 154    }
 155
 156    internal long LeasedBytes
 157    {
 158        get { lock (_sync) return _leasedBytes; }
 159    }
 160
 161    internal long DetachedBytes
 162    {
 163        get { lock (_sync) return _detachedBytes; }
 164    }
 165
 166    internal long ReservedPayloadBytes
 167    {
 168        get { lock (_sync) return _reservedPayloadBytes; }
 169    }
 170
 171    internal int ActiveLeaseCount
 172    {
 173        get { lock (_sync) return _activeLeaseCount; }
 174    }
 175
 176    internal int ReservedLeaseCount
 177    {
 178        get { lock (_sync) return _reservedLeaseCount; }
 179    }
 180
 181    internal long MaximumSinglePayloadBytes => _maximumSinglePayloadBytes;
 182
 183    internal NavigationGuideStatus TryCreateGuide(
 184        NavigationWorldGraphStore store,
 185        NavigationFlowQueryResult result,
 186        out NavigationFlowFieldLease guide)
 187    {
 188        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 189        guide = default;
 190        NavigationFlowFieldPayloadLease payloadLease = result.PayloadLease;
 191        NavigationFlowFieldGuideLease? inner = null;
 192        NavigationFlowFieldPayload? payload = null;
 193        NavigationGuideStatus status = NavigationGuideStatus.Stale;
 194        int slot = -1;
 195        ulong generation = 0;
 196        FixedMathSharp.Fixed64 originIntegrationCost = FixedMathSharp.Fixed64.Zero;
 197        lock (_sync)
 198        {
 199            if (!_disposed
 200                && payloadLease.IsOwnedBy(this, out slot, out generation))
 201            {
 202                ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 203                if (leaseSlot.Generation == generation
 204                    && leaseSlot.State == LeaseSlotState.ActivePayload
 205                    && leaseSlot.Entry != null
 206                    && !leaseSlot.Entry.IsInvalidated)
 207                {
 208                    payload = leaseSlot.Entry.Payload;
 209                    NavigationWorldGraph current = store.Current;
 210                    if (current.MapCount > _guideMapCapacity)
 211                    {
 212                        status = NavigationGuideStatus.CapacityExceeded;
 213                    }
 214                    else if (!IsPayloadCurrent(current, payload))
 215                    {
 216                        leaseSlot.Entry.IsInvalidated = true;
 217                    }
 218                    else if (!payload.TryGetNode(
 219                            result.ResolvedOrigin,
 220                            payload.Key.StartMedium,
 221                            out NavigationFlowFieldNode origin))
 222                    {
 223                        status = NavigationGuideStatus.Stale;
 224                    }
 225                    else
 226                    {
 227                        inner = RentGuide(
 228                            _freeGuides,
 229                            ref _freeGuideCount,
 230                            _guideMapCapacity);
 231                        leaseSlot.State = LeaseSlotState.ActiveGuide;
 232                        originIntegrationCost = origin.IntegrationCost;
 233                        status = NavigationGuideStatus.Success;
 234                    }
 235                }
 236            }
 237        }
 238        if (status != NavigationGuideStatus.Success)
 239        {
 240            payloadLease.Dispose();
 241            return status;
 242        }
 243        NavigationFlowFieldGuideLease activeGuide = inner!;
 244        NavigationFlowFieldPayload activePayload = payload!;
 245        activeGuide.Bind(
 246            this,
 247            store,
 248            slot,
 249            generation,
 250            result.ResolvedOrigin,
 251            activePayload.Key.StartMedium,
 252            originIntegrationCost);
 253        guide = new NavigationFlowFieldLease(activeGuide);
 254        bool currentAfterBind = IsBoundGuideCurrent(
 255            TryGetGuidePayload(slot, generation, out _),
 256            IsPayloadCurrent(store.Current, activePayload));
 257        return ResolveBoundGuideValidation(currentAfterBind, ref guide);
 258    }
 259
 260    internal static bool IsBoundGuideCurrent(
 261        NavigationFlowFieldStatus leaseStatus,
 262        bool payloadCurrent) =>
 263        leaseStatus == NavigationFlowFieldStatus.Success && payloadCurrent;
 264
 265    internal static NavigationGuideStatus ResolveBoundGuideValidation(
 266        bool current,
 267        ref NavigationFlowFieldLease guide)
 268    {
 269        if (current)
 270            return NavigationGuideStatus.Success;
 271        guide.Dispose();
 272        guide = default;
 273        return NavigationGuideStatus.Stale;
 274    }
 275
 276    internal bool TryReservePayload(
 277        long maximumBytes,
 278        out NavigationFlowFieldReservation reservation)
 279    {
 280        if (maximumBytes < 0)
 281            throw new ArgumentOutOfRangeException(nameof(maximumBytes));
 282        reservation = default;
 283        if (maximumBytes > _maximumSinglePayloadBytes)
 284            return false;
 285        lock (_sync)
 286        {
 287            if (_disposed
 288                || maximumBytes > _maximumActivePayloadBytes
 289                    - _leasedBytes
 290                    - _reservedPayloadBytes
 291                || !TryIssueLeaseSlot(out int slot, out ulong generation))
 292            {
 293                return false;
 294            }
 295            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 296            leaseSlot.State = LeaseSlotState.Reserved;
 297            leaseSlot.MaximumBytes = maximumBytes;
 298            _reservedLeaseCount++;
 299            _reservedPayloadBytes = checked(_reservedPayloadBytes + maximumBytes);
 300            reservation = new NavigationFlowFieldReservation(this, slot, generation);
 301            return true;
 302        }
 303    }
 304
 305    internal void ReleasePayloadReservation(
 306        ref NavigationFlowFieldReservation reservation)
 307    {
 308        if (reservation.Owner == null)
 309        {
 310            reservation = default;
 311            return;
 312        }
 313        if (!ReferenceEquals(reservation.Owner, this))
 314            return;
 315        NavigationFlowFieldReservation released = reservation;
 316        reservation = default;
 317        lock (_sync)
 318        {
 319            if (TryGetReservationSlot(released, out int slot))
 320                ReleaseReservationSlot(slot);
 321        }
 322    }
 323
 324    internal NavigationFlowFieldStatus TryCheckout(
 325        NavigationWorldGraphStore store,
 326        NavigationWorldGraph expectedGraph,
 327        NavigationFlowFieldPayloadKey key,
 328        NavigationCellAddress requiredOrigin,
 329        out NavigationFlowFieldPayloadLease lease,
 330        out NavigationFlowFieldPayload? proof)
 331    {
 332        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 333        SwiftThrowHelper.ThrowIfNull(expectedGraph, nameof(expectedGraph));
 334        lease = default;
 335        proof = null;
 336        NavigationFlowFieldPayload? checkedPayload = null;
 337        NavigationFlowFieldStatus status;
 338        lock (_sync)
 339        {
 340            if (_disposed)
 341                return NavigationFlowFieldStatus.Stale;
 342            FindSlot(key, out int slot, out bool found);
 343            if (!found)
 344                return NavigationFlowFieldStatus.Pending;
 345            CacheEntry entry = _entries[slot]!;
 346            NavigationWorldGraph graph = store.Current;
 347            if (!IsPayloadCurrent(graph, entry.Payload))
 348            {
 349                RemoveAt(slot, invalidate: true);
 350                return NavigationFlowFieldStatus.Stale;
 351            }
 352            if (!IsPayloadCurrent(expectedGraph, entry.Payload))
 353                return NavigationFlowFieldStatus.Pending;
 354            checkedPayload = entry.Payload;
 355            status = ClassifyCoverage(checkedPayload, requiredOrigin);
 356            if (status == NavigationFlowFieldStatus.Success)
 357            {
 358                if (!TryCheckout(entry, out lease))
 359                    return NavigationFlowFieldStatus.CapacityExceeded;
 360                Touch(slot);
 361            }
 362            else if (status is NavigationFlowFieldStatus.NoPath
 363                or NavigationFlowFieldStatus.CostOverflow)
 364            {
 365                Touch(slot);
 366            }
 367        }
 368        if (status != NavigationFlowFieldStatus.Pending)
 369        {
 370            status = CompletePostLockValidation(
 371                store,
 372                checkedPayload!,
 373                status,
 374                ref lease);
 375            proof = ResolveValidatedProof(status, checkedPayload);
 376        }
 377        return status;
 378    }
 379
 380    internal static NavigationFlowFieldPayload? ResolveValidatedProof(
 381        NavigationFlowFieldStatus status,
 382        NavigationFlowFieldPayload payload) => status == NavigationFlowFieldStatus.Stale
 383        ? null
 384        : payload;
 385
 386    internal bool IsExactProofCurrent(
 387        NavigationWorldGraphStore store,
 388        NavigationFlowFieldPayload proof,
 389        NavigationCellAddress requiredOrigin,
 390        NavigationFlowFieldStatus expectedStatus)
 391    {
 392        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 393        SwiftThrowHelper.ThrowIfNull(proof, nameof(proof));
 394        bool current;
 395        lock (_sync)
 396        {
 397            FindSlot(proof.Key, out int slot, out bool found);
 398            current = !_disposed
 399                && found
 400                && ReferenceEquals(_entries[slot]!.Payload, proof)
 401                && IsPayloadCurrent(store.Current, proof)
 402                && ClassifyCoverage(proof, requiredOrigin) == expectedStatus;
 403        }
 404        if (current && IsPayloadCurrent(store.Current, proof))
 405        {
 406            return true;
 407        }
 408        InvalidateExact(proof);
 409        return false;
 410    }
 411
 412    internal NavigationFlowFieldStatus TryPublishOrPromote(
 413        NavigationWorldGraphStore store,
 414        NavigationFlowFieldPayload payload,
 415        NavigationCellAddress requiredOrigin,
 416        ref NavigationFlowFieldReservation reservation,
 417        out NavigationFlowFieldPayloadLease lease)
 418    {
 419        SwiftThrowHelper.ThrowIfNull(store, nameof(store));
 420        SwiftThrowHelper.ThrowIfNull(payload, nameof(payload));
 421        lease = default;
 422        NavigationFlowFieldPayload? canonical = null;
 423        NavigationFlowFieldStatus status;
 424        lock (_sync)
 425        {
 426            if (_disposed || !TryGetReservationSlot(reservation, out int reservationSlot))
 427                return NavigationFlowFieldStatus.Stale;
 428            ref LeaseSlot reserved = ref _leaseSlots[reservationSlot];
 429            if (payload.RetainedBytes > reserved.MaximumBytes)
 430                return NavigationFlowFieldStatus.CapacityExceeded;
 431            NavigationWorldGraph graph = store.Current;
 432            if (!IsPayloadCurrent(graph, payload))
 433                return NavigationFlowFieldStatus.Stale;
 434
 435            FindSlot(payload.Key, out int currentSlot, out bool found);
 436            if (found
 437                && !IsPayloadCurrent(graph, _entries[currentSlot]!.Payload))
 438            {
 439                RemoveAt(currentSlot, invalidate: true);
 440                found = false;
 441            }
 442
 443            status = ClassifyCoverage(payload, requiredOrigin);
 444
 445            CacheEntry? incumbent = found ? _entries[currentSlot] : null;
 446            if (incumbent != null)
 447            {
 448                PrefixRelation relation = ComparePrefixes(incumbent.Payload, payload);
 449                if (relation is PrefixRelation.Equal or PrefixRelation.ExistingLonger)
 450                {
 451                    canonical = incumbent.Payload;
 452                    status = ClassifyCoverage(canonical, requiredOrigin);
 453                    if (!TryFinishExisting(
 454                            currentSlot,
 455                            incumbent,
 456                            status,
 457                            reservationSlot,
 458                            ref reservation,
 459                            out lease))
 460                    {
 461                        return NavigationFlowFieldStatus.CapacityExceeded;
 462                    }
 463                }
 464                else
 465                {
 466                    canonical = payload;
 467                    PublishCandidate(
 468                        payload,
 469                        status,
 470                        currentSlot,
 471                        incumbent,
 472                        reservationSlot,
 473                        ref reservation,
 474                        out lease);
 475                }
 476            }
 477            else
 478            {
 479                canonical = payload;
 480                PublishCandidate(
 481                    payload,
 482                    status,
 483                    currentSlot: -1,
 484                    incumbent: null,
 485                    reservationSlot,
 486                    ref reservation,
 487                    out lease);
 488            }
 489        }
 490
 491        return CompletePostLockValidation(
 492            store,
 493            canonical!,
 494            status,
 495            ref lease);
 496    }
 497
 498    internal NavigationFlowFieldStatus TryGetPayload(
 499        int slot,
 500        ulong generation,
 501        out NavigationFlowFieldPayload payload)
 502    {
 503        lock (_sync)
 504        {
 505            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 506            CacheEntry? entry = leaseSlot.Entry;
 507            if (leaseSlot.Generation == generation
 508                && leaseSlot.State == LeaseSlotState.ActivePayload
 509                && entry != null
 510                && !entry!.IsInvalidated)
 511            {
 512                payload = entry!.Payload;
 513                return NavigationFlowFieldStatus.Success;
 514            }
 515        }
 516        payload = null!;
 517        return NavigationFlowFieldStatus.Stale;
 518    }
 519
 520    internal NavigationFlowFieldStatus TryGetGuidePayload(
 521        int slot,
 522        ulong generation,
 523        out NavigationFlowFieldPayload payload)
 524    {
 525        lock (_sync)
 526        {
 527            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 528            CacheEntry? entry = leaseSlot.Entry;
 529            if (leaseSlot.Generation == generation
 530                && leaseSlot.State == LeaseSlotState.ActiveGuide
 531                && entry != null
 532                && !entry!.IsInvalidated)
 533            {
 534                payload = entry!.Payload;
 535                return NavigationFlowFieldStatus.Success;
 536            }
 537        }
 538        payload = null!;
 539        return NavigationFlowFieldStatus.Stale;
 540    }
 541
 542    internal void Return(int slot, ulong generation)
 543    {
 544        lock (_sync)
 545        {
 546            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 547            bool active = leaseSlot.Generation == generation
 548                && leaseSlot.State == LeaseSlotState.ActivePayload
 549                && leaseSlot.Entry != null;
 550            if (!active)
 551            {
 552                return;
 553            }
 554            CacheEntry entry = leaseSlot.Entry!;
 555            entry.LeaseCount--;
 556            _activeLeaseCount--;
 557            if (entry.LeaseCount == 0)
 558            {
 559                _leasedBytes = checked(_leasedBytes - entry.Payload.RetainedBytes);
 560                if (!entry.IsCached)
 561                    _detachedBytes = checked(_detachedBytes - entry.Payload.RetainedBytes);
 562            }
 563            RecycleLeaseSlot(slot);
 564        }
 565    }
 566
 567    internal void ReturnDetachedGuide(
 568        NavigationFlowFieldGuideLease guide,
 569        ulong generation,
 570        int slot)
 571    {
 572        lock (_sync)
 573        {
 574            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 575            CacheEntry entry = leaseSlot.Entry!;
 576            entry.LeaseCount--;
 577            _activeLeaseCount--;
 578            if (entry.LeaseCount == 0)
 579            {
 580                _leasedBytes = checked(_leasedBytes - entry.Payload.RetainedBytes);
 581                if (!entry.IsCached)
 582                {
 583                    _detachedBytes = checked(
 584                        _detachedBytes - entry.Payload.RetainedBytes);
 585                }
 586            }
 587            RecycleLeaseSlot(slot);
 588            _freeGuideCount = RetainGuideIfReusable(
 589                generation,
 590                _disposed,
 591                guide,
 592                _freeGuides,
 593                _freeGuideCount);
 594        }
 595    }
 596
 597    internal static int RetainGuideIfReusable(
 598        ulong generation,
 599        bool disposed,
 600        NavigationFlowFieldGuideLease guide,
 601        NavigationFlowFieldGuideLease?[] freeGuides,
 602        int freeGuideCount)
 603    {
 604        if (disposed
 605            || !NavigationGenerationCounter.CanAdvance(generation)
 606            || freeGuideCount >= freeGuides.Length)
 607        {
 608            return freeGuideCount;
 609        }
 610        freeGuides[freeGuideCount] = guide;
 611        return freeGuideCount + 1;
 612    }
 613
 614    internal void RemoveExact(NavigationFlowFieldPayload payload)
 615    {
 616        SwiftThrowHelper.ThrowIfNull(payload, nameof(payload));
 617        InvalidateExact(payload);
 618    }
 619
 620    internal void Reset()
 621    {
 622        lock (_sync)
 623        {
 624            if (_disposed)
 625                return;
 626            ResetUnderLock();
 627        }
 628    }
 629
 630    public void Dispose()
 631    {
 632        lock (_sync)
 633        {
 634            if (_disposed)
 635                return;
 636            ResetUnderLock();
 637            _freeGuides = Array.Empty<NavigationFlowFieldGuideLease?>();
 638            _freeGuideCount = 0;
 639            _disposed = true;
 640        }
 641    }
 642
 643    private bool TryFinishExisting(
 644        int currentSlot,
 645        CacheEntry incumbent,
 646        NavigationFlowFieldStatus status,
 647        int reservationSlot,
 648        ref NavigationFlowFieldReservation reservation,
 649        out NavigationFlowFieldPayloadLease lease)
 650    {
 651        lease = default;
 652        if (status == NavigationFlowFieldStatus.Success)
 653        {
 654            if (!CanActivate(incumbent, reservationSlot))
 655                return false;
 656            lease = ConvertReservation(
 657                incumbent,
 658                reservationSlot,
 659                ref reservation);
 660        }
 661        else
 662        {
 663            ReleaseReservationSlot(reservationSlot);
 664            reservation = default;
 665        }
 666        Touch(currentSlot);
 667        return true;
 668    }
 669
 670    private void PublishCandidate(
 671        NavigationFlowFieldPayload payload,
 672        NavigationFlowFieldStatus status,
 673        int currentSlot,
 674        CacheEntry? incumbent,
 675        int reservationSlot,
 676        ref NavigationFlowFieldReservation reservation,
 677        out NavigationFlowFieldPayloadLease lease)
 678    {
 679        lease = default;
 680        bool reusable = _maximumEntries > 0
 681            && payload.RetainedBytes <= _maximumReusableBytes;
 682        if (!reusable && status != NavigationFlowFieldStatus.Success)
 683        {
 684            ReleaseReservationSlot(reservationSlot);
 685            reservation = default;
 686            return;
 687        }
 688
 689        CacheEntry candidate;
 690        if (reusable)
 691        {
 692            EvictFor(payload.RetainedBytes, currentSlot);
 693            if (incumbent != null)
 694                RemoveAt(currentSlot, invalidate: false);
 695            candidate = new CacheEntry(payload, isCached: true);
 696            Insert(candidate);
 697        }
 698        else
 699        {
 700            candidate = new CacheEntry(payload, isCached: false);
 701        }
 702
 703        if (status == NavigationFlowFieldStatus.Success)
 704        {
 705            lease = ConvertReservation(candidate, reservationSlot, ref reservation);
 706        }
 707        else
 708        {
 709            ReleaseReservationSlot(reservationSlot);
 710            reservation = default;
 711        }
 712    }
 713
 714    private bool TryCheckout(
 715        CacheEntry entry,
 716        out NavigationFlowFieldPayloadLease lease)
 717    {
 718        lease = default;
 719        if (entry.LeaseCount == 0
 720            && entry.Payload.RetainedBytes > _maximumActivePayloadBytes
 721                - _leasedBytes
 722                - _reservedPayloadBytes)
 723        {
 724            return false;
 725        }
 726        if (!TryIssueLeaseSlot(out int slot, out ulong generation))
 727            return false;
 728        ActivateEntry(entry);
 729        ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 730        leaseSlot.State = LeaseSlotState.ActivePayload;
 731        leaseSlot.Entry = entry;
 732        _activeLeaseCount++;
 733        lease = new NavigationFlowFieldPayloadLease(this, slot, generation);
 734        return true;
 735    }
 736
 737    private NavigationFlowFieldPayloadLease ConvertReservation(
 738        CacheEntry entry,
 739        int reservationSlot,
 740        ref NavigationFlowFieldReservation reservation)
 741    {
 742        ref LeaseSlot leaseSlot = ref _leaseSlots[reservationSlot];
 743        _reservedPayloadBytes -= leaseSlot.MaximumBytes;
 744        _reservedLeaseCount--;
 745        leaseSlot.MaximumBytes = 0;
 746        leaseSlot.State = LeaseSlotState.ActivePayload;
 747        leaseSlot.Entry = entry;
 748        ActivateEntry(entry);
 749        _activeLeaseCount++;
 750        var lease = new NavigationFlowFieldPayloadLease(
 751            this,
 752            reservationSlot,
 753            leaseSlot.Generation);
 754        reservation = default;
 755        return lease;
 756    }
 757
 758    private void ActivateEntry(CacheEntry entry)
 759    {
 760        if (entry.LeaseCount == 0)
 761        {
 762            _leasedBytes = checked(_leasedBytes + entry.Payload.RetainedBytes);
 763            if (!entry.IsCached)
 764                _detachedBytes = checked(_detachedBytes + entry.Payload.RetainedBytes);
 765        }
 766        entry.LeaseCount++;
 767    }
 768
 769    internal static NavigationFlowFieldGuideLease RentGuide(
 770        NavigationFlowFieldGuideLease?[] freeGuides,
 771        ref int freeGuideCount,
 772        int coveredAddressGenerationCapacity)
 773    {
 774        if (freeGuideCount == 0)
 775            return new NavigationFlowFieldGuideLease(coveredAddressGenerationCapacity);
 776        int index = --freeGuideCount;
 777        NavigationFlowFieldGuideLease guide = freeGuides[index]!;
 778        freeGuides[index] = null;
 779        return guide;
 780    }
 781
 782    private bool CanActivate(CacheEntry entry, int reservationSlot) =>
 783        entry.LeaseCount != 0
 784        || CanActivateNew(entry.Payload.RetainedBytes, reservationSlot);
 785
 786    private bool CanActivateNew(long retainedBytes, int reservationSlot)
 787    {
 788        long ownReservation = _leaseSlots[reservationSlot].MaximumBytes;
 789        long otherReservations = _reservedPayloadBytes - ownReservation;
 790        return retainedBytes <= _maximumActivePayloadBytes
 791            - _leasedBytes
 792            - otherReservations;
 793    }
 794
 795    private static NavigationFlowFieldStatus ClassifyCoverage(
 796        NavigationFlowFieldPayload payload,
 797        NavigationCellAddress requiredOrigin)
 798    {
 799        if (!payload.TryGetNode(
 800                requiredOrigin,
 801                payload.Key.StartMedium,
 802                out NavigationFlowFieldNode origin))
 803        {
 804            return payload.IsComplete
 805                ? NavigationFlowFieldStatus.NoPath
 806                : NavigationFlowFieldStatus.Pending;
 807        }
 808        if (!FixedMathSharp.Fixed64.TryAdd(
 809                origin.IntegrationCost,
 810                payload.Key.FlowField.ExtraIntegrationCost,
 811                out FixedMathSharp.Fixed64 requiredCost))
 812        {
 813            return NavigationFlowFieldStatus.CostOverflow;
 814        }
 815        return payload.IsComplete || payload.LastSettledCost >= requiredCost
 816            ? NavigationFlowFieldStatus.Success
 817            : NavigationFlowFieldStatus.Pending;
 818    }
 819
 820    private static PrefixRelation ComparePrefixes(
 821        NavigationFlowFieldPayload existing,
 822        NavigationFlowFieldPayload candidate)
 823    {
 824        int common = Math.Min(existing.Nodes.Length, candidate.Nodes.Length);
 825        for (int i = 0; i < common; i++)
 826        {
 827            NavigationFlowFieldNode left = existing.Nodes[i];
 828            NavigationFlowFieldNode right = candidate.Nodes[i];
 829            bool nodesEqual = left.Address == right.Address
 830                && left.Medium == right.Medium
 831                && left.IntegrationCost == right.IntegrationCost
 832                && left.SelectedEdge.Equals(right.SelectedEdge)
 833                && left.TransitionInstructionOrdinal
 834                    == right.TransitionInstructionOrdinal
 835                && NodeTransitionInstructionsAreEqual(
 836                    existing,
 837                    left,
 838                    candidate,
 839                    right);
 840            SwiftThrowHelper.ThrowIfTrue(
 841                !nodesEqual,
 842                message: "Same-key flow payloads do not share one canonical node prefix.");
 843        }
 844
 845        if (existing.Nodes.Length == candidate.Nodes.Length)
 846        {
 847            bool completionEqual = existing.IsComplete == candidate.IsComplete
 848                && existing.WorldChangeSequence == candidate.WorldChangeSequence
 849                && existing.TransitionInstructions.Length
 850                    == candidate.TransitionInstructions.Length
 851                && DependenciesAreEqual(existing.Dependencies, candidate.Dependencies);
 852            SwiftThrowHelper.ThrowIfTrue(
 853                !completionEqual,
 854                message: "Equal flow prefixes do not share exact completion dependencies.");
 855            return PrefixRelation.Equal;
 856        }
 857
 858        NavigationFlowFieldPayload shorter = existing.Nodes.Length < candidate.Nodes.Length
 859            ? existing
 860            : candidate;
 861        NavigationFlowFieldPayload longer = ReferenceEquals(shorter, existing)
 862            ? candidate
 863            : existing;
 864        SwiftThrowHelper.ThrowIfTrue(
 865            shorter.IsComplete
 866            || !WorldDependencyIsSubset(shorter, longer)
 867            || longer.Nodes[shorter.Nodes.Length].IntegrationCost
 868                <= shorter.LastSettledCost
 869            || !DependenciesAreSubset(shorter.Dependencies, longer.Dependencies),
 870            message: "A complete or dependency-incompatible flow payload cannot be a strict prefix.");
 871        return ReferenceEquals(longer, candidate)
 872            ? PrefixRelation.CandidateLonger
 873            : PrefixRelation.ExistingLonger;
 874    }
 875
 876    internal bool IsWorldCurrent(NavigationFlowFieldPayload payload) =>
 877        !payload.WorldChangeSequence.HasValue
 878        || _world.ChangeSequence == payload.WorldChangeSequence.GetValueOrDefault();
 879
 880    internal bool IsPayloadCurrent(
 881        NavigationWorldGraph graph,
 882        NavigationFlowFieldPayload payload) =>
 883        graph.IsDependencyCurrent(payload.Dependencies)
 884        && IsWorldCurrent(payload);
 885
 886    internal NavigationFlowFieldStatus CompletePostLockValidation(
 887        NavigationWorldGraphStore store,
 888        NavigationFlowFieldPayload payload,
 889        NavigationFlowFieldStatus status,
 890        ref NavigationFlowFieldPayloadLease lease)
 891    {
 892        if (IsPayloadCurrent(store.Current, payload))
 893            return status;
 894        InvalidateExact(payload);
 895        lease.Dispose();
 896        lease = default;
 897        return NavigationFlowFieldStatus.Stale;
 898    }
 899
 900    // Both operands are current against the same graph before prefix comparison,
 901    // so two active transition-rule dependencies necessarily share its one version.
 902    private static bool DependenciesAreEqual(
 903        GraphDependencyStamp left,
 904        GraphDependencyStamp right) =>
 905        left.Components.Length == right.Components.Length
 906        && left.Pages.Length == right.Pages.Length
 907        && left.HasTransitionRuleDependency == right.HasTransitionRuleDependency
 908        && DependenciesAreSubset(left, right);
 909
 910    internal static bool DependenciesAreSubset(
 911        GraphDependencyStamp shorter,
 912        GraphDependencyStamp longer) =>
 913        // Prefix comparison only runs for payloads with the same key, and that
 914        // key already includes the exact area-policy revision.
 915        (!shorter.HasTransitionRuleDependency
 916            || longer.HasTransitionRuleDependency)
 917        && ComponentDependenciesAreSubset(shorter.Components, longer.Components)
 918        && PageDependenciesAreSubset(shorter.Pages, longer.Pages);
 919
 920    private static bool WorldDependencyIsSubset(
 921        NavigationFlowFieldPayload shorter,
 922        NavigationFlowFieldPayload longer) =>
 923        !shorter.WorldChangeSequence.HasValue
 924        || longer.WorldChangeSequence == shorter.WorldChangeSequence;
 925
 926    private static bool NodeTransitionInstructionsAreEqual(
 927        NavigationFlowFieldPayload leftPayload,
 928        NavigationFlowFieldNode left,
 929        NavigationFlowFieldPayload rightPayload,
 930        NavigationFlowFieldNode right)
 931    {
 932        int ordinal = left.TransitionInstructionOrdinal;
 933        if (ordinal < 0)
 934            return true;
 935        return TransitionInstructionsAreEqual(
 936            leftPayload.TransitionInstructions[ordinal],
 937            rightPayload.TransitionInstructions[ordinal]);
 938    }
 939
 940    private static bool TransitionInstructionsAreEqual(
 941        in NavigationTransitionInstruction left,
 942        in NavigationTransitionInstruction right) =>
 943        left.IdentityKind == right.IdentityKind
 944        && string.Equals(left.OwnerMapId, right.OwnerMapId, StringComparison.Ordinal)
 945        && string.Equals(left.Id, right.Id, StringComparison.Ordinal)
 946        && left.Type == right.Type
 947        && left.SourceAddress == right.SourceAddress
 948        && left.DestinationAddress == right.DestinationAddress
 949        && left.SourceMedium == right.SourceMedium
 950        && left.DestinationMedium == right.DestinationMedium
 951        && left.SourcePosition == right.SourcePosition
 952        && left.DestinationPosition == right.DestinationPosition
 953        && left.LocomotionHints == right.LocomotionHints;
 954
 955    private static bool ComponentDependenciesAreSubset(
 956        GraphComponentDependency[] shorter,
 957        GraphComponentDependency[] longer)
 958    {
 959        int longerIndex = 0;
 960        for (int i = 0; i < shorter.Length; i++)
 961        {
 962            GraphComponentDependency expected = shorter[i];
 963            while (longerIndex < longer.Length
 964                && longer[longerIndex].Key.CompareTo(expected.Key) < 0)
 965            {
 966                longerIndex++;
 967            }
 968            if (longerIndex >= longer.Length
 969                || !longer[longerIndex].Equals(expected))
 970            {
 971                return false;
 972            }
 973            longerIndex++;
 974        }
 975        return true;
 976    }
 977
 978    private static bool PageDependenciesAreSubset(
 979        GraphPageDependency[] shorter,
 980        GraphPageDependency[] longer)
 981    {
 982        int longerIndex = 0;
 983        for (int i = 0; i < shorter.Length; i++)
 984        {
 985            GraphPageDependency expected = shorter[i];
 986            while (longerIndex < longer.Length
 987                && ComparePageAddress(longer[longerIndex], expected) < 0)
 988            {
 989                longerIndex++;
 990            }
 991            if (longerIndex >= longer.Length
 992                || !longer[longerIndex].Equals(expected))
 993            {
 994                return false;
 995            }
 996            longerIndex++;
 997        }
 998        return true;
 999    }
 1000
 1001    private static int ComparePageAddress(
 1002        GraphPageDependency left,
 1003        GraphPageDependency right)
 1004    {
 1005        int mapComparison = string.CompareOrdinal(left.MapId, right.MapId);
 1006        return mapComparison != 0
 1007            ? mapComparison
 1008            : left.PageIndex.CompareTo(right.PageIndex);
 1009    }
 1010
 1011    private void EvictFor(long candidateBytes, int replacedSlot)
 1012    {
 1013        int prospectiveCount = _count + (replacedSlot >= 0 ? 0 : 1);
 1014        long prospectiveBytes = checked(
 1015            _cachedBytes
 1016            - (replacedSlot >= 0 ? _entries[replacedSlot]!.Payload.RetainedBytes : 0)
 1017            + candidateBytes);
 1018        while (prospectiveCount > _maximumEntries
 1019            || prospectiveBytes > _maximumReusableBytes)
 1020        {
 1021            int eviction = _leastRecent;
 1022            if (eviction == replacedSlot)
 1023                eviction = _next[eviction];
 1024            long bytes = _entries[eviction]!.Payload.RetainedBytes;
 1025            RemoveAt(eviction, invalidate: false);
 1026            prospectiveCount--;
 1027            prospectiveBytes -= bytes;
 1028        }
 1029    }
 1030
 1031    private void Insert(CacheEntry entry)
 1032    {
 1033        FindSlot(entry.Payload.Key, out int slot, out _);
 1034        _states[slot] = 1;
 1035        _keys[slot] = entry.Payload.Key;
 1036        _entries[slot] = entry;
 1037        LinkMostRecent(slot);
 1038        _count++;
 1039        _cachedBytes = checked(_cachedBytes + entry.Payload.RetainedBytes);
 1040    }
 1041
 1042    private bool TryIssueLeaseSlot(out int slot, out ulong generation)
 1043    {
 1044        if (_freeLeaseCount > 0)
 1045        {
 1046            slot = _freeLeaseSlots[--_freeLeaseCount];
 1047            ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 1048            // RecycleLeaseSlot never returns retired generations to the free stack.
 1049            leaseSlot.Generation++;
 1050            generation = leaseSlot.Generation;
 1051            return true;
 1052        }
 1053        slot = -1;
 1054        generation = 0;
 1055        return false;
 1056    }
 1057
 1058    private bool TryGetReservationSlot(
 1059        NavigationFlowFieldReservation reservation,
 1060        out int slot)
 1061    {
 1062        slot = reservation.Slot;
 1063        return ReferenceEquals(reservation.Owner, this)
 1064            && (uint)slot < (uint)_leaseSlots.Length
 1065            && _leaseSlots[slot].Generation == reservation.Generation
 1066            && _leaseSlots[slot].State == LeaseSlotState.Reserved;
 1067    }
 1068
 1069    private void ReleaseReservationSlot(int slot)
 1070    {
 1071        ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 1072        _reservedPayloadBytes -= leaseSlot.MaximumBytes;
 1073        _reservedLeaseCount--;
 1074        RecycleLeaseSlot(slot);
 1075    }
 1076
 1077    private void RecycleLeaseSlot(int slot)
 1078    {
 1079        ref LeaseSlot leaseSlot = ref _leaseSlots[slot];
 1080        leaseSlot.Entry = null;
 1081        leaseSlot.MaximumBytes = 0;
 1082        leaseSlot.State = RecycleLeaseSlotIdentity(
 1083            leaseSlot.Generation,
 1084            slot,
 1085            _freeLeaseSlots,
 1086            ref _freeLeaseCount);
 1087    }
 1088
 1089    internal static LeaseSlotState RecycleLeaseSlotIdentity(
 1090        ulong generation,
 1091        int slot,
 1092        int[] freeLeaseSlots,
 1093        ref int freeLeaseCount)
 1094    {
 1095        if (!NavigationGenerationCounter.CanAdvance(generation))
 1096            return LeaseSlotState.Retired;
 1097        freeLeaseSlots[freeLeaseCount++] = slot;
 1098        return LeaseSlotState.Free;
 1099    }
 1100
 1101    private void InvalidateExact(NavigationFlowFieldPayload payload)
 1102    {
 1103        lock (_sync)
 1104        {
 1105            FindSlot(payload.Key, out int slot, out bool found);
 1106            if (found && ReferenceEquals(_entries[slot]!.Payload, payload))
 1107                RemoveAt(slot, invalidate: true);
 1108            for (int i = 0; i < _leaseSlots.Length; i++)
 1109            {
 1110                ref LeaseSlot leaseSlot = ref _leaseSlots[i];
 1111                if ((leaseSlot.State is LeaseSlotState.ActivePayload
 1112                        or LeaseSlotState.ActiveGuide)
 1113                    && leaseSlot.Entry != null
 1114                    && ReferenceEquals(leaseSlot.Entry.Payload, payload))
 1115                {
 1116                    leaseSlot.Entry.IsInvalidated = true;
 1117                }
 1118            }
 1119        }
 1120    }
 1121
 1122    private void ResetUnderLock()
 1123    {
 1124        while (_leastRecent >= 0)
 1125            RemoveAt(_leastRecent, invalidate: true);
 1126        Array.Clear(_states, 0, _states.Length);
 1127        for (int i = 0; i < _leaseSlots.Length; i++)
 1128        {
 1129            ref LeaseSlot leaseSlot = ref _leaseSlots[i];
 1130            if (leaseSlot.State == LeaseSlotState.Reserved)
 1131            {
 1132                _reservedPayloadBytes -= leaseSlot.MaximumBytes;
 1133                _reservedLeaseCount--;
 1134                RecycleLeaseSlot(i);
 1135            }
 1136            else if (leaseSlot.State is LeaseSlotState.ActivePayload
 1137                or LeaseSlotState.ActiveGuide)
 1138            {
 1139                leaseSlot.Entry!.IsInvalidated = true;
 1140            }
 1141        }
 1142    }
 1143
 1144    private void FindSlot(
 1145        NavigationFlowFieldPayloadKey key,
 1146        out int slot,
 1147        out bool found)
 1148    {
 1149        slot = key.GetHashCode() & _mask;
 1150        int firstRemoved = -1;
 1151        for (int probe = 0; probe < _states.Length; probe++)
 1152        {
 1153            byte state = _states[slot];
 1154            if (state == 0)
 1155            {
 1156                found = false;
 1157                if (firstRemoved >= 0)
 1158                    slot = firstRemoved;
 1159                return;
 1160            }
 1161            if (state == 1 && _keys[slot].Equals(key))
 1162            {
 1163                found = true;
 1164                return;
 1165            }
 1166            if (state == 2 && firstRemoved < 0)
 1167                firstRemoved = slot;
 1168            slot = (slot + 1) & _mask;
 1169        }
 1170        found = false;
 1171        slot = firstRemoved;
 1172    }
 1173
 1174    private void Touch(int slot)
 1175    {
 1176        if (_mostRecent == slot)
 1177            return;
 1178        Unlink(slot);
 1179        LinkMostRecent(slot);
 1180    }
 1181
 1182    private void LinkMostRecent(int slot)
 1183    {
 1184        _previous[slot] = _mostRecent;
 1185        _next[slot] = -1;
 1186        if (_mostRecent >= 0)
 1187            _next[_mostRecent] = slot;
 1188        else
 1189            _leastRecent = slot;
 1190        _mostRecent = slot;
 1191    }
 1192
 1193    private void Unlink(int slot)
 1194    {
 1195        int previous = _previous[slot];
 1196        int next = _next[slot];
 1197        if (previous >= 0)
 1198            _next[previous] = next;
 1199        else
 1200            _leastRecent = next;
 1201        if (next >= 0)
 1202            _previous[next] = previous;
 1203        else
 1204            _mostRecent = previous;
 1205        _previous[slot] = -1;
 1206        _next[slot] = -1;
 1207    }
 1208
 1209    private void RemoveAt(int slot, bool invalidate)
 1210    {
 1211        CacheEntry entry = _entries[slot]!;
 1212        _cachedBytes = checked(_cachedBytes - entry.Payload.RetainedBytes);
 1213        entry.IsCached = false;
 1214        entry.IsInvalidated = entry.IsInvalidated || invalidate;
 1215        if (entry.LeaseCount != 0)
 1216            _detachedBytes = checked(_detachedBytes + entry.Payload.RetainedBytes);
 1217        Unlink(slot);
 1218        _keys[slot] = default;
 1219        _entries[slot] = null;
 1220        _states[slot] = 2;
 1221        _count--;
 1222    }
 1223
 1224    private sealed class CacheEntry
 1225    {
 1226        internal CacheEntry(NavigationFlowFieldPayload payload, bool isCached)
 1227        {
 1228            Payload = payload;
 1229            IsCached = isCached;
 1230        }
 1231
 1232        internal NavigationFlowFieldPayload Payload { get; }
 1233        internal int LeaseCount { get; set; }
 1234        internal bool IsCached { get; set; }
 1235        internal bool IsInvalidated { get; set; }
 1236    }
 1237
 1238    private struct LeaseSlot
 1239    {
 1240        internal CacheEntry? Entry;
 1241        internal long MaximumBytes;
 1242        internal ulong Generation;
 1243        internal LeaseSlotState State;
 1244    }
 1245}