| | | 1 | | //======================================================================= |
| | | 2 | | // TrailblazerPathingService.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 GridForge.Grids; |
| | | 10 | | using GridForge.Spatial; |
| | | 11 | | |
| | | 12 | | namespace Trailblazer.Pathing; |
| | | 13 | | |
| | | 14 | | /// <summary> |
| | | 15 | | /// Context-owned admission, query, maintenance, and diagnostic API for unified navigation maps. |
| | | 16 | | /// </summary> |
| | | 17 | | public sealed class TrailblazerPathingService |
| | | 18 | | { |
| | | 19 | | private readonly TrailblazerWorldContext _context; |
| | | 20 | | |
| | | 21 | | private bool _disposed; |
| | | 22 | | |
| | | 23 | | private readonly NavigationGraphRuntime _navigationGraph; |
| | | 24 | | private readonly NavigationImmediateRayWorkspace _immediateRayWorkspace; |
| | | 25 | | private readonly NavigationAStarAdmissionGate _navigationAStarAdmissionGate; |
| | | 26 | | private readonly NavigationFlowAdmissionGate _navigationFlowAdmissionGate; |
| | | 27 | | |
| | 989 | 28 | | internal TrailblazerPathingService(TrailblazerWorldContext context) |
| | | 29 | | { |
| | 989 | 30 | | _context = context; |
| | 989 | 31 | | _navigationGraph = new NavigationGraphRuntime(context.World, context.Settings); |
| | 989 | 32 | | NavigationQueryLimits queryLimits = context.Settings.QueryLimits; |
| | 989 | 33 | | _immediateRayWorkspace = new NavigationImmediateRayWorkspace( |
| | 989 | 34 | | Math.Max( |
| | 989 | 35 | | queryLimits.AStarWorkspaceMapCapacity, |
| | 989 | 36 | | queryLimits.FlowWorkspaceMapCapacity), |
| | 989 | 37 | | Math.Max( |
| | 989 | 38 | | queryLimits.AStarWorkspaceEndpointPageCapacity, |
| | 989 | 39 | | queryLimits.FlowWorkspaceEndpointPageCapacity), |
| | 989 | 40 | | Math.Max( |
| | 989 | 41 | | queryLimits.AStarWorkspaceComponentCapacity, |
| | 989 | 42 | | queryLimits.FlowWorkspaceComponentCapacity), |
| | 989 | 43 | | queryLimits.RayWorkspaceCoveredAddressCapacity, |
| | 989 | 44 | | queryLimits.RayWorkspaceTraceIntervalCapacity); |
| | 989 | 45 | | var admissionCoordinator = new NavigationQueryAdmissionCoordinator( |
| | 989 | 46 | | queryLimits.MaxConcurrentNavigationQueries); |
| | 989 | 47 | | _navigationAStarAdmissionGate = new NavigationAStarAdmissionGate( |
| | 989 | 48 | | context.World, |
| | 989 | 49 | | _navigationGraph.Store, |
| | 989 | 50 | | context.Settings.QueryLimits, |
| | 989 | 51 | | admissionCoordinator); |
| | 989 | 52 | | _navigationFlowAdmissionGate = new NavigationFlowAdmissionGate( |
| | 989 | 53 | | context.World, |
| | 989 | 54 | | _navigationGraph.Store, |
| | 989 | 55 | | context.Settings.QueryLimits, |
| | 989 | 56 | | admissionCoordinator, |
| | 989 | 57 | | _immediateRayWorkspace); |
| | 989 | 58 | | context.World.OnChangeCommitted += HandleCommittedChange; |
| | 989 | 59 | | } |
| | | 60 | | |
| | | 61 | | internal int RetainedBaselineCaptureCount => |
| | 21 | 62 | | _navigationGraph.RetainedBaselineCaptureCount; |
| | | 63 | | |
| | | 64 | | internal NavigationWorldGraphStore NavigationGraphStore => |
| | 1034 | 65 | | _navigationGraph.Store; |
| | | 66 | | |
| | | 67 | | internal NavigationAStarAdmissionGate NavigationAStarAdmissionGate => |
| | 404 | 68 | | _navigationAStarAdmissionGate; |
| | | 69 | | |
| | | 70 | | internal NavigationFlowAdmissionGate NavigationFlowAdmissionGate => |
| | 69 | 71 | | _navigationFlowAdmissionGate; |
| | | 72 | | |
| | | 73 | | internal NavigationImmediateRayWorkspace ImmediateRayWorkspace => |
| | 577 | 74 | | _immediateRayWorkspace; |
| | | 75 | | |
| | | 76 | | internal int RetainedCompositionWorkCount => |
| | 341 | 77 | | _navigationGraph.RetainedCompositionWorkCount; |
| | | 78 | | |
| | | 79 | | internal int RetainedOperationWorkCount => |
| | 44 | 80 | | _navigationGraph.RetainedOperationWorkCount; |
| | | 81 | | |
| | | 82 | | internal MaintenanceWorkMeter NavigationMaintenanceMeter => |
| | 321 | 83 | | _navigationGraph.MaintenanceMeter; |
| | | 84 | | |
| | | 85 | | internal int LastAffectedMapCollectionCount => |
| | 1 | 86 | | _navigationGraph.LastAffectedMapCollectionCount; |
| | | 87 | | |
| | | 88 | | /// <summary>Admits one prepared map commit for deterministic fixed-step publication.</summary> |
| | | 89 | | public bool Admit(NavigationMapCommitOperation operation) |
| | | 90 | | { |
| | 931 | 91 | | EnsureUsable(); |
| | 931 | 92 | | return _navigationGraph.Admit(operation); |
| | | 93 | | } |
| | | 94 | | |
| | | 95 | | /// <summary>Admits one map removal for deterministic fixed-step publication.</summary> |
| | | 96 | | public bool Admit(NavigationMapRemoveOperation operation) |
| | | 97 | | { |
| | 9 | 98 | | EnsureUsable(); |
| | 9 | 99 | | return _navigationGraph.Admit(operation); |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | /// <summary>Admits one atomic semantic overlay transaction for deterministic fixed-step publication.</summary> |
| | | 103 | | public bool Admit(NavigationOverlayCommitOperation operation) |
| | | 104 | | { |
| | 191 | 105 | | EnsureUsable(); |
| | 191 | 106 | | return _navigationGraph.Admit(operation); |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | /// <summary>Admits one immutable navigation-area policy revision for fixed-step publication.</summary> |
| | | 110 | | public bool Admit(NavigationAreaPolicyCommitOperation operation) |
| | | 111 | | { |
| | 172 | 112 | | EnsureUsable(); |
| | 172 | 113 | | return _navigationGraph.Admit(operation); |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | internal void MaintainNavigationGraph(int frame) |
| | | 117 | | { |
| | 12185 | 118 | | EnsureUsable(); |
| | 12185 | 119 | | _navigationGraph.Maintain(frame); |
| | 12185 | 120 | | } |
| | | 121 | | |
| | | 122 | | internal NavigationWorldGraphLease? TryAcquireNavigationGraph() => |
| | 888 | 123 | | _navigationGraph.TryAcquire(); |
| | | 124 | | |
| | | 125 | | internal bool TryGetNavigationGraphCellState( |
| | | 126 | | string mapId, |
| | | 127 | | VoxelIndex index, |
| | | 128 | | out NavigationGraphCellState state) |
| | | 129 | | { |
| | 378 | 130 | | EnsureUsable(); |
| | 378 | 131 | | return _navigationGraph.TryGetCellState(mapId, index, out state); |
| | | 132 | | } |
| | | 133 | | |
| | | 134 | | internal NavigationCommittedCellResolveStatus TryResolveCommittedCell( |
| | | 135 | | GridForge.Configuration.GridConfigurationKey gridKey, |
| | | 136 | | WorldVoxelIndex worldIndex, |
| | | 137 | | out NavigationCellAddress address, |
| | | 138 | | out NavigationAreaId area, |
| | | 139 | | out long graphVersion) |
| | | 140 | | { |
| | 64 | 141 | | EnsureUsable(); |
| | 64 | 142 | | NavigationWorldGraph graph = _navigationGraph.Current; |
| | 64 | 143 | | graphVersion = graph.GraphVersion; |
| | 64 | 144 | | if (!graph.TryGetMapId(gridKey, out string mapId)) |
| | | 145 | | { |
| | 25 | 146 | | address = default; |
| | 25 | 147 | | area = default; |
| | 25 | 148 | | return NavigationCommittedCellResolveStatus.NoCell; |
| | | 149 | | } |
| | 39 | 150 | | graph.TryGetMap(mapId, out NavigationMapInstance? instance); |
| | | 151 | | System.Diagnostics.Debug.Assert(instance != null); |
| | 39 | 152 | | if (!instance.GridIdentity.Matches( |
| | 39 | 153 | | worldIndex.WorldSpawnToken, |
| | 39 | 154 | | worldIndex.GridIndex, |
| | 39 | 155 | | worldIndex.GridSpawnToken)) |
| | | 156 | | { |
| | 3 | 157 | | address = default; |
| | 3 | 158 | | area = default; |
| | 3 | 159 | | return NavigationCommittedCellResolveStatus.Unavailable; |
| | | 160 | | } |
| | | 161 | | NavigationCell cell; |
| | 36 | 162 | | if (!instance.TryGetSlot(worldIndex.VoxelIndex, out int slot)) |
| | | 163 | | { |
| | 1 | 164 | | address = default; |
| | 1 | 165 | | area = default; |
| | 1 | 166 | | return NavigationCommittedCellResolveStatus.NoCell; |
| | | 167 | | } |
| | 35 | 168 | | bool foundPhysicalState = instance.TryGetPhysicalState(slot, out bool isPresent, out _); |
| | | 169 | | System.Diagnostics.Debug.Assert(foundPhysicalState); |
| | 35 | 170 | | if (!isPresent |
| | 35 | 171 | | || !instance.TryGetEffectiveCell(slot, out cell)) |
| | | 172 | | { |
| | 2 | 173 | | address = default; |
| | 2 | 174 | | area = default; |
| | 2 | 175 | | return NavigationCommittedCellResolveStatus.NoCell; |
| | | 176 | | } |
| | | 177 | | |
| | 33 | 178 | | address = new NavigationCellAddress(mapId, worldIndex.VoxelIndex); |
| | 33 | 179 | | area = cell.Area; |
| | 33 | 180 | | return NavigationCommittedCellResolveStatus.Resolved; |
| | | 181 | | } |
| | | 182 | | |
| | | 183 | | internal bool TryResolveNavigationAreaPolicy( |
| | | 184 | | NavigationAreaPolicyKey key, |
| | | 185 | | out NavigationAreaPolicy? policy) |
| | | 186 | | { |
| | 7 | 187 | | EnsureUsable(); |
| | 7 | 188 | | return _navigationGraph.TryResolveAreaPolicy(key, out policy); |
| | | 189 | | } |
| | | 190 | | |
| | | 191 | | /// <summary>Copies a bounded immutable diagnostic view of the context navigation graph.</summary> |
| | | 192 | | public NavigationGraphDiagnosticsSnapshot GetNavigationGraphDiagnostics() |
| | | 193 | | { |
| | 1641 | 194 | | EnsureUsable(); |
| | 1640 | 195 | | return _navigationGraph.GetDiagnostics( |
| | 1640 | 196 | | _context.Settings.MaintenanceBudget.MaxBaselineAddresses); |
| | | 197 | | } |
| | | 198 | | |
| | | 199 | | /// <summary> |
| | | 200 | | /// Cancels active graph queries and clears this context's unified navigation graph. |
| | | 201 | | /// </summary> |
| | | 202 | | public void Reset() |
| | | 203 | | { |
| | 23 | 204 | | EnsureUsable(); |
| | 23 | 205 | | _navigationAStarAdmissionGate.CancelActive(); |
| | 23 | 206 | | _navigationFlowAdmissionGate.CancelActive(); |
| | 23 | 207 | | _navigationGraph.Reset(); |
| | 23 | 208 | | } |
| | | 209 | | |
| | | 210 | | internal void Dispose() |
| | | 211 | | { |
| | 988 | 212 | | _context.World.OnChangeCommitted -= HandleCommittedChange; |
| | 988 | 213 | | _navigationAStarAdmissionGate.Dispose(); |
| | 988 | 214 | | _navigationFlowAdmissionGate.Dispose(); |
| | 988 | 215 | | _navigationGraph.Dispose(); |
| | 988 | 216 | | _disposed = true; |
| | 988 | 217 | | } |
| | | 218 | | |
| | | 219 | | private void HandleCommittedChange(GridEventInfo eventInfo) => |
| | 1186 | 220 | | _navigationGraph.EnqueueCommittedChange(eventInfo); |
| | | 221 | | |
| | | 222 | | private void EnsureUsable() |
| | | 223 | | { |
| | 15601 | 224 | | SwiftThrowHelper.ThrowIfDisposed( |
| | 15601 | 225 | | _disposed, |
| | 15601 | 226 | | nameof(TrailblazerWorldContext)); |
| | 15601 | 227 | | SwiftThrowHelper.ThrowIfTrue( |
| | 15601 | 228 | | !_context.World.IsActive, |
| | 15601 | 229 | | nameof(TrailblazerPathingService), |
| | 15601 | 230 | | "TrailblazerPathingService is bound to an inactive GridWorld."); |
| | 15600 | 231 | | } |
| | | 232 | | } |
| | | 233 | | |
| | | 234 | | internal enum NavigationCommittedCellResolveStatus : byte |
| | | 235 | | { |
| | | 236 | | NoCell, |
| | | 237 | | Resolved, |
| | | 238 | | Unavailable |
| | | 239 | | } |