mirror of
https://github.com/AnnulusGames/Alchemy.git
synced 2025-01-22 08:18:51 -05:00
Add : Support AlchemySerialization to generics and inheritance
This commit is contained in:
parent
ea19961861
commit
12ac998c27
@ -31,20 +31,21 @@ namespace Alchemy.SourceGenerator
|
||||
|
||||
if (!IsPartial(typeSyntax))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MustBePartial, typeSyntax.Identifier.GetLocation(), typeSymbol.Name));
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MustBePartial,
|
||||
typeSyntax.Identifier.GetLocation(), typeSymbol.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsNested(typeSyntax))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.NestedNotAllow, typeSyntax.Identifier.GetLocation(), typeSymbol.Name));
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.NestedNotAllow,
|
||||
typeSyntax.Identifier.GetLocation(), typeSymbol.Name));
|
||||
continue;
|
||||
}
|
||||
|
||||
var fieldSymbols = new List<IFieldSymbol>();
|
||||
var fields = typeSyntax.Members
|
||||
.Where(x => x is FieldDeclarationSyntax)
|
||||
.Select(x => (FieldDeclarationSyntax)x);
|
||||
.OfType<FieldDeclarationSyntax>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var model = context.Compilation.GetSemanticModel(field.SyntaxTree);
|
||||
@ -69,7 +70,9 @@ namespace Alchemy.SourceGenerator
|
||||
{
|
||||
if (nonSerializedAttribute == null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.ShouldBeNonSerialized, variable.Identifier.GetLocation(), fieldSymbol.Name));
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.ShouldBeNonSerialized, variable.Identifier.GetLocation(),
|
||||
fieldSymbol.Name));
|
||||
}
|
||||
|
||||
fieldSymbols.Add(fieldSymbol);
|
||||
@ -88,16 +91,76 @@ namespace Alchemy.SourceGenerator
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var diagnosticDescriptor = new DiagnosticDescriptor("AlchemySerializeGeneratorError", "AlchemySerializeGeneratorError", $"Generation failed with:\n {ex}", "AlchemySerializeGeneratorError", DiagnosticSeverity.Error, true);
|
||||
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.None, DiagnosticSeverity.Error));
|
||||
var diagnosticDescriptor = new DiagnosticDescriptor("AlchemySerializeGeneratorError",
|
||||
"AlchemySerializeGeneratorError", $"Generation failed with:\n {ex}",
|
||||
"AlchemySerializeGeneratorError", DiagnosticSeverity.Error, true);
|
||||
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)
|
||||
{
|
||||
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 alchemySerializationDataName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
.Replace("global::", "");
|
||||
alchemySerializationDataName = ReplaceGenericsToCount(alchemySerializationDataName,genericsCount) + "_alchemySerializationData";
|
||||
|
||||
var inheritedSerializationCallback = hasInheritedImplementation
|
||||
? "base.SerializationCallback_AlchemyImpl(isBeforeSerialize);"
|
||||
: string.Empty;
|
||||
|
||||
var hasShowSerializationData = typeSymbol.GetAttributes().Any(x => x.AttributeClass.Name
|
||||
is "ShowAlchemySerializationData"
|
||||
@ -105,18 +168,22 @@ namespace Alchemy.SourceGenerator
|
||||
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.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 +191,11 @@ catch (global::System.Exception ex)
|
||||
}}";
|
||||
|
||||
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,23 +210,37 @@ catch (global::System.Exception ex)
|
||||
}
|
||||
|
||||
return
|
||||
@$"
|
||||
@$"
|
||||
// <auto-generated/>
|
||||
{ns}
|
||||
|
||||
partial class {typeSymbol.Name} : global::UnityEngine.ISerializationCallbackReceiver
|
||||
partial class {typeSymbol.Name}{typeGenerics} : global::UnityEngine.ISerializationCallbackReceiver
|
||||
{{
|
||||
void global::UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{{
|
||||
{onAfterDeserializeCodeBuilder}
|
||||
SerializationCallback_AlchemyImpl(false);
|
||||
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();
|
||||
SerializationCallback_AlchemyImpl(true);
|
||||
}}
|
||||
|
||||
protected {(hasInheritedImplementation ? "new" : "")} void SerializationCallback_AlchemyImpl(bool isBeforeSerialize = false)
|
||||
{{
|
||||
{inheritedSerializationCallback}
|
||||
if (isBeforeSerialize)
|
||||
{{
|
||||
{alchemySerializationDataName}.UnityObjectReferences.Clear();
|
||||
{onBeforeSerializeCodeBuilder}
|
||||
|
||||
}}
|
||||
else
|
||||
{{
|
||||
{onAfterDeserializeCodeBuilder}
|
||||
}}
|
||||
}}
|
||||
|
||||
[global::System.Serializable]
|
||||
@ -178,7 +259,7 @@ catch (global::System.Exception ex)
|
||||
}}
|
||||
}}
|
||||
|
||||
{serializationDataAttibutesCode} private AlchemySerializationData alchemySerializationData = new();
|
||||
{serializationDataAttributesCode} private AlchemySerializationData {alchemySerializationDataName} = new();
|
||||
}}
|
||||
|
||||
{(string.IsNullOrEmpty(ns) ? "" : "}")}
|
||||
|
@ -235,13 +235,21 @@ namespace Alchemy.Editor
|
||||
|
||||
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||
if (serializedObject.targetObject != null &&
|
||||
serializedObject.targetObject.GetType().HasCustomAttribute<AlchemySerializeAttribute>() &&
|
||||
memberInfo.DeclaringType.HasCustomAttribute<AlchemySerializeAttribute>() &&
|
||||
memberInfo.HasCustomAttribute<AlchemySerializeFieldAttribute>())
|
||||
{
|
||||
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 = declaredType.FullName.Replace("`","").Replace(".", "_") + "_alchemySerializationData";
|
||||
|
||||
SerializedProperty GetProperty() => findPropertyFunc?.Invoke(dataName)
|
||||
.FindPropertyRelative(memberInfo.Name);
|
||||
|
||||
var p = GetProperty();
|
||||
if (p != null)
|
||||
|
Binary file not shown.
11
Alchemy/Assets/Tests/InheritedSerializeTest.cs
Normal file
11
Alchemy/Assets/Tests/InheritedSerializeTest.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Alchemy.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
[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:
|
13
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs
Normal file
13
Alchemy/Assets/Tests/InheritedSerializeTestBase.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Alchemy.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
[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