diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll
index 21aac9e253..ace54bb2c6 100644
--- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll
+++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c76d63dbc0c7c64e52f8ac2c5f8d15645956737befdcccac5ca09c5b86240ed3
-size 150528
+oid sha256:63c65b2d24cd6ae487ea6c9d957343dba83c7992a0a2831d20f0f4da5b78dadc
+size 147456
diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash
index 9832b1c9f7..da40182b23 100644
--- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash
+++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash
@@ -1 +1 @@
-C76D63DBC0C7C64E52F8AC2C5F8D15645956737BEFDCCCAC5CA09C5B86240ED3
\ No newline at end of file
+63C65B2D24CD6AE487EA6C9D957343DBA83C7992A0A2831D20F0F4DA5B78DADC
\ No newline at end of file
diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb
index bea31f4e1d..f5d5cf9d20 100644
--- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb
+++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0d24c0c9a848e934363ba72dd325057f3f0f89737b7e0403896abaca860e1981
-size 411136
+oid sha256:c21b7719e470059842e4525453a248a3dcf033f75c4cc61a50382684c478a33f
+size 407040
diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll
index 9761425c8c..8cad1bab2b 100644
--- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll
+++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:dd9b63a4a434e57ede5727114221868fe77d252fff4c14c3342ba4e74ad3905d
-size 158208
+oid sha256:6c5bc9da7e5a867c953c362c3419ad3c9554df115baa61baec10ed2c3856b752
+size 155136
diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash
index 1f1345ecea..e9d3f2dc84 100644
--- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash
+++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash
@@ -1 +1 @@
-DD9B63A4A434E57EDE5727114221868FE77D252FFF4C14C3342BA4E74AD3905D
\ No newline at end of file
+6C5BC9DA7E5A867C953C362C3419AD3C9554DF115BAA61BAEC10ED2C3856B752
\ No newline at end of file
diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb
index 7019b2d210..7cb404ba5f 100644
--- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb
+++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:bee19bb3a32f1397b17d0e587ec0c65d88cd9dc5ba09253220df43802a93e806
+oid sha256:27df1b2f1b7d6beb16f173e0039a4d0810171d87a4f160c7d3756912dbb4c290
size 550400
diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs
deleted file mode 100644
index 2b37026ff3..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Generates array serializer type from a given array type.
-///
-public class CecilArraySerializerFactory : ICecilSerializerFactory
-{
- private readonly TypeReference genericArraySerializerType;
-
- public CecilArraySerializerFactory(TypeReference genericArraySerializerType)
- {
- this.genericArraySerializerType = genericArraySerializerType;
- }
-
- public TypeReference? GetSerializer(TypeReference objectType)
- {
- if (objectType.IsArray)
- {
- return genericArraySerializerType.MakeGenericType(((ArrayType)objectType).ElementType);
- }
-
- return null;
- }
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs
deleted file mode 100644
index 8cd4928d98..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Generates enum serializer type from a given enum type.
-///
-public class CecilEnumSerializerFactory : ICecilSerializerFactory
-{
- private readonly TypeReference genericEnumSerializerType;
-
- public CecilEnumSerializerFactory(TypeReference genericEnumSerializerType)
- {
- this.genericEnumSerializerType = genericEnumSerializerType;
- }
-
- public TypeReference? GetSerializer(TypeReference objectType)
- {
- var resolvedObjectType = objectType.Resolve();
- if (resolvedObjectType?.IsEnum == true)
- {
- return genericEnumSerializerType.MakeGenericType(objectType);
- }
-
- return null;
- }
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs
deleted file mode 100644
index 71ab3533d3..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Gives the specified serializer type constructed with generic arguments of the serialized type.
-/// As an example, this instance with will give from .
-///
-public class CecilGenericSerializerFactory : ICecilSerializerFactory
-{
- private readonly Type genericSerializableType;
- private readonly bool checkInterfaces;
-
- protected Func CreateSerializer { get; set; }
-
- private CecilGenericSerializerFactory(Type genericSerializableType, Func createSerializer)
- {
- this.genericSerializableType = genericSerializableType;
- CreateSerializer = createSerializer;
- checkInterfaces = genericSerializableType.IsInterface;
- }
-
- public CecilGenericSerializerFactory(Type genericSerializableType, TypeReference genericSerializerType)
- : this(genericSerializableType, type => genericSerializerType.MakeGenericType([.. ((GenericInstanceType)type).GenericArguments]))
- {
- if (genericSerializerType == null)
- throw new ArgumentNullException(nameof(genericSerializerType));
- if (genericSerializableType == null)
- throw new ArgumentNullException(nameof(genericSerializableType));
- }
-
- #region IDataSerializerFactory Members
-
- public virtual TypeReference? GetSerializer(TypeReference objectType)
- {
- // Check if objectType matches genericSerializableType.
- // Note: Not perfectly valid but hopefully it should be fast enough.
- if (objectType.IsGenericInstance && checkInterfaces)
- {
- if (objectType.GetElementType().Resolve().Interfaces.Any(x => x.InterfaceType.IsGenericInstance && x.InterfaceType.GetElementType().FullName == genericSerializableType.FullName))
- return CreateSerializer(objectType);
- }
- if (objectType.IsGenericInstance && objectType.GetElementType().FullName == genericSerializableType.FullName)
- return CreateSerializer(objectType);
-
- return null;
- }
-
- #endregion
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs
deleted file mode 100644
index db25748be2..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Enumerates required subtypes the given serializer will use internally.
-///
-public class CecilSerializerDependency : ICecilSerializerDependency
-{
- readonly string genericSerializerTypeFullName;
- readonly TypeReference? genericSerializableType;
-
- ///
- /// Initializes a new instance of the class.
- /// It will enumerates T1, T2 from genericSerializerType{T1, T2}.
- ///
- /// Type of the generic serializer.
- public CecilSerializerDependency(string genericSerializerTypeFullName)
- {
- this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName));
- }
-
- ///
- /// Initializes a new instance of the class.
- /// It will enumerates genericSerializableType{T1, T2} from genericSerializerType{T1, T2}.
- ///
- /// Type of the generic serializer.
- /// Type of the generic serializable.
- public CecilSerializerDependency(string genericSerializerTypeFullName, TypeReference genericSerializableType)
- {
- this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName));
- this.genericSerializableType = genericSerializableType ?? throw new ArgumentNullException(nameof(genericSerializableType));
- }
-
- public IEnumerable EnumerateSubTypesFromSerializer(TypeReference serializerType)
- {
- // Check if serializer type name matches
- if (serializerType.IsGenericInstance && serializerType.GetElementType().FullName == genericSerializerTypeFullName)
- {
- if (genericSerializableType != null)
- {
- // Transforms genericSerializerType{T1, T2} into genericSerializableType{T1, T2}
- return Enumerable.Repeat(genericSerializableType.MakeGenericType([.. ((GenericInstanceType)serializerType).GenericArguments]), 1);
- }
- else
- {
- // Transforms genericSerializerType{T1, T2} into T1, T2
- return ((GenericInstanceType)serializerType).GenericArguments;
- }
- }
-
- return [];
- }
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs b/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs
deleted file mode 100644
index 8a39826cd0..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Enumerates required subtypes the given serializer will use internally.
-/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available).
-///
-public interface ICecilSerializerDependency
-{
- ///
- /// Enumerates the types this serializer requires.
- ///
- /// Type of the serializer.
- ///
- IEnumerable EnumerateSubTypesFromSerializer(TypeReference serializerType);
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs
deleted file mode 100644
index 5c6ced14b9..0000000000
--- a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using Mono.Cecil;
-
-namespace Stride.Core.AssemblyProcessor;
-
-///
-/// Gives the required generic serializer for a given type.
-/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available).
-///
-public interface ICecilSerializerFactory
-{
- ///
- /// Gets the serializer type from a given object type.
- ///
- /// Type of the object to serialize.
- ///
- TypeReference? GetSerializer(TypeReference objectType);
-}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs b/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs
new file mode 100644
index 0000000000..c3c76e543c
--- /dev/null
+++ b/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs
@@ -0,0 +1,206 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace Stride.Core.AssemblyProcessor;
+
+///
+/// A thin fluent wrapper over Cecil's IL emission that reduces verbosity.
+/// Provides automatic and composite patterns
+/// like typeof(T) emission.
+///
+internal class ILBuilder
+{
+ private readonly MethodBody body;
+ private readonly ModuleDefinition module;
+
+ public ILBuilder(MethodBody body, ModuleDefinition module)
+ {
+ this.body = body;
+ this.module = module;
+ }
+
+ ///
+ /// The underlying method body, for direct access when needed (e.g. adding variables).
+ ///
+ public MethodBody Body => body;
+
+ ///
+ /// The module used for importing references.
+ ///
+ public ModuleDefinition Module => module;
+
+ // ── Core Emit overloads ──────────────────────────────────────────
+
+ public ILBuilder Emit(OpCode opcode)
+ {
+ body.Instructions.Add(Instruction.Create(opcode));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, int value)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, value));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, byte value)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, value));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, string value)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, value));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, TypeReference type)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, type));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, MethodReference method)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, method));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, FieldReference field)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, field));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, VariableDefinition variable)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, variable));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, ParameterDefinition parameter)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, parameter));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, Instruction target)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, target));
+ return this;
+ }
+
+ public ILBuilder Emit(OpCode opcode, CallSite callSite)
+ {
+ body.Instructions.Add(Instruction.Create(opcode, callSite));
+ return this;
+ }
+
+ ///
+ /// Appends a pre-created instruction to the method body.
+ ///
+ public ILBuilder Append(Instruction instruction)
+ {
+ body.Instructions.Add(instruction);
+ return this;
+ }
+
+ // ── Import helpers ───────────────────────────────────────────────
+
+ ///
+ /// Imports a into the current module.
+ /// Shorthand for module.ImportReference(type).
+ ///
+ public TypeReference Import(TypeReference type) => module.ImportReference(type);
+
+ ///
+ /// Imports a into the current module.
+ /// Shorthand for module.ImportReference(method).
+ ///
+ public MethodReference Import(MethodReference method) => module.ImportReference(method);
+
+ ///
+ /// Imports a into the current module.
+ /// Shorthand for module.ImportReference(field).
+ ///
+ public FieldReference Import(FieldReference field) => module.ImportReference(field);
+
+ // ── Composite patterns ───────────────────────────────────────────
+
+ ///
+ /// Emits typeof(T): ldtoken type; call GetTypeFromHandle.
+ ///
+ public ILBuilder EmitTypeof(TypeReference type, MethodReference getTypeFromHandle)
+ {
+ Emit(OpCodes.Ldtoken, type);
+ Emit(OpCodes.Call, getTypeFromHandle);
+ return this;
+ }
+
+ ///
+ /// Emits typeof(T).GetTypeInfo().Assembly.
+ ///
+ public ILBuilder EmitTypeofAssembly(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getAssembly)
+ {
+ EmitTypeof(type, getTypeFromHandle);
+ Emit(OpCodes.Call, getTypeInfo);
+ Emit(OpCodes.Callvirt, getAssembly);
+ return this;
+ }
+
+ ///
+ /// Emits typeof(T).GetTypeInfo().Module.
+ ///
+ public ILBuilder EmitTypeofModule(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getModule)
+ {
+ EmitTypeof(type, getTypeFromHandle);
+ Emit(OpCodes.Call, getTypeInfo);
+ Emit(OpCodes.Callvirt, getModule);
+ return this;
+ }
+
+ ///
+ /// Emits typeof(T).TypeHandle: ldtoken type; call GetTypeFromHandle; callvirt get_TypeHandle.
+ ///
+ public ILBuilder EmitTypeHandle(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeHandle)
+ {
+ EmitTypeof(type, getTypeFromHandle);
+ Emit(OpCodes.Callvirt, getTypeHandle);
+ return this;
+ }
+
+ // ── Variable helpers ─────────────────────────────────────────────
+
+ ///
+ /// Adds a local variable to the method body and returns it.
+ /// Sets to .
+ ///
+ public VariableDefinition AddLocal(TypeReference type)
+ {
+ var variable = new VariableDefinition(type);
+ body.Variables.Add(variable);
+ body.InitLocals = true;
+ return variable;
+ }
+
+ // ── Label support ────────────────────────────────────────────────
+
+ ///
+ /// Creates a that can be used as a branch target,
+ /// without appending it to the body. Call to place it.
+ ///
+ public static Instruction DefineLabel() => Instruction.Create(OpCodes.Nop);
+
+ ///
+ /// Appends a previously defined label instruction to the body, marking the current position.
+ ///
+ public ILBuilder MarkLabel(Instruction label)
+ {
+ body.Instructions.Add(label);
+ return this;
+ }
+}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs
index ebaa6a79ad..45ccd8fb14 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs
@@ -2,17 +2,17 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
//
// Copyright (c) 2010-2012 SharpDX - Alexandre Mutel
-//
+//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -103,7 +103,7 @@ private void CreateModuleInit(MethodDefinition method)
}
///
- /// Creates the write method with the following signature:
+ /// Creates the write method with the following signature:
///
/// public static unsafe void* Write<T>(void* pDest, ref T data) where T : struct
///
@@ -114,7 +114,7 @@ private void CreateWriteMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
+ var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -123,34 +123,28 @@ private void CreateWriteMethod(MethodDefinition method)
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// Push (0) pDest for memcpy
- gen.Emit(OpCodes.Ldarg_0);
-
- // fixed (void* pinnedData = &data[offset])
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Stloc_1);
-
- // Push (1) pinnedData for memcpy
- gen.Emit(OpCodes.Ldloc_1);
-
- // totalSize = sizeof(T)
- gen.Emit(OpCodes.Sizeof, paramT);
- gen.Emit(OpCodes.Conv_I4);
- gen.Emit(OpCodes.Stloc_0);
-
- // Push (2) totalSize
- gen.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldarg_0)
+ // fixed (void* pinnedData = &data[offset])
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Stloc_1)
+ // Push (1) pinnedData for memcpy
+ .Emit(OpCodes.Ldloc_1)
+ // totalSize = sizeof(T)
+ .Emit(OpCodes.Sizeof, paramT)
+ .Emit(OpCodes.Conv_I4)
+ .Emit(OpCodes.Stloc_0)
+ // Push (2) totalSize
+ .Emit(OpCodes.Ldloc_0);
// Emit cpblk
- EmitCpblk(method, gen);
+ EmitCpblk(il);
// Return pDest + totalSize
- gen.Emit(OpCodes.Ldloc_0);
- gen.Emit(OpCodes.Conv_I);
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Add);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ il.Emit(OpCodes.Ldloc_0)
+ .Emit(OpCodes.Conv_I)
+ .Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Add)
+ .Emit(OpCodes.Ret);
}
private void ReplacePinStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch)
@@ -348,12 +342,9 @@ private void CreateCastMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
-
- gen.Emit(OpCodes.Ldarg_0);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ var il = new ILBuilder(method.Body, assembly.MainModule);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ret);
}
///
@@ -368,12 +359,9 @@ private void CreateCastArrayMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
-
- gen.Emit(OpCodes.Ldarg_0);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ var il = new ILBuilder(method.Body, assembly.MainModule);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ret);
}
private void ReplaceFixedArrayStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch)
@@ -431,7 +419,7 @@ private void CreateWriteRangeMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
+ var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -440,38 +428,32 @@ private void CreateWriteRangeMethod(MethodDefinition method)
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// Push (0) pDest for memcpy
- gen.Emit(OpCodes.Ldarg_0);
-
- // fixed (void* pinnedData = &data[offset])
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Ldarg_2);
- gen.Emit(OpCodes.Ldelema, paramT);
- gen.Emit(OpCodes.Stloc_1);
-
- // Push (1) pinnedData for memcpy
- gen.Emit(OpCodes.Ldloc_1);
-
- // totalSize = sizeof(T) * count
- gen.Emit(OpCodes.Sizeof, paramT);
- gen.Emit(OpCodes.Conv_I4);
- gen.Emit(OpCodes.Ldarg_3);
- gen.Emit(OpCodes.Mul);
- gen.Emit(OpCodes.Stloc_0);
-
- // Push (2) totalSize
- gen.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldarg_0)
+ // fixed (void* pinnedData = &data[offset])
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Ldelema, paramT)
+ .Emit(OpCodes.Stloc_1)
+ // Push (1) pinnedData for memcpy
+ .Emit(OpCodes.Ldloc_1)
+ // totalSize = sizeof(T) * count
+ .Emit(OpCodes.Sizeof, paramT)
+ .Emit(OpCodes.Conv_I4)
+ .Emit(OpCodes.Ldarg_3)
+ .Emit(OpCodes.Mul)
+ .Emit(OpCodes.Stloc_0)
+ // Push (2) totalSize
+ .Emit(OpCodes.Ldloc_0);
// Emit cpblk
- EmitCpblk(method, gen);
+ EmitCpblk(il);
// Return pDest + totalSize
- gen.Emit(OpCodes.Ldloc_0);
- gen.Emit(OpCodes.Conv_I);
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Add);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ il.Emit(OpCodes.Ldloc_0)
+ .Emit(OpCodes.Conv_I)
+ .Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Add)
+ .Emit(OpCodes.Ret);
}
///
@@ -486,45 +468,38 @@ private void CreateReadMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
+ var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
method.Body.Variables.Add(new VariableDefinition(intType));
// local(1) T*
-
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// fixed (void* pinnedData = &data[offset])
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Stloc_1);
-
- // Push (0) pinnedData for memcpy
- gen.Emit(OpCodes.Ldloc_1);
-
- // Push (1) pSrc for memcpy
- gen.Emit(OpCodes.Ldarg_0);
-
- // totalSize = sizeof(T)
- gen.Emit(OpCodes.Sizeof, paramT);
- gen.Emit(OpCodes.Conv_I4);
- gen.Emit(OpCodes.Stloc_0);
-
- // Push (2) totalSize
- gen.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Stloc_1)
+ // Push (0) pinnedData for memcpy
+ .Emit(OpCodes.Ldloc_1)
+ // Push (1) pSrc for memcpy
+ .Emit(OpCodes.Ldarg_0)
+ // totalSize = sizeof(T)
+ .Emit(OpCodes.Sizeof, paramT)
+ .Emit(OpCodes.Conv_I4)
+ .Emit(OpCodes.Stloc_0)
+ // Push (2) totalSize
+ .Emit(OpCodes.Ldloc_0);
// Emit cpblk
- EmitCpblk(method, gen);
+ EmitCpblk(il);
// Return pDest + totalSize
- gen.Emit(OpCodes.Ldloc_0);
- gen.Emit(OpCodes.Conv_I);
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Add);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ il.Emit(OpCodes.Ldloc_0)
+ .Emit(OpCodes.Conv_I)
+ .Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Add)
+ .Emit(OpCodes.Ret);
}
///
@@ -539,12 +514,10 @@ private void CreateReadRawMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
- var paramT = method.GenericParameters[0];
+ var il = new ILBuilder(method.Body, assembly.MainModule);
// Push (1) pSrc for memcpy
- gen.Emit(OpCodes.Cpobj);
-
+ il.Emit(OpCodes.Cpobj);
}
///
@@ -559,7 +532,7 @@ private void CreateReadRangeMethod(MethodDefinition method)
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
- var gen = method.Body.GetILProcessor();
+ var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -568,39 +541,33 @@ private void CreateReadRangeMethod(MethodDefinition method)
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// fixed (void* pinnedData = &data[offset])
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Ldarg_2);
- gen.Emit(OpCodes.Ldelema, paramT);
- gen.Emit(OpCodes.Stloc_1);
-
- // Push (0) pinnedData for memcpy
- gen.Emit(OpCodes.Ldloc_1);
-
- // Push (1) pDest for memcpy
- gen.Emit(OpCodes.Ldarg_0);
-
- // totalSize = sizeof(T) * count
- gen.Emit(OpCodes.Sizeof, paramT);
- gen.Emit(OpCodes.Conv_I4);
- gen.Emit(OpCodes.Ldarg_3);
- gen.Emit(OpCodes.Conv_I4);
- gen.Emit(OpCodes.Mul);
- gen.Emit(OpCodes.Stloc_0);
-
- // Push (2) totalSize
- gen.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Ldelema, paramT)
+ .Emit(OpCodes.Stloc_1)
+ // Push (0) pinnedData for memcpy
+ .Emit(OpCodes.Ldloc_1)
+ // Push (1) pDest for memcpy
+ .Emit(OpCodes.Ldarg_0)
+ // totalSize = sizeof(T) * count
+ .Emit(OpCodes.Sizeof, paramT)
+ .Emit(OpCodes.Conv_I4)
+ .Emit(OpCodes.Ldarg_3)
+ .Emit(OpCodes.Conv_I4)
+ .Emit(OpCodes.Mul)
+ .Emit(OpCodes.Stloc_0)
+ // Push (2) totalSize
+ .Emit(OpCodes.Ldloc_0);
// Emit cpblk
- EmitCpblk(method, gen);
+ EmitCpblk(il);
// Return pDest + totalSize
- gen.Emit(OpCodes.Ldloc_0);
- gen.Emit(OpCodes.Conv_I);
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Add);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ il.Emit(OpCodes.Ldloc_0)
+ .Emit(OpCodes.Conv_I)
+ .Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Add)
+ .Emit(OpCodes.Ret);
}
///
@@ -614,16 +581,13 @@ private void CreateMemcpy(MethodDefinition methodCopyStruct)
{
methodCopyStruct.Body.Instructions.Clear();
- var gen = methodCopyStruct.Body.GetILProcessor();
-
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Ldarg_2);
- gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
- gen.Emit(OpCodes.Cpblk);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ var il = new ILBuilder(methodCopyStruct.Body, assembly.MainModule);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
+ .Emit(OpCodes.Cpblk)
+ .Emit(OpCodes.Ret);
}
///
@@ -637,32 +601,27 @@ private void CreateMemset(MethodDefinition methodSetStruct)
{
methodSetStruct.Body.Instructions.Clear();
- var gen = methodSetStruct.Body.GetILProcessor();
-
- gen.Emit(OpCodes.Ldarg_0);
- gen.Emit(OpCodes.Ldarg_1);
- gen.Emit(OpCodes.Ldarg_2);
- gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
- gen.Emit(OpCodes.Initblk);
-
- // Ret
- gen.Emit(OpCodes.Ret);
+ var il = new ILBuilder(methodSetStruct.Body, assembly.MainModule);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
+ .Emit(OpCodes.Initblk)
+ .Emit(OpCodes.Ret);
}
///
/// Emits the cpblk method, supporting x86 and x64 platform.
///
- /// The method.
- /// The gen.
- private void EmitCpblk(MethodDefinition method, ILProcessor gen)
+ /// The IL builder.
+ private static void EmitCpblk(ILBuilder il)
{
- var cpblk = gen.Create(OpCodes.Cpblk);
- //gen.Emit(OpCodes.Sizeof, voidPointerType);
- //gen.Emit(OpCodes.Ldc_I4_8);
- //gen.Emit(OpCodes.Bne_Un_S, cpblk);
- gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
- gen.Append(cpblk);
-
+ var cpblk = Instruction.Create(OpCodes.Cpblk);
+ //il.Emit(OpCodes.Sizeof, voidPointerType);
+ //il.Emit(OpCodes.Ldc_I4_8);
+ //il.Emit(OpCodes.Bne_Un_S, cpblk);
+ il.Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
+ .Append(cpblk);
}
private List GetSharpDXAttributes(MethodDefinition method)
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs
index a802087ca2..d2020cf076 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs
@@ -22,7 +22,7 @@ public void ProcessSerializers(CecilSerializerContext context)
// For each profile, try to instantiate all types existing in default profile
foreach (var type in defaultProfile.SerializableTypes)
{
- context.GenerateSerializer(type.Key, false, profile.Key);
+ context.ResolveSerializer(type.Key, false, profile.Key);
}
}
}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs b/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs
index 151fc45a64..635b1c0fb9 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs
@@ -65,6 +65,21 @@ public static TypeReference Process(TypeReference context, TypeReference type)
return result;
}
+ ///
+ /// Creates a visitor that maps generic parameters from to .
+ /// Returns null if the source has no generic parameters.
+ ///
+ public static ResolveGenericsVisitor? FromMapping(IGenericParameterProvider source, IGenericParameterProvider target)
+ {
+ if (source.GenericParameters.Count == 0)
+ return null;
+
+ var mapping = new Dictionary();
+ for (int i = 0; i < source.GenericParameters.Count; i++)
+ mapping[source.GenericParameters[i]] = target.GenericParameters[i];
+ return new ResolveGenericsVisitor(mapping);
+ }
+
public override TypeReference Visit(GenericParameter type)
{
TypeReference typeParent = type;
diff --git a/sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs b/sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs
similarity index 55%
rename from sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs
rename to sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs
index cb0c3de7aa..9827fc7ae6 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs
@@ -1,144 +1,38 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Runtime.Versioning;
-using Stride.Core.AssemblyProcessor.Serializers;
using System.Runtime.CompilerServices;
-using System.Text;
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
-internal class ComplexSerializerRegistry
+///
+/// Static utility methods for analyzing serializable types and generating names.
+///
+internal static class SerializationHelpers
{
- private static readonly HashSet forbiddenKeywords;
- private static readonly HashSet ignoredMembers;
-
- static ComplexSerializerRegistry()
- {
- ignoredMembers = [];
-
- forbiddenKeywords =
- [ "obj", "stream", "mode",
- "abstract", "event", "new", "struct",
- "as", "explicit", "null", "switch",
- "base", "extern", "object", "this",
- "bool", "false", "operator", "throw",
- "break", "finally", "out", "true",
- "byte", "fixed", "override", "try",
- "case", "float", "params", "typeof",
- "catch", "for", "private", "uint",
- "char", "foreach", "protected", "ulong",
- "checked", "goto", "public", "unchecked",
- "class", "if", "readonly", "unsafe",
- "const", "implicit", "ref", "ushort",
- "continue", "in", "return", "using",
- "decimal", "int", "sbyte", "virtual",
- "default", "interface", "sealed", "volatile",
- "delegate", "internal", "short", "void",
- "do", "is", "sizeof", "while",
- "double", "lock", "stackalloc",
- "else", "long", "static",
- "enum", "namespace", "string" ];
- }
-
- public List ReferencedAssemblySerializerFactoryTypes { get; } = [];
-
- public CecilSerializerContext Context { get; }
-
- //private List serializerFactories = new List();
-
- public string? TargetFramework { get; }
-
- public string ClassName { get; }
-
- public AssemblyDefinition Assembly { get; }
-
- public List SerializerDependencies { get; } = [];
-
- public List SerializerFactories { get; } = [];
-
- public ComplexSerializerRegistry(PlatformType platform, AssemblyDefinition assembly, TextWriter log)
- {
- Assembly = assembly;
- ClassName = Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory";
-
- // Register referenced assemblies serializer factory, so that we can call them recursively
- foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences)
- {
- try
- {
- var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName);
-
- var assemblySerializerFactoryType = GetSerializerFactoryType(referencedAssembly);
- if (assemblySerializerFactoryType != null)
- ReferencedAssemblySerializerFactoryTypes.Add(assemblySerializerFactoryType);
- }
- catch (AssemblyResolutionException)
- {
- continue;
- }
- }
-
- // Find target framework and replicate it for serializer assembly.
- var targetFrameworkAttribute = assembly.CustomAttributes
- .FirstOrDefault(x => x.AttributeType.FullName == typeof(TargetFrameworkAttribute).FullName);
- if (targetFrameworkAttribute != null)
- {
- TargetFramework = "\"" + (string)targetFrameworkAttribute.ConstructorArguments[0].Value + "\"";
- var frameworkDisplayNameField = targetFrameworkAttribute.Properties.FirstOrDefault(x => x.Name == "FrameworkDisplayName");
- if (frameworkDisplayNameField.Name != null)
- {
- TargetFramework += ", FrameworkDisplayName=\"" + (string)frameworkDisplayNameField.Argument.Value + "\"";
- }
- }
-
- // Prepare serializer processors
- Context = new CecilSerializerContext(platform, assembly, log);
- var processors = new List
- {
- // Import list of serializer registered by referenced assemblies
- new ReferencedAssemblySerializerProcessor(),
-
- // Generate serializers for types tagged as serializable
- new CecilComplexClassSerializerProcessor(),
-
- // Generate serializers for PropertyKey and ParameterKey
- new PropertyKeySerializerProcessor(),
-
- // Update Engine (with AnimationData)
- new UpdateEngineProcessor(),
-
- // Profile serializers
- new ProfileSerializerProcessor(),
-
- // Data contract aliases
- new DataContractAliasProcessor()
- };
-
- // Apply each processor
- foreach (var processor in processors)
- processor.ProcessSerializers(Context);
- }
-
- private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly)
- {
- var assemblySerializerFactoryAttribute =
- referencedAssembly.CustomAttributes.FirstOrDefault(
- x =>
- x.AttributeType.FullName ==
- "Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
-
- // No serializer factory?
- if (assemblySerializerFactoryAttribute == null)
- return null;
-
- var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value;
- if (typeReference == null)
- return null;
-
- return typeReference.Resolve();
- }
+ private static readonly HashSet forbiddenKeywords =
+ [ "obj", "stream", "mode",
+ "abstract", "event", "new", "struct",
+ "as", "explicit", "null", "switch",
+ "base", "extern", "object", "this",
+ "bool", "false", "operator", "throw",
+ "break", "finally", "out", "true",
+ "byte", "fixed", "override", "try",
+ "case", "float", "params", "typeof",
+ "catch", "for", "private", "uint",
+ "char", "foreach", "protected", "ulong",
+ "checked", "goto", "public", "unchecked",
+ "class", "if", "readonly", "unsafe",
+ "const", "implicit", "ref", "ushort",
+ "continue", "in", "return", "using",
+ "decimal", "int", "sbyte", "virtual",
+ "default", "interface", "sealed", "volatile",
+ "delegate", "internal", "short", "void",
+ "do", "is", "sizeof", "while",
+ "double", "lock", "stackalloc",
+ "else", "long", "static",
+ "enum", "namespace", "string" ];
private static string TypeNameWithoutGenericEnding(TypeReference type)
{
@@ -175,88 +69,17 @@ public static string SerializerTypeName(TypeReference type, bool appendGenerics,
return typeName;
}
- public static string GetSerializerInstantiateMethodName(TypeReference serializerType, bool appendGenerics)
- {
- return "Instantiate_" + SerializerTypeName(serializerType, appendGenerics, false);
- }
-
- ///
- /// Generates the generic constraints in a code form.
- ///
- /// The type.
- ///
- public static string GenerateGenericConstraints(TypeReference type)
- {
- if (!type.HasGenericParameters)
- return string.Empty;
-
- var result = new StringBuilder();
- foreach (var genericParameter in type.GenericParameters)
- {
- // If no constraints, skip it
- var hasContraints = genericParameter.HasReferenceTypeConstraint || genericParameter.HasNotNullableValueTypeConstraint || genericParameter.Constraints.Count > 0 || genericParameter.HasDefaultConstructorConstraint;
- if (!hasContraints)
- {
- continue;
- }
-
- bool hasFirstContraint = false;
-
- result.AppendFormat(" where {0}: ", genericParameter.Name);
-
- // Where class/struct constraint must be before any other constraint
- if (genericParameter.HasReferenceTypeConstraint)
- {
- result.AppendFormat("class");
- hasFirstContraint = true;
- }
- else if (genericParameter.HasNotNullableValueTypeConstraint)
- {
- result.AppendFormat("struct");
- hasFirstContraint = true;
- }
-
- foreach (var genericParameterConstraint in genericParameter.Constraints)
- {
- // Skip value type constraint
- if (genericParameterConstraint.ConstraintType.FullName != typeof(ValueType).FullName)
- {
- if (hasFirstContraint)
- {
- result.Append(", ");
- }
-
- result.AppendFormat("{0}", genericParameterConstraint.ConstraintType.ConvertCSharp());
- result.AppendLine();
-
- hasFirstContraint = true;
- }
- }
-
- // New constraint must be last
- if (!genericParameter.HasNotNullableValueTypeConstraint && genericParameter.HasDefaultConstructorConstraint)
- {
- if (hasFirstContraint)
- {
- result.Append(", ");
- }
-
- result.AppendFormat("new()");
- result.AppendLine();
- }
- }
-
- return result.ToString();
- }
-
- public static void IgnoreMember(IMemberDefinition memberInfo)
+ public static string CreateMemberVariableName(IMemberDefinition memberInfo)
{
- ignoredMembers.Add(memberInfo);
+ var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..];
+ if (forbiddenKeywords.Contains(memberVariableName))
+ memberVariableName += "_";
+ return memberVariableName;
}
- public static IEnumerable GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
+ public static IEnumerable GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet? ignoredMembers = null)
{
- foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride))
+ foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride, ignoredMembers))
{
var serializableItem = serializableItemOriginal;
@@ -270,7 +93,7 @@ public static IEnumerable GetSerializableItems(TypeReference t
}
}
- public static IEnumerable GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
+ public static IEnumerable GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet? ignoredMembers = null)
{
ComplexTypeSerializerFlags flags;
@@ -282,7 +105,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition
if (field.IsStatic)
continue;
- if (ignoredMembers.Contains(field))
+ if (ignoredMembers?.Contains(field) == true)
continue;
if (field.IsPublic || (field.IsAssembly && field.CustomAttributes.Any(a => a.AttributeType.FullName == "Stride.Core.DataMemberAttribute")))
@@ -315,7 +138,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition
}
// Ignore blacklisted properties
- if (ignoredMembers.Contains(property))
+ if (ignoredMembers?.Contains(property) == true)
continue;
properties.Add(property);
@@ -385,7 +208,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition
}
}
- static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property)
+ private static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property)
{
var get = property.GetMethod;
var set = property.SetMethod;
@@ -445,14 +268,6 @@ private static bool IsReadOnlyTypeSerializable(TypeReference type)
&& !((type.MetadataType == MetadataType.ValueType || type.MetadataType == MetadataType.Class) && type.Resolve().IsValueType);
}
- public static string CreateMemberVariableName(IMemberDefinition memberInfo)
- {
- var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..];
- if (forbiddenKeywords.Contains(memberVariableName))
- memberVariableName += "_";
- return memberVariableName;
- }
-
public struct SerializableItem
{
public bool HasFixedAttribute;
diff --git a/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs
index e308694fd4..6d7be94a96 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs
@@ -7,6 +7,7 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
+using Stride.Core.AssemblyProcessor.Serializers;
using Stride.Core.Serialization;
using Stride.Core.Storage;
using CustomAttributeNamedArgument = Mono.Cecil.CustomAttributeNamedArgument;
@@ -16,23 +17,31 @@
namespace Stride.Core.AssemblyProcessor;
-internal class SerializationProcessor : IAssemblyDefinitionProcessor
+///
+/// Bundles per-descriptor state used during serializer code generation.
+///
+internal struct SerializerCodegenContext
{
- public delegate void RegisterSourceCode(string code, string? name = null);
+ public TypeDefinition Type;
+ public TypeDefinition SerializerType;
+ public TypeReference[] GenericParameters;
+ public TypeReference TypeWithGenerics;
+ public SerializationHelpers.SerializableItem[] SerializableItems;
+ public Dictionary SerializableItemInfos;
+ public Dictionary LocalsByTypes;
+ public TypeReference ParentType;
+ public FieldDefinition ParentSerializerField;
+ public ModuleDefinition Module;
+}
+internal class SerializationProcessor : IAssemblyDefinitionProcessor
+{
public bool Process(AssemblyProcessorContext context)
{
- var registry = new ComplexSerializerRegistry(context.Platform, context.Assembly, context.Log);
-
- // Register default serialization profile (to help AOT generic instantiation of serializers)
- RegisterDefaultSerializationProfile(context.AssemblyResolver, context.Assembly, registry, context.Log);
-
- // Generate serializer code
- // Create the serializer code generator
- //var serializerGenerator = new ComplexSerializerCodeGenerator(registry);
- //sourceCodeRegisterAction(serializerGenerator.TransformText(), "DataSerializers");
+ var serializerContext = new CecilSerializerContext(context.Platform, context.Assembly, context.Log);
- GenerateSerializerCode(registry, out var serializationHash);
+ // Generate serializer code using Cecil and ILBuilder
+ GenerateSerializerCode(serializerContext, out var serializationHash);
context.SerializationHash = serializationHash;
@@ -40,75 +49,138 @@ public bool Process(AssemblyProcessorContext context)
}
///
- /// Generates serializer code using Cecil.
- /// Note: we might want something more fluent? (probably lot of work to get the system working, but would make changes easier to do -- not sure if worth it considering it didn't change much recently)
+ /// Generates serializer code using Cecil and for readable IL emission.
///
- ///
- private static void GenerateSerializerCode(ComplexSerializerRegistry registry, out ObjectId serializationHash)
+ private static void GenerateSerializerCode(CecilSerializerContext serializerContext, out ObjectId serializationHash)
{
var hash = new ObjectIdBuilder();
// First, hash global binary format version, in case it gets bumped
hash.Write(DataSerializer.BinaryFormatVersion);
- var assembly = registry.Assembly;
+ var assembly = serializerContext.Assembly;
+ var module = assembly.MainModule;
var strideCoreModule = assembly.GetStrideCoreModule();
- var dataSerializerTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializer`1"));
+ // Generate serializer classes for each pending type
+ GenerateSerializerTypes(serializerContext, module, strideCoreModule, hash);
+
+ // Generate the factory type with attributes and module initializer
+ GenerateSerializerFactory(serializerContext, assembly, module, strideCoreModule);
+
+ serializationHash = hash.ComputeHash();
+ }
+
+ ///
+ /// Creates the for a serializer from its ,
+ /// adds it to the module, and wires it into the .
+ ///
+ private static TypeDefinition CreateSerializerTypeDefinition(
+ SerializerDescriptor descriptor,
+ ModuleDefinition module,
+ ModuleDefinition strideCoreModule)
+ {
+ var type = descriptor.DataType;
+ var serializerType = new TypeDefinition("Stride.Core.DataSerializers", descriptor.SerializerClassName,
+ TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Sealed |
+ TypeAttributes.BeforeFieldInit |
+ (descriptor.IsPublic ? TypeAttributes.Public : TypeAttributes.NotPublic));
+
+ // Clone generic parameters from the data type
+ if (type.HasGenericParameters)
+ {
+ foreach (var genericParameter in type.GenericParameters)
+ {
+ var newGenericParameter = new GenericParameter(genericParameter.Name, serializerType)
+ {
+ Attributes = genericParameter.Attributes
+ };
+
+ foreach (var constraint in genericParameter.Constraints)
+ newGenericParameter.Constraints.Add(constraint);
+
+ serializerType.GenericParameters.Add(newGenericParameter);
+ }
+ }
+
+ // Setup base class
+ var baseSerializerName = descriptor.UseClassDataSerializer
+ ? "Stride.Core.Serialization.ClassDataSerializer`1"
+ : "Stride.Core.Serialization.DataSerializer`1";
+ var classDataSerializerType = strideCoreModule.GetType(baseSerializerName);
+ var parentType = module.ImportReference(classDataSerializerType)
+ .MakeGenericType(type.MakeGenericType(serializerType.GenericParameters.ToArray()));
+ serializerType.BaseType = parentType;
+
+ module.Types.Add(serializerType);
+
+ // Update the SerializableTypeInfo to point to the real TypeDefinition
+ descriptor.SerializableTypeInfo.SerializerType = serializerType;
+
+ return serializerType;
+ }
+
+ ///
+ /// Generates serializer classes (constructor, Initialize, Serialize methods) for each pending type.
+ ///
+ private static void GenerateSerializerTypes(
+ CecilSerializerContext serializerContext,
+ ModuleDefinition module,
+ ModuleDefinition strideCoreModule,
+ ObjectIdBuilder hash)
+ {
+ var dataSerializerTypeRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializer`1"));
var serializerSelectorType = strideCoreModule.GetType("Stride.Core.Serialization.SerializerSelector");
- var serializerSelectorTypeRef = assembly.MainModule.ImportReference(serializerSelectorType);
- var serializerSelectorGetSerializerRef = assembly.MainModule.ImportReference(serializerSelectorType.Methods.Single(x => x.Name == "GetSerializer" && x.Parameters.Count == 0 && x.GenericParameters.Count == 1));
- var memberSerializerCreateRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.MemberSerializer`1").Methods.Single(x => x.Name == "Create"));
+ var serializerSelectorTypeRef = module.ImportReference(serializerSelectorType);
+ var serializerSelectorGetSerializerRef = module.ImportReference(serializerSelectorType.Methods.Single(x => x.Name == "GetSerializer" && x.Parameters.Count == 0 && x.GenericParameters.Count == 1));
+ var memberSerializerCreateRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.MemberSerializer`1").Methods.Single(x => x.Name == "Create"));
var dataSerializerSerializeMethod = dataSerializerTypeRef.Resolve().Methods.Single(x => x.Name == "Serialize" && (x.Attributes & MethodAttributes.Abstract) != 0);
- var dataSerializerSerializeMethodRef = assembly.MainModule.ImportReference(dataSerializerSerializeMethod);
+ var dataSerializerSerializeMethodRef = module.ImportReference(dataSerializerSerializeMethod);
- // Generate serializer code for each type (we generate code similar to ComplexClassSerializerGenerator.tt, see this file for reference)
- foreach (var complexType in registry.Context.ComplexTypes)
+ foreach (var descriptor in serializerContext.PendingSerializers)
{
- var type = complexType.Key;
- var serializerType = (TypeDefinition)complexType.Value.SerializerType;
+ var type = descriptor.DataType;
+ var serializerType = CreateSerializerTypeDefinition(descriptor, module, strideCoreModule);
var genericParameters = serializerType.GenericParameters.ToArray();
var typeWithGenerics = type.MakeGenericType(genericParameters);
// Hash
hash.Write(typeWithGenerics.FullName);
- TypeReference parentType = null;
- FieldDefinition parentSerializerField = null;
- if (complexType.Value.ComplexSerializerProcessParentType != null)
+ var ctx = new SerializerCodegenContext
+ {
+ Type = type,
+ SerializerType = serializerType,
+ GenericParameters = genericParameters,
+ TypeWithGenerics = typeWithGenerics,
+ SerializableItems = descriptor.SerializableItems,
+ SerializableItemInfos = new Dictionary(TypeReferenceEqualityComparer.Default),
+ LocalsByTypes = new Dictionary(TypeReferenceEqualityComparer.Default),
+ Module = module,
+ };
+
+ if (descriptor.SerializedParentType != null)
{
- parentType = complexType.Value.ComplexSerializerProcessParentType;
- serializerType.Fields.Add(parentSerializerField = new FieldDefinition("parentSerializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(parentType)));
+ ctx.ParentType = descriptor.SerializedParentType;
+ ctx.ParentSerializerField = new FieldDefinition("parentSerializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(ctx.ParentType));
+ serializerType.Fields.Add(ctx.ParentSerializerField);
hash.Write("parent");
}
- var serializableItems = ComplexSerializerRegistry.GetSerializableItems(type, true).ToArray();
- var serializableItemInfos = new Dictionary(TypeReferenceEqualityComparer.Default);
- var localsByTypes = new Dictionary(TypeReferenceEqualityComparer.Default);
+ var genericResolver = ResolveGenericsVisitor.FromMapping(type, serializerType);
- ResolveGenericsVisitor genericResolver = null;
- if (type.HasGenericParameters)
+ foreach (var serializableItem in ctx.SerializableItems)
{
- var genericMapping = new Dictionary();
- for (int i = 0; i < type.GenericParameters.Count; i++)
- {
- genericMapping[type.GenericParameters[i]] = serializerType.GenericParameters[i];
- }
- genericResolver = new ResolveGenericsVisitor(genericMapping);
- }
-
- foreach (var serializableItem in serializableItems)
- {
- if (serializableItemInfos.ContainsKey(serializableItem.Type))
+ if (ctx.SerializableItemInfos.ContainsKey(serializableItem.Type))
continue;
var serializableItemType = serializableItem.Type;
if (genericResolver != null)
serializableItemType = genericResolver.VisitDynamic(serializableItemType);
var fieldDefinition = new FieldDefinition($"{Utilities.BuildValidClassName(serializableItemType.FullName)}Serializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(serializableItemType));
- serializableItemInfos.Add(serializableItem.Type, (fieldDefinition, serializableItemType));
+ ctx.SerializableItemInfos.Add(serializableItem.Type, (fieldDefinition, serializableItemType));
serializerType.Fields.Add(fieldDefinition);
hash.Write(serializableItem.Type.FullName);
@@ -116,464 +188,523 @@ private static void GenerateSerializerCode(ComplexSerializerRegistry registry, o
hash.Write(serializableItem.AssignBack);
}
- // Add constructor (call parent constructor)
+ // Generates: public TypeSerializer() : base() { }
var ctor = new MethodDefinition(".ctor",
MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig |
- MethodAttributes.Public, assembly.MainModule.TypeSystem.Void);
- ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
- ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(serializerType.BaseType.Resolve().GetEmptyConstructor(true)).MakeGeneric(typeWithGenerics)));
- ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+ MethodAttributes.Public, module.TypeSystem.Void);
+ var ctorIL = new ILBuilder(ctor.Body, module);
+ ctorIL.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Call, ctorIL.Import(serializerType.BaseType.Resolve().GetEmptyConstructor(true)).MakeGeneric(typeWithGenerics))
+ .Emit(OpCodes.Ret);
serializerType.Methods.Add(ctor);
- // Add Initialize method
- var initialize = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void);
+ // Generates:
+ // public override void Initialize(SerializerSelector serializerSelector)
+ // {
+ // parentSerializer = serializerSelector.GetSerializer(); // if has parent
+ // field1Serializer = MemberSerializer.Create(serializerSelector, true);
+ // field2Serializer = MemberSerializer.Create(serializerSelector, true);
+ // ...
+ // }
+ var initialize = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, module.TypeSystem.Void);
initialize.Parameters.Add(new ParameterDefinition("serializerSelector", ParameterAttributes.None, serializerSelectorTypeRef));
- if (complexType.Value.ComplexSerializerProcessParentType != null)
+ var initIL = new ILBuilder(initialize.Body, module);
+ if (ctx.ParentType != null)
{
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, serializerSelectorGetSerializerRef.MakeGenericMethod(parentType)));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, parentSerializerField.MakeGeneric(genericParameters)));
+ // this.parentSerializer = serializerSelector.GetSerializer();
+ initIL.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Callvirt, serializerSelectorGetSerializerRef.MakeGenericMethod(ctx.ParentType))
+ .Emit(OpCodes.Stfld, ctx.ParentSerializerField.MakeGeneric(genericParameters));
}
- foreach (var serializableItem in serializableItemInfos)
+ foreach (var serializableItem in ctx.SerializableItemInfos)
{
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Call, memberSerializerCreateRef.MakeGeneric(serializableItem.Value.Type)));
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, serializableItem.Value.SerializerField.MakeGeneric(genericParameters)));
+ // this.fieldSerializer = MemberSerializer.Create(serializerSelector, true);
+ initIL.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldc_I4_1)
+ .Emit(OpCodes.Call, memberSerializerCreateRef.MakeGeneric(serializableItem.Value.Type))
+ .Emit(OpCodes.Stfld, serializableItem.Value.SerializerField.MakeGeneric(genericParameters));
}
- initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+ initIL.Emit(OpCodes.Ret);
serializerType.Methods.Add(initialize);
// Add Serialize method
- var serialize = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void);
- serialize.Parameters.Add(new ParameterDefinition("obj", ParameterAttributes.None, typeWithGenerics.MakeByReferenceType()));
- // Copy other parameters from parent method
- for (int i = 1; i < dataSerializerSerializeMethod.Parameters.Count; ++i)
- {
- var parentParameter = dataSerializerSerializeMethod.Parameters[i];
- serialize.Parameters.Add(new ParameterDefinition(parentParameter.Name, ParameterAttributes.None, assembly.MainModule.ImportReference(parentParameter.ParameterType)));
- }
+ GenerateSerializeMethod(ctx, dataSerializerSerializeMethod, dataSerializerSerializeMethodRef);
+ }
+ }
- if (complexType.Value.ComplexSerializerProcessParentType != null)
- {
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, parentSerializerField.MakeGeneric(genericParameters)));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(parentType)));
- }
+ ///
+ /// Generates the Serialize method for a complex serializer type.
+ ///
+ ///
+ /// Generates code equivalent to:
+ ///
+ /// public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream)
+ /// {
+ /// parentSerializer.Serialize(ref obj, mode, stream); // if has parent
+ /// if (mode == ArchiveMode.Serialize)
+ /// {
+ /// // For each member (field or property):
+ /// var tmp = obj.Member;
+ /// memberSerializer.Serialize(ref tmp, mode, stream);
+ /// }
+ /// else
+ /// {
+ /// // For each member:
+ /// var tmp = default(MemberType);
+ /// memberSerializer.Serialize(ref tmp, mode, stream);
+ /// obj.Member = tmp; // assign back
+ /// }
+ /// }
+ ///
+ /// For fields with AssignBack, the field address is used directly (no temp variable).
+ ///
+ private static void GenerateSerializeMethod(
+ SerializerCodegenContext ctx,
+ MethodDefinition dataSerializerSerializeMethod,
+ MethodReference dataSerializerSerializeMethodRef)
+ {
+ var module = ctx.Module;
+ var serialize = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, module.TypeSystem.Void);
+ serialize.Parameters.Add(new ParameterDefinition("obj", ParameterAttributes.None, ctx.TypeWithGenerics.MakeByReferenceType()));
+ // Copy other parameters from parent method
+ for (int i = 1; i < dataSerializerSerializeMethod.Parameters.Count; ++i)
+ {
+ var parentParameter = dataSerializerSerializeMethod.Parameters[i];
+ serialize.Parameters.Add(new ParameterDefinition(parentParameter.Name, ParameterAttributes.None, module.ImportReference(parentParameter.ParameterType)));
+ }
+
+ var il = new ILBuilder(serialize.Body, module);
+
+ // this.parentSerializer.Serialize(ref obj, mode, stream);
+ if (ctx.ParentType != null)
+ {
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, ctx.ParentSerializerField.MakeGeneric(ctx.GenericParameters))
+ .Emit(OpCodes.Ldarg_1)
+ .Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Ldarg_3)
+ .Emit(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(ctx.ParentType));
+ }
+
+ if (ctx.SerializableItems.Length > 0)
+ {
+ var deserializeLabel = ILBuilder.DefineLabel();
+ var endLabel = ILBuilder.DefineLabel();
- if (serializableItems.Length > 0)
+ // Iterate over ArchiveMode
+ for (int i = 0; i < 2; ++i)
{
- var blockStartInstructions = new[] { Instruction.Create(OpCodes.Nop), Instruction.Create(OpCodes.Nop) };
- // Iterate over ArchiveMode
- for (int i = 0; i < 2; ++i)
+ var archiveMode = i == 0 ? ArchiveMode.Serialize : ArchiveMode.Deserialize;
+
+ // Check mode
+ if (archiveMode == ArchiveMode.Serialize)
+ {
+ il.Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Ldc_I4, (int)archiveMode)
+ .Emit(OpCodes.Ceq)
+ .Emit(OpCodes.Brfalse, deserializeLabel);
+ }
+ else
{
- var archiveMode = i == 0 ? ArchiveMode.Serialize : ArchiveMode.Deserialize;
+ il.MarkLabel(deserializeLabel);
+ }
- // Check mode
- if (archiveMode == ArchiveMode.Serialize)
- {
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int) archiveMode));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ceq));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, blockStartInstructions[0]));
- }
- else
+ foreach (var serializableItem in ctx.SerializableItems)
+ {
+ if (serializableItem.HasFixedAttribute)
{
- serialize.Body.Instructions.Add(blockStartInstructions[0]);
+ throw new NotImplementedException("FixedBuffer attribute is not supported.");
}
- foreach (var serializableItem in serializableItems)
+ var memberAssignBack = serializableItem.AssignBack;
+ var memberVariableName = (serializableItem.MemberInfo is PropertyDefinition || !memberAssignBack) ? SerializationHelpers.CreateMemberVariableName(serializableItem.MemberInfo) : null;
+ var serializableItemInfo = ctx.SerializableItemInfos[serializableItem.Type];
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, serializableItemInfo.SerializerField.MakeGeneric(ctx.GenericParameters));
+
+ var fieldReference = serializableItem.MemberInfo is FieldReference ? il.Import((FieldReference)serializableItem.MemberInfo).MakeGeneric(ctx.GenericParameters) : null;
+
+ if (memberVariableName != null)
{
- if (serializableItem.HasFixedAttribute)
+ // Properties (and non-assignback fields) need a temp variable:
+ // var tmp = obj.Member; // serialize path
+ // var tmp = default(MemberType); // deserialize path
+ if (!ctx.LocalsByTypes.TryGetValue(serializableItemInfo.Type, out var tempLocal))
{
- throw new NotImplementedException("FixedBuffer attribute is not supported.");
+ tempLocal = il.AddLocal(serializableItemInfo.Type);
+ ctx.LocalsByTypes.Add(serializableItemInfo.Type, tempLocal);
}
- var memberAssignBack = serializableItem.AssignBack;
- var memberVariableName = (serializableItem.MemberInfo is PropertyDefinition || !memberAssignBack) ? ComplexSerializerRegistry.CreateMemberVariableName(serializableItem.MemberInfo) : null;
- var serializableItemInfo = serializableItemInfos[serializableItem.Type];
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, serializableItemInfo.SerializerField.MakeGeneric(genericParameters)));
-
- var fieldReference = serializableItem.MemberInfo is FieldReference ? assembly.MainModule.ImportReference((FieldReference)serializableItem.MemberInfo).MakeGeneric(genericParameters) : null;
-
- if (memberVariableName != null)
+ if (!(archiveMode == ArchiveMode.Deserialize && memberAssignBack))
{
- // Use a temporary variable
- if (!localsByTypes.TryGetValue(serializableItemInfo.Type, out var tempLocal))
- {
- tempLocal = new VariableDefinition(serializableItemInfo.Type);
- localsByTypes.Add(serializableItemInfo.Type, tempLocal);
- serialize.Body.Variables.Add(tempLocal);
- serialize.Body.InitLocals = true;
- }
-
- if (!(archiveMode == ArchiveMode.Deserialize && memberAssignBack))
- {
- // obj.Member
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- if (!type.IsValueType)
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref));
-
- if (serializableItem.MemberInfo is PropertyDefinition property)
- {
- var getMethod = property.Resolve().GetMethod;
- serialize.Body.Instructions.Add(Instruction.Create(getMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(getMethod).MakeGeneric(genericParameters)));
- }
- else if (serializableItem.MemberInfo is FieldDefinition)
- {
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, fieldReference));
- }
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stloc, tempLocal));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal));
- }
- else
- {
- // default(T)
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Dup));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Initobj, serializableItemInfo.Type));
- }
+ // var tmp = obj.Member;
+ il.Emit(OpCodes.Ldarg_1);
+ if (!ctx.Type.IsValueType)
+ il.Emit(OpCodes.Ldind_Ref);
+
+ EmitLoadMember(il, serializableItem, fieldReference, ctx.GenericParameters);
+ il.Emit(OpCodes.Stloc, tempLocal)
+ .Emit(OpCodes.Ldloca, tempLocal);
}
else
{
- // Use object directly
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- if (!type.IsValueType)
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldflda, fieldReference));
- }
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2));
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3));
-
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(serializableItemInfo.Type)));
-
- if (archiveMode == ArchiveMode.Deserialize && memberVariableName != null && memberAssignBack)
- {
- // Need to copy back to object
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
- if (!type.IsValueType)
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref));
-
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloc, localsByTypes[serializableItemInfo.Type]));
-
- if (serializableItem.MemberInfo is PropertyDefinition property)
- {
- var setMethod = property.Resolve().SetMethod;
- serialize.Body.Instructions.Add(Instruction.Create(setMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(setMethod).MakeGeneric(genericParameters)));
- }
- else if (serializableItem.MemberInfo is FieldDefinition)
- {
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, fieldReference));
- }
+ // var tmp = default(MemberType);
+ il.Emit(OpCodes.Ldloca, tempLocal)
+ .Emit(OpCodes.Dup)
+ .Emit(OpCodes.Initobj, serializableItemInfo.Type);
}
}
+ else
+ {
+ // Field with AssignBack: pass field address directly — &obj.field
+ il.Emit(OpCodes.Ldarg_1);
+ if (!ctx.Type.IsValueType)
+ il.Emit(OpCodes.Ldind_Ref);
+ il.Emit(OpCodes.Ldflda, fieldReference);
+ }
+ // memberSerializer.Serialize(ref tmp, mode, stream);
+ il.Emit(OpCodes.Ldarg_2)
+ .Emit(OpCodes.Ldarg_3)
+ .Emit(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(serializableItemInfo.Type));
- if (archiveMode == ArchiveMode.Serialize)
+ if (archiveMode == ArchiveMode.Deserialize && memberVariableName != null && memberAssignBack)
{
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Br, blockStartInstructions[1]));
+ // obj.Member = tmp;
+ il.Emit(OpCodes.Ldarg_1);
+ if (!ctx.Type.IsValueType)
+ il.Emit(OpCodes.Ldind_Ref);
+
+ il.Emit(OpCodes.Ldloc, ctx.LocalsByTypes[serializableItemInfo.Type]);
+ EmitStoreMember(il, serializableItem, fieldReference, ctx.GenericParameters);
}
}
- serialize.Body.Instructions.Add(blockStartInstructions[1]);
+ if (archiveMode == ArchiveMode.Serialize)
+ {
+ il.Emit(OpCodes.Br, endLabel);
+ }
}
- serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
- serializerType.Methods.Add(serialize);
- //assembly.MainModule.Types.Add(serializerType);
+ il.MarkLabel(endLabel);
}
+ il.Emit(OpCodes.Ret);
+ ctx.SerializerType.Methods.Add(serialize);
+ }
- var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly);
- var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly);
+ private static void EmitLoadMember(ILBuilder il, SerializationHelpers.SerializableItem item, FieldReference fieldReference, TypeReference[] genericParameters)
+ {
+ if (item.MemberInfo is PropertyDefinition property)
+ {
+ var getMethod = property.Resolve().GetMethod;
+ il.Emit(getMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, il.Import(getMethod).MakeGeneric(genericParameters));
+ }
+ else if (item.MemberInfo is FieldDefinition)
+ {
+ il.Emit(OpCodes.Ldfld, fieldReference);
+ }
+ }
- // String
- var stringType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(string).FullName);
- var stringTypeRef = assembly.MainModule.ImportReference(stringType);
- // Type
- var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName);
- var typeTypeRef = assembly.MainModule.ImportReference(typeType);
- var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle));
- var getTokenInfoExMethod = reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo));
- var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName);
- // Note: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime
- var getTypeInfoAssembly = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod;
- var getTypeInfoModule = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Module)).GetMethod;
- var typeHandleProperty = typeType.Properties.First(x => x.Name == nameof(Type.TypeHandle));
- var getTypeHandleMethodRef = assembly.MainModule.ImportReference(typeHandleProperty.GetMethod);
+ private static void EmitStoreMember(ILBuilder il, SerializationHelpers.SerializableItem item, FieldReference fieldReference, TypeReference[] genericParameters)
+ {
+ if (item.MemberInfo is PropertyDefinition property)
+ {
+ var setMethod = property.Resolve().SetMethod;
+ il.Emit(setMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, il.Import(setMethod).MakeGeneric(genericParameters));
+ }
+ else if (item.MemberInfo is FieldDefinition)
+ {
+ il.Emit(OpCodes.Stfld, fieldReference);
+ }
+ }
+
+ ///
+ /// Generates the serializer factory type with s
+ /// and the module initializer that registers all serializers at runtime.
+ ///
+ private static void GenerateSerializerFactory(
+ CecilSerializerContext serializerContext,
+ AssemblyDefinition assembly,
+ ModuleDefinition module,
+ ModuleDefinition strideCoreModule)
+ {
+ var typeTypeRef = module.ImportReference(CecilExtensions.FindCorlibAssembly(assembly).MainModule.GetTypeResolved(typeof(Type).FullName));
- // Generate code
+ // Create factory type
var serializerFactoryType = new TypeDefinition("Stride.Core.DataSerializers",
Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory",
TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.AutoClass |
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract,
- assembly.MainModule.TypeSystem.Object);
- assembly.MainModule.Types.Add(serializerFactoryType);
+ module.TypeSystem.Object);
+ module.Types.Add(serializerFactoryType);
- var dataSerializerModeTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGenericMode"));
+ // Add [DataSerializerGlobal] attributes for each serializable type
+ EmitDataSerializerGlobalAttributes(serializerContext, module, strideCoreModule, typeTypeRef, serializerFactoryType);
- var dataSerializerGlobalAttribute = strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGlobalAttribute");
- var dataSerializerGlobalCtorRef = assembly.MainModule.ImportReference(dataSerializerGlobalAttribute.GetConstructors().Single(x => !x.IsStatic && x.Parameters.Count == 5));
+ // Generate the Initialize method (module initializer body)
+ GenerateInitializeMethod(serializerContext, assembly, module, strideCoreModule, serializerFactoryType);
- foreach (var profile in registry.Context.SerializableTypesProfiles)
+ // Add [AssemblySerializerFactory] attribute to the assembly
+ var assemblySerializerFactoryAttribute = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
+ assembly.CustomAttributes.Add(new CustomAttribute(module.ImportReference(assemblySerializerFactoryAttribute.GetEmptyConstructor()))
{
- foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.Local))
+ Fields =
{
- // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(false)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(false) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")]
- serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef)
- {
- ConstructorArguments =
- {
- new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null),
- new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)),
- new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.Mode),
- new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.Inherited),
- new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.ComplexSerializer),
- },
- Properties =
- {
- new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key))
- },
- });
+ new CustomAttributeNamedArgument("Type", new CustomAttributeArgument(typeTypeRef, serializerFactoryType)),
}
- foreach (var type in profile.Value.GenericSerializableTypes.Where(x => x.Value.Local))
+ });
+ }
+
+ ///
+ /// Adds [DataSerializerGlobal] attributes to the factory type for each serializable type.
+ ///
+ private static void EmitDataSerializerGlobalAttributes(
+ CecilSerializerContext serializerContext,
+ ModuleDefinition module,
+ ModuleDefinition strideCoreModule,
+ TypeReference typeTypeRef,
+ TypeDefinition serializerFactoryType)
+ {
+ var dataSerializerModeTypeRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGenericMode"));
+ var dataSerializerGlobalAttribute = strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGlobalAttribute");
+ var dataSerializerGlobalCtorRef = module.ImportReference(dataSerializerGlobalAttribute.GetConstructors().Single(x => !x.IsStatic && x.Parameters.Count == 5));
+
+ foreach (var profile in serializerContext.SerializableTypesProfiles)
+ {
+ // Emit attributes for both concrete and generic serializable types
+ var allTypes = profile.Value.SerializableTypes.Where(x => x.Value.Local)
+ .Concat(profile.Value.GenericSerializableTypes.Where(x => x.Value.Local));
+
+ foreach (var type in allTypes)
{
- // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(true)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(true) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")]
serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef)
{
ConstructorArguments =
{
- new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null),
- new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)),
+ new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? module.ImportReference(type.Value.SerializerType) : null),
+ new CustomAttributeArgument(typeTypeRef, module.ImportReference(type.Key)),
new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.Mode),
- new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.Inherited),
- new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.ComplexSerializer),
+ new CustomAttributeArgument(module.TypeSystem.Boolean, type.Value.Inherited),
+ new CustomAttributeArgument(module.TypeSystem.Boolean, type.Value.IsGeneratedSerializer),
},
Properties =
{
- new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key))
+ new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(module.TypeSystem.String, profile.Key))
},
});
}
}
+ }
+
+ ///
+ /// Generates the Initialize method that serves as the module initializer,
+ /// registering all serializers with the runtime .
+ ///
+ ///
+ /// Generates code equivalent to:
+ ///
+ /// static void Initialize()
+ /// {
+ /// var assemblySerializers = new AssemblySerializers(typeof(Factory).GetTypeInfo().Assembly);
+ /// // Register DataContract aliases
+ /// assemblySerializers.DataContractAliases.Add(new DataContractAlias("alias", typeof(T), isRemap));
+ /// // Register referenced modules
+ /// assemblySerializers.Modules.Add(typeof(RefFactory).GetTypeInfo().Module);
+ /// // Register serializer entries per profile
+ /// var profile = new AssemblySerializersPerProfile();
+ /// profile.Add(new AssemblySerializerEntry(objectId, typeof(T), typeof(TSerializer)));
+ /// assemblySerializers.Profiles["Default"] = profile;
+ /// DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers);
+ /// AssemblyRegistry.Register(typeof(Factory).GetTypeInfo().Assembly, new[] { "Engine" });
+ /// }
+ ///
+ ///
+ private static void GenerateInitializeMethod(
+ CecilSerializerContext serializerContext,
+ AssemblyDefinition assembly,
+ ModuleDefinition module,
+ ModuleDefinition strideCoreModule,
+ TypeDefinition serializerFactoryType)
+ {
+ var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly);
+ var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly);
// Create Initialize method
var initializeMethod = new MethodDefinition("Initialize",
MethodAttributes.Assembly | MethodAttributes.HideBySig | MethodAttributes.Static,
- assembly.MainModule.TypeSystem.Void);
+ module.TypeSystem.Void);
serializerFactoryType.Methods.Add(initializeMethod);
- // Obtain the static constructor of and the return instruction
- var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction);
+ var il = new ILBuilder(initializeMethod.Body, module);
- // Get the IL processor of the module constructor
- var il = moduleConstructor.Body.GetILProcessor();
-
- // Create the call to Initialize method
- var initializeMethodReference = assembly.MainModule.ImportReference(initializeMethod);
- var callInitializeInstruction = il.Create(OpCodes.Call, initializeMethodReference);
-
- var initializeMethodIL = initializeMethod.Body.GetILProcessor();
+ // Resolve and import reflection helpers
+ var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName);
+ var getTypeFromHandleRef = il.Import(typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle)));
+ var getTypeInfoRef = il.Import(reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo)));
+ var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName);
+ // Note: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime
+ var typeInfoProperties = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties);
+ var getAssemblyRef = il.Import(typeInfoProperties.First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod);
+ var getModuleRef = il.Import(typeInfoProperties.First(x => x.Name == nameof(TypeInfo.Module)).GetMethod);
+ var getTypeHandleMethodRef = module.ImportReference(typeType.Properties.First(x => x.Name == nameof(Type.TypeHandle)).GetMethod);
- // Generating: var assemblySerializers = new AssemblySerializers(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly);
+ // Emit: var assemblySerializers = new AssemblySerializers(typeof(Factory).GetTypeInfo().Assembly);
var assemblySerializersType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializers");
+ il.EmitTypeofAssembly(serializerFactoryType, getTypeFromHandleRef, getTypeInfoRef, getAssemblyRef)
+ .Emit(OpCodes.Newobj, il.Import(assemblySerializersType.Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1)));
+
+ EmitDataContractAliases(il, serializerContext, module, assemblySerializersType, getTypeFromHandleRef);
+ EmitModuleRegistrations(il, serializerContext, module, assemblySerializersType, getTypeFromHandleRef, getTypeInfoRef, getModuleRef);
+ EmitProfileEntries(il, serializerContext, module, strideCoreModule, mscorlibAssembly, assemblySerializersType, getTypeFromHandleRef, getTypeHandleMethodRef);
+
+ // Emit: DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers);
+ var dataSerializerFactoryRegisterRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerFactory").Methods.Single(x => x.Name == "RegisterSerializationAssembly" && x.Parameters[0].ParameterType.FullName == assemblySerializersType.FullName));
+ il.Emit(OpCodes.Call, dataSerializerFactoryRegisterRef);
+
+ // Emit: AssemblyRegistry.Register(typeof(Factory).GetTypeInfo().Assembly, new[] { "Engine" });
+ il.EmitTypeofAssembly(serializerFactoryType, getTypeFromHandleRef, getTypeInfoRef, getAssemblyRef)
+ .Emit(OpCodes.Ldc_I4_1)
+ .Emit(OpCodes.Newarr, module.TypeSystem.String)
+ .Emit(OpCodes.Dup)
+ .Emit(OpCodes.Ldc_I4_0)
+ .Emit(OpCodes.Ldstr, Reflection.AssemblyCommonCategories.Engine)
+ .Emit(OpCodes.Stelem_Ref);
+
+ var assemblyRegistryRegisterMethodRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry").Methods.Single(x => x.Name == "Register" && x.Parameters[1].ParameterType.IsArray));
+ il.Emit(OpCodes.Call, assemblyRegistryRegisterMethodRef)
+ .Emit(OpCodes.Ret);
+
+ // Wire up module constructor to call Initialize
+ var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction);
+ var moduleCtorIL = moduleConstructor.Body.GetILProcessor();
+ var callInitializeInstruction = moduleCtorIL.Create(OpCodes.Call, module.ImportReference(initializeMethod));
+ moduleCtorIL.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
+ }
+
+ ///
+ /// Emits: assemblySerializers.DataContractAliases.Add(new (alias, typeof(T), isRemap)); for each alias.
+ ///
+ private static void EmitDataContractAliases(
+ ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module,
+ TypeDefinition assemblySerializersType, MethodReference getTypeFromHandleRef)
+ {
+ var getAliasesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "DataContractAliases").GetMethod);
+ var addMethod = getAliasesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add");
+ var aliasTypeRef = ((GenericInstanceType)getAliasesRef.ReturnType).GenericArguments[0];
+ var aliasCtorRef = module.ImportReference(aliasTypeRef.Resolve().GetConstructors().Single());
+ var addRef = module.ImportReference(addMethod).MakeGeneric(aliasTypeRef);
- var assemblySerializersGetDataContractAliasesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "DataContractAliases").GetMethod);
- var assemblySerializersGetDataContractAliasesAdd = assemblySerializersGetDataContractAliasesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add");
- var dataContractAliasTypeRef = ((GenericInstanceType)assemblySerializersGetDataContractAliasesRef.ReturnType).GenericArguments[0];
- var dataContractAliasTypeCtorRef = assembly.MainModule.ImportReference(dataContractAliasTypeRef.Resolve().GetConstructors().Single());
- var assemblySerializersGetDataContractAliasesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetDataContractAliasesAdd).MakeGeneric(dataContractAliasTypeRef);
- initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType);
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod));
- initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly));
- initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(assemblySerializersType.Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1)));
-
- foreach (var alias in registry.Context.DataContractAliases)
+ foreach (var alias in serializerContext.DataContractAliases)
{
- initializeMethodIL.Emit(OpCodes.Dup);
-
- // Generating: assemblySerializers.DataContractAliases.Add(new AssemblySerializers.DataContractAlias(@"<#= alias.Item1 #>", typeof(<#= alias.Item2.ConvertCSharp(true) #>), <#=alias.Item3 ? "true" : "false"#>));
- initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesRef);
- initializeMethodIL.Emit(OpCodes.Ldstr, alias.Item1);
- initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(alias.Item2));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
- initializeMethodIL.Emit(alias.Item3 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- initializeMethodIL.Emit(OpCodes.Newobj, dataContractAliasTypeCtorRef);
- initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesAddRef);
+ il.Emit(OpCodes.Dup)
+ .Emit(OpCodes.Call, getAliasesRef)
+ .Emit(OpCodes.Ldstr, alias.Item1)
+ .EmitTypeof(il.Import(alias.Item2), getTypeFromHandleRef)
+ .Emit(alias.Item3 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)
+ .Emit(OpCodes.Newobj, aliasCtorRef)
+ .Emit(OpCodes.Call, addRef);
}
+ }
- var assemblySerializersGetModulesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Modules").GetMethod);
- var assemblySerializersGetModulesAdd = assemblySerializersGetModulesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add");
- var moduleRef = ((GenericInstanceType)assemblySerializersGetModulesRef.ReturnType).GenericArguments[0];
- var assemblySerializersGetModulesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetModulesAdd).MakeGeneric(moduleRef);
+ ///
+ /// Emits: assemblySerializers.Modules.Add(typeof(RefFactory).GetTypeInfo().Module); for each referenced assembly.
+ ///
+ private static void EmitModuleRegistrations(
+ ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module,
+ TypeDefinition assemblySerializersType,
+ MethodReference getTypeFromHandleRef, MethodReference getTypeInfoRef, MethodReference getModuleRef)
+ {
+ var getModulesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Modules").GetMethod);
+ var addMethod = getModulesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add");
+ var moduleTypeRef = ((GenericInstanceType)getModulesRef.ReturnType).GenericArguments[0];
+ var addRef = module.ImportReference(addMethod).MakeGeneric(moduleTypeRef);
- foreach (var referencedAssemblySerializerFactoryType in registry.ReferencedAssemblySerializerFactoryTypes)
+ foreach (var referencedAssemblySerializerFactoryType in serializerContext.ReferencedAssemblySerializerFactoryTypes)
{
- initializeMethodIL.Emit(OpCodes.Dup);
-
- // Generating: assemblySerializers.Modules.Add(typeof(<#=referencedAssemblySerializerFactoryType.ConvertCSharp()#>).GetTypeInfo().Module);
- initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesRef);
- initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(referencedAssemblySerializerFactoryType));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod));
- initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoModule));
- initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesAddRef);
+ il.Emit(OpCodes.Dup)
+ .Emit(OpCodes.Call, getModulesRef)
+ .EmitTypeofModule(il.Import(referencedAssemblySerializerFactoryType), getTypeFromHandleRef, getTypeInfoRef, getModuleRef)
+ .Emit(OpCodes.Call, addRef);
}
+ }
- var objectIdCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Storage.ObjectId").GetConstructors().Single(x => x.Parameters.Count == 4));
- var serializerEntryTypeCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerEntry").GetConstructors().Single());
- var assemblySerializersPerProfileType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializersPerProfile");
- var assemblySerializersPerProfileTypeAddRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.BaseType.Resolve().Methods.First(x => x.Name == "Add")).MakeGeneric(serializerEntryTypeCtorRef.DeclaringType);
- var assemblySerializersPerProfileTypeCtorRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.GetEmptyConstructor());
- var assemblySerializersGetProfilesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Profiles").GetMethod);
- var assemblySerializersGetProfilesSetItemRef = assembly.MainModule.ImportReference(assemblySerializersGetProfilesRef.ReturnType.Resolve().Methods.First(x => x.Name == "set_Item"))
- .MakeGeneric([.. ((GenericInstanceType)assemblySerializersGetProfilesRef.ReturnType).GenericArguments]);
+ ///
+ /// Emits per-profile serializer registration:
+ ///
+ /// var profile = new AssemblySerializersPerProfile();
+ /// profile.Add(new AssemblySerializerEntry(typeId, typeof(T), typeof(TSerializer)));
+ /// RuntimeHelpers.RunClassConstructor(typeof(TSerializer).TypeHandle); // if has static ctor
+ /// assemblySerializers.Profiles["Default"] = profile;
+ ///
+ ///
+ private static void EmitProfileEntries(
+ ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module,
+ ModuleDefinition strideCoreModule, AssemblyDefinition mscorlibAssembly,
+ TypeDefinition assemblySerializersType,
+ MethodReference getTypeFromHandleRef, MethodReference getTypeHandleMethodRef)
+ {
+ var objectIdCtorRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Storage.ObjectId").GetConstructors().Single(x => x.Parameters.Count == 4));
+ var serializerEntryTypeCtorRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerEntry").GetConstructors().Single());
+ var perProfileType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializersPerProfile");
+ var perProfileAddRef = module.ImportReference(perProfileType.BaseType.Resolve().Methods.First(x => x.Name == "Add")).MakeGeneric(serializerEntryTypeCtorRef.DeclaringType);
+ var perProfileCtorRef = module.ImportReference(perProfileType.GetEmptyConstructor());
+ var getProfilesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Profiles").GetMethod);
+ var setItemRef = module.ImportReference(getProfilesRef.ReturnType.Resolve().Methods.First(x => x.Name == "set_Item"))
+ .MakeGeneric([.. ((GenericInstanceType)getProfilesRef.ReturnType).GenericArguments]);
var runtimeHelpersType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(RuntimeHelpers).FullName);
- var runClassConstructorMethod = assembly.MainModule.ImportReference(runtimeHelpersType.Methods.Single(x => x.IsPublic && x.Name == "RunClassConstructor" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.FullName == typeof(RuntimeTypeHandle).FullName));
+ var runClassConstructorMethod = module.ImportReference(runtimeHelpersType.Methods.Single(x => x.IsPublic && x.Name == "RunClassConstructor" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.FullName == typeof(RuntimeTypeHandle).FullName));
- foreach (var profile in registry.Context.SerializableTypesProfiles)
+ foreach (var profile in serializerContext.SerializableTypesProfiles)
{
- initializeMethodIL.Emit(OpCodes.Dup);
-
- // Generating: var assemblySerializersProfile = new AssemblySerializersPerProfile();
- // Generating: assemblySerializers.Profiles["<#=profile.Key#>"] = assemblySerializersProfile;
- initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesRef);
- initializeMethodIL.Emit(OpCodes.Ldstr, profile.Key);
- initializeMethodIL.Emit(OpCodes.Newobj, assemblySerializersPerProfileTypeCtorRef);
+ il.Emit(OpCodes.Dup)
+ .Emit(OpCodes.Callvirt, getProfilesRef)
+ .Emit(OpCodes.Ldstr, profile.Key)
+ .Emit(OpCodes.Newobj, perProfileCtorRef);
foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.Local))
{
- // Generating: assemblySerializersProfile.Add(new AssemblySerializerEntry(<#=type.Key.ConvertTypeId()#>, typeof(<#= type.Key.ConvertCSharp() #>), <# if (type.Value.SerializerType != null) { #>typeof(<#= type.Value.SerializerType.ConvertCSharp() #>)<# } else { #>null<# } #>));
- initializeMethodIL.Emit(OpCodes.Dup);
+ il.Emit(OpCodes.Dup);
var typeName = type.Key.ConvertCSharp(false);
var typeId = ObjectId.FromBytes(Encoding.UTF8.GetBytes(typeName));
unsafe
{
- var typeIdHash = (int*)&typeId;
+ var typeIdHash = (int*)&typeId;
- for (int i = 0; i < ObjectId.HashSize / 4; ++i)
- initializeMethodIL.Emit(OpCodes.Ldc_I4, typeIdHash[i]);
- }
-
- initializeMethodIL.Emit(OpCodes.Newobj, objectIdCtorRef);
+ for (int i = 0; i < ObjectId.HashSize / 4; ++i)
+ il.Emit(OpCodes.Ldc_I4, typeIdHash[i]);
+ }
- initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Key));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
+ il.Emit(OpCodes.Newobj, objectIdCtorRef)
+ .EmitTypeof(il.Import(type.Key), getTypeFromHandleRef);
if (type.Value.SerializerType != null)
{
- initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Value.SerializerType));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
+ il.EmitTypeof(il.Import(type.Value.SerializerType), getTypeFromHandleRef);
}
else
{
- initializeMethodIL.Emit(OpCodes.Ldnull);
+ il.Emit(OpCodes.Ldnull);
}
- initializeMethodIL.Emit(OpCodes.Newobj, serializerEntryTypeCtorRef);
- initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersPerProfileTypeAddRef);
+ il.Emit(OpCodes.Newobj, serializerEntryTypeCtorRef)
+ .Emit(OpCodes.Callvirt, perProfileAddRef);
if (type.Value.SerializerType?.Resolve()?.Methods.Any(x => x.IsConstructor && x.IsStatic) == true)
{
- // Generating: System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(<#=type.Value.SerializerType.ConvertCSharp()#>).TypeHandle);
- initializeMethodIL.Append(Instruction.Create(OpCodes.Ldtoken, type.Value.SerializerType));
- initializeMethodIL.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)));
- initializeMethodIL.Append(Instruction.Create(OpCodes.Callvirt, getTypeHandleMethodRef));
- initializeMethodIL.Append(Instruction.Create(OpCodes.Call, runClassConstructorMethod));
+ // RuntimeHelpers.RunClassConstructor(typeof(SerializerType).TypeHandle);
+ il.EmitTypeHandle(type.Value.SerializerType, getTypeFromHandleRef, getTypeHandleMethodRef)
+ .Emit(OpCodes.Call, runClassConstructorMethod);
}
}
- initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesSetItemRef);
+ il.Emit(OpCodes.Callvirt, setItemRef);
}
-
- // Generating: DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers);
- var dataSerializerFactoryRegisterSerializationAssemblyMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerFactory").Methods.Single(x => x.Name == "RegisterSerializationAssembly" && x.Parameters[0].ParameterType.FullName == assemblySerializersType.FullName));
- initializeMethodIL.Emit(OpCodes.Call, dataSerializerFactoryRegisterSerializationAssemblyMethodRef);
-
- // Generating: AssemblyRegistry.Register(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly, AssemblyCommonCategories.Engine);
- initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType);
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod));
- initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod));
- initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly));
-
- // create new[] { AssemblyCommonCategories.Engine }
- initializeMethodIL.Emit(OpCodes.Ldc_I4_1);
- initializeMethodIL.Emit(OpCodes.Newarr, assembly.MainModule.TypeSystem.String);
- initializeMethodIL.Emit(OpCodes.Dup);
- initializeMethodIL.Emit(OpCodes.Ldc_I4_0);
- initializeMethodIL.Emit(OpCodes.Ldstr, Reflection.AssemblyCommonCategories.Engine);
- initializeMethodIL.Emit(OpCodes.Stelem_Ref);
-
- var assemblyRegistryRegisterMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry").Methods.Single(x => x.Name == "Register" && x.Parameters[1].ParameterType.IsArray));
- initializeMethodIL.Emit(OpCodes.Call, assemblyRegistryRegisterMethodRef);
-
- initializeMethodIL.Emit(OpCodes.Ret);
-
- // Add AssemblySerializerFactoryAttribute
- var assemblySerializerFactoryAttribute = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
- assembly.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.ImportReference(assemblySerializerFactoryAttribute.GetEmptyConstructor()))
- {
- Fields =
- {
- new CustomAttributeNamedArgument("Type", new CustomAttributeArgument(typeTypeRef, serializerFactoryType)),
- }
- });
- // Insert the call before the end of the method body
- il.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
-
- serializationHash = hash.ComputeHash();
}
- private static void RegisterDefaultSerializationProfile(IAssemblyResolver assemblyResolver, AssemblyDefinition assembly, ComplexSerializerRegistry registry, System.IO.TextWriter log)
- {
- var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly);
- if (mscorlibAssembly == null)
- {
- log.WriteLine("Missing mscorlib.dll from assembly {0}", assembly.FullName);
- throw new InvalidOperationException("Missing mscorlib.dll from assembly");
- }
-
- var coreSerializationAssembly = assemblyResolver.Resolve(new AssemblyNameReference("Stride.Core", null));
-
- // Register serializer factories (determine which type requires which serializer)
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(IList<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ListInterfaceSerializer`1")));
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(List<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ListSerializer`1")));
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(KeyValuePair<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.KeyValuePairSerializer`2")));
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(IDictionary<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.DictionaryInterfaceSerializer`2")));
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(Dictionary<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.DictionarySerializer`2")));
- registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(Nullable<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.NullableSerializer`1")));
- registry.SerializerFactories.Add(new CecilEnumSerializerFactory(coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.EnumSerializer`1")));
- registry.SerializerFactories.Add(new CecilArraySerializerFactory(coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1")));
-
- // Iterate over tuple size
- for (int i = 1; i <= 4; ++i)
- {
- registry.SerializerDependencies.Add(new CecilSerializerDependency(
- string.Format("System.Tuple`{0}", i),
- coreSerializationAssembly.MainModule.GetTypeResolved(string.Format("Stride.Core.Serialization.Serializers.TupleSerializer`{0}", i))));
-
- registry.SerializerDependencies.Add(new CecilSerializerDependency(string.Format("Stride.Core.Serialization.Serializers.TupleSerializer`{0}", i)));
- }
-
- // Register serializer dependencies (determine which serializer serializes which sub-type)
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ArraySerializer`1"));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.KeyValuePairSerializer`2"));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ListSerializer`1"));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ListInterfaceSerializer`1"));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.NullableSerializer`1"));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.DictionarySerializer`2",
- mscorlibAssembly.MainModule.GetTypeResolved(typeof(KeyValuePair<,>).FullName)));
- registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.DictionaryInterfaceSerializer`2",
- mscorlibAssembly.MainModule.GetTypeResolved(typeof(KeyValuePair<,>).FullName)));
- }
}
public static class HashExtensions
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs
similarity index 86%
rename from sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs
rename to sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs
index 99d0969aef..22b0fe4e21 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs
@@ -6,14 +6,13 @@
namespace Stride.Core.AssemblyProcessor.Serializers;
-class CecilComplexClassSerializerProcessor : ICecilSerializerProcessor
+class CecilDataContractSerializerProcessor : ICecilSerializerProcessor
{
public void ProcessSerializers(CecilSerializerContext context)
{
foreach (var type in context.Assembly.MainModule.GetAllTypes().ToArray())
{
- // Force generation of serializers (complex types, etc...)
- // Check complex type definitions
+ // Discover [DataContract] types and resolve their serializers
ProcessType(context, type);
}
}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs
index 920129fa52..227d1f6000 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs
@@ -18,10 +18,39 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly
SerializableTypesProfiles = [];
SerializableTypes = new ProfileInfo();
SerializableTypesProfiles.Add("Default", SerializableTypes);
- ComplexTypes = [];
+ PendingSerializers = [];
+ IgnoredMembers = [];
this.log = log;
StrideCoreModule = assembly.GetStrideCoreModule();
+
+ // Discover referenced assemblies' serializer factories
+ foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences)
+ {
+ try
+ {
+ var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName);
+ var factoryType = GetSerializerFactoryType(referencedAssembly);
+ if (factoryType != null)
+ ReferencedAssemblySerializerFactoryTypes.Add(factoryType);
+ }
+ catch (AssemblyResolutionException)
+ {
+ }
+ }
+
+ // Run the processor pipeline
+ ICecilSerializerProcessor[] processors =
+ [
+ new ReferencedAssemblySerializerProcessor(),
+ new CecilDataContractSerializerProcessor(),
+ new PropertyKeySerializerProcessor(),
+ new UpdateEngineProcessor(),
+ new ProfileSerializerProcessor(),
+ new DataContractAliasProcessor(),
+ ];
+ foreach (var processor in processors)
+ processor.ProcessSerializers(this);
}
public PlatformType Platform { get; }
@@ -29,21 +58,17 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly
///
/// Gets the assembly being processed.
///
- ///
- /// The assembly being processed.
- ///
public AssemblyDefinition Assembly { get; }
public ModuleDefinition StrideCoreModule { get; }
+ public List ReferencedAssemblySerializerFactoryTypes { get; } = [];
+
public List> DataContractAliases { get; } = [];
///
/// Gets the list of serializable type grouped by profile.
///
- ///
- /// The serializable types profiles.
- ///
public Dictionary SerializableTypesProfiles { get; }
///
@@ -52,158 +77,154 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly
public ProfileInfo SerializableTypes { get; }
///
- /// Gets the list of complex serializers to generate.
+ /// Gets the list of serializers pending code generation.
+ /// Populated during collection, consumed by the code generation phase.
///
- ///
- /// The list of complex serializers to generate.
- ///
- public Dictionary ComplexTypes { get; }
+ public List PendingSerializers { get; }
+
+ ///
+ /// Members that should be excluded from serialization (e.g. members without valid serializers).
+ ///
+ public HashSet IgnoredMembers { get; }
///
/// Ensure the following type can be serialized. If not, try to register appropriate serializer.
/// This method can be recursive.
///
- /// The type.
- public SerializableTypeInfo GenerateSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false)
+ public SerializableTypeInfo ResolveSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false)
{
var serializableTypes = GetSerializableTypes(profile);
// Already handled?
if (serializableTypes.TryGetSerializableTypeInfo(type, generic, out var serializableTypeInfo))
return serializableTypeInfo;
-
- // Try to get one without generic
if (generic && serializableTypes.TryGetSerializableTypeInfo(type, false, out serializableTypeInfo))
return serializableTypeInfo;
- // TDOO: Array, List, Generic types, etc... (equivalent of previous serializer factories)
+ // Arrays
if (type is ArrayType arrayType)
- {
- // Only proceed if element type is serializable (and in Default profile, otherwise ElementType is enough)
- if (GenerateSerializer(arrayType.ElementType, force, profile) != null)
- {
- if (profile == "Default")
- {
- var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1");
- var serializerType = new GenericInstanceType(arraySerializerType);
- serializerType.GenericArguments.Add(arrayType.ElementType);
- AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true), profile);
- return serializableTypeInfo;
- }
- else
- {
- // Fallback to default
- return GenerateSerializer(type, force, "Default");
- }
- }
+ return ResolveArraySerializer(arrayType, force, profile);
- return null;
- }
-
- // Try to match with existing generic serializer (for List, Dictionary, etc...)
+ // Generic instances (List, Dictionary, etc.)
if (type is GenericInstanceType genericInstanceType)
{
- var elementType = genericInstanceType.ElementType;
- SerializableTypeInfo elementSerializableTypeInfo;
- if ((elementSerializableTypeInfo = GenerateSerializer(elementType, false, profile, true)) != null)
- {
- switch (elementSerializableTypeInfo.Mode)
- {
- case DataSerializerGenericMode.Type:
- {
- var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
- serializerType.GenericArguments.Add(type);
-
- AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
- break;
- }
- case DataSerializerGenericMode.TypeAndGenericArguments:
- {
- var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
- serializerType.GenericArguments.Add(type);
- foreach (var genericArgument in genericInstanceType.GenericArguments)
- {
- // Generate serializer for each generic argument
- //GenerateSerializer(genericArgument);
-
- serializerType.GenericArguments.Add(genericArgument);
- }
-
- AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
- break;
- }
- case DataSerializerGenericMode.GenericArguments:
- {
- var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
- foreach (var genericArgument in genericInstanceType.GenericArguments)
- {
- // Generate serializer for each generic argument
- //GenerateSerializer(genericArgument);
-
- serializerType.GenericArguments.Add(genericArgument);
- }
-
- AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
- break;
- }
- default:
- throw new NotImplementedException();
- }
-
- if (elementSerializableTypeInfo.ComplexSerializer)
- {
- ProcessComplexSerializerMembers(type, serializableTypeInfo);
- }
+ serializableTypeInfo = ResolveGenericSerializer(genericInstanceType, profile);
+ if (serializableTypeInfo != null)
return serializableTypeInfo;
- }
}
- // Check complex type definitions
- if (profile == "Default" && (serializableTypeInfo = FindSerializerInfo(type, generic)) != null)
+ // Check type definitions for serializer info (only in Default profile)
+ if (profile == "Default")
{
- return serializableTypeInfo;
+ serializableTypeInfo = FindSerializerInfo(type, generic);
+ if (serializableTypeInfo != null)
+ return serializableTypeInfo;
}
- // Fallback to default
+ // Non-Default profiles fall back to Default
if (profile != "Default")
- return GenerateSerializer(type, force, "Default", generic);
+ return ResolveSerializer(type, force, "Default", generic);
- // Part after that is only if a serializer is absolutely necessary. This is skipped when scanning normal assemblies type that might have nothing to do with serialization.
+ // Past this point, only proceed if a serializer is absolutely necessary.
+ // This is skipped when scanning normal assembly types that might have nothing to do with serialization.
if (!force)
return null;
- // Non instantiable type? (object, interfaces, abstract classes)
+ // Non-instantiable types (object, interfaces, abstract classes)
// Serializer can be null since they will be inherited anyway (handled through MemberSerializer)
var resolvedType = type.Resolve();
if (resolvedType.IsAbstract || resolvedType.IsInterface || resolvedType.FullName == typeof(object).FullName)
{
- AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(null, true), profile);
+ serializableTypeInfo = new SerializableTypeInfo(null, true);
+ AddSerializableType(type, serializableTypeInfo, profile);
return serializableTypeInfo;
}
return null;
}
- private void ProcessComplexSerializerMembers(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default")
+ private SerializableTypeInfo ResolveArraySerializer(ArrayType arrayType, bool force, string profile)
{
- // Process base type (for complex serializers)
- // Check if we have any serializable closed base type and collect it to pass it over to GenerateSerializerCode later,
- // who'll ensure the generated serializer for this type calls into its base types' serializer
+ // Only proceed if element type is serializable
+ if (ResolveSerializer(arrayType.ElementType, force, profile) == null)
+ return null;
+
+ // Non-Default profiles fall back to Default for array serializer registration
+ if (profile != "Default")
+ return ResolveSerializer(arrayType, force, "Default");
+
+ var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1");
+ var serializerType = new GenericInstanceType(arraySerializerType);
+ serializerType.GenericArguments.Add(arrayType.ElementType);
+
+ var info = new SerializableTypeInfo(serializerType, true);
+ AddSerializableType(arrayType, info, profile);
+ return info;
+ }
+
+ private SerializableTypeInfo ResolveGenericSerializer(GenericInstanceType type, string profile)
+ {
+ // Try to match with existing generic serializer (for List, Dictionary, etc.)
+ var elementInfo = ResolveSerializer(type.ElementType, false, profile, true);
+ if (elementInfo == null)
+ return null;
+
+ var serializerType = InstantiateSerializerType(elementInfo.SerializerType, elementInfo.Mode, type, type.GenericArguments);
+
+ var info = new SerializableTypeInfo(serializerType, true) { IsGeneratedSerializer = elementInfo.IsGeneratedSerializer };
+ AddSerializableType(type, info, profile);
+
+ if (elementInfo.IsGeneratedSerializer)
+ CollectSerializerDependencies(type, info);
+
+ return info;
+ }
+
+ ///
+ /// Constructs a concrete serializer type from an open generic serializer definition
+ /// by adding type arguments according to the .
+ ///
+ private static GenericInstanceType InstantiateSerializerType(
+ TypeReference openSerializerType,
+ DataSerializerGenericMode mode,
+ TypeReference dataType,
+ IEnumerable genericArguments)
+ {
+ var serializerType = new GenericInstanceType(openSerializerType);
+
+ // Add the data type itself as first arg for Type/TypeAndGenericArguments modes
+ if (mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
+ serializerType.GenericArguments.Add(dataType);
+
+ // Add the data type's generic arguments for GenericArguments/TypeAndGenericArguments modes
+ if (mode is DataSerializerGenericMode.GenericArguments or DataSerializerGenericMode.TypeAndGenericArguments)
+ {
+ foreach (var arg in genericArguments)
+ serializerType.GenericArguments.Add(arg);
+ }
+
+ return serializerType;
+ }
+
+ private void CollectSerializerDependencies(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default", SerializerDescriptor? descriptor = null)
+ {
+ // Find the nearest serializable base type so the generated serializer can chain to it
for (var baseType = type; (baseType = ResolveGenericsVisitor.Process(baseType, baseType.Resolve().BaseType)) != null;)
{
if (baseType.ContainsGenericParameter())
continue; // ResolveGenericsVisitor failed, the type it returned is not closed, we can't serialize it
- var parentSerializableTypeInfo = GenerateSerializer(baseType, false, profile);
- if (parentSerializableTypeInfo?.SerializerType != null)
+ var parentSerializableTypeInfo = ResolveSerializer(baseType, false, profile);
+ if (parentSerializableTypeInfo?.SerializerType is not null)
{
- serializableTypeInfo.ComplexSerializerProcessParentType = baseType;
+ if (descriptor is not null)
+ descriptor.SerializedParentType = baseType;
break;
}
}
- // Process members
- foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true))
+ // Resolve serializers for all members, ignoring those without valid serializers
+ foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers))
{
// Check that all closed types have a proper serializer
if (serializableItem.Attributes.Any(x => x.AttributeType.FullName == "Stride.Core.DataMemberCustomSerializerAttribute")
@@ -217,9 +238,9 @@ private void ProcessComplexSerializerMembers(TypeReference type, SerializableTyp
try
{
- if (GenerateSerializer(serializableItem.Type, profile: profile) == null)
+ if (ResolveSerializer(serializableItem.Type, profile: profile) == null)
{
- ComplexSerializerRegistry.IgnoreMember(serializableItem.MemberInfo);
+ IgnoredMembers.Add(serializableItem.MemberInfo);
if (!isInterface)
{
log.Write(
@@ -232,15 +253,15 @@ private void ProcessComplexSerializerMembers(TypeReference type, SerializableTyp
throw new InvalidOperationException($"Could not process serialization for member {serializableItem.MemberInfo}", e);
}
}
+
+ // Cache final serializable items (after ignored members have been updated)
+ if (descriptor is not null)
+ descriptor.SerializableItems = SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers).ToArray();
}
///
- /// Finds the serializer information.
+ /// Finds the serializer information by inspecting the type's attributes and inheritance chain.
///
- /// The type.
- /// If set to true, when using , it will returns the generic version instead of actual type one.
- ///
- /// Not sure how to process this inherited serializer
internal SerializableTypeInfo FindSerializerInfo(TypeReference type, bool generic)
{
if (type == null || type.FullName == typeof(object).FullName || type.FullName == typeof(ValueType).FullName || type.IsGenericParameter)
@@ -248,199 +269,138 @@ internal SerializableTypeInfo FindSerializerInfo(TypeReference type, bool generi
var resolvedType = type.Resolve();
- // Nested type
- if (resolvedType.IsNested)
- {
- // Check public/private flags
- if (!resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly)
- return null;
- }
+ // Nested type — must be publicly accessible
+ if (resolvedType.IsNested && !resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly)
+ return null;
+ // Enums
if (resolvedType.IsEnum)
{
- // Enum
- // Let's generate a EnumSerializer
var enumSerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.EnumSerializer`1");
var serializerType = new GenericInstanceType(enumSerializerType);
serializerType.GenericArguments.Add(type);
- var serializableTypeInfo = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None);
- AddSerializableType(type, serializableTypeInfo);
- return serializableTypeInfo;
+ var info = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None);
+ AddSerializableType(type, info);
+ return info;
}
- // 1. Check if there is a Serializable attribute
- // Note: Not anymore since we don't want all system types to have unknown complex serializers.
- //if (((resolvedType.Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable) || resolvedType.CustomAttributes.Any(x => x.AttributeType.FullName == typeof(SerializableAttribute).FullName))
- //{
- // serializerInfo.Serializable = true;
- // serializerInfo.ComplexSerializer = true;
- // serializerInfo.ComplexSerializerName = SerializerTypeName(resolvedType);
- // return serializerInfo;
- //}
-
- // 2.1. Check if there is DataSerializerAttribute on this type (if yes, it is serializable, but not a "complex type")
- var dataSerializerAttribute =
- resolvedType.CustomAttributes.FirstOrDefault(
- x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute");
+ // [DataSerializer] attribute — explicit serializer assignment
+ var dataSerializerAttribute = resolvedType.CustomAttributes
+ .FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute");
if (dataSerializerAttribute != null)
- {
- var modeField = dataSerializerAttribute.Fields.FirstOrDefault(x => x.Name == "Mode");
- var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None;
-
- var dataSerializerType = ((TypeReference)dataSerializerAttribute.ConstructorArguments[0].Value);
+ return ProcessDataSerializerAttribute(type, dataSerializerAttribute, generic);
- // Reading from custom arguments doesn't have its ValueType properly set
- dataSerializerType = dataSerializerType.FixupValueType();
+ // [DataContract] attribute — collect generated serializer
+ var dataContractAttribute = resolvedType.CustomAttributes
+ .FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute");
+ if (dataContractAttribute != null)
+ {
+ var inherited = dataContractAttribute.Properties
+ .Where(x => x.Name == "Inherited")
+ .Select(x => (bool)x.Argument.Value)
+ .FirstOrDefault();
- if (mode == DataSerializerGenericMode.Type || (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is GenericInstanceType))
- {
- var genericSerializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true };
- AddSerializableType(type, genericSerializableTypeInfo);
+ var (info, descriptor) = CollectSerializer(type);
+ info.Inherited = inherited;
+ CollectSerializerDependencies(type, info, descriptor: descriptor);
+ return info;
+ }
- var actualSerializerType = new GenericInstanceType(dataSerializerType);
+ // Check if parent type has Inherited attribute
+ return FindInheritedSerializerInfo(type, generic);
+ }
- // Add Type as generic arguments
- actualSerializerType.GenericArguments.Add(type);
+ private SerializableTypeInfo ProcessDataSerializerAttribute(TypeReference type, CustomAttribute attribute, bool generic)
+ {
+ var modeField = attribute.Fields.FirstOrDefault(x => x.Name == "Mode");
+ var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None;
+ var dataSerializerType = ((TypeReference)attribute.ConstructorArguments[0].Value).FixupValueType();
- // If necessary, add generic arguments too
- if (mode == DataSerializerGenericMode.TypeAndGenericArguments)
- {
- foreach (var genericArgument in ((GenericInstanceType)type).GenericArguments)
- {
- actualSerializerType.GenericArguments.Add(genericArgument);
- }
- }
+ if (mode is not (DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
+ || (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is not GenericInstanceType))
+ {
+ // Simple non-generic or GenericArguments mode
+ var info = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false };
+ AddSerializableType(type, info);
+ return info;
+ }
- // Special case for GenericMode == DataSerializerGenericMode.Type: we store actual serializer instantiation in SerializerType (alongside the generic version in GenericSerializerType).
- var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true);
- AddSerializableType(type, serializableTypeInfo);
+ // Type or TypeAndGenericArguments mode — register both generic and concrete versions
+ var genericInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true };
+ AddSerializableType(type, genericInfo);
- if (!generic)
- return serializableTypeInfo;
+ var genericArguments = type is GenericInstanceType git ? git.GenericArguments : [];
+ var actualSerializerType = InstantiateSerializerType(dataSerializerType, mode, type, genericArguments);
- return genericSerializableTypeInfo;
- }
- else
- {
- var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false };
- AddSerializableType(type, serializableTypeInfo);
- return serializableTypeInfo;
- }
- }
+ var concreteInfo = new SerializableTypeInfo(actualSerializerType, true);
+ AddSerializableType(type, concreteInfo);
- // 2.2. Check if SerializableExtendedAttribute is set on this class, or any of its base class with ApplyHierarchy
- var serializableExtendedAttribute =
- resolvedType.CustomAttributes.FirstOrDefault(
- x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute");
- if (dataSerializerAttribute == null && serializableExtendedAttribute != null)
- {
- // CHeck if ApplyHierarchy is active, otherwise it needs to be the exact type.
- var inherited = serializableExtendedAttribute.Properties.Where(x => x.Name == "Inherited")
- .Select(x => (bool)x.Argument.Value)
- .FirstOrDefault();
+ return generic ? genericInfo : concreteInfo;
+ }
- var serializableTypeInfo = CreateComplexSerializer(type);
- serializableTypeInfo.Inherited = inherited;
+ private SerializableTypeInfo FindInheritedSerializerInfo(TypeReference type, bool generic)
+ {
+ var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType);
+ if (parentType == null)
+ return null;
- // Process members
- ProcessComplexSerializerMembers(type, serializableTypeInfo);
+ // Generate serializer for parent type
+ var parentInfo = ResolveSerializer(parentType.Resolve(), false, generic: true);
+ if (parentInfo?.Inherited != true)
+ return null;
- return serializableTypeInfo;
+ // Parent has a generated serializer — collect one for this type too
+ if (parentInfo.IsGeneratedSerializer)
+ {
+ var (info, descriptor) = CollectSerializer(type);
+ info.Inherited = true;
+ CollectSerializerDependencies(type, info, descriptor: descriptor);
+ return info;
}
- // Check if parent type contains Inherited attribute
- var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType);
- if (parentType != null)
+ // Parent has a Type/TypeAndGenericArguments mode serializer — inherit it
+ if (parentInfo.Mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
{
- // Generate serializer for parent type
- var parentSerializableInfoType = GenerateSerializer(parentType.Resolve(), false, generic: true);
+ // Register generic version
+ var genericInfo = new SerializableTypeInfo(parentInfo.SerializerType, true, parentInfo.Mode);
+ AddSerializableType(type, genericInfo);
- // If Inherited flag is on, we also generate a serializer for this type
- if (parentSerializableInfoType?.Inherited == true)
+ if (!type.HasGenericParameters)
{
- if (parentSerializableInfoType.ComplexSerializer)
- {
- var serializableTypeInfo = CreateComplexSerializer(type);
- serializableTypeInfo.Inherited = true;
-
- // Process members
- ProcessComplexSerializerMembers(type, serializableTypeInfo);
-
- return serializableTypeInfo;
- }
- else if (parentSerializableInfoType.Mode == DataSerializerGenericMode.Type || parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments)
- {
- // Register generic version
- var genericSerializableTypeInfo = new SerializableTypeInfo(parentSerializableInfoType.SerializerType, true, parentSerializableInfoType.Mode);
- AddSerializableType(type, genericSerializableTypeInfo);
+ var genericArguments = parentType is GenericInstanceType git ? git.GenericArguments : [];
+ var actualSerializerType = InstantiateSerializerType(parentInfo.SerializerType, parentInfo.Mode, type, genericArguments);
- if (!type.HasGenericParameters)
- {
- var actualSerializerType = new GenericInstanceType(parentSerializableInfoType.SerializerType);
-
- // Add Type as generic arguments
- actualSerializerType.GenericArguments.Add(type);
-
- // If necessary, add generic arguments too
- if (parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments)
- {
- foreach (var genericArgument in ((GenericInstanceType)parentType).GenericArguments)
- {
- actualSerializerType.GenericArguments.Add(genericArgument);
- }
- }
-
- // Register actual type
- var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true);
- AddSerializableType(type, serializableTypeInfo);
-
- if (!generic)
- return serializableTypeInfo;
- }
+ var concreteInfo = new SerializableTypeInfo(actualSerializerType, true);
+ AddSerializableType(type, concreteInfo);
- return genericSerializableTypeInfo;
- }
- else
- {
- throw new InvalidOperationException("Not sure how to process this inherited serializer");
- }
+ if (!generic)
+ return concreteInfo;
}
+
+ return genericInfo;
}
- return null;
+ throw new InvalidOperationException("Not sure how to process this inherited serializer");
}
- private SerializableTypeInfo CreateComplexSerializer(TypeReference type)
+ private (SerializableTypeInfo Info, SerializerDescriptor? Descriptor) CollectSerializer(TypeReference type)
{
var isLocal = type.Resolve().Module.Assembly == Assembly;
- // Create a fake TypeReference (even though it doesn't really exist yet, but at least ConvertCSharp to get its name will work).
- TypeReference dataSerializerType;
- var className = ComplexSerializerRegistry.SerializerTypeName(type, false, true);
+ // Create a forward TypeReference for the serializer (the actual TypeDefinition is created later during code generation).
+ var className = SerializationHelpers.SerializerTypeName(type, false, true);
if (type.HasGenericParameters)
className += "`" + type.GenericParameters.Count;
- if (isLocal && type is TypeDefinition)
- {
- dataSerializerType = new TypeDefinition("Stride.Core.DataSerializers", className,
- TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Sealed |
- TypeAttributes.BeforeFieldInit |
- (type.HasGenericParameters ? TypeAttributes.Public : TypeAttributes.NotPublic));
- // TODO: Only if not using Roslyn
- Assembly.MainModule.Types.Add((TypeDefinition)dataSerializerType);
- }
- else
- {
- dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, type.Scope);
- }
+ var dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, isLocal ? Assembly.MainModule : type.Scope);
var mode = DataSerializerGenericMode.None;
if (type.HasGenericParameters)
{
mode = DataSerializerGenericMode.GenericArguments;
- // Clone generic parameters
+ // Clone generic parameters onto the forward reference
foreach (var genericParameter in type.GenericParameters)
{
var newGenericParameter = new GenericParameter(genericParameter.Name, dataSerializerType)
@@ -448,7 +408,6 @@ private SerializableTypeInfo CreateComplexSerializer(TypeReference type)
Attributes = genericParameter.Attributes
};
- // Clone type constraints (others will be in Attributes)
foreach (var constraint in genericParameter.Constraints)
newGenericParameter.Constraints.Add(constraint);
@@ -456,31 +415,32 @@ private SerializableTypeInfo CreateComplexSerializer(TypeReference type)
}
}
- if (dataSerializerType is TypeDefinition dataSerializerTypeDefinition)
- {
- // Setup base class
- var resolvedType = type.Resolve();
- var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null;
- var classDataSerializerType = Assembly.GetStrideCoreModule().GetType(useClassDataSerializer ? "Stride.Core.Serialization.ClassDataSerializer`1" : "Stride.Core.Serialization.DataSerializer`1");
- var parentType = Assembly.MainModule.ImportReference(classDataSerializerType).MakeGenericType(type.MakeGenericType(dataSerializerType.GenericParameters.ToArray()));
- //parentType = ResolveGenericsVisitor.Process(serializerType, type.BaseType);
- dataSerializerTypeDefinition.BaseType = parentType;
- }
-
var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode)
{
- Local = type.Resolve().Module.Assembly == Assembly
+ Local = isLocal
};
AddSerializableType(type, serializableTypeInfo);
+ SerializerDescriptor? descriptor = null;
if (isLocal && type is TypeDefinition definition)
{
- ComplexTypes.Add(definition, serializableTypeInfo);
+ var resolvedType = type.Resolve();
+ var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null;
+
+ descriptor = new SerializerDescriptor
+ {
+ DataType = definition,
+ SerializerClassName = className,
+ IsPublic = type.HasGenericParameters,
+ UseClassDataSerializer = useClassDataSerializer,
+ SerializableTypeInfo = serializableTypeInfo,
+ };
+ PendingSerializers.Add(descriptor);
}
- serializableTypeInfo.ComplexSerializer = true;
+ serializableTypeInfo.IsGeneratedSerializer = true;
- return serializableTypeInfo;
+ return (serializableTypeInfo, descriptor);
}
public void AddSerializableType(TypeReference dataType, SerializableTypeInfo serializableTypeInfo, string profile = "Default")
@@ -498,8 +458,7 @@ public void AddSerializableType(TypeReference dataType, SerializableTypeInfo ser
if (profileInfo.TryGetSerializableTypeInfo(dataType, serializableTypeInfo.Mode != DataSerializerGenericMode.None, out var currentValue))
{
// TODO: Doesn't work in some generic case
- if (//currentValue.SerializerType.ConvertCSharp() != serializableTypeInfo.SerializerType.ConvertCSharp() ||
- currentValue.Mode != serializableTypeInfo.Mode)
+ if (currentValue.Mode != serializableTypeInfo.Mode)
throw new InvalidOperationException(string.Format("Incompatible serializer found for same type in different assemblies for {0}", dataType.ConvertCSharp()));
return;
}
@@ -516,37 +475,46 @@ public void AddSerializableType(TypeReference dataType, SerializableTypeInfo ser
profileInfo.AddSerializableTypeInfo(dataType, serializableTypeInfo);
- // Scan and add dependencies (stored in EnumerateGenericInstantiations() functions).
- if (serializableTypeInfo.Local && serializableTypeInfo.SerializerType != null)
+ // Scan and add dependencies (stored in EnumerateGenericInstantiations() methods)
+ ScanSerializerDependencies(dataType, serializableTypeInfo);
+ }
+
+ ///
+ /// Scans a serializer type's EnumerateGenericInstantiations method for ldtoken instructions
+ /// to discover dependent types that also need serializers.
+ ///
+ private void ScanSerializerDependencies(TypeReference dataType, SerializableTypeInfo serializableTypeInfo)
+ {
+ if (!serializableTypeInfo.Local || serializableTypeInfo.SerializerType == null)
+ return;
+
+ var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve();
+ if (resolvedSerializerType == null)
+ return;
+
+ var enumerateMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations");
+ if (enumerateMethod == null)
+ return;
+
+ // Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes)
+ foreach (var inst in enumerateMethod.Body.Instructions)
{
- var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve();
- if (resolvedSerializerType != null)
+ if (inst.OpCode.Code != Code.Ldtoken)
+ continue;
+
+ var type = (TypeReference)inst.Operand;
+
+ // Try to "close" generics type with serializer type as a context
+ var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type);
+ if (dependentType.ContainsGenericParameter())
+ continue;
+
+ // Import type so that it becomes local to the assembly
+ // (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated)
+ var importedType = Assembly.MainModule.ImportReference(dependentType);
+ if (ResolveSerializer(importedType) == null)
{
- var enumerateGenericInstantiationsMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations");
- if (enumerateGenericInstantiationsMethod != null)
- {
- // Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes)
- foreach (var inst in enumerateGenericInstantiationsMethod.Body.Instructions)
- {
- if (inst.OpCode.Code == Code.Ldtoken)
- {
- var type = (TypeReference)inst.Operand;
-
- // Try to "close" generics type with serializer type as a context
- var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type);
- if (!dependentType.ContainsGenericParameter())
- {
- // Import type so that it becomes local to the assembly
- // (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated)
- var importedType = Assembly.MainModule.ImportReference(dependentType);
- if (GenerateSerializer(importedType) == null)
- {
- throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType));
- }
- }
- }
- }
- }
+ throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType));
}
}
}
@@ -578,14 +546,9 @@ internal class SerializableTypeInfo
public bool Inherited;
///
- /// True if it's a complex serializer.
+ /// True if the serializer is auto-generated (for [DataContract] types).
///
- public bool ComplexSerializer;
-
- ///
- /// Not null if it's a complex serializer and its base class should be serialized too.
- ///
- public TypeReference? ComplexSerializerProcessParentType;
+ public bool IsGeneratedSerializer;
public SerializableTypeInfo(TypeReference serializerType, bool local, DataSerializerGenericMode mode = DataSerializerGenericMode.None)
{
@@ -595,6 +558,22 @@ public SerializableTypeInfo(TypeReference serializerType, bool local, DataSerial
}
}
+ private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly)
+ {
+ var assemblySerializerFactoryAttribute =
+ referencedAssembly.CustomAttributes.FirstOrDefault(
+ x => x.AttributeType.FullName == "Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
+
+ if (assemblySerializerFactoryAttribute == null)
+ return null;
+
+ var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value;
+ if (typeReference == null)
+ return null;
+
+ return typeReference.Resolve();
+ }
+
public class ProfileInfo
{
///
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs
index 8ce94b2eeb..d66ce1040f 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs
@@ -18,7 +18,7 @@ public void ProcessSerializers(CecilSerializerContext context)
if (!member.IsStatic || member.IsPrivate)
continue;
- if (ComplexSerializerRegistry.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default))
+ if (SerializationHelpers.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default))
continue;
if (member.FieldType.Name == "PropertyKey`1"
@@ -27,12 +27,12 @@ public void ProcessSerializers(CecilSerializerContext context)
|| member.FieldType.Name == "ObjectParameterKey`1"
|| member.FieldType.Name == "PermutationParameterKey`1")
{
- context.GenerateSerializer(member.FieldType);
+ context.ResolveSerializer(member.FieldType);
var genericType = (GenericInstanceType)member.FieldType;
// Also generate serializer for embedded type
- context.GenerateSerializer(genericType.GenericArguments[0]);
+ context.ResolveSerializer(genericType.GenericArguments[0]);
}
}
}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs
index ca5fd06cb2..a270516bef 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs
@@ -25,7 +25,7 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex
if (!processedAssemblies.Add(assembly))
return;
- // TODO: Add a flag for ComplexSerializer and transmit it properly (it needs different kind of analysis)
+ // TODO: Add a flag for generated serializers and transmit it properly (it needs different kind of analysis)
// Let's recurse over referenced assemblies
foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences.ToArray())
@@ -80,8 +80,8 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex
if (dataSerializerType == null)
{
- // TODO: We should avoid calling GenerateSerializer now just to have the dataSerializerType (we should do so only in a second step)
- serializableTypeInfo = context.GenerateSerializer(dataType, profile: profile);
+ // TODO: We should avoid calling ResolveSerializer now just to have the dataSerializerType (we should do so only in a second step)
+ serializableTypeInfo = context.ResolveSerializer(dataType, profile: profile);
if (serializableTypeInfo == null)
throw new InvalidOperationException(string.Format("Can't find serializer for type {0}", dataType));
serializableTypeInfo.Local = local;
@@ -91,7 +91,7 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex
else
{
// Add it to list of serializable types
- serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, ComplexSerializer = complexSerializer };
+ serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, IsGeneratedSerializer = complexSerializer };
context.AddSerializableType(dataType, serializableTypeInfo, profile);
}
}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs
new file mode 100644
index 0000000000..041a389bfe
--- /dev/null
+++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Mono.Cecil;
+
+namespace Stride.Core.AssemblyProcessor.Serializers;
+
+///
+/// Describes a serializer to be generated. Collected during the discovery phase
+/// and consumed by the code generation phase to create the actual .
+///
+internal class SerializerDescriptor
+{
+ ///
+ /// The data type being serialized.
+ ///
+ public required TypeDefinition DataType { get; init; }
+
+ ///
+ /// The class name for the generated serializer (e.g. "MyTypeSerializer").
+ ///
+ public required string SerializerClassName { get; init; }
+
+ ///
+ /// Whether the serializer type should be public (true for generic types).
+ ///
+ public required bool IsPublic { get; init; }
+
+ ///
+ /// Whether to use ClassDataSerializer{T} (true) or DataSerializer{T} (false) as the base class.
+ ///
+ public required bool UseClassDataSerializer { get; init; }
+
+ ///
+ /// The serializable type info created during collection, which holds the serializer type reference
+ /// and metadata used by the registration/factory phase.
+ ///
+ public required CecilSerializerContext.SerializableTypeInfo SerializableTypeInfo { get; init; }
+
+ ///
+ /// Base type whose serializer should be called first (for inheritance chains).
+ /// Set during .
+ ///
+ public TypeReference? SerializedParentType { get; set; }
+
+ ///
+ /// The serializable fields/properties of .
+ /// Computed once during collection and reused during code generation.
+ ///
+ public SerializationHelpers.SerializableItem[] SerializableItems { get; set; }
+}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj b/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj
index 39be9ff147..0cfa7d9bf1 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj
+++ b/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj
@@ -72,21 +72,6 @@
Core\ObjectIdBuilder.cs
-
- True
- True
- AssemblyScanCodeGenerator.tt
-
-
- ComplexSerializerCodeGenerator.tt
- True
- True
-
-
- ComplexClassSerializerGenerator.tt
- True
- True
-
diff --git a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs
index 650ecc9b68..5fb8d780c6 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs
@@ -14,73 +14,45 @@ private void ProcessStrideEngineAssembly(CecilSerializerContext context)
var assembly = context.Assembly;
// Check "#if IL" directly in the source to easily see what is generated
- GenerateUpdateEngineHelperCode(assembly);
GenerateUpdatableFieldCode(assembly);
new UpdatablePropertyCodeGenerator(assembly).GenerateUpdatablePropertyCode();
new UpdatableListCodeGenerator(assembly).GenerateUpdatablePropertyCode();
}
- private static void GenerateUpdateEngineHelperCode(AssemblyDefinition assembly)
- {
- var updateEngineHelperType = assembly.MainModule.GetType("Stride.Updater.UpdateEngineHelper");
-
- // UpdateEngineHelper.ObjectToPtr
- var objectToPtr = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "ObjectToPtr"));
- objectToPtr.Emit(OpCodes.Ldarg, objectToPtr.Body.Method.Parameters[0]);
- objectToPtr.Emit(OpCodes.Conv_I);
- objectToPtr.Emit(OpCodes.Ret);
-
- // UpdateEngineHelper.PtrToObject
- // Simpler "ldarg.0 + ret" doesn't work with Xamarin: https://bugzilla.xamarin.com/show_bug.cgi?id=40608
- var ptrToObject = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "PtrToObject"));
- ptrToObject.Body.Variables.Add(new VariableDefinition(assembly.MainModule.TypeSystem.Object));
- ptrToObject.Emit(OpCodes.Ldloca_S, (byte)0);
- ptrToObject.Emit(OpCodes.Ldarg, ptrToObject.Body.Method.Parameters[0]);
-
- // Somehow Xamarin forces us to do a roundtrip to an object
- ptrToObject.Emit(OpCodes.Stind_I);
- ptrToObject.Emit(OpCodes.Ldloc_0);
-
- ptrToObject.Emit(OpCodes.Ret);
-
- // UpdateEngineHelper.Unbox
- var unbox = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "Unbox"));
- unbox.Emit(OpCodes.Ldarg, unbox.Body.Method.Parameters[0]);
- unbox.Emit(OpCodes.Unbox, unbox.Body.Method.GenericParameters[0]);
- unbox.Emit(OpCodes.Ret);
- }
-
+ ///
+ /// Rewrites UpdatableField methods with raw pointer-based field access.
+ ///
private static void GenerateUpdatableFieldCode(AssemblyDefinition assembly)
{
var updatableFieldType = assembly.MainModule.GetType("Stride.Updater.UpdatableField");
var updatableFieldGenericType = assembly.MainModule.GetType("Stride.Updater.UpdatableField`1");
- // UpdatableField.GetObject
- var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject"));
- getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
- getObject.Emit(OpCodes.Ldind_Ref);
- getObject.Emit(OpCodes.Ret);
-
- // UpdatableField.SetObject
- var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject"));
- setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
- setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
- setObject.Emit(OpCodes.Stind_Ref);
- setObject.Emit(OpCodes.Ret);
-
- // UpdatableField.SetStruct
- var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct"));
- setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]);
- setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]);
- setStruct.Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0]);
- setStruct.Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0]);
- setStruct.Emit(OpCodes.Ret);
+ // Generates: object GetObject(IntPtr ptr) => *(object*)ptr;
+ var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject"), assembly);
+ getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0])
+ .Emit(OpCodes.Ldind_Ref)
+ .Emit(OpCodes.Ret);
+
+ // Generates: void SetObject(IntPtr ptr, object value) { *(object*)ptr = value; }
+ var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject"), assembly);
+ setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0])
+ .Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1])
+ .Emit(OpCodes.Stind_Ref)
+ .Emit(OpCodes.Ret);
+
+ // Generates: void SetStruct(IntPtr ptr, object value) { *(T*)ptr = (T)value; }
+ var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct"), assembly);
+ setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0])
+ .Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1])
+ .Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0])
+ .Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0])
+ .Emit(OpCodes.Ret);
}
- private static ILProcessor RewriteBody(MethodDefinition method)
+ private static ILBuilder RewriteBody(MethodDefinition method, AssemblyDefinition assembly)
{
method.Body = new MethodBody(method);
- return method.Body.GetILProcessor();
+ return new ILBuilder(method.Body, assembly.MainModule);
}
///
@@ -96,50 +68,49 @@ public UpdatablePropertyBaseCodeGenerator(AssemblyDefinition assembly)
this.assembly = assembly;
}
- public abstract void EmitGetCode(ILProcessor il, TypeReference type);
+ public abstract void EmitGetCode(ILBuilder il, TypeReference type);
- public virtual void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type)
+ public virtual void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type)
{
}
- public abstract void EmitSetCodeAfterValue(ILProcessor il, TypeReference type);
+ public abstract void EmitSetCodeAfterValue(ILBuilder il, TypeReference type);
public virtual void GenerateUpdatablePropertyCode()
{
- // UpdatableProperty.GetStructAndUnbox
- var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox"));
- getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1]);
- //getStructAndUnbox.Emit(OpCodes.Call, assembly.MainModule.ImportReference(unbox).MakeGenericMethod(declaringType.GenericParameters[0]));
- getStructAndUnbox.Emit(OpCodes.Unbox, declaringType.GenericParameters[0]);
- getStructAndUnbox.Emit(OpCodes.Dup);
- getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]);
+ // Generates: void GetStructAndUnbox(IntPtr ptr, object obj) { *(T*)Unbox(obj) = Get(ptr); }
+ var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox"), assembly);
+ getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1])
+ .Emit(OpCodes.Unbox, declaringType.GenericParameters[0])
+ .Emit(OpCodes.Dup)
+ .Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]);
EmitGetCode(getStructAndUnbox, declaringType.GenericParameters[0]);
- getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]);
- getStructAndUnbox.Emit(OpCodes.Ret);
+ getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0])
+ .Emit(OpCodes.Ret);
- // UpdatableProperty.GetBlittable
- var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable"));
- getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1]);
- getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]);
+ // Generates: void GetBlittable(IntPtr ptr, IntPtr dest) { *(T*)dest = Get(ptr); }
+ var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable"), assembly);
+ getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1])
+ .Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]);
EmitGetCode(getBlittable, declaringType.GenericParameters[0]);
- getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]);
- getBlittable.Emit(OpCodes.Ret);
+ getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0])
+ .Emit(OpCodes.Ret);
- // UpdatableProperty.SetStruct
- var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct"));
+ // Generates: void SetStruct(IntPtr ptr, object value) { Set(ptr, (T)value); }
+ var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct"), assembly);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setStruct, declaringType.GenericParameters[0]);
- setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]);
- setStruct.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]);
+ setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1])
+ .Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]);
EmitSetCodeAfterValue(setStruct, declaringType.GenericParameters[0]);
setStruct.Emit(OpCodes.Ret);
- // UpdatableProperty.SetBlittable
- var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable"));
+ // Generates: void SetBlittable(IntPtr ptr, IntPtr src) { Set(ptr, *(T*)src); }
+ var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable"), assembly);
setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setBlittable, declaringType.GenericParameters[0]);
- setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1]);
- setBlittable.Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]);
+ setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1])
+ .Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]);
EmitSetCodeAfterValue(setBlittable, declaringType.GenericParameters[0]);
setBlittable.Emit(OpCodes.Ret);
}
@@ -167,16 +138,14 @@ public UpdatablePropertyCodeGenerator(AssemblyDefinition assembly) : base(assemb
public override void GenerateUpdatablePropertyCode()
{
- // For UpdatableProperty, GetObject/SetObject are declared on another type
-
- // UpdatableProperty.GetObject
- var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject"));
+ // Generates: object GetObject(IntPtr ptr) => Getter(ptr); // via calli
+ var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject"), assembly);
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
EmitGetCode(getObject, assembly.MainModule.TypeSystem.Object);
getObject.Emit(OpCodes.Ret);
- // UpdatableProperty.SetObject
- var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject"));
+ // Generates: void SetObject(IntPtr ptr, object value) { Setter(ptr, value); } // via calli
+ var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject"), assembly);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setObject, assembly.MainModule.TypeSystem.Object);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
@@ -186,48 +155,60 @@ public override void GenerateUpdatablePropertyCode()
base.GenerateUpdatablePropertyCode();
}
- public override void EmitGetCode(ILProcessor il, TypeReference type)
+ ///
+ /// Emits getter call via function pointer (calli). Branches on VirtualDispatchGetter:
+ ///
+ /// if (VirtualDispatchGetter)
+ /// return calli(Getter, (IntPtr)obj); // static dispatcher that does ldvirtftn internally
+ /// else
+ /// return calli(Getter, obj); // direct instance call via fn pointer
+ ///
+ ///
+ public override void EmitGetCode(ILBuilder il, TypeReference type)
{
var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = true });
- // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object
+ // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr
// (another option would be to use "castclass object" after pushing the IntPtr on the stack)
var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr) } });
- var postCalli = Instruction.Create(OpCodes.Nop);
-
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, updatablePropertyGetter);
- il.Emit(OpCodes.Ldarg_0);
- // For normal calls, we use ldftn and an instance calls
- // For virtual and interface calls, we generate a dispatch function that calls ldvirtftn on the actual object, then call the method on the object
- // this dispatcher method is static, so the calli has a different signature
- // Note: we could later optimize the bool check by having two variant of Get/SetObject
- // and two different implementations of both UpdatableProperty and UpdatablePropertyObject
- // (not sure if worth it)
- il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter);
- il.Emit(OpCodes.Brfalse, calliInstance);
- il.Append(calliVirtualDispatch);
- il.Emit(OpCodes.Br, postCalli);
- il.Append(calliInstance);
- il.Append(postCalli);
+ var postCalli = ILBuilder.DefineLabel();
+
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, updatablePropertyGetter)
+ .Emit(OpCodes.Ldarg_0)
+ // For normal calls, we use ldftn and an instance calli.
+ // For virtual/interface calls, we generate a static dispatch function (via )
+ // that calls ldvirtftn on the actual object — the calli has a different signature since the dispatcher is static.
+ /// Note: we could later optimize the bool check by having two variants of Get/SetObject
+ // and two different implementations of both UpdatableProperty<T> and UpdatablePropertyObject<T>
+ // (not sure if worth it).
+ .Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter)
+ .Emit(OpCodes.Brfalse, calliInstance)
+ .Append(calliVirtualDispatch)
+ .Emit(OpCodes.Br, postCalli)
+ .Append(calliInstance)
+ .MarkLabel(postCalli);
}
- public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type)
+ ///
+ /// Emits setter call via function pointer (calli), same branching pattern as getter.
+ ///
+ public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type)
{
var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = true, Parameters = { new ParameterDefinition(type) } });
- // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object
+ // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr
// (another option would be to use "castclass object" after pushing the IntPtr on the stack)
var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr), new ParameterDefinition(type) } });
- var postCalli = Instruction.Create(OpCodes.Nop);
-
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, updatablePropertySetter);
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter);
- il.Emit(OpCodes.Brfalse, calliInstance);
- il.Append(calliVirtualDispatch);
- il.Emit(OpCodes.Br, postCalli);
- il.Append(calliInstance);
- il.Append(postCalli);
+ var postCalli = ILBuilder.DefineLabel();
+
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, updatablePropertySetter)
+ .Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter)
+ .Emit(OpCodes.Brfalse, calliInstance)
+ .Append(calliVirtualDispatch)
+ .Emit(OpCodes.Br, postCalli)
+ .Append(calliInstance)
+ .MarkLabel(postCalli);
}
}
@@ -239,21 +220,19 @@ public UpdatableCustomPropertyCodeGenerator(AssemblyDefinition assembly) : base(
public override void GenerateUpdatablePropertyCode()
{
- // For UpdatableCustomAccessor, GetObject/SetObject are declared in the generic type
-
- // UpdatableProperty.GetObject
- var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject"));
+ // Generates: object GetObject(IntPtr ptr) => (object)Get(ptr);
+ var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject"), assembly);
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
EmitGetCode(getObject, declaringType.GenericParameters[0]);
- getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
- getObject.Emit(OpCodes.Ret);
+ getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]) // Required for Windows 10 AOT
+ .Emit(OpCodes.Ret);
- // UpdatableProperty.SetObject
- var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject"));
+ // Generates: void SetObject(IntPtr ptr, object value) { Set(ptr, (T)value); }
+ var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject"), assembly);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setObject, declaringType.GenericParameters[0]);
- setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
- setObject.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
+ setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1])
+ .Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
EmitSetCodeAfterValue(setObject, declaringType.GenericParameters[0]);
setObject.Emit(OpCodes.Ret);
@@ -261,6 +240,11 @@ public override void GenerateUpdatablePropertyCode()
}
}
+ ///
+ /// Generates list accessor methods that delegate to IList<T>[Index].
+ /// Get: return ((IList<T>)obj)[this.Index];
+ /// Set: ((IList<T>)obj)[this.Index] = value;
+ ///
class UpdatableListCodeGenerator : UpdatableCustomPropertyCodeGenerator
{
private readonly FieldDefinition indexField;
@@ -282,20 +266,20 @@ public UpdatableListCodeGenerator(AssemblyDefinition assembly) : base(assembly)
ilistSetItem = assembly.MainModule.ImportReference(ilistItem.SetMethod).MakeGeneric(declaringType.GenericParameters[0]);
}
- public override void EmitGetCode(ILProcessor il, TypeReference type)
+ public override void EmitGetCode(ILBuilder il, TypeReference type)
{
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, indexField);
- il.Emit(OpCodes.Callvirt, ilistGetItem);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, indexField)
+ .Emit(OpCodes.Callvirt, ilistGetItem);
}
- public override void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type)
+ public override void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type)
{
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldfld, indexField);
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldfld, indexField);
}
- public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type)
+ public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type)
{
il.Emit(OpCodes.Callvirt, ilistSetItem);
}
diff --git a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs
index 23aff266e5..45bdf8a800 100644
--- a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs
+++ b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs
@@ -41,22 +41,37 @@ private MethodDefinition CreateUpdateMethod(AssemblyDefinition assembly)
// Obtain the static constructor of and the return instruction
var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction);
- // Get the IL processor of the module constructor
- var il = moduleConstructor.Body.GetILProcessor();
+ // Get the IL processor of the module constructor (used only for InsertBefore)
+ var moduleCtorIL = moduleConstructor.Body.GetILProcessor();
// Create the call to Initialize method
var initializeMethodReference = assembly.MainModule.ImportReference(mainPrepareMethod);
- var callInitializeInstruction = il.Create(OpCodes.Call, initializeMethodReference);
- il.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
+ var callInitializeInstruction = moduleCtorIL.Create(OpCodes.Call, initializeMethodReference);
+ moduleCtorIL.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
return mainPrepareMethod;
}
+ ///
+ /// Creates a static dispatcher method for virtual/interface property access via ldvirtftn/calli.
+ ///
+ ///
+ /// Generates code equivalent to:
+ ///
+ /// static TReturn Dispatcher_get_Property(object @this /*, params... */)
+ /// {
+ /// return @this./* virtual call via ldvirtftn+calli */get_Property(/* params... */);
+ /// }
+ ///
+ /// This is needed because ldftn of a virtual method gives the base slot, not the override.
+ /// The dispatcher uses ldvirtftn to resolve the actual vtable entry at runtime.
+ ///
private MethodReference CreateDispatcher(AssemblyDefinition assembly, MethodReference method)
{
var updateEngineType = GetOrCreateUpdateType(assembly, true);
+ var module = assembly.MainModule;
- var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, assembly.MainModule.ImportReference(method.ReturnType));
+ var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, module.ImportReference(method.ReturnType));
updateEngineType.Methods.Add(dispatcherMethod);
dispatcherMethod.Parameters.Add(new ParameterDefinition("this", ParameterAttributes.None, method.DeclaringType));
@@ -65,19 +80,22 @@ private MethodReference CreateDispatcher(AssemblyDefinition assembly, MethodRefe
dispatcherMethod.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, param.ParameterType));
}
- var il = dispatcherMethod.Body.GetILProcessor();
+ // Emit: load all args, then resolve virtual method and call via calli
+ var il = new ILBuilder(dispatcherMethod.Body, module);
il.Emit(OpCodes.Ldarg_0);
- foreach (var param in dispatcherMethod.Parameters.Skip(1)) // first parameter is "this"
+ // note: first parameter is "this"
+ foreach (var param in dispatcherMethod.Parameters.Skip(1))
{
il.Emit(OpCodes.Ldarg, param);
}
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldvirtftn, method);
var callsite = new Mono.Cecil.CallSite(method.ReturnType) { HasThis = true };
foreach (var param in method.Parameters)
callsite.Parameters.Add(param);
- il.Emit(OpCodes.Calli, callsite);
- il.Emit(OpCodes.Ret);
+ // Emit: @this.ldvirtftn(method) then calli — resolves the actual override at runtime
+ il.Emit(OpCodes.Ldarg_0)
+ .Emit(OpCodes.Ldvirtftn, method)
+ .Emit(OpCodes.Calli, callsite)
+ .Emit(OpCodes.Ret);
return dispatcherMethod;
}
@@ -88,6 +106,7 @@ public void ProcessSerializers(CecilSerializerContext context)
EnumerateReferences(references, context.Assembly);
var coreAssembly = CecilExtensions.FindCorlibAssembly(context.Assembly);
+ var module = context.Assembly.MainModule;
// Only process assemblies depending on Stride.Engine
if (!references.Any(x => x.Name.Name == "Stride.Engine"))
@@ -97,12 +116,12 @@ public void ProcessSerializers(CecilSerializerContext context)
var serializationAssemblyName = "Stride.Engine.Serializers";
// Add [InteralsVisibleTo] attribute
- var internalsVisibleToAttributeCtor = context.Assembly.MainModule.ImportReference(internalsVisibleToAttribute.GetConstructors().Single());
+ var internalsVisibleToAttributeCtor = module.ImportReference(internalsVisibleToAttribute.GetConstructors().Single());
var internalsVisibleAttribute = new CustomAttribute(internalsVisibleToAttributeCtor)
{
ConstructorArguments =
{
- new CustomAttributeArgument(context.Assembly.MainModule.ImportReference(context.Assembly.MainModule.TypeSystem.String), serializationAssemblyName)
+ new CustomAttributeArgument(module.ImportReference(module.TypeSystem.String), serializationAssemblyName)
}
};
context.Assembly.CustomAttributes.Add(internalsVisibleAttribute);
@@ -112,7 +131,7 @@ public void ProcessSerializers(CecilSerializerContext context)
var strideEngineAssembly = context.Assembly.Name.Name == "Stride.Engine"
? context.Assembly
- : context.Assembly.MainModule.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null));
+ : module.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null));
var strideEngineModule = strideEngineAssembly.MainModule;
// Generate IL for Stride.Core
@@ -140,14 +159,14 @@ public void ProcessSerializers(CecilSerializerContext context)
parameterCollectionResolverInstantiateValueAccessor = parameterCollectionResolver.Methods.First(x => x.Name == "InstantiateValueAccessor");
var registerMemberMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMember");
- updateEngineRegisterMemberMethod = context.Assembly.MainModule.ImportReference(registerMemberMethod);
+ updateEngineRegisterMemberMethod = module.ImportReference(registerMemberMethod);
var registerMemberResolverMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMemberResolver");
//pclVisitor.VisitMethod(registerMemberResolverMethod);
- updateEngineRegisterMemberResolverMethod = context.Assembly.MainModule.ImportReference(registerMemberResolverMethod);
+ updateEngineRegisterMemberResolverMethod = module.ImportReference(registerMemberResolverMethod);
var typeType = coreAssembly.MainModule.GetTypeResolved(typeof(Type).FullName);
- getTypeFromHandleMethod = context.Assembly.MainModule.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle"));
+ getTypeFromHandleMethod = module.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle"));
var mainPrepareMethod = CreateUpdateMethod(context.Assembly);
@@ -168,7 +187,7 @@ public void ProcessSerializers(CecilSerializerContext context)
try
{
- ProcessType(context, context.Assembly.MainModule.ImportReference(typeDefinition), mainPrepareMethod);
+ ProcessType(context, module.ImportReference(typeDefinition), mainPrepareMethod);
}
catch (Exception e)
{
@@ -176,15 +195,20 @@ public void ProcessSerializers(CecilSerializerContext context)
}
}
- // Force generic instantiations
- var il = mainPrepareMethod.Body.GetILProcessor();
+ // Force generic instantiations — register resolvers for lists, arrays, and trigger generic update methods
+ // Generates calls like:
+ // UpdateEngine.RegisterMemberResolver(new ListUpdateResolver());
+ // UpdateEngine.RegisterMemberResolver(new ArrayUpdateResolver());
+ // ParameterCollectionResolver.InstantiateValueAccessor(); // iOS AOT only
+ // UpdateGeneric_TypeName(); // for closed generic types
+ var il = new ILBuilder(mainPrepareMethod.Body, module);
foreach (var serializableType in context.SerializableTypesProfiles.SelectMany(x => x.Value.SerializableTypes).ToArray())
{
// Special case: when processing Stride.Engine assembly, we automatically add dependent assemblies types too
if (!serializableType.Value.Local && strideEngineAssembly != context.Assembly)
continue;
- // Try to find if original method definition was generated
+ // Try to find if original method definition was generated
var typeDefinition = serializableType.Key.Resolve();
// If using List, register this type in UpdateEngine
@@ -196,8 +220,8 @@ public void ProcessSerializers(CecilSerializerContext context)
{
//call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ListUpdateResolver());
var elementType = ResolveGenericsVisitor.Process(serializableType.Key, listInterfaceType.GenericArguments[0]);
- il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableListUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType)));
- il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
+ il.Emit(OpCodes.Newobj, il.Import(updatableListUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType)))
+ .Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
}
parentTypeDefinition = parentTypeDefinition.BaseType?.Resolve();
@@ -208,15 +232,15 @@ public void ProcessSerializers(CecilSerializerContext context)
{
//call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ArrayUpdateResolver());
var elementType = ResolveGenericsVisitor.Process(serializableType.Key, arrayType.ElementType);
- il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableArrayUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType)));
- il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
+ il.Emit(OpCodes.Newobj, il.Import(updatableArrayUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType)))
+ .Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
}
// Generic instantiation for AOT platforms
if (context.Platform == Core.PlatformType.iOS && serializableType.Key.Name == "ValueParameterKey`1")
{
var keyType = ((GenericInstanceType)serializableType.Key).GenericArguments[0];
- il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(context.Assembly.MainModule.ImportReference(keyType)));
+ il.Emit(OpCodes.Call, il.Import(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(il.Import(keyType)));
}
if (serializableType.Key is GenericInstanceType genericInstanceType)
@@ -233,9 +257,9 @@ public void ProcessSerializers(CecilSerializerContext context)
if (updateMethod != null)
{
// Emit call to update engine setup method with generic arguments of current type
- il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(updateMethod)
+ il.Emit(OpCodes.Call, il.Import(updateMethod)
.MakeGenericMethod(genericInstanceType.GenericArguments
- .Select(context.Assembly.MainModule.ImportReference)
+ .Select(x => module.ImportReference(x))
.ToArray()));
}
}
@@ -244,6 +268,11 @@ public void ProcessSerializers(CecilSerializerContext context)
il.Emit(OpCodes.Ret);
}
+ ///
+ /// Registers all updatable fields and properties of a type with the update engine.
+ /// For generic types, creates a separate UpdateGeneric_TypeName<T>() method
+ /// that can be instantiated per closed generic type.
+ ///
public void ProcessType(CecilSerializerContext context, TypeReference type, MethodDefinition updateMainMethod)
{
var typeDefinition = type.Resolve();
@@ -252,14 +281,14 @@ public void ProcessType(CecilSerializerContext context, TypeReference type, Meth
if (typeDefinition.IsEnum)
return;
+ var module = context.Assembly.MainModule;
var updateCurrentMethod = updateMainMethod;
ResolveGenericsVisitor replaceGenericsVisitor = null;
if (typeDefinition.HasGenericParameters)
{
// Make a prepare method for just this object since it might need multiple instantiation
- updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, context.Assembly.MainModule.TypeSystem.Void);
- var genericsMapping = new Dictionary();
+ updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void);
foreach (var genericParameter in typeDefinition.GenericParameters)
{
var genericParameterCopy = new GenericParameter(genericParameter.Name, updateCurrentMethod)
@@ -267,139 +296,180 @@ public void ProcessType(CecilSerializerContext context, TypeReference type, Meth
Attributes = genericParameter.Attributes,
};
foreach (var constraint in genericParameter.Constraints)
- genericParameterCopy.Constraints.Add(new GenericParameterConstraint(context.Assembly.MainModule.ImportReference(constraint.ConstraintType)));
+ genericParameterCopy.Constraints.Add(new GenericParameterConstraint(module.ImportReference(constraint.ConstraintType)));
updateCurrentMethod.GenericParameters.Add(genericParameterCopy);
-
- genericsMapping[genericParameter] = genericParameterCopy;
}
- replaceGenericsVisitor = new ResolveGenericsVisitor(genericsMapping);
+ replaceGenericsVisitor = ResolveGenericsVisitor.FromMapping(typeDefinition, updateCurrentMethod);
updateMainMethod.DeclaringType.Methods.Add(updateCurrentMethod);
}
- var il = updateCurrentMethod.Body.GetILProcessor();
+ var il = new ILBuilder(updateCurrentMethod.Body, module);
var typeIsValueType = type.IsResolvedValueType();
var emptyObjectField = typeIsValueType ? null : updateMainMethod.DeclaringType.Fields.FirstOrDefault(x => x.Name == "emptyObject");
VariableDefinition emptyStruct = null;
// Note: forcing fields and properties to be processed in all cases
- foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable))
+ foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable, context.IgnoredMembers))
{
if (serializableItem.MemberInfo is FieldReference fieldReference)
- {
- var field = fieldReference.Resolve();
-
- // First time it is needed, let's create empty object in the class (var emptyObject = new object()) or empty local struct in the method
- if (typeIsValueType)
- {
- if (emptyStruct == null)
- {
- emptyStruct = new VariableDefinition(type);
- updateMainMethod.Body.Variables.Add(emptyStruct);
- }
- }
- else
- {
- if (emptyObjectField == null)
- {
- emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, context.Assembly.MainModule.TypeSystem.Object);
-
- // Create static ctor that will initialize this object
- var staticConstructor = new MethodDefinition(".cctor",
- MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
- context.Assembly.MainModule.TypeSystem.Void);
- var staticConstructorIL = staticConstructor.Body.GetILProcessor();
- staticConstructorIL.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters)));
- staticConstructorIL.Emit(OpCodes.Stsfld, emptyObjectField);
- staticConstructorIL.Emit(OpCodes.Ret);
-
- updateMainMethod.DeclaringType.Fields.Add(emptyObjectField);
- updateMainMethod.DeclaringType.Methods.Add(staticConstructor);
- }
- }
-
- il.Emit(OpCodes.Ldtoken, type);
- il.Emit(OpCodes.Call, getTypeFromHandleMethod);
- il.Emit(OpCodes.Ldstr, field.Name);
-
- if (typeIsValueType)
- il.Emit(OpCodes.Ldloca, emptyStruct);
- else
- il.Emit(OpCodes.Ldsfld, emptyObjectField);
- il.Emit(OpCodes.Ldflda, context.Assembly.MainModule.ImportReference(fieldReference));
- il.Emit(OpCodes.Conv_I);
- if (typeIsValueType)
- il.Emit(OpCodes.Ldloca, emptyStruct);
- else
- il.Emit(OpCodes.Ldsfld, emptyObjectField);
- il.Emit(OpCodes.Conv_I);
- il.Emit(OpCodes.Sub);
- il.Emit(OpCodes.Conv_I4);
-
- var fieldType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType);
- il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableFieldGenericCtor).MakeGeneric(fieldType));
- il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
- }
+ EmitFieldRegistration(il, fieldReference, type, typeIsValueType, replaceGenericsVisitor, module, updateMainMethod, ref emptyObjectField, ref emptyStruct);
if (serializableItem.MemberInfo is PropertyReference propertyReference)
- {
- var property = propertyReference.Resolve();
+ EmitPropertyRegistration(il, propertyReference, type, replaceGenericsVisitor, context.Assembly, updateCurrentMethod);
+ }
- var propertyGetMethod = context.Assembly.MainModule.ImportReference(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
+ if (updateCurrentMethod != updateMainMethod)
+ {
+ // If we have a local method, close it
+ il.Emit(OpCodes.Ret);
- il.Emit(OpCodes.Ldtoken, type);
- il.Emit(OpCodes.Call, getTypeFromHandleMethod);
- il.Emit(OpCodes.Ldstr, property.Name);
+ // Also call it from main method if it was a closed generic instantiation
+ if (type is GenericInstanceType genericInstanceType)
+ {
+ var mainIL = new ILBuilder(updateMainMethod.Body, module);
+ mainIL.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(module.ImportReference).ToArray()));
+ }
+ }
+ }
- // If it's a virtual or interface call, we need to create a dispatcher using ldvirtftn
- if (property.GetMethod.IsVirtual)
- propertyGetMethod = CreateDispatcher(context.Assembly, propertyGetMethod);
+ ///
+ /// Emits field registration with the update engine using pointer offset computation.
+ ///
+ ///
+ /// Generates code equivalent to:
+ ///
+ /// // For reference types:
+ /// UpdateEngine.RegisterMember(typeof(T), "fieldName",
+ /// new UpdatableField<FieldType>(&emptyObject.field - &emptyObject));
+ /// // For value types:
+ /// UpdateEngine.RegisterMember(typeof(T), "fieldName",
+ /// new UpdatableField<FieldType>(&emptyStruct.field - &emptyStruct));
+ ///
+ ///
+ private void EmitFieldRegistration(
+ ILBuilder il, FieldReference fieldReference, TypeReference type, bool typeIsValueType,
+ ResolveGenericsVisitor replaceGenericsVisitor, ModuleDefinition module,
+ MethodDefinition updateMainMethod, ref FieldDefinition emptyObjectField, ref VariableDefinition emptyStruct)
+ {
+ var field = fieldReference.Resolve();
- il.Emit(OpCodes.Ldftn, propertyGetMethod);
+ // We need a dummy instance to compute field offsets via pointer math: &empty.field - &empty
+ // For reference types: a static field initialized in .cctor (var emptyObject = new object())
+ // For value types: a local variable (T emptyStruct)
+ if (typeIsValueType)
+ {
+ if (emptyStruct == null)
+ {
+ emptyStruct = new VariableDefinition(type);
+ updateMainMethod.Body.Variables.Add(emptyStruct);
+ }
+ }
+ else
+ {
+ if (emptyObjectField == null)
+ {
+ emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, module.TypeSystem.Object);
+
+ // Create static ctor that will initialize this object
+ var staticConstructor = new MethodDefinition(".cctor",
+ MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ module.TypeSystem.Void);
+ var staticCtorIL = new ILBuilder(staticConstructor.Body, module);
+ staticCtorIL.Emit(OpCodes.Newobj, module.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters)))
+ .Emit(OpCodes.Stsfld, emptyObjectField)
+ .Emit(OpCodes.Ret);
+
+ updateMainMethod.DeclaringType.Fields.Add(emptyObjectField);
+ updateMainMethod.DeclaringType.Methods.Add(staticConstructor);
+ }
+ }
- // Set whether getter method uses a VirtualDispatch (static call) or instance call
- il.Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
+ // Emit: typeof(T), "fieldName"
+ il.EmitTypeof(type, getTypeFromHandleMethod)
+ .Emit(OpCodes.Ldstr, field.Name);
+
+ // Emit: (int)(&empty.field - &empty) — computes field byte offset
+ if (typeIsValueType)
+ il.Emit(OpCodes.Ldloca, emptyStruct);
+ else
+ il.Emit(OpCodes.Ldsfld, emptyObjectField);
+ il.Emit(OpCodes.Ldflda, il.Import(fieldReference))
+ .Emit(OpCodes.Conv_I);
+ if (typeIsValueType)
+ il.Emit(OpCodes.Ldloca, emptyStruct);
+ else
+ il.Emit(OpCodes.Ldsfld, emptyObjectField);
+ il.Emit(OpCodes.Conv_I)
+ .Emit(OpCodes.Sub)
+ .Emit(OpCodes.Conv_I4);
+
+ // Emit: new UpdatableField(offset)
+ var fieldType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType);
+ il.Emit(OpCodes.Newobj, il.Import(updatableFieldGenericCtor).MakeGeneric(fieldType))
+ // Emit: UpdateEngine.RegisterMember(typeof(T), "fieldName", updatableField);
+ .Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
+ }
- // Only uses setter if it exists and it's public
- if (property.SetMethod?.IsPublic == true)
- {
- var propertySetMethod = context.Assembly.MainModule.ImportReference(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
- if (property.SetMethod.IsVirtual)
- propertySetMethod = CreateDispatcher(context.Assembly, propertySetMethod);
- il.Emit(OpCodes.Ldftn, propertySetMethod);
- }
- else
- {
- // 0 (native int)
- il.Emit(OpCodes.Ldc_I4_0);
- il.Emit(OpCodes.Conv_I);
- }
+ ///
+ /// Emits property registration with the update engine using function pointers.
+ ///
+ ///
+ /// Generates code equivalent to:
+ ///
+ /// UpdateEngine.RegisterMember(typeof(T), "PropertyName",
+ /// new UpdatableProperty<PropType>(
+ /// ldftn(get_Property), // or ldftn(Dispatcher_get_Property) for virtual
+ /// isGetterVirtual,
+ /// ldftn(set_Property), // or IntPtr.Zero if no public setter
+ /// isSetterVirtual));
+ ///
+ /// Virtual/interface properties use a dispatcher trampoline (see ).
+ ///
+ private void EmitPropertyRegistration(
+ ILBuilder il, PropertyReference propertyReference, TypeReference type,
+ ResolveGenericsVisitor replaceGenericsVisitor, AssemblyDefinition assembly,
+ MethodDefinition updateCurrentMethod)
+ {
+ var property = propertyReference.Resolve();
- // Set whether setter method uses a VirtualDispatch (static call) or instance call
- il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
+ var propertyGetMethod = il.Import(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
- var propertyType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType);
+ // Emit: typeof(T), "PropertyName"
+ il.EmitTypeof(type, getTypeFromHandleMethod)
+ .Emit(OpCodes.Ldstr, property.Name);
- var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(context.Assembly, propertyType);
+ // Emit: ldftn(get_Property) — for virtual, wrap in a dispatcher that uses ldvirtftn+calli
+ if (property.GetMethod.IsVirtual)
+ propertyGetMethod = CreateDispatcher(assembly, propertyGetMethod);
+ il.Emit(OpCodes.Ldftn, propertyGetMethod)
+ // Set whether setter method uses a VirtualDispatch (static call) or instance call
+ .Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
- il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor);
- il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
- }
+ // Emit: ldftn(set_Property) or IntPtr.Zero if no public setter
+ if (property.SetMethod?.IsPublic == true)
+ {
+ var propertySetMethod = il.Import(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
+ if (property.SetMethod.IsVirtual)
+ propertySetMethod = CreateDispatcher(assembly, propertySetMethod);
+ il.Emit(OpCodes.Ldftn, propertySetMethod);
}
-
- if (updateCurrentMethod != updateMainMethod)
+ else
{
- // If we have a local method, close it
- il.Emit(OpCodes.Ret);
-
- // Also call it from main method if it was a closed generic instantiation
- if (type is GenericInstanceType genericInstanceType)
- {
- il = updateMainMethod.Body.GetILProcessor();
- il.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(context.Assembly.MainModule.ImportReference).ToArray()));
- }
+ il.Emit(OpCodes.Ldc_I4_0)
+ .Emit(OpCodes.Conv_I);
}
+
+ // Set whether setter method uses a VirtualDispatch (static call) or instance call
+ il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
+
+ // Emit: new UpdatableProperty(getter, isGetterVirtual, setter, isSetterVirtual)
+ var propertyType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType);
+ var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(assembly, propertyType);
+ il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor)
+ // Emit: UpdateEngine.RegisterMember(typeof(T), "PropertyName", updatableProperty);
+ .Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
}
private static string ComputeUpdateMethodName(TypeDefinition typeDefinition)
diff --git a/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs b/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs
index 1c34362d98..e3b7a9af01 100644
--- a/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs
+++ b/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs
@@ -26,79 +26,18 @@ ldarga.s o
=> ((nint*)Unsafe.AsPointer(ref o))[0];
[MethodImpl(MethodImplOptions.AggressiveInlining), Obsolete("Do not use.", DiagnosticId = "STRIDE2000")]
public static unsafe T PointerToObject(nint address) where T : class
- #if false
- ldarga.s address
- conv.u
- nop // call !!0& Unsafe::AsRef(void*) /* ldarg.0; ret */
- ldobj !!T
- ret
- #endif
=> Unsafe.AsRef(&address);
/// Copies the value out of the pinned box.
[MethodImpl(MethodImplOptions.AggressiveInlining), Obsolete("Do not use.", DiagnosticId = "STRIDE2000")]
public static unsafe T PointerToStruct(nint address) where T : struct
- #if false
- ldarga.s address
- conv.u
- nop // call !!0& Unsafe::AsRef