| | | 1 | | //======================================================================= |
| | | 2 | | // NavigationSeamEditTree.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 | | |
| | | 10 | | namespace Trailblazer.Pathing; |
| | | 11 | | |
| | | 12 | | /// <summary>Provides a seam-local immutable AVL with exact token-owned edit accounting.</summary> |
| | | 13 | | internal sealed class NavigationSeamEditTree<TKey, TValue> |
| | | 14 | | where TKey : struct, IComparable<TKey> |
| | | 15 | | where TValue : class |
| | | 16 | | { |
| | | 17 | | private readonly Node? _root; |
| | | 18 | | private readonly int _nodeBytes; |
| | | 19 | | |
| | | 20 | | internal NavigationSeamEditTree(int nodeBytes) |
| | 19 | 21 | | : this(null, 0, nodeBytes) |
| | | 22 | | { |
| | 19 | 23 | | } |
| | | 24 | | |
| | 1195 | 25 | | private NavigationSeamEditTree(Node? root, int count, int nodeBytes) |
| | | 26 | | { |
| | 1195 | 27 | | _root = root; |
| | 1195 | 28 | | Count = count; |
| | 1195 | 29 | | _nodeBytes = nodeBytes; |
| | 1195 | 30 | | } |
| | | 31 | | |
| | | 32 | | internal int Count { get; } |
| | | 33 | | |
| | 226457 | 34 | | internal long RetainedBytes => checked(32L + ((long)Count * _nodeBytes)); |
| | | 35 | | |
| | 226453 | 36 | | internal int PersistentPageCount => checked(1 + Count); |
| | | 37 | | |
| | | 38 | | internal bool TryGetValue(TKey key, out TValue value) |
| | | 39 | | { |
| | 610229 | 40 | | Node? node = _root; |
| | 856970 | 41 | | while (node != null) |
| | | 42 | | { |
| | 390475 | 43 | | int comparison = key.CompareTo(node.Key); |
| | 390475 | 44 | | if (comparison == 0) |
| | | 45 | | { |
| | 143734 | 46 | | value = node.Value; |
| | 143734 | 47 | | return true; |
| | | 48 | | } |
| | 246741 | 49 | | node = comparison < 0 ? node.Left : node.Right; |
| | | 50 | | } |
| | 466495 | 51 | | value = null!; |
| | 466495 | 52 | | return false; |
| | | 53 | | } |
| | | 54 | | |
| | 1298 | 55 | | internal Editor Edit(NavigationSeamEditToken ownershipToken) => new(this, ownershipToken); |
| | | 56 | | |
| | | 57 | | internal Cursor CreateCursor(int maximumHeight, int shellBytes) => |
| | 631 | 58 | | new(maximumHeight, shellBytes); |
| | | 59 | | |
| | | 60 | | internal sealed class Editor |
| | | 61 | | { |
| | | 62 | | private readonly int _nodeBytes; |
| | | 63 | | private readonly long _token; |
| | | 64 | | private readonly NavigationSeamEditTree<TKey, TValue> _source; |
| | | 65 | | private Node? _root; |
| | | 66 | | private bool _sealed; |
| | | 67 | | |
| | 1298 | 68 | | internal Editor( |
| | 1298 | 69 | | NavigationSeamEditTree<TKey, TValue> source, |
| | 1298 | 70 | | NavigationSeamEditToken ownershipToken) |
| | | 71 | | { |
| | 1298 | 72 | | _root = source._root; |
| | 1298 | 73 | | _source = source; |
| | 1298 | 74 | | _nodeBytes = source._nodeBytes; |
| | 1298 | 75 | | _token = ownershipToken.Value; |
| | 1298 | 76 | | Count = source.Count; |
| | 1298 | 77 | | } |
| | | 78 | | |
| | | 79 | | internal int Count { get; private set; } |
| | | 80 | | |
| | | 81 | | internal int OwnedNodeCount { get; private set; } |
| | | 82 | | |
| | | 83 | | internal bool IsChanged { get; private set; } |
| | | 84 | | |
| | 684149 | 85 | | internal long RetainedBytes => checked(56L + ((long)OwnedNodeCount * _nodeBytes)); |
| | | 86 | | |
| | 684133 | 87 | | internal int PersistentPageCount => checked(1 + OwnedNodeCount); |
| | | 88 | | |
| | | 89 | | internal bool TryGetValue(TKey key, out TValue value) |
| | | 90 | | { |
| | 101024 | 91 | | Node? node = _root; |
| | 941548 | 92 | | while (node != null) |
| | | 93 | | { |
| | 876677 | 94 | | int comparison = key.CompareTo(node.Key); |
| | 876677 | 95 | | if (comparison == 0) |
| | | 96 | | { |
| | 36153 | 97 | | value = node.Value; |
| | 36153 | 98 | | return true; |
| | | 99 | | } |
| | 840524 | 100 | | node = comparison < 0 ? node.Left : node.Right; |
| | | 101 | | } |
| | 64871 | 102 | | value = null!; |
| | 64871 | 103 | | return false; |
| | | 104 | | } |
| | | 105 | | |
| | | 106 | | internal void Set(TKey key, TValue value) |
| | | 107 | | { |
| | 16489 | 108 | | EnsureWritable(); |
| | 16488 | 109 | | if (TryGetValue(key, out TValue prior) && ReferenceEquals(prior, value)) |
| | 1 | 110 | | return; |
| | 16487 | 111 | | bool added = false; |
| | 16487 | 112 | | _root = Set(_root, key, value, ref added); |
| | 16487 | 113 | | if (added) |
| | 16389 | 114 | | Count = checked(Count + 1); |
| | 16487 | 115 | | IsChanged = true; |
| | 16487 | 116 | | } |
| | | 117 | | |
| | | 118 | | internal bool Remove(TKey key) |
| | | 119 | | { |
| | 223 | 120 | | EnsureWritable(); |
| | 222 | 121 | | if (!TryGetValue(key, out _)) |
| | 1 | 122 | | return false; |
| | 221 | 123 | | _root = Remove(_root!, key); |
| | 221 | 124 | | Count--; |
| | 221 | 125 | | IsChanged = true; |
| | 221 | 126 | | return true; |
| | | 127 | | } |
| | | 128 | | |
| | | 129 | | internal NavigationSeamEditTree<TKey, TValue> Seal() |
| | | 130 | | { |
| | 1249 | 131 | | EnsureWritable(); |
| | 1249 | 132 | | _sealed = true; |
| | 1249 | 133 | | return IsChanged |
| | 1249 | 134 | | ? new NavigationSeamEditTree<TKey, TValue>(_root, Count, _nodeBytes) |
| | 1249 | 135 | | : _source; |
| | | 136 | | } |
| | | 137 | | |
| | | 138 | | private Node Set(Node? node, TKey key, TValue value, ref bool added) |
| | | 139 | | { |
| | 150303 | 140 | | if (node == null) |
| | | 141 | | { |
| | 16389 | 142 | | added = true; |
| | 16389 | 143 | | return NewNode(key, value, null, null); |
| | | 144 | | } |
| | 133914 | 145 | | node = Own(node); |
| | 133914 | 146 | | int comparison = key.CompareTo(node.Key); |
| | 133914 | 147 | | if (comparison == 0) |
| | | 148 | | { |
| | 98 | 149 | | node.Value = value; |
| | 98 | 150 | | return node; |
| | | 151 | | } |
| | 133816 | 152 | | if (comparison < 0) |
| | 12275 | 153 | | node.Left = Set(node.Left, key, value, ref added); |
| | | 154 | | else |
| | 121541 | 155 | | node.Right = Set(node.Right, key, value, ref added); |
| | 133816 | 156 | | Update(node); |
| | 133816 | 157 | | return Balance(node); |
| | | 158 | | } |
| | | 159 | | |
| | | 160 | | private Node? Remove(Node node, TKey key) |
| | | 161 | | { |
| | 455 | 162 | | int comparison = key.CompareTo(node.Key); |
| | 455 | 163 | | if (comparison == 0) |
| | | 164 | | { |
| | 221 | 165 | | if (node.Left == null || node.Right == null) |
| | | 166 | | { |
| | 220 | 167 | | Node? replacement = node.Left ?? node.Right; |
| | 220 | 168 | | ReleaseIfOwned(node); |
| | 220 | 169 | | return replacement; |
| | | 170 | | } |
| | 1 | 171 | | node = Own(node); |
| | 1 | 172 | | Node successor = FindMinimum(node.Right!); |
| | 1 | 173 | | node.Key = successor.Key; |
| | 1 | 174 | | node.Value = successor.Value; |
| | 1 | 175 | | node.Right = RemoveMinimum(node.Right!); |
| | 1 | 176 | | Update(node); |
| | 1 | 177 | | return Balance(node); |
| | | 178 | | } |
| | 234 | 179 | | node = Own(node); |
| | 234 | 180 | | if (comparison < 0) |
| | 212 | 181 | | node.Left = Remove(node.Left!, key); |
| | | 182 | | else |
| | 22 | 183 | | node.Right = Remove(node.Right!, key); |
| | 234 | 184 | | Update(node); |
| | 234 | 185 | | return Balance(node); |
| | | 186 | | } |
| | | 187 | | |
| | | 188 | | private Node? RemoveMinimum(Node node) |
| | | 189 | | { |
| | 2 | 190 | | if (node.Left == null) |
| | | 191 | | { |
| | 1 | 192 | | Node? replacement = node.Right; |
| | 1 | 193 | | ReleaseIfOwned(node); |
| | 1 | 194 | | return replacement; |
| | | 195 | | } |
| | 1 | 196 | | node = Own(node); |
| | 1 | 197 | | node.Left = RemoveMinimum(node.Left!); |
| | 1 | 198 | | Update(node); |
| | 1 | 199 | | return Balance(node); |
| | | 200 | | } |
| | | 201 | | |
| | | 202 | | private Node Balance(Node node) |
| | | 203 | | { |
| | 134052 | 204 | | int balance = HeightOf(node.Left) - HeightOf(node.Right); |
| | 134052 | 205 | | if (balance > 1) |
| | | 206 | | { |
| | 996 | 207 | | node.Left = Own(node.Left!); |
| | 996 | 208 | | if (HeightOf(node.Left.Left) < HeightOf(node.Left.Right)) |
| | 928 | 209 | | node.Left = RotateLeft(node.Left!); |
| | 996 | 210 | | return RotateRight(node); |
| | | 211 | | } |
| | 133056 | 212 | | if (balance < -1) |
| | | 213 | | { |
| | 12218 | 214 | | node.Right = Own(node.Right!); |
| | 12218 | 215 | | if (HeightOf(node.Right.Right) < HeightOf(node.Right.Left)) |
| | 187 | 216 | | node.Right = RotateRight(node.Right!); |
| | 12218 | 217 | | return RotateLeft(node); |
| | | 218 | | } |
| | 120838 | 219 | | return node; |
| | | 220 | | } |
| | | 221 | | |
| | | 222 | | private Node RotateLeft(Node node) |
| | | 223 | | { |
| | 13146 | 224 | | Node right = Own(node.Right!); |
| | 13146 | 225 | | node.Right = right.Left; |
| | 13146 | 226 | | Update(node); |
| | 13146 | 227 | | right.Left = node; |
| | 13146 | 228 | | Update(right); |
| | 13146 | 229 | | return right; |
| | | 230 | | } |
| | | 231 | | |
| | | 232 | | private Node RotateRight(Node node) |
| | | 233 | | { |
| | 1183 | 234 | | Node left = Own(node.Left!); |
| | 1183 | 235 | | node.Left = left.Right; |
| | 1183 | 236 | | Update(node); |
| | 1183 | 237 | | left.Right = node; |
| | 1183 | 238 | | Update(left); |
| | 1183 | 239 | | return left; |
| | | 240 | | } |
| | | 241 | | |
| | | 242 | | private Node Own(Node node) |
| | | 243 | | { |
| | 161693 | 244 | | if (node.OwnerToken == _token) |
| | 161495 | 245 | | return node; |
| | 198 | 246 | | return NewNode(node.Key, node.Value, node.Left, node.Right); |
| | | 247 | | } |
| | | 248 | | |
| | | 249 | | private Node NewNode(TKey key, TValue value, Node? left, Node? right) |
| | | 250 | | { |
| | 16587 | 251 | | OwnedNodeCount = checked(OwnedNodeCount + 1); |
| | 16587 | 252 | | return new Node(key, value, left, right, _token); |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | private void ReleaseIfOwned(Node node) |
| | | 256 | | { |
| | 221 | 257 | | if (node.OwnerToken == _token) |
| | 57 | 258 | | OwnedNodeCount--; |
| | 221 | 259 | | } |
| | | 260 | | |
| | | 261 | | private void EnsureWritable() |
| | | 262 | | { |
| | 17961 | 263 | | SwiftThrowHelper.ThrowIfTrue( |
| | 17961 | 264 | | _sealed, |
| | 17961 | 265 | | message: "The seam edit session is already sealed."); |
| | 17959 | 266 | | } |
| | | 267 | | } |
| | | 268 | | |
| | | 269 | | internal sealed class Cursor |
| | | 270 | | { |
| | | 271 | | private readonly Node?[] _stack; |
| | | 272 | | private readonly int _shellBytes; |
| | | 273 | | private int _count; |
| | | 274 | | private TKey _maximum; |
| | | 275 | | private bool _hasMaximum; |
| | | 276 | | |
| | 631 | 277 | | internal Cursor(int maximumHeight, int shellBytes) |
| | | 278 | | { |
| | 631 | 279 | | SwiftThrowHelper.ThrowIfArgumentOutOfRange( |
| | 631 | 280 | | maximumHeight <= 0, |
| | 631 | 281 | | maximumHeight, |
| | 631 | 282 | | nameof(maximumHeight)); |
| | 631 | 283 | | _stack = new Node?[maximumHeight]; |
| | 631 | 284 | | _shellBytes = shellBytes; |
| | 631 | 285 | | } |
| | | 286 | | |
| | 168781 | 287 | | internal long RetainedBytes => checked( |
| | 168781 | 288 | | _shellBytes + 24L + ((long)_stack.Length * 8L)); |
| | | 289 | | |
| | 168772 | 290 | | internal int PersistentPageCount => 2; |
| | | 291 | | |
| | | 292 | | internal TValue Current { get; private set; } = null!; |
| | | 293 | | |
| | | 294 | | internal TKey CurrentKey { get; private set; } |
| | | 295 | | |
| | 9010 | 296 | | internal bool HasNext => _count != 0; |
| | | 297 | | |
| | | 298 | | internal void Begin( |
| | | 299 | | NavigationSeamEditTree<TKey, TValue> tree, |
| | | 300 | | TKey minimum, |
| | | 301 | | TKey maximum) |
| | | 302 | | { |
| | 1 | 303 | | ClearStack(); |
| | 1 | 304 | | _maximum = maximum; |
| | 1 | 305 | | _hasMaximum = true; |
| | 1 | 306 | | PushLowerBound(tree._root, minimum); |
| | 1 | 307 | | Current = null!; |
| | 1 | 308 | | CurrentKey = default; |
| | 1 | 309 | | } |
| | | 310 | | |
| | | 311 | | internal void BeginAll(NavigationSeamEditTree<TKey, TValue> tree) |
| | | 312 | | { |
| | 519 | 313 | | ClearStack(); |
| | 519 | 314 | | _hasMaximum = false; |
| | 519 | 315 | | PushLeft(tree._root); |
| | 518 | 316 | | Current = null!; |
| | 518 | 317 | | CurrentKey = default; |
| | 518 | 318 | | } |
| | | 319 | | |
| | | 320 | | internal void BeginAtLeast( |
| | | 321 | | NavigationSeamEditTree<TKey, TValue> tree, |
| | | 322 | | TKey minimum) |
| | | 323 | | { |
| | 113 | 324 | | ClearStack(); |
| | 113 | 325 | | _hasMaximum = false; |
| | 113 | 326 | | PushLowerBound(tree._root, minimum); |
| | 113 | 327 | | Current = null!; |
| | 113 | 328 | | CurrentKey = default; |
| | 113 | 329 | | } |
| | | 330 | | |
| | | 331 | | internal bool MoveNext() |
| | | 332 | | { |
| | 8278 | 333 | | if (_count == 0) |
| | | 334 | | { |
| | 5 | 335 | | Current = null!; |
| | 5 | 336 | | CurrentKey = default; |
| | 5 | 337 | | return false; |
| | | 338 | | } |
| | 8273 | 339 | | Node node = _stack[--_count]!; |
| | 8273 | 340 | | _stack[_count] = null; |
| | 8273 | 341 | | if (_hasMaximum && node.Key.CompareTo(_maximum) >= 0) |
| | | 342 | | { |
| | 1 | 343 | | ClearStack(); |
| | 1 | 344 | | Current = null!; |
| | 1 | 345 | | CurrentKey = default; |
| | 1 | 346 | | return false; |
| | | 347 | | } |
| | 8272 | 348 | | PushLeft(node.Right); |
| | 8272 | 349 | | CurrentKey = node.Key; |
| | 8272 | 350 | | Current = node.Value; |
| | 8272 | 351 | | return true; |
| | | 352 | | } |
| | | 353 | | |
| | | 354 | | private void PushLowerBound(Node? node, TKey minimum) |
| | | 355 | | { |
| | 198 | 356 | | while (node != null) |
| | | 357 | | { |
| | 84 | 358 | | if (node.Key.CompareTo(minimum) < 0) |
| | | 359 | | { |
| | 30 | 360 | | node = node.Right; |
| | 30 | 361 | | continue; |
| | | 362 | | } |
| | 54 | 363 | | Push(node); |
| | 54 | 364 | | node = node.Left; |
| | | 365 | | } |
| | 114 | 366 | | } |
| | | 367 | | |
| | | 368 | | private void PushLeft(Node? node) |
| | | 369 | | { |
| | 17024 | 370 | | while (node != null) |
| | | 371 | | { |
| | 8234 | 372 | | Push(node); |
| | 8233 | 373 | | node = node.Left; |
| | | 374 | | } |
| | 8790 | 375 | | } |
| | | 376 | | |
| | | 377 | | private void Push(Node node) |
| | | 378 | | { |
| | 8288 | 379 | | SwiftThrowHelper.ThrowIfTrue( |
| | 8288 | 380 | | _count == _stack.Length, |
| | 8288 | 381 | | message: "The configured seam tree height was exceeded."); |
| | 8287 | 382 | | _stack[_count++] = node; |
| | 8287 | 383 | | } |
| | | 384 | | |
| | | 385 | | private void ClearStack() |
| | | 386 | | { |
| | 637 | 387 | | while (_count != 0) |
| | 3 | 388 | | _stack[--_count] = null; |
| | 634 | 389 | | } |
| | | 390 | | } |
| | | 391 | | |
| | | 392 | | private static Node FindMinimum(Node node) |
| | | 393 | | { |
| | 2 | 394 | | while (node.Left != null) |
| | 1 | 395 | | node = node.Left; |
| | 1 | 396 | | return node; |
| | | 397 | | } |
| | | 398 | | |
| | | 399 | | private static void Update(Node node) |
| | | 400 | | { |
| | 179297 | 401 | | node.Height = Math.Max(HeightOf(node.Left), HeightOf(node.Right)) + 1; |
| | 179297 | 402 | | node.Count = CountOf(node.Left) + CountOf(node.Right) + 1; |
| | 179297 | 403 | | } |
| | | 404 | | |
| | 653126 | 405 | | private static int HeightOf(Node? node) => node?.Height ?? 0; |
| | | 406 | | |
| | 358594 | 407 | | private static int CountOf(Node? node) => node?.Count ?? 0; |
| | | 408 | | |
| | | 409 | | private sealed class Node |
| | | 410 | | { |
| | 16587 | 411 | | internal Node( |
| | 16587 | 412 | | TKey key, |
| | 16587 | 413 | | TValue value, |
| | 16587 | 414 | | Node? left, |
| | 16587 | 415 | | Node? right, |
| | 16587 | 416 | | long ownerToken) |
| | | 417 | | { |
| | 16587 | 418 | | Key = key; |
| | 16587 | 419 | | Value = value; |
| | 16587 | 420 | | Left = left; |
| | 16587 | 421 | | Right = right; |
| | 16587 | 422 | | OwnerToken = ownerToken; |
| | 16587 | 423 | | Update(this); |
| | 16587 | 424 | | } |
| | | 425 | | |
| | | 426 | | internal TKey Key; |
| | | 427 | | internal TValue Value; |
| | | 428 | | internal Node? Left; |
| | | 429 | | internal Node? Right; |
| | | 430 | | internal long OwnerToken; |
| | | 431 | | internal int Height; |
| | | 432 | | internal int Count; |
| | | 433 | | } |
| | | 434 | | } |