Skip to content

Commit fa6f431

Browse files
committed
improve single number serialization logic
1 parent b6480ce commit fa6f431

20 files changed

+281
-83
lines changed

src/Backdash/Network/Protocol/Comm/ProtocolMessageSerializer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
using Backdash.Network.Messages;
22
using Backdash.Serialization;
3+
using Backdash.Serialization.Internal;
34

45
namespace Backdash.Network.Protocol.Comm;
56

67
sealed class ProtocolMessageSerializer(Endianness endianness) : IBinarySerializer<ProtocolMessage>
78
{
9+
readonly EndiannessSerializer.INumberSerializer numberSerializer = EndiannessSerializer.Get(endianness);
810
public Endianness Endianness => endianness;
911

1012
public int Serialize(in ProtocolMessage data, Span<byte> buffer)
1113
{
1214
var offset = 0;
13-
BinarySpanWriter writer = new(buffer, ref offset, endianness);
15+
BinarySpanWriter writer = new(buffer, ref offset, numberSerializer);
1416
data.Serialize(in writer);
1517
return writer.WrittenCount;
1618
}
1719

1820
public int Deserialize(ReadOnlySpan<byte> data, ref ProtocolMessage value)
1921
{
2022
var offset = 0;
21-
BinaryBufferReader reader = new(data, ref offset, endianness);
23+
BinaryBufferReader reader = new(data, ref offset, numberSerializer);
2224
value.Deserialize(in reader);
2325
return offset;
2426
}

src/Backdash/Options/NetcodeOptions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Backdash.Network;
22
using Backdash.Network.Client;
3+
using Backdash.Serialization.Internal;
34
using Backdash.Synchronizing.State;
45

56
namespace Backdash.Options;
@@ -45,6 +46,9 @@ public sealed record NetcodeOptions
4546
internal Endianness GetStateSerializationEndianness() =>
4647
StateSerializationEndianness ?? Protocol.SerializationEndianness;
4748

49+
internal EndiannessSerializer.INumberSerializer GetEndiannessNumberStateSerializer() =>
50+
EndiannessSerializer.Get(GetStateSerializationEndianness());
51+
4852
/// <summary>
4953
/// Max length for player input queues.
5054
/// </summary>

src/Backdash/Options/ProtocolOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public sealed record ProtocolOptions
2222
public int UdpPacketBufferSize { get; set; }
2323

2424
/// <summary>
25-
/// Max allowed pending inputs in sending queue.
25+
/// Max allowed pending inputs in the sending queue.
2626
/// When reached <see cref="INetcodeSession{TInput}.AddLocalInput(Backdash.NetcodePlayer, in TInput)" /> will return
2727
/// <see cref="ResultCode.InputDropped" />.
2828
/// </summary>
@@ -31,7 +31,7 @@ public sealed record ProtocolOptions
3131

3232
/// <summary>
3333
/// Max allowed pending UDP output messages.
34-
/// When reached removes and ignores the oldest package in the queue in order to make room for the new package.
34+
/// When reached, removes and ignores the oldest package in the queue in order to make room for the new package.
3535
/// </summary>
3636
/// <value>Defaults to <c>64</c></value>
3737
public int MaxPackageQueue { get; set; } = 64;

src/Backdash/Serialization/BinaryBufferReader.cs

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Backdash.Core;
88
using Backdash.Data;
99
using Backdash.Network;
10+
using Backdash.Serialization.Internal;
1011

1112
namespace Backdash.Serialization;
1213

@@ -16,24 +17,46 @@ namespace Backdash.Serialization;
1617
[DebuggerDisplay("Read: {ReadCount}")]
1718
public readonly ref struct BinaryBufferReader
1819
{
20+
readonly ref int offset;
21+
readonly ReadOnlySpan<byte> buffer;
22+
readonly EndiannessSerializer.INumberSerializer numberSerializer;
23+
1924
/// <summary>
2025
/// Initialize a new <see cref="BinaryBufferReader" /> for <paramref name="buffer" />
2126
/// </summary>
2227
/// <param name="buffer">Byte buffer to be read</param>
2328
/// <param name="offset">Read offset reference</param>
24-
/// <param name="endianness">Deserialization endianness</param>
25-
public BinaryBufferReader(ReadOnlySpan<byte> buffer, ref int offset, Endianness? endianness = null)
29+
/// <param name="numberSerializer">Number serializer instance</param>
30+
public BinaryBufferReader(
31+
ReadOnlySpan<byte> buffer, ref int offset,
32+
EndiannessSerializer.INumberSerializer numberSerializer
33+
)
2634
{
35+
ArgumentNullException.ThrowIfNull(numberSerializer);
2736
this.offset = ref offset;
2837
this.buffer = buffer;
29-
Endianness = endianness ?? Platform.Endianness;
38+
this.numberSerializer = numberSerializer;
3039
}
3140

32-
readonly ref int offset;
33-
readonly ReadOnlySpan<byte> buffer;
41+
/// <summary>
42+
/// Initialize a new <see cref="BinaryBufferReader" /> for <paramref name="buffer" />
43+
/// </summary>
44+
/// <param name="buffer">Byte buffer to be read</param>
45+
/// <param name="offset">Read offset reference</param>
46+
/// <param name="endianness">Deserialization endianness</param>
47+
public BinaryBufferReader(ReadOnlySpan<byte> buffer, ref int offset, Endianness endianness)
48+
: this(buffer, ref offset, EndiannessSerializer.Get(endianness)) { }
49+
50+
/// <summary>
51+
/// Initialize a new <see cref="BinaryBufferReader" /> for <paramref name="buffer" />
52+
/// </summary>
53+
/// <param name="buffer">Byte buffer to be read</param>
54+
/// <param name="offset">Read offset reference</param>
55+
public BinaryBufferReader(ReadOnlySpan<byte> buffer, ref int offset)
56+
: this(buffer, ref offset, Platform.Endianness) { }
3457

35-
/// <summary>Gets or init the value to define which endianness should be used for serialization.</summary>
36-
public readonly Endianness Endianness;
58+
/// <summary>Gets current serialization endianness.</summary>
59+
public Endianness Endianness => numberSerializer.Endianness;
3760

3861
/// <summary>Total read byte count.</summary>
3962
public int ReadCount => offset;
@@ -313,35 +336,16 @@ public void ReadUtf8String(in Span<char> values)
313336
/// <summary>Reads single <see cref="IBinaryInteger{T}" /> from buffer.</summary>
314337
/// <typeparam name="T">A numeric type that implements <see cref="IBinaryInteger{T}" />.</typeparam>
315338
/// <param name="isUnsigned">
316-
/// true if source represents an unsigned two's complement number; otherwise, false to indicate it
339+
/// true if value represents an unsigned two's complement number; otherwise, false to indicate it
317340
/// represents a signed two's complement number
318341
/// </param>
319342
public T ReadNumber<T>(bool isUnsigned) where T : unmanaged, IBinaryInteger<T>
320343
{
321-
var size = Unsafe.SizeOf<T>();
322-
var result = Endianness switch
323-
{
324-
Endianness.LittleEndian => T.ReadLittleEndian(CurrentBuffer[..size], isUnsigned),
325-
Endianness.BigEndian => T.ReadBigEndian(CurrentBuffer[..size], isUnsigned),
326-
_ => default,
327-
};
328-
Advance(size);
344+
var result = numberSerializer.Read<T>(CurrentBuffer, isUnsigned, out var written);
345+
Advance(written);
329346
return result;
330347
}
331348

332-
/// <summary>Reads single <see cref="IBinaryInteger{T}" /> from buffer.</summary>
333-
/// <typeparam name="T">A numeric type that implements <see cref="IBinaryInteger{T}" /> and <see cref="IMinMaxValue{T}" />.</typeparam>
334-
public T ReadNumber<T>() where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T> =>
335-
ReadNumber<T>(T.IsZero(T.MinValue));
336-
337-
/// <inheritdoc cref="ReadNumber{T}()" />
338-
public void ReadNumber<T>(ref T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T> =>
339-
value = ReadNumber<T>();
340-
341-
/// <inheritdoc cref="ReadNullableNumber{T}()" />
342-
public void ReadNumber<T>(ref T? value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T> =>
343-
value = ReadNullableNumber<T>();
344-
345349
/// <inheritdoc cref="ReadNumber{T}(bool)" />
346350
public void ReadNumber<T>(ref T value, bool isUnsigned) where T : unmanaged, IBinaryInteger<T> =>
347351
value = ReadNumber<T>(isUnsigned);
@@ -350,10 +354,6 @@ public void ReadNumber<T>(ref T value, bool isUnsigned) where T : unmanaged, IBi
350354
public void ReadNumber<T>(ref T? value, bool isUnsigned) where T : unmanaged, IBinaryInteger<T> =>
351355
value = ReadNullableNumber<T>(isUnsigned);
352356

353-
/// <inheritdoc cref="ReadNumber{T}()" />
354-
public T? ReadNullableNumber<T>() where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T> =>
355-
ReadBoolean() ? ReadNumber<T>() : null;
356-
357357
/// <inheritdoc cref="ReadNumber{T}(bool)" />
358358
public T? ReadNullableNumber<T>(bool isUnsigned) where T : unmanaged, IBinaryInteger<T> =>
359359
ReadBoolean() ? ReadNumber<T>(isUnsigned) : null;

src/Backdash/Serialization/BinaryBufferWriter.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Backdash.Core;
99
using Backdash.Data;
1010
using Backdash.Network;
11+
using Backdash.Serialization.Internal;
1112

1213
namespace Backdash.Serialization;
1314

@@ -18,14 +19,27 @@ namespace Backdash.Serialization;
1819
/// Initialize a new <see cref="BinaryBufferWriter" /> for <paramref name="buffer" />
1920
/// </remarks>
2021
/// <param name="buffer">Byte buffer to be written</param>
21-
/// <param name="endianness">Serialization endianness</param>
22+
/// <param name="numberSerializer">Number serializer instance</param>
2223
[DebuggerDisplay("Written: {WrittenCount}")]
23-
public readonly struct BinaryBufferWriter(ArrayBufferWriter<byte> buffer, Endianness? endianness = null)
24+
public readonly struct BinaryBufferWriter(
25+
ArrayBufferWriter<byte> buffer,
26+
EndiannessSerializer.INumberSerializer numberSerializer
27+
)
2428
{
2529
/// <summary>
26-
/// Gets or init the value to define which endianness should be used for serialization.
30+
/// Instantiate a new binary span writer.
2731
/// </summary>
28-
public readonly Endianness Endianness = endianness ?? Platform.Endianness;
32+
/// <param name="buffer">Byte buffer to be written</param>
33+
/// <param name="endianness">Serialization endianness</param>
34+
public BinaryBufferWriter(ArrayBufferWriter<byte> buffer, Endianness endianness) : this(buffer,
35+
EndiannessSerializer.Get(endianness))
36+
{ }
37+
38+
/// <inheritdoc cref="BinaryBufferWriter(System.Buffers.ArrayBufferWriter{byte},Backdash.Network.Endianness)"/>
39+
public BinaryBufferWriter(ArrayBufferWriter<byte> buffer) : this(buffer, Platform.Endianness) { }
40+
41+
/// <summary>Gets current serialization endianness.</summary>
42+
public Endianness Endianness => numberSerializer.Endianness;
2943

3044
/// <summary>
3145
/// Backing IBufferWriter <see cref="IBufferWriter{T}" />
@@ -660,20 +674,7 @@ public void WriteUtf8String(in ReadOnlySpan<char> value)
660674
/// <typeparam name="T">A numeric type that implements <see cref="IBinaryInteger{T}" />.</typeparam>
661675
public void WriteNumber<T>(in T value) where T : unmanaged, IBinaryInteger<T>
662676
{
663-
ref var valueRef = ref Unsafe.AsRef(in value);
664-
var size = Unsafe.SizeOf<T>();
665-
switch (Endianness)
666-
{
667-
case Endianness.LittleEndian:
668-
valueRef.TryWriteLittleEndian(buffer.GetSpan(size), out size);
669-
break;
670-
case Endianness.BigEndian:
671-
valueRef.TryWriteBigEndian(buffer.GetSpan(size), out size);
672-
break;
673-
default:
674-
return;
675-
}
676-
677+
numberSerializer.Write(buffer, in value, out var size);
677678
Advance(size);
678679
}
679680

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Numerics;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Backdash.Serialization;
5+
6+
/// <summary>
7+
/// Signed number extensions
8+
/// </summary>
9+
public static class BinarySerializerSignedExtensions
10+
{
11+
const bool IsUnsigned = false;
12+
13+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}(bool)"/>
14+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
15+
public static T ReadNumber<T>(ref readonly this BinaryBufferReader reader)
16+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
17+
reader.ReadNumber<T>(IsUnsigned);
18+
19+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}(ref T, bool)"/>
20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
21+
public static void ReadNumber<T>(ref readonly this BinaryBufferReader reader, ref T value)
22+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
23+
reader.ReadNumber(ref value, IsUnsigned);
24+
25+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}( ref T?, bool)"/>
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
public static void ReadNumber<T>(ref readonly this BinaryBufferReader reader, ref T? value)
28+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
29+
reader.ReadNumber(ref value, IsUnsigned);
30+
31+
/// <inheritdoc cref="BinaryBufferReader.ReadNullableNumber{T}(bool)"/>
32+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
33+
public static T? ReadNullableNumber<T>(ref readonly this BinaryBufferReader reader)
34+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
35+
reader.ReadNullableNumber<T>(IsUnsigned);
36+
}
37+
38+
/// <summary>
39+
/// Unsigned number extensions
40+
/// </summary>
41+
public static class BinarySerializerUnsignedExtensions
42+
{
43+
const bool IsUnsigned = true;
44+
45+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}(bool)"/>
46+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
47+
public static T ReadNumber<T>(ref readonly this BinaryBufferReader reader)
48+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
49+
reader.ReadNumber<T>(IsUnsigned);
50+
51+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}(ref T, bool)"/>
52+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
53+
public static void ReadNumber<T>(ref readonly this BinaryBufferReader reader, ref T value)
54+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
55+
reader.ReadNumber(ref value, IsUnsigned);
56+
57+
/// <inheritdoc cref="BinaryBufferReader.ReadNumber{T}( ref T?, bool)"/>
58+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59+
public static void ReadNumber<T>(ref readonly this BinaryBufferReader reader, ref T? value)
60+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
61+
reader.ReadNumber(ref value, IsUnsigned);
62+
63+
/// <inheritdoc cref="BinaryBufferReader.ReadNullableNumber{T}(bool)"/>
64+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
65+
public static T? ReadNullableNumber<T>(ref readonly this BinaryBufferReader reader)
66+
where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T> =>
67+
reader.ReadNullableNumber<T>(IsUnsigned);
68+
}

src/Backdash/Serialization/BinarySpanWriter.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Runtime.InteropServices;
66
using Backdash.Core;
77
using Backdash.Network;
8+
using Backdash.Serialization.Internal;
89

910
namespace Backdash.Serialization;
1011

@@ -14,28 +15,47 @@ namespace Backdash.Serialization;
1415
[DebuggerDisplay("Written: {WrittenCount}")]
1516
public readonly ref struct BinarySpanWriter
1617
{
18+
readonly ref int offset;
19+
readonly Span<byte> buffer;
20+
readonly EndiannessSerializer.INumberSerializer numberSerializer;
21+
1722
/// <summary>
1823
/// Initialize a new <see cref="BinarySpanWriter" /> for <paramref name="buffer" />
1924
/// </summary>
2025
/// <param name="buffer">Byte buffer to be written</param>
2126
/// <param name="offset">Write offset reference</param>
22-
/// <param name="endianness">Serialization endianness</param>
27+
/// <param name="numberSerializer">Number serializer instance</param>
2328
public BinarySpanWriter(
2429
scoped in Span<byte> buffer,
2530
ref int offset,
26-
Endianness? endianness = null
31+
EndiannessSerializer.INumberSerializer numberSerializer
2732
)
2833
{
34+
ArgumentNullException.ThrowIfNull(numberSerializer);
2935
this.buffer = buffer;
3036
this.offset = ref offset;
31-
Endianness = endianness ?? Platform.Endianness;
37+
this.numberSerializer = numberSerializer;
3238
}
3339

34-
readonly ref int offset;
35-
readonly Span<byte> buffer;
40+
/// <summary>
41+
/// Initialize a new <see cref="BinarySpanWriter" /> for <paramref name="buffer" />
42+
/// </summary>
43+
/// <param name="buffer">Byte buffer to be written</param>
44+
/// <param name="offset">Write offset reference</param>
45+
/// <param name="endianness">Serialization endianness</param>
46+
public BinarySpanWriter(scoped in Span<byte> buffer, ref int offset, Endianness endianness)
47+
: this(in buffer, ref offset, EndiannessSerializer.Get(endianness)) { }
48+
49+
/// <summary>
50+
/// Initialize a new <see cref="BinarySpanWriter" /> for <paramref name="buffer" />
51+
/// </summary>
52+
/// <param name="buffer">Byte buffer to be written</param>
53+
/// <param name="offset">Write offset reference</param>
54+
public BinarySpanWriter(scoped in Span<byte> buffer, ref int offset)
55+
: this(in buffer, ref offset, Platform.Endianness) { }
3656

37-
/// <summary>Gets or init the value to define which endianness should be used for serialization.</summary>
38-
public readonly Endianness Endianness;
57+
/// <summary>Gets current serialization endianness.</summary>
58+
public Endianness Endianness => numberSerializer.Endianness;
3959

4060
/// <summary>Total written byte count.</summary>
4161
public int WrittenCount => offset;

src/Backdash/Serialization/Internal/BinarySerializerFactory.cs

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

45
namespace Backdash.Serialization.Internal;
@@ -7,7 +8,7 @@ static class BinarySerializerFactory
78
{
89
public static IBinarySerializer<TInput> ForInteger<TInput>(Endianness? endianness = null)
910
where TInput : unmanaged, IBinaryInteger<TInput>, IMinMaxValue<TInput> =>
10-
IntegerBinarySerializer.Create<TInput>(endianness);
11+
IntegerBinarySerializer.Create<TInput>(Mem.IsUnsigned<TInput>(), endianness);
1112

1213
public static IBinarySerializer<TInput> ForInteger<TInput>(bool isUnsigned, Endianness? endianness = null)
1314
where TInput : unmanaged, IBinaryInteger<TInput> =>

0 commit comments

Comments
 (0)