// Go reference: server/stree/node.go, leaf.go, node4.go, node10.go, node16.go, node48.go, node256.go namespace NATS.Server.Internal.SubjectTree; /// /// Internal node interface for the Adaptive Radix Tree. /// internal interface INode { /// /// Gets whether this node is a terminal subject node that directly stores a subscription value. /// bool IsLeaf { get; } /// /// Gets structural metadata for branch nodes, including compressed path prefix and child count. /// NodeMeta? Base { get; } /// /// Sets the compressed path fragment represented by this node. /// /// Subject bytes shared by all descendants below this node. void SetPrefix(ReadOnlySpan pre); /// /// Adds a child edge for the next subject byte in the adaptive radix tree. /// /// Subject byte used to route lookups to the child node. /// Child node that owns the remaining subject suffix for this edge. void AddChild(byte c, INode n); /// /// Returns the child node for the given key byte, or null if not found. /// The returned wrapper allows in-place replacement of the child reference. /// /// Subject byte to look up in the node's child index. ChildRef? FindChild(byte c); /// /// Removes the child edge for the provided subject byte. /// /// Subject byte whose child mapping should be removed. void DeleteChild(byte c); /// /// Gets whether this node has reached its capacity and must grow to the next node shape. /// bool IsFull { get; } /// /// Expands this node to a larger branching factor to accept more distinct subject bytes. /// INode Grow(); /// /// Attempts to shrink this node to a smaller branching representation when sparse. /// INode? Shrink(); /// /// Matches a subject split into tokens against this node's compressed path fragment. /// /// Remaining subject tokens to match from this node downward. /// The remaining tokens after consuming this node, and whether the fragment matched. (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts); /// /// Gets a short node kind name used by diagnostics and debugging tools. /// string Kind { get; } /// /// Iterates child nodes until the callback returns . /// /// Callback invoked for each child node in this branch. void Iter(Func f); /// /// Returns the current child nodes for traversal or inspection. /// INode?[] Children(); /// /// Gets the number of active child edges in this node. /// ushort NumChildren { get; } /// /// Gets the compressed path bytes represented by this node. /// byte[] Path(); } /// /// Wrapper that allows in-place replacement of a child reference in a node. /// This is analogous to Go's *node pointer. /// internal sealed class ChildRef(Func getter, Action setter) { /// /// Gets or replaces the child node reference stored at a specific branch slot. /// public INode? Node { get => getter(); set => setter(value); } } /// /// Base metadata for internal (non-leaf) nodes. /// internal sealed class NodeMeta { /// /// Gets or sets the compressed subject prefix shared by descendants of this branch node. /// public byte[] Prefix { get; set; } = []; /// /// Gets or sets the number of child edges currently populated for this branch node. /// public ushort Size { get; set; } } #region Leaf Node /// /// Leaf node holding a value and suffix. /// Go reference: server/stree/leaf.go /// internal sealed class Leaf : INode { public T Value; public byte[] Suffix; /// /// Initializes a terminal subject-tree node that stores a value for an exact suffix match. /// /// Remaining subject bytes that must match to resolve this leaf. /// Subscription payload or state associated with the matched subject. public Leaf(ReadOnlySpan suffix, T value) { Value = value; Suffix = Parts.CopyBytes(suffix); } /// public bool IsLeaf => true; /// public NodeMeta? Base => null; /// public bool IsFull => true; /// public ushort NumChildren => 0; /// public string Kind => "LEAF"; /// /// Checks whether the provided subject bytes exactly match this leaf suffix. /// /// Subject bytes remaining after traversing parent branch prefixes. /// when the subject resolves to this exact leaf. public bool Match(ReadOnlySpan subject) => subject.SequenceEqual(Suffix); /// /// Replaces the stored suffix when leaf content is split or merged during tree updates. /// /// New exact-match suffix bytes for this leaf. public void SetSuffix(ReadOnlySpan suffix) => Suffix = Parts.CopyBytes(suffix); /// public byte[] Path() => Suffix; /// public INode?[] Children() => []; /// public void Iter(Func f) { } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Suffix); // These should not be called on a leaf. /// public void SetPrefix(ReadOnlySpan pre) => throw new InvalidOperationException("setPrefix called on leaf"); /// public void AddChild(byte c, INode n) => throw new InvalidOperationException("addChild called on leaf"); /// public ChildRef? FindChild(byte c) => throw new InvalidOperationException("findChild called on leaf"); /// public INode Grow() => throw new InvalidOperationException("grow called on leaf"); /// public void DeleteChild(byte c) => throw new InvalidOperationException("deleteChild called on leaf"); /// public INode? Shrink() => throw new InvalidOperationException("shrink called on leaf"); } #endregion #region Node4 /// /// Node with up to 4 children. /// Go reference: server/stree/node4.go /// internal sealed class Node4 : INode { private readonly INode?[] _child = new INode?[4]; private readonly byte[] _key = new byte[4]; internal readonly NodeMeta Meta = new(); /// /// Initializes a small branch node for up to four subject-byte fan-out edges. /// /// Compressed subject prefix represented by this branch. public Node4(ReadOnlySpan prefix) { SetPrefix(prefix); } /// public bool IsLeaf => false; /// public NodeMeta? Base => Meta; /// public ushort NumChildren => Meta.Size; /// public bool IsFull => Meta.Size >= 4; /// public string Kind => "NODE4"; /// public byte[] Path() => Meta.Prefix; /// public void SetPrefix(ReadOnlySpan pre) { Meta.Prefix = pre.ToArray(); } /// public void AddChild(byte c, INode n) { if (Meta.Size >= 4) throw new InvalidOperationException("node4 full!"); _key[Meta.Size] = c; _child[Meta.Size] = n; Meta.Size++; } /// public ChildRef? FindChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var idx = i; return new ChildRef(() => _child[idx], v => _child[idx] = v); } } return null; } /// public void DeleteChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var last = Meta.Size - 1; if (i < last) { _key[i] = _key[last]; _child[i] = _child[last]; _key[last] = 0; _child[last] = null; } else { _key[i] = 0; _child[i] = null; } Meta.Size--; return; } } } /// public INode Grow() { var nn = new Node10(Meta.Prefix); for (int i = 0; i < 4; i++) { nn.AddChild(_key[i], _child[i]!); } return nn; } /// public INode? Shrink() { if (Meta.Size == 1) return _child[0]; return null; } /// public void Iter(Func f) { for (int i = 0; i < Meta.Size; i++) { if (!f(_child[i]!)) return; } } /// public INode?[] Children() { var result = new INode?[Meta.Size]; Array.Copy(_child, result, Meta.Size); return result; } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Meta.Prefix); } #endregion #region Node10 /// /// Node with up to 10 children. Optimized for numeric subject tokens (0-9). /// Go reference: server/stree/node10.go /// internal sealed class Node10 : INode { private readonly INode?[] _child = new INode?[10]; private readonly byte[] _key = new byte[10]; internal readonly NodeMeta Meta = new(); /// /// Initializes a branch node tuned for numeric token fan-out, common in ordered stream subjects. /// /// Compressed subject prefix represented by this branch. public Node10(ReadOnlySpan prefix) { SetPrefix(prefix); } /// public bool IsLeaf => false; /// public NodeMeta? Base => Meta; /// public ushort NumChildren => Meta.Size; /// public bool IsFull => Meta.Size >= 10; /// public string Kind => "NODE10"; /// public byte[] Path() => Meta.Prefix; /// public void SetPrefix(ReadOnlySpan pre) { Meta.Prefix = pre.ToArray(); } /// public void AddChild(byte c, INode n) { if (Meta.Size >= 10) throw new InvalidOperationException("node10 full!"); _key[Meta.Size] = c; _child[Meta.Size] = n; Meta.Size++; } /// public ChildRef? FindChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var idx = i; return new ChildRef(() => _child[idx], v => _child[idx] = v); } } return null; } /// public void DeleteChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var last = Meta.Size - 1; if (i < last) { _key[i] = _key[last]; _child[i] = _child[last]; _key[last] = 0; _child[last] = null; } else { _key[i] = 0; _child[i] = null; } Meta.Size--; return; } } } /// public INode Grow() { var nn = new Node16(Meta.Prefix); for (int i = 0; i < 10; i++) { nn.AddChild(_key[i], _child[i]!); } return nn; } /// public INode? Shrink() { if (Meta.Size > 4) return null; var nn = new Node4([]); for (int i = 0; i < Meta.Size; i++) { nn.AddChild(_key[i], _child[i]!); } return nn; } /// public void Iter(Func f) { for (int i = 0; i < Meta.Size; i++) { if (!f(_child[i]!)) return; } } /// public INode?[] Children() { var result = new INode?[Meta.Size]; Array.Copy(_child, result, Meta.Size); return result; } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Meta.Prefix); } #endregion #region Node16 /// /// Node with up to 16 children. /// Go reference: server/stree/node16.go /// internal sealed class Node16 : INode { private readonly INode?[] _child = new INode?[16]; private readonly byte[] _key = new byte[16]; internal readonly NodeMeta Meta = new(); /// /// Initializes a medium branch node for moderate subject fan-out without index indirection. /// /// Compressed subject prefix represented by this branch. public Node16(ReadOnlySpan prefix) { SetPrefix(prefix); } /// public bool IsLeaf => false; /// public NodeMeta? Base => Meta; /// public ushort NumChildren => Meta.Size; /// public bool IsFull => Meta.Size >= 16; /// public string Kind => "NODE16"; /// public byte[] Path() => Meta.Prefix; /// public void SetPrefix(ReadOnlySpan pre) { Meta.Prefix = pre.ToArray(); } /// public void AddChild(byte c, INode n) { if (Meta.Size >= 16) throw new InvalidOperationException("node16 full!"); _key[Meta.Size] = c; _child[Meta.Size] = n; Meta.Size++; } /// public ChildRef? FindChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var idx = i; return new ChildRef(() => _child[idx], v => _child[idx] = v); } } return null; } /// public void DeleteChild(byte c) { for (int i = 0; i < Meta.Size; i++) { if (_key[i] == c) { var last = Meta.Size - 1; if (i < last) { _key[i] = _key[last]; _child[i] = _child[last]; _key[last] = 0; _child[last] = null; } else { _key[i] = 0; _child[i] = null; } Meta.Size--; return; } } } /// public INode Grow() { var nn = new Node48(Meta.Prefix); for (int i = 0; i < 16; i++) { nn.AddChild(_key[i], _child[i]!); } return nn; } /// public INode? Shrink() { if (Meta.Size > 10) return null; var nn = new Node10([]); for (int i = 0; i < Meta.Size; i++) { nn.AddChild(_key[i], _child[i]!); } return nn; } /// public void Iter(Func f) { for (int i = 0; i < Meta.Size; i++) { if (!f(_child[i]!)) return; } } /// public INode?[] Children() { var result = new INode?[Meta.Size]; Array.Copy(_child, result, Meta.Size); return result; } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Meta.Prefix); } #endregion #region Node48 /// /// Node with up to 48 children. Uses a 256-byte index array (1-indexed) to map keys to child slots. /// Go reference: server/stree/node48.go /// internal sealed class Node48 : INode { internal readonly INode?[] Child = new INode?[48]; internal readonly byte[] Key = new byte[256]; // 1-indexed: 0 means no entry internal readonly NodeMeta Meta = new(); /// /// Initializes a high fan-out branch node that trades memory for faster byte-key lookups. /// /// Compressed subject prefix represented by this branch. public Node48(ReadOnlySpan prefix) { SetPrefix(prefix); } /// public bool IsLeaf => false; /// public NodeMeta? Base => Meta; /// public ushort NumChildren => Meta.Size; /// public bool IsFull => Meta.Size >= 48; /// public string Kind => "NODE48"; /// public byte[] Path() => Meta.Prefix; /// public void SetPrefix(ReadOnlySpan pre) { Meta.Prefix = pre.ToArray(); } /// public void AddChild(byte c, INode n) { if (Meta.Size >= 48) throw new InvalidOperationException("node48 full!"); Child[Meta.Size] = n; Key[c] = (byte)(Meta.Size + 1); // 1-indexed Meta.Size++; } /// public ChildRef? FindChild(byte c) { var i = Key[c]; if (i == 0) return null; var idx = i - 1; return new ChildRef(() => Child[idx], v => Child[idx] = v); } /// public void DeleteChild(byte c) { var i = Key[c]; if (i == 0) return; i--; // Adjust for 1-indexing var last = (byte)(Meta.Size - 1); if (i < last) { Child[i] = Child[last]; for (int ic = 0; ic < 256; ic++) { if (Key[ic] == last + 1) { Key[ic] = (byte)(i + 1); break; } } } Child[last] = null; Key[c] = 0; Meta.Size--; } /// public INode Grow() { var nn = new Node256(Meta.Prefix); for (int c = 0; c < 256; c++) { var i = Key[c]; if (i > 0) { nn.AddChild((byte)c, Child[i - 1]!); } } return nn; } /// public INode? Shrink() { if (Meta.Size > 16) return null; var nn = new Node16([]); for (int c = 0; c < 256; c++) { var i = Key[c]; if (i > 0) { nn.AddChild((byte)c, Child[i - 1]!); } } return nn; } /// public void Iter(Func f) { foreach (var c in Child) { if (c != null && !f(c)) return; } } /// public INode?[] Children() { var result = new INode?[Meta.Size]; Array.Copy(Child, result, Meta.Size); return result; } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Meta.Prefix); } #endregion #region Node256 /// /// Node with up to 256 children. Direct array indexed by byte value. /// Go reference: server/stree/node256.go /// internal sealed class Node256 : INode { internal readonly INode?[] Child = new INode?[256]; internal readonly NodeMeta Meta = new(); /// /// Initializes the maximum fan-out branch node with direct byte-to-child indexing. /// /// Compressed subject prefix represented by this branch. public Node256(ReadOnlySpan prefix) { SetPrefix(prefix); } /// public bool IsLeaf => false; /// public NodeMeta? Base => Meta; /// public ushort NumChildren => Meta.Size; /// public bool IsFull => false; // node256 is never full /// public string Kind => "NODE256"; /// public byte[] Path() => Meta.Prefix; /// public void SetPrefix(ReadOnlySpan pre) { Meta.Prefix = pre.ToArray(); } /// public void AddChild(byte c, INode n) { Child[c] = n; Meta.Size++; } /// public ChildRef? FindChild(byte c) { if (Child[c] == null) return null; return new ChildRef(() => Child[c], v => Child[c] = v); } /// public void DeleteChild(byte c) { if (Child[c] != null) { Child[c] = null; Meta.Size--; } } /// public INode Grow() => throw new InvalidOperationException("grow can not be called on node256"); /// public INode? Shrink() { if (Meta.Size > 48) return null; var nn = new Node48([]); for (int c = 0; c < 256; c++) { if (Child[c] != null) { nn.AddChild((byte)c, Child[c]!); } } return nn; } /// public void Iter(Func f) { for (int i = 0; i < 256; i++) { if (Child[i] != null) { if (!f(Child[i]!)) return; } } } /// public INode?[] Children() { // Return the full 256 array, same as Go return (INode?[])Child.Clone(); } /// public (ReadOnlyMemory[] RemainingParts, bool Matched) MatchParts(ReadOnlyMemory[] parts) => Parts.MatchPartsAgainstFragment(parts, Meta.Prefix); } #endregion