public static ReadOnlySpan <byte> Deserialize(ReadOnlySpan <byte> input, Type type, out object value) { object result = Activator.CreateInstance(type) !; ReadOnlySpan <byte> itr = input; // Get the object's serialization settings. BitStructAttribute structAttribute = type.GetCustomAttribute <BitStructAttribute>() ?? BitStructAttribute.Default; if (type.StructLayoutAttribute == null || type.StructLayoutAttribute.Value != LayoutKind.Sequential) { throw new Exception($"Type {type.Name} must a LayoutKind.Sequential struct layout."); } // Gets the type's fields in the order in which they are declared. // Note: Sorting by MetadataToken sorts the fields by declaration order when StructLayout is set to LayoutKind.Sequential. IEnumerable <FieldInfo> fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .OrderBy((field) => field.MetadataToken); // Iterate through the object's fields. foreach (FieldInfo field in fields) { Type fieldType = field.FieldType; object fieldValue; if (fieldType.IsArray) { itr = DeserializeArray(structAttribute.Endianess, field, type.Name, itr, out fieldValue); } else { itr = DeserializeValue(structAttribute.Endianess, fieldType, itr, field.Name, out fieldValue); } field.SetValue(result, fieldValue); } value = result; return(itr); }
private static Span <byte> Serialize(Span <byte> output, object value) { Span <byte> itr = output; Type type = value.GetType(); // Get the object's serialization settings. BitStructAttribute structAttribute = type.GetCustomAttribute <BitStructAttribute>() ?? BitStructAttribute.Default; if (type.StructLayoutAttribute == null || type.StructLayoutAttribute.Value != LayoutKind.Sequential) { throw new Exception($"Type {type.Name} must have a LayoutKind.Sequential struct layout."); } // Gets the type's fields in the order in which they are declared. // Note: Sorting by MetadataToken sorts the fields by declaration order when StructLayout is set to LayoutKind.Sequential. IEnumerable <FieldInfo> fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .OrderBy((field) => field.MetadataToken); // Iterate through the object's fields. foreach (FieldInfo field in fields) { Type fieldType = field.FieldType; object fieldValueAsObject = field.GetValue(value) !; if (fieldType.IsArray) { itr = SerializeArray(structAttribute.Endianess, fieldValueAsObject, field, type.Name, itr); } else { itr = SerializeValue(structAttribute.Endianess, fieldValueAsObject, fieldType, field.Name, itr); } } return(itr); }
private void WriteSerializerClasses(SourceGeneratorContext context, List <INamedTypeSymbol> bitStructClasses, Dictionary <INamedTypeSymbol, ClassSerializeSizeInfo> classesSizeInfo, INamedTypeSymbol bitStructAttributeSymbol, INamedTypeSymbol bitArrayAttributeSymbol) { foreach (INamedTypeSymbol classSymbol in bitStructClasses) { ClassSerializeSizeInfo classSizeInfo = classesSizeInfo[classSymbol]; string classFullName = SourceGenUtils.GetTypeFullName(classSymbol); string serializerClassName = CreateSerializerName(classSymbol); BitStructAttribute bitStructAttribute = SourceGenUtils.GetAttribute <BitStructAttribute>(classSymbol, bitStructAttributeSymbol); var sourceBuilder = new StringBuilder(); sourceBuilder.Append($@" namespace {classSymbol.ContainingNamespace} {{ "); ITypeSymbol[] classContainingTypes = SourceGenUtils.GetContainingTypesList(classSymbol); foreach (ITypeSymbol containingType in classContainingTypes) { switch (containingType.TypeKind) { case TypeKind.Class: sourceBuilder.Append($@" partial class {containingType.Name} {{ "); break; case TypeKind.Struct: sourceBuilder.Append($@" partial struct {containingType.Name} {{ "); break; default: context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("TMP", "TMP", $"Only expecting struct or class containing types. Have {containingType.TypeKind}.", "TMP", DiagnosticSeverity.Error, true), Location.Create("TMP", new TextSpan(), new LinePositionSpan()))); return; } } sourceBuilder.Append($@" {SourceGenUtils.GetAccessibilityString(classSymbol.DeclaredAccessibility)} static class {serializerClassName} {{ "); var sizeFuncBuilder = new StringBuilder(); var serializeFuncBuilder = new StringBuilder(); var deserializeFuncBuilder = new StringBuilder(); if (classSizeInfo.Type == ClassSerializeSizeType.Const) { sizeFuncBuilder.Append($@" public const int Size = {classSizeInfo.ConstSize}; "); } sizeFuncBuilder.Append($@" public static int CalculateSize({classFullName} value) {{ int result = {classSizeInfo.ConstSize}; "); serializeFuncBuilder.Append($@" public static global::System.Span<byte> Serialize(global::System.Span<byte> output, {classFullName} value) {{ "); deserializeFuncBuilder.Append($@" public static global::System.ReadOnlySpan<byte> Deserialize(global::System.ReadOnlySpan<byte> input, out {classFullName} value) {{ value = new {classFullName}(); "); foreach (IFieldSymbol classFieldMember in GetClassFieldMembers(classSymbol)) { if (classFieldMember.Type.IsIntegerType() || classFieldMember.Type.TypeKind == TypeKind.Enum) { INamedTypeSymbol fieldType = (INamedTypeSymbol)classFieldMember.Type; IntegerOrEnumTypeInfo fieldTypeInfo = GetIntegerOrEnumTypeInfo(fieldType, bitStructAttribute.Endianess); serializeFuncBuilder.Append($@" if (!{fieldTypeInfo.SerializeFuncName}(output, {fieldTypeInfo.SerializeTypeCast}value.{classFieldMember.Name})) {{ throw new global::System.Exception(string.Format(""Not enough space to serialize field {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} output = output.Slice({fieldTypeInfo.TypeSize}); "); deserializeFuncBuilder.Append($@" {{ if (!{fieldTypeInfo.DeserializeFuncName}(input, out var fieldValue)) {{ throw new global::System.Exception(string.Format(""Not enough data to deserialize field {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} value.{classFieldMember.Name} = {fieldTypeInfo.DeserializeTypeCast}fieldValue; input = input.Slice({fieldTypeInfo.TypeSize}); }} "); } else if (classFieldMember.Type.TypeKind == TypeKind.Class || classFieldMember.Type.TypeKind == TypeKind.Struct) { if (!SourceGenUtils.HasAttribute(classFieldMember.Type, bitStructAttributeSymbol)) { // Type requires BitStruct attribute. return; } INamedTypeSymbol fieldType = (INamedTypeSymbol)classFieldMember.Type; ClassSerializeSizeInfo fieldTypeSizeInfo = classesSizeInfo[fieldType]; string serializerClassFullName = CreateSerializerFullName(fieldType); if (fieldTypeSizeInfo.Type == ClassSerializeSizeType.Dynamic) { sizeFuncBuilder.Append($@" result += {serializerClassFullName}.CalculateSize(value.{classFieldMember.Name}); "); } serializeFuncBuilder.Append($@" output = {serializerClassFullName}.Serialize(output, value.{classFieldMember.Name}); "); deserializeFuncBuilder.Append($@" {{ input = {serializerClassFullName}.Deserialize(input, out var fieldValue); value.{classFieldMember.Name} = fieldValue; }} "); } else if (classFieldMember.Type.TypeKind == TypeKind.Array) { BitArrayAttribute bitArrayAttribute = SourceGenUtils.GetAttribute <BitArrayAttribute>(classFieldMember, bitArrayAttributeSymbol); if (bitArrayAttribute == null) { // Type requires BitArray attribute. return; } IArrayTypeSymbol arrayType = (IArrayTypeSymbol)classFieldMember.Type; string elementTypeFullName = SourceGenUtils.GetTypeFullName(arrayType.ElementType); ClassSerializeSizeInfo elementTypeSizeInfo; string calculateElementSize; string serializeItem; string deserializeItem; if (arrayType.ElementType.IsIntegerType() || arrayType.ElementType.TypeKind == TypeKind.Enum) { IntegerOrEnumTypeInfo elementTypeInfo = GetIntegerOrEnumTypeInfo((INamedTypeSymbol)arrayType.ElementType, bitStructAttribute.Endianess); elementTypeSizeInfo = new ClassSerializeSizeInfo() { Type = ClassSerializeSizeType.Const, ConstSize = elementTypeInfo.TypeSize, }; calculateElementSize = $"result += {elementTypeInfo.TypeSize};"; serializeItem = $@" if (!{elementTypeInfo.SerializeFuncName}(output, {elementTypeInfo.SerializeTypeCast}item)) {{ throw new global::System.Exception(string.Format(""Not enough space to serialize item from list {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} output = output.Slice({elementTypeInfo.TypeSize}); "; deserializeItem = $@" if (!{elementTypeInfo.DeserializeFuncName}(input, out var tmp)) {{ throw new global::System.Exception(string.Format(""Not enough data to deserialize item from list {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} var item = {elementTypeInfo.DeserializeTypeCast}tmp; input = input.Slice({elementTypeInfo.TypeSize}); "; } else if (arrayType.ElementType.TypeKind == TypeKind.Class || arrayType.ElementType.TypeKind == TypeKind.Struct) { INamedTypeSymbol elementType = (INamedTypeSymbol)arrayType.ElementType; elementTypeSizeInfo = classesSizeInfo[elementType]; string elementSerializerClassFullName = CreateSerializerFullName(elementType); if (elementTypeSizeInfo.Type == ClassSerializeSizeType.Const) { calculateElementSize = $@"result += {elementSerializerClassFullName}.Size;"; } else { calculateElementSize = $@"result += {elementSerializerClassFullName}.CalculateSize(item);"; } serializeItem = $@"output = {elementSerializerClassFullName}.Serialize(output, item);"; deserializeItem = $@"input = {elementSerializerClassFullName}.Deserialize(input, out var item);"; } else { // Can't serialize type. return; } switch (bitArrayAttribute.SizeType) { case BitArraySizeType.Const: if (elementTypeSizeInfo.Type == ClassSerializeSizeType.Dynamic) { sizeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; int collectionCount = array?.Length ?? 0; if (collectionCount > {bitArrayAttribute.ConstSize}) {{ throw new global::System.Exception(string.Format($""Constant size list {{0}} from type {{1}} has too many items."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} if (array != null) {{ foreach (var item in array) {{ {calculateElementSize} }} }} int backfillCount = {bitArrayAttribute.ConstSize} - collectionCount; if (backfillCount > 0) {{ {elementTypeFullName} item = default; for (int i = 0; i != backfillCount; ++i) {{ {calculateElementSize} }} }} }} "); } serializeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; int collectionCount = array?.Length ?? 0; if (collectionCount > {bitArrayAttribute.ConstSize}) {{ throw new global::System.Exception(string.Format($""Constant size list {{0}} from type {{1}} has too many items."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} if (array != null) {{ foreach (var item in array) {{ {serializeItem} }} }} int backfillCount = {bitArrayAttribute.ConstSize} - collectionCount; if (backfillCount > 0) {{ {elementTypeFullName} item = default; for (int i = 0; i != backfillCount; ++i) {{ {serializeItem} }} }} }} "); deserializeFuncBuilder.Append($@" {{ var array = new {elementTypeFullName}[{bitArrayAttribute.ConstSize}]; for (int i = 0; i != {bitArrayAttribute.ConstSize}; ++i) {{ {deserializeItem} array[i] = item; }} value.{classFieldMember.Name} = array; }} "); break; case BitArraySizeType.EndFill: sizeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; if (array != null) {{ foreach (var item in array) {{ {calculateElementSize} }} }} }} "); serializeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; if (array != null) {{ foreach (var item in array) {{ {serializeItem} }} }} }} "); deserializeFuncBuilder.Append($@" var list = new global::System.Collections.Generic.List<{elementTypeFullName}>(); while (!input.IsEmpty) {{ {deserializeItem} list.Add(item); }} value.{classFieldMember.Name} = list.ToArray(); "); break; default: // Unknown BitArraySizeType. return; } } else { // Can't serialize type. return; } } sizeFuncBuilder.Append($@" return result; }} "); serializeFuncBuilder.Append($@" return output; }} "); deserializeFuncBuilder.Append($@" return input; }} "); sourceBuilder.Append(sizeFuncBuilder.ToString()); sourceBuilder.Append(serializeFuncBuilder.ToString()); sourceBuilder.Append(deserializeFuncBuilder.ToString()); for (int i = 0; i != classContainingTypes.Length + 1; ++i) { sourceBuilder.Append($@" }} "); } sourceBuilder.Append($@" }} "); string sourceCode = sourceBuilder.ToString(); context.AddSource($"{serializerClassName}.cs", SourceText.From(sourceCode, Encoding.UTF8)); } }
// Creates a playbook for serializing and deserializing the type T. static BitSerializer() { Type type = typeof(T); if (!type.IsLayoutSequential) { throw new Exception($"Type {type.Name} must have a LayoutKind.Sequential struct layout."); } BitStructAttribute structAttribute = type.GetCustomAttribute <BitStructAttribute>() ?? BitStructAttribute.Default; // Gets the type's fields in the order in which they are declared. // Note: Sorting by MetadataToken sorts the fields by declaration order when StructLayout is set to LayoutKind.Sequential. IEnumerable <FieldInfo> fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .OrderBy((field) => field.MetadataToken); List <FieldSerializationData> playbook = new List <FieldSerializationData>(); foreach (FieldInfo fieldInfo in fields) { DeserializeFieldHandler?deserializeFunc = null; SerializeFieldHandler? serializeFunc = null; bool handled = false; if (fieldInfo.FieldType.IsArray) { // Get the array's serialization settings. BitArrayAttribute?arrayAttribute = fieldInfo.GetCustomAttribute <BitArrayAttribute>(); if (arrayAttribute == null) { throw new Exception($"Field ${fieldInfo.Name} from type ${type.Name} must be annotated with BitArrayAttribute."); } switch (arrayAttribute.SizeType) { case BitArraySizeType.Const: case BitArraySizeType.EndFill: break; default: throw new Exception($"Unknown BitArraySizeType value of {arrayAttribute.SizeType}"); } Type elementType = fieldInfo.FieldType.GetElementType() !; if (elementType.IsArray) { // Can't handle array of arrays directly as serialization settings are required for the nested arrays. throw new Exception($"Cannot serialize a pure array of array for field {fieldInfo.Name} of type {type.Name}. Use a wrapper struct instead."); } else if (elementType.IsStruct() || elementType.IsClass || elementType.IsPrimitive || elementType.IsEnum) { Type?arraySerializerType = null; if (elementType.IsStruct() || elementType.IsClass) { // Create an array serializer type for this object type. arraySerializerType = typeof(BitSerializerStructArray <>).MakeGenericType(elementType); } else { // If element type is an enum, get the underlying integer type to use for serialization. Type elementUnderlyingType = elementType.IsEnum ? elementType.GetEnumUnderlyingType() : elementType; Dictionary <Type, BitSerializerPrimitives.TypeData> types = BitSerializerPrimitives.GetTypeData(structAttribute.Endianess); // Get the array serializer class type for the integer type. if (types.TryGetValue(elementUnderlyingType, out var typeData)) { arraySerializerType = typeData.ArraySerializerType; if (elementType.IsEnum) { // Change the array serializer class's generic type parameter to the enum type. arraySerializerType = arraySerializerType.GetGenericTypeDefinition().MakeGenericType(elementType); } } } if (arraySerializerType != null) { // Create an instance of the array serialization type. object arraySerializer = Activator.CreateInstance(arraySerializerType, arrayAttribute) !; // Get references to the serialization and deserialization member functions. deserializeFunc = (DeserializeFieldHandler)arraySerializerType.GetMethod(nameof(BitSerializerArray <int> .DeserializeField), BindingFlags.Instance | BindingFlags.Public) !.CreateDelegate(typeof(DeserializeFieldHandler), arraySerializer); serializeFunc = (SerializeFieldHandler)arraySerializerType.GetMethod(nameof(BitSerializerArray <int> .SerializeField), BindingFlags.Instance | BindingFlags.Public) !.CreateDelegate(typeof(SerializeFieldHandler), arraySerializer); handled = true; } } } else if (fieldInfo.FieldType.IsEnum || fieldInfo.FieldType.IsPrimitive) { // If element type is an enum, get the underlying integer type to use for serialization. Type fieldUnderlyingType = fieldInfo.FieldType.IsEnum ? fieldInfo.FieldType.GetEnumUnderlyingType() : fieldInfo.FieldType; Dictionary <Type, BitSerializerPrimitives.TypeData> types = BitSerializerPrimitives.GetTypeData(structAttribute.Endianess); // Get the serialization methods for the integer type. if (types.TryGetValue(fieldUnderlyingType, out var typeData)) { deserializeFunc = typeData.DeserializeFunc; serializeFunc = typeData.SerializeFunc; if (fieldInfo.FieldType.IsEnum) { // Change deserialization function's generic type parameter to the enum type. // Note: This isn't required for the serialization function since an enum wrapped in an object box can be directly // cast to the integer type. MethodInfo deserializeFuncInfo = deserializeFunc.GetMethodInfo() !; deserializeFunc = (DeserializeFieldHandler)deserializeFuncInfo.GetGenericMethodDefinition().MakeGenericMethod(fieldInfo.FieldType).CreateDelegate(typeof(DeserializeFieldHandler)); } handled = true; } } else if (fieldInfo.FieldType.IsStruct() || fieldInfo.FieldType.IsClass) { // Get the BitSerializer type for this object. Type bitSerializerStructType = typeof(BitSerializer <>).MakeGenericType(fieldInfo.FieldType); // Get references to the serialization and deserialization static functions. deserializeFunc = (DeserializeFieldHandler)bitSerializerStructType.GetMethod(nameof(DeserializeField), BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(typeof(DeserializeFieldHandler)); serializeFunc = (SerializeFieldHandler)bitSerializerStructType.GetMethod(nameof(SerializeField), BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(typeof(SerializeFieldHandler)); handled = true; } if (!handled) { throw new Exception($"Can't serialize type of {fieldInfo.FieldType.Name} from field {fieldInfo.Name}."); } playbook.Add(new FieldSerializationData(fieldInfo, deserializeFunc !, serializeFunc !)); } _Playbook = playbook.ToArray(); }