diff --git a/src/Ceras.AotGenerator/Generator.cs b/src/Ceras.AotGenerator/Generator.cs index b6c5c3a..2088676 100644 --- a/src/Ceras.AotGenerator/Generator.cs +++ b/src/Ceras.AotGenerator/Generator.cs @@ -1,3 +1,5 @@ +//#define ENHANCED_ASSEMBLY_DEBUG_INFO // Having this enabled kills Unity play-in-editor startup performance + using System; namespace CerasAotFormatterGenerator @@ -55,6 +57,7 @@ public static void Generate(string ns, IEnumerable asms, StringBuilder config = (SerializerConfig)configMethods[0].Invoke(null, null); var ceras = new CerasSerializer(config); + ceras.IsDuringAOTGeneration = true; // Start with KnownTypes and user-marked types... @@ -72,9 +75,18 @@ public static void Generate(string ns, IEnumerable asms, StringBuilder // Get first, remove from "to explore" list, and add it to the "done" list. var t = newTypes.First(); - if(t.IsArray) + if (t.IsArray) + { + newTypes.Remove(t); + processedTypes.Add(t); t = t.GetElementType(); + if (processedTypes.Contains(t)) + { + continue; + } + } + newTypes.Remove(t); processedTypes.Add(t); @@ -82,6 +94,11 @@ public static void Generate(string ns, IEnumerable asms, StringBuilder // Skip int, string, Type, ... continue; + if (t.IsAbstract && asms.Any(x => x.GetTypes().Contains(t))) + { + newTypes.AddRange(asms.SelectMany(x => x.GetTypes()).Where(y => !y.IsAbstract && y.IsSubclassOf(t) && !processedTypes.Contains(y))); + } + if (t.IsAbstract || t.ContainsGenericParameters) // Can't explore abstract or open generics continue; @@ -144,7 +161,9 @@ public static void RegisterAssemblyResolver() { if (_resolverRegistered) return; +#if ENHANCED_ASSEMBLY_DEBUG_INFO AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly; +#endif _resolverRegistered = true; } diff --git a/src/Ceras.AotGenerator/Helpers/TypeNameHelper.cs b/src/Ceras.AotGenerator/Helpers/TypeNameHelper.cs index b0b2780..e33ddbf 100644 --- a/src/Ceras.AotGenerator/Helpers/TypeNameHelper.cs +++ b/src/Ceras.AotGenerator/Helpers/TypeNameHelper.cs @@ -57,6 +57,12 @@ public static string ToVariableSafeName(this Type type) + "_" + string.Join("_", type.GetGenericArguments().Select(t => t.ToFriendlyName(false)).ToArray()); } + else if (type.IsArray) + { + int rank = type.GetArrayRank(); + string baseName = name.Split('[')[0]; + return $"{baseName}_Array{rank}"; + } else { return name; diff --git a/src/Ceras.AotGenerator/SourceGenerator/SourceFormatterGenerator.cs b/src/Ceras.AotGenerator/SourceGenerator/SourceFormatterGenerator.cs index dad701d..75ce1e7 100644 --- a/src/Ceras.AotGenerator/SourceGenerator/SourceFormatterGenerator.cs +++ b/src/Ceras.AotGenerator/SourceGenerator/SourceFormatterGenerator.cs @@ -13,12 +13,13 @@ static class SourceFormatterGenerator public static void GenerateAll(string ns, List targets, Dictionary aotHint, CerasSerializer ceras, StringBuilder text) { - text.AppendLine(@" -// ReSharper disable All - -#nullable disable -#pragma warning disable 649 -"); + text.AppendLine("// ReSharper disable All"); + text.AppendLine(""); +#if !CSHARP_7_OR_LATER || UNITY_2020_2_OR_NEWER + text.AppendLine("#nullable disable"); +#endif + text.AppendLine("#pragma warning disable 649"); + text.AppendLine(""); text.AppendLine("using Ceras;"); text.AppendLine("using Ceras.Formatters;"); text.AppendLine("using Ceras.Formatters.AotGenerator;"); @@ -26,7 +27,7 @@ public static void GenerateAll(string ns, List targets, Dictionary $"\t\t\t{aotHint[t].ToFriendlyName(true)} var{i} = default;\n\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = var{i};"); + var setFormattersHint = aotHint.Keys.Select((t, i) => $"\t\t\t{aotHint[t].ToFriendlyName(true)} var{i} = default;{Environment.NewLine}\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = var{i};"); var setCustomFormatters = targets.Select(t => $"\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = new {t.ToVariableSafeName()}Formatter();"); text.AppendLine( $@" public static class GeneratedFormatters @@ -48,7 +49,9 @@ private static void AotHint(SerializerConfig config) text.Length -= Environment.NewLine.Length; text.AppendLine("}"); - text.AppendLine("#nullable restore"); +#if !CSHARP_7_OR_LATER || UNITY_2020_2_OR_NEWER + text.AppendLine("#nullable restore"); +#endif text.AppendLine("#pragma warning restore 649"); text.AppendLine(); } diff --git a/src/Ceras/CerasSerializer.cs b/src/Ceras/CerasSerializer.cs index 595916c..d4a2682 100644 --- a/src/Ceras/CerasSerializer.cs +++ b/src/Ceras/CerasSerializer.cs @@ -183,6 +183,8 @@ public static void ClearGenericCaches() int _recursionDepth = 0; RecursionMode _mode = RecursionMode.Idle; // while in one mode we cannot enter the others + internal bool IsDuringAOTGeneration { get; set; } = false; + /// /// A "free" property where you can store anything. /// This is useful for scenarios where callbacks like are used, because the instance is passed to those methods (assuming they take a instance as an argument). That way you can obtain the inside those methods. diff --git a/src/Ceras/Formatters/CollectionFormatter.cs b/src/Ceras/Formatters/CollectionFormatter.cs index d6f0ad7..21c63e7 100644 --- a/src/Ceras/Formatters/CollectionFormatter.cs +++ b/src/Ceras/Formatters/CollectionFormatter.cs @@ -336,7 +336,16 @@ public void Deserialize(byte[] buffer, ref int offset, ref TCollection value) if (value == null) - value = _capacityConstructor((int)itemCount); + { + if (_capacityConstructor != null) + { + value = _capacityConstructor((int)itemCount); + } + else + { + value = Activator.CreateInstance(); + } + } else { if (value.Count > 0) diff --git a/src/Ceras/Formatters/ReferenceFormatter.cs b/src/Ceras/Formatters/ReferenceFormatter.cs index f01ca89..4c4ca5f 100644 --- a/src/Ceras/Formatters/ReferenceFormatter.cs +++ b/src/Ceras/Formatters/ReferenceFormatter.cs @@ -536,9 +536,9 @@ static SerializeDelegate CreateSpecificSerializerDispatcher_Aot(Type type, IF } // Can't call directly, need to invoke through reflection so T gets casted up/down correctly. - var args = new object[3]; return (ref byte[] buffer, ref int offset, T value) => { + var args = new object[3]; args[0] = buffer; args[1] = offset; args[2] = value; @@ -559,10 +559,9 @@ static DeserializeDelegate CreateSpecificDeserializerDispatcher_Aot(Type type // return (ref byte[] buffer, ref int offset, T value) => { f.Serialize(ref buffer, ref offset, value); }; } - - var args = new object[3]; return new DeserializeDelegate((byte[] buffer, ref int offset, ref T value) => { + var args = new object[3]; args[0] = buffer; args[1] = offset; args[2] = value; diff --git a/src/Ceras/Helpers/ReflectionHelper.cs b/src/Ceras/Helpers/ReflectionHelper.cs index d2022be..5abfe04 100644 --- a/src/Ceras/Helpers/ReflectionHelper.cs +++ b/src/Ceras/Helpers/ReflectionHelper.cs @@ -641,7 +641,8 @@ public static string FriendlyName(this Type type, bool fullName = false) } else { - return fullName ? type.FullName : type.Name; + var name = fullName ? type.FullName : type.Name; + return name.Replace("+", "."); } } } diff --git a/src/Ceras/Resolvers/ReinterpretFormatterResolver.cs b/src/Ceras/Resolvers/ReinterpretFormatterResolver.cs index 55eea75..126affa 100644 --- a/src/Ceras/Resolvers/ReinterpretFormatterResolver.cs +++ b/src/Ceras/Resolvers/ReinterpretFormatterResolver.cs @@ -29,6 +29,9 @@ public IFormatter GetFormatter(Type type) if (!ReflectionHelper.IsBlittableType(type)) return null; + if (_ceras.IsDuringAOTGeneration && type.IsGenericType) + return null; // ReinterpretFormatter's unmanaged constraint on T causes the compiler reject generic types, but runtime doesn't enforce it + var formatterType = typeof(ReinterpretFormatter<>).MakeGenericType(type); return (IFormatter) Activator.CreateInstance(formatterType); diff --git a/src/Ceras/Resolvers/StandardFormatterResolver.cs b/src/Ceras/Resolvers/StandardFormatterResolver.cs index 210143c..8ba79ed 100644 --- a/src/Ceras/Resolvers/StandardFormatterResolver.cs +++ b/src/Ceras/Resolvers/StandardFormatterResolver.cs @@ -172,7 +172,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref Nullable value) } - class KeyValuePairFormatter : IFormatter> + public class KeyValuePairFormatter : IFormatter> { IFormatter _keyFormatter; IFormatter _valueFormatter; @@ -196,7 +196,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref KeyValuePair + public class DateTimeFormatter : IFormatter { public void Serialize(ref byte[] buffer, ref int offset, DateTime value) { @@ -211,7 +211,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref DateTime value) } } - class DateTimeOffsetFormatter : IFormatter + public class DateTimeOffsetFormatter : IFormatter { public void Serialize(ref byte[] buffer, ref int offset, DateTimeOffset value) { @@ -228,7 +228,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref DateTimeOffset value) } } - class TimeSpanFormatter : IFormatter + public class TimeSpanFormatter : IFormatter { public void Serialize(ref byte[] buffer, ref int offset, TimeSpan value) { @@ -241,7 +241,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref TimeSpan value) } } - class BitVector32Formatter : IFormatter + public class BitVector32Formatter : IFormatter { public void Serialize(ref byte[] buffer, ref int offset, BitVector32 value) { @@ -255,7 +255,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref BitVector32 value) } } - class BigIntegerFormatter : IFormatter + public class BigIntegerFormatter : IFormatter { public void Serialize(ref byte[] buffer, ref int offset, BigInteger value) {