Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions benchmarks/Backdash.Benchmarks/Cases/ChecksumBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public void Setup()
public sealed class Fletcher32SpanChecksumProvider : IChecksumProvider
{
/// <inheritdoc />
public uint Compute(ReadOnlySpan<byte> data)
public Checksum Compute(ReadOnlySpan<byte> data)
{
if (data.IsEmpty) return 0;
if (data.IsEmpty) return Checksum.Empty;
var buffer = MemoryMarshal.Cast<byte, ushort>(data);
uint sum1 = 0xFFFF, sum2 = 0xFFFF;
var dataIndex = 0;
Expand Down Expand Up @@ -75,7 +75,7 @@ public uint Compute(ReadOnlySpan<byte> data)

sum1 = (sum1 & 0xFFFF) + (sum1 >> 16);
sum2 = (sum2 & 0xFFFF) + (sum2 >> 16);
return (sum2 << 16) | sum1;
return new((sum2 << 16) | sum1);
}
}

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

/// <inheritdoc />
public unsafe uint Compute(ReadOnlySpan<byte> data)
public unsafe Checksum Compute(ReadOnlySpan<byte> data)
{
if (data.IsEmpty) return 0;
if (data.IsEmpty) return Checksum.Empty;

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

sum1 = (sum1 & 0xFFFF) + (sum1 >> 16);
sum2 = (sum2 & 0xFFFF) + (sum2 >> 16);
return (sum2 << 16) | sum1;
return new((sum2 << 16) | sum1);
}
}

public sealed class Crc32ChecksumProvider : IChecksumProvider
{
/// <inheritdoc />
public uint Compute(ReadOnlySpan<byte> data)
public Checksum Compute(ReadOnlySpan<byte> data)
{
if (data.Length == 0) return 0;
if (data.Length == 0) return Checksum.Empty;

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

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

var sum = sum3 + (sum2 << 8) + (sum1 << 16) + (sum0 << 24);

return sum;
return (Checksum)sum;
}
}

public sealed class Crc32BigEndianChecksumProvider : IChecksumProvider
{
/// <inheritdoc />
public unsafe uint Compute(ReadOnlySpan<byte> data)
public unsafe Checksum Compute(ReadOnlySpan<byte> data)
{
if (data.Length == 0) return 0;
if (data.Length == 0) return Checksum.Empty;

fixed (byte* ptr = data)
{
Expand Down Expand Up @@ -203,7 +202,7 @@ public unsafe uint Compute(ReadOnlySpan<byte> data)
break;
}

return sum;
return (Checksum)sum;
}
}
}
2 changes: 1 addition & 1 deletion samples/ConsoleGame/GameState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class NonGameState
public bool IsRunning;
public float SyncProgress;
public string LastError = "";
public uint Checksum;
public Checksum Checksum;
public PlayerStatus RemotePlayerStatus;
public DateTime LostConnectionTime;
public TimeSpan DisconnectTimeout;
Expand Down
6 changes: 0 additions & 6 deletions samples/ConsoleGame/Util.cs

This file was deleted.

2 changes: 1 addition & 1 deletion samples/ConsoleGame/View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ static void DrawStats(GameState currentState, NonGameState nonGameState)
$"""
Ping: {peer.Ping.TotalMilliseconds:f4} ms
Rollback: {info.RollbackFrames}
Checksum: {nonGameState.Checksum:x8}
Checksum: {nonGameState.Checksum}
Rng Seed: {currentState.RandomSeed:x8}
"""
);
Expand Down
9 changes: 6 additions & 3 deletions samples/SpaceWar.Shared/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public void TimeSync(FrameSpan framesAhead)
void UpdateStats()
{
nonGameState.RollbackFrames = session.RollbackFrames;
var saved = session.GetCurrentSavedFrame();
var saved = session.GetSavedState();
nonGameState.StateChecksum = saved.Checksum;
nonGameState.StateSize = saved.Size;
}
Expand Down Expand Up @@ -156,15 +156,18 @@ public void OnPeerEvent(NetcodePlayer player, in PeerEventInfo evt)
nonGameState.SetConnectState(player, PlayerConnectState.Disconnected);
break;
case PeerEvent.ChecksumMismatch:
Log($"=> CHECKSUM MISMATCH: {evt.ChecksumMismatch}");
Log($"=> CHECKSUM MISMATCH: {player.EndPoint} => {evt.ChecksumMismatch}");
break;
}
}

// used by SyncTest, the return value is used on the state desync handler call
object? INetcodeSessionHandler.CreateState(Frame frame, ref readonly BinaryBufferReader reader)
{
GameState state = new();
GameState state = new()
{
Ships = new Ship[session.NumberOfPlayers],
};
state.LoadState(in reader);
return state;
}
Expand Down
2 changes: 1 addition & 1 deletion samples/SpaceWar.Shared/Logic/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ void DrawStateStats(GameState gs, NonGameState ngs)
const int statsPadding = 2;

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

Vector2 pos = new((gs.Bounds.Width - size.X) / 2, gs.Bounds.Top - Config.WindowPadding);
Expand Down
14 changes: 13 additions & 1 deletion samples/SpaceWar/Game1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,24 @@ void HandleNonGameKeys()
if (keyboard.IsKeyPressed(Keys.Escape))
Exit();

if (keyboard.IsKeyPressed(Keys.Z))
{
foreach (var s in session.EnumerateStateStrings().Take(3))
{
Console.WriteLine($"--> State(Frame: {s.Frame.Number}, Checksum: {s.Checksum}");
Console.WriteLine(s.State);
Console.WriteLine($"--> End State(Frame: {s.Frame.Number})");
}

return;
}

if (session.IsOnline())
return;

if (keyboard.IsKeyPressed(Keys.S))
{
snapshot = session.CurrentStateSnapshot();
snapshot = session.GetStateSnapshot();
session.WriteLog($"Snapshot saved {snapshot?.Frame}");
}
else if (keyboard.IsKeyPressed(Keys.L) && snapshot is not null)
Expand Down
4 changes: 2 additions & 2 deletions src/Backdash/Core/Builders/Utf8StringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ public bool WriteEnum<T>(in T value, ReadOnlySpan<char> format = default) where
}
}

readonly ref struct Utf8ObjectStringWriter
readonly ref struct Utf8ObjectStringBuilder
{
readonly Utf8StringBuilder writer;
readonly int firstOffset;
readonly ref int offset;

public Utf8ObjectStringWriter(in Span<byte> bufferArg, ref int offset)
public Utf8ObjectStringBuilder(in Span<byte> bufferArg, ref int offset)
{
writer = new(in bufferArg, ref offset);
this.offset = ref offset;
Expand Down
144 changes: 144 additions & 0 deletions src/Backdash/Core/Checksum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;
using Backdash.Core;

namespace Backdash;

/// <summary>
/// Value representation of a Checksum
/// </summary>
[Serializable]
[DebuggerDisplay("{ToString()}")]
[JsonConverter(typeof(JsonConverter))]
public readonly record struct Checksum :
IComparable<Checksum>,
IComparable<uint>,
IEquatable<uint>,
IUtf8SpanFormattable,
ISpanFormattable,
IComparisonOperators<Checksum, Checksum, bool>,
IComparisonOperators<Checksum, uint, bool>
{
/// <summary>Return frame value <c>0</c></summary>
public static readonly Checksum Empty = new(0);

/// <summary>Returns the <see cref="uint" /> value for the current <see cref="Checksum" />.</summary>
public readonly uint Value;

/// <summary>
/// Initialize new <see cref="Checksum" /> for frame <paramref name="value" />.
/// </summary>
/// <param name="value"></param>
public Checksum(uint value) => Value = value;

/// <inheritdoc />
public int CompareTo(Checksum other) => Value.CompareTo(other.Value);

/// <inheritdoc />
public int CompareTo(uint other) => Value.CompareTo(other);

/// <inheritdoc />
public bool Equals(uint other) => Value == other;

/// <summary>Return true if current value is <c>0</c></summary>
public bool IsEmpty => Value is 0u;

const string DefaultFormat = "x8";

/// <inheritdoc />
public string ToString(
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
string? format,
IFormatProvider? formatProvider
) =>
Value.ToString(format ?? DefaultFormat, formatProvider);

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

/// <inheritdoc cref="ToString(string, IFormatProvider)" />
public string ToString(
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
string format) => ToString(format, null);

/// <inheritdoc />
public bool TryFormat(
Span<byte> utf8Destination, out int bytesWritten,
ReadOnlySpan<char> format,
IFormatProvider? provider
)
{
bytesWritten = 0;
if (format.IsEmpty) format = DefaultFormat;
Utf8StringBuilder writer = new(in utf8Destination, ref bytesWritten);
return writer.Write(Value, format);
}

/// <inheritdoc />
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format,
IFormatProvider? provider)
{
if (format.IsEmpty) format = DefaultFormat;
return Value.TryFormat(destination, out charsWritten, format, provider);
}

/// <inheritdoc />
public static bool operator >(Checksum left, Checksum right) => left.Value > right.Value;

/// <inheritdoc />
public static bool operator >=(Checksum left, Checksum right) => left.Value >= right.Value;

/// <inheritdoc />
public static bool operator <(Checksum left, Checksum right) => left.Value < right.Value;

/// <inheritdoc />
public static bool operator <=(Checksum left, Checksum right) => left.Value <= right.Value;

/// <inheritdoc />
public static bool operator ==(Checksum left, uint right) => left.Value == right;

/// <inheritdoc />
public static bool operator !=(Checksum left, uint right) => left.Value != right;

/// <inheritdoc />
public static bool operator >(Checksum left, uint right) => left.Value > right;

/// <inheritdoc />
public static bool operator >=(Checksum left, uint right) => left.Value >= right;

/// <inheritdoc />
public static bool operator <(Checksum left, uint right) => left.Value < right;

/// <inheritdoc />
public static bool operator <=(Checksum left, uint right) => left.Value <= right;

/// <inheritdoc cref="Value" />
public static implicit operator uint(Checksum frame) => frame.Value;

/// <inheritdoc cref="Checksum(uint)" />
public static explicit operator Checksum(uint frame) => new(frame);

internal sealed class JsonConverter : JsonConverter<Checksum>
{
public override Checksum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
return string.IsNullOrWhiteSpace(value) ? Empty : new(uint.Parse(value, NumberStyles.HexNumber, null));
}

public override void Write(Utf8JsonWriter writer, Checksum value, JsonSerializerOptions options)
{
Span<char> buffer = stackalloc char[8];
if (value.TryFormat(buffer, out var charCount, DefaultFormat, null))
{
writer.WriteStringValue(buffer[..charCount]);
}
else
writer.WriteStringValue(value.ToString(DefaultFormat, null));
}
}
}
18 changes: 17 additions & 1 deletion src/Backdash/Core/Frame/Frame.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Backdash.Core;

Expand Down Expand Up @@ -75,6 +76,11 @@ namespace Backdash;
/// </summary>
public bool IsNull => Number is NullValue;

/// <summary>
/// Returns <see langword="true" /> if the current frame is 0
/// </summary>
public bool IsZero => Number is 0;

/// <inheritdoc />
public int CompareTo(Frame other) => Number.CompareTo(other.Number);

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

/// <inheritdoc />
public string ToString(string? format, IFormatProvider? formatProvider) =>
public string ToString(
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
string? format,
IFormatProvider? formatProvider
) =>
Number.ToString(format ?? DefaultFormat, formatProvider);

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

/// <inheritdoc cref="ToString(string, IFormatProvider)" />
public string ToString(
[StringSyntax(StringSyntaxAttribute.NumericFormat)]
string format
) => ToString(format, null);

/// <inheritdoc />
public bool TryFormat(
Span<byte> utf8Destination, out int bytesWritten,
Expand Down
4 changes: 2 additions & 2 deletions src/Backdash/Core/Json/InternalJsonConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
Unsafe.BitCast<int, T>(reader.GetInt32());

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
writer.WriteNumberValue(Unsafe.As<T, int>(ref Unsafe.AsRef(ref value)));
writer.WriteNumberValue(Unsafe.As<T, int>(ref value));
}

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)]
Expand All @@ -25,7 +25,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
Unsafe.BitCast<long, T>(reader.GetInt64());

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
writer.WriteNumberValue(Unsafe.As<T, long>(ref Unsafe.AsRef(ref value)));
writer.WriteNumberValue(Unsafe.As<T, long>(ref value));
}

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)]
Expand Down
Loading
Loading