diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Internal/DataStructures/SubscriptionIndex.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/DataStructures/SubscriptionIndex.cs index 8cb1bdc..0a5d6db 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/Internal/DataStructures/SubscriptionIndex.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/DataStructures/SubscriptionIndex.cs @@ -58,7 +58,7 @@ public sealed class SubscriptionIndex private long _cacheHits; private long _inserts; private long _removes; - private SublistLevel _root; + private Level _root; private Dictionary? _cache; private int _ccSweep; private NotifyMaps? _notify; @@ -70,7 +70,7 @@ public sealed class SubscriptionIndex private SubscriptionIndex(bool enableCache) { - _root = new SublistLevel(); + _root = new Level(); _cache = enableCache ? new Dictionary() : null; } @@ -116,7 +116,7 @@ public sealed class SubscriptionIndex try { bool sfwc = false, haswc = false, isnew = false; - SublistNode? n = null; + Node? n = null; var l = _root; var start = 0; @@ -135,7 +135,7 @@ public sealed class SubscriptionIndex { if (!l.Nodes.TryGetValue(t, out n)) { - n = new SublistNode(); + n = new Node(); l.Nodes[t] = n; } } @@ -148,7 +148,7 @@ public sealed class SubscriptionIndex haswc = true; if (n == null) { - n = new SublistNode(); + n = new Node(); l.Pwc = n; } break; @@ -158,21 +158,21 @@ public sealed class SubscriptionIndex sfwc = true; if (n == null) { - n = new SublistNode(); + n = new Node(); l.Fwc = n; } break; default: if (!l.Nodes.TryGetValue(t, out n)) { - n = new SublistNode(); + n = new Node(); l.Nodes[t] = n; } break; } } - n.Next ??= new SublistLevel(); + n.Next ??= new Level(); l = n.Next; start = i + 1; @@ -446,7 +446,7 @@ public sealed class SubscriptionIndex try { bool sfwc = false, haswc = false; - SublistNode? n = null; + Node? n = null; var l = _root; var lnts = new LevelNodeToken[32]; @@ -535,7 +535,7 @@ public sealed class SubscriptionIndex } } - private static (bool found, bool last) RemoveFromNode(SublistNode? n, Subscription sub) + private static (bool found, bool last) RemoveFromNode(Node? n, Subscription sub) { if (n == null) return (false, true); @@ -743,9 +743,9 @@ public sealed class SubscriptionIndex // Private: Trie matching (matchLevel) // ------------------------------------------------------------------------- - private static void MatchLevel(SublistLevel? l, string[] toks, SubscriptionIndexResult results) + private static void MatchLevel(Level? l, string[] toks, SubscriptionIndexResult results) { - SublistNode? pwc = null, n = null; + Node? pwc = null, n = null; for (int i = 0; i < toks.Length; i++) { if (l == null) return; @@ -760,9 +760,9 @@ public sealed class SubscriptionIndex if (pwc != null) AddNodeToResults(pwc, results); } - private static bool MatchLevelForAny(SublistLevel? l, ReadOnlySpan toks, ref int np, ref int nq) + private static bool MatchLevelForAny(Level? l, ReadOnlySpan toks, ref int np, ref int nq) { - SublistNode? pwc = null, n = null; + Node? pwc = null, n = null; for (int i = 0; i < toks.Length; i++) { if (l == null) return false; @@ -804,7 +804,7 @@ public sealed class SubscriptionIndex // Private: Reverse match // ------------------------------------------------------------------------- - private static void ReverseMatchLevel(SublistLevel? l, ReadOnlySpan toks, SublistNode? n, SubscriptionIndexResult results) + private static void ReverseMatchLevel(Level? l, ReadOnlySpan toks, Node? n, SubscriptionIndexResult results) { if (l == null) return; for (int i = 0; i < toks.Length; i++) @@ -848,7 +848,7 @@ public sealed class SubscriptionIndex if (n != null) AddNodeToResults(n, results); } - private static void GetAllNodes(SublistLevel? l, SubscriptionIndexResult results) + private static void GetAllNodes(Level? l, SubscriptionIndexResult results) { if (l == null) return; if (l.Pwc != null) AddNodeToResults(l.Pwc, results); @@ -864,7 +864,7 @@ public sealed class SubscriptionIndex // Private: addNodeToResults // ------------------------------------------------------------------------- - private static void AddNodeToResults(SublistNode n, SubscriptionIndexResult results) + private static void AddNodeToResults(Node n, SubscriptionIndexResult results) { // Plain subscriptions. if (n.PList != null) @@ -940,7 +940,7 @@ public sealed class SubscriptionIndex } } - private static void AddNodeToSubsLocal(SublistNode n, List subs, bool includeLeafHubs) + private static void AddNodeToSubsLocal(Node n, List subs, bool includeLeafHubs) { if (n.PList != null) { @@ -960,7 +960,7 @@ public sealed class SubscriptionIndex } } - private static void CollectLocalSubs(SublistLevel? l, List subs, bool includeLeafHubs) + private static void CollectLocalSubs(Level? l, List subs, bool includeLeafHubs) { if (l == null) return; foreach (var n in l.Nodes.Values) @@ -972,7 +972,7 @@ public sealed class SubscriptionIndex if (l.Fwc != null) { AddNodeToSubsLocal(l.Fwc, subs, includeLeafHubs); CollectLocalSubs(l.Fwc.Next, subs, includeLeafHubs); } } - private static void AddAllNodeToSubs(SublistNode n, List subs) + private static void AddAllNodeToSubs(Node n, List subs) { if (n.PList != null) subs.AddRange(n.PList); @@ -986,7 +986,7 @@ public sealed class SubscriptionIndex subs.Add(sub); } - private static void CollectAllSubs(SublistLevel? l, List subs) + private static void CollectAllSubs(Level? l, List subs) { if (l == null) return; foreach (var n in l.Nodes.Values) @@ -1204,7 +1204,7 @@ public sealed class SubscriptionIndex // Private: visitLevel (depth calculation for tests) // ------------------------------------------------------------------------- - private static int VisitLevel(SublistLevel? l, int depth) + private static int VisitLevel(Level? l, int depth) { if (l == null || l.NumNodes() == 0) return depth; depth++; @@ -1529,18 +1529,18 @@ public sealed class SubscriptionIndex } // ------------------------------------------------------------------------- - // Nested types: SublistNode, SublistLevel, NotifyMaps + // Nested types: Node, Level, NotifyMaps // ------------------------------------------------------------------------- - internal sealed class SublistNode + internal sealed class Node { public readonly Dictionary PSubs = new(ReferenceEqualityComparer.Instance); public Dictionary>? QSubs; public List? PList; - public SublistLevel? Next; + public Level? Next; /// Factory method matching Go's newNode(). - public static SublistNode NewNode() => new(); + public static Node NewNode() => new(); public bool IsEmpty() { @@ -1549,14 +1549,14 @@ public sealed class SubscriptionIndex } } - internal sealed class SublistLevel + internal sealed class Level { - public readonly Dictionary Nodes = new(); - public SublistNode? Pwc; - public SublistNode? Fwc; + public readonly Dictionary Nodes = new(); + public Node? Pwc; + public Node? Fwc; /// Factory method matching Go's newLevel(). - public static SublistLevel NewLevel() => new(); + public static Level NewLevel() => new(); public int NumNodes() { @@ -1566,8 +1566,9 @@ public sealed class SubscriptionIndex return num; } - public void PruneNode(SublistNode n, string t) + public void PruneNode(Node? n, string t) { + if (n == null) return; if (ReferenceEquals(n, Fwc)) Fwc = null; else if (ReferenceEquals(n, Pwc)) Pwc = null; else Nodes.Remove(t); @@ -1582,11 +1583,11 @@ public sealed class SubscriptionIndex private readonly struct LevelNodeToken { - public readonly SublistLevel Level; - public readonly SublistNode Node; + public readonly Level Level; + public readonly Node Node; public readonly string Token; - public LevelNodeToken(SublistLevel level, SublistNode node, string token) + public LevelNodeToken(Level level, Node node, string token) { Level = level; Node = node; diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/DataStructures/SubscriptionIndexTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/DataStructures/SubscriptionIndexTests.cs index fe11062..cdbc26e 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/DataStructures/SubscriptionIndexTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/DataStructures/SubscriptionIndexTests.cs @@ -219,6 +219,51 @@ public class SubscriptionIndexTests s.NumLevels().ShouldBe(0); } + [Fact] + public void PruneNode_NullNode_DoesNotMutateLiteralNodes() + { + var level = new SubscriptionIndex.Level(); + var literal = new SubscriptionIndex.Node(); + level.Nodes["foo"] = literal; + level.Fwc = new SubscriptionIndex.Node(); + level.Pwc = new SubscriptionIndex.Node(); + + level.PruneNode(null!, "foo"); + + level.Nodes.Count.ShouldBe(1); + level.Nodes["foo"].ShouldBeSameAs(literal); + } + + [Fact] + public void IsEmpty_WithAndWithoutChildren_TracksNodeEmptiness() + { + var node = new SubscriptionIndex.Node(); + node.IsEmpty().ShouldBeTrue(); + + node.Next = new SubscriptionIndex.Level(); + node.Next.Nodes["bar"] = new SubscriptionIndex.Node(); + node.IsEmpty().ShouldBeFalse(); + + node.Next.Nodes.Clear(); + node.IsEmpty().ShouldBeTrue(); + } + + [Fact] + public void NumNodes_WithLiteralAndWildcardEntries_CountsAllBranches() + { + var level = new SubscriptionIndex.Level(); + level.Nodes["foo"] = new SubscriptionIndex.Node(); + level.Pwc = new SubscriptionIndex.Node(); + level.Fwc = new SubscriptionIndex.Node(); + + level.NumNodes().ShouldBe(3); + + level.PruneNode(level.Pwc, SubscriptionIndex.Pwcs); + level.PruneNode(level.Fwc, SubscriptionIndex.Fwcs); + level.PruneNode(level.Nodes["foo"], "foo"); + level.NumNodes().ShouldBe(0); + } + [Theory] // T:2983, T:2984 [InlineData(true)] [InlineData(false)] diff --git a/porting.db b/porting.db index 424aa65..6bbe23b 100644 Binary files a/porting.db and b/porting.db differ diff --git a/reports/current.md b/reports/current.md new file mode 100644 index 0000000..506c986 --- /dev/null +++ b/reports/current.md @@ -0,0 +1,37 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-28 12:11:43 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| verified | 12 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| deferred | 2361 | +| n_a | 24 | +| stub | 1 | +| verified | 1287 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| deferred | 2091 | +| n_a | 187 | +| verified | 979 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**2489/6942 items complete (35.9%)** diff --git a/reports/report_38b6fc8.md b/reports/report_38b6fc8.md new file mode 100644 index 0000000..506c986 --- /dev/null +++ b/reports/report_38b6fc8.md @@ -0,0 +1,37 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-28 12:11:43 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| verified | 12 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| deferred | 2361 | +| n_a | 24 | +| stub | 1 | +| verified | 1287 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| deferred | 2091 | +| n_a | 187 | +| verified | 979 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**2489/6942 items complete (35.9%)**