< Summary

Information
Class: Trailblazer.Pathing.NavigationTransitionOwnerKey
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationTransitionPage.cs
Line coverage
100%
Covered lines: 9
Uncovered lines: 0
Coverable lines: 9
Total lines: 471
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
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%
Equals(...)100%22100%
GetHashCode()100%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationTransitionPage.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.Runtime.CompilerServices;
 10using GridForge.Spatial;
 11using SwiftCollections.Utility;
 12
 13namespace Trailblazer.Pathing;
 14
 15/// <summary>Identifies one source-map-owned explicit transition.</summary>
 16internal readonly struct NavigationTransitionOwnerKey : IEquatable<NavigationTransitionOwnerKey>
 17{
 18    internal NavigationTransitionOwnerKey(string mapId, string transitionId)
 19    {
 2105520        MapId = mapId;
 2105521        TransitionId = transitionId;
 2105522    }
 23
 24    internal string MapId { get; }
 25
 26    internal string TransitionId { get; }
 27
 28    public bool Equals(NavigationTransitionOwnerKey other) =>
 14176229        string.Equals(MapId, other.MapId, StringComparison.Ordinal)
 14176230        && string.Equals(TransitionId, other.TransitionId, StringComparison.Ordinal);
 31
 32    public override int GetHashCode()
 33    {
 1534        var comparer = SwiftHashTools.GetDeterministicStringEqualityComparer();
 1535        return SwiftHashTools.CombineHashCodes(
 1536            comparer.GetHashCode(MapId),
 1537            comparer.GetHashCode(TransitionId));
 38    }
 39}
 40
 41/// <summary>Identifies one exact map-local transition page.</summary>
 42internal readonly struct NavigationTransitionPageAddress : IEquatable<NavigationTransitionPageAddress>
 43{
 44    internal NavigationTransitionPageAddress(string mapId, int pageIndex)
 45    {
 46        MapId = mapId;
 47        PageIndex = pageIndex;
 48    }
 49
 50    internal string MapId { get; }
 51
 52    internal int PageIndex { get; }
 53
 54    public bool Equals(NavigationTransitionPageAddress other) =>
 55        PageIndex == other.PageIndex
 56        && string.Equals(MapId, other.MapId, StringComparison.Ordinal);
 57
 58    public override int GetHashCode()
 59    {
 60        var comparer = SwiftHashTools.GetDeterministicStringEqualityComparer();
 61        return SwiftHashTools.CombineHashCodes(comparer.GetHashCode(MapId), PageIndex);
 62    }
 63}
 64
 65/// <summary>Stores one complete source-owned explicit transition record.</summary>
 66internal readonly struct NavigationPublishedTransition : IEquatable<NavigationPublishedTransition>
 67{
 68    internal NavigationPublishedTransition(
 69        string ownerMapId,
 70        TraversalTransitionDefinition definition,
 71        NavigationTransitionPageAddress sourcePage,
 72        NavigationTransitionPageAddress? destinationPage)
 73    {
 74        Owner = new NavigationTransitionOwnerKey(ownerMapId, definition.Id);
 75        Definition = definition;
 76        SourcePageIndex = sourcePage.PageIndex;
 77        HasDestinationPage = destinationPage.HasValue;
 78        DestinationPageIndex = destinationPage?.PageIndex ?? -1;
 79    }
 80
 81    internal NavigationTransitionOwnerKey Owner { get; }
 82
 83    internal TraversalTransitionDefinition Definition { get; }
 84
 85    internal int SourcePageIndex { get; }
 86
 87    internal bool HasDestinationPage { get; }
 88
 89    internal int DestinationPageIndex { get; }
 90
 91    internal NavigationTransitionPageAddress SourcePage => new(
 92        Owner.MapId,
 93        SourcePageIndex);
 94
 95    internal NavigationTransitionPageAddress? DestinationPage => HasDestinationPage
 96        ? new NavigationTransitionPageAddress(
 97            Definition.Destination.MapId,
 98            DestinationPageIndex)
 99        : null;
 100
 101    internal NavigationCellAddress SourceAddress => new(Owner.MapId, Definition.SourceIndex);
 102
 103    public bool Equals(NavigationPublishedTransition other) =>
 104        Owner.Equals(other.Owner)
 105        && Definition.Equals(other.Definition)
 106        && SourcePageIndex == other.SourcePageIndex
 107        && HasDestinationPage == other.HasDestinationPage
 108        && DestinationPageIndex == other.DestinationPageIndex;
 109
 110    public override int GetHashCode() =>
 111        SwiftHashTools.CombineHashCodes(Owner.GetHashCode(), Definition.GetHashCode());
 112}
 113
 114/// <summary>Stores the lightweight destination-side reference to one source-owned transition.</summary>
 115internal readonly struct NavigationIncomingTransitionRef : IEquatable<NavigationIncomingTransitionRef>
 116{
 117    internal NavigationIncomingTransitionRef(NavigationPublishedTransition transition)
 118    {
 119        Owner = transition.Owner;
 120        SourcePageIndex = transition.SourcePageIndex;
 121        SourceIndex = transition.Definition.SourceIndex;
 122        SourceMedium = transition.Definition.SourceMedium;
 123        DestinationMedium = transition.Definition.DestinationMedium;
 124        Type = transition.Definition.Type;
 125    }
 126
 127    internal NavigationTransitionOwnerKey Owner { get; }
 128
 129    internal int SourcePageIndex { get; }
 130
 131    internal VoxelIndex SourceIndex { get; }
 132
 133    internal NavigationTransitionPageAddress SourcePage => new(
 134        Owner.MapId,
 135        SourcePageIndex);
 136
 137    internal NavigationCellAddress SourceAddress => new(Owner.MapId, SourceIndex);
 138
 139    internal TraversalMedium SourceMedium { get; }
 140
 141    internal TraversalMedium DestinationMedium { get; }
 142
 143    internal TraversalTransitionType Type { get; }
 144
 145    public bool Equals(NavigationIncomingTransitionRef other) =>
 146        Owner.Equals(other.Owner)
 147        && SourcePageIndex == other.SourcePageIndex
 148        && SourceIndex.Equals(other.SourceIndex)
 149        && SourceMedium == other.SourceMedium
 150        && DestinationMedium == other.DestinationMedium
 151        && Type == other.Type;
 152
 153    public override int GetHashCode() =>
 154        SwiftHashTools.CombineHashCodes(Owner.GetHashCode(), SourceIndex.GetHashCode());
 155}
 156
 157/// <summary>Stores immutable outgoing records and incoming references for one cell page.</summary>
 158internal sealed class NavigationTransitionPage
 159{
 160    internal const long BaseRetainedBytes = 64L;
 161    internal static readonly long OutgoingRecordBytes =
 162        Unsafe.SizeOf<NavigationPublishedTransition>();
 163    internal static readonly long IncomingRecordBytes =
 164        Unsafe.SizeOf<NavigationIncomingTransitionRef>();
 165    private const long ArrayHeaderBytes = 24L;
 166
 167    private readonly NavigationPublishedTransition[] _outgoing;
 168    private readonly NavigationIncomingTransitionRef[] _incoming;
 169
 170    internal NavigationTransitionPage(
 171        NavigationTransitionPageAddress address,
 172        long version,
 173        NavigationPublishedTransition[] outgoing,
 174        NavigationIncomingTransitionRef[] incoming)
 175    {
 176        Address = address;
 177        Version = version;
 178        _outgoing = outgoing;
 179        _incoming = incoming;
 180    }
 181
 182    internal NavigationTransitionPageAddress Address { get; }
 183
 184    internal long Version { get; }
 185
 186    internal int OutgoingCount => _outgoing.Length;
 187
 188    internal int IncomingCount => _incoming.Length;
 189
 190    internal bool IsEmpty => _outgoing.Length == 0 && _incoming.Length == 0;
 191
 192    internal long RetainedBytes => GetRetainedBytes(_outgoing.Length, _incoming.Length);
 193
 194    internal int PersistentPageCount => 1
 195        + (_outgoing.Length == 0 ? 0 : 1)
 196        + (_incoming.Length == 0 ? 0 : 1);
 197
 198    internal bool TryGetOutgoing(
 199        NavigationTransitionOwnerKey owner,
 200        out NavigationPublishedTransition transition)
 201    {
 202        int index = FindOutgoing(owner);
 203        if (index >= 0)
 204        {
 205            transition = _outgoing[index];
 206            return true;
 207        }
 208        transition = default;
 209        return false;
 210    }
 211
 212    internal NavigationPublishedTransition GetOutgoingAt(int index) => _outgoing[index];
 213
 214    internal NavigationIncomingTransitionRef GetIncomingAt(int index) => _incoming[index];
 215
 216    internal Enumerator GetOutgoingEnumerator(
 217        NavigationWorldGraph graph,
 218        NavigationMediumStateRef state) => new(
 219        graph,
 220        state,
 221        _outgoing,
 222        null,
 223        activeOnly: true);
 224
 225    internal Enumerator GetOutgoingCandidateEnumerator(
 226        NavigationWorldGraph graph,
 227        NavigationMediumStateRef state) => new(
 228        graph,
 229        state,
 230        _outgoing,
 231        null,
 232        activeOnly: false);
 233
 234    internal Enumerator GetIncomingEnumerator(
 235        NavigationWorldGraph graph,
 236        NavigationMediumStateRef state) => new(
 237        graph,
 238        state,
 239        null,
 240        _incoming,
 241        activeOnly: true);
 242
 243    internal Enumerator GetIncomingCandidateEnumerator(
 244        NavigationWorldGraph graph,
 245        NavigationMediumStateRef state) => new(
 246        graph,
 247        state,
 248        null,
 249        _incoming,
 250        activeOnly: false);
 251
 252    private int FindOutgoing(NavigationTransitionOwnerKey owner)
 253    {
 254        for (int i = 0; i < _outgoing.Length; i++)
 255        {
 256            if (_outgoing[i].Owner.Equals(owner))
 257                return i;
 258        }
 259        return -1;
 260    }
 261
 262    internal static int CompareOutgoing(
 263        NavigationPublishedTransition left,
 264        NavigationPublishedTransition right)
 265    {
 266        int comparison = left.SourceAddress.CompareTo(right.SourceAddress);
 267        if (comparison != 0)
 268            return comparison;
 269        comparison = ((int)left.Definition.SourceMedium).CompareTo(
 270            (int)right.Definition.SourceMedium);
 271        if (comparison != 0)
 272            return comparison;
 273        comparison = left.Definition.Destination.CompareTo(right.Definition.Destination);
 274        if (comparison != 0)
 275            return comparison;
 276        comparison = ((int)left.Definition.DestinationMedium).CompareTo(
 277            (int)right.Definition.DestinationMedium);
 278        if (comparison != 0)
 279            return comparison;
 280        comparison = ((int)left.Definition.Type).CompareTo((int)right.Definition.Type);
 281        return comparison != 0
 282            ? comparison
 283            : string.CompareOrdinal(left.Owner.TransitionId, right.Owner.TransitionId);
 284    }
 285
 286    internal static int CompareIncoming(
 287        NavigationIncomingTransitionRef left,
 288        NavigationIncomingTransitionRef right)
 289    {
 290        int comparison = ((int)left.DestinationMedium).CompareTo((int)right.DestinationMedium);
 291        if (comparison != 0)
 292            return comparison;
 293        comparison = left.SourceAddress.CompareTo(right.SourceAddress);
 294        if (comparison != 0)
 295            return comparison;
 296        comparison = ((int)left.SourceMedium).CompareTo((int)right.SourceMedium);
 297        if (comparison != 0)
 298            return comparison;
 299        comparison = ((int)left.Type).CompareTo((int)right.Type);
 300        if (comparison != 0)
 301            return comparison;
 302        return string.CompareOrdinal(left.Owner.TransitionId, right.Owner.TransitionId);
 303    }
 304
 305    internal static long GetRetainedBytes(int outgoingCount, int incomingCount) => checked(
 306        BaseRetainedBytes
 307        + GetArrayBytes(outgoingCount, OutgoingRecordBytes)
 308        + GetArrayBytes(incomingCount, IncomingRecordBytes));
 309
 310    internal static long GetArrayBytes(int count, long elementBytes) => count == 0
 311        ? 0L
 312        : checked(ArrayHeaderBytes + ((long)count * elementBytes));
 313
 314    internal struct Enumerator
 315    {
 316        private readonly NavigationWorldGraph? _graph;
 317        private readonly NavigationMediumStateRef _state;
 318        private readonly NavigationPublishedTransition[]? _outgoing;
 319        private readonly NavigationIncomingTransitionRef[]? _incoming;
 320        private readonly bool _activeOnly;
 321        private int _index;
 322
 323        internal Enumerator(
 324            NavigationWorldGraph graph,
 325            NavigationMediumStateRef state,
 326            NavigationPublishedTransition[]? outgoing,
 327            NavigationIncomingTransitionRef[]? incoming,
 328            bool activeOnly)
 329        {
 330            _graph = graph;
 331            _state = state;
 332            _outgoing = outgoing;
 333            _incoming = incoming;
 334            _activeOnly = activeOnly;
 335            _index = 0;
 336            Current = default;
 337        }
 338
 339        internal NavigationPublishedTransition Current { get; private set; }
 340
 341        internal bool MoveNext()
 342        {
 343            if (_outgoing != null)
 344            {
 345                while (_index < _outgoing.Length)
 346                {
 347                    NavigationPublishedTransition candidate = _outgoing[_index++];
 348                    if (_activeOnly
 349                        ? _graph!.IsTransitionActive(candidate, _state, outgoing: true)
 350                        : _graph!.IsTransitionEndpoint(candidate, _state, outgoing: true))
 351                    {
 352                        Current = candidate;
 353                        return true;
 354                    }
 355                }
 356                return false;
 357            }
 358            while (_incoming != null && _index < _incoming.Length)
 359            {
 360                NavigationIncomingTransitionRef candidate = _incoming[_index++];
 361                NavigationPublishedTransition record =
 362                    _graph!.GetPublishedTransition(candidate);
 363                if (_activeOnly
 364                        ? _graph.IsTransitionActive(record, _state, outgoing: false)
 365                        : _graph.IsTransitionEndpoint(record, _state, outgoing: false))
 366                {
 367                    Current = record;
 368                    return true;
 369                }
 370            }
 371            Current = default;
 372            return false;
 373        }
 374    }
 375}
 376
 377/// <summary>Owns immutable transition pages without adding a second edge registry or cache.</summary>
 378internal sealed class NavigationTransitionPageRoot
 379{
 380    internal static readonly NavigationTransitionPageRoot Empty = new(
 381        PersistentStringMap<PersistentIntMap<NavigationTransitionPage>>.Empty,
 382        innerMapBytes: 0,
 383        pageBytes: 0,
 384        innerMapPages: 0,
 385        pagePages: 0);
 386
 387    private readonly PersistentStringMap<PersistentIntMap<NavigationTransitionPage>> _maps;
 388    private readonly long _innerMapBytes;
 389    private readonly long _pageBytes;
 390    private readonly int _innerMapPages;
 391    private readonly int _pagePages;
 392
 393    private NavigationTransitionPageRoot(
 394        PersistentStringMap<PersistentIntMap<NavigationTransitionPage>> maps,
 395        long innerMapBytes,
 396        long pageBytes,
 397        int innerMapPages,
 398        int pagePages)
 399    {
 400        _maps = maps;
 401        _innerMapBytes = innerMapBytes;
 402        _pageBytes = pageBytes;
 403        _innerMapPages = innerMapPages;
 404        _pagePages = pagePages;
 405    }
 406
 407    internal long RetainedBytes => checked(
 408        56L + _maps.RetainedBytes + _innerMapBytes + _pageBytes);
 409
 410    internal int PersistentPageCount => checked(
 411        1 + _maps.PersistentNodeCount + _innerMapPages + _pagePages);
 412
 413    internal bool TryGet(
 414        NavigationTransitionPageAddress address,
 415        out NavigationTransitionPage page)
 416    {
 417        if (_maps.TryGetValue(
 418                address.MapId,
 419                out PersistentIntMap<NavigationTransitionPage> map)
 420            && map.TryGetValue(address.PageIndex, out page!))
 421        {
 422            return true;
 423        }
 424        page = null!;
 425        return false;
 426    }
 427
 428    internal NavigationTransitionPageRoot Set(
 429        NavigationTransitionPage page,
 430        out int copiedNodes) => Set(page, out copiedNodes, out _);
 431
 432    internal NavigationTransitionPageRoot Set(
 433        NavigationTransitionPage page,
 434        out int copiedNodes,
 435        out long copiedBytes)
 436    {
 437        copiedNodes = 0;
 438        copiedBytes = 0;
 439        bool hadMap = _maps.TryGetValue(
 440            page.Address.MapId,
 441            out PersistentIntMap<NavigationTransitionPage> map);
 442        map ??= PersistentIntMap<NavigationTransitionPage>.Empty;
 443        map.TryGetValue(page.Address.PageIndex, out NavigationTransitionPage prior);
 444        long innerBytes = _innerMapBytes - (hadMap ? map.RetainedBytes : 0L);
 445        int innerPages = _innerMapPages - (hadMap ? map.PersistentNodeCount : 0);
 446        int copied;
 447        if (page.IsEmpty)
 448            map = map.Remove(page.Address.PageIndex, out copied);
 449        else
 450            map = map.Set(page.Address.PageIndex, page, out copied);
 451        copiedNodes = checked(copiedNodes + copied);
 452        copiedBytes = checked(copiedBytes + ((long)copied * 72L));
 453        PersistentStringMap<PersistentIntMap<NavigationTransitionPage>> maps;
 454        if (map.Count == 0)
 455            maps = _maps.Remove(page.Address.MapId, out _, out copied);
 456        else
 457        {
 458            innerBytes = checked(innerBytes + map.RetainedBytes);
 459            innerPages = checked(innerPages + map.PersistentNodeCount);
 460            maps = _maps.Set(page.Address.MapId, map, out copied);
 461        }
 462        copiedNodes = checked(copiedNodes + copied);
 463        copiedBytes = checked(copiedBytes + ((long)copied * 64L));
 464        return new NavigationTransitionPageRoot(
 465            maps,
 466            innerBytes,
 467            checked(_pageBytes - (prior?.RetainedBytes ?? 0L) + (page.IsEmpty ? 0L : page.RetainedBytes)),
 468            innerPages,
 469            checked(_pagePages - (prior?.PersistentPageCount ?? 0) + (page.IsEmpty ? 0 : page.PersistentPageCount)));
 470    }
 471}