// 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