| | | 1 | | //======================================================================= |
| | | 2 | | // NavigationAutomaticSeamLifecycleWork.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 GridForge.Grids; |
| | | 9 | | |
| | | 10 | | namespace Trailblazer.Pathing; |
| | | 11 | | |
| | | 12 | | /// <summary>Retains one fail-closed GridForge lifecycle prefix until seam state is atomic.</summary> |
| | | 13 | | internal sealed class NavigationAutomaticSeamLifecycleWork |
| | | 14 | | { |
| | | 15 | | internal const long BaseRetainedBytes = 80L; |
| | | 16 | | |
| | | 17 | | private readonly NavigationWorldGraph _source; |
| | | 18 | | private readonly NavigationAutomaticSeamRefreshWork _refresh; |
| | 1064 | 19 | | private NavigationSurfaceComponentKeySet _affectedComponents = |
| | 1064 | 20 | | NavigationSurfaceComponentKeySet.Empty; |
| | 1064 | 21 | | private NavigationCellAddressSet _affectedAddresses = NavigationCellAddressSet.Empty; |
| | | 22 | | private NavigationWorldGraph? _structuralGraph; |
| | | 23 | | private NavigationSurfaceComponentBuildWork? _componentUpdate; |
| | | 24 | | private int _affectedMemberCount; |
| | | 25 | | private int _endpointOrdinal; |
| | 1064 | 26 | | private TraversalMedium _endpointMedium = TraversalMedium.Solid; |
| | | 27 | | private bool _affectedCaptureComplete; |
| | | 28 | | |
| | 1064 | 29 | | internal NavigationAutomaticSeamLifecycleWork( |
| | 1064 | 30 | | GridWorld world, |
| | 1064 | 31 | | NavigationWorldGraph source, |
| | 1064 | 32 | | GridEventInfo[] events, |
| | 1064 | 33 | | int eventCount, |
| | 1064 | 34 | | bool fullRebuild = false) |
| | | 35 | | { |
| | 1064 | 36 | | _source = source; |
| | 1064 | 37 | | _refresh = new NavigationAutomaticSeamRefreshWork( |
| | 1064 | 38 | | world, |
| | 1064 | 39 | | source, |
| | 1064 | 40 | | source, |
| | 1064 | 41 | | events, |
| | 1064 | 42 | | eventCount, |
| | 1064 | 43 | | fullRebuild); |
| | 1064 | 44 | | } |
| | | 45 | | |
| | 1539 | 46 | | internal long RetainedBytes => checked( |
| | 1539 | 47 | | BaseRetainedBytes |
| | 1539 | 48 | | + _refresh.RetainedBytes |
| | 1539 | 49 | | + (ReferenceEquals(_affectedComponents, NavigationSurfaceComponentKeySet.Empty) |
| | 1539 | 50 | | ? 0L |
| | 1539 | 51 | | : _affectedComponents.RetainedBytes) |
| | 1539 | 52 | | + (ReferenceEquals(_affectedAddresses, NavigationCellAddressSet.Empty) |
| | 1539 | 53 | | ? 0L |
| | 1539 | 54 | | : _affectedAddresses.RetainedBytes) |
| | 1539 | 55 | | + (_componentUpdate?.RetainedBytes ?? 0L)); |
| | | 56 | | |
| | 1536 | 57 | | internal int PersistentPageCount => checked( |
| | 1536 | 58 | | 1 |
| | 1536 | 59 | | + _refresh.PersistentPageCount |
| | 1536 | 60 | | + (ReferenceEquals(_affectedComponents, NavigationSurfaceComponentKeySet.Empty) |
| | 1536 | 61 | | ? 0 |
| | 1536 | 62 | | : _affectedComponents.PersistentPageCount) |
| | 1536 | 63 | | + (ReferenceEquals(_affectedAddresses, NavigationCellAddressSet.Empty) |
| | 1536 | 64 | | ? 0 |
| | 1536 | 65 | | : _affectedAddresses.PersistentPageCount) |
| | 1536 | 66 | | + (_componentUpdate?.PersistentPageCount ?? 0)); |
| | | 67 | | |
| | | 68 | | internal NavigationWorldGraph Result |
| | | 69 | | { |
| | | 70 | | get |
| | | 71 | | { |
| | 66 | 72 | | return _structuralGraph!.WithSurfaceComponents(_componentUpdate!.Result); |
| | | 73 | | } |
| | | 74 | | } |
| | | 75 | | |
| | 33 | 76 | | internal bool RevalidateForPublication() => _refresh.RevalidateForPublication(); |
| | | 77 | | |
| | | 78 | | internal AdvanceStatus AdvanceOne( |
| | | 79 | | MaintenanceWorkMeter meter, |
| | | 80 | | long maximumRetainedBytes, |
| | | 81 | | int maximumPersistentPages) |
| | | 82 | | { |
| | 1469 | 83 | | if (!_refresh.IsComplete) |
| | | 84 | | { |
| | 1405 | 85 | | long revision = _refresh.Revision; |
| | 1405 | 86 | | NavigationAutomaticSeamRefreshWork.SeamAdvanceStatus status = |
| | 1405 | 87 | | _refresh.AdvanceOne(meter); |
| | 1405 | 88 | | if (_refresh.Revision != revision) |
| | 2 | 89 | | return AdvanceStatus.RestartRequired; |
| | 1403 | 90 | | if (ExceedsCapacity(maximumRetainedBytes, maximumPersistentPages)) |
| | 1 | 91 | | return AdvanceStatus.CapacityExceeded; |
| | 1402 | 92 | | return status switch |
| | 1402 | 93 | | { |
| | 1402 | 94 | | NavigationAutomaticSeamRefreshWork.SeamAdvanceStatus.Blocked => |
| | 154 | 95 | | AdvanceStatus.Blocked, |
| | 1402 | 96 | | NavigationAutomaticSeamRefreshWork.SeamAdvanceStatus.Complete => |
| | 36 | 97 | | AdvanceStatus.Progressed, |
| | 1212 | 98 | | _ => AdvanceStatus.Progressed |
| | 1402 | 99 | | }; |
| | | 100 | | } |
| | | 101 | | |
| | 142 | 102 | | while (!_affectedCaptureComplete) |
| | | 103 | | { |
| | 125 | 104 | | if (_endpointOrdinal >= _refresh.ChangedStructuralEndpointCount) |
| | | 105 | | { |
| | 34 | 106 | | _affectedCaptureComplete = true; |
| | 34 | 107 | | break; |
| | | 108 | | } |
| | 91 | 109 | | if (!meter.TryConsumeDependencyEntries(1)) |
| | 12 | 110 | | return AdvanceStatus.Blocked; |
| | 79 | 111 | | NavigationCellAddress address = |
| | 79 | 112 | | _refresh.GetChangedStructuralEndpointAt(_endpointOrdinal); |
| | 79 | 113 | | if (_endpointMedium == TraversalMedium.Solid) |
| | 27 | 114 | | _affectedAddresses = _affectedAddresses.Add(address); |
| | 79 | 115 | | if (_source.TryGetSurfaceComponent( |
| | 79 | 116 | | address, |
| | 79 | 117 | | _endpointMedium, |
| | 79 | 118 | | out NavigationSurfaceComponentKey key, |
| | 79 | 119 | | out _) |
| | 79 | 120 | | && !_affectedComponents.Contains(key)) |
| | | 121 | | { |
| | 18 | 122 | | _affectedComponents = _affectedComponents.Add(key); |
| | 18 | 123 | | _source.SurfaceComponents.TryGet(key, out NavigationSurfaceComponent component); |
| | 18 | 124 | | _affectedMemberCount = checked( |
| | 18 | 125 | | _affectedMemberCount + component.Members.Count); |
| | | 126 | | } |
| | 79 | 127 | | if (_endpointMedium == TraversalMedium.Liquid) |
| | | 128 | | { |
| | 26 | 129 | | _endpointMedium = TraversalMedium.Solid; |
| | 26 | 130 | | _endpointOrdinal++; |
| | | 131 | | } |
| | | 132 | | else |
| | | 133 | | { |
| | 53 | 134 | | _endpointMedium++; |
| | | 135 | | } |
| | 79 | 136 | | if (ExceedsCapacity(maximumRetainedBytes, maximumPersistentPages)) |
| | 1 | 137 | | return AdvanceStatus.CapacityExceeded; |
| | | 138 | | } |
| | | 139 | | |
| | 51 | 140 | | NavigationWorldGraph next = _source |
| | 51 | 141 | | .WithAutomaticSeams(_refresh.Result) |
| | 51 | 142 | | .ReopenStructuralScopes(_source.GraphVersion + 1); |
| | 51 | 143 | | _structuralGraph ??= next; |
| | 51 | 144 | | _componentUpdate ??= new NavigationSurfaceComponentBuildWork( |
| | 51 | 145 | | _structuralGraph, |
| | 51 | 146 | | _source, |
| | 51 | 147 | | _affectedComponents, |
| | 51 | 148 | | _affectedAddresses, |
| | 51 | 149 | | checked(_affectedMemberCount + _affectedAddresses.Count)); |
| | 51 | 150 | | bool complete = _componentUpdate.Advance(meter); |
| | 51 | 151 | | if (ExceedsCapacity(maximumRetainedBytes, maximumPersistentPages)) |
| | 1 | 152 | | return AdvanceStatus.CapacityExceeded; |
| | 50 | 153 | | return complete ? AdvanceStatus.Complete : AdvanceStatus.Blocked; |
| | | 154 | | } |
| | | 155 | | |
| | | 156 | | private bool ExceedsCapacity(long maximumRetainedBytes, int maximumPersistentPages) => |
| | 1533 | 157 | | RetainedBytes > maximumRetainedBytes |
| | 1533 | 158 | | || PersistentPageCount > maximumPersistentPages; |
| | | 159 | | |
| | | 160 | | internal enum AdvanceStatus : byte |
| | | 161 | | { |
| | | 162 | | Blocked, |
| | | 163 | | Progressed, |
| | | 164 | | Complete, |
| | | 165 | | RestartRequired, |
| | | 166 | | CapacityExceeded |
| | | 167 | | } |
| | | 168 | | } |