Skip to content

Commit 3b7f0d9

Browse files
authored
Desync tools (#159)
1 parent a40187d commit 3b7f0d9

51 files changed

Lines changed: 699 additions & 186 deletions

Some content is hidden

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

benchmarks/Backdash.Benchmarks/Cases/ChecksumBenchmark.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ public void Setup()
4444
public sealed class Fletcher32SpanChecksumProvider : IChecksumProvider
4545
{
4646
/// <inheritdoc />
47-
public uint Compute(ReadOnlySpan<byte> data)
47+
public Checksum Compute(ReadOnlySpan<byte> data)
4848
{
49-
if (data.IsEmpty) return 0;
49+
if (data.IsEmpty) return Checksum.Empty;
5050
var buffer = MemoryMarshal.Cast<byte, ushort>(data);
5151
uint sum1 = 0xFFFF, sum2 = 0xFFFF;
5252
var dataIndex = 0;
@@ -75,7 +75,7 @@ public uint Compute(ReadOnlySpan<byte> data)
7575

7676
sum1 = (sum1 & 0xFFFF) + (sum1 >> 16);
7777
sum2 = (sum2 & 0xFFFF) + (sum2 >> 16);
78-
return (sum2 << 16) | sum1;
78+
return new((sum2 << 16) | sum1);
7979
}
8080
}
8181

@@ -84,9 +84,9 @@ public sealed class Fletcher32UnsafeChecksumProvider : IChecksumProvider
8484
const int BlockSize = 360;
8585

8686
/// <inheritdoc />
87-
public unsafe uint Compute(ReadOnlySpan<byte> data)
87+
public unsafe Checksum Compute(ReadOnlySpan<byte> data)
8888
{
89-
if (data.IsEmpty) return 0;
89+
if (data.IsEmpty) return Checksum.Empty;
9090

9191
uint sum1 = 0xFFFF, sum2 = 0xFFFF;
9292
var dataIndex = 0;
@@ -121,16 +121,16 @@ public unsafe uint Compute(ReadOnlySpan<byte> data)
121121

122122
sum1 = (sum1 & 0xFFFF) + (sum1 >> 16);
123123
sum2 = (sum2 & 0xFFFF) + (sum2 >> 16);
124-
return (sum2 << 16) | sum1;
124+
return new((sum2 << 16) | sum1);
125125
}
126126
}
127127

128128
public sealed class Crc32ChecksumProvider : IChecksumProvider
129129
{
130130
/// <inheritdoc />
131-
public uint Compute(ReadOnlySpan<byte> data)
131+
public Checksum Compute(ReadOnlySpan<byte> data)
132132
{
133-
if (data.Length == 0) return 0;
133+
if (data.Length == 0) return Checksum.Empty;
134134

135135
uint sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
136136

@@ -146,17 +146,16 @@ public uint Compute(ReadOnlySpan<byte> data)
146146
}
147147

148148
var sum = sum3 + (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
149-
150-
return sum;
149+
return (Checksum)sum;
151150
}
152151
}
153152

154153
public sealed class Crc32BigEndianChecksumProvider : IChecksumProvider
155154
{
156155
/// <inheritdoc />
157-
public unsafe uint Compute(ReadOnlySpan<byte> data)
156+
public unsafe Checksum Compute(ReadOnlySpan<byte> data)
158157
{
159-
if (data.Length == 0) return 0;
158+
if (data.Length == 0) return Checksum.Empty;
160159

161160
fixed (byte* ptr = data)
162161
{
@@ -203,7 +202,7 @@ public unsafe uint Compute(ReadOnlySpan<byte> data)
203202
break;
204203
}
205204

206-
return sum;
205+
return (Checksum)sum;
207206
}
208207
}
209208
}

samples/ConsoleGame/GameState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class NonGameState
3131
public bool IsRunning;
3232
public float SyncProgress;
3333
public string LastError = "";
34-
public uint Checksum;
34+
public Checksum Checksum;
3535
public PlayerStatus RemotePlayerStatus;
3636
public DateTime LostConnectionTime;
3737
public TimeSpan DisconnectTimeout;

samples/ConsoleGame/Util.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

samples/ConsoleGame/View.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ static void DrawStats(GameState currentState, NonGameState nonGameState)
169169
$"""
170170
Ping: {peer.Ping.TotalMilliseconds:f4} ms
171171
Rollback: {info.RollbackFrames}
172-
Checksum: {nonGameState.Checksum:x8}
172+
Checksum: {nonGameState.Checksum}
173173
Rng Seed: {currentState.RandomSeed:x8}
174174
"""
175175
);

