123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- using System;
- using System.Collections.Generic;
- using Mono.CecilX;
- using Mono.CecilX.Cil;
- // to use Mono.CecilX.Rocks here, we need to 'override references' in the
- // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
- // otherwise we get an unknown import exception.
- using Mono.CecilX.Rocks;
- namespace Mirror.Weaver
- {
- // not static, because ILPostProcessor is multithreaded
- public class Writers
- {
- // Writers are only for this assembly.
- // can't be used from another assembly, otherwise we will get:
- // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
- AssemblyDefinition assembly;
- WeaverTypes weaverTypes;
- TypeDefinition GeneratedCodeClass;
- Logger Log;
- Dictionary<TypeReference, MethodReference> writeFuncs =
- new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
- public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
- {
- this.assembly = assembly;
- this.weaverTypes = weaverTypes;
- this.GeneratedCodeClass = GeneratedCodeClass;
- this.Log = Log;
- }
- public void Register(TypeReference dataType, MethodReference methodReference)
- {
- if (writeFuncs.ContainsKey(dataType))
- {
- // TODO enable this again later.
- // Writer has some obsolete functions that were renamed.
- // Don't want weaver warnings for all of them.
- //Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
- }
- // we need to import type when we Initialize Writers so import here in case it is used anywhere else
- TypeReference imported = assembly.MainModule.ImportReference(dataType);
- writeFuncs[imported] = methodReference;
- }
- void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
- {
- Register(typeReference, newWriterFunc);
- GeneratedCodeClass.Methods.Add(newWriterFunc);
- }
- // Finds existing writer for type, if non exists trys to create one
- public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
- {
- if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
- return foundFunc;
- // this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
- try
- {
- TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
- return GenerateWriter(importedVariable, ref WeavingFailed);
- }
- catch (GenerateWriterException e)
- {
- Log.Error(e.Message, e.MemberReference);
- WeavingFailed = true;
- return null;
- }
- }
- //Throws GenerateWriterException when writer could not be generated for type
- MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
- {
- if (variableReference.IsByReference)
- {
- throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
- }
- // Arrays are special, if we resolve them, we get the element type,
- // e.g. int[] resolves to int
- // therefore process this before checks below
- if (variableReference.IsArray)
- {
- if (variableReference.IsMultidimensionalArray())
- {
- throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
- }
- TypeReference elementType = variableReference.GetElementType();
- return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
- }
- if (variableReference.Resolve()?.IsEnum ?? false)
- {
- // serialize enum as their base type
- return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
- }
- // check for collections
- if (variableReference.Is(typeof(ArraySegment<>)))
- {
- GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
- TypeReference elementType = genericInstance.GenericArguments[0];
- return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
- }
- if (variableReference.Is(typeof(List<>)))
- {
- GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
- TypeReference elementType = genericInstance.GenericArguments[0];
- return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
- }
- if (variableReference.IsDerivedFrom<NetworkBehaviour>())
- {
- return GetNetworkBehaviourWriter(variableReference);
- }
- // check for invalid types
- TypeDefinition variableDefinition = variableReference.Resolve();
- if (variableDefinition == null)
- {
- throw new GenerateWriterException($"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
- {
- throw new GenerateWriterException($"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableReference.Is<UnityEngine.Object>())
- {
- throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableReference.Is<UnityEngine.ScriptableObject>())
- {
- throw new GenerateWriterException($"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableDefinition.HasGenericParameters)
- {
- throw new GenerateWriterException($"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableDefinition.IsInterface)
- {
- throw new GenerateWriterException($"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- if (variableDefinition.IsAbstract)
- {
- throw new GenerateWriterException($"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer", variableReference);
- }
- // generate writer for class/struct
- return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
- }
- MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
- {
- // all NetworkBehaviours can use the same write function
- if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out MethodReference func))
- {
- // register function so it is added to writer<T>
- // use Register instead of RegisterWriteFunc because this is not a generated function
- Register(variableReference, func);
- return func;
- }
- else
- {
- // this exception only happens if mirror is missing the WriteNetworkBehaviour method
- throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
- }
- }
- MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
- {
- MethodDefinition writerFunc = GenerateWriterFunc(variable);
- ILProcessor worker = writerFunc.Body.GetILProcessor();
- MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldarg_1);
- worker.Emit(OpCodes.Call, underlyingWriter);
- worker.Emit(OpCodes.Ret);
- return writerFunc;
- }
- MethodDefinition GenerateWriterFunc(TypeReference variable)
- {
- string functionName = $"_Write_{variable.FullName}";
- // create new writer for this type
- MethodDefinition writerFunc = new MethodDefinition(functionName,
- MethodAttributes.Public |
- MethodAttributes.Static |
- MethodAttributes.HideBySig,
- weaverTypes.Import(typeof(void)));
- writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
- writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
- writerFunc.Body.InitLocals = true;
- RegisterWriteFunc(variable, writerFunc);
- return writerFunc;
- }
- MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
- {
- MethodDefinition writerFunc = GenerateWriterFunc(variable);
- ILProcessor worker = writerFunc.Body.GetILProcessor();
- if (!variable.Resolve().IsValueType)
- WriteNullCheck(worker, ref WeavingFailed);
- if (!WriteAllFields(variable, worker, ref WeavingFailed))
- return null;
- worker.Emit(OpCodes.Ret);
- return writerFunc;
- }
- void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
- {
- // if (value == null)
- // {
- // writer.WriteBoolean(false);
- // return;
- // }
- //
- Instruction labelNotNull = worker.Create(OpCodes.Nop);
- worker.Emit(OpCodes.Ldarg_1);
- worker.Emit(OpCodes.Brtrue, labelNotNull);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldc_I4_0);
- worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
- worker.Emit(OpCodes.Ret);
- worker.Append(labelNotNull);
- // write.WriteBoolean(true);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldc_I4_1);
- worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
- }
- // Find all fields in type and write them
- bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
- {
- uint fields = 0;
- foreach (FieldDefinition field in variable.FindAllPublicFields())
- {
- MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
- // need this null check till later PR when GetWriteFunc throws exception instead
- if (writeFunc == null) { return false; }
- FieldReference fieldRef = assembly.MainModule.ImportReference(field);
- fields++;
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldarg_1);
- worker.Emit(OpCodes.Ldfld, fieldRef);
- worker.Emit(OpCodes.Call, writeFunc);
- }
- return true;
- }
- MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
- {
- MethodDefinition writerFunc = GenerateWriterFunc(variable);
- MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
- MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
- // need this null check till later PR when GetWriteFunc throws exception instead
- if (elementWriteFunc == null)
- {
- Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
- WeavingFailed = true;
- return writerFunc;
- }
- ModuleDefinition module = assembly.MainModule;
- TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
- MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
- GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
- methodRef.GenericArguments.Add(elementType);
- // generates
- // reader.WriteArray<T>(array);
- ILProcessor worker = writerFunc.Body.GetILProcessor();
- worker.Emit(OpCodes.Ldarg_0); // writer
- worker.Emit(OpCodes.Ldarg_1); // collection
- worker.Emit(OpCodes.Call, methodRef); // WriteArray
- worker.Emit(OpCodes.Ret);
- return writerFunc;
- }
- // Save a delegate for each one of the writers into Writer{T}.write
- internal void InitializeWriters(ILProcessor worker)
- {
- ModuleDefinition module = assembly.MainModule;
- TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
- System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
- FieldReference fieldRef = module.ImportReference(fieldInfo);
- TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
- TypeReference actionRef = module.ImportReference(typeof(Action<,>));
- MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
- foreach (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
- {
- TypeReference targetType = kvp.Key;
- MethodReference writeFunc = kvp.Value;
- // create a Action<NetworkWriter, T> delegate
- worker.Emit(OpCodes.Ldnull);
- worker.Emit(OpCodes.Ldftn, writeFunc);
- GenericInstanceType actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
- MethodReference actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
- worker.Emit(OpCodes.Newobj, actionRefInstance);
- // save it in Writer<T>.write
- GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
- FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
- worker.Emit(OpCodes.Stsfld, specializedField);
- }
- }
- }
- }
|