Skip to content

Commit 7e7aafd

Browse files
authored
Enhance State Serialization API (#114)
1 parent 0862ad8 commit 7e7aafd

24 files changed

+2796
-892
lines changed

samples/SpaceWar.Shared/Extensions.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ public static void Write(this BinaryBufferWriter writer, in Vector2 rect)
2121
writer.Write(rect.Y);
2222
}
2323

24-
public static Rectangle ReadRectangle(this BinaryBufferReader reader) =>
25-
new()
26-
{
27-
X = reader.ReadInt32(),
28-
Y = reader.ReadInt32(),
29-
Width = reader.ReadInt32(),
30-
Height = reader.ReadInt32(),
31-
};
24+
public static void Read(this BinaryBufferReader reader, ref Vector2 vector)
25+
{
26+
reader.Read(ref vector.X);
27+
reader.Read(ref vector.Y);
28+
}
29+
30+
public static void Read(this BinaryBufferReader reader, ref Rectangle rect)
31+
{
32+
rect.X = reader.ReadInt32();
33+
rect.Y = reader.ReadInt32();
34+
rect.Width = reader.ReadInt32();
35+
rect.Height = reader.ReadInt32();
36+
}
3237
}

samples/SpaceWar.Shared/Logic/GameState.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public void SaveState(ref readonly BinaryBufferWriter writer)
4747

4848
public void LoadState(ref readonly BinaryBufferReader reader)
4949
{
50-
Bounds = reader.ReadRectangle();
51-
FrameNumber = reader.ReadInt32();
52-
reader.Read(in Ships);
50+
reader.Read(ref Bounds);
51+
reader.Read(ref FrameNumber);
52+
reader.Read(Ships);
5353
}
5454

5555
static GameInput GetShipAI(in Ship ship) => new(

samples/SpaceWar.Shared/Logic/Ship.cs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Backdash.Serialization;
2-
using Backdash.Serialization.Numerics;
32

43
namespace SpaceWar.Logic;
54

@@ -17,7 +16,7 @@ public sealed record Ship : IBinarySerializable
1716
public int Invincible;
1817
public int Score;
1918
public int Thrust;
20-
public Missile Missile;
19+
public Missile Missile = new();
2120
public readonly Bullet[] Bullets = new Bullet[Config.MaxBullets];
2221

2322
public void Serialize(ref readonly BinaryBufferWriter writer)
@@ -34,28 +33,28 @@ public void Serialize(ref readonly BinaryBufferWriter writer)
3433
writer.Write(in Invincible);
3534
writer.Write(in Score);
3635
writer.Write(in Thrust);
36+
writer.Write(in Missile);
3737

3838
// Caution: WriteStruct not normalize endianness
39-
writer.WriteStruct(in Missile);
4039
writer.WriteStruct(in Bullets);
4140
}
4241

4342
public void Deserialize(ref readonly BinaryBufferReader reader)
4443
{
45-
Id = reader.ReadByte();
46-
Active = reader.ReadBoolean();
47-
Position = reader.ReadVector2();
48-
Velocity = reader.ReadVector2();
49-
Radius = reader.ReadInt32();
50-
Heading = reader.ReadInt32();
51-
Health = reader.ReadInt32();
52-
FireCooldown = reader.ReadInt32();
53-
MissileCooldown = reader.ReadInt32();
54-
Invincible = reader.ReadInt32();
55-
Score = reader.ReadInt32();
56-
Thrust = reader.ReadInt32();
44+
reader.Read(ref Id);
45+
reader.Read(ref Active);
46+
reader.Read(ref Position);
47+
reader.Read(ref Velocity);
48+
reader.Read(ref Radius);
49+
reader.Read(ref Heading);
50+
reader.Read(ref Health);
51+
reader.Read(ref FireCooldown);
52+
reader.Read(ref MissileCooldown);
53+
reader.Read(ref Invincible);
54+
reader.Read(ref Score);
55+
reader.Read(ref Thrust);
56+
reader.Read(Missile);
5757

58-
reader.ReadStruct(ref Missile);
5958
reader.ReadStruct(in Bullets);
6059
}
6160
}
@@ -67,7 +66,7 @@ public record struct Bullet
6766
public Vector2 Velocity;
6867
}
6968

70-
public record struct Missile
69+
public record Missile : IBinarySerializable
7170
{
7271
public bool Active;
7372
public int ExplodeTimeout;
@@ -77,5 +76,29 @@ public record struct Missile
7776
public int Heading;
7877
public Vector2 Position;
7978
public Vector2 Velocity;
80-
public readonly bool IsExploding() => ExplodeTimeout is 0 && HitBoxTime > 0;
79+
public bool IsExploding() => ExplodeTimeout is 0 && HitBoxTime > 0;
80+
81+
public void Serialize(ref readonly BinaryBufferWriter writer)
82+
{
83+
writer.Write(in Active);
84+
writer.Write(in ExplodeTimeout);
85+
writer.Write(in HitBoxTime);
86+
writer.Write(in ExplosionRadius);
87+
writer.Write(in ProjectileRadius);
88+
writer.Write(in Heading);
89+
writer.Write(in Position);
90+
writer.Write(in Velocity);
91+
}
92+
93+
public void Deserialize(ref readonly BinaryBufferReader reader)
94+
{
95+
reader.Read(ref Active);
96+
reader.Read(ref ExplodeTimeout);
97+
reader.Read(ref HitBoxTime);
98+
reader.Read(ref ExplosionRadius);
99+
reader.Read(ref ProjectileRadius);
100+
reader.Read(ref Heading);
101+
reader.Read(ref Position);
102+
reader.Read(ref Velocity);
103+
}
81104
}

src/Backdash.Analyzers/SourceGenerationHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void BuildArrayMember(ITypeSymbol itemType, ClassMember member)
9999
writes.AppendLine($"binaryWriter.Write(data.{member.Name});");
100100

101101
reads.Append(tab2);
102-
reads.AppendLine($"binaryReader.Read{itemType.Name}(result.{member.Name});");
102+
reads.AppendLine($"binaryReader.Read(result.{member.Name});");
103103
}
104104
else
105105
{

src/Backdash/Core/Mem.cs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,16 @@ public static Span<byte> AsBytesUnsafe<T>(scoped ref readonly T data) where T :
3838
}
3939

4040
[MethodImpl(MethodImplOptions.AggressiveInlining)]
41-
public static ref TInt EnumAsInteger<TEnum, TInt>(ref TEnum enumValue)
41+
public static ref TInt EnumAsInteger<TEnum, TInt>(ref readonly TEnum enumValue)
4242
where TEnum : unmanaged, Enum
43-
where TInt : unmanaged, IBinaryInteger<TInt>
44-
{
45-
if (Unsafe.SizeOf<TEnum>() != Unsafe.SizeOf<TInt>()) throw new NetcodeException("type mismatch");
46-
return ref Unsafe.As<TEnum, TInt>(ref enumValue);
47-
}
43+
where TInt : unmanaged, IBinaryInteger<TInt> =>
44+
ref Unsafe.As<TEnum, TInt>(ref Unsafe.AsRef(in enumValue));
4845

4946
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5047
public static ref TEnum IntegerAsEnum<TEnum, TInt>(ref TInt intValue)
5148
where TEnum : unmanaged, Enum
52-
where TInt : unmanaged, IBinaryInteger<TInt>
53-
{
54-
if (Unsafe.SizeOf<TEnum>() != Unsafe.SizeOf<TInt>()) throw new NetcodeException("type mismatch");
55-
return ref Unsafe.As<TInt, TEnum>(ref intValue);
56-
}
49+
where TInt : unmanaged, IBinaryInteger<TInt> =>
50+
ref Unsafe.As<TInt, TEnum>(ref intValue);
5751

5852
public static bool EqualBytes(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right, bool truncate = false)
5953
{
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace Backdash.Data;
2+
3+
sealed class DefaultObjectPool<T> : IObjectPool<T> where T : class, new()
4+
{
5+
public static readonly IObjectPool<T> Instance = new DefaultObjectPool<T>();
6+
7+
const int MaxCapacity = 99; // -1 to account for fastItem
8+
9+
int numItems;
10+
readonly Stack<T> items = new(MaxCapacity);
11+
readonly HashSet<T> set = new(MaxCapacity, ReferenceEqualityComparer.Instance);
12+
T? fastItem;
13+
14+
public T Rent()
15+
{
16+
var item = fastItem;
17+
18+
if (item is not null)
19+
{
20+
fastItem = null;
21+
return item;
22+
}
23+
24+
if (!items.TryPop(out item))
25+
return new();
26+
27+
numItems--;
28+
set.Remove(item);
29+
return item;
30+
}
31+
32+
public void Return(T value)
33+
{
34+
ArgumentNullException.ThrowIfNull(value);
35+
36+
if (ReferenceEquals(fastItem, value) || set.Contains(value))
37+
return;
38+
39+
if (fastItem is null)
40+
{
41+
fastItem = value;
42+
return;
43+
}
44+
45+
if (numItems >= MaxCapacity)
46+
return;
47+
48+
numItems++;
49+
items.Push(value);
50+
set.Add(value);
51+
}
52+
}

src/Backdash/Data/IObjectPool.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Backdash.Data;
2+
3+
/// <summary>
4+
/// Defines a object pooling contract
5+
/// </summary>
6+
/// <typeparam name="T"></typeparam>
7+
public interface IObjectPool<T>
8+
{
9+
/// <summary>
10+
/// Rent an instance on <typeparamref name="T"/> from the pool
11+
/// </summary>
12+
T Rent();
13+
14+
/// <summary>
15+
/// Return <paramref name="value"/> to the pool
16+
/// </summary>
17+
void Return(T value);
18+
}

src/Backdash/NetcodeOptions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,21 @@ public sealed class NetcodeOptions
3232
public int RecommendationInterval { get; init; } = Default.RecommendationInterval;
3333

3434
/// <summary>
35-
/// Forces serialization byte order to network order <see cref="Endianness.BigEndian"/>.
35+
/// Forces input serialization byte order to network order <see cref="Endianness.BigEndian"/>.
3636
/// </summary>
3737
/// <seealso cref="Endianness"/>
3838
/// <value>Defaults to <see langword="true"/></value>
3939
public bool UseNetworkEndianness { get; init; } = true;
4040

41+
/// <summary>
42+
/// Sets the endianness used for state serialization.
43+
/// If null, the same endianness as the input serializer will be used.
44+
/// </summary>
45+
/// <seealso cref="Endianness"/>
46+
/// <seealso cref="UseNetworkEndianness"/>
47+
/// <value>Defaults to <see cref="Endianness.LittleEndian"/></value>
48+
public Endianness? StateSerializationEndianness { get; init; } = Endianness.LittleEndian;
49+
4150
/// <summary>
4251
/// Max length for player input queues.
4352
/// </summary>

src/Backdash/Network/Messages/InputMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void Deserialize(in BinaryBufferReader reader)
5757
InputSize = reader.ReadByte();
5858
NumBits = reader.ReadUInt16();
5959
var bitCount = (int)Math.Ceiling(NumBits / (float)ByteSize.ByteToBits);
60-
reader.ReadByte(Bits[..bitCount]);
60+
reader.Read(Bits[..bitCount]);
6161
}
6262

6363
public readonly bool TryFormat(

0 commit comments

Comments
 (0)