samples/SpaceWar.Shared/GameSession.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public void TimeSync(FrameSpan framesAhead)
117117
void UpdateStats()
118118
{
119119
nonGameState.RollbackFrames = session.RollbackFrames;
120-
var saved = session.GetCurrentSavedFrame();
120+
var saved = session.GetSavedState();
121121
nonGameState.StateChecksum = saved.Checksum;
122122
nonGameState.StateSize = saved.Size;
123123
}
@@ -156,15 +156,18 @@ public void OnPeerEvent(NetcodePlayer player, in PeerEventInfo evt)
156156
nonGameState.SetConnectState(player, PlayerConnectState.Disconnected);
157157
break;
158158
case PeerEvent.ChecksumMismatch:
159-
Log($"=> CHECKSUM MISMATCH: {evt.ChecksumMismatch}");
159+
Log($"=> CHECKSUM MISMATCH: {player.EndPoint} => {evt.ChecksumMismatch}");
160160
break;
161161
}
162162
}
163163

164164
// used by SyncTest, the return value is used on the state desync handler call
165165
object? INetcodeSessionHandler.CreateState(Frame frame, ref readonly BinaryBufferReader reader)
166166
{
167-
GameState state = new();
167+
GameState state = new()
168+
{
169+
Ships = new Ship[session.NumberOfPlayers],
170+
};
168171
state.LoadState(in reader);
169172
return state;
170173
}

samples/SpaceWar.Shared/Logic/Renderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ void DrawStateStats(GameState gs, NonGameState ngs)
279279
const int statsPadding = 2;
280280

281281
stateInfoString.Clear();
282-
stateInfoString.Append($"State: {ngs.StateChecksum:x8} {ngs.StateSize}");
282+
stateInfoString.Append($"State: {ngs.StateChecksum} {ngs.StateSize}");
283283
var size = gameAssets.MainFont.MeasureString(stateInfoString) * statsScale;
284284

285285
Vector2 pos = new((gs.Bounds.Width - size.X) / 2, gs.Bounds.Top - Config.WindowPadding);

samples/SpaceWar/Game1.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,24 @@ void HandleNonGameKeys()
165165
if (keyboard.IsKeyPressed(Keys.Escape))
166166
Exit();
167167

168+
if (keyboard.IsKeyPressed(Keys.Z))
169+
{
170+
foreach (var s in session.EnumerateStateStrings().Take(3))
171+
{
172+
Console.WriteLine($"--> State(Frame: {s.Frame.Number}, Checksum: {s.Checksum}");
173+
Console.WriteLine(s.State);
174+
Console.WriteLine($"--> End State(Frame: {s.Frame.Number})");
175+
}
176+
177+
return;
178+
}
179+
168180
if (session.IsOnline())
169181
return;
170182

171183
if (keyboard.IsKeyPressed(Keys.S))
172184
{
173-
snapshot = session.CurrentStateSnapshot();
185+
snapshot = session.GetStateSnapshot();
174186
session.WriteLog($"Snapshot saved {snapshot?.Frame}");
175187
}
176188
else if (keyboard.IsKeyPressed(Keys.L) && snapshot is not null)

src/Backdash/Core/Builders/Utf8StringBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ public bool WriteEnum<T>(in T value, ReadOnlySpan<char> format = default) where
7171
}
7272
}
7373

74-
readonly ref struct Utf8ObjectStringWriter
74+
readonly ref struct Utf8ObjectStringBuilder
7575
{
7676
readonly Utf8StringBuilder writer;
7777
readonly int firstOffset;
7878
readonly ref int offset;
7979

80-
public Utf8ObjectStringWriter(in Span<byte> bufferArg, ref int offset)
80+
public Utf8ObjectStringBuilder(in Span<byte> bufferArg, ref int offset)
8181
{
8282
writer = new(in bufferArg, ref offset);
8383
this.offset = ref offset;

src/Backdash/Core/Checksum.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Globalization;
4+
using System.Numerics;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
using Backdash.Core;
8+
9+
namespace Backdash;
10+
11+
/// <summary>
12+
/// Value representation of a Checksum
13+
/// </summary>
14+
[Serializable]
15+
[DebuggerDisplay("{ToString()}")]
16+
[JsonConverter(typeof(JsonConverter))]
17+
public readonly record struct Checksum :
18+
IComparable<Checksum>,
19+
IComparable<uint>,
20+
IEquatable<uint>,
21+
IUtf8SpanFormattable,
22+
ISpanFormattable,
23+
IComparisonOperators<Checksum, Checksum, bool>,
24+
IComparisonOperators<Checksum, uint, bool>
25+
{
26+
/// <summary>Return frame value <c>0</c></summary>
27+
public static readonly Checksum Empty = new(0);
28+
29+
/// <summary>Returns the <see cref="uint" /> value for the current <see cref="Checksum" />.</summary>
30+
public readonly uint Value;
31+
32+
/// <summary>
33+
/// Initialize new <see cref="Checksum" /> for frame <paramref name="value" />.
34+
/// </summary>
35+
/// <param name="value"></param>
36+
public Checksum(uint value) => Value = value;
37+
38+
/// <inheritdoc />
39+
public int CompareTo(Checksum other) => Value.CompareTo(other.Value);
40+
41+
/// <inheritdoc />
42+
public int CompareTo(uint other) => Value.CompareTo(other);
43+
44+
/// <inheritdoc />
45+
public bool Equals(uint other) => Value == other;
46+
47+
/// <summary>Return true if current value is <c>0</c></summary>
48+
public bool IsEmpty => Value is 0u;
49+
50+
const string DefaultFormat = "x8";
51+
52+
/// <inheritdoc />
53+
public string ToString(
54+
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
55+
string? format,
56+
IFormatProvider? formatProvider
57+
) =>
58+
Value.ToString(format ?? DefaultFormat, formatProvider);
59+
60+
/// <inheritdoc />
61+
public override string ToString() => ToString(null, null);
62+
63+
/// <inheritdoc cref="ToString(string, IFormatProvider)" />
64+
public string ToString(
65+
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
66+
string format) => ToString(format, null);
67+
68+
/// <inheritdoc />
69+
public bool TryFormat(
70+
Span<byte> utf8Destination, out int bytesWritten,
71+
ReadOnlySpan<char> format,
72+
IFormatProvider? provider
73+
)
74+
{
75+
bytesWritten = 0;
76+
if (format.IsEmpty) format = DefaultFormat;
77+
Utf8StringBuilder writer = new(in utf8Destination, ref bytesWritten);
78+
return writer.Write(Value, format);
79+
}
80+
81+
/// <inheritdoc />
82+
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format,
83+
IFormatProvider? provider)
84+
{
85+
if (format.IsEmpty) format = DefaultFormat;
86+
return Value.TryFormat(destination, out charsWritten, format, provider);
87+
}
88+
89+
/// <inheritdoc />
90+
public static bool operator >(Checksum left, Checksum right) => left.Value > right.Value;
91+
92+
/// <inheritdoc />
93+
public static bool operator >=(Checksum left, Checksum right) => left.Value >= right.Value;
94+
95+
/// <inheritdoc />
96+
public static bool operator <(Checksum left, Checksum right) => left.Value < right.Value;
97+
98+
/// <inheritdoc />
99+
public static bool operator <=(Checksum left, Checksum right) => left.Value <= right.Value;
100+
101+
/// <inheritdoc />
102+
public static bool operator ==(Checksum left, uint right) => left.Value == right;
103+
104+
/// <inheritdoc />
105+
public static bool operator !=(Checksum left, uint right) => left.Value != right;
106+
107+
/// <inheritdoc />
108+
public static bool operator >(Checksum left, uint right) => left.Value > right;
109+
110+
/// <inheritdoc />
111+
public static bool operator >=(Checksum left, uint right) => left.Value >= right;
112+
113+
/// <inheritdoc />
114+
public static bool operator <(Checksum left, uint right) => left.Value < right;
115+
116+
/// <inheritdoc />
117+
public static bool operator <=(Checksum left, uint right) => left.Value <= right;
118+
119+
/// <inheritdoc cref="Value" />
120+
public static implicit operator uint(Checksum frame) => frame.Value;
121+
122+
/// <inheritdoc cref="Checksum(uint)" />
123+
public static explicit operator Checksum(uint frame) => new(frame);
124+
125+
internal sealed class JsonConverter : JsonConverter<Checksum>
126+
{
127+
public override Checksum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
128+
{
129+
var value = reader.GetString();
130+
return string.IsNullOrWhiteSpace(value) ? Empty : new(uint.Parse(value, NumberStyles.HexNumber, null));
131+
}
132+
133+
public override void Write(Utf8JsonWriter writer, Checksum value, JsonSerializerOptions options)
134+
{
135+
Span<char> buffer = stackalloc char[8];
136+
if (value.TryFormat(buffer, out var charCount, DefaultFormat, null))
137+
{
138+
writer.WriteStringValue(buffer[..charCount]);
139+
}
140+
else
141+
writer.WriteStringValue(value.ToString(DefaultFormat, null));
142+
}
143+
}
144+
}

src/Backdash/Core/Frame/Frame.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Numerics;
34
using Backdash.Core;
45

@@ -75,6 +76,11 @@ namespace Backdash;
7576
/// </summary>
7677
public bool IsNull => Number is NullValue;
7778

79+
/// <summary>
80+
/// Returns <see langword="true" /> if the current frame is 0
81+
/// </summary>
82+
public bool IsZero => Number is 0;
83+
7884
/// <inheritdoc />
7985
public int CompareTo(Frame other) => Number.CompareTo(other.Number);
8086

@@ -87,12 +93,22 @@ namespace Backdash;
8793
const string DefaultFormat = "(Frame 0);(Frame -#)";
8894

8995
/// <inheritdoc />
90-
public string ToString(string? format, IFormatProvider? formatProvider) =>
96+
public string ToString(
97+
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
98+
string? format,
99+
IFormatProvider? formatProvider
100+
) =>
91101
Number.ToString(format ?? DefaultFormat, formatProvider);
92102

93103
/// <inheritdoc />
94104
public override string ToString() => ToString(null, null);
95105

106+
/// <inheritdoc cref="ToString(string, IFormatProvider)" />
107+
public string ToString(
108+
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
109+
string format
110+
) => ToString(format, null);
111+
96112
/// <inheritdoc />
97113
public bool TryFormat(
98114
Span<byte> utf8Destination, out int bytesWritten,

0 commit comments

Comments
 (0)