Skip to content

Commit c6e9661

Browse files
committed
null ref serialization helpers
1 parent 40d779c commit c6e9661

File tree

5 files changed

+172
-2
lines changed

5 files changed

+172
-2
lines changed

src/Backdash/Data/ObjectPool.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,14 @@ public void Return(T value)
6666
items.Push(value);
6767
set.Add(value);
6868
}
69+
70+
public void Clear()
71+
{
72+
numItems = 0;
73+
fastItem = null;
74+
items.Clear();
75+
set.Clear();
76+
}
77+
78+
public int Count => numItems + (fastItem is null ? 0 : 1);
6979
}

src/Backdash/Network/Messages/KeepAlive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
namespace Backdash.Network.Messages;
44

5-
[Serializable, StructLayout(LayoutKind.Sequential)]
5+
[Serializable, StructLayout(LayoutKind.Sequential, Size = 1)]
66
readonly record struct KeepAlive;

src/Backdash/Serialization/BinaryBufferReader.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,34 @@ public void ReadNumber<T>(ref T? value, bool isUnsigned) where T : unmanaged, IB
365365
return value;
366366
}
367367

368+
/// <summary>Reads a nullable <see cref="IBinarySerializable"/> <paramref name="value"/> from buffer.</summary>
369+
/// <typeparam name="T">A nullable reference type that implements <see cref="IBinarySerializable"/>.</typeparam>
370+
public void ReadNullable<T>(ref T? value, IObjectPool<T> pool, bool forceReturn = true)
371+
where T : class, IBinarySerializable
372+
{
373+
if (ReadBoolean())
374+
{
375+
if (forceReturn)
376+
{
377+
if (value is not null)
378+
pool.Return(value);
379+
380+
value = pool.Rent();
381+
}
382+
else
383+
value ??= pool.Rent();
384+
385+
Read(value);
386+
}
387+
else
388+
value = null;
389+
}
390+
391+
/// <summary>Reads a nullable <see cref="IBinarySerializable"/> <paramref name="value"/> from buffer.</summary>
392+
/// <typeparam name="T">A nullable reference type that implements <see cref="IBinarySerializable"/>.</typeparam>
393+
public void ReadNullable<T>(ref T? value, bool forceReturn = true) where T : class, IBinarySerializable, new() =>
394+
ReadNullable(ref value, DefaultObjectPool<T>.Instance, forceReturn);
395+
368396
/// <summary>Reads a <see cref="IBinarySerializable"/> <paramref name="value"/> from buffer.</summary>
369397
/// <typeparam name="T">A value type that implements <see cref="IBinarySerializable"/>.</typeparam>
370398
public void Read<T>(ref T value) where T : struct, IBinarySerializable => value.Deserialize(in this);
@@ -417,9 +445,20 @@ public void Read<T>(in CircularBuffer<T> values) where T : IBinarySerializable
417445
}
418446

419447
/// <summary>Reads a <see cref="IBinarySerializable"/> <paramref name="value"/> from buffer.</summary>
420-
/// <typeparam name="T">A reference value type that implements <see cref="IBinarySerializable"/>.</typeparam>
448+
/// <typeparam name="T">A reference type that implements <see cref="IBinarySerializable"/>.</typeparam>
421449
public void Read<T>(T value) where T : class, IBinarySerializable => value.Deserialize(in this);
422450

451+
/// <inheritdoc cref="Read{T}(T)"/>
452+
public void Read<T>(
453+
ref T? value, bool nullable, bool forceReturn = true
454+
) where T : class, IBinarySerializable, new()
455+
{
456+
if (nullable)
457+
ReadNullable(ref value, forceReturn);
458+
else
459+
Read(value!);
460+
}
461+
423462
/// <summary>Reads a span of <see cref="IBinarySerializable"/> <paramref name="values"/> into buffer.</summary>
424463
/// <typeparam name="T">A list of a reference type that implements <see cref="IBinarySerializable"/>.</typeparam>
425464
public void Read<T>(in Span<T> values, in IObjectPool<T> pool) where T : class, IBinarySerializable

src/Backdash/Serialization/BinaryBufferWriter.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,18 @@ public void Write<T>(ref readonly T? value) where T : struct, IBinarySerializabl
530530
/// <typeparam name="T">A type that implements <see cref="IBinarySerializable"/>.</typeparam>
531531
public void Write<T>(T value) where T : class, IBinarySerializable => value.Serialize(in this);
532532

533+
/// <summary>Writes a <see cref="IBinarySerializable"/> <paramref name="value"/> into buffer.</summary>
534+
/// <typeparam name="T">A type that implements <see cref="IBinarySerializable"/>.</typeparam>
535+
/// <param name="value">Value to be written</param>
536+
/// <param name="nullable">If true write as nullable reference type.</param>
537+
public void Write<T>(T? value, bool nullable) where T : class, IBinarySerializable
538+
{
539+
if (nullable)
540+
WriteNullable(value);
541+
else
542+
Write(value!);
543+
}
544+
533545
/// <summary>Writes span of <see cref="IBinarySerializable"/> <paramref name="values"/> into buffer.</summary>
534546
/// <typeparam name="T">A type that implements <see cref="IBinarySerializable"/>.</typeparam>
535547
public void Write<T>(in ReadOnlySpan<T> values) where T : IBinarySerializable
@@ -573,6 +585,19 @@ public void Write(in StringBuilder value)
573585
value.CopyTo(0, AllocSpan<char>(len), len);
574586
}
575587

588+
/// <summary>Writes a maybe null <see cref="IBinarySerializable"/> <paramref name="value"/> into buffer.</summary>
589+
/// <typeparam name="T">A nullable reference type that implements <see cref="IBinarySerializable"/>.</typeparam>
590+
public void WriteNullable<T>(T? value) where T : class, IBinarySerializable
591+
{
592+
if (value is null)
593+
Write(false);
594+
else
595+
{
596+
Write(true);
597+
Write(value);
598+
}
599+
}
600+
576601
/// <summary>Writes an unmanaged struct into buffer.</summary>
577602
public void WriteStruct<T>(in T value) where T : unmanaged => Write(Mem.AsBytes(in value));
578603

tests/Backdash.Tests/Specs/Unit/Serialization/BinaryBufferReadWriteValueTests.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Backdash.Serialization.Numerics;
88
using Backdash.Tests.TestUtils;
99
using Backdash.Tests.TestUtils.Types;
10+
using Microsoft.FSharp.Core;
1011

1112
// ReSharper disable CompareOfFloatsByEqualityOperator
1213
#pragma warning disable S1244
@@ -402,6 +403,101 @@ public bool SerializableObject(SimpleRefData value, SimpleRefData result, Endian
402403
return value == result;
403404
}
404405

406+
static DefaultObjectPool<SimpleRefData> DataPool =>
407+
(DefaultObjectPool<SimpleRefData>)DefaultObjectPool<SimpleRefData>.Instance;
408+
409+
[PropertyTest]
410+
public bool SerializableNullableObjectToObject(
411+
FSharpOption<SimpleRefData> valueOpt,
412+
SimpleRefData? result,
413+
Endianness endianness,
414+
bool forceReturn
415+
)
416+
{
417+
SimpleRefData? value = OptionModule.ToObj(valueOpt);
418+
419+
var size = Setup<SimpleStructData>(endianness, out var writer);
420+
var expectedSize = (value is null ? 0 : size) + 1;
421+
422+
writer.Write(value, nullable: true);
423+
writer.WrittenCount.Should().Be(expectedSize);
424+
425+
var reader = GetReader(writer);
426+
reader.Read(ref result, nullable: true, forceReturn);
427+
reader.ReadCount.Should().Be(expectedSize);
428+
429+
return value == result;
430+
}
431+
432+
[PropertyTest]
433+
public bool SerializableNullableObjectToNullRef(
434+
FSharpOption<SimpleRefData> valueOpt,
435+
Endianness endianness,
436+
bool forceReturn
437+
)
438+
{
439+
SimpleRefData? value = OptionModule.ToObj(valueOpt);
440+
SimpleRefData? result = null;
441+
442+
DataPool.Clear();
443+
var size = Setup<SimpleStructData>(endianness, out var writer);
444+
var expectedSize = (value is null ? 0 : size) + 1;
445+
446+
writer.Write(value, nullable: true);
447+
writer.WrittenCount.Should().Be(expectedSize);
448+
449+
var reader = GetReader(writer);
450+
reader.Read(ref result, nullable: true, forceReturn);
451+
reader.ReadCount.Should().Be(expectedSize);
452+
453+
DataPool.Count.Should().Be(0);
454+
return value == result;
455+
}
456+
457+
[PropertyTest]
458+
public bool SerializableNullableObjectShouldUsePool(
459+
FSharpOption<SimpleRefData> valueOpt,
460+
SimpleRefData? result,
461+
Endianness endianness,
462+
bool forceReturn
463+
)
464+
{
465+
DataPool.Clear();
466+
DataPool.Return(DataPool.Rent());
467+
SimpleRefData? value = OptionModule.ToObj(valueOpt);
468+
469+
Setup<SimpleStructData>(endianness, out var writer);
470+
writer.Write(value, nullable: true);
471+
472+
var reader = GetReader(writer);
473+
reader.Read(ref result, nullable: true, forceReturn);
474+
475+
if (forceReturn)
476+
DataPool.Count.Should().Be(1);
477+
478+
return value == result;
479+
}
480+
481+
[PropertyTest]
482+
public bool SerializableNullableObject(
483+
FSharpOption<SimpleRefData> valueOpt,
484+
FSharpOption<SimpleRefData> resultOpt,
485+
bool useNullable,
486+
Endianness endianness
487+
)
488+
{
489+
SimpleRefData? value = OptionModule.ToObj(valueOpt);
490+
SimpleRefData? result = OptionModule.ToObj(resultOpt);
491+
var withNullable = value is null || result is null || useNullable;
492+
Setup<SimpleStructData>(endianness, out var writer);
493+
writer.Write(value, nullable: withNullable);
494+
var reader = GetReader(writer);
495+
reader.Read(ref result, nullable: withNullable);
496+
497+
return value == result;
498+
}
499+
500+
405501
[Collection(SerialCollectionDefinition.Name)]
406502
public class BinaryIntegerTests
407503
{

0 commit comments

Comments
 (0)