| | | 1 | | //======================================================================= |
| | | 2 | | // NavigationFlowFieldWork.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 System; |
| | | 9 | | using FixedMathSharp; |
| | | 10 | | using GridForge.Grids; |
| | | 11 | | |
| | | 12 | | namespace Trailblazer.Pathing; |
| | | 13 | | |
| | | 14 | | /// <summary>Builds one bounded weighted destination-centric flow-field prefix.</summary> |
| | | 15 | | internal sealed class NavigationFlowFieldWork : IDisposable |
| | | 16 | | { |
| | | 17 | | private enum Stage : byte |
| | | 18 | | { |
| | | 19 | | Search = 0, |
| | | 20 | | CountTransitions = 1, |
| | | 21 | | ReplayTransitions = 2, |
| | | 22 | | SortDependencies = 3, |
| | | 23 | | CaptureDependencies = 4, |
| | | 24 | | BuildPayload = 5, |
| | | 25 | | SortPayloadNodes = 6, |
| | | 26 | | SortPayloadLookup = 7 |
| | | 27 | | } |
| | | 28 | | |
| | | 29 | | private NavigationResolvedPathQuery? _query; |
| | | 30 | | private GridWorld? _world; |
| | | 31 | | private NavigationWorldGraph? _graph; |
| | | 32 | | private readonly NavigationFlowFieldWorkspace _workspace; |
| | | 33 | | private readonly NavigationWorkMeter _meter; |
| | | 34 | | private readonly long _maximumPayloadBytes; |
| | | 35 | | private NavigationFlowFieldOpenHeap _heap; |
| | | 36 | | private NavigationIncomingTraversalEdgeEnumerator _incoming; |
| | | 37 | | private NavigationTraversalEdgeEnumerator _replayEdges; |
| | | 38 | | private NavigationDependencySortWork _dependencySort; |
| | | 39 | | private NavigationFlowPayloadSortWork _payloadSort; |
| | | 40 | | private NavigationDependencyStampWork? _dependencyStamp; |
| | | 41 | | private NavigationFlowFieldNode[]? _payloadNodes; |
| | | 42 | | private NavigationTransitionInstruction[]? _payloadTransitionInstructions; |
| | | 43 | | private int[]? _payloadLookup; |
| | | 44 | | private NavigationFlowFieldStatus _resultStatus; |
| | | 45 | | private Stage _stage; |
| | | 46 | | private int _currentSlot; |
| | | 47 | | private int _postNodeOrdinal; |
| | | 48 | | private int _transitionOrdinal; |
| | | 49 | | private readonly ulong _worldChangeSequence; |
| | | 50 | | private bool _hasCurrent; |
| | | 51 | | private bool _replayActive; |
| | | 52 | | private bool _originSettled; |
| | | 53 | | private bool _isComplete; |
| | | 54 | | private bool _requiresWorldStamp; |
| | | 55 | | private Fixed64 _coverageThreshold; |
| | | 56 | | |
| | 736 | 57 | | internal NavigationFlowFieldWork( |
| | 736 | 58 | | GridWorld world, |
| | 736 | 59 | | NavigationResolvedPathQuery query, |
| | 736 | 60 | | NavigationFlowFieldWorkspace workspace, |
| | 736 | 61 | | long maximumPayloadBytes = long.MaxValue) |
| | | 62 | | { |
| | 736 | 63 | | SwiftThrowHelper.ThrowIfNull(world, nameof(world)); |
| | 736 | 64 | | SwiftThrowHelper.ThrowIfNull(query, nameof(query)); |
| | 736 | 65 | | SwiftThrowHelper.ThrowIfNull(workspace, nameof(workspace)); |
| | | 66 | | |
| | 736 | 67 | | _query = query; |
| | 736 | 68 | | _world = world; |
| | 736 | 69 | | _graph = query.Graph; |
| | 736 | 70 | | _workspace = workspace; |
| | 736 | 71 | | _workspace.ResetSearch(); |
| | 736 | 72 | | _meter = query.Meter; |
| | 736 | 73 | | _maximumPayloadBytes = maximumPayloadBytes; |
| | 736 | 74 | | _worldChangeSequence = query.WorldChangeSequence; |
| | 736 | 75 | | _requiresWorldStamp = query.RequiresWorldStamp |
| | 736 | 76 | | || query.StartMedium == TraversalMedium.Gas |
| | 736 | 77 | | || query.StartMedium == TraversalMedium.Liquid; |
| | 736 | 78 | | _heap = new NavigationFlowFieldOpenHeap(workspace); |
| | 736 | 79 | | _resultStatus = NavigationFlowFieldStatus.Success; |
| | | 80 | | |
| | 736 | 81 | | if (!TryRecordPage(query.Start.Node) |
| | 736 | 82 | | || !TryRecordPage(query.End.Node)) |
| | | 83 | | { |
| | 1 | 84 | | Finish(NavigationFlowFieldStatus.CapacityExceeded); |
| | 1 | 85 | | return; |
| | | 86 | | } |
| | 735 | 87 | | if (!query.Query.AllowTransitions |
| | 735 | 88 | | && !_graph.AreInSameSurfaceComponent( |
| | 735 | 89 | | query.Start.Address, |
| | 735 | 90 | | query.StartMedium, |
| | 735 | 91 | | query.End.Address, |
| | 735 | 92 | | query.StartMedium)) |
| | | 93 | | { |
| | 1 | 94 | | Finish(NavigationFlowFieldStatus.NoPath); |
| | 1 | 95 | | return; |
| | | 96 | | } |
| | | 97 | | |
| | 734 | 98 | | if (!TrySeedTargetMedium(TraversalMedium.Solid) |
| | 734 | 99 | | || !TrySeedTargetMedium(TraversalMedium.Gas) |
| | 734 | 100 | | || !TrySeedTargetMedium(TraversalMedium.Liquid)) |
| | | 101 | | { |
| | 5 | 102 | | Finish(NavigationFlowFieldStatus.CapacityExceeded); |
| | 5 | 103 | | return; |
| | | 104 | | } |
| | 729 | 105 | | } |
| | | 106 | | |
| | | 107 | | internal NavigationFlowFieldStatus Status { get; private set; } |
| | | 108 | | |
| | | 109 | | internal NavigationFlowFieldPayload? Result { get; private set; } |
| | | 110 | | |
| | | 111 | | internal NavigationFlowFieldStatus Advance( |
| | | 112 | | int lookupStepLimit, |
| | | 113 | | int nodeStepLimit, |
| | | 114 | | int edgeStepLimit, |
| | | 115 | | int connectionStepLimit) |
| | | 116 | | { |
| | 1227 | 117 | | SwiftThrowHelper.ThrowIfNegative(lookupStepLimit, nameof(lookupStepLimit)); |
| | 1227 | 118 | | SwiftThrowHelper.ThrowIfNegative(nodeStepLimit, nameof(nodeStepLimit)); |
| | 1227 | 119 | | SwiftThrowHelper.ThrowIfNegative(edgeStepLimit, nameof(edgeStepLimit)); |
| | 1227 | 120 | | SwiftThrowHelper.ThrowIfNegative(connectionStepLimit, nameof(connectionStepLimit)); |
| | 1227 | 121 | | if (Status != NavigationFlowFieldStatus.Pending) |
| | 1 | 122 | | return Status; |
| | 1226 | 123 | | int lookupRemaining = lookupStepLimit; |
| | 1226 | 124 | | int nodeRemaining = nodeStepLimit; |
| | 1226 | 125 | | int edgeRemaining = edgeStepLimit; |
| | 1226 | 126 | | int connectionRemaining = connectionStepLimit; |
| | | 127 | | |
| | | 128 | | while (true) |
| | | 129 | | { |
| | 37625 | 130 | | if (!IsWorldCurrent()) |
| | 2 | 131 | | return Finish(NavigationFlowFieldStatus.Stale); |
| | 37623 | 132 | | if (_stage == Stage.Search) |
| | | 133 | | { |
| | 32599 | 134 | | if (!_hasCurrent) |
| | | 135 | | { |
| | 4626 | 136 | | if (!TryBeginCurrent(ref nodeRemaining)) |
| | 389 | 137 | | return Status; |
| | 4237 | 138 | | if (_stage != Stage.Search) |
| | | 139 | | continue; |
| | | 140 | | } |
| | 31500 | 141 | | NavigationTraversalEdgeAdvanceStatus edgeStatus = |
| | 31500 | 142 | | _incoming.AdvanceOne( |
| | 31500 | 143 | | _meter, |
| | 31500 | 144 | | _workspace.EndpointWorkspace.Dependencies, |
| | 31500 | 145 | | ref edgeRemaining, |
| | 31500 | 146 | | ref connectionRemaining); |
| | 31500 | 147 | | CaptureWorldDependency(_incoming.RequiresWorldStamp); |
| | 31500 | 148 | | edgeStatus = ResolvePostEnumerationStatus( |
| | 31500 | 149 | | IsWorldCurrent(), |
| | 31500 | 150 | | edgeStatus); |
| | 31500 | 151 | | if (edgeStatus == NavigationTraversalEdgeAdvanceStatus.Pending) |
| | | 152 | | continue; |
| | 9724 | 153 | | if (edgeStatus == NavigationTraversalEdgeAdvanceStatus.Complete) |
| | | 154 | | { |
| | 3516 | 155 | | _hasCurrent = false; |
| | 3516 | 156 | | continue; |
| | | 157 | | } |
| | 6208 | 158 | | if (edgeStatus == NavigationTraversalEdgeAdvanceStatus.Blocked) |
| | 31 | 159 | | return Status; |
| | 6177 | 160 | | if (edgeStatus != NavigationTraversalEdgeAdvanceStatus.Edge) |
| | 3 | 161 | | return Finish(NavigationGuideStatusMapper.ToFlowField(edgeStatus)); |
| | 6174 | 162 | | NavigationFlowFieldStatus applied = ApplyIncoming(); |
| | 6174 | 163 | | if (applied != NavigationFlowFieldStatus.Pending) |
| | 3 | 164 | | return applied; |
| | | 165 | | continue; |
| | | 166 | | } |
| | | 167 | | |
| | 5024 | 168 | | if (_stage == Stage.CountTransitions) |
| | | 169 | | { |
| | 4043 | 170 | | while (_postNodeOrdinal < _workspace.SettledCount |
| | 4043 | 171 | | && nodeRemaining > 0) |
| | | 172 | | { |
| | 3318 | 173 | | int slot = _workspace.SettledSlots[_postNodeOrdinal++]; |
| | 3318 | 174 | | if (_workspace.GetRecord(slot).SelectedIsTransition) |
| | 12 | 175 | | _transitionOrdinal++; |
| | 3318 | 176 | | nodeRemaining--; |
| | | 177 | | } |
| | 725 | 178 | | if (_postNodeOrdinal < _workspace.SettledCount) |
| | 15 | 179 | | return Status; |
| | 710 | 180 | | _payloadTransitionInstructions = _transitionOrdinal == 0 |
| | 710 | 181 | | ? Array.Empty<NavigationTransitionInstruction>() |
| | 710 | 182 | | : new NavigationTransitionInstruction[_transitionOrdinal]; |
| | 710 | 183 | | _postNodeOrdinal = 0; |
| | 710 | 184 | | _transitionOrdinal = 0; |
| | 710 | 185 | | _stage = Stage.ReplayTransitions; |
| | 710 | 186 | | continue; |
| | | 187 | | } |
| | | 188 | | |
| | 4299 | 189 | | if (_stage == Stage.ReplayTransitions) |
| | | 190 | | { |
| | 722 | 191 | | NavigationFlowFieldStatus replay = AdvanceTransitionReplay( |
| | 722 | 192 | | ref nodeRemaining, |
| | 722 | 193 | | ref edgeRemaining, |
| | 722 | 194 | | ref connectionRemaining); |
| | 722 | 195 | | if (replay != NavigationFlowFieldStatus.Pending) |
| | 4 | 196 | | return replay; |
| | 718 | 197 | | if (_stage == Stage.ReplayTransitions) |
| | 13 | 198 | | return Status; |
| | | 199 | | continue; |
| | | 200 | | } |
| | | 201 | | |
| | 3577 | 202 | | if (_stage == Stage.SortDependencies) |
| | | 203 | | { |
| | 716 | 204 | | int lookupBefore = _meter.LookupProbes; |
| | 716 | 205 | | bool complete = _dependencySort.Advance(_meter, lookupRemaining); |
| | 716 | 206 | | lookupRemaining -= _meter.LookupProbes - lookupBefore; |
| | 716 | 207 | | if (!complete) |
| | | 208 | | { |
| | 12 | 209 | | return _meter.RemainingLookupProbes == 0 |
| | 12 | 210 | | ? Finish(NavigationFlowFieldStatus.BudgetExceeded) |
| | 12 | 211 | | : Status; |
| | | 212 | | } |
| | 704 | 213 | | _stage = Stage.CaptureDependencies; |
| | 704 | 214 | | continue; |
| | | 215 | | } |
| | | 216 | | |
| | 2861 | 217 | | if (_stage == Stage.CaptureDependencies) |
| | | 218 | | { |
| | 707 | 219 | | _dependencyStamp ??= new NavigationDependencyStampWork( |
| | 707 | 220 | | _graph!, |
| | 707 | 221 | | _query!.AreaPolicy, |
| | 707 | 222 | | _workspace.DependencyComponents, |
| | 707 | 223 | | _workspace.DependencyComponentCount, |
| | 707 | 224 | | _workspace.DependencyPages, |
| | 707 | 225 | | _workspace.DependencyPageCount, |
| | 707 | 226 | | _workspace.EndpointWorkspace.Dependencies |
| | 707 | 227 | | .HasTransitionDependency); |
| | 707 | 228 | | int lookupBefore = _meter.LookupProbes; |
| | 707 | 229 | | bool complete = _dependencyStamp.Advance(_meter, lookupRemaining); |
| | 707 | 230 | | lookupRemaining -= _meter.LookupProbes - lookupBefore; |
| | 707 | 231 | | if (!complete) |
| | | 232 | | { |
| | 4 | 233 | | return _meter.RemainingLookupProbes == 0 |
| | 4 | 234 | | ? Finish(NavigationFlowFieldStatus.BudgetExceeded) |
| | 4 | 235 | | : Status; |
| | | 236 | | } |
| | 703 | 237 | | _payloadNodes = new NavigationFlowFieldNode[_workspace.SettledCount]; |
| | 703 | 238 | | _payloadLookup = new int[_workspace.SettledCount]; |
| | 703 | 239 | | _stage = Stage.BuildPayload; |
| | 703 | 240 | | continue; |
| | | 241 | | } |
| | | 242 | | |
| | 2154 | 243 | | if (_stage == Stage.BuildPayload) |
| | | 244 | | { |
| | 3882 | 245 | | while (_postNodeOrdinal < _workspace.SettledCount && nodeRemaining > 0) |
| | | 246 | | { |
| | 3174 | 247 | | int slot = _workspace.SettledSlots[_postNodeOrdinal]; |
| | 3174 | 248 | | ref NavigationFlowFieldSearchNode record = |
| | 3174 | 249 | | ref _workspace.GetRecord(slot); |
| | 3174 | 250 | | _payloadNodes![_postNodeOrdinal] = new NavigationFlowFieldNode( |
| | 3174 | 251 | | record.Address, |
| | 3174 | 252 | | _workspace.GetNode(slot).Medium, |
| | 3174 | 253 | | record.IntegrationCost, |
| | 3174 | 254 | | record.SelectedEdge, |
| | 3174 | 255 | | record.SelectedIsTransition |
| | 3174 | 256 | | ? _transitionOrdinal++ |
| | 3174 | 257 | | : -1); |
| | 3174 | 258 | | _payloadLookup![_postNodeOrdinal] = _postNodeOrdinal; |
| | 3174 | 259 | | _postNodeOrdinal++; |
| | 3174 | 260 | | nodeRemaining--; |
| | | 261 | | } |
| | 708 | 262 | | if (_postNodeOrdinal < _workspace.SettledCount) |
| | 5 | 263 | | return Status; |
| | 703 | 264 | | _payloadSort = new NavigationFlowPayloadSortWork( |
| | 703 | 265 | | _payloadNodes!, |
| | 703 | 266 | | _payloadLookup!, |
| | 703 | 267 | | sortNodes: true); |
| | 703 | 268 | | _stage = Stage.SortPayloadNodes; |
| | 703 | 269 | | continue; |
| | | 270 | | } |
| | | 271 | | |
| | 1446 | 272 | | if (_stage == Stage.SortPayloadNodes) |
| | | 273 | | { |
| | 724 | 274 | | int lookupBefore = _meter.LookupProbes; |
| | 724 | 275 | | bool complete = _payloadSort.Advance(_meter, lookupRemaining); |
| | 724 | 276 | | lookupRemaining -= _meter.LookupProbes - lookupBefore; |
| | 724 | 277 | | if (!complete) |
| | | 278 | | { |
| | 23 | 279 | | return _meter.RemainingLookupProbes == 0 |
| | 23 | 280 | | ? Finish(NavigationFlowFieldStatus.BudgetExceeded) |
| | 23 | 281 | | : Status; |
| | | 282 | | } |
| | 701 | 283 | | _payloadSort = new NavigationFlowPayloadSortWork( |
| | 701 | 284 | | _payloadNodes!, |
| | 701 | 285 | | _payloadLookup!, |
| | 701 | 286 | | sortNodes: false); |
| | 701 | 287 | | _stage = Stage.SortPayloadLookup; |
| | 701 | 288 | | continue; |
| | | 289 | | } |
| | | 290 | | |
| | 722 | 291 | | int finalLookupBefore = _meter.LookupProbes; |
| | 722 | 292 | | bool lookupComplete = _payloadSort.Advance(_meter, lookupRemaining); |
| | 722 | 293 | | lookupRemaining -= _meter.LookupProbes - finalLookupBefore; |
| | 722 | 294 | | if (!lookupComplete) |
| | | 295 | | { |
| | 22 | 296 | | return _meter.RemainingLookupProbes == 0 |
| | 22 | 297 | | ? Finish(NavigationFlowFieldStatus.BudgetExceeded) |
| | 22 | 298 | | : Status; |
| | | 299 | | } |
| | 700 | 300 | | NavigationResolvedPathQuery resolved = _query!; |
| | 700 | 301 | | Result = new NavigationFlowFieldPayload( |
| | 700 | 302 | | new NavigationFlowFieldPayloadKey( |
| | 700 | 303 | | resolved.Query, |
| | 700 | 304 | | resolved.End.Address, |
| | 700 | 305 | | resolved.StartMedium, |
| | 700 | 306 | | resolved.TargetMedia), |
| | 700 | 307 | | _payloadNodes!, |
| | 700 | 308 | | _payloadLookup!, |
| | 700 | 309 | | _payloadTransitionInstructions!, |
| | 700 | 310 | | _dependencyStamp!.Result, |
| | 700 | 311 | | _isComplete, |
| | 700 | 312 | | _requiresWorldStamp ? _worldChangeSequence : null); |
| | 700 | 313 | | NavigationFlowFieldPayload? result = Result; |
| | 700 | 314 | | NavigationFlowFieldStatus resultStatus = ResolveFinalPublication( |
| | 700 | 315 | | IsWorldCurrent(), |
| | 700 | 316 | | _resultStatus, |
| | 700 | 317 | | ref result); |
| | 700 | 318 | | Result = result; |
| | 700 | 319 | | return Finish(resultStatus); |
| | | 320 | | } |
| | | 321 | | } |
| | | 322 | | |
| | | 323 | | public void Dispose() |
| | | 324 | | { |
| | 737 | 325 | | NavigationResolvedPathQuery? query = _query; |
| | 737 | 326 | | _query = null; |
| | 737 | 327 | | query?.Dispose(); |
| | 737 | 328 | | ReleaseRuntimeState(); |
| | 737 | 329 | | } |
| | | 330 | | |
| | | 331 | | private void ReleaseRuntimeState() |
| | | 332 | | { |
| | 1463 | 333 | | _graph = null; |
| | 1463 | 334 | | _world = null; |
| | 1463 | 335 | | _incoming = default; |
| | 1463 | 336 | | _replayEdges = default; |
| | 1463 | 337 | | _dependencySort = default; |
| | 1463 | 338 | | _dependencyStamp = null; |
| | 1463 | 339 | | _payloadNodes = null; |
| | 1463 | 340 | | _payloadTransitionInstructions = null; |
| | 1463 | 341 | | _payloadLookup = null; |
| | 1463 | 342 | | _payloadSort = default; |
| | 1463 | 343 | | } |
| | | 344 | | |
| | | 345 | | private bool TryBeginCurrent(ref int nodeRemaining) |
| | | 346 | | { |
| | 4626 | 347 | | if (!_heap.TryPeek(out int nextSlot)) |
| | | 348 | | { |
| | 372 | 349 | | _isComplete = true; |
| | 372 | 350 | | _resultStatus = _originSettled |
| | 372 | 351 | | ? NavigationFlowFieldStatus.Success |
| | 372 | 352 | | : NavigationFlowFieldStatus.NoPath; |
| | 372 | 353 | | return TryBeginTransitionCount(); |
| | | 354 | | } |
| | 4254 | 355 | | if (_originSettled |
| | 4254 | 356 | | && _workspace.GetRecord(nextSlot).IntegrationCost > _coverageThreshold) |
| | | 357 | | { |
| | 338 | 358 | | _isComplete = false; |
| | 338 | 359 | | _resultStatus = NavigationFlowFieldStatus.Success; |
| | 338 | 360 | | return TryBeginTransitionCount(); |
| | | 361 | | } |
| | 3916 | 362 | | if (nodeRemaining == 0) |
| | | 363 | | { |
| | 387 | 364 | | if (_meter.RemainingExpandedNodes == 0) |
| | 1 | 365 | | Finish(NavigationFlowFieldStatus.BudgetExceeded); |
| | 387 | 366 | | return false; |
| | | 367 | | } |
| | 3529 | 368 | | if (!_meter.TryConsumeExpandedNodes(1)) |
| | | 369 | | { |
| | 1 | 370 | | Finish(NavigationFlowFieldStatus.BudgetExceeded); |
| | 1 | 371 | | return false; |
| | | 372 | | } |
| | 3528 | 373 | | nodeRemaining--; |
| | 3528 | 374 | | _currentSlot = _heap.Pop(); |
| | 3528 | 375 | | ref NavigationFlowFieldSearchNode current = |
| | 3528 | 376 | | ref _workspace.GetRecord(_currentSlot); |
| | 3528 | 377 | | current.Closed = true; |
| | 3528 | 378 | | _workspace.SettledSlots[_workspace.SettledCount++] = _currentSlot; |
| | 3528 | 379 | | NavigationMediumStateRef currentState = _workspace.GetNode(_currentSlot); |
| | 3528 | 380 | | if (currentState.Equals(new NavigationMediumStateRef( |
| | 3528 | 381 | | _query!.Start.Node, |
| | 3528 | 382 | | _query.StartMedium))) |
| | | 383 | | { |
| | 707 | 384 | | if (!Fixed64.TryAdd( |
| | 707 | 385 | | current.IntegrationCost, |
| | 707 | 386 | | _query.Query.FlowField.ExtraIntegrationCost, |
| | 707 | 387 | | out _coverageThreshold)) |
| | | 388 | | { |
| | 1 | 389 | | Finish(NavigationFlowFieldStatus.CostOverflow); |
| | 1 | 390 | | return false; |
| | | 391 | | } |
| | 706 | 392 | | _originSettled = true; |
| | | 393 | | } |
| | 3527 | 394 | | _incoming = new NavigationIncomingTraversalEdgeEnumerator( |
| | 3527 | 395 | | _world!, |
| | 3527 | 396 | | _graph!, |
| | 3527 | 397 | | currentState, |
| | 3527 | 398 | | _query.Query.Agent, |
| | 3527 | 399 | | _query.AreaPolicy, |
| | 3527 | 400 | | _workspace.RayWorkspace, |
| | 3527 | 401 | | _query.Query.AllowTransitions); |
| | 3527 | 402 | | _hasCurrent = true; |
| | 3527 | 403 | | return true; |
| | | 404 | | } |
| | | 405 | | |
| | | 406 | | private NavigationFlowFieldStatus ApplyIncoming() |
| | | 407 | | { |
| | 6174 | 408 | | NavigationMediumStateRef predecessorState = _incoming.CurrentPredecessor; |
| | 6174 | 409 | | if (!TryRecordStateDependencies(predecessorState)) |
| | 1 | 410 | | return Finish(NavigationFlowFieldStatus.CapacityExceeded); |
| | 6173 | 411 | | ref NavigationFlowFieldSearchNode current = |
| | 6173 | 412 | | ref _workspace.GetRecord(_currentSlot); |
| | 6173 | 413 | | if (!Fixed64.TryAdd( |
| | 6173 | 414 | | current.IntegrationCost, |
| | 6173 | 415 | | _incoming.CurrentCost, |
| | 6173 | 416 | | out Fixed64 candidate)) |
| | | 417 | | { |
| | 1 | 418 | | return Finish(NavigationFlowFieldStatus.CostOverflow); |
| | | 419 | | } |
| | 6172 | 420 | | if (!_workspace.TryGetOrAdd( |
| | 6172 | 421 | | predecessorState, |
| | 6172 | 422 | | out int predecessorSlot, |
| | 6172 | 423 | | out bool added)) |
| | | 424 | | { |
| | 1 | 425 | | return Finish(NavigationFlowFieldStatus.CapacityExceeded); |
| | | 426 | | } |
| | 6171 | 427 | | ref NavigationFlowFieldSearchNode predecessor = |
| | 6171 | 428 | | ref _workspace.GetRecord(predecessorSlot); |
| | 6171 | 429 | | if (added) |
| | | 430 | | { |
| | 3181 | 431 | | _graph!.TryGetNodeAddress(predecessorState.Node, out predecessor.Address); |
| | 3181 | 432 | | predecessor.IntegrationCost = candidate; |
| | 3181 | 433 | | SetSelectedEdge(ref predecessor, current.Address, currentState: _workspace.GetNode(_currentSlot)); |
| | 3181 | 434 | | predecessor.HeapIndex = -1; |
| | 3181 | 435 | | _heap.Push(predecessorSlot); |
| | 3181 | 436 | | return Status; |
| | | 437 | | } |
| | 2990 | 438 | | if (predecessor.Closed) |
| | 2849 | 439 | | return Status; |
| | 141 | 440 | | if (candidate > predecessor.IntegrationCost) |
| | 106 | 441 | | return Status; |
| | 35 | 442 | | if (candidate == predecessor.IntegrationCost) |
| | | 443 | | { |
| | 25 | 444 | | if (predecessor.SelectedEdge.IsValid |
| | 25 | 445 | | && CompareSelectedEdge( |
| | 25 | 446 | | CreateSelectedEdge(current.Address, _workspace.GetNode(_currentSlot)), |
| | 25 | 447 | | predecessor.SelectedEdge) < 0) |
| | | 448 | | { |
| | 8 | 449 | | SetSelectedEdge(ref predecessor, current.Address, _workspace.GetNode(_currentSlot)); |
| | | 450 | | } |
| | 25 | 451 | | return Status; |
| | | 452 | | } |
| | 10 | 453 | | predecessor.IntegrationCost = candidate; |
| | 10 | 454 | | SetSelectedEdge(ref predecessor, current.Address, _workspace.GetNode(_currentSlot)); |
| | 10 | 455 | | _heap.DecreaseKey(predecessorSlot); |
| | 10 | 456 | | return Status; |
| | | 457 | | } |
| | | 458 | | |
| | | 459 | | private NavigationFlowFieldStatus AdvanceTransitionReplay( |
| | | 460 | | ref int nodeRemaining, |
| | | 461 | | ref int edgeRemaining, |
| | | 462 | | ref int connectionRemaining) |
| | | 463 | | { |
| | 4060 | 464 | | while (_postNodeOrdinal < _workspace.SettledCount) |
| | | 465 | | { |
| | 3352 | 466 | | int slot = _workspace.SettledSlots[_postNodeOrdinal]; |
| | 3352 | 467 | | ref NavigationFlowFieldSearchNode record = ref _workspace.GetRecord(slot); |
| | 3352 | 468 | | if (!record.SelectedIsTransition) |
| | | 469 | | { |
| | 3316 | 470 | | if (nodeRemaining == 0) |
| | 10 | 471 | | return Status; |
| | 3306 | 472 | | nodeRemaining--; |
| | 3306 | 473 | | _postNodeOrdinal++; |
| | 3306 | 474 | | continue; |
| | | 475 | | } |
| | 36 | 476 | | NavigationMediumStateRef source = _workspace.GetNode(slot); |
| | 36 | 477 | | if (!_replayActive) |
| | | 478 | | { |
| | 13 | 479 | | if (nodeRemaining == 0) |
| | 1 | 480 | | return Status; |
| | 12 | 481 | | nodeRemaining--; |
| | 12 | 482 | | _replayEdges = new NavigationTraversalEdgeEnumerator( |
| | 12 | 483 | | _world!, |
| | 12 | 484 | | _graph!, |
| | 12 | 485 | | source, |
| | 12 | 486 | | _query!.Query.Agent, |
| | 12 | 487 | | _query.AreaPolicy, |
| | 12 | 488 | | _workspace.RayWorkspace, |
| | 12 | 489 | | allowTransitions: true, |
| | 12 | 490 | | emittedSurfaceOrdinal: -1); |
| | 12 | 491 | | _replayActive = true; |
| | | 492 | | } |
| | 35 | 493 | | NavigationTraversalEdgeAdvanceStatus status = _replayEdges.AdvanceOne( |
| | 35 | 494 | | _meter, |
| | 35 | 495 | | _workspace.EndpointWorkspace.Dependencies, |
| | 35 | 496 | | ref edgeRemaining, |
| | 35 | 497 | | ref connectionRemaining); |
| | 35 | 498 | | CaptureWorldDependency(_replayEdges.RequiresWorldStamp); |
| | 35 | 499 | | status = ResolvePostEnumerationStatus(IsWorldCurrent(), status); |
| | 35 | 500 | | if (status == NavigationTraversalEdgeAdvanceStatus.Pending) |
| | | 501 | | continue; |
| | 15 | 502 | | if (status == NavigationTraversalEdgeAdvanceStatus.Blocked) |
| | 2 | 503 | | return Status; |
| | 13 | 504 | | if (status != NavigationTraversalEdgeAdvanceStatus.Edge) |
| | 1 | 505 | | return Finish(NavigationGuideStatusMapper.ToFlowField(status)); |
| | 12 | 506 | | int selectedOrdinal = record.SelectedEdge.CanonicalOutgoingOrdinal; |
| | 12 | 507 | | if (_replayEdges.CurrentOrdinal < selectedOrdinal) |
| | | 508 | | continue; |
| | 10 | 509 | | NavigationMediumStateRef target = _replayEdges.CurrentTarget; |
| | 10 | 510 | | _payloadTransitionInstructions![_transitionOrdinal++] = |
| | 10 | 511 | | new NavigationTransitionInstruction( |
| | 10 | 512 | | _replayEdges.CurrentTransitionIdentityKind, |
| | 10 | 513 | | _replayEdges.CurrentTransitionOwnerMapId, |
| | 10 | 514 | | _replayEdges.CurrentTransitionId, |
| | 10 | 515 | | _replayEdges.CurrentTransitionType, |
| | 10 | 516 | | record.Address, |
| | 10 | 517 | | record.SelectedEdge.Target, |
| | 10 | 518 | | source.Medium, |
| | 10 | 519 | | target.Medium, |
| | 10 | 520 | | _replayEdges.CurrentTransitionSourceAction, |
| | 10 | 521 | | _replayEdges.CurrentTransitionDestinationAction, |
| | 10 | 522 | | _replayEdges.CurrentTransitionHints); |
| | 10 | 523 | | _replayEdges = default; |
| | 10 | 524 | | _replayActive = false; |
| | 10 | 525 | | _postNodeOrdinal++; |
| | | 526 | | } |
| | 708 | 527 | | _postNodeOrdinal = 0; |
| | 708 | 528 | | _transitionOrdinal = 0; |
| | 708 | 529 | | TryBeginDependencySort(); |
| | 708 | 530 | | return Status; |
| | | 531 | | } |
| | | 532 | | |
| | | 533 | | internal static NavigationTraversalEdgeAdvanceStatus ResolvePostEnumerationStatus( |
| | | 534 | | bool worldCurrent, |
| | 31551 | 535 | | NavigationTraversalEdgeAdvanceStatus status) => worldCurrent |
| | 31551 | 536 | | ? status |
| | 31551 | 537 | | : NavigationTraversalEdgeAdvanceStatus.Stale; |
| | | 538 | | |
| | | 539 | | internal static NavigationFlowFieldStatus ResolveFinalPublication( |
| | | 540 | | bool worldCurrent, |
| | | 541 | | NavigationFlowFieldStatus status, |
| | | 542 | | ref NavigationFlowFieldPayload? payload) |
| | | 543 | | { |
| | 704 | 544 | | if (worldCurrent) |
| | 702 | 545 | | return status; |
| | 2 | 546 | | payload = null; |
| | 2 | 547 | | return NavigationFlowFieldStatus.Stale; |
| | | 548 | | } |
| | | 549 | | |
| | | 550 | | private bool TryRecordPage(NavigationNodeRef node) |
| | | 551 | | { |
| | 1471 | 552 | | _graph!.TryGetNodeAddress(node, out NavigationCellAddress address); |
| | 1471 | 553 | | return _workspace.TryRecordPage( |
| | 1471 | 554 | | address.MapId, |
| | 1471 | 555 | | node.CellSlot / NavigationSemanticPage.SlotCount); |
| | | 556 | | } |
| | | 557 | | |
| | | 558 | | private bool TryRecordStateDependencies(NavigationMediumStateRef state) |
| | | 559 | | { |
| | 6174 | 560 | | _graph!.TryGetNodeAddress(state.Node, out NavigationCellAddress address); |
| | 6174 | 561 | | if (!_workspace.TryRecordPage( |
| | 6174 | 562 | | address.MapId, |
| | 6174 | 563 | | state.Node.CellSlot / NavigationSemanticPage.SlotCount)) |
| | | 564 | | { |
| | 1 | 565 | | return false; |
| | | 566 | | } |
| | 6173 | 567 | | _graph.TryGetSurfaceComponent( |
| | 6173 | 568 | | address, |
| | 6173 | 569 | | state.Medium, |
| | 6173 | 570 | | out NavigationSurfaceComponentKey component, |
| | 6173 | 571 | | out _); |
| | 6173 | 572 | | return _workspace.TryRecordComponent(component); |
| | | 573 | | } |
| | | 574 | | |
| | | 575 | | private NavigationFlowFieldStatus Finish(NavigationFlowFieldStatus status) |
| | | 576 | | { |
| | 726 | 577 | | Status = status; |
| | 726 | 578 | | _query!.ReleaseLease(); |
| | 726 | 579 | | ReleaseRuntimeState(); |
| | 726 | 580 | | return Status; |
| | | 581 | | } |
| | | 582 | | |
| | | 583 | | private bool TryBeginTransitionCount() |
| | | 584 | | { |
| | 710 | 585 | | _stage = Stage.CountTransitions; |
| | 710 | 586 | | return true; |
| | | 587 | | } |
| | | 588 | | |
| | | 589 | | private bool TryBeginDependencySort() |
| | | 590 | | { |
| | 708 | 591 | | long maximumRetainedBytes = |
| | 708 | 592 | | NavigationFlowFieldPayload.GetMaximumRetainedBytes( |
| | 708 | 593 | | _workspace.SettledCount, |
| | 708 | 594 | | _payloadTransitionInstructions!.Length, |
| | 708 | 595 | | _workspace.DependencyComponentCount, |
| | 708 | 596 | | _workspace.DependencyPageCount); |
| | 708 | 597 | | if (maximumRetainedBytes > _maximumPayloadBytes) |
| | | 598 | | { |
| | 3 | 599 | | Finish(NavigationFlowFieldStatus.CapacityExceeded); |
| | 3 | 600 | | return false; |
| | | 601 | | } |
| | 705 | 602 | | _dependencySort = new NavigationDependencySortWork( |
| | 705 | 603 | | _workspace.DependencyComponents, |
| | 705 | 604 | | _workspace.DependencyComponentCount, |
| | 705 | 605 | | _workspace.DependencyPages, |
| | 705 | 606 | | _workspace.DependencyPageCount); |
| | 705 | 607 | | _stage = Stage.SortDependencies; |
| | 705 | 608 | | return true; |
| | | 609 | | } |
| | | 610 | | |
| | | 611 | | private static int CompareSelectedEdge( |
| | | 612 | | NavigationSelectedEdgeRef left, |
| | | 613 | | NavigationSelectedEdgeRef right) => |
| | 24 | 614 | | left.CanonicalOutgoingOrdinal.CompareTo(right.CanonicalOutgoingOrdinal); |
| | | 615 | | |
| | | 616 | | private NavigationSelectedEdgeRef CreateSelectedEdge( |
| | | 617 | | NavigationCellAddress target, |
| | 3223 | 618 | | NavigationMediumStateRef targetState) => new( |
| | 3223 | 619 | | target, |
| | 3223 | 620 | | targetState.Medium, |
| | 3223 | 621 | | _incoming.CurrentOrdinal); |
| | | 622 | | |
| | | 623 | | private void SetSelectedEdge( |
| | | 624 | | ref NavigationFlowFieldSearchNode record, |
| | | 625 | | NavigationCellAddress target, |
| | | 626 | | NavigationMediumStateRef currentState) |
| | | 627 | | { |
| | 3199 | 628 | | record.SelectedEdge = CreateSelectedEdge(target, currentState); |
| | 3199 | 629 | | record.SelectedIsTransition = |
| | 3199 | 630 | | _incoming.CurrentKind == NavigationTraversalEdgeKind.Transition; |
| | 3199 | 631 | | } |
| | | 632 | | |
| | | 633 | | private bool TrySeedTargetMedium(TraversalMedium medium) |
| | | 634 | | { |
| | 2195 | 635 | | if (medium is TraversalMedium.Gas or TraversalMedium.Liquid) |
| | | 636 | | { |
| | 1461 | 637 | | if ((_query!.TargetMedia & NavigationCell.ToMedia(medium)) != 0) |
| | 29 | 638 | | _requiresWorldStamp = true; |
| | | 639 | | } |
| | 2195 | 640 | | if ((_query!.End.Media & NavigationCell.ToMedia(medium)) == 0) |
| | 1457 | 641 | | return true; |
| | 738 | 642 | | _graph!.TryGetSurfaceComponent( |
| | 738 | 643 | | _query.End.Address, |
| | 738 | 644 | | medium, |
| | 738 | 645 | | out NavigationSurfaceComponentKey component, |
| | 738 | 646 | | out _); |
| | 738 | 647 | | var state = new NavigationMediumStateRef(_query.End.Node, medium); |
| | 738 | 648 | | if (!_workspace.TryRecordComponent(component) |
| | 738 | 649 | | || !_workspace.TryGetOrAdd(state, out int slot, out _)) |
| | | 650 | | { |
| | 5 | 651 | | return false; |
| | | 652 | | } |
| | 733 | 653 | | ref NavigationFlowFieldSearchNode destination = |
| | 733 | 654 | | ref _workspace.GetRecord(slot); |
| | 733 | 655 | | destination.Address = _query.End.Address; |
| | 733 | 656 | | destination.IntegrationCost = Fixed64.Zero; |
| | 733 | 657 | | destination.HeapIndex = -1; |
| | 733 | 658 | | _heap.Push(slot); |
| | 733 | 659 | | return true; |
| | | 660 | | } |
| | | 661 | | |
| | | 662 | | private void CaptureWorldDependency(bool requiresWorldStamp) |
| | | 663 | | { |
| | 31535 | 664 | | if (requiresWorldStamp) |
| | 769 | 665 | | _requiresWorldStamp = true; |
| | 31535 | 666 | | } |
| | | 667 | | |
| | | 668 | | private bool IsWorldCurrent() => |
| | 69860 | 669 | | !_requiresWorldStamp |
| | 69860 | 670 | | || _world!.ChangeSequence == _worldChangeSequence; |
| | | 671 | | |
| | | 672 | | } |
| | | 673 | | |
| | | 674 | | /// <summary>Canonically heap-sorts flow nodes or lookup ordinals with bounded comparisons.</summary> |
| | | 675 | | internal struct NavigationFlowPayloadSortWork |
| | | 676 | | { |
| | | 677 | | private enum SiftStage : byte |
| | | 678 | | { |
| | | 679 | | None = 0, |
| | | 680 | | ChooseChild = 1, |
| | | 681 | | CompareRoot = 2 |
| | | 682 | | } |
| | | 683 | | |
| | | 684 | | private readonly NavigationFlowFieldNode[] _nodes; |
| | | 685 | | private readonly int[] _lookup; |
| | | 686 | | private readonly bool _sortNodes; |
| | | 687 | | private SiftStage _siftStage; |
| | | 688 | | private int _heapSize; |
| | | 689 | | private int _buildIndex; |
| | | 690 | | private int _sortEnd; |
| | | 691 | | private int _siftRoot; |
| | | 692 | | private int _siftCandidate; |
| | | 693 | | private bool _building; |
| | | 694 | | |
| | | 695 | | internal NavigationFlowPayloadSortWork( |
| | | 696 | | NavigationFlowFieldNode[] nodes, |
| | | 697 | | int[] lookup, |
| | | 698 | | bool sortNodes) |
| | | 699 | | { |
| | | 700 | | _nodes = nodes; |
| | | 701 | | _lookup = lookup; |
| | | 702 | | _sortNodes = sortNodes; |
| | | 703 | | _siftStage = SiftStage.None; |
| | | 704 | | _heapSize = nodes.Length; |
| | | 705 | | _buildIndex = (nodes.Length / 2) - 1; |
| | | 706 | | _sortEnd = nodes.Length - 1; |
| | | 707 | | _siftRoot = 0; |
| | | 708 | | _siftCandidate = 0; |
| | | 709 | | _building = true; |
| | | 710 | | } |
| | | 711 | | |
| | | 712 | | internal bool Advance(NavigationWorkMeter meter, int lookupStepLimit) |
| | | 713 | | { |
| | | 714 | | int remaining = Math.Min(lookupStepLimit, meter.RemainingLookupProbes); |
| | | 715 | | while (true) |
| | | 716 | | { |
| | | 717 | | if (_siftStage == SiftStage.None) |
| | | 718 | | { |
| | | 719 | | if (_building) |
| | | 720 | | { |
| | | 721 | | if (_buildIndex < 0) |
| | | 722 | | { |
| | | 723 | | _building = false; |
| | | 724 | | continue; |
| | | 725 | | } |
| | | 726 | | BeginSift(_buildIndex--); |
| | | 727 | | continue; |
| | | 728 | | } |
| | | 729 | | if (_sortEnd <= 0) |
| | | 730 | | return true; |
| | | 731 | | Swap(0, _sortEnd); |
| | | 732 | | _heapSize = _sortEnd--; |
| | | 733 | | BeginSift(0); |
| | | 734 | | continue; |
| | | 735 | | } |
| | | 736 | | if (_siftStage == SiftStage.ChooseChild) |
| | | 737 | | { |
| | | 738 | | int left = checked((_siftRoot * 2) + 1); |
| | | 739 | | if (left >= _heapSize) |
| | | 740 | | { |
| | | 741 | | _siftStage = SiftStage.None; |
| | | 742 | | continue; |
| | | 743 | | } |
| | | 744 | | _siftCandidate = left; |
| | | 745 | | int right = left + 1; |
| | | 746 | | if (right < _heapSize) |
| | | 747 | | { |
| | | 748 | | if (!TryConsumeComparison(meter, ref remaining)) |
| | | 749 | | return false; |
| | | 750 | | if (Compare(left, right) < 0) |
| | | 751 | | _siftCandidate = right; |
| | | 752 | | } |
| | | 753 | | _siftStage = SiftStage.CompareRoot; |
| | | 754 | | continue; |
| | | 755 | | } |
| | | 756 | | if (!TryConsumeComparison(meter, ref remaining)) |
| | | 757 | | return false; |
| | | 758 | | if (Compare(_siftRoot, _siftCandidate) >= 0) |
| | | 759 | | { |
| | | 760 | | _siftStage = SiftStage.None; |
| | | 761 | | continue; |
| | | 762 | | } |
| | | 763 | | Swap(_siftRoot, _siftCandidate); |
| | | 764 | | _siftRoot = _siftCandidate; |
| | | 765 | | _siftStage = SiftStage.ChooseChild; |
| | | 766 | | } |
| | | 767 | | } |
| | | 768 | | |
| | | 769 | | private void BeginSift(int root) |
| | | 770 | | { |
| | | 771 | | _siftRoot = root; |
| | | 772 | | _siftStage = SiftStage.ChooseChild; |
| | | 773 | | } |
| | | 774 | | |
| | | 775 | | private int Compare(int left, int right) |
| | | 776 | | { |
| | | 777 | | if (!_sortNodes) |
| | | 778 | | { |
| | | 779 | | NavigationFlowFieldNode lookupLeft = _nodes[_lookup[left]]; |
| | | 780 | | NavigationFlowFieldNode lookupRight = _nodes[_lookup[right]]; |
| | | 781 | | int lookupComparison = lookupLeft.Address.CompareTo(lookupRight.Address); |
| | | 782 | | return lookupComparison != 0 |
| | | 783 | | ? lookupComparison |
| | | 784 | | : ((int)lookupLeft.Medium).CompareTo((int)lookupRight.Medium); |
| | | 785 | | } |
| | | 786 | | NavigationFlowFieldNode leftNode = _nodes[left]; |
| | | 787 | | NavigationFlowFieldNode rightNode = _nodes[right]; |
| | | 788 | | int comparison = leftNode.IntegrationCost.CompareTo( |
| | | 789 | | rightNode.IntegrationCost); |
| | | 790 | | if (comparison != 0) |
| | | 791 | | return comparison; |
| | | 792 | | comparison = leftNode.Address.CompareTo(rightNode.Address); |
| | | 793 | | return comparison != 0 |
| | | 794 | | ? comparison |
| | | 795 | | : ((int)leftNode.Medium).CompareTo((int)rightNode.Medium); |
| | | 796 | | } |
| | | 797 | | |
| | | 798 | | private void Swap(int left, int right) |
| | | 799 | | { |
| | | 800 | | if (_sortNodes) |
| | | 801 | | { |
| | | 802 | | (_nodes[left], _nodes[right]) = (_nodes[right], _nodes[left]); |
| | | 803 | | return; |
| | | 804 | | } |
| | | 805 | | (_lookup[left], _lookup[right]) = (_lookup[right], _lookup[left]); |
| | | 806 | | } |
| | | 807 | | |
| | | 808 | | private static bool TryConsumeComparison( |
| | | 809 | | NavigationWorkMeter meter, |
| | | 810 | | ref int remaining) |
| | | 811 | | { |
| | | 812 | | if (remaining == 0 || !meter.TryConsumeLookupProbes(1)) |
| | | 813 | | return false; |
| | | 814 | | remaining--; |
| | | 815 | | return true; |
| | | 816 | | } |
| | | 817 | | } |