Skip to content

Commit 10bc80a

Browse files
authored
Add CircularBuffer data structure (#115)
1 parent aba5ee9 commit 10bc80a

40 files changed

+1101
-302
lines changed

benchmarks/Backdash.Benchmarks.Ping/PingMessageHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Diagnostics;
21
using System.Net;
2+
using Backdash.Core;
33
using Backdash.Network.Client;
44

55
namespace Backdash.Benchmarks.Ping;
@@ -24,6 +24,6 @@ int bytesReceived
2424
_ => throw new ArgumentOutOfRangeException(nameof(message), message, null),
2525
};
2626

27-
Trace.Assert(sender.TrySendTo(from, reply));
27+
ThrowIf.Assert(sender.TrySendTo(from, reply));
2828
}
2929
}

benchmarks/Backdash.Benchmarks/Cases/UdpClientBenchmark.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System.Diagnostics;
21
using System.Net;
32
using Backdash.Benchmarks.Network;
3+
using Backdash.Core;
44

55
#pragma warning disable CS0649, AsyncFixer01, AsyncFixer02
66
// ReSharper disable AccessToDisposedClosure
@@ -44,17 +44,17 @@ void OnProcessed(long count)
4444
IPEndPoint pongerEndpoint = new(IPAddress.Loopback, 9001);
4545
var pongerAddress = pongerEndpoint.Serialize();
4646

47-
Trace.Assert(pinger.TrySendTo(pongerAddress, PingMessage.Ping));
47+
ThrowIf.Assert(pinger.TrySendTo(pongerAddress, PingMessage.Ping));
4848

4949
await Task.WhenAll(
5050
pinger.Start(ct),
5151
ponger.Start(ct)
5252
).ConfigureAwait(false);
5353

5454
pingerHandler.OnProcessed -= OnProcessed;
55-
Trace.Assert(pingerHandler.BadMessages is 0,
55+
ThrowIf.Assert(pingerHandler.BadMessages is 0,
5656
$"** Pinger: {pingerHandler.BadMessages} bad messages");
57-
Trace.Assert(pingerHandler.ProcessedCount >= numberOfSpins,
57+
ThrowIf.Assert(pingerHandler.ProcessedCount >= numberOfSpins,
5858
$"** Pinger incomplete (Expected: >= {numberOfSpins}, Received: {pingerHandler.ProcessedCount})");
5959
}
6060
}

benchmarks/Backdash.Benchmarks/Network/Message.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Diagnostics;
21
using System.Net;
2+
using Backdash.Core;
33
using Backdash.Network.Client;
44

55
#pragma warning disable CS9113 // Parameter is unread.
@@ -34,7 +34,7 @@ public void OnPeerMessage(in PingMessage message, SocketAddress from, int bytesR
3434
_ => throw new ArgumentOutOfRangeException(nameof(message), message, null),
3535
};
3636

37-
Trace.Assert(sender.TrySendTo(from, reply));
37+
ThrowIf.Assert(sender.TrySendTo(from, reply));
3838
OnProcessed(processedCount);
3939
#if DEBUG
4040
Console.WriteLine(

src/Backdash/Backends/BackendServices.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ sealed class BackendServices<TInput> where TInput : unmanaged
2626
public IDelayStrategy DelayStrategy { get; }
2727
public IInputListener<TInput>? InputListener { get; }
2828

29+
public EqualityComparer<TInput> InputComparer { get; }
30+
2931
public BackendServices(NetcodeOptions options, SessionServices<TInput>? services)
3032
{
3133
ChecksumProvider = services?.ChecksumProvider ?? new Fletcher32ChecksumProvider();
@@ -35,6 +37,7 @@ public BackendServices(NetcodeOptions options, SessionServices<TInput>? services
3537
Random = new DefaultRandomNumberGenerator(services?.Random ?? System.Random.Shared);
3638
DelayStrategy = DelayStrategyFactory.Create(Random, options.Protocol.DelayStrategy);
3739
InputGenerator = services?.InputGenerator;
40+
InputComparer = services?.InputComparer ?? EqualityComparer<TInput>.Default;
3841

3942
InputSerializer = services?.InputSerializer ?? BinarySerializerFactory
4043
.FindOrThrow<TInput>(options.UseNetworkEndianness);

src/Backdash/Backends/LocalBackend.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public LocalBackend(NetcodeOptions options, BackendServices<TInput> services)
3737
addedPlayers,
3838
services.StateStore,
3939
services.ChecksumProvider,
40-
new(Max.NumberOfPlayers)
40+
new(Max.NumberOfPlayers),
41+
services.InputComparer
4142
)
4243
{
4344
Callbacks = callbacks,
@@ -177,7 +178,7 @@ public void AdvanceFrame()
177178

178179
public void SetFrameDelay(PlayerHandle player, int delayInFrames)
179180
{
180-
ThrowHelpers.ThrowIfArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
181+
ThrowIf.ArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
181182
ArgumentOutOfRangeException.ThrowIfNegative(delayInFrames);
182183
synchronizer.SetFrameDelay(player, delayInFrames);
183184
}

src/Backdash/Backends/RemoteBackend.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Runtime.CompilerServices;
32
using System.Runtime.InteropServices;
43
using Backdash.Core;
@@ -36,6 +35,8 @@ sealed class RemoteBackend<TInput> : INetcodeSession<TInput>, IProtocolNetworkEv
3635
readonly HashSet<PlayerHandle> addedSpectators = [];
3736
readonly IInputListener<TInput>? inputListener;
3837
public IDeterministicRandom Random { get; }
38+
readonly EqualityComparer<TInput> inputComparer;
39+
readonly EqualityComparer<ConfirmedInputs<TInput>> inputGroupComparer;
3940

4041
bool isSynchronizing = true;
4142
int nextRecommendedInterval;
@@ -59,7 +60,7 @@ BackendServices<TInput> services
5960
ArgumentNullException.ThrowIfNull(options);
6061
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(port);
6162
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(options.FramesPerSecond);
62-
ThrowHelpers.ThrowIfArgumentOutOfBounds(options.SpectatorOffset, min: Max.NumberOfPlayers);
63+
ThrowIf.ArgumentOutOfBounds(options.SpectatorOffset, min: Max.NumberOfPlayers);
6364

6465
this.options = options;
6566
inputSerializer = services.InputSerializer;
@@ -68,6 +69,8 @@ BackendServices<TInput> services
6869
inputListener = services.InputListener;
6970
Random = services.DeterministicRandom;
7071
syncNumber = services.Random.MagicNumber();
72+
inputComparer = services.InputComparer;
73+
inputGroupComparer = ConfirmedInputComparer<TInput>.Create(services.InputComparer);
7174

7275
peerInputEventQueue = new();
7376
peerCombinedInputsEventPublisher = new ProtocolCombinedInputsEventPublisher<TInput>(peerInputEventQueue);
@@ -84,7 +87,8 @@ BackendServices<TInput> services
8487
addedPlayers,
8588
services.StateStore,
8689
services.ChecksumProvider,
87-
localConnections
90+
localConnections,
91+
inputComparer
8892
)
8993
{
9094
Callbacks = callbacks,
@@ -253,7 +257,7 @@ public void SetHandler(INetcodeSessionHandler handler)
253257

254258
public void SetFrameDelay(PlayerHandle player, int delayInFrames)
255259
{
256-
ThrowHelpers.ThrowIfArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
260+
ThrowIf.ArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
257261
ArgumentOutOfRangeException.ThrowIfNegative(delayInFrames);
258262
synchronizer.SetFrameDelay(player, delayInFrames);
259263
}
@@ -295,7 +299,7 @@ ResultCode AddRemotePlayer(RemotePlayer player)
295299
var endpoint = player.EndPoint;
296300
var protocol = peerConnectionFactory.Create(
297301
new(player.Handle, endpoint, localConnections, syncNumber),
298-
inputSerializer, peerInputEventQueue
302+
inputSerializer, peerInputEventQueue, inputComparer
299303
);
300304

301305
peerObservers.Add(protocol.GetUdpObserver());
@@ -334,7 +338,8 @@ ResultCode AddSpectator(Spectator spectator)
334338
var protocol = peerConnectionFactory.Create(
335339
new(spectatorHandle, spectator.EndPoint, localConnections, syncNumber),
336340
inputGroupSerializer,
337-
peerCombinedInputsEventPublisher
341+
peerCombinedInputsEventPublisher,
342+
inputGroupComparer
338343
);
339344
peerObservers.Add(protocol.GetUdpObserver());
340345
spectators.Add(protocol);
@@ -432,7 +437,7 @@ void ConsumeProtocolInputEvents()
432437
var currentRemoteFrame = localConnections[player].LastFrame;
433438
var newRemoteFrame = eventInput.Frame;
434439

435-
Trace.Assert(currentRemoteFrame.IsNull || newRemoteFrame == currentRemoteFrame.Next());
440+
ThrowIf.Assert(currentRemoteFrame.IsNull || newRemoteFrame == currentRemoteFrame.Next());
436441
synchronizer.AddRemoteInput(in player, eventInput);
437442
// Notify the other endpoints which frame we received from a peer
438443
logger.Write(LogLevel.Trace, $"setting remote connect status frame {player} to {eventInput.Frame}");
@@ -490,7 +495,7 @@ void DoSync()
490495
eps[i]?.SetLocalFrameNumber(currentFrame, options.FramesPerSecond);
491496

492497
var minConfirmedFrame = NumberOfPlayers <= 2 ? MinimumFrame2Players() : MinimumFrameNPlayers();
493-
Trace.Assert(minConfirmedFrame != Frame.MaxValue);
498+
ThrowIf.Assert(minConfirmedFrame != Frame.MaxValue);
494499
logger.Write(LogLevel.Trace, $"last confirmed frame in p2p backend is {minConfirmedFrame}");
495500

496501
if (minConfirmedFrame >= Frame.Zero)
@@ -697,7 +702,7 @@ void DisconnectPlayerQueue(in PlayerHandle player, in Frame syncTo)
697702
$"Changing player {player} local connect status for last frame from {connStatus.LastFrame} to {syncTo} on disconnect request (current: {frameCount})");
698703
connStatus.Disconnected = true;
699704
connStatus.LastFrame = syncTo;
700-
if (syncTo < frameCount && syncTo.IsNotNull)
705+
if (syncTo < frameCount && !syncTo.IsNull)
701706
{
702707
logger.Write(LogLevel.Information,
703708
$"adjusting simulation to account for the fact that {player} disconnected on frame {syncTo}");

src/Backdash/Backends/ReplayBackend.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using Backdash.Core;
32
using Backdash.Data;
43
using Backdash.Network;
@@ -140,7 +139,7 @@ public ResultCode SynchronizeInputs()
140139
if (confirmed.Count is 0 && CurrentFrame == Frame.Zero)
141140
return ResultCode.NotSynchronized;
142141

143-
Trace.Assert(confirmed.Count > 0);
142+
ThrowIf.Assert(confirmed.Count > 0);
144143
NumberOfPlayers = confirmed.Count;
145144

146145
if (syncInputBuffer.Length != NumberOfPlayers)
@@ -185,7 +184,7 @@ public void LoadFrame(in Frame frame)
185184

186185
try
187186
{
188-
var savedFrame = stateStore.Load(frame);
187+
var savedFrame = stateStore.Load(in frame);
189188
logger.Write(LogLevel.Trace,
190189
$"Loading replay frame {savedFrame.Frame} (checksum: {savedFrame.Checksum})");
191190
var offset = 0;

src/Backdash/Backends/SpectatorBackend.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Net;
32
using Backdash.Core;
43
using Backdash.Data;
@@ -78,7 +77,8 @@ public SpectatorBackend(int port,
7877
ProtocolState protocolState =
7978
new(new(PlayerType.Remote, 0), hostEndpoint, localConnections, magicNumber);
8079

81-
host = peerConnectionFactory.Create(protocolState, inputGroupSerializer, this);
80+
var inputGroupComparer = ConfirmedInputComparer<TInput>.Create(services.InputComparer);
81+
host = peerConnectionFactory.Create(protocolState, inputGroupSerializer, this, inputGroupComparer);
8282
peerObservers.Add(host.GetUdpObserver());
8383
host.Synchronize();
8484
isSynchronizing = true;
@@ -227,7 +227,7 @@ public ResultCode SynchronizeInputs()
227227
return ResultCode.InputDropped;
228228
}
229229

230-
Trace.Assert(input.Data.Count > 0);
230+
ThrowIf.Assert(input.Data.Count > 0);
231231
NumberOfPlayers = input.Data.Count;
232232

233233
if (syncInputBuffer.Length != NumberOfPlayers)

src/Backdash/Backends/SyncTestBackend.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public SyncTestBackend(NetcodeOptions options,
6464
addedPlayers,
6565
services.StateStore,
6666
services.ChecksumProvider,
67-
new(Max.NumberOfPlayers)
67+
new(Max.NumberOfPlayers),
68+
services.InputComparer
6869
)
6970
{
7071
Callbacks = callbacks,
@@ -299,7 +300,7 @@ void LogText(LogLevel level, string text)
299300

300301
public void SetFrameDelay(PlayerHandle player, int delayInFrames)
301302
{
302-
ThrowHelpers.ThrowIfArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
303+
ThrowIf.ArgumentOutOfBounds(player.InternalQueue, 0, addedPlayers.Count);
303304
ArgumentOutOfRangeException.ThrowIfNegative(delayInFrames);
304305
synchronizer.SetFrameDelay(player, delayInFrames);
305306
}

src/Backdash/Core/Default.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ static class Default
4141
///<value>Defaults to <c>32_768</c></value>
4242
public const int MaxSeqDistance = 1 << 15;
4343

44-
///<value>Defaults to <c>500</c> milliseconds</value>
45-
public const int ConsistencyCheckInterval = 500;
44+
///<value>Defaults to <c>3_000</c> milliseconds</value>
45+
public const int ConsistencyCheckInterval = 3_000;
4646

47-
///<value>Defaults to <c>6_000</c> milliseconds</value>
48-
public const int ConsistencyCheckTimeout = 6_000;
47+
///<value>Defaults to <c>10_000</c> milliseconds</value>
48+
public const int ConsistencyCheckTimeout = 10_000;
4949

5050
///<value>Defaults to <c>8</c></value>
5151
public const int ConsistencyCheckOffset = 8;

0 commit comments

Comments
 (0)