Skip to content

Commit

Permalink
Added GenerateSerializationForGenericParameterAttribute and `Genera…
Browse files Browse the repository at this point in the history
…teSerializationForTypeAttribute`

This enables users to control the generation of serialization code through codegen, making it easier to create their own network variable subtypes, and also making it possible for them to easily generate serialization for specific types they know they need to use. NetworkVariable and NetworkList have been changed to use these attributes so they are no longer special cases in our codegen.

This PR also exposes methods in `NetworkVariableSerialization<T>` to further support this type of serialization need.

resolves #2686
  • Loading branch information
ShadauxCat committed Sep 7, 2023
1 parent f59f5e6 commit 404d032
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
.ToList()
.ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines));

foreach (var type in mainModule.GetTypes())
{
var resolved = type.Resolve();
foreach (var attribute in resolved.CustomAttributes)
{
if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute))
{
var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value);
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}

foreach (var method in resolved.Methods)
{
foreach (var attribute in method.CustomAttributes)
{
if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute))
{
var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value);
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
}

CreateNetworkVariableTypeInitializers(assemblyDefinition);
}
catch (Exception e)
Expand Down Expand Up @@ -196,10 +227,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef);
}

if (serializeMethod != null)
{
serializeMethod.GenericArguments.Add(wrappedType);
}
serializeMethod?.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
Expand Down Expand Up @@ -259,10 +287,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef);
}

if (serializeMethod != null)
{
serializeMethod.GenericArguments.Add(type);
}
serializeMethod?.GenericArguments.Add(type);
equalityMethod.GenericArguments.Add(type);
}
else
Expand Down Expand Up @@ -296,10 +321,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
}

if (serializeMethod != null)
{
serializeMethod.GenericArguments.Add(type);
}
serializeMethod?.GenericArguments.Add(type);
equalityMethod.GenericArguments.Add(type);
}

Expand Down Expand Up @@ -400,6 +422,8 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;

private MethodReference m_SerializesGenericParameterAttribute_Constructor_MethodRef;

private MethodReference m_NetworkBehaviour_createNativeList_MethodRef;

private TypeReference m_FastBufferWriter_TypeRef;
Expand Down Expand Up @@ -583,6 +607,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
byteUnpackerTypeDef = netcodeTypeDef;
continue;
}

if (netcodeTypeDef.Name == nameof(GenerateSerializationForGenericParameterAttribute))
{
var constructors = netcodeTypeDef.GetConstructors();
m_SerializesGenericParameterAttribute_Constructor_MethodRef = moduleDefinition.ImportReference(constructors.First());
}
}

foreach (var methodDef in debugTypeDef.Methods)
Expand Down Expand Up @@ -1146,13 +1176,22 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
//var type = field.FieldType;
if (type.IsGenericInstance)
{
if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name)
foreach (var attribute in type.Resolve().CustomAttributes)
{
var genericInstanceType = (GenericInstanceType)type;
var wrappedType = genericInstanceType.GenericArguments[0];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
if (attribute.AttributeType.Name == nameof(GenerateSerializationForGenericParameterAttribute))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
var idx = (int)attribute.ConstructorArguments[0].Value;
var genericInstanceType = (GenericInstanceType)type;
if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count)
{
m_Diagnostics.AddError($"{type} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}");
continue;
}
var wrappedType = genericInstanceType.GenericArguments[idx];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
Expand All @@ -1173,13 +1212,22 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
GetAllBaseTypesAndResolveGenerics(type.Resolve(), ref baseTypes, genericParams);
foreach (var baseType in baseTypes)
{
if (baseType.Resolve().Name == typeof(NetworkVariable<>).Name || baseType.Resolve().Name == typeof(NetworkList<>).Name)
foreach (var attribute in baseType.Resolve().CustomAttributes)
{
var genericInstanceType = (GenericInstanceType)baseType;
var wrappedType = genericInstanceType.GenericArguments[0];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
if (attribute.Constructor.Resolve() == m_SerializesGenericParameterAttribute_Constructor_MethodRef.Resolve())
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
var idx = (int)attribute.ConstructorArguments[0].Value;
var genericInstanceType = (GenericInstanceType)baseType;
if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count)
{
m_Diagnostics.AddError($"{baseType} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}");
continue;
}
var wrappedType = genericInstanceType.GenericArguments[idx];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;

namespace Unity.Netcode
{
/// <summary>
/// Marks a generic parameter in this class as a type that should be serialized through
/// <see cref="NetworkVariableSerialization{T}"/>. This enables the use of the following methods to support
/// serialization within a Network Variable type:
/// <br/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Read"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Write"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.AreEqual"/>
/// <br/>
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Duplicate"/>
/// <br/>
/// <br/>
/// The parameter is indicated by index (and is 0-indexed); for example:
/// <br/>
/// <code>
/// [SerializesGenericParameter(1)]
/// public class MyClass&lt;TTypeOne, TTypeTwo&gt;
/// {
/// }
/// </code>
/// <br/>
/// This tells the code generation for <see cref="NetworkVariableSerialization{T}"/> to generate
/// serialized code for <b>TTypeTwo</b> (generic parameter 1).
/// <br/>
/// <br/>
/// Note that this is primarily intended to support subtypes of <see cref="NetworkVariableBase"/>,
/// and as such, the type resolution is done by examining fields of <see cref="NetworkBehaviour"/>
/// subclasses. If your type is not used in a <see cref="NetworkBehaviour"/>, the codegen will
/// not find the types, even with this attribute.
/// <br/>
/// <br/>
/// This attribute is properly inherited by subclasses. For example:
/// <br/>
/// <code>
/// [SerializesGenericParameter(0)]
/// public class MyClass&lt;T&gt;
/// {
/// }
/// <br/>
/// public class MySubclass1 : MyClass&lt;Foo&gt;
/// {
/// }
/// <br/>
/// public class MySubclass2&lt;T&gt; : MyClass&lt;T&gt;
/// {
/// }
/// <br/>
/// [SerializesGenericParameter(1)]
/// public class MySubclass3&lt;TTypeOne, TTypeTwo&gt; : MyClass&lt;TTypeOne&gt;
/// {
/// }
/// <br/>
/// public class MyBehaviour : NetworkBehaviour
/// {
/// public MySubclass1 TheValue;
/// public MySubclass2&lt;Bar&gt; TheValue;
/// public MySubclass3&lt;Baz, Qux&gt; TheValue;
/// }
/// </code>
/// <br/>
/// The above code will trigger generation of serialization code for <b>Foo</b> (passed directly to the
/// base class), <b>Bar</b> (passed indirectly to the base class), <b>Baz</b> (passed indirectly to the base class),
/// and <b>Qux</b> (marked as serializable in the subclass).
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class GenerateSerializationForGenericParameterAttribute : Attribute
{
internal int ParameterIndex;

public GenerateSerializationForGenericParameterAttribute(int parameterIndex)
{
ParameterIndex = parameterIndex;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Unity.Netcode
{
/// <summary>
/// Specifies a specific type that needs serialization to be generated by codegen.
/// This is only needed in special circumstances where manual serialization is being done.
/// If you are making a generic network variable-style class, use <see cref="GenerateSerializationForGenericParameterAttribute"/>.
/// <br />
/// <br />
/// This attribute can be attached to any class or method anywhere in the codebase and
/// will trigger codegen to generate serialization code for the provided type. It only needs
/// to be included once type per codebase, but including it multiple times for the same type
/// is safe.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method, AllowMultiple = true)]
public class GenerateSerializationForTypeAttribute : Attribute
{
internal Type Type;

public GenerateSerializationForTypeAttribute(Type type)
{
Type = type;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Unity.Netcode
/// Event based NetworkVariable container for syncing Lists
/// </summary>
/// <typeparam name="T">The type for the list</typeparam>
[GenerateSerializationForGenericParameter(0)]
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
{
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Unity.Netcode
/// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable]
[GenerateSerializationForGenericParameter(0)]
public class NetworkVariable<T> : NetworkVariableBase
{
/// <summary>
Expand Down Expand Up @@ -149,7 +150,7 @@ public override void ResetDirty()
if (!m_HasPreviousValue || !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_PreviousValue))
{
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue);
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
}

Expand Down
Loading

0 comments on commit 404d032

Please sign in to comment.