mirror of
https://github.com/AnnulusGames/Alchemy.git
synced 2025-01-22 00:08:53 -05:00
Merge pull request #61 from Akeit0/alchemy-serialize-with-inheritance
Add : Support AlchemySerialization with generics and inheritance
This commit is contained in:
commit
93a56757a8
@ -43,8 +43,7 @@ namespace Alchemy.SourceGenerator
|
|||||||
|
|
||||||
var fieldSymbols = new List<IFieldSymbol>();
|
var fieldSymbols = new List<IFieldSymbol>();
|
||||||
var fields = typeSyntax.Members
|
var fields = typeSyntax.Members
|
||||||
.Where(x => x is FieldDeclarationSyntax)
|
.OfType<FieldDeclarationSyntax>();
|
||||||
.Select(x => (FieldDeclarationSyntax)x);
|
|
||||||
foreach (var field in fields)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
var model = context.Compilation.GetSemanticModel(field.SyntaxTree);
|
var model = context.Compilation.GetSemanticModel(field.SyntaxTree);
|
||||||
@ -54,16 +53,16 @@ namespace Alchemy.SourceGenerator
|
|||||||
var alchemySerializeAttribute = fieldSymbol.GetAttributes()
|
var alchemySerializeAttribute = fieldSymbol.GetAttributes()
|
||||||
.FirstOrDefault(x =>
|
.FirstOrDefault(x =>
|
||||||
x.AttributeClass.Name is "AlchemySerializeField"
|
x.AttributeClass.Name is "AlchemySerializeField"
|
||||||
or "AlchemySerializeFieldAttribute"
|
or "AlchemySerializeFieldAttribute"
|
||||||
or "Alchemy.Serialization.AlchemySerializeField"
|
or "Alchemy.Serialization.AlchemySerializeField"
|
||||||
or "Alchemy.Serialization.AlchemySerializeFieldAttribute");
|
or "Alchemy.Serialization.AlchemySerializeFieldAttribute");
|
||||||
|
|
||||||
var nonSerializedAttribute = fieldSymbol.GetAttributes()
|
var nonSerializedAttribute = fieldSymbol.GetAttributes()
|
||||||
.FirstOrDefault(x =>
|
.FirstOrDefault(x =>
|
||||||
x.AttributeClass.Name is "NonSerialized"
|
x.AttributeClass.Name is "NonSerialized"
|
||||||
or "NonSerializedAttribute"
|
or "NonSerializedAttribute"
|
||||||
or "System.NonSerialized"
|
or "System.NonSerialized"
|
||||||
or "System.NonSerializedAttribute");
|
or "System.NonSerializedAttribute");
|
||||||
|
|
||||||
if (alchemySerializeAttribute != null)
|
if (alchemySerializeAttribute != null)
|
||||||
{
|
{
|
||||||
@ -92,31 +91,93 @@ namespace Alchemy.SourceGenerator
|
|||||||
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.None, DiagnosticSeverity.Error));
|
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<IFieldSymbol> fieldSymbols)
|
static string ProcessClass(INamedTypeSymbol typeSymbol, List<IFieldSymbol> fieldSymbols)
|
||||||
{
|
{
|
||||||
var onAfterDeserializeCodeBuilder = new StringBuilder();
|
var onAfterDeserializeCodeBuilder = new StringBuilder();
|
||||||
var onBeforeSerializeCodeBuilder = new StringBuilder();
|
var onBeforeSerializeCodeBuilder = new StringBuilder();
|
||||||
var serializationDataCodeBuilder = 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
|
var hasShowSerializationData = typeSymbol.GetAttributes().Any(x => x.AttributeClass.Name
|
||||||
is "ShowAlchemySerializationData"
|
is "ShowAlchemySerializationData"
|
||||||
or "ShowAlchemySerializationDataAttribute"
|
or "ShowAlchemySerializationDataAttribute"
|
||||||
or "Alchemy.Serialization.ShowAlchemySerializationData"
|
or "Alchemy.Serialization.ShowAlchemySerializationData"
|
||||||
or "Alchemy.Serialization.ShowAlchemySerializationDataAttribute");
|
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
|
// 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)
|
foreach (var field in fieldSymbols)
|
||||||
{
|
{
|
||||||
var serializeCode =
|
var serializeCode =
|
||||||
@$"try
|
@$"try
|
||||||
{{
|
{{
|
||||||
alchemySerializationData.{field.Name}.data = global::Alchemy.Serialization.Internal.SerializationHelper.ToJson(this.{field.Name} , alchemySerializationData.UnityObjectReferences);
|
{alchemySerializationDataName}.{field.Name}.data = global::Alchemy.Serialization.Internal.SerializationHelper.ToJson(this.{field.Name} , {alchemySerializationDataName}.UnityObjectReferences);
|
||||||
alchemySerializationData.{field.Name}.isCreated = true;
|
{alchemySerializationDataName}.{field.Name}.isCreated = true;
|
||||||
}}
|
}}
|
||||||
catch (global::System.Exception ex)
|
catch (global::System.Exception ex)
|
||||||
{{
|
{{
|
||||||
@ -124,11 +185,11 @@ catch (global::System.Exception ex)
|
|||||||
}}";
|
}}";
|
||||||
|
|
||||||
var deserializeCode =
|
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)
|
catch (global::System.Exception ex)
|
||||||
@ -143,25 +204,38 @@ catch (global::System.Exception ex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@$"
|
@$"
|
||||||
// <auto-generated/>
|
// <auto-generated/>
|
||||||
{ns}
|
{ns}
|
||||||
|
|
||||||
partial class {typeSymbol.Name} : global::UnityEngine.ISerializationCallbackReceiver
|
partial class {typeSymbol.Name}{typeGenerics} : global::UnityEngine.ISerializationCallbackReceiver
|
||||||
{{
|
{{
|
||||||
void global::UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize()
|
void global::UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||||
{{
|
{{
|
||||||
{onAfterDeserializeCodeBuilder}
|
__AlchemyOnAfterDeserialize();
|
||||||
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnAfterDeserialize();
|
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnAfterDeserialize();
|
||||||
}}
|
}}
|
||||||
|
|
||||||
void global::UnityEngine.ISerializationCallbackReceiver.OnBeforeSerialize()
|
void global::UnityEngine.ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||||
{{
|
{{
|
||||||
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnBeforeSerialize();
|
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnBeforeSerialize();
|
||||||
alchemySerializationData.UnityObjectReferences.Clear();
|
__AlchemyOnBeforeSerialize();
|
||||||
{onBeforeSerializeCodeBuilder}
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
protected {(hasInheritedImplementation ? "new" : "")} void __AlchemyOnAfterDeserialize()
|
||||||
|
{{
|
||||||
|
{inheritedOnAfterDeserialize}
|
||||||
|
{onAfterDeserializeCodeBuilder}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
protected {(hasInheritedImplementation ? "new" : "")} void __AlchemyOnBeforeSerialize()
|
||||||
|
{{
|
||||||
|
{inheritedOnBeforeSerialize}
|
||||||
|
{alchemySerializationDataName}.UnityObjectReferences.Clear();
|
||||||
|
{onBeforeSerializeCodeBuilder}
|
||||||
|
}}
|
||||||
|
|
||||||
[global::System.Serializable]
|
[global::System.Serializable]
|
||||||
sealed class AlchemySerializationData
|
sealed class AlchemySerializationData
|
||||||
{{
|
{{
|
||||||
@ -178,7 +252,7 @@ catch (global::System.Exception ex)
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{serializationDataAttibutesCode} private AlchemySerializationData alchemySerializationData = new();
|
{serializationDataAttributesCode} private AlchemySerializationData {alchemySerializationDataName} = new();
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{(string.IsNullOrEmpty(ns) ? "" : "}")}
|
{(string.IsNullOrEmpty(ns) ? "" : "}")}
|
||||||
@ -209,12 +283,12 @@ catch (global::System.Exception ex)
|
|||||||
if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax)
|
if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax)
|
||||||
{
|
{
|
||||||
var hasAttribute = typeDeclarationSyntax.AttributeLists
|
var hasAttribute = typeDeclarationSyntax.AttributeLists
|
||||||
.SelectMany(x => x.Attributes)
|
.SelectMany(x => x.Attributes)
|
||||||
.Any(x => x.Name.ToString()
|
.Any(x => x.Name.ToString()
|
||||||
is "AlchemySerialize"
|
is "AlchemySerialize"
|
||||||
or "AlchemySerializeAttribute"
|
or "AlchemySerializeAttribute"
|
||||||
or "Alchemy.Serialization.AlchemySerialize"
|
or "Alchemy.Serialization.AlchemySerialize"
|
||||||
or "Alchemy.Serialization.AlchemySerializeAttribute");
|
or "Alchemy.Serialization.AlchemySerializeAttribute");
|
||||||
if (hasAttribute)
|
if (hasAttribute)
|
||||||
{
|
{
|
||||||
TargetTypes.Add(typeDeclarationSyntax);
|
TargetTypes.Add(typeDeclarationSyntax);
|
||||||
|
@ -230,13 +230,21 @@ namespace Alchemy.Editor
|
|||||||
|
|
||||||
#if ALCHEMY_SUPPORT_SERIALIZATION
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
if (serializedObject.targetObject != null &&
|
if (serializedObject.targetObject != null &&
|
||||||
serializedObject.targetObject.GetType().HasCustomAttribute<AlchemySerializeAttribute>() &&
|
memberInfo.DeclaringType.HasCustomAttribute<AlchemySerializeAttribute>() &&
|
||||||
memberInfo.HasCustomAttribute<AlchemySerializeFieldAttribute>())
|
memberInfo.HasCustomAttribute<AlchemySerializeFieldAttribute>())
|
||||||
{
|
{
|
||||||
var element = default(VisualElement);
|
var element = default(VisualElement);
|
||||||
if (memberInfo is FieldInfo fieldInfo)
|
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();
|
var p = GetProperty();
|
||||||
if (p != null)
|
if (p != null)
|
||||||
|
Binary file not shown.
@ -18,21 +18,11 @@ PluginImporter:
|
|||||||
second:
|
second:
|
||||||
enabled: 0
|
enabled: 0
|
||||||
settings:
|
settings:
|
||||||
Exclude Android: 1
|
|
||||||
Exclude Editor: 1
|
Exclude Editor: 1
|
||||||
Exclude Linux64: 1
|
Exclude Linux64: 1
|
||||||
Exclude OSXUniversal: 1
|
Exclude OSXUniversal: 1
|
||||||
Exclude WebGL: 1
|
|
||||||
Exclude Win: 1
|
Exclude Win: 1
|
||||||
Exclude Win64: 1
|
Exclude Win64: 1
|
||||||
Exclude iOS: 1
|
|
||||||
- first:
|
|
||||||
Android: Android
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AndroidSharedLibraryType: Executable
|
|
||||||
CPU: ARMv7
|
|
||||||
- first:
|
- first:
|
||||||
Any:
|
Any:
|
||||||
second:
|
second:
|
||||||
@ -76,15 +66,6 @@ PluginImporter:
|
|||||||
enabled: 0
|
enabled: 0
|
||||||
settings:
|
settings:
|
||||||
CPU: AnyCPU
|
CPU: AnyCPU
|
||||||
- first:
|
|
||||||
iPhone: iOS
|
|
||||||
second:
|
|
||||||
enabled: 0
|
|
||||||
settings:
|
|
||||||
AddToEmbeddedBinaries: false
|
|
||||||
CPU: AnyCPU
|
|
||||||
CompileFlags:
|
|
||||||
FrameworkDependencies:
|
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
13
Alchemy/Assets/Tests/InheritedSerializeTest.cs
Normal file
13
Alchemy/Assets/Tests/InheritedSerializeTest.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using Alchemy.Serialization;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
[ShowAlchemySerializationData]
|
||||||
|
[AlchemySerialize]
|
||||||
|
public partial class InheritedSerializeTest : InheritedSerializeTestBase<string>
|
||||||
|
{
|
||||||
|
[AlchemySerializeField, NonSerialized] int? nullableInt;
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Tests/InheritedSerializeTest.cs.meta
Normal file
11
Alchemy/Assets/Tests/InheritedSerializeTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 54b828d414a20a74b92a3ec43f78f187
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
12
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs
Normal file
12
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Alchemy.Serialization;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[ShowAlchemySerializationData]
|
||||||
|
[AlchemySerialize]
|
||||||
|
public partial class InheritedSerializeTestBase<T> : MonoBehaviour
|
||||||
|
{
|
||||||
|
[AlchemySerializeField, NonSerialized] HashSet<T> set;
|
||||||
|
}
|
||||||
|
|
3
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs.meta
Normal file
3
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 74b376952c9a4c42a10525c553c6e044
|
||||||
|
timeCreated: 1709000968
|
Loading…
Reference in New Issue
Block a user