| | | 1 | | //======================================================================= |
| | | 2 | | // NavigationIncomingTraversalEdgeEnumerator.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 | | |
| | | 8 | | using FixedMathSharp; |
| | | 9 | | using GridForge.Grids; |
| | | 10 | | |
| | | 11 | | namespace Trailblazer.Pathing; |
| | | 12 | | |
| | | 13 | | /// <summary>Discovers incoming candidates and recovers their exact forward edge.</summary> |
| | | 14 | | internal struct NavigationIncomingTraversalEdgeEnumerator |
| | | 15 | | { |
| | | 16 | | private readonly GridWorld _world; |
| | | 17 | | private readonly NavigationWorldGraph? _graph; |
| | | 18 | | private readonly NavigationMediumStateRef _destination; |
| | | 19 | | private readonly NavigationAgentProfile _profile; |
| | | 20 | | private readonly NavigationAreaPolicy _areaPolicy; |
| | | 21 | | private readonly NavigationRayWorkspace _workspace; |
| | | 22 | | private readonly bool _allowTransitions; |
| | | 23 | | private NavigationIncomingSurfaceEdgeEnumerator _surfaceIncoming; |
| | | 24 | | private NavigationAutomaticSeamIndex.EndpointEnumerator _volumeSeams; |
| | | 25 | | private NavigationAutomaticSeamRef _volumeSeamLookahead; |
| | | 26 | | private NavigationTransitionPage.Enumerator _definitions; |
| | | 27 | | private NavigationPublishedTransition _definitionLookahead; |
| | | 28 | | private NavigationTraversalEdgeEnumerator _outgoing; |
| | | 29 | | private NavigationMediumStateRef _candidateSource; |
| | | 30 | | private NavigationTraversalEdgeKind _candidateTraversalKind; |
| | | 31 | | private NavigationGraphEdge _candidateSurfaceEdge; |
| | | 32 | | private TraversalTransitionType _candidateType; |
| | | 33 | | private NavigationTransitionIdentityKind _candidateKind; |
| | | 34 | | private string _candidateOwnerMapId; |
| | | 35 | | private string _candidateId; |
| | | 36 | | private NavigationCellAddress _lastRuleSourceAddress; |
| | | 37 | | private TraversalMedium _lastRuleSourceMedium; |
| | | 38 | | private TraversalTransitionType _lastRuleType; |
| | | 39 | | private string _lastRuleId; |
| | | 40 | | private TraversalTransitionRule _scannedRule; |
| | | 41 | | private NavigationMediumStateRef _scannedRuleSource; |
| | | 42 | | private NavigationCellAddress _scannedRuleAddress; |
| | | 43 | | private int _ruleScanIndex; |
| | | 44 | | private int _ruleContactDirection; |
| | | 45 | | private bool _started; |
| | | 46 | | private bool _definitionsComplete; |
| | | 47 | | private bool _hasDefinitionLookahead; |
| | | 48 | | private bool _definitionNeedsDebit; |
| | | 49 | | private bool _hasLastRule; |
| | | 50 | | private bool _rescanActive; |
| | | 51 | | private bool _ruleScanActive; |
| | | 52 | | private bool _hasScannedRule; |
| | | 53 | | private bool _hasVolumeSeamLookahead; |
| | | 54 | | private bool _volumeSeamsComplete; |
| | | 55 | | private bool _baseComplete; |
| | | 56 | | private uint _visitedVolumeDirections; |
| | | 57 | | |
| | | 58 | | internal NavigationIncomingTraversalEdgeEnumerator( |
| | | 59 | | GridWorld world, |
| | | 60 | | NavigationWorldGraph graph, |
| | | 61 | | NavigationMediumStateRef destination, |
| | | 62 | | NavigationAgentProfile profile, |
| | | 63 | | NavigationAreaPolicy areaPolicy, |
| | | 64 | | NavigationRayWorkspace workspace, |
| | | 65 | | bool allowTransitions) |
| | | 66 | | { |
| | 3549 | 67 | | SwiftThrowHelper.ThrowIfNull(world, nameof(world)); |
| | 3549 | 68 | | SwiftThrowHelper.ThrowIfNull(graph, nameof(graph)); |
| | 3549 | 69 | | SwiftThrowHelper.ThrowIfNull(areaPolicy, nameof(areaPolicy)); |
| | 3549 | 70 | | SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace)); |
| | 3549 | 71 | | bool foundDestinationAddress = graph.TryGetNodeAddress( |
| | 3549 | 72 | | destination.Node, |
| | 3549 | 73 | | out NavigationCellAddress destinationAddress); |
| | | 74 | | System.Diagnostics.Debug.Assert( |
| | | 75 | | destination.IsValid && foundDestinationAddress, |
| | | 76 | | "Incoming traversal enumerators own a valid destination from their immutable graph."); |
| | 3549 | 77 | | _world = world; |
| | 3549 | 78 | | _graph = graph; |
| | 3549 | 79 | | _destination = destination; |
| | 3549 | 80 | | _profile = profile; |
| | 3549 | 81 | | _areaPolicy = areaPolicy; |
| | 3549 | 82 | | _workspace = workspace; |
| | 3549 | 83 | | _allowTransitions = allowTransitions; |
| | 3549 | 84 | | _surfaceIncoming = destination.Medium == TraversalMedium.Solid |
| | 3549 | 85 | | ? graph.EnumerateIncomingSurfaceEdges(destination.Node) |
| | 3549 | 86 | | : default; |
| | 3549 | 87 | | _volumeSeams = (destination.Medium == TraversalMedium.Gas |
| | 3549 | 88 | | || destination.Medium == TraversalMedium.Liquid) |
| | 3549 | 89 | | ? graph.AutomaticSeams.GetActiveEndpointEnumerator(destinationAddress) |
| | 3549 | 90 | | : default; |
| | 3549 | 91 | | _volumeSeamLookahead = default; |
| | 3549 | 92 | | _definitions = allowTransitions |
| | 3549 | 93 | | ? graph.EnumerateIncomingTransitionCandidates(destination) |
| | 3549 | 94 | | : default; |
| | 3549 | 95 | | _definitionLookahead = default; |
| | 3549 | 96 | | _outgoing = default; |
| | 3549 | 97 | | _candidateSource = default; |
| | 3549 | 98 | | _candidateTraversalKind = default; |
| | 3549 | 99 | | _candidateSurfaceEdge = default; |
| | 3549 | 100 | | _candidateType = default; |
| | 3549 | 101 | | _candidateKind = default; |
| | 3549 | 102 | | _candidateOwnerMapId = string.Empty; |
| | 3549 | 103 | | _candidateId = string.Empty; |
| | 3549 | 104 | | _lastRuleSourceAddress = default; |
| | 3549 | 105 | | _lastRuleSourceMedium = default; |
| | 3549 | 106 | | _lastRuleType = default; |
| | 3549 | 107 | | _lastRuleId = string.Empty; |
| | 3549 | 108 | | _scannedRule = default; |
| | 3549 | 109 | | _scannedRuleSource = default; |
| | 3549 | 110 | | _scannedRuleAddress = default; |
| | 3549 | 111 | | _ruleScanIndex = 0; |
| | 3549 | 112 | | _ruleContactDirection = -1; |
| | 3549 | 113 | | _started = false; |
| | 3549 | 114 | | _definitionsComplete = !allowTransitions; |
| | 3549 | 115 | | _hasDefinitionLookahead = false; |
| | 3549 | 116 | | _definitionNeedsDebit = false; |
| | 3549 | 117 | | _hasLastRule = false; |
| | 3549 | 118 | | _rescanActive = false; |
| | 3549 | 119 | | _ruleScanActive = false; |
| | 3549 | 120 | | _hasScannedRule = false; |
| | 3549 | 121 | | _hasVolumeSeamLookahead = false; |
| | 3549 | 122 | | _volumeSeamsComplete = destination.Medium != TraversalMedium.Gas |
| | 3549 | 123 | | && destination.Medium != TraversalMedium.Liquid; |
| | 3549 | 124 | | _baseComplete = false; |
| | 3549 | 125 | | _visitedVolumeDirections = 0; |
| | 3549 | 126 | | CurrentPredecessor = default; |
| | 3549 | 127 | | CurrentKind = default; |
| | 3549 | 128 | | CurrentOrdinal = -1; |
| | 3549 | 129 | | CurrentCost = default; |
| | 3549 | 130 | | CurrentTransitionId = string.Empty; |
| | 3549 | 131 | | CurrentTransitionOwnerMapId = string.Empty; |
| | 3549 | 132 | | CurrentTransitionType = default; |
| | 3549 | 133 | | CurrentTransitionHints = default; |
| | 3549 | 134 | | CurrentTransitionIdentityKind = default; |
| | 3549 | 135 | | RequiresWorldStamp = destination.Medium == TraversalMedium.Gas |
| | 3549 | 136 | | || destination.Medium == TraversalMedium.Liquid; |
| | 3549 | 137 | | } |
| | | 138 | | |
| | | 139 | | internal NavigationMediumStateRef CurrentPredecessor { get; private set; } |
| | | 140 | | |
| | | 141 | | internal NavigationTraversalEdgeKind CurrentKind { get; private set; } |
| | | 142 | | |
| | | 143 | | internal int CurrentOrdinal { get; private set; } |
| | | 144 | | |
| | | 145 | | internal Fixed64 CurrentCost { get; private set; } |
| | | 146 | | |
| | | 147 | | internal string CurrentTransitionId { get; private set; } |
| | | 148 | | |
| | | 149 | | internal string CurrentTransitionOwnerMapId { get; private set; } |
| | | 150 | | |
| | | 151 | | internal TraversalTransitionType CurrentTransitionType { get; private set; } |
| | | 152 | | |
| | | 153 | | internal TraversalTransitionLocomotionHints CurrentTransitionHints { get; private set; } |
| | | 154 | | |
| | | 155 | | internal NavigationTransitionIdentityKind CurrentTransitionIdentityKind { get; private set; } |
| | | 156 | | |
| | | 157 | | internal bool RequiresWorldStamp { get; private set; } |
| | | 158 | | |
| | | 159 | | internal Vector3d CurrentTransitionSourceAction => |
| | 3 | 160 | | CurrentKind == NavigationTraversalEdgeKind.Transition |
| | 3 | 161 | | ? _outgoing.CurrentTransitionSourceAction |
| | 3 | 162 | | : default; |
| | | 163 | | |
| | | 164 | | internal Vector3d CurrentTransitionDestinationAction => |
| | 3 | 165 | | CurrentKind == NavigationTraversalEdgeKind.Transition |
| | 3 | 166 | | ? _outgoing.CurrentTransitionDestinationAction |
| | 3 | 167 | | : default; |
| | | 168 | | |
| | | 169 | | internal NavigationTraversalEdgeAdvanceStatus AdvanceOne( |
| | | 170 | | NavigationWorkMeter meter, |
| | | 171 | | NavigationDependencyWorkspace dependencies, |
| | | 172 | | ref int edgeStepRemaining, |
| | | 173 | | ref int connectionStepRemaining) |
| | | 174 | | { |
| | 31847 | 175 | | SwiftThrowHelper.ThrowIfNull(meter, nameof(meter)); |
| | 31847 | 176 | | SwiftThrowHelper.ThrowIfNull(dependencies, nameof(dependencies)); |
| | 31847 | 177 | | SwiftThrowHelper.ThrowIfNegative(edgeStepRemaining, nameof(edgeStepRemaining)); |
| | 31847 | 178 | | SwiftThrowHelper.ThrowIfNegative( |
| | 31847 | 179 | | connectionStepRemaining, |
| | 31847 | 180 | | nameof(connectionStepRemaining)); |
| | | 181 | | System.Diagnostics.Debug.Assert(_graph != null, |
| | | 182 | | "Incoming traversal enumerators must be initialized with an immutable graph destination."); |
| | 31847 | 183 | | if (!_started && _allowTransitions) |
| | | 184 | | { |
| | 44 | 185 | | dependencies.RecordTransitionDependency(); |
| | 44 | 186 | | bool found = _graph.TryGetNodeAddress( |
| | 44 | 187 | | _destination.Node, |
| | 44 | 188 | | out NavigationCellAddress address); |
| | | 189 | | System.Diagnostics.Debug.Assert(found, |
| | | 190 | | "Incoming traversal starts from a published destination state."); |
| | 44 | 191 | | if (!dependencies.TryRecordPage( |
| | 44 | 192 | | address.MapId, |
| | 44 | 193 | | _destination.Node.CellSlot / NavigationSemanticPage.SlotCount)) |
| | | 194 | | { |
| | 1 | 195 | | return NavigationTraversalEdgeAdvanceStatus.CapacityExceeded; |
| | | 196 | | } |
| | 43 | 197 | | _started = true; |
| | | 198 | | } |
| | | 199 | | |
| | | 200 | | while (true) |
| | | 201 | | { |
| | 35166 | 202 | | if (!_rescanActive) |
| | | 203 | | { |
| | 19295 | 204 | | if (!_baseComplete) |
| | | 205 | | { |
| | 19210 | 206 | | NavigationTraversalEdgeAdvanceStatus baseStatus = PrepareBaseCandidate( |
| | 19210 | 207 | | meter, |
| | 19210 | 208 | | ref edgeStepRemaining); |
| | 19210 | 209 | | if (baseStatus != NavigationTraversalEdgeAdvanceStatus.Complete) |
| | 15681 | 210 | | return baseStatus; |
| | 3529 | 211 | | _baseComplete = true; |
| | | 212 | | } |
| | 3614 | 213 | | if (!_allowTransitions) |
| | 3486 | 214 | | return Complete(); |
| | 128 | 215 | | NavigationTraversalEdgeAdvanceStatus prepared = PrepareCandidate( |
| | 128 | 216 | | meter, |
| | 128 | 217 | | ref edgeStepRemaining); |
| | 128 | 218 | | if (prepared != NavigationTraversalEdgeAdvanceStatus.Pending |
| | 128 | 219 | | || !_rescanActive) |
| | | 220 | | { |
| | 94 | 221 | | return prepared; |
| | | 222 | | } |
| | 34 | 223 | | return NavigationTraversalEdgeAdvanceStatus.Pending; |
| | | 224 | | } |
| | | 225 | | |
| | 15871 | 226 | | NavigationTraversalEdgeAdvanceStatus status = _outgoing.AdvanceOne( |
| | 15871 | 227 | | meter, |
| | 15871 | 228 | | dependencies, |
| | 15871 | 229 | | ref edgeStepRemaining, |
| | 15871 | 230 | | ref connectionStepRemaining); |
| | 15871 | 231 | | if (_outgoing.RequiresWorldStamp) |
| | 1772 | 232 | | RequiresWorldStamp = true; |
| | 15871 | 233 | | if (status == NavigationTraversalEdgeAdvanceStatus.Complete) |
| | | 234 | | { |
| | 45 | 235 | | _rescanActive = false; |
| | 45 | 236 | | continue; |
| | | 237 | | } |
| | 15826 | 238 | | if (status != NavigationTraversalEdgeAdvanceStatus.Edge) |
| | 6364 | 239 | | return status; |
| | 9462 | 240 | | if (!MatchesCandidate()) |
| | | 241 | | { |
| | | 242 | | continue; |
| | | 243 | | } |
| | | 244 | | |
| | 6187 | 245 | | CurrentPredecessor = _candidateSource; |
| | 6187 | 246 | | CurrentKind = _outgoing.CurrentKind; |
| | 6187 | 247 | | CurrentOrdinal = _outgoing.CurrentOrdinal; |
| | 6187 | 248 | | CurrentCost = _outgoing.CurrentCost; |
| | 6187 | 249 | | CurrentTransitionId = _outgoing.CurrentTransitionId; |
| | 6187 | 250 | | CurrentTransitionOwnerMapId = _outgoing.CurrentTransitionOwnerMapId; |
| | 6187 | 251 | | CurrentTransitionType = _outgoing.CurrentTransitionType; |
| | 6187 | 252 | | CurrentTransitionHints = _outgoing.CurrentTransitionHints; |
| | 6187 | 253 | | CurrentTransitionIdentityKind = _outgoing.CurrentTransitionIdentityKind; |
| | 6187 | 254 | | _rescanActive = false; |
| | 6187 | 255 | | return NavigationTraversalEdgeAdvanceStatus.Edge; |
| | | 256 | | } |
| | | 257 | | } |
| | | 258 | | |
| | | 259 | | private NavigationTraversalEdgeAdvanceStatus PrepareBaseCandidate( |
| | | 260 | | NavigationWorkMeter meter, |
| | | 261 | | ref int edgeStepRemaining) |
| | | 262 | | { |
| | 19210 | 263 | | if (_destination.Medium == TraversalMedium.Solid) |
| | | 264 | | { |
| | 18817 | 265 | | NavigationSurfaceEdgeAdvanceStatus status = _surfaceIncoming.AdvanceOne( |
| | 18817 | 266 | | meter, |
| | 18817 | 267 | | ref edgeStepRemaining); |
| | 18817 | 268 | | if (status == NavigationSurfaceEdgeAdvanceStatus.Complete) |
| | 3458 | 269 | | return NavigationTraversalEdgeAdvanceStatus.Complete; |
| | 15359 | 270 | | if (status == NavigationSurfaceEdgeAdvanceStatus.Blocked) |
| | | 271 | | { |
| | 9 | 272 | | return meter.RemainingEvaluatedEdges == 0 |
| | 9 | 273 | | ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded |
| | 9 | 274 | | : NavigationTraversalEdgeAdvanceStatus.Blocked; |
| | | 275 | | } |
| | 15350 | 276 | | if (status != NavigationSurfaceEdgeAdvanceStatus.Edge) |
| | 9461 | 277 | | return NavigationTraversalEdgeAdvanceStatus.Pending; |
| | 5889 | 278 | | NavigationIncomingSurfaceEdge candidate = _surfaceIncoming.Current; |
| | 5889 | 279 | | _candidateSource = new NavigationMediumStateRef( |
| | 5889 | 280 | | candidate.Predecessor, |
| | 5889 | 281 | | TraversalMedium.Solid); |
| | 5889 | 282 | | _candidateTraversalKind = NavigationTraversalEdgeKind.Surface; |
| | 5889 | 283 | | _candidateSurfaceEdge = candidate.ForwardEdge; |
| | 5889 | 284 | | BeginOutgoingRescan(); |
| | 5889 | 285 | | return NavigationTraversalEdgeAdvanceStatus.Pending; |
| | | 286 | | } |
| | 393 | 287 | | int selectedDirection = -1; |
| | 393 | 288 | | NavigationMediumStateRef selected = default; |
| | 393 | 289 | | NavigationCellAddress selectedAddress = default; |
| | 393 | 290 | | int count = _graph!.GetCompleteDirectionCount(_destination.Node); |
| | 19230 | 291 | | for (int direction = 0; direction < count; direction++) |
| | | 292 | | { |
| | 9222 | 293 | | if ((_visitedVolumeDirections & (1U << direction)) != 0) |
| | | 294 | | continue; |
| | 7690 | 295 | | if (!_graph.TryGetCompleteNeighbor( |
| | 7690 | 296 | | _destination.Node, |
| | 7690 | 297 | | direction, |
| | 7690 | 298 | | out NavigationNodeRef candidateNode, |
| | 7690 | 299 | | out _)) |
| | | 300 | | continue; |
| | 1539 | 301 | | bool found = _graph.TryGetNodeAddress( |
| | 1539 | 302 | | candidateNode, |
| | 1539 | 303 | | out NavigationCellAddress candidateAddress); |
| | | 304 | | System.Diagnostics.Debug.Assert(found, |
| | | 305 | | "Topology neighbors belong to the same published graph."); |
| | 1539 | 306 | | if (selectedDirection >= 0 |
| | 1539 | 307 | | && selectedAddress.CompareTo(candidateAddress) <= 0) |
| | | 308 | | continue; |
| | 675 | 309 | | selectedDirection = direction; |
| | 675 | 310 | | selected = new NavigationMediumStateRef( |
| | 675 | 311 | | candidateNode, |
| | 675 | 312 | | _destination.Medium); |
| | 675 | 313 | | selectedAddress = candidateAddress; |
| | | 314 | | } |
| | 393 | 315 | | bool selectedSeam = false; |
| | 393 | 316 | | if (!_hasVolumeSeamLookahead && !_volumeSeamsComplete) |
| | | 317 | | { |
| | 89 | 318 | | if (_volumeSeams.MoveNext()) |
| | | 319 | | { |
| | 13 | 320 | | _volumeSeamLookahead = _volumeSeams.Current; |
| | 13 | 321 | | _hasVolumeSeamLookahead = true; |
| | | 322 | | } |
| | | 323 | | else |
| | | 324 | | { |
| | 76 | 325 | | _volumeSeamsComplete = true; |
| | | 326 | | } |
| | | 327 | | } |
| | 393 | 328 | | if (_hasVolumeSeamLookahead) |
| | | 329 | | { |
| | 18 | 330 | | bool found = _graph.TryGetNodeRef( |
| | 18 | 331 | | _volumeSeamLookahead.Destination, |
| | 18 | 332 | | out NavigationNodeRef seamSource); |
| | | 333 | | System.Diagnostics.Debug.Assert(found, |
| | | 334 | | "Active seam rows reference published graph nodes."); |
| | 18 | 335 | | if (selectedDirection < 0 |
| | 18 | 336 | | || _volumeSeamLookahead.Destination.CompareTo(selectedAddress) < 0) |
| | | 337 | | { |
| | 17 | 338 | | selectedDirection = 0; |
| | 17 | 339 | | selected = new NavigationMediumStateRef( |
| | 17 | 340 | | seamSource, |
| | 17 | 341 | | _destination.Medium); |
| | 17 | 342 | | selectedAddress = _volumeSeamLookahead.Destination; |
| | 17 | 343 | | selectedSeam = true; |
| | | 344 | | } |
| | | 345 | | } |
| | 393 | 346 | | if (selectedDirection < 0) |
| | 71 | 347 | | return NavigationTraversalEdgeAdvanceStatus.Complete; |
| | 322 | 348 | | if (edgeStepRemaining == 0) |
| | | 349 | | { |
| | 7 | 350 | | return meter.RemainingEvaluatedEdges == 0 |
| | 7 | 351 | | ? NavigationTraversalEdgeAdvanceStatus.BudgetExceeded |
| | 7 | 352 | | : NavigationTraversalEdgeAdvanceStatus.Blocked; |
| | | 353 | | } |
| | 315 | 354 | | if (!meter.TryConsumeEvaluatedEdges(1)) |
| | 1 | 355 | | return NavigationTraversalEdgeAdvanceStatus.BudgetExceeded; |
| | 314 | 356 | | edgeStepRemaining--; |
| | 314 | 357 | | if (selectedSeam) |
| | | 358 | | { |
| | 13 | 359 | | _volumeSeamLookahead = default; |
| | 13 | 360 | | _hasVolumeSeamLookahead = false; |
| | | 361 | | } |
| | | 362 | | else |
| | | 363 | | { |
| | 301 | 364 | | _visitedVolumeDirections |= 1U << selectedDirection; |
| | | 365 | | } |
| | 314 | 366 | | _candidateSource = selected; |
| | 314 | 367 | | _candidateTraversalKind = NavigationTraversalEdgeKind.Volume; |
| | 314 | 368 | | _candidateSurfaceEdge = default; |
| | 314 | 369 | | BeginOutgoingRescan(); |
| | 314 | 370 | | return NavigationTraversalEdgeAdvanceStatus.Pending; |
| | | 371 | | } |
| | | 372 | | |
| | | 373 | | private void BeginOutgoingRescan() |
| | | 374 | | { |
| | 6237 | 375 | | if (_candidateSource.Medium == TraversalMedium.Gas |
| | 6237 | 376 | | || _candidateSource.Medium == TraversalMedium.Liquid) |
| | | 377 | | { |
| | 336 | 378 | | RequiresWorldStamp = true; |
| | | 379 | | } |
| | 6237 | 380 | | _outgoing = new NavigationTraversalEdgeEnumerator( |
| | 6237 | 381 | | _world, |
| | 6237 | 382 | | _graph!, |
| | 6237 | 383 | | _candidateSource, |
| | 6237 | 384 | | _profile, |
| | 6237 | 385 | | _areaPolicy, |
| | 6237 | 386 | | _workspace, |
| | 6237 | 387 | | _allowTransitions, |
| | 6237 | 388 | | emittedSurfaceOrdinal: -1); |
| | 6237 | 389 | | _rescanActive = true; |
| | 6237 | 390 | | } |
| | | 391 | | |
| | | 392 | | private bool MatchesCandidate() |
| | | 393 | | { |
| | 9462 | 394 | | if (_outgoing.CurrentKind != _candidateTraversalKind |
| | 9462 | 395 | | || !_outgoing.CurrentTarget.Equals(_destination)) |
| | | 396 | | { |
| | 3258 | 397 | | return false; |
| | | 398 | | } |
| | 6204 | 399 | | if (_candidateTraversalKind == NavigationTraversalEdgeKind.Volume) |
| | 288 | 400 | | return true; |
| | 5916 | 401 | | if (_candidateTraversalKind == NavigationTraversalEdgeKind.Surface) |
| | 5894 | 402 | | return MatchesSurfaceEdge( |
| | 5894 | 403 | | _candidateSurfaceEdge, |
| | 5894 | 404 | | _outgoing.CurrentSurfaceEdge); |
| | 22 | 405 | | return _outgoing.CurrentTransitionIdentityKind == _candidateKind |
| | 22 | 406 | | && _outgoing.CurrentTransitionType == _candidateType |
| | 22 | 407 | | && string.Equals( |
| | 22 | 408 | | _outgoing.CurrentTransitionOwnerMapId, |
| | 22 | 409 | | _candidateOwnerMapId, |
| | 22 | 410 | | System.StringComparison.Ordinal) |
| | 22 | 411 | | && string.Equals( |
| | 22 | 412 | | _outgoing.CurrentTransitionId, |
| | 22 | 413 | | _candidateId, |
| | 22 | 414 | | System.StringComparison.Ordinal); |
| | | 415 | | } |
| | | 416 | | |
| | | 417 | | private static bool MatchesSurfaceEdge( |
| | | 418 | | NavigationGraphEdge candidate, |
| | | 419 | | NavigationGraphEdge forward) |
| | | 420 | | { |
| | 5894 | 421 | | if (candidate.Kind != forward.Kind || !candidate.Target.Equals(forward.Target)) |
| | 11 | 422 | | return false; |
| | 5883 | 423 | | return candidate.Kind switch |
| | 5883 | 424 | | { |
| | 30 | 425 | | NavigationGraphEdgeKind.Explicit => ReferenceEquals( |
| | 30 | 426 | | candidate.ExplicitConnection, |
| | 30 | 427 | | forward.ExplicitConnection), |
| | 12 | 428 | | NavigationGraphEdgeKind.Seam => ReferenceEquals( |
| | 12 | 429 | | candidate.AutomaticSeam.Pair, |
| | 12 | 430 | | forward.AutomaticSeam.Pair), |
| | 5841 | 431 | | _ => true |
| | 5883 | 432 | | }; |
| | | 433 | | } |
| | | 434 | | |
| | | 435 | | private NavigationTraversalEdgeAdvanceStatus PrepareCandidate( |
| | | 436 | | NavigationWorkMeter meter, |
| | | 437 | | ref int edgeStepRemaining) |
| | | 438 | | { |
| | 128 | 439 | | bool hasDefinition = _hasDefinitionLookahead; |
| | 128 | 440 | | NavigationPublishedTransition definition = _definitionLookahead; |
| | 128 | 441 | | NavigationMediumStateRef definitionSource = default; |
| | 128 | 442 | | if (!hasDefinition && !_definitionsComplete) |
| | | 443 | | { |
| | 54 | 444 | | if (_definitions.MoveNext()) |
| | | 445 | | { |
| | 16 | 446 | | _definitionLookahead = _definitions.Current; |
| | 16 | 447 | | _hasDefinitionLookahead = true; |
| | 16 | 448 | | _definitionNeedsDebit = true; |
| | 16 | 449 | | hasDefinition = true; |
| | 16 | 450 | | definition = _definitionLookahead; |
| | | 451 | | } |
| | | 452 | | else |
| | | 453 | | { |
| | 38 | 454 | | _definitionsComplete = true; |
| | | 455 | | } |
| | | 456 | | } |
| | 128 | 457 | | if (_definitionNeedsDebit) |
| | | 458 | | { |
| | 16 | 459 | | if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate( |
| | 16 | 460 | | meter, |
| | 16 | 461 | | ref edgeStepRemaining, |
| | 16 | 462 | | out NavigationTraversalEdgeAdvanceStatus blocked)) |
| | | 463 | | { |
| | 2 | 464 | | return blocked; |
| | | 465 | | } |
| | 14 | 466 | | _definitionNeedsDebit = false; |
| | | 467 | | } |
| | 126 | 468 | | if (hasDefinition) |
| | | 469 | | { |
| | 22 | 470 | | bool foundDefinitionSource = _graph!.TryGetNodeRef( |
| | 22 | 471 | | definition.SourceAddress, |
| | 22 | 472 | | out NavigationNodeRef definitionSourceNode); |
| | | 473 | | System.Diagnostics.Debug.Assert(foundDefinitionSource, |
| | | 474 | | "Published incoming transition definitions retain their source map node."); |
| | 22 | 475 | | definitionSource = new NavigationMediumStateRef( |
| | 22 | 476 | | definitionSourceNode, |
| | 22 | 477 | | definition.Definition.SourceMedium); |
| | | 478 | | } |
| | | 479 | | |
| | 126 | 480 | | if (!_ruleScanActive) |
| | | 481 | | { |
| | 70 | 482 | | _ruleScanActive = true; |
| | 70 | 483 | | _ruleScanIndex = 0; |
| | 70 | 484 | | _hasScannedRule = false; |
| | 70 | 485 | | _scannedRule = default; |
| | 70 | 486 | | _scannedRuleSource = default; |
| | 70 | 487 | | _scannedRuleAddress = default; |
| | 70 | 488 | | _ruleContactDirection = -1; |
| | | 489 | | } |
| | 367 | 490 | | while (_ruleScanIndex < _graph!.TransitionRules.Count) |
| | | 491 | | { |
| | 299 | 492 | | TraversalTransitionRule rule = _graph.TransitionRules[_ruleScanIndex]; |
| | 299 | 493 | | if (_ruleContactDirection < 0) |
| | | 494 | | { |
| | 60 | 495 | | if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate( |
| | 60 | 496 | | meter, |
| | 60 | 497 | | ref edgeStepRemaining, |
| | 60 | 498 | | out NavigationTraversalEdgeAdvanceStatus blocked)) |
| | | 499 | | { |
| | 6 | 500 | | return blocked; |
| | | 501 | | } |
| | 54 | 502 | | if (rule.DestinationMedium != _destination.Medium) |
| | | 503 | | { |
| | 8 | 504 | | CompleteRuleContactScan(); |
| | 8 | 505 | | continue; |
| | | 506 | | } |
| | 46 | 507 | | _ruleContactDirection = 0; |
| | | 508 | | } |
| | 285 | 509 | | if (rule.Scope == TraversalTransitionRuleScope.SameCell) |
| | | 510 | | { |
| | 24 | 511 | | if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate( |
| | 24 | 512 | | meter, |
| | 24 | 513 | | ref edgeStepRemaining, |
| | 24 | 514 | | out NavigationTraversalEdgeAdvanceStatus blocked)) |
| | | 515 | | { |
| | 6 | 516 | | return blocked; |
| | | 517 | | } |
| | 18 | 518 | | bool foundSource = _graph.TryGetNodeAddress( |
| | 18 | 519 | | _destination.Node, |
| | 18 | 520 | | out NavigationCellAddress sourceAddress); |
| | | 521 | | System.Diagnostics.Debug.Assert(foundSource, |
| | | 522 | | "Same-cell rules reuse the published destination node."); |
| | 18 | 523 | | ConsiderRule( |
| | 18 | 524 | | rule, |
| | 18 | 525 | | new NavigationMediumStateRef( |
| | 18 | 526 | | _destination.Node, |
| | 18 | 527 | | rule.SourceMedium), |
| | 18 | 528 | | sourceAddress, |
| | 18 | 529 | | ref _hasScannedRule, |
| | 18 | 530 | | ref _scannedRule, |
| | 18 | 531 | | ref _scannedRuleSource, |
| | 18 | 532 | | ref _scannedRuleAddress); |
| | 18 | 533 | | CompleteRuleContactScan(); |
| | 18 | 534 | | continue; |
| | | 535 | | } |
| | 261 | 536 | | int primaryCount = _graph.GetPrimaryDirectionCount(_destination.Node); |
| | 261 | 537 | | if (_ruleContactDirection < primaryCount) |
| | | 538 | | { |
| | 194 | 539 | | if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate( |
| | 194 | 540 | | meter, |
| | 194 | 541 | | ref edgeStepRemaining, |
| | 194 | 542 | | out NavigationTraversalEdgeAdvanceStatus blocked)) |
| | | 543 | | { |
| | 29 | 544 | | return blocked; |
| | | 545 | | } |
| | 165 | 546 | | int direction = _ruleContactDirection++; |
| | 165 | 547 | | if (_graph.TryGetPrimaryNeighbor( |
| | 165 | 548 | | _destination.Node, |
| | 165 | 549 | | direction, |
| | 165 | 550 | | out NavigationNodeRef sourceNode)) |
| | | 551 | | { |
| | 18 | 552 | | bool foundSource = _graph.TryGetNodeAddress( |
| | 18 | 553 | | sourceNode, |
| | 18 | 554 | | out NavigationCellAddress sourceAddress); |
| | | 555 | | System.Diagnostics.Debug.Assert(foundSource, |
| | | 556 | | "Topology neighbors belong to the same published graph."); |
| | 18 | 557 | | ConsiderRule( |
| | 18 | 558 | | rule, |
| | 18 | 559 | | new NavigationMediumStateRef( |
| | 18 | 560 | | sourceNode, |
| | 18 | 561 | | rule.SourceMedium), |
| | 18 | 562 | | sourceAddress, |
| | 18 | 563 | | ref _hasScannedRule, |
| | 18 | 564 | | ref _scannedRule, |
| | 18 | 565 | | ref _scannedRuleSource, |
| | 18 | 566 | | ref _scannedRuleAddress); |
| | | 567 | | } |
| | 18 | 568 | | continue; |
| | | 569 | | } |
| | 67 | 570 | | if (_ruleContactDirection == primaryCount) |
| | | 571 | | { |
| | 27 | 572 | | bool foundDestination = _graph.TryGetNodeAddress( |
| | 27 | 573 | | _destination.Node, |
| | 27 | 574 | | out NavigationCellAddress seamDestinationAddress); |
| | | 575 | | System.Diagnostics.Debug.Assert(foundDestination, |
| | | 576 | | "Volume rule scans start from a published destination node."); |
| | 27 | 577 | | _volumeSeams = _graph.AutomaticSeams.GetActiveEndpointEnumerator( |
| | 27 | 578 | | seamDestinationAddress); |
| | 27 | 579 | | _volumeSeamLookahead = default; |
| | 27 | 580 | | _hasVolumeSeamLookahead = false; |
| | 27 | 581 | | _volumeSeamsComplete = false; |
| | 27 | 582 | | _ruleContactDirection++; |
| | | 583 | | } |
| | | 584 | | System.Diagnostics.Debug.Assert(!_volumeSeamsComplete, |
| | | 585 | | "Completed volume-seam contact scans advance to the next transition rule immediately."); |
| | 67 | 586 | | if (edgeStepRemaining == 0) |
| | 16 | 587 | | return NavigationTraversalEdgeAdvanceStatus.Blocked; |
| | 51 | 588 | | if (!_volumeSeams.MoveNext()) |
| | | 589 | | { |
| | 26 | 590 | | _volumeSeamsComplete = true; |
| | 26 | 591 | | CompleteRuleContactScan(); |
| | 26 | 592 | | continue; |
| | | 593 | | } |
| | 25 | 594 | | if (!NavigationTraversalEdgeEnumerator.TryConsumeTransitionCandidate( |
| | 25 | 595 | | meter, |
| | 25 | 596 | | ref edgeStepRemaining, |
| | 25 | 597 | | out NavigationTraversalEdgeAdvanceStatus seamBlocked)) |
| | | 598 | | { |
| | 1 | 599 | | return seamBlocked; |
| | | 600 | | } |
| | 24 | 601 | | NavigationAutomaticSeamRef seam = _volumeSeams.Current; |
| | 24 | 602 | | bool found = _graph.TryGetNodeRef( |
| | 24 | 603 | | seam.Destination, |
| | 24 | 604 | | out NavigationNodeRef seamSource); |
| | | 605 | | System.Diagnostics.Debug.Assert(found, |
| | | 606 | | "Active seam rows reference published graph nodes."); |
| | 24 | 607 | | ConsiderRule( |
| | 24 | 608 | | rule, |
| | 24 | 609 | | new NavigationMediumStateRef( |
| | 24 | 610 | | seamSource, |
| | 24 | 611 | | rule.SourceMedium), |
| | 24 | 612 | | seam.Destination, |
| | 24 | 613 | | ref _hasScannedRule, |
| | 24 | 614 | | ref _scannedRule, |
| | 24 | 615 | | ref _scannedRuleSource, |
| | 24 | 616 | | ref _scannedRuleAddress); |
| | | 617 | | } |
| | | 618 | | |
| | 68 | 619 | | bool hasRule = _hasScannedRule; |
| | 68 | 620 | | TraversalTransitionRule selectedRule = _scannedRule; |
| | 68 | 621 | | NavigationMediumStateRef selectedRuleSource = _scannedRuleSource; |
| | 68 | 622 | | NavigationCellAddress selectedRuleAddress = _scannedRuleAddress; |
| | 68 | 623 | | _ruleScanActive = false; |
| | 68 | 624 | | _ruleScanIndex = 0; |
| | 68 | 625 | | _ruleContactDirection = -1; |
| | 68 | 626 | | _hasScannedRule = false; |
| | 68 | 627 | | _scannedRule = default; |
| | 68 | 628 | | _scannedRuleSource = default; |
| | 68 | 629 | | _scannedRuleAddress = default; |
| | | 630 | | |
| | 68 | 631 | | NavigationCellAddress definitionAddress = hasDefinition |
| | 68 | 632 | | ? definition.SourceAddress |
| | 68 | 633 | | : default; |
| | 68 | 634 | | bool selectRule = hasRule && (!hasDefinition |
| | 68 | 635 | | || NavigationTraversalEdgeEnumerator.CompareTransitionKey( |
| | 68 | 636 | | selectedRuleAddress, |
| | 68 | 637 | | selectedRule.SourceMedium, |
| | 68 | 638 | | selectedRule.Type, |
| | 68 | 639 | | NavigationTransitionIdentityKind.Rule, |
| | 68 | 640 | | selectedRule.Id, |
| | 68 | 641 | | definitionAddress, |
| | 68 | 642 | | definition.Definition.SourceMedium, |
| | 68 | 643 | | definition.Definition.Type, |
| | 68 | 644 | | NavigationTransitionIdentityKind.Definition, |
| | 68 | 645 | | definition.Owner.TransitionId) < 0); |
| | 68 | 646 | | if (!hasDefinition && !hasRule) |
| | 34 | 647 | | return Complete(); |
| | | 648 | | |
| | 34 | 649 | | if (selectRule) |
| | | 650 | | { |
| | 20 | 651 | | _candidateSource = selectedRuleSource; |
| | 20 | 652 | | _candidateTraversalKind = NavigationTraversalEdgeKind.Transition; |
| | 20 | 653 | | _candidateType = selectedRule.Type; |
| | 20 | 654 | | _candidateKind = NavigationTransitionIdentityKind.Rule; |
| | 20 | 655 | | _candidateOwnerMapId = string.Empty; |
| | 20 | 656 | | _candidateId = selectedRule.Id; |
| | 20 | 657 | | _hasLastRule = true; |
| | 20 | 658 | | _lastRuleSourceAddress = selectedRuleAddress; |
| | 20 | 659 | | _lastRuleSourceMedium = selectedRule.SourceMedium; |
| | 20 | 660 | | _lastRuleType = selectedRule.Type; |
| | 20 | 661 | | _lastRuleId = selectedRule.Id; |
| | | 662 | | } |
| | | 663 | | else |
| | | 664 | | { |
| | 14 | 665 | | _candidateSource = definitionSource; |
| | 14 | 666 | | _candidateTraversalKind = NavigationTraversalEdgeKind.Transition; |
| | 14 | 667 | | _candidateType = definition.Definition.Type; |
| | 14 | 668 | | _candidateKind = NavigationTransitionIdentityKind.Definition; |
| | 14 | 669 | | _candidateOwnerMapId = definition.Owner.MapId; |
| | 14 | 670 | | _candidateId = definition.Owner.TransitionId; |
| | 14 | 671 | | _definitionLookahead = default; |
| | 14 | 672 | | _hasDefinitionLookahead = false; |
| | | 673 | | } |
| | 34 | 674 | | BeginOutgoingRescan(); |
| | 34 | 675 | | return NavigationTraversalEdgeAdvanceStatus.Pending; |
| | | 676 | | } |
| | | 677 | | |
| | | 678 | | private void CompleteRuleContactScan() |
| | | 679 | | { |
| | 52 | 680 | | _ruleScanIndex++; |
| | 52 | 681 | | _ruleContactDirection = -1; |
| | 52 | 682 | | _volumeSeamLookahead = default; |
| | 52 | 683 | | _hasVolumeSeamLookahead = false; |
| | 52 | 684 | | _volumeSeamsComplete = true; |
| | 52 | 685 | | } |
| | | 686 | | |
| | | 687 | | private void ConsiderRule( |
| | | 688 | | TraversalTransitionRule rule, |
| | | 689 | | NavigationMediumStateRef source, |
| | | 690 | | NavigationCellAddress sourceAddress, |
| | | 691 | | ref bool hasRule, |
| | | 692 | | ref TraversalTransitionRule selectedRule, |
| | | 693 | | ref NavigationMediumStateRef selectedSource, |
| | | 694 | | ref NavigationCellAddress selectedAddress) |
| | | 695 | | { |
| | 60 | 696 | | if ((_hasLastRule |
| | 60 | 697 | | && NavigationTraversalEdgeEnumerator.CompareTransitionKey( |
| | 60 | 698 | | _lastRuleSourceAddress, |
| | 60 | 699 | | _lastRuleSourceMedium, |
| | 60 | 700 | | _lastRuleType, |
| | 60 | 701 | | NavigationTransitionIdentityKind.Rule, |
| | 60 | 702 | | _lastRuleId, |
| | 60 | 703 | | sourceAddress, |
| | 60 | 704 | | rule.SourceMedium, |
| | 60 | 705 | | rule.Type, |
| | 60 | 706 | | NavigationTransitionIdentityKind.Rule, |
| | 60 | 707 | | rule.Id) >= 0) |
| | 60 | 708 | | || (hasRule |
| | 60 | 709 | | && NavigationTraversalEdgeEnumerator.CompareTransitionKey( |
| | 60 | 710 | | selectedAddress, |
| | 60 | 711 | | selectedRule.SourceMedium, |
| | 60 | 712 | | selectedRule.Type, |
| | 60 | 713 | | NavigationTransitionIdentityKind.Rule, |
| | 60 | 714 | | selectedRule.Id, |
| | 60 | 715 | | sourceAddress, |
| | 60 | 716 | | rule.SourceMedium, |
| | 60 | 717 | | rule.Type, |
| | 60 | 718 | | NavigationTransitionIdentityKind.Rule, |
| | 60 | 719 | | rule.Id) <= 0)) |
| | | 720 | | { |
| | 38 | 721 | | return; |
| | | 722 | | } |
| | 22 | 723 | | hasRule = true; |
| | 22 | 724 | | selectedRule = rule; |
| | 22 | 725 | | selectedSource = source; |
| | 22 | 726 | | selectedAddress = sourceAddress; |
| | 22 | 727 | | } |
| | | 728 | | |
| | | 729 | | private NavigationTraversalEdgeAdvanceStatus Complete() |
| | | 730 | | { |
| | 3520 | 731 | | CurrentPredecessor = default; |
| | 3520 | 732 | | CurrentKind = default; |
| | 3520 | 733 | | CurrentOrdinal = -1; |
| | 3520 | 734 | | CurrentCost = default; |
| | 3520 | 735 | | CurrentTransitionId = string.Empty; |
| | 3520 | 736 | | CurrentTransitionOwnerMapId = string.Empty; |
| | 3520 | 737 | | CurrentTransitionType = default; |
| | 3520 | 738 | | CurrentTransitionHints = default; |
| | 3520 | 739 | | CurrentTransitionIdentityKind = default; |
| | 3520 | 740 | | return NavigationTraversalEdgeAdvanceStatus.Complete; |
| | | 741 | | } |
| | | 742 | | } |