359 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 
 | |
| using FishNet.CodeGenerating.Helping;
 | |
| using FishNet.CodeGenerating.Helping.Extension;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Serializing.Helping;
 | |
| using MonoFN.Cecil;
 | |
| using MonoFN.Cecil.Cil;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.CodeGenerating.Processing
 | |
| {
 | |
|     internal class CustomSerializerProcessor : CodegenBase
 | |
|     {
 | |
| 
 | |
|         #region Types.
 | |
|         internal enum ExtensionType
 | |
|         {
 | |
|             None,
 | |
|             Write,
 | |
|             Read
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         internal bool CreateSerializerDelegates(TypeDefinition typeDef, bool replace)
 | |
|         {
 | |
|             bool modified = false;
 | |
|             /* Find all declared methods and register delegates to them.
 | |
|              * After they are all registered create any custom writers
 | |
|              * needed to complete the declared methods. It's important to
 | |
|              * make generated writers after so that a generated method
 | |
|              * isn't made for a type when the user has already made a declared one. */
 | |
|             foreach (MethodDefinition methodDef in typeDef.Methods)
 | |
|             {
 | |
|                 ExtensionType extensionType = GetExtensionType(methodDef);
 | |
|                 if (extensionType == ExtensionType.None)
 | |
|                     continue;
 | |
|                 if (base.GetClass<GeneralHelper>().CodegenExclude(methodDef))
 | |
|                     continue;
 | |
| 
 | |
|                 MethodReference methodRef = base.ImportReference(methodDef);
 | |
|                 if (extensionType == ExtensionType.Write)
 | |
|                 {
 | |
|                     base.GetClass<WriterProcessor>().AddWriterMethod(methodRef.Parameters[1].ParameterType, methodRef, false, !replace);
 | |
|                     modified = true;
 | |
|                 }
 | |
|                 else if (extensionType == ExtensionType.Read)
 | |
|                 {
 | |
|                     base.GetClass<ReaderProcessor>().AddReaderMethod(methodRef.ReturnType, methodRef, false, !replace);
 | |
|                     modified = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return modified;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates serializers for any custom types for declared methods.
 | |
|         /// </summary>
 | |
|         /// <param name="declaredMethods"></param>
 | |
|         /// <param name="moduleDef"></param>
 | |
|         internal bool CreateSerializers(TypeDefinition typeDef)
 | |
|         {
 | |
|             bool modified = false;
 | |
| 
 | |
|             List<(MethodDefinition, ExtensionType)> declaredMethods = new List<(MethodDefinition, ExtensionType)>();
 | |
|             /* Go through all custom serializers again and see if 
 | |
|              * they use any types that the user didn't make a serializer for
 | |
|              * and that there isn't a built-in type for. Create serializers
 | |
|              * for these types. */
 | |
|             foreach (MethodDefinition methodDef in typeDef.Methods)
 | |
|             {
 | |
|                 ExtensionType extensionType = GetExtensionType(methodDef);
 | |
|                 if (extensionType == ExtensionType.None)
 | |
|                     continue;
 | |
|                 if (base.GetClass<GeneralHelper>().CodegenExclude(methodDef))
 | |
|                     continue;
 | |
| 
 | |
|                 declaredMethods.Add((methodDef, extensionType));
 | |
|                 modified = true;
 | |
|             }
 | |
|             //Now that all declared are loaded see if any of them need generated serializers.
 | |
|             foreach ((MethodDefinition methodDef, ExtensionType extensionType) in declaredMethods)
 | |
|                 CreateSerializers(extensionType, methodDef);
 | |
| 
 | |
|             return modified;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates a custom serializer for any types not handled within users declared.
 | |
|         /// </summary>
 | |
|         /// <param name="extensionType"></param>
 | |
|         /// <param name="moduleDef"></param>
 | |
|         /// <param name="methodDef"></param>
 | |
|         /// <param name="diagnostics"></param>
 | |
|         private void CreateSerializers(ExtensionType extensionType, MethodDefinition methodDef)
 | |
|         {
 | |
|             for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
 | |
|                 CheckToModifyInstructions(extensionType, methodDef, ref i);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates delegates for custom comparers.
 | |
|         /// </summary>
 | |
|         internal bool CreateComparerDelegates(TypeDefinition typeDef)
 | |
|         {
 | |
|             bool modified = false;
 | |
|             GeneralHelper gh = base.GetClass<GeneralHelper>();
 | |
|             /* Find all declared methods and register delegates to them.
 | |
|              * After they are all registered create any custom writers
 | |
|              * needed to complete the declared methods. It's important to
 | |
|              * make generated writers after so that a generated method
 | |
|              * isn't made for a type when the user has already made a declared one. */
 | |
|             foreach (MethodDefinition methodDef in typeDef.Methods)
 | |
|             {
 | |
|                 if (gh.CodegenExclude(methodDef))
 | |
|                     continue;
 | |
|                 if (!methodDef.HasCustomAttribute<CustomComparerAttribute>())
 | |
|                     continue;
 | |
|                 //Validate return type.
 | |
|                 if (methodDef.ReturnType.FullName != gh.GetTypeReference(typeof(bool)).FullName)
 | |
|                 {
 | |
|                     base.LogError($"Comparer method {methodDef.Name} in type {typeDef.FullName} must return bool.");
 | |
|                     continue;
 | |
|                 }
 | |
|                 /* Make sure parameters are correct. */
 | |
|                 //Invalid count.
 | |
|                 if (methodDef.Parameters.Count != 2)
 | |
|                 {
 | |
|                     base.LogError($"Comparer method {methodDef.Name} in type {typeDef.FullName} must have exactly two parameters, each of the same type which is being compared.");
 | |
|                     continue;
 | |
|                 }
 | |
|                 TypeReference p0Tr = methodDef.Parameters[0].ParameterType;
 | |
|                 TypeReference p1Tr = methodDef.Parameters[0].ParameterType;
 | |
|                 //Not the same types.
 | |
|                 if (p0Tr != p1Tr)
 | |
|                 {
 | |
|                     base.LogError($"Both parameters must be the same type in comparer method {methodDef.Name} in type {typeDef.FullName}.");
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 base.ImportReference(methodDef);
 | |
|                 base.ImportReference(p0Tr);
 | |
|                 gh.RegisterComparerDelegate(methodDef, p0Tr);
 | |
|                 gh.CreateComparerDelegate(methodDef, p0Tr);
 | |
|             }
 | |
| 
 | |
|             return modified;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks if instructions need to be modified and does so.
 | |
|         /// </summary>
 | |
|         /// <param name="methodDef"></param>
 | |
|         /// <param name="instructionIndex"></param>
 | |
|         private void CheckToModifyInstructions(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
 | |
|         {
 | |
|             Instruction instruction = methodDef.Body.Instructions[instructionIndex];
 | |
|             //Fields.
 | |
|             if (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld)
 | |
|                 CheckFieldReferenceInstruction(extensionType, methodDef, ref instructionIndex);
 | |
|             //Method calls.
 | |
|             else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt)
 | |
|                 CheckCallInstruction(extensionType, methodDef, ref instructionIndex, (MethodReference)instruction.Operand);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks if a reader or writer must be generated for a field type.
 | |
|         /// </summary>
 | |
|         /// <param name="methodDef"></param>
 | |
|         /// <param name="instructionIndex"></param>
 | |
|         private void CheckFieldReferenceInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
 | |
|         {
 | |
|             Instruction instruction = methodDef.Body.Instructions[instructionIndex];
 | |
|             FieldReference field = (FieldReference)instruction.Operand;
 | |
|             TypeReference typeRef = field.DeclaringType;
 | |
| 
 | |
|             if (typeRef.IsType(typeof(GenericWriter<>)) || typeRef.IsType(typeof(GenericReader<>)) && typeRef.IsGenericInstance)
 | |
|             {
 | |
|                 GenericInstanceType typeGenericInst = (GenericInstanceType)typeRef;
 | |
|                 TypeReference parameterType = typeGenericInst.GenericArguments[0];
 | |
|                 CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks if a reader or writer must be generated for a call type.
 | |
|         /// </summary>
 | |
|         /// <param name="extensionType"></param>
 | |
|         /// <param name="moduleDef"></param>
 | |
|         /// <param name="methodDef"></param>
 | |
|         /// <param name="instructionIndex"></param>
 | |
|         /// <param name="method"></param>
 | |
|         private void CheckCallInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, MethodReference method)
 | |
|         {
 | |
|             if (!method.IsGenericInstance)
 | |
|                 return;
 | |
| 
 | |
|             //True if call is to read/write.
 | |
|             bool canCreate = (
 | |
|                 method.Is<Writer>(nameof(Writer.Write)) ||
 | |
|                 method.Is<Reader>(nameof(Reader.Read))
 | |
|                 );
 | |
| 
 | |
|             if (canCreate)
 | |
|             {
 | |
|                 GenericInstanceMethod instanceMethod = (GenericInstanceMethod)method;
 | |
|                 TypeReference parameterType = instanceMethod.GenericArguments[0];
 | |
|                 if (parameterType.IsGenericParameter)
 | |
|                     return;
 | |
| 
 | |
|                 CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates a reader or writer for parameterType if needed. Otherwise calls existing reader.
 | |
|         /// </summary>
 | |
|         private void CreateReaderOrWriter(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, TypeReference parameterType)
 | |
|         {
 | |
|             if (!parameterType.IsGenericParameter && parameterType.CanBeResolved(base.Session))
 | |
|             {
 | |
|                 TypeDefinition typeDefinition = parameterType.CachedResolve(base.Session);
 | |
|                 //If class and not value type check for accessible constructor.
 | |
|                 if (typeDefinition.IsClass && !typeDefinition.IsValueType)
 | |
|                 {
 | |
|                     MethodDefinition constructor = typeDefinition.GetMethod(".ctor");
 | |
|                     //Constructor is inaccessible, cannot create serializer for type.
 | |
|                     if (!constructor.IsPublic)
 | |
|                     {
 | |
|                         base.LogError($"Unable to generator serializers for {typeDefinition.FullName} because it's constructor is not public.");
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 ILProcessor processor = methodDef.Body.GetILProcessor();
 | |
| 
 | |
|                 //Find already existing read or write method.
 | |
|                 MethodReference createdMethodRef = (extensionType == ExtensionType.Write) ?
 | |
|                     base.GetClass<WriterProcessor>().GetWriteMethodReference(parameterType) :
 | |
|                     base.GetClass<ReaderProcessor>().GetReadMethodReference(parameterType);
 | |
|                 //If a created method already exist nothing further is required.
 | |
|                 if (createdMethodRef != null)
 | |
|                 {
 | |
|                     TryInsertAutoPack(ref instructionIndex);
 | |
|                     //Replace call to generic with already made serializer.
 | |
|                     Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
 | |
|                     methodDef.Body.Instructions[instructionIndex] = newInstruction;                    
 | |
|                     return;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     createdMethodRef = (extensionType == ExtensionType.Write) ?
 | |
|                         base.GetClass<WriterProcessor>().CreateWriter(parameterType) :
 | |
|                         base.GetClass<ReaderProcessor>().CreateReader(parameterType);
 | |
|                 }
 | |
| 
 | |
|                 //If method was created.
 | |
|                 if (createdMethodRef != null)
 | |
|                 {
 | |
|                     TryInsertAutoPack(ref instructionIndex);
 | |
|                     //Set new instruction.
 | |
|                     Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
 | |
|                     methodDef.Body.Instructions[instructionIndex] = newInstruction;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             void TryInsertAutoPack(ref int insertIndex)
 | |
|             {
 | |
|                 if (IsAutoPackMethod(parameterType, extensionType))
 | |
|                 {
 | |
|                     ILProcessor processor = methodDef.Body.GetILProcessor();
 | |
|                     AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(parameterType);
 | |
|                     Instruction autoPack = processor.Create(OpCodes.Ldc_I4, (int)packType);
 | |
|                     methodDef.Body.Instructions.Insert(insertIndex, autoPack);
 | |
|                     insertIndex++;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if a typeRef serializer requires or uses autopacktype.
 | |
|         /// </summary>
 | |
|         private bool IsAutoPackMethod(TypeReference typeRef, ExtensionType extensionType)
 | |
|         {
 | |
|             if (extensionType == ExtensionType.Write)
 | |
|                 return base.GetClass<WriterProcessor>().IsAutoPackedType(typeRef);
 | |
|             else if (extensionType == ExtensionType.Read)
 | |
|                 return base.GetClass<ReaderProcessor>().IsAutoPackedType(typeRef);
 | |
|             else
 | |
|                 return false;
 | |
| 
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Returns the RPC attribute on a method, if one exist. Otherwise returns null.
 | |
|         /// </summary>
 | |
|         /// <param name="methodDef"></param>
 | |
|         /// <returns></returns>
 | |
|         private ExtensionType GetExtensionType(MethodDefinition methodDef)
 | |
|         {
 | |
|             bool hasExtensionAttribute = methodDef.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>();
 | |
|             if (!hasExtensionAttribute)
 | |
|                 return ExtensionType.None;
 | |
| 
 | |
|             bool write = (methodDef.ReturnType == methodDef.Module.TypeSystem.Void);
 | |
| 
 | |
|             //Return None for Mirror types.
 | |
| #if MIRROR
 | |
|             if (write)
 | |
|             {
 | |
|                 if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkWriter")
 | |
|                     return ExtensionType.None;                    
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkReader")
 | |
|                     return ExtensionType.None;
 | |
|             }
 | |
| #endif
 | |
| 
 | |
| 
 | |
|             string prefix = (write) ? WriterProcessor.WRITE_PREFIX : ReaderProcessor.READ_PREFIX;
 | |
| 
 | |
|             //Does not contain prefix.
 | |
|             if (methodDef.Name.Length < prefix.Length || methodDef.Name.Substring(0, prefix.Length) != prefix)
 | |
|                 return ExtensionType.None;
 | |
| 
 | |
|             //Make sure first parameter is right.
 | |
|             if (methodDef.Parameters.Count >= 1)
 | |
|             {
 | |
|                 TypeReference tr = methodDef.Parameters[0].ParameterType;
 | |
|                 if (tr.FullName != base.GetClass<WriterImports>().Writer_TypeRef.FullName &&
 | |
|                     tr.FullName != base.GetClass<ReaderImports>().Reader_TypeRef.FullName)
 | |
|                     return ExtensionType.None;
 | |
|             }
 | |
| 
 | |
|             if (write && methodDef.Parameters.Count < 2)
 | |
|             {
 | |
|                 base.LogError($"{methodDef.FullName} must have at least two parameters, the first being PooledWriter, and second value to write.");
 | |
|                 return ExtensionType.None;
 | |
|             }
 | |
|             else if (!write && methodDef.Parameters.Count < 1)
 | |
|             {
 | |
|                 base.LogError($"{methodDef.FullName} must have at least one parameters, the first being PooledReader.");
 | |
|                 return ExtensionType.None;
 | |
|             }
 | |
| 
 | |
|             return (write) ? ExtensionType.Write : ExtensionType.Read;
 | |
|         }
 | |
| 
 | |
| 
 | |
|     }
 | |
| } |