diff --git a/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs b/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs index 98d9e7f..1f70e14 100644 --- a/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs +++ b/Alchemy.SourceGenerator/AlchemySerializeGenerator.cs @@ -43,8 +43,7 @@ public void Execute(GeneratorExecutionContext context) var fieldSymbols = new List(); var fields = typeSyntax.Members - .Where(x => x is FieldDeclarationSyntax) - .Select(x => (FieldDeclarationSyntax)x); + .OfType(); foreach (var field in fields) { var model = context.Compilation.GetSemanticModel(field.SyntaxTree); @@ -54,16 +53,16 @@ public void Execute(GeneratorExecutionContext context) var alchemySerializeAttribute = fieldSymbol.GetAttributes() .FirstOrDefault(x => x.AttributeClass.Name is "AlchemySerializeField" - or "AlchemySerializeFieldAttribute" - or "Alchemy.Serialization.AlchemySerializeField" - or "Alchemy.Serialization.AlchemySerializeFieldAttribute"); + or "AlchemySerializeFieldAttribute" + or "Alchemy.Serialization.AlchemySerializeField" + or "Alchemy.Serialization.AlchemySerializeFieldAttribute"); var nonSerializedAttribute = fieldSymbol.GetAttributes() .FirstOrDefault(x => x.AttributeClass.Name is "NonSerialized" - or "NonSerializedAttribute" - or "System.NonSerialized" - or "System.NonSerializedAttribute"); + or "NonSerializedAttribute" + or "System.NonSerialized" + or "System.NonSerializedAttribute"); if (alchemySerializeAttribute != null) { @@ -92,31 +91,93 @@ x.AttributeClass.Name is "NonSerialized" context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.None, DiagnosticSeverity.Error)); } } + static string ReplaceGenericsToCount( string typeName,int count) + { + if(count == 0) return typeName; + var builder = new StringBuilder(); + bool skip = false; + foreach (var c in typeName) + { + if (c == '<') + { + skip = true; + builder.Append(count); + } + else if (c == '>') + { + skip = false; + } + else if (!skip) + { + builder.Append(c); + } + } + return builder.ToString(); + } static string ProcessClass(INamedTypeSymbol typeSymbol, List fieldSymbols) { var onAfterDeserializeCodeBuilder = new StringBuilder(); var onBeforeSerializeCodeBuilder = new StringBuilder(); var serializationDataCodeBuilder = new StringBuilder(); + bool hasInheritedImplementation = false; + var baseType = typeSymbol.BaseType; + while (baseType != null) + { + if (baseType.GetAttributes().Any(x => x.AttributeClass!.Name + is "AlchemySerialize" + or "AlchemySerializeAttribute" + or "Alchemy.Serialization.AlchemySerialize" + or "Alchemy.Serialization.AlchemySerializeAttribute")) + { + hasInheritedImplementation = true; + break; + } + + baseType = baseType.BaseType; + } + var genericsCount = 0; + if (typeSymbol.IsGenericType) + { + genericsCount = typeSymbol.TypeParameters.Length; + + } + var typeGenerics = typeSymbol.IsGenericType + ? "<" + string.Join(", ", typeSymbol.TypeParameters.Select(x => x.Name)) + ">" + : ""; + var displayName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Replace("global::", ""); + var alchemySerializationDataName = displayName.Replace(".", "_"); + alchemySerializationDataName ="__alchemySerializationData_"+ ReplaceGenericsToCount(alchemySerializationDataName,genericsCount) ; + + var inheritedOnBeforeSerialize = hasInheritedImplementation + ? "base.__AlchemyOnBeforeSerialize();" + : string.Empty; + var inheritedOnAfterDeserialize = hasInheritedImplementation + ? "base.__AlchemyOnAfterDeserialize();" + : string.Empty; var hasShowSerializationData = typeSymbol.GetAttributes().Any(x => x.AttributeClass.Name is "ShowAlchemySerializationData" or "ShowAlchemySerializationDataAttribute" or "Alchemy.Serialization.ShowAlchemySerializationData" or "Alchemy.Serialization.ShowAlchemySerializationDataAttribute"); - var serializationDataAttibutesCode = hasShowSerializationData ? "[global::Alchemy.Inspector.ReadOnly, global::UnityEngine.TextArea(3, 999), global::UnityEngine.SerializeField]" : "[global::UnityEngine.HideInInspector, global::UnityEngine.SerializeField]"; - + var serializationDataAttributesCode = hasShowSerializationData + ? $"[global::Alchemy.Inspector.LabelText(\"Alchemy Serialization Data ({displayName})\"),global::Alchemy.Inspector.ReadOnly, global::UnityEngine.TextArea(3, 999), global::UnityEngine.SerializeField]" + : "[global::UnityEngine.HideInInspector, global::UnityEngine.SerializeField]"; + // target class namespace - var ns = typeSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : $"namespace {typeSymbol.ContainingNamespace} {{"; + var ns = typeSymbol.ContainingNamespace.IsGlobalNamespace + ? string.Empty + : $"namespace {typeSymbol.ContainingNamespace} {{"; foreach (var field in fieldSymbols) { var serializeCode = -@$"try + @$"try {{ - alchemySerializationData.{field.Name}.data = global::Alchemy.Serialization.Internal.SerializationHelper.ToJson(this.{field.Name} , alchemySerializationData.UnityObjectReferences); - alchemySerializationData.{field.Name}.isCreated = true; + {alchemySerializationDataName}.{field.Name}.data = global::Alchemy.Serialization.Internal.SerializationHelper.ToJson(this.{field.Name} , {alchemySerializationDataName}.UnityObjectReferences); + {alchemySerializationDataName}.{field.Name}.isCreated = true; }} catch (global::System.Exception ex) {{ @@ -124,11 +185,11 @@ static string ProcessClass(INamedTypeSymbol typeSymbol, List field }}"; var deserializeCode = -@$"try + @$"try {{ - if (alchemySerializationData.{field.Name}.isCreated) + if ({alchemySerializationDataName}.{field.Name}.isCreated) {{ - this.{field.Name} = global::Alchemy.Serialization.Internal.SerializationHelper.FromJson<{field.Type.ToDisplayString()}>(alchemySerializationData.{field.Name}.data, alchemySerializationData.UnityObjectReferences); + this.{field.Name} = global::Alchemy.Serialization.Internal.SerializationHelper.FromJson<{field.Type.ToDisplayString()}>({alchemySerializationDataName}.{field.Name}.data, {alchemySerializationDataName}.UnityObjectReferences); }} }} catch (global::System.Exception ex) @@ -143,25 +204,38 @@ static string ProcessClass(INamedTypeSymbol typeSymbol, List field } return -@$" + @$" // {ns} - partial class {typeSymbol.Name} : global::UnityEngine.ISerializationCallbackReceiver + partial class {typeSymbol.Name}{typeGenerics} : global::UnityEngine.ISerializationCallbackReceiver {{ void global::UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize() {{ - {onAfterDeserializeCodeBuilder} + __AlchemyOnAfterDeserialize(); if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnAfterDeserialize(); }} void global::UnityEngine.ISerializationCallbackReceiver.OnBeforeSerialize() {{ if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnBeforeSerialize(); - alchemySerializationData.UnityObjectReferences.Clear(); - {onBeforeSerializeCodeBuilder} + __AlchemyOnBeforeSerialize(); + }} + + + protected {(hasInheritedImplementation ? "new" : "")} void __AlchemyOnAfterDeserialize() + {{ + {inheritedOnAfterDeserialize} + {onAfterDeserializeCodeBuilder} }} + protected {(hasInheritedImplementation ? "new" : "")} void __AlchemyOnBeforeSerialize() + {{ + {inheritedOnBeforeSerialize} + {alchemySerializationDataName}.UnityObjectReferences.Clear(); + {onBeforeSerializeCodeBuilder} + }} + [global::System.Serializable] sealed class AlchemySerializationData {{ @@ -178,7 +252,7 @@ public sealed class Item }} }} - {serializationDataAttibutesCode} private AlchemySerializationData alchemySerializationData = new(); + {serializationDataAttributesCode} private AlchemySerializationData {alchemySerializationDataName} = new(); }} {(string.IsNullOrEmpty(ns) ? "" : "}")} @@ -209,12 +283,12 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode) if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax) { var hasAttribute = typeDeclarationSyntax.AttributeLists - .SelectMany(x => x.Attributes) - .Any(x => x.Name.ToString() - is "AlchemySerialize" - or "AlchemySerializeAttribute" - or "Alchemy.Serialization.AlchemySerialize" - or "Alchemy.Serialization.AlchemySerializeAttribute"); + .SelectMany(x => x.Attributes) + .Any(x => x.Name.ToString() + is "AlchemySerialize" + or "AlchemySerializeAttribute" + or "Alchemy.Serialization.AlchemySerialize" + or "Alchemy.Serialization.AlchemySerializeAttribute"); if (hasAttribute) { TargetTypes.Add(typeDeclarationSyntax); diff --git a/Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs b/Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs index 0e269ae..2fa14cd 100644 --- a/Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs +++ b/Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs @@ -230,13 +230,21 @@ public static VisualElement CreateMemberElement(SerializedObject serializedObjec #if ALCHEMY_SUPPORT_SERIALIZATION if (serializedObject.targetObject != null && - serializedObject.targetObject.GetType().HasCustomAttribute() && + memberInfo.DeclaringType.HasCustomAttribute() && memberInfo.HasCustomAttribute()) { var element = default(VisualElement); if (memberInfo is FieldInfo fieldInfo) { - SerializedProperty GetProperty() => findPropertyFunc?.Invoke("alchemySerializationData").FindPropertyRelative(memberInfo.Name); + var declaredType = fieldInfo.DeclaringType; + if (declaredType.IsConstructedGenericType) + { + declaredType = declaredType.GetGenericTypeDefinition(); + } + var dataName ="__alchemySerializationData_"+ declaredType.FullName.Replace("`","").Replace(".", "_") ; + + SerializedProperty GetProperty() => findPropertyFunc?.Invoke(dataName) + .FindPropertyRelative(memberInfo.Name); var p = GetProperty(); if (p != null) diff --git a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll index e7145a8..1390888 100644 Binary files a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll and b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll differ diff --git a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll.meta b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll.meta index 823c6bd..8affc7b 100644 --- a/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll.meta +++ b/Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll.meta @@ -18,21 +18,11 @@ PluginImporter: second: enabled: 0 settings: - Exclude Android: 1 Exclude Editor: 1 Exclude Linux64: 1 Exclude OSXUniversal: 1 - Exclude WebGL: 1 Exclude Win: 1 Exclude Win64: 1 - Exclude iOS: 1 - - first: - Android: Android - second: - enabled: 0 - settings: - AndroidSharedLibraryType: Executable - CPU: ARMv7 - first: Any: second: @@ -76,15 +66,6 @@ PluginImporter: enabled: 0 settings: CPU: AnyCPU - - first: - iPhone: iOS - second: - enabled: 0 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: userData: assetBundleName: assetBundleVariant: diff --git a/Alchemy/Assets/Tests/InheritedSerializeTest.cs b/Alchemy/Assets/Tests/InheritedSerializeTest.cs new file mode 100644 index 0000000..c116dd1 --- /dev/null +++ b/Alchemy/Assets/Tests/InheritedSerializeTest.cs @@ -0,0 +1,13 @@ +using System; +using Alchemy.Serialization; +using UnityEngine; + +namespace Test +{ + [ShowAlchemySerializationData] + [AlchemySerialize] + public partial class InheritedSerializeTest : InheritedSerializeTestBase + { + [AlchemySerializeField, NonSerialized] int? nullableInt; + } +} \ No newline at end of file diff --git a/Alchemy/Assets/Tests/InheritedSerializeTest.cs.meta b/Alchemy/Assets/Tests/InheritedSerializeTest.cs.meta new file mode 100644 index 0000000..e2f03de --- /dev/null +++ b/Alchemy/Assets/Tests/InheritedSerializeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54b828d414a20a74b92a3ec43f78f187 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs b/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs new file mode 100644 index 0000000..f750970 --- /dev/null +++ b/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using Alchemy.Serialization; +using UnityEngine; + +[ShowAlchemySerializationData] +[AlchemySerialize] +public partial class InheritedSerializeTestBase : MonoBehaviour +{ + [AlchemySerializeField, NonSerialized] HashSet set; +} + diff --git a/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs.meta b/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs.meta new file mode 100644 index 0000000..7427156 --- /dev/null +++ b/Alchemy/Assets/Tests/InheritedSerializeTestBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 74b376952c9a4c42a10525c553c6e044 +timeCreated: 1709000968 \ No newline at end of file