Skip to content

Commit 7fb591d

Browse files
committed
BonSerializer List<T> and HashSet<T>
1 parent 9be4069 commit 7fb591d

File tree

4 files changed

+214
-8
lines changed

4 files changed

+214
-8
lines changed

BTDB/Serialization/BonSerializer.cs

+96-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
35
using System.IO;
46
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
58
using BTDB.Bon;
9+
using BTDB.IL;
610
using BTDB.StreamLayer;
711

812
namespace BTDB.Serialization;
@@ -62,7 +66,10 @@ enum BonSerializerCmd : byte
6266
GetByOffsetAndWriteObject,
6367
GetCustomAndWriteObject,
6468
InitArray,
65-
NextArray
69+
NextArray,
70+
InitList,
71+
InitHashSet,
72+
NextHashSet
6673
}
6774

6875
public ref struct BonSerializerCtx
@@ -237,6 +244,27 @@ public void Generate()
237244
var elementType = _type.GetElementType()!;
238245
WriteCmdByType(null, elementType);
239246
_memWriter.WriteUInt8((byte)BonSerializerCmd.NextArray);
247+
return;
248+
}
249+
250+
if (_type.SpecializationOf(typeof(List<>)) is { } listType)
251+
{
252+
_memWriter.WriteUInt8((byte)BonSerializerCmd.InitList);
253+
var elementType = listType.GetGenericArguments()[0];
254+
WriteCmdByType(null, elementType);
255+
_memWriter.WriteUInt8((byte)BonSerializerCmd.NextArray);
256+
return;
257+
}
258+
259+
if (_type.SpecializationOf(typeof(HashSet<>)) is { } hashSetType)
260+
{
261+
_memWriter.WriteUInt8((byte)BonSerializerCmd.InitHashSet);
262+
var elementType = hashSetType.GetGenericArguments()[0];
263+
var layout = RawData.GetHashSetEntriesLayout(elementType);
264+
_memWriter.WriteVUInt32(layout.Offset);
265+
WriteCmdByType(null, elementType);
266+
_memWriter.WriteUInt8((byte)BonSerializerCmd.NextHashSet);
267+
return;
240268
}
241269

242270
var classMetadata = ReflectionMetadata.FindByType(_type);
@@ -245,6 +273,8 @@ public void Generate()
245273
AddClass(classMetadata);
246274
return;
247275
}
276+
277+
throw new NotSupportedException("BonSerialization of " + _type.ToSimpleName() + " is not supported.");
248278
}
249279

250280
public unsafe BonSerialize Build()
@@ -259,6 +289,7 @@ public unsafe BonSerialize Build()
259289
object? tempObject2 = null;
260290
uint offset = 0;
261291
uint offsetDelta = 0;
292+
uint offsetOffset = 0;
262293
uint offsetFinal = 0;
263294
uint offsetLabel = 0;
264295
UInt128 tempBytes = default;
@@ -590,7 +621,7 @@ public unsafe BonSerialize Build()
590621
{
591622
tempObject2 = Unsafe.As<byte, object>(ref value);
592623
var count = Unsafe.As<byte, int>(ref RawData.Ref(tempObject2, (uint)Unsafe.SizeOf<nint>()));
593-
ref var mt = ref RawData.MethodTableRef(tempObject2);
624+
ref readonly var mt = ref RawData.MethodTableRef(tempObject2);
594625
offset = mt.BaseSize - (uint)Unsafe.SizeOf<nint>();
595626
offsetDelta = mt.ComponentSize;
596627
offsetFinal = offset + offsetDelta * (uint)count;
@@ -613,6 +644,69 @@ public unsafe BonSerialize Build()
613644

614645
break;
615646
}
647+
case BonSerializerCmd.InitList:
648+
{
649+
tempObject2 = Unsafe.As<byte, object>(ref value);
650+
var count = Unsafe.As<ICollection>(tempObject2).Count;
651+
tempObject2 = RawData.ListItems(Unsafe.As<List<object>>(tempObject2));
652+
ref readonly var mt = ref RawData.MethodTableRef(tempObject2);
653+
offset = mt.BaseSize - (uint)Unsafe.SizeOf<nint>();
654+
offsetDelta = mt.ComponentSize;
655+
offsetFinal = offset + offsetDelta * (uint)count;
656+
offsetLabel = (uint)reader.GetCurrentPositionWithoutController();
657+
ctx.Builder.StartArray();
658+
break;
659+
}
660+
case BonSerializerCmd.InitHashSet:
661+
{
662+
tempObject2 = Unsafe.As<byte, object>(ref value);
663+
var count = Unsafe.As<byte, int>(ref RawData.Ref(tempObject2,
664+
RawData.Align(8 + 4 * (uint)Unsafe.SizeOf<nint>(), 8)));
665+
tempObject2 = RawData.HashSetEntries(Unsafe.As<HashSet<object>>(tempObject2));
666+
offsetOffset = reader.ReadVUInt32();
667+
ref readonly var mt = ref RawData.MethodTableRef(tempObject2);
668+
offset = mt.BaseSize - (uint)Unsafe.SizeOf<nint>();
669+
offsetDelta = mt.ComponentSize;
670+
offsetFinal = offset + offsetDelta * (uint)count;
671+
offsetLabel = (uint)reader.GetCurrentPositionWithoutController();
672+
ctx.Builder.StartArray();
673+
while (offset < offsetFinal)
674+
{
675+
if (Unsafe.As<byte, int>(ref RawData.Ref(tempObject2, offset + 4)) >= -1) break;
676+
offset += offsetDelta;
677+
}
678+
679+
if (offset >= offsetFinal)
680+
{
681+
ctx.Builder.FinishArray();
682+
return;
683+
}
684+
685+
offset += offsetOffset;
686+
break;
687+
}
688+
case BonSerializerCmd.NextHashSet:
689+
{
690+
offset += offsetDelta - offsetOffset;
691+
while (offset < offsetFinal)
692+
{
693+
if (Unsafe.As<byte, int>(ref RawData.Ref(tempObject2, offset + 4)) >= -1) break;
694+
offset += offsetDelta;
695+
}
696+
697+
if (offset < offsetFinal)
698+
{
699+
reader.SetCurrentPositionWithoutController(offsetLabel);
700+
offset += offsetOffset;
701+
}
702+
else
703+
{
704+
ctx.Builder.FinishArray();
705+
return;
706+
}
707+
708+
break;
709+
}
616710
default:
617711
throw new InvalidDataException("Unknown command in BonSerializer " + (byte)cmd + " at " +
618712
(reader.GetCurrentPosition() - 1));

BTDB/Serialization/RawData.cs

+98-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
5+
using BTDB.IL;
46

57
namespace BTDB.Serialization;
68

@@ -43,8 +45,102 @@ public struct MethodTable
4345
[FieldOffset(4)] public uint BaseSize;
4446
}
4547

46-
public static unsafe ref MethodTable MethodTableRef(object @object)
48+
public static unsafe ref readonly MethodTable MethodTableRef(object @object)
4749
{
48-
return ref *(MethodTable *)Unsafe.As<byte, nint>(ref Ref(@object));
50+
return ref *(MethodTable*)Unsafe.As<byte, nint>(ref Ref(@object));
51+
}
52+
53+
public static unsafe ref readonly MethodTable MethodTableOf(Type type)
54+
{
55+
return ref *(MethodTable*)type.TypeHandle.Value;
56+
}
57+
58+
// Array returned could have more items than are actually in the list
59+
public static ref object[] ListItems(List<object> @this)
60+
{
61+
return ref Unsafe.As<byte, object[]>(ref Ref(@this, (uint)Unsafe.SizeOf<object>()));
62+
}
63+
64+
public static ref HashSetEntry[] HashSetEntries(HashSet<object> @this)
65+
{
66+
return ref Unsafe.As<byte, HashSetEntry[]>(ref Ref(@this, 2 * (uint)Unsafe.SizeOf<object>()));
67+
}
68+
69+
public struct HashSetEntry
70+
{
71+
public int HashCode;
72+
public int Next;
73+
public byte Value;
74+
}
75+
76+
public static (uint Offset, uint Size) GetHashSetEntriesLayout(Type memberType)
77+
{
78+
var sa = Combine((8, 4), GetSizeAndAlign(memberType));
79+
return (Align(8, sa.Align), sa.Size);
80+
}
81+
82+
public static uint CombineAlign(uint align1, uint align2)
83+
{
84+
return align1 > align2 ? align1 : align2;
85+
}
86+
87+
public static uint Align(uint size, uint align)
88+
{
89+
return (size + align - 1) & ~(align - 1);
90+
}
91+
92+
public static (uint Size, uint Align) GetSizeAndAlign(Type type)
93+
{
94+
if (!type.IsValueType)
95+
{
96+
return ((uint)Unsafe.SizeOf<object>(), (uint)Unsafe.SizeOf<object>());
97+
}
98+
99+
if (type.IsEnum)
100+
{
101+
type = Enum.GetUnderlyingType(type);
102+
}
103+
104+
if (Nullable.GetUnderlyingType(type) is { } underlyingType)
105+
{
106+
var res = GetSizeAndAlign(underlyingType);
107+
return (Align(1, res.Align) + res.Size, res.Align);
108+
}
109+
110+
if (type.SpecializationOf(typeof(KeyValuePair<,>)) is { } kvpType)
111+
{
112+
var arguments = kvpType.GetGenericArguments();
113+
var keySizeAlign = GetSizeAndAlign(arguments[0]);
114+
var valueSizeAlign = GetSizeAndAlign(arguments[1]);
115+
return Combine(keySizeAlign, valueSizeAlign);
116+
}
117+
118+
if (type.SpecializationOf(typeof(ValueTuple<,>)) is { } vt2Type)
119+
{
120+
var arguments = vt2Type.GetGenericArguments();
121+
return Combine(GetSizeAndAlign(arguments[0]), GetSizeAndAlign(arguments[1]));
122+
}
123+
124+
if (type.SpecializationOf(typeof(ValueTuple<,,>)) is { } vt3Type)
125+
{
126+
var arguments = vt3Type.GetGenericArguments();
127+
return Combine(GetSizeAndAlign(arguments[0]), GetSizeAndAlign(arguments[1]), GetSizeAndAlign(arguments[2]));
128+
}
129+
130+
var size = MethodTableOf(type).BaseSize;
131+
return (size, size);
132+
}
133+
134+
static (uint Size, uint Align) Combine((uint Size, uint Align) f1, (uint Size, uint Align) f2)
135+
{
136+
var align = CombineAlign(f1.Align, f2.Align);
137+
return (Align(Align(f1.Size, f2.Align) + f2.Size, align), align);
138+
}
139+
140+
static (uint Size, uint Align) Combine((uint Size, uint Align) f1, (uint Size, uint Align) f2,
141+
(uint Size, uint Align) f3)
142+
{
143+
var align = CombineAlign(CombineAlign(f1.Align, f2.Align), f3.Align);
144+
return (Align(Align(Align(f1.Size, f2.Align) + f2.Size, f3.Align) + f3.Size, align), align);
49145
}
50146
}

BTDBTest/SerializationTests/BonSerializerTest.SerializeDeserializeAllSupportedTypes.approved.txt

+12-1
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,21 @@
3333
"Float32": 0,
3434
"Float64": 0,
3535
"Self": null,
36-
"DoubleArray": null
36+
"DoubleArray": null,
37+
"IntList": null,
38+
"UShortSet": null
3739
},
3840
"DoubleArray": [
3941
2.718281828459045,
4042
3.141592653589793
43+
],
44+
"IntList": [
45+
1,
46+
20,
47+
300
48+
],
49+
"UShortSet": [
50+
666,
51+
12345
4152
]
4253
}

BTDBTest/SerializationTests/BonSerializerTest.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using Assent;
34
using BTDB;
45
using BTDB.Bon;
@@ -29,6 +30,8 @@ public class AllSupportedTypes
2930
public double Float64;
3031
public AllSupportedTypes? Self;
3132
public double[]? DoubleArray;
33+
public List<int>? IntList;
34+
public HashSet<ushort>? UShortSet;
3235
}
3336

3437
[Fact]
@@ -37,9 +40,11 @@ public void SerializeDeserializeAllSupportedTypes()
3740
var obj = new AllSupportedTypes
3841
{
3942
Str = "Hello", Bool = true, Int8 = 42, Int16 = 1234, Int32 = 12345678, Int64 = long.MaxValue,
40-
UInt8 = 42, UInt16 = 1234, UInt32 = 12345678, UInt64 = ulong.MaxValue, DateTime = new(2024, 2, 11, 14, 4, 30),
41-
Guid = Guid.Parse("9e251065-0873-49bc-8fd9-266cc9aa39d3"), Float16 = (Half) 3.14, Float32 = 3.14f, Float64 = Math.PI,
42-
Self = new (), DoubleArray = [Math.E, Math.PI]
43+
UInt8 = 42, UInt16 = 1234, UInt32 = 12345678, UInt64 = ulong.MaxValue,
44+
DateTime = new(2024, 2, 11, 14, 4, 30),
45+
Guid = Guid.Parse("9e251065-0873-49bc-8fd9-266cc9aa39d3"), Float16 = (Half)3.14, Float32 = 3.14f,
46+
Float64 = Math.PI,
47+
Self = new(), DoubleArray = [Math.E, Math.PI], IntList = [1, 20, 300], UShortSet = [666, 12345]
4348
};
4449
var builder = new BonBuilder();
4550
BonSerializerFactory.Serialize(ref builder, obj);

0 commit comments

Comments
 (0)