#nullable enable using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; namespace GameCult.Networking.Tests { /// /// Ordered delivery has one owner: the contiguous received-through watermark. A frame is held for /// exactly one reason, a reliable sequence below it has not arrived, and it is delivered in the call /// that fills the last such gap, whichever channel the gap belonged to. /// public sealed class CultNetRudpOrderedDeliveryTests { private static (CultNetRudpSession Client, CultNetRudpSession Server) Handshake() { var client = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 410, InitialSequence = 1 }); var server = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 410, InitialSequence = 500 }); var accept = server.AcceptConnect(client.CreateConnect(0), 0); client.Receive(accept, 0); return (client, server); } private static CultNetRudpSendOptions Reliable(bool ordered) => new CultNetRudpSendOptions { Reliable = true, Ordered = ordered }; private static CultNetRudpPacket Send(CultNetRudpSession session, string channel, string payload, bool ordered) => session.Send(channel, Encoding.UTF8.GetBytes(payload), Reliable(ordered)); private static string[] Names(CultNetRudpReceiveResult result) => result.Delivered.Select(frame => Encoding.UTF8.GetString(frame.Payload)).ToArray(); [Test] public void OrderedFrameWaitsForAGapFilledByAnotherChannel() { var (sender, receiver) = Handshake(); var o1 = Send(sender, "schema", "o1", true); var u = Send(sender, "rel", "u", false); var o2 = Send(sender, "schema", "o2", true); var o3 = Send(sender, "schema", "o3", true); var o4 = Send(sender, "schema", "o4", true); Assert.That(Names(receiver.Receive(o1, 1)), Is.EqualTo(new[] { "o1" })); Assert.That(Names(receiver.Receive(o2, 2)), Is.Empty, "u is missing"); Assert.That(Names(receiver.Receive(o3, 3)), Is.Empty, "u is missing"); Assert.That(Names(receiver.Receive(o4, 4)), Is.Empty, "u is missing"); Assert.That(Names(receiver.Receive(u, 5)), Is.EqualTo(new[] { "u", "o2", "o3", "o4" })); } [Test] public void TwoOrderedChannelsReleaseEachOtherInSequenceOrder() { var (sender, receiver) = Handshake(); var a1 = Send(sender, "schema", "A1", true); var b1 = Send(sender, "other", "B1", true); var a2 = Send(sender, "schema", "A2", true); var a3 = Send(sender, "schema", "A3", true); var delivered = new List(); delivered.AddRange(Names(receiver.Receive(a1, 1))); delivered.AddRange(Names(receiver.Receive(a2, 2))); delivered.AddRange(Names(receiver.Receive(a3, 3))); Assert.That(delivered, Is.EqualTo(new[] { "A1" }), "B1 is missing, so A2 and A3 wait"); Assert.That(Names(receiver.Receive(b1, 4)), Is.EqualTo(new[] { "B1", "A2", "A3" })); } [Test] public void ChannelFirstUsedAfterOtherTrafficLosesNothing() { var (sender, receiver) = Handshake(); var c1 = Send(sender, "late", "C1", true); var c2 = Send(sender, "late", "C2", true); var x = Send(sender, "schema", "X", true); Assert.That(Names(receiver.Receive(x, 1)), Is.Empty, "C1 is missing"); Assert.That(Names(receiver.Receive(c2, 2)), Is.Empty, "C1 is missing"); Assert.That(Names(receiver.Receive(c1, 3)), Is.EqualTo(new[] { "C1", "C2", "X" })); } [Test] public void AcceptSeedsTheWatermarkOfTheConnectingSide() { var (client, server) = Handshake(); var s1 = Send(server, "schema", "s1", true); var s2 = Send(server, "schema", "s2", true); Assert.That(Names(client.Receive(s2, 1)), Is.Empty); Assert.That(Names(client.Receive(s1, 2)), Is.EqualTo(new[] { "s1", "s2" })); } [Test] public void ConnectSeedsTheWatermarkOfTheAcceptingSide() { var (client, server) = Handshake(); var first = Send(client, "schema", "first", true); var second = Send(client, "schema", "second", true); Assert.That(Names(server.Receive(second, 1)), Is.Empty); Assert.That(Names(server.Receive(first, 2)), Is.EqualTo(new[] { "first", "second" })); } [Test] public void DuplicateOfAHeldFrameIsNotDeliveredTwice() { var (sender, receiver) = Handshake(); var s1 = Send(sender, "schema", "s1", true); var s2 = Send(sender, "schema", "s2", true); Assert.That(Names(receiver.Receive(s2, 1)), Is.Empty); Assert.That(Names(receiver.Receive(s2, 2)), Is.Empty); Assert.That(Names(receiver.Receive(s1, 3)), Is.EqualTo(new[] { "s1", "s2" })); Assert.That(Names(receiver.Receive(s2, 4)), Is.Empty); } [Test] public void ResetPeerStateForgetsHeldFramesAndReseedsTheWatermarkFromTheNextHandshake() { var (sender, receiver) = Handshake(); Send(sender, "schema", "s1", true); var s2 = Send(sender, "schema", "s2", true); Assert.That(Names(receiver.Receive(s2, 1)), Is.Empty); receiver.ResetPeerState(); var next = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 410, InitialSequence = 100 }); next.Receive(receiver.AcceptConnect(next.CreateConnect(0), 2), 2); var d1 = Send(next, "schema", "d1", true); var d2 = Send(next, "schema", "d2", true); // The held s2 died with the reset, and the watermark follows the new peer. Assert.That(Names(receiver.Receive(d2, 3)), Is.Empty); Assert.That(Names(receiver.Receive(d1, 4)), Is.EqualTo(new[] { "d1", "d2" })); } } }