Skip to content

Commit b30cfa5

Browse files
committed
perf: message serializations
1 parent ad67976 commit b30cfa5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+366
-210
lines changed

benchmarks/Backdash.Benchmarks.Ping/PingMessageHandler.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ sealed class PingMessageHandler(IPeerClient<PingMessage> sender) : IPeerObserver
99
public static long TotalProcessed => processedCount;
1010
static long processedCount;
1111

12-
public void OnPeerMessage(
13-
in PingMessage message,
14-
SocketAddress from,
15-
int bytesReceived
16-
)
12+
public void OnPeerMessage(ref readonly PingMessage message, in SocketAddress from, int bytesReceived)
1713
{
1814
Interlocked.Increment(ref processedCount);
1915

benchmarks/Backdash.Benchmarks/Network/Message.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ sealed class PingMessageHandler(string name, IPeerClient<PingMessage> sender) :
1919
public long BadMessages => badMessages;
2020
public event Action<long> OnProcessed = delegate { };
2121

22-
public void OnPeerMessage(in PingMessage message, SocketAddress from, int bytesReceived
22+
public void OnPeerMessage(ref readonly PingMessage message, in SocketAddress from, int bytesReceived
2323
)
2424
{
2525
Interlocked.Increment(ref processedCount);

samples/SpaceWar.Shared/Logic/GameState.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Backdash.Data;
1+
using Backdash;
22
using Backdash.Serialization;
33

44
namespace SpaceWar.Logic;
@@ -13,8 +13,7 @@ public sealed record GameState
1313
public void Init(int numberOfPlayers)
1414
{
1515
Ships = new Ship[numberOfPlayers];
16-
for (var i = 0; i < numberOfPlayers; i++)
17-
Ships[i] = new();
16+
for (var i = 0; i < numberOfPlayers; i++) Ships[i] = new();
1817
FrameNumber = 0;
1918
Bounds = Config.InternalBounds;
2019
Bounds.Inflate(-Config.WindowPadding, -Config.WindowPadding);

src/Backdash/Backends/LocalBackend.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Backdash.Network;
44
using Backdash.Synchronizing.Input;
55
using Backdash.Synchronizing.Random;
6+
using Backdash.Synchronizing.State;
67

78
namespace Backdash.Backends;
89

@@ -55,6 +56,7 @@ public LocalBackend(NetcodeOptions options, BackendServices<TInput> services)
5556
public SessionMode Mode => SessionMode.Local;
5657
public FrameSpan FramesBehind => synchronizer.FramesBehind;
5758
public FrameSpan RollbackFrames => synchronizer.RollbackFrames;
59+
public SavedFrame CurrentSavedFrame => synchronizer.GetLastSavedFrame();
5860

5961
public IReadOnlyCollection<PlayerHandle> GetPlayers() => addedPlayers;
6062

src/Backdash/Backends/RemoteBackend.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Backdash.Synchronizing.Input;
1212
using Backdash.Synchronizing.Input.Confirmed;
1313
using Backdash.Synchronizing.Random;
14+
using Backdash.Synchronizing.State;
1415

1516
namespace Backdash.Backends;
1617

@@ -68,10 +69,10 @@ BackendServices<TInput> services
6869
logger = services.Logger;
6970
inputListener = services.InputListener;
7071
Random = services.DeterministicRandom;
71-
syncNumber = services.Random.MagicNumber();
7272
inputComparer = services.InputComparer;
73-
inputGroupComparer = ConfirmedInputComparer<TInput>.Create(services.InputComparer);
7473

74+
inputGroupComparer = ConfirmedInputComparer<TInput>.Create(services.InputComparer);
75+
syncNumber = services.Random.MagicNumber();
7576
peerInputEventQueue = new();
7677
peerCombinedInputsEventPublisher = new ProtocolCombinedInputsEventPublisher<TInput>(peerInputEventQueue);
7778
inputGroupSerializer = new ConfirmedInputsSerializer<TInput>(inputSerializer);
@@ -140,6 +141,7 @@ public void Close()
140141
public Frame CurrentFrame => synchronizer.CurrentFrame;
141142
public FrameSpan RollbackFrames => synchronizer.RollbackFrames;
142143
public FrameSpan FramesBehind => synchronizer.FramesBehind;
144+
public SavedFrame CurrentSavedFrame => synchronizer.GetLastSavedFrame();
143145
public int NumberOfPlayers => addedPlayers.Count;
144146

145147
public int NumberOfSpectators => addedSpectators.Count;

src/Backdash/Backends/ReplayBackend.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@
99

1010
namespace Backdash.Backends;
1111

12-
sealed class ReplayBackend<TInput> : INetcodeSession<TInput>
13-
where TInput : unmanaged
12+
sealed class ReplayBackend<TInput> : INetcodeSession<TInput> where TInput : unmanaged
1413
{
1514
readonly Logger logger;
1615
readonly PlayerHandle[] fakePlayers;
1716
INetcodeSessionHandler callbacks;
18-
readonly IDeterministicRandom deterministicRandom;
1917
bool isSynchronizing = true;
2018
SynchronizedInput<TInput>[] syncInputBuffer = [];
2119
TInput[] inputBuffer = [];
@@ -43,12 +41,12 @@ public ReplayBackend(int numberOfPlayers,
4341
logger = services.Logger;
4442
stateStore = services.StateStore;
4543
checksumProvider = services.ChecksumProvider;
46-
deterministicRandom = services.DeterministicRandom;
44+
Random = services.DeterministicRandom;
4745
NumberOfPlayers = numberOfPlayers;
48-
fakePlayers = Enumerable.Range(0, numberOfPlayers)
49-
.Select(x => new PlayerHandle(PlayerType.Remote, x + 1, x)).ToArray();
50-
5146
callbacks = new EmptySessionHandler(logger);
47+
fakePlayers = Enumerable.Range(0, numberOfPlayers)
48+
.Select(x => new PlayerHandle(PlayerType.Remote, x + 1, x))
49+
.ToArray();
5250

5351
stateStore.Initialize(controls.MaxBackwardFrames);
5452
}
@@ -72,9 +70,13 @@ public void Close()
7270
public Frame CurrentFrame { get; private set; } = Frame.Zero;
7371
public FrameSpan RollbackFrames => FrameSpan.Zero;
7472
public FrameSpan FramesBehind => FrameSpan.Zero;
75-
public int NumberOfPlayers { get; private set; }
73+
74+
public SavedFrame CurrentSavedFrame => stateStore.GetCurrent();
75+
7676
public int NumberOfSpectators => 0;
77-
public IDeterministicRandom Random => deterministicRandom;
77+
public int NumberOfPlayers { get; private set; }
78+
public IDeterministicRandom Random { get; }
79+
7880
public SessionMode Mode => SessionMode.Replaying;
7981
public void DisconnectPlayer(in PlayerHandle player) { }
8082
public ResultCode AddLocalInput(PlayerHandle player, in TInput localInput) => ResultCode.Ok;
@@ -155,7 +157,7 @@ public ResultCode SynchronizeInputs()
155157
}
156158

157159
var inputPopCount = useInputSeedForRandom ? Mem.PopCount<TInput>(inputBuffer.AsSpan()) : 0;
158-
deterministicRandom.UpdateSeed(CurrentFrame.Number, inputPopCount);
160+
Random.UpdateSeed(CurrentFrame.Number, inputPopCount);
159161

160162
return ResultCode.Ok;
161163
}
@@ -190,7 +192,6 @@ public void LoadFrame(in Frame frame)
190192
var offset = 0;
191193
BinaryBufferReader reader = new(savedFrame.GameState.WrittenSpan, ref offset);
192194
callbacks.LoadState(in frame, in reader);
193-
194195
CurrentFrame = savedFrame.Frame;
195196
}
196197
catch (NetcodeException)

src/Backdash/Backends/SpectatorBackend.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Backdash.Synchronizing.Input;
1010
using Backdash.Synchronizing.Input.Confirmed;
1111
using Backdash.Synchronizing.Random;
12+
using Backdash.Synchronizing.State;
1213

1314
namespace Backdash.Backends;
1415

@@ -24,7 +25,6 @@ sealed class SpectatorBackend<TInput> :
2425
readonly NetcodeOptions options;
2526
readonly IBackgroundJobManager backgroundJobManager;
2627
readonly IClock clock;
27-
readonly IDeterministicRandom deterministicRandom;
2828
readonly ConnectionsState localConnections = new(0);
2929
readonly GameInput<ConfirmedInputs<TInput>>[] inputs;
3030
readonly PeerConnection<ConfirmedInputs<TInput>> host;
@@ -38,6 +38,8 @@ sealed class SpectatorBackend<TInput> :
3838
SynchronizedInput<TInput>[] syncInputBuffer = [];
3939
TInput[] inputBuffer = [];
4040
bool closed;
41+
readonly IStateStore stateStore;
42+
readonly IChecksumProvider checksumProvider;
4143

4244
public SpectatorBackend(int port,
4345
IPEndPoint hostEndpoint,
@@ -53,9 +55,11 @@ public SpectatorBackend(int port,
5355
this.hostEndpoint = hostEndpoint;
5456
this.options = options;
5557
backgroundJobManager = services.JobManager;
56-
deterministicRandom = services.DeterministicRandom;
58+
Random = services.DeterministicRandom;
5759
logger = services.Logger;
5860
clock = services.Clock;
61+
stateStore = services.StateStore;
62+
checksumProvider = services.ChecksumProvider;
5963
NumberOfPlayers = numberOfPlayers;
6064
fakePlayers = Enumerable.Range(0, numberOfPlayers)
6165
.Select(x => new PlayerHandle(PlayerType.Remote, x + 1, x)).ToArray();
@@ -71,17 +75,19 @@ public SpectatorBackend(int port,
7175

7276
PeerConnectionFactory peerConnectionFactory = new(
7377
this, clock, services.Random, logger, udp,
74-
options.Protocol, options.TimeSync, services.StateStore
78+
options.Protocol, options.TimeSync, stateStore
7579
);
7680

7781
ProtocolState protocolState =
7882
new(new(PlayerType.Remote, 0), hostEndpoint, localConnections, magicNumber);
7983

8084
var inputGroupComparer = ConfirmedInputComparer<TInput>.Create(services.InputComparer);
8185
host = peerConnectionFactory.Create(protocolState, inputGroupSerializer, this, inputGroupComparer);
86+
8287
peerObservers.Add(host.GetUdpObserver());
8388
host.Synchronize();
8489
isSynchronizing = true;
90+
stateStore.Initialize(options.TotalPredictionFrames);
8591
}
8692

8793
public void Dispose()
@@ -106,11 +112,14 @@ public void Close()
106112
public Frame CurrentFrame { get; private set; } = Frame.Zero;
107113
public FrameSpan RollbackFrames => FrameSpan.Zero;
108114
public FrameSpan FramesBehind => FrameSpan.Zero;
115+
public SavedFrame CurrentSavedFrame => stateStore.GetCurrent();
116+
109117
public int NumberOfPlayers { get; private set; }
110118
public int NumberOfSpectators => 0;
111119

112-
public IDeterministicRandom Random => deterministicRandom;
120+
public IDeterministicRandom Random { get; }
113121
public SessionMode Mode => SessionMode.Spectating;
122+
114123
public void DisconnectPlayer(in PlayerHandle player) { }
115124
public ResultCode AddLocalInput(PlayerHandle player, in TInput localInput) => ResultCode.Ok;
116125
public IReadOnlyCollection<PlayerHandle> GetPlayers() => fakePlayers;
@@ -124,8 +133,8 @@ public void BeginFrame()
124133
if (isSynchronizing)
125134
return;
126135

127-
if (lastReceivedInputTime > 0
128-
&& clock.GetElapsedTime(lastReceivedInputTime) > options.Protocol.DisconnectTimeout)
136+
if (lastReceivedInputTime > 0 &&
137+
clock.GetElapsedTime(lastReceivedInputTime) > options.Protocol.DisconnectTimeout)
129138
Close();
130139
}
131140

@@ -243,12 +252,27 @@ public ResultCode SynchronizeInputs()
243252
}
244253

245254
var inputPopCount = options.UseInputSeedForRandom ? Mem.PopCount<TInput>(inputBuffer.AsSpan()) : 0;
246-
deterministicRandom.UpdateSeed(CurrentFrame.Number, inputPopCount);
255+
Random.UpdateSeed(CurrentFrame.Number, inputPopCount);
247256

248257
CurrentFrame++;
258+
SaveCurrentFrame();
249259
return ResultCode.Ok;
250260
}
251261

262+
void SaveCurrentFrame()
263+
{
264+
var currentFrame = CurrentFrame;
265+
ref var nextState = ref stateStore.GetCurrent();
266+
267+
BinaryBufferWriter writer = new(nextState.GameState);
268+
callbacks.SaveState(in currentFrame, in writer);
269+
nextState.Frame = currentFrame;
270+
nextState.Checksum = checksumProvider.Compute(nextState.GameState.WrittenSpan);
271+
272+
stateStore.Advance();
273+
logger.Write(LogLevel.Trace, $"spectator: saved frame {nextState.Frame} (checksum: {nextState.Checksum}).");
274+
}
275+
252276
public ref readonly SynchronizedInput<TInput> GetInput(int index) =>
253277
ref syncInputBuffer[index];
254278

@@ -257,12 +281,12 @@ public ref readonly SynchronizedInput<TInput> GetInput(in PlayerHandle player) =
257281

258282
public void GetInputs(Span<SynchronizedInput<TInput>> buffer) => syncInputBuffer.CopyTo(buffer);
259283

260-
void IProtocolInputEventPublisher<ConfirmedInputs<TInput>>.Publish(in GameInputEvent<ConfirmedInputs<TInput>> evt)
284+
bool IProtocolInputEventPublisher<ConfirmedInputs<TInput>>.Publish(in GameInputEvent<ConfirmedInputs<TInput>> evt)
261285
{
262286
lastReceivedInputTime = clock.GetTimeStamp();
263287
var (_, input) = evt;
264288
inputs[input.Frame.Number % inputs.Length] = input;
265289
host.SetLocalFrameNumber(input.Frame, options.FramesPerSecond);
266-
host.SendInputAck();
290+
return host.SendInputAck();
267291
}
268292
}

src/Backdash/Backends/SyncTestBackend.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public SyncTestBackend(NetcodeOptions options,
8282
public SessionMode Mode => SessionMode.SyncTest;
8383
public FrameSpan FramesBehind => synchronizer.FramesBehind;
8484
public FrameSpan RollbackFrames => synchronizer.RollbackFrames;
85+
public SavedFrame CurrentSavedFrame => synchronizer.GetLastSavedFrame();
8586

8687
public IReadOnlyCollection<PlayerHandle> GetPlayers() =>
8788
addedPlayers.Count is 0 ? [new(PlayerType.Local, 1, 0)] : addedPlayers;

src/Backdash/Core/MathI.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace Backdash;
4+
5+
/// <summary>
6+
/// Int Math
7+
/// </summary>
8+
public static class MathI
9+
{
10+
/// <summary>
11+
/// Divide two integers ceiling the result
12+
/// </summary>
13+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14+
public static int CeilDiv(int x, int y) => x is 0 ? 0 : 1 + ((x - 1) / y);
15+
}

src/Backdash/Data/ByteSize.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ static ReadOnlySpan<char> FindSymbol(ReadOnlySpan<char> str)
226226
return [];
227227
}
228228

229+
/// <summary>
230+
/// Returns number of bits for <paramref name="byteCount"/> bytes.
231+
/// </summary>
232+
public static int ByteCountOfBits(in ushort byteCount) => MathI.CeilDiv(byteCount, ByteToBits);
233+
229234
/// <inheritdoc />
230235
public static bool operator >(ByteSize left, ByteSize right) => left.ByteCount > right.ByteCount;
231236

0 commit comments

Comments
 (0)