mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-02-22 03:40:54 +00:00
Revert "cleanup mirrorweaver"
This reverts commit 7c5e57db544da18cf7b8eb4b9dca28c6316af333.
This commit is contained in:
parent
1991c681eb
commit
0a5970ebdc
@ -12,10 +12,7 @@ namespace MirrorWeaver
|
|||||||
|
|
||||||
public void Warning(string message, MemberReference mr)
|
public void Warning(string message, MemberReference mr)
|
||||||
{
|
{
|
||||||
if (mr != null)
|
if (mr != null) message = $"{message} (at {mr})";
|
||||||
{
|
|
||||||
message = $"{message} (at {mr})";
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
}
|
}
|
||||||
@ -24,10 +21,7 @@ namespace MirrorWeaver
|
|||||||
|
|
||||||
public void Error(string message, MemberReference mr)
|
public void Error(string message, MemberReference mr)
|
||||||
{
|
{
|
||||||
if (mr != null)
|
if (mr != null) message = $"{message} (at {mr})";
|
||||||
{
|
|
||||||
message = $"{message} (at {mr})";
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Error.WriteLine(message);
|
Console.Error.WriteLine(message);
|
||||||
}
|
}
|
||||||
|
@ -19,27 +19,17 @@ namespace MirrorWeaver
|
|||||||
|
|
||||||
foreach (var type in assembly.MainModule.GetTypes())
|
foreach (var type in assembly.MainModule.GetTypes())
|
||||||
{
|
{
|
||||||
if (type.HasGenericParameters)
|
if (type.HasGenericParameters) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeReference currentType = type;
|
TypeReference currentType = type;
|
||||||
while (currentType != null)
|
while (currentType != null)
|
||||||
{
|
{
|
||||||
foreach (var method in currentType.Resolve().Methods)
|
foreach (var method in currentType.Resolve().Methods)
|
||||||
{
|
{
|
||||||
if (!method.HasBody)
|
if (!method.HasBody) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var instruction in method.Body.Instructions)
|
foreach (var instruction in method.Body.Instructions)
|
||||||
{
|
{
|
||||||
if (instruction.Operand is not GenericInstanceMethod calledMethod)
|
if (instruction.Operand is not GenericInstanceMethod calledMethod) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calledMethod.DeclaringType.Name == NetworkWriter_Write.DeclaringType.Name &&
|
if (calledMethod.DeclaringType.Name == NetworkWriter_Write.DeclaringType.Name &&
|
||||||
calledMethod.Name == NetworkWriter_Write.Name)
|
calledMethod.Name == NetworkWriter_Write.Name)
|
||||||
@ -47,9 +37,7 @@ namespace MirrorWeaver
|
|||||||
var argType = calledMethod.GenericArguments[0];
|
var argType = calledMethod.GenericArguments[0];
|
||||||
|
|
||||||
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
|
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
|
||||||
{
|
|
||||||
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
|
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
|
||||||
}
|
|
||||||
|
|
||||||
writers.GetWriteFunc(argType, ref weavingFailed);
|
writers.GetWriteFunc(argType, ref weavingFailed);
|
||||||
}
|
}
|
||||||
@ -59,9 +47,7 @@ namespace MirrorWeaver
|
|||||||
var argType = calledMethod.GenericArguments[0];
|
var argType = calledMethod.GenericArguments[0];
|
||||||
|
|
||||||
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
|
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentType.Resolve())
|
||||||
{
|
|
||||||
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
|
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
|
||||||
}
|
|
||||||
|
|
||||||
readers.GetReadFunc(argType, ref weavingFailed);
|
readers.GetReadFunc(argType, ref weavingFailed);
|
||||||
}
|
}
|
||||||
|
@ -1,283 +1,263 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
public static bool Is(this TypeReference td, Type t)
|
public static bool Is(this TypeReference td, Type t)
|
||||||
{
|
{
|
||||||
if (t.IsGenericType)
|
if (t.IsGenericType)
|
||||||
{
|
{
|
||||||
return td.GetElementType().FullName == t.FullName;
|
return td.GetElementType().FullName == t.FullName;
|
||||||
}
|
}
|
||||||
return td.FullName == t.FullName;
|
return td.FullName == t.FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
|
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
|
||||||
|
|
||||||
public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T));
|
public static bool IsDerivedFrom<T>(this TypeReference tr) => IsDerivedFrom(tr, typeof(T));
|
||||||
|
|
||||||
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
|
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
|
||||||
{
|
{
|
||||||
if (tr == null)
|
if (tr == null)
|
||||||
{
|
return false;
|
||||||
return false;
|
TypeDefinition td = tr.Resolve();
|
||||||
}
|
if (td == null)
|
||||||
|
return false;
|
||||||
|
if (!td.IsClass)
|
||||||
|
return false;
|
||||||
|
|
||||||
var td = tr.Resolve();
|
// are ANY parent classes of baseClass?
|
||||||
if (td == null)
|
TypeReference parent = td.BaseType;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!td.IsClass)
|
if (parent == null)
|
||||||
{
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// are ANY parent classes of baseClass?
|
if (parent.Is(baseClass))
|
||||||
var parent = td.BaseType;
|
return true;
|
||||||
|
|
||||||
if (parent == null)
|
if (parent.CanBeResolved())
|
||||||
{
|
return IsDerivedFrom(parent.Resolve(), baseClass);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.Is(baseClass))
|
return false;
|
||||||
{
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent.CanBeResolved())
|
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
|
||||||
{
|
{
|
||||||
return IsDerivedFrom(parent.Resolve(), baseClass);
|
foreach (FieldDefinition field in td.Fields)
|
||||||
}
|
{
|
||||||
|
if (!field.IsStatic)
|
||||||
|
return field.FieldType;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid enum {td.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
public static bool ImplementsInterface<TInterface>(this TypeDefinition td)
|
||||||
}
|
{
|
||||||
|
TypeDefinition typedef = td;
|
||||||
|
|
||||||
public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
|
while (typedef != null)
|
||||||
{
|
{
|
||||||
foreach (var field in td.Fields)
|
if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
|
||||||
{
|
return true;
|
||||||
if (!field.IsStatic)
|
|
||||||
{
|
|
||||||
return field.FieldType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ArgumentException($"Invalid enum {td.FullName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ImplementsInterface<TInterface>(this TypeDefinition td)
|
try
|
||||||
{
|
{
|
||||||
var typedef = td;
|
TypeReference parent = typedef.BaseType;
|
||||||
|
typedef = parent?.Resolve();
|
||||||
|
}
|
||||||
|
catch (AssemblyResolutionException)
|
||||||
|
{
|
||||||
|
// this can happen for plugins.
|
||||||
|
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (typedef != null)
|
return false;
|
||||||
{
|
}
|
||||||
if (typedef.Interfaces.Any(iface => iface.InterfaceType.Is<TInterface>()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
public static bool IsMultidimensionalArray(this TypeReference tr) =>
|
||||||
{
|
tr is ArrayType arrayType && arrayType.Rank > 1;
|
||||||
var parent = typedef.BaseType;
|
|
||||||
typedef = parent?.Resolve();
|
|
||||||
}
|
|
||||||
catch (AssemblyResolutionException)
|
|
||||||
{
|
|
||||||
// this can happen for plugins.
|
|
||||||
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// Does type use netId as backing field
|
||||||
}
|
public static bool IsNetworkIdentityField(this TypeReference tr) =>
|
||||||
|
tr.Is<UnityEngine.GameObject>() ||
|
||||||
|
tr.Is<NetworkIdentity>() ||
|
||||||
|
tr.IsDerivedFrom<NetworkBehaviour>();
|
||||||
|
|
||||||
public static bool IsMultidimensionalArray(this TypeReference tr) =>
|
public static bool CanBeResolved(this TypeReference parent)
|
||||||
tr is ArrayType arrayType && arrayType.Rank > 1;
|
{
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
if (parent.Scope.Name == "Windows")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Does type use netId as backing field
|
if (parent.Scope.Name == "mscorlib")
|
||||||
public static bool IsNetworkIdentityField(this TypeReference tr) =>
|
{
|
||||||
tr.Is<UnityEngine.GameObject>() ||
|
TypeDefinition resolved = parent.Resolve();
|
||||||
tr.Is<NetworkIdentity>() ||
|
return resolved != null;
|
||||||
tr.IsDerivedFrom<NetworkBehaviour>();
|
}
|
||||||
|
|
||||||
public static bool CanBeResolved(this TypeReference parent)
|
try
|
||||||
{
|
{
|
||||||
while (parent != null)
|
parent = parent.Resolve().BaseType;
|
||||||
{
|
}
|
||||||
if (parent.Scope.Name == "Windows")
|
catch
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (parent.Scope.Name == "mscorlib")
|
// Makes T => Variable and imports function
|
||||||
{
|
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
|
||||||
var resolved = parent.Resolve();
|
{
|
||||||
return resolved != null;
|
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
|
||||||
}
|
instance.GenericArguments.Add(variableReference);
|
||||||
|
|
||||||
try
|
MethodReference readFunc = module.ImportReference(instance);
|
||||||
{
|
return readFunc;
|
||||||
parent = parent.Resolve().BaseType;
|
}
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Makes T => Variable and imports function
|
// Given a method of a generic class such as ArraySegment`T.get_Count,
|
||||||
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
|
// and a generic instance such as ArraySegment`int
|
||||||
{
|
// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
||||||
var instance = new GenericInstanceMethod(generic);
|
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
|
||||||
instance.GenericArguments.Add(variableReference);
|
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
||||||
|
{
|
||||||
|
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
|
||||||
|
{
|
||||||
|
CallingConvention = self.CallingConvention,
|
||||||
|
HasThis = self.HasThis,
|
||||||
|
ExplicitThis = self.ExplicitThis
|
||||||
|
};
|
||||||
|
|
||||||
var readFunc = module.ImportReference(instance);
|
foreach (ParameterDefinition parameter in self.Parameters)
|
||||||
return readFunc;
|
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
||||||
}
|
|
||||||
|
|
||||||
// Given a method of a generic class such as ArraySegment`T.get_Count,
|
foreach (GenericParameter generic_parameter in self.GenericParameters)
|
||||||
// and a generic instance such as ArraySegment`int
|
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
|
||||||
// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
|
||||||
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
|
|
||||||
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
|
||||||
{
|
|
||||||
var reference = new MethodReference(self.Name, self.ReturnType, instanceType)
|
|
||||||
{
|
|
||||||
CallingConvention = self.CallingConvention,
|
|
||||||
HasThis = self.HasThis,
|
|
||||||
ExplicitThis = self.ExplicitThis
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var parameter in self.Parameters)
|
return module.ImportReference(reference);
|
||||||
{
|
}
|
||||||
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var generic_parameter in self.GenericParameters)
|
// Given a field of a generic class such as Writer<T>.write,
|
||||||
{
|
// and a generic instance such as ArraySegment`int
|
||||||
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
|
// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
||||||
}
|
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
|
||||||
|
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
||||||
|
{
|
||||||
|
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
|
||||||
|
return module.ImportReference(reference);
|
||||||
|
}
|
||||||
|
|
||||||
return module.ImportReference(reference);
|
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method)
|
||||||
}
|
{
|
||||||
|
return method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
// Given a field of a generic class such as Writer<T>.write,
|
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider)
|
||||||
// and a generic instance such as ArraySegment`int
|
{
|
||||||
// Creates a reference to the specialized method ArraySegment`int`.get_Count
|
return attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
|
||||||
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
|
}
|
||||||
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
|
|
||||||
{
|
|
||||||
var reference = new FieldReference(self.Name, self.FieldType, instanceType);
|
|
||||||
return module.ImportReference(reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider method) => method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Is<TAttribute>());
|
public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue)
|
||||||
|
{
|
||||||
|
foreach (CustomAttributeNamedArgument customField in ca.Fields)
|
||||||
|
if (customField.Name == field)
|
||||||
|
return (T)customField.Argument.Value;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool HasCustomAttribute<TAttribute>(this ICustomAttributeProvider attributeProvider) => attributeProvider.CustomAttributes.Any(attr => attr.AttributeType.Is<TAttribute>());
|
public static MethodDefinition GetMethod(this TypeDefinition td, string methodName)
|
||||||
|
{
|
||||||
|
return td.Methods.FirstOrDefault(method => method.Name == methodName);
|
||||||
|
}
|
||||||
|
|
||||||
public static T GetField<T>(this CustomAttribute ca, string field, T defaultValue)
|
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName)
|
||||||
{
|
{
|
||||||
foreach (var customField in ca.Fields)
|
return td.Methods.Where(method => method.Name == methodName).ToList();
|
||||||
{
|
}
|
||||||
if (customField.Name == field)
|
|
||||||
{
|
|
||||||
return (T)customField.Argument.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName)
|
||||||
}
|
{
|
||||||
|
TypeDefinition typedef = td;
|
||||||
|
while (typedef != null)
|
||||||
|
{
|
||||||
|
foreach (MethodDefinition md in typedef.Methods)
|
||||||
|
{
|
||||||
|
if (md.Name == methodName)
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
public static MethodDefinition GetMethod(this TypeDefinition td, string methodName) => td.Methods.FirstOrDefault(method => method.Name == methodName);
|
try
|
||||||
|
{
|
||||||
|
TypeReference parent = typedef.BaseType;
|
||||||
|
typedef = parent?.Resolve();
|
||||||
|
}
|
||||||
|
catch (AssemblyResolutionException)
|
||||||
|
{
|
||||||
|
// this can happen for plugins.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static List<MethodDefinition> GetMethods(this TypeDefinition td, string methodName) => td.Methods.Where(method => method.Name == methodName).ToList();
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, string methodName)
|
// Finds public fields in type and base type
|
||||||
{
|
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
|
||||||
var typedef = td;
|
{
|
||||||
while (typedef != null)
|
return FindAllPublicFields(variable.Resolve());
|
||||||
{
|
}
|
||||||
foreach (var md in typedef.Methods)
|
|
||||||
{
|
|
||||||
if (md.Name == methodName)
|
|
||||||
{
|
|
||||||
return md;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
// Finds public fields in type and base type
|
||||||
{
|
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
|
||||||
var parent = typedef.BaseType;
|
{
|
||||||
typedef = parent?.Resolve();
|
while (typeDefinition != null)
|
||||||
}
|
{
|
||||||
catch (AssemblyResolutionException)
|
foreach (FieldDefinition field in typeDefinition.Fields)
|
||||||
{
|
{
|
||||||
// this can happen for plugins.
|
if (field.IsStatic || field.IsPrivate)
|
||||||
break;
|
continue;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
if (field.IsNotSerialized)
|
||||||
}
|
continue;
|
||||||
|
|
||||||
// Finds public fields in type and base type
|
yield return field;
|
||||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable) => FindAllPublicFields(variable.Resolve());
|
}
|
||||||
|
|
||||||
// Finds public fields in type and base type
|
try
|
||||||
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
|
{
|
||||||
{
|
typeDefinition = typeDefinition.BaseType?.Resolve();
|
||||||
while (typeDefinition != null)
|
}
|
||||||
{
|
catch (AssemblyResolutionException)
|
||||||
foreach (var field in typeDefinition.Fields)
|
{
|
||||||
{
|
break;
|
||||||
if (field.IsStatic || field.IsPrivate)
|
}
|
||||||
{
|
}
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (field.IsNotSerialized)
|
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
|
||||||
{
|
module.GetTypes().Any(td => td.Namespace == nameSpace &&
|
||||||
continue;
|
td.Name == className);
|
||||||
}
|
|
||||||
|
|
||||||
yield return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
typeDefinition = typeDefinition.BaseType?.Resolve();
|
|
||||||
}
|
|
||||||
catch (AssemblyResolutionException)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
|
|
||||||
module.GetTypes().Any(td => td.Namespace == nameSpace &&
|
|
||||||
td.Name == className);
|
|
||||||
|
|
||||||
|
|
||||||
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
|
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
|
||||||
{
|
{
|
||||||
foreach (var reference in module.AssemblyReferences)
|
foreach (AssemblyNameReference reference in module.AssemblyReferences)
|
||||||
{
|
{
|
||||||
if (reference.Name == referenceName)
|
if (reference.Name == referenceName)
|
||||||
{
|
return reference;
|
||||||
return reference;
|
}
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
internal static class Helpers
|
static class Helpers
|
||||||
{
|
{
|
||||||
// This code is taken from SerializationWeaver
|
// This code is taken from SerializationWeaver
|
||||||
public static string UnityEngineDllDirectoryName()
|
public static string UnityEngineDllDirectoryName()
|
||||||
{
|
{
|
||||||
var directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
|
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
|
||||||
return directoryName?.Replace(@"file:\", "");
|
return directoryName?.Replace(@"file:\", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsEditorAssembly(AssemblyDefinition currentAssembly) =>
|
public static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
|
||||||
// we want to add the [InitializeOnLoad] attribute if it's available
|
{
|
||||||
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
|
// we want to add the [InitializeOnLoad] attribute if it's available
|
||||||
currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
// -> usually either 'UnityEditor' or 'UnityEditor.CoreModule'
|
||||||
assemblyReference.Name.StartsWith(nameof(UnityEditor))
|
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
||||||
);
|
assemblyReference.Name.StartsWith(nameof(UnityEditor))
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ using Mono.Cecil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
public interface Logger
|
public interface Logger
|
||||||
{
|
{
|
||||||
void Warning(string message);
|
void Warning(string message);
|
||||||
void Warning(string message, MemberReference mr);
|
void Warning(string message, MemberReference mr);
|
||||||
void Error(string message);
|
void Error(string message);
|
||||||
void Error(string message, MemberReference mr);
|
void Error(string message, MemberReference mr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// Processes [Command] methods in NetworkBehaviour
|
// Processes [Command] methods in NetworkBehaviour
|
||||||
public static class CommandProcessor
|
public static class CommandProcessor
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
// generates code like:
|
// generates code like:
|
||||||
public void CmdThrust(float thrusting, int spin)
|
public void CmdThrust(float thrusting, int spin)
|
||||||
{
|
{
|
||||||
@ -29,45 +29,43 @@ namespace Mirror.Weaver
|
|||||||
This way we do not need to modify the code anywhere else, and this works
|
This way we do not need to modify the code anywhere else, and this works
|
||||||
correctly in dependent assemblies
|
correctly in dependent assemblies
|
||||||
*/
|
*/
|
||||||
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
|
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||||
|
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||||
|
|
||||||
// NetworkWriter writer = new NetworkWriter();
|
// NetworkWriter writer = new NetworkWriter();
|
||||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||||
|
|
||||||
// write all the arguments that the user passed to the Cmd call
|
// write all the arguments that the user passed to the Cmd call
|
||||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel = commandAttr.GetField("channel", 0);
|
int channel = commandAttr.GetField("channel", 0);
|
||||||
var requiresAuthority = commandAttr.GetField("requiresAuthority", true);
|
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
|
||||||
|
|
||||||
// invoke internal send and return
|
// invoke internal send and return
|
||||||
// load 'base.' to call the SendCommand function with
|
// load 'base.' to call the SendCommand function with
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||||
// writer
|
// writer
|
||||||
worker.Emit(OpCodes.Ldloc_0);
|
worker.Emit(OpCodes.Ldloc_0);
|
||||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||||
// requiresAuthority ? 1 : 0
|
// requiresAuthority ? 1 : 0
|
||||||
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
|
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// generates code like:
|
// generates code like:
|
||||||
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
||||||
{
|
{
|
||||||
@ -78,49 +76,47 @@ namespace Mirror.Weaver
|
|||||||
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
|
public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
||||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||||
weaverTypes.Import(typeof(void)));
|
weaverTypes.Import(typeof(void)));
|
||||||
|
|
||||||
var worker = cmd.Body.GetILProcessor();
|
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||||
var label = worker.Create(OpCodes.Nop);
|
Instruction label = worker.Create(OpCodes.Nop);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
|
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
|
||||||
|
|
||||||
// setup for reader
|
// setup for reader
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Castclass, td);
|
worker.Emit(OpCodes.Castclass, td);
|
||||||
|
|
||||||
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddSenderConnection(method, worker);
|
AddSenderConnection(method, worker);
|
||||||
|
|
||||||
// invoke actual command function
|
// invoke actual command function
|
||||||
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
|
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
|
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
|
||||||
|
|
||||||
td.Methods.Add(cmd);
|
td.Methods.Add(cmd);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
||||||
{
|
{
|
||||||
foreach (var param in method.Parameters)
|
foreach (ParameterDefinition param in method.Parameters)
|
||||||
{
|
{
|
||||||
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
||||||
{
|
{
|
||||||
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
||||||
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||||
worker.Emit(OpCodes.Ldarg_2);
|
worker.Emit(OpCodes.Ldarg_2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,138 +3,128 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class MethodProcessor
|
public static class MethodProcessor
|
||||||
{
|
{
|
||||||
private const string RpcPrefix = "UserCode_";
|
const string RpcPrefix = "UserCode_";
|
||||||
|
|
||||||
// creates a method substitute
|
// creates a method substitute
|
||||||
// For example, if we have this:
|
// For example, if we have this:
|
||||||
// public void CmdThrust(float thrusting, int spin)
|
// public void CmdThrust(float thrusting, int spin)
|
||||||
// {
|
// {
|
||||||
// xxxxx
|
// xxxxx
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// it will substitute the method and move the code to a new method with a provided name
|
// it will substitute the method and move the code to a new method with a provided name
|
||||||
// for example:
|
// for example:
|
||||||
//
|
//
|
||||||
// public void CmdTrust(float thrusting, int spin)
|
// public void CmdTrust(float thrusting, int spin)
|
||||||
// {
|
// {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// public void <newName>(float thrusting, int spin)
|
// public void <newName>(float thrusting, int spin)
|
||||||
// {
|
// {
|
||||||
// xxxxx
|
// xxxxx
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Note that all the calls to the method remain untouched
|
// Note that all the calls to the method remain untouched
|
||||||
//
|
//
|
||||||
// the original method definition loses all code
|
// the original method definition loses all code
|
||||||
// this returns the newly created method with all the user provided code
|
// this returns the newly created method with all the user provided code
|
||||||
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
|
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var newName = RpcPrefix + md.Name;
|
string newName = RpcPrefix + md.Name;
|
||||||
var cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType)
|
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||||
{
|
|
||||||
|
|
||||||
// force the substitute method to be protected.
|
// force the substitute method to be protected.
|
||||||
// -> public would show in the Inspector for UnityEvents as
|
// -> public would show in the Inspector for UnityEvents as
|
||||||
// User_CmdUsePotion() etc. but the user shouldn't use those.
|
// User_CmdUsePotion() etc. but the user shouldn't use those.
|
||||||
// -> private would not allow inheriting classes to call it, see
|
// -> private would not allow inheriting classes to call it, see
|
||||||
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
|
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
|
||||||
// -> IL has no concept of 'protected', it's called IsFamily there.
|
// -> IL has no concept of 'protected', it's called IsFamily there.
|
||||||
IsPublic = false,
|
cmd.IsPublic = false;
|
||||||
IsFamily = true
|
cmd.IsFamily = true;
|
||||||
};
|
|
||||||
|
|
||||||
// add parameters
|
// add parameters
|
||||||
foreach (var pd in md.Parameters)
|
foreach (ParameterDefinition pd in md.Parameters)
|
||||||
{
|
{
|
||||||
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
||||||
}
|
}
|
||||||
|
|
||||||
// swap bodies
|
// swap bodies
|
||||||
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
||||||
|
|
||||||
// Move over all the debugging information
|
// Move over all the debugging information
|
||||||
foreach (var sequencePoint in md.DebugInformation.SequencePoints)
|
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
|
||||||
{
|
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
||||||
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
md.DebugInformation.SequencePoints.Clear();
|
||||||
}
|
|
||||||
|
|
||||||
md.DebugInformation.SequencePoints.Clear();
|
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
|
||||||
|
cmd.CustomDebugInformations.Add(customInfo);
|
||||||
|
md.CustomDebugInformations.Clear();
|
||||||
|
|
||||||
foreach (var customInfo in md.CustomDebugInformations)
|
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
||||||
{
|
|
||||||
cmd.CustomDebugInformations.Add(customInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
md.CustomDebugInformations.Clear();
|
td.Methods.Add(cmd);
|
||||||
|
|
||||||
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
td.Methods.Add(cmd);
|
// Finds and fixes call to base methods within remote calls
|
||||||
|
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`
|
||||||
|
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
string callName = method.Name;
|
||||||
|
|
||||||
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
|
// Cmd/rpc start with Weaver.RpcPrefix
|
||||||
return cmd;
|
// e.g. CallCmdDoSomething
|
||||||
}
|
if (!callName.StartsWith(RpcPrefix))
|
||||||
|
return;
|
||||||
|
|
||||||
// Finds and fixes call to base methods within remote calls
|
// e.g. CmdDoSomething
|
||||||
//For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`
|
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||||
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
|
|
||||||
{
|
|
||||||
var callName = method.Name;
|
|
||||||
|
|
||||||
// Cmd/rpc start with Weaver.RpcPrefix
|
foreach (Instruction instruction in method.Body.Instructions)
|
||||||
// e.g. CallCmdDoSomething
|
{
|
||||||
if (!callName.StartsWith(RpcPrefix))
|
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
||||||
{
|
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
|
||||||
return;
|
calledMethod.Name == baseRemoteCallName)
|
||||||
}
|
{
|
||||||
|
TypeDefinition baseType = type.BaseType.Resolve();
|
||||||
|
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||||
|
|
||||||
// e.g. CmdDoSomething
|
if (baseMethod == null)
|
||||||
var baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
{
|
||||||
|
Log.Error($"Could not find base method for {callName}", method);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var instruction in method.Body.Instructions)
|
if (!baseMethod.IsVirtual)
|
||||||
{
|
{
|
||||||
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
Log.Error($"Could not find base method that was virtual {callName}", method);
|
||||||
if (IsCallToMethod(instruction, out var calledMethod) &&
|
WeavingFailed = true;
|
||||||
calledMethod.Name == baseRemoteCallName)
|
return;
|
||||||
{
|
}
|
||||||
var baseType = type.BaseType.Resolve();
|
|
||||||
var baseMethod = baseType.GetMethodInBaseType(callName);
|
|
||||||
|
|
||||||
if (baseMethod == null)
|
instruction.Operand = baseMethod;
|
||||||
{
|
}
|
||||||
Log.Error($"Could not find base method for {callName}", method);
|
}
|
||||||
WeavingFailed = true;
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!baseMethod.IsVirtual)
|
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
||||||
{
|
{
|
||||||
Log.Error($"Could not find base method that was virtual {callName}", method);
|
if (instruction.OpCode == OpCodes.Call &&
|
||||||
WeavingFailed = true;
|
instruction.Operand is MethodDefinition method)
|
||||||
return;
|
{
|
||||||
}
|
calledMethod = method;
|
||||||
|
return true;
|
||||||
instruction.Operand = baseMethod;
|
}
|
||||||
}
|
else
|
||||||
}
|
{
|
||||||
}
|
calledMethod = null;
|
||||||
|
return false;
|
||||||
private static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
}
|
||||||
{
|
}
|
||||||
if (instruction.OpCode == OpCodes.Call &&
|
}
|
||||||
instruction.Operand is MethodDefinition method)
|
|
||||||
{
|
|
||||||
calledMethod = method;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
calledMethod = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,55 +2,55 @@ using Mono.Cecil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||||
internal static class MonoBehaviourProcessor
|
static class MonoBehaviourProcessor
|
||||||
{
|
{
|
||||||
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
public static void Process(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
ProcessSyncVars(Log, td, ref WeavingFailed);
|
ProcessSyncVars(Log, td, ref WeavingFailed);
|
||||||
ProcessMethods(Log, td, ref WeavingFailed);
|
ProcessMethods(Log, td, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
static void ProcessSyncVars(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
// find syncvars
|
// find syncvars
|
||||||
foreach (var fd in td.Fields)
|
foreach (FieldDefinition fd in td.Fields)
|
||||||
{
|
{
|
||||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||||
{
|
{
|
||||||
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
Log.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||||
{
|
{
|
||||||
Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
Log.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
static void ProcessMethods(Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
// find command and RPC functions
|
// find command and RPC functions
|
||||||
foreach (var md in td.Methods)
|
foreach (MethodDefinition md in td.Methods)
|
||||||
{
|
{
|
||||||
if (md.HasCustomAttribute<CommandAttribute>())
|
if (md.HasCustomAttribute<CommandAttribute>())
|
||||||
{
|
{
|
||||||
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
Log.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||||
{
|
{
|
||||||
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
Log.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||||
{
|
{
|
||||||
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
Log.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,242 +1,216 @@
|
|||||||
// finds all readers and writers and register them
|
// finds all readers and writers and register them
|
||||||
|
using System.Linq;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class ReaderWriterProcessor
|
public static class ReaderWriterProcessor
|
||||||
{
|
{
|
||||||
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
// find NetworkReader/Writer extensions from Mirror.dll first.
|
// find NetworkReader/Writer extensions from Mirror.dll first.
|
||||||
// and NetworkMessage custom writer/reader extensions.
|
// and NetworkMessage custom writer/reader extensions.
|
||||||
// NOTE: do not include this result in our 'modified' return value,
|
// NOTE: do not include this result in our 'modified' return value,
|
||||||
// otherwise Unity crashes when running tests
|
// otherwise Unity crashes when running tests
|
||||||
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
||||||
|
|
||||||
// find readers/writers in the assembly we are in right now.
|
// find readers/writers in the assembly we are in right now.
|
||||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
// find Mirror.dll in assembly's references.
|
// find Mirror.dll in assembly's references.
|
||||||
// those are guaranteed to be resolvable and correct.
|
// those are guaranteed to be resolvable and correct.
|
||||||
// after all, it references them :)
|
// after all, it references them :)
|
||||||
var mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
|
AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
|
||||||
if (mirrorAssemblyReference != null)
|
if (mirrorAssemblyReference != null)
|
||||||
{
|
{
|
||||||
// resolve the assembly to load the AssemblyDefinition.
|
// resolve the assembly to load the AssemblyDefinition.
|
||||||
// we need to search all types in it.
|
// we need to search all types in it.
|
||||||
// if we only were to resolve one known type like in WeaverTypes,
|
// if we only were to resolve one known type like in WeaverTypes,
|
||||||
// then we wouldn't need it.
|
// then we wouldn't need it.
|
||||||
var mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
|
AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
|
||||||
if (mirrorAssembly != null)
|
if (mirrorAssembly != null)
|
||||||
{
|
{
|
||||||
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
|
ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
else
|
else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
|
||||||
{
|
}
|
||||||
Log.Error($"Failed to resolve {mirrorAssemblyReference}");
|
else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
|
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
foreach (var klass in assembly.MainModule.Types)
|
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||||
{
|
{
|
||||||
// extension methods only live in static classes
|
// extension methods only live in static classes
|
||||||
// static classes are represented as sealed and abstract
|
// static classes are represented as sealed and abstract
|
||||||
if (klass.IsAbstract && klass.IsSealed)
|
if (klass.IsAbstract && klass.IsSealed)
|
||||||
{
|
{
|
||||||
// if assembly has any declared writers then it is "modified"
|
// if assembly has any declared writers then it is "modified"
|
||||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
|
modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
|
||||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
|
modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var klass in assembly.MainModule.Types)
|
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||||
{
|
{
|
||||||
// if assembly has any network message then it is modified
|
// if assembly has any network message then it is modified
|
||||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
|
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
|
static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||||
{
|
{
|
||||||
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
|
readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||||
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
|
writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var td in klass.NestedTypes)
|
foreach (TypeDefinition td in klass.NestedTypes)
|
||||||
{
|
{
|
||||||
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
|
modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
|
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
|
||||||
{
|
{
|
||||||
// register all the writers in this class. Skip the ones with wrong signature
|
// register all the writers in this class. Skip the ones with wrong signature
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
foreach (var method in klass.Methods)
|
foreach (MethodDefinition method in klass.Methods)
|
||||||
{
|
{
|
||||||
if (method.Parameters.Count != 2)
|
if (method.Parameters.Count != 2)
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.ReturnType.Is(typeof(void)))
|
if (!method.ReturnType.Is(typeof(void)))
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.HasGenericParameters)
|
if (method.HasGenericParameters)
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataType = method.Parameters[1].ParameterType;
|
TypeReference dataType = method.Parameters[1].ParameterType;
|
||||||
writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
|
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
|
||||||
{
|
{
|
||||||
// register all the reader in this class. Skip the ones with wrong signature
|
// register all the reader in this class. Skip the ones with wrong signature
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
foreach (var method in klass.Methods)
|
foreach (MethodDefinition method in klass.Methods)
|
||||||
{
|
{
|
||||||
if (method.Parameters.Count != 1)
|
if (method.Parameters.Count != 1)
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.ReturnType.Is(typeof(void)))
|
if (method.ReturnType.Is(typeof(void)))
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method.HasGenericParameters)
|
if (method.HasGenericParameters)
|
||||||
{
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to add [RuntimeInitializeOnLoad] attribute to method
|
// helper function to add [RuntimeInitializeOnLoad] attribute to method
|
||||||
private static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||||
{
|
{
|
||||||
// NOTE: previously we used reflection because according paul,
|
// NOTE: previously we used reflection because according paul,
|
||||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||||
// order, which breaks rewired'
|
// order, which breaks rewired'
|
||||||
// it's not obvious why importing an attribute via reflection instead
|
// it's not obvious why importing an attribute via reflection instead
|
||||||
// of cecil would break anything. let's use cecil.
|
// of cecil would break anything. let's use cecil.
|
||||||
|
|
||||||
// to add a CustomAttribute, we need the attribute's constructor.
|
// to add a CustomAttribute, we need the attribute's constructor.
|
||||||
// in this case, there are two: empty, and RuntimeInitializeOnLoadType.
|
// in this case, there are two: empty, and RuntimeInitializeOnLoadType.
|
||||||
// we want the last one, with the type parameter.
|
// we want the last one, with the type parameter.
|
||||||
var ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
|
MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
|
||||||
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
|
//MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
|
||||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||||
// we need to import it first.
|
// we need to import it first.
|
||||||
var attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||||
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
|
// add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
|
||||||
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||||
method.CustomAttributes.Add(attribute);
|
method.CustomAttributes.Add(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to add [InitializeOnLoad] attribute to method
|
// helper function to add [InitializeOnLoad] attribute to method
|
||||||
// (only works in Editor assemblies. check IsEditorAssembly first.)
|
// (only works in Editor assemblies. check IsEditorAssembly first.)
|
||||||
private static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
|
||||||
{
|
{
|
||||||
// NOTE: previously we used reflection because according paul,
|
// NOTE: previously we used reflection because according paul,
|
||||||
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
// 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
|
||||||
// order, which breaks rewired'
|
// order, which breaks rewired'
|
||||||
// it's not obvious why importing an attribute via reflection instead
|
// it's not obvious why importing an attribute via reflection instead
|
||||||
// of cecil would break anything. let's use cecil.
|
// of cecil would break anything. let's use cecil.
|
||||||
|
|
||||||
// to add a CustomAttribute, we need the attribute's constructor.
|
// to add a CustomAttribute, we need the attribute's constructor.
|
||||||
// in this case, there's only one - and it's an empty constructor.
|
// in this case, there's only one - and it's an empty constructor.
|
||||||
var ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
|
MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
|
||||||
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
// using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
|
||||||
// we need to import it first.
|
// we need to import it first.
|
||||||
var attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
|
||||||
method.CustomAttributes.Add(attribute);
|
method.CustomAttributes.Add(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
|
// adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
|
||||||
// registers all generated writers into Mirror.Writer<T> static class.
|
// registers all generated writers into Mirror.Writer<T> static class.
|
||||||
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
|
// -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
|
||||||
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
|
// -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
|
||||||
// in Editor and in tests too
|
// in Editor and in tests too
|
||||||
//
|
//
|
||||||
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
|
// use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
|
||||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
|
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
|
||||||
{
|
{
|
||||||
var initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||||
MethodAttributes.Static,
|
MethodAttributes.Static,
|
||||||
weaverTypes.Import(typeof(void)));
|
weaverTypes.Import(typeof(void)));
|
||||||
|
|
||||||
// add [RuntimeInitializeOnLoad] in any case
|
// add [RuntimeInitializeOnLoad] in any case
|
||||||
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||||
|
|
||||||
// add [InitializeOnLoad] if UnityEditor is referenced
|
// add [InitializeOnLoad] if UnityEditor is referenced
|
||||||
if (Helpers.IsEditorAssembly(currentAssembly))
|
if (Helpers.IsEditorAssembly(currentAssembly))
|
||||||
{
|
{
|
||||||
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill function body with reader/writer initializers
|
// fill function body with reader/writer initializers
|
||||||
var worker = initReadWriters.Body.GetILProcessor();
|
ILProcessor worker = initReadWriters.Body.GetILProcessor();
|
||||||
// for debugging: add a log to see if initialized on load
|
// for debugging: add a log to see if initialized on load
|
||||||
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
|
//worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
|
||||||
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
|
//worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
|
||||||
writers.InitializeWriters(worker);
|
writers.InitializeWriters(worker);
|
||||||
readers.InitializeReaders(worker);
|
readers.InitializeReaders(worker);
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
GeneratedCodeClass.Methods.Add(initReadWriters);
|
GeneratedCodeClass.Methods.Add(initReadWriters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,40 +3,38 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// Processes [Rpc] methods in NetworkBehaviour
|
// Processes [Rpc] methods in NetworkBehaviour
|
||||||
public static class RpcProcessor
|
public static class RpcProcessor
|
||||||
{
|
{
|
||||||
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var rpc = new MethodDefinition(
|
MethodDefinition rpc = new MethodDefinition(
|
||||||
Weaver.InvokeRpcPrefix + md.Name,
|
Weaver.InvokeRpcPrefix + md.Name,
|
||||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||||
weaverTypes.Import(typeof(void)));
|
weaverTypes.Import(typeof(void)));
|
||||||
|
|
||||||
var worker = rpc.Body.GetILProcessor();
|
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||||
var label = worker.Create(OpCodes.Nop);
|
Instruction label = worker.Create(OpCodes.Nop);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
|
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
|
||||||
|
|
||||||
// setup for reader
|
// setup for reader
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Castclass, td);
|
worker.Emit(OpCodes.Castclass, td);
|
||||||
|
|
||||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke actual command function
|
// invoke actual command function
|
||||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||||
td.Methods.Add(rpc);
|
td.Methods.Add(rpc);
|
||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generates code like:
|
* generates code like:
|
||||||
|
|
||||||
public void RpcTest (int param)
|
public void RpcTest (int param)
|
||||||
@ -58,46 +56,44 @@ namespace Mirror.Weaver
|
|||||||
This way we do not need to modify the code anywhere else, and this works
|
This way we do not need to modify the code anywhere else, and this works
|
||||||
correctly in dependent assemblies
|
correctly in dependent assemblies
|
||||||
*/
|
*/
|
||||||
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
|
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||||
|
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||||
|
|
||||||
// add a log message if needed for debugging
|
// add a log message if needed for debugging
|
||||||
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
|
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
|
||||||
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
|
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||||
|
|
||||||
// write all the arguments that the user passed to the Rpc call
|
// write all the arguments that the user passed to the Rpc call
|
||||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel = clientRpcAttr.GetField("channel", 0);
|
int channel = clientRpcAttr.GetField("channel", 0);
|
||||||
var includeOwner = clientRpcAttr.GetField("includeOwner", true);
|
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
|
||||||
|
|
||||||
// invoke SendInternal and return
|
// invoke SendInternal and return
|
||||||
// this
|
// this
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||||
// writer
|
// writer
|
||||||
worker.Emit(OpCodes.Ldloc_0);
|
worker.Emit(OpCodes.Ldloc_0);
|
||||||
worker.Emit(OpCodes.Ldc_I4, channel);
|
worker.Emit(OpCodes.Ldc_I4, channel);
|
||||||
// includeOwner ? 1 : 0
|
// includeOwner ? 1 : 0
|
||||||
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
|
||||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
|
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,163 +4,151 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
internal static class ServerClientAttributeProcessor
|
static class ServerClientAttributeProcessor
|
||||||
{
|
{
|
||||||
public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
public static bool Process(WeaverTypes weaverTypes, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
foreach (var md in td.Methods)
|
foreach (MethodDefinition md in td.Methods)
|
||||||
{
|
{
|
||||||
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
|
modified |= ProcessSiteMethod(weaverTypes, Log, md, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var nested in td.NestedTypes)
|
foreach (TypeDefinition nested in td.NestedTypes)
|
||||||
{
|
{
|
||||||
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
|
modified |= Process(weaverTypes, Log, nested, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
|
static bool ProcessSiteMethod(WeaverTypes weaverTypes, Logger Log, MethodDefinition md, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
if (md.Name == ".cctor" ||
|
if (md.Name == ".cctor" ||
|
||||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||||
{
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (md.IsAbstract)
|
if (md.IsAbstract)
|
||||||
{
|
{
|
||||||
if (HasServerClientAttribute(md))
|
if (HasServerClientAttribute(md))
|
||||||
{
|
{
|
||||||
Log.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
Log.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (md.Body != null && md.Body.Instructions != null)
|
if (md.Body != null && md.Body.Instructions != null)
|
||||||
{
|
{
|
||||||
return ProcessMethodAttributes(weaverTypes, md);
|
return ProcessMethodAttributes(weaverTypes, md);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasServerClientAttribute(MethodDefinition md)
|
public static bool HasServerClientAttribute(MethodDefinition md)
|
||||||
{
|
{
|
||||||
foreach (var attr in md.CustomAttributes)
|
foreach (CustomAttribute attr in md.CustomAttributes)
|
||||||
{
|
{
|
||||||
switch (attr.Constructor.DeclaringType.ToString())
|
switch (attr.Constructor.DeclaringType.ToString())
|
||||||
{
|
{
|
||||||
case "Mirror.ServerAttribute":
|
case "Mirror.ServerAttribute":
|
||||||
case "Mirror.ServerCallbackAttribute":
|
case "Mirror.ServerCallbackAttribute":
|
||||||
case "Mirror.ClientAttribute":
|
case "Mirror.ClientAttribute":
|
||||||
case "Mirror.ClientCallbackAttribute":
|
case "Mirror.ClientCallbackAttribute":
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md)
|
public static bool ProcessMethodAttributes(WeaverTypes weaverTypes, MethodDefinition md)
|
||||||
{
|
{
|
||||||
if (md.HasCustomAttribute<ServerAttribute>())
|
if (md.HasCustomAttribute<ServerAttribute>())
|
||||||
{
|
InjectServerGuard(weaverTypes, md, true);
|
||||||
InjectServerGuard(weaverTypes, md, true);
|
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||||
}
|
InjectServerGuard(weaverTypes, md, false);
|
||||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||||
{
|
InjectClientGuard(weaverTypes, md, true);
|
||||||
InjectServerGuard(weaverTypes, md, false);
|
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||||
}
|
InjectClientGuard(weaverTypes, md, false);
|
||||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
else
|
||||||
{
|
return false;
|
||||||
InjectClientGuard(weaverTypes, md, true);
|
|
||||||
}
|
|
||||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
|
||||||
{
|
|
||||||
InjectClientGuard(weaverTypes, md, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
static void InjectServerGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||||
{
|
{
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
var top = md.Body.Instructions[0];
|
Instruction top = md.Body.Instructions[0];
|
||||||
|
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive));
|
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkServerGetActive));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||||
if (logWarning)
|
if (logWarning)
|
||||||
{
|
{
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||||
}
|
}
|
||||||
InjectGuardParameters(md, worker, top);
|
InjectGuardParameters(md, worker, top);
|
||||||
InjectGuardReturnValue(md, worker, top);
|
InjectGuardReturnValue(md, worker, top);
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
static void InjectClientGuard(WeaverTypes weaverTypes, MethodDefinition md, bool logWarning)
|
||||||
{
|
{
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
var top = md.Body.Instructions[0];
|
Instruction top = md.Body.Instructions[0];
|
||||||
|
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive));
|
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.NetworkClientGetActive));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||||
if (logWarning)
|
if (logWarning)
|
||||||
{
|
{
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
worker.InsertBefore(top, worker.Create(OpCodes.Call, weaverTypes.logWarningReference));
|
||||||
}
|
}
|
||||||
|
|
||||||
InjectGuardParameters(md, worker, top);
|
InjectGuardParameters(md, worker, top);
|
||||||
InjectGuardReturnValue(md, worker, top);
|
InjectGuardReturnValue(md, worker, top);
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is required to early-out from a function with "ref" or "out" parameters
|
// this is required to early-out from a function with "ref" or "out" parameters
|
||||||
private static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||||
{
|
{
|
||||||
var offset = md.Resolve().IsStatic ? 0 : 1;
|
int offset = md.Resolve().IsStatic ? 0 : 1;
|
||||||
for (var index = 0; index < md.Parameters.Count; index++)
|
for (int index = 0; index < md.Parameters.Count; index++)
|
||||||
{
|
{
|
||||||
var param = md.Parameters[index];
|
ParameterDefinition param = md.Parameters[index];
|
||||||
if (param.IsOut)
|
if (param.IsOut)
|
||||||
{
|
{
|
||||||
var elementType = param.ParameterType.GetElementType();
|
TypeReference elementType = param.ParameterType.GetElementType();
|
||||||
|
|
||||||
md.Body.Variables.Add(new VariableDefinition(elementType));
|
md.Body.Variables.Add(new VariableDefinition(elementType));
|
||||||
md.Body.InitLocals = true;
|
md.Body.InitLocals = true;
|
||||||
|
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is required to early-out from a function with a return value.
|
// this is required to early-out from a function with a return value.
|
||||||
private static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||||
{
|
{
|
||||||
if (!md.ReturnType.Is(typeof(void)))
|
if (!md.ReturnType.Is(typeof(void)))
|
||||||
{
|
{
|
||||||
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
||||||
md.Body.InitLocals = true;
|
md.Body.InitLocals = true;
|
||||||
|
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
||||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,37 +3,37 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class SyncObjectInitializer
|
public static class SyncObjectInitializer
|
||||||
{
|
{
|
||||||
// generates code like:
|
// generates code like:
|
||||||
// this.InitSyncObject(m_sizes);
|
// this.InitSyncObject(m_sizes);
|
||||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
|
public static void GenerateSyncObjectInitializer(ILProcessor worker, WeaverTypes weaverTypes, FieldDefinition fd)
|
||||||
{
|
{
|
||||||
// register syncobject in network behaviour
|
// register syncobject in network behaviour
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldfld, fd);
|
worker.Emit(OpCodes.Ldfld, fd);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
|
worker.Emit(OpCodes.Call, weaverTypes.InitSyncObjectReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// value types cant inherit from SyncObject
|
// value types cant inherit from SyncObject
|
||||||
if (typeRef.IsValueType)
|
if (typeRef.IsValueType)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeRef.Resolve().IsDerivedFrom<SyncObject>();
|
return typeRef.Resolve().IsDerivedFrom<SyncObject>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,84 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class SyncObjectProcessor
|
public static class SyncObjectProcessor
|
||||||
{
|
{
|
||||||
// ulong = 64 bytes
|
// ulong = 64 bytes
|
||||||
private const int SyncObjectsLimit = 64;
|
const int SyncObjectsLimit = 64;
|
||||||
|
|
||||||
// Finds SyncObjects fields in a type
|
// Finds SyncObjects fields in a type
|
||||||
// Type should be a NetworkBehaviour
|
// Type should be a NetworkBehaviour
|
||||||
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
public static List<FieldDefinition> FindSyncObjectsFields(Writers writers, Readers readers, Logger Log, TypeDefinition td, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var syncObjects = new List<FieldDefinition>();
|
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||||
|
|
||||||
foreach (var fd in td.Fields)
|
foreach (FieldDefinition fd in td.Fields)
|
||||||
{
|
{
|
||||||
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
|
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
|
||||||
{
|
{
|
||||||
if (fd.IsStatic)
|
if (fd.IsStatic)
|
||||||
{
|
{
|
||||||
Log.Error($"{fd.Name} cannot be static", fd);
|
Log.Error($"{fd.Name} cannot be static", fd);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncObjects always needs to be readonly to guarantee.
|
// SyncObjects always needs to be readonly to guarantee.
|
||||||
// Weaver calls InitSyncObject on them for dirty bits etc.
|
// Weaver calls InitSyncObject on them for dirty bits etc.
|
||||||
// Reassigning at runtime would cause undefined behaviour.
|
// Reassigning at runtime would cause undefined behaviour.
|
||||||
// (C# 'readonly' is called 'initonly' in IL code.)
|
// (C# 'readonly' is called 'initonly' in IL code.)
|
||||||
//
|
//
|
||||||
// NOTE: instead of forcing readonly, we could also scan all
|
// NOTE: instead of forcing readonly, we could also scan all
|
||||||
// instructions for SyncObject assignments. this would
|
// instructions for SyncObject assignments. this would
|
||||||
// make unit tests very difficult though.
|
// make unit tests very difficult though.
|
||||||
if (!fd.IsInitOnly)
|
if (!fd.IsInitOnly)
|
||||||
{
|
{
|
||||||
// just a warning for now.
|
// just a warning for now.
|
||||||
// many people might still use non-readonly SyncObjects.
|
// many people might still use non-readonly SyncObjects.
|
||||||
Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd);
|
Log.Warning($"{fd.Name} should have a 'readonly' keyword in front of the variable because {typeof(SyncObject)}s always need to be initialized by the Weaver.", fd);
|
||||||
|
|
||||||
// only log, but keep weaving. no need to break projects.
|
// only log, but keep weaving. no need to break projects.
|
||||||
//WeavingFailed = true;
|
//WeavingFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
|
GenerateReadersAndWriters(writers, readers, fd.FieldType, ref WeavingFailed);
|
||||||
|
|
||||||
syncObjects.Add(fd);
|
syncObjects.Add(fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncObjects dirty mask is 64 bit. can't sync more than 64.
|
// SyncObjects dirty mask is 64 bit. can't sync more than 64.
|
||||||
if (syncObjects.Count > 64)
|
if (syncObjects.Count > 64)
|
||||||
{
|
{
|
||||||
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
|
Log.Error($"{td.Name} has > {SyncObjectsLimit} SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", td);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return syncObjects;
|
return syncObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates serialization methods for synclists
|
// Generates serialization methods for synclists
|
||||||
private static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
|
static void GenerateReadersAndWriters(Writers writers, Readers readers, TypeReference tr, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
if (tr is GenericInstanceType genericInstance)
|
if (tr is GenericInstanceType genericInstance)
|
||||||
{
|
{
|
||||||
foreach (var argument in genericInstance.GenericArguments)
|
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||||
{
|
{
|
||||||
if (!argument.IsGenericParameter)
|
if (!argument.IsGenericParameter)
|
||||||
{
|
{
|
||||||
readers.GetReadFunc(argument, ref WeavingFailed);
|
readers.GetReadFunc(argument, ref WeavingFailed);
|
||||||
writers.GetWriteFunc(argument, ref WeavingFailed);
|
writers.GetWriteFunc(argument, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tr != null)
|
if (tr != null)
|
||||||
{
|
{
|
||||||
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
|
GenerateReadersAndWriters(writers, readers, tr.Resolve().BaseType, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,181 +2,173 @@
|
|||||||
// is replaced with:
|
// is replaced with:
|
||||||
// public int Networkhealth { get; set; } properties.
|
// public int Networkhealth { get; set; } properties.
|
||||||
// this class processes all access to 'health' and replaces it with 'Networkhealth'
|
// this class processes all access to 'health' and replaces it with 'Networkhealth'
|
||||||
|
using System;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class SyncVarAttributeAccessReplacer
|
public static class SyncVarAttributeAccessReplacer
|
||||||
{
|
{
|
||||||
// process the module
|
// process the module
|
||||||
public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
|
public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
|
||||||
{
|
{
|
||||||
var startTime = DateTime.Now;
|
DateTime startTime = DateTime.Now;
|
||||||
|
|
||||||
// process all classes in this module
|
// process all classes in this module
|
||||||
foreach (var td in moduleDef.Types)
|
foreach (TypeDefinition td in moduleDef.Types)
|
||||||
{
|
{
|
||||||
if (td.IsClass)
|
if (td.IsClass)
|
||||||
{
|
{
|
||||||
ProcessClass(syncVarAccessLists, td);
|
ProcessClass(syncVarAccessLists, td);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
|
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
|
static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
|
||||||
{
|
{
|
||||||
//Console.WriteLine($" ProcessClass {td}");
|
//Console.WriteLine($" ProcessClass {td}");
|
||||||
|
|
||||||
// process all methods in this class
|
// process all methods in this class
|
||||||
foreach (var md in td.Methods)
|
foreach (MethodDefinition md in td.Methods)
|
||||||
{
|
{
|
||||||
ProcessMethod(syncVarAccessLists, md);
|
ProcessMethod(syncVarAccessLists, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
// processes all nested classes in this class recursively
|
// processes all nested classes in this class recursively
|
||||||
foreach (var nested in td.NestedTypes)
|
foreach (TypeDefinition nested in td.NestedTypes)
|
||||||
{
|
{
|
||||||
ProcessClass(syncVarAccessLists, nested);
|
ProcessClass(syncVarAccessLists, nested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
|
static void ProcessMethod(SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
|
||||||
{
|
{
|
||||||
// process all references to replaced members with properties
|
// process all references to replaced members with properties
|
||||||
//Log.Warning($" ProcessSiteMethod {md}");
|
//Log.Warning($" ProcessSiteMethod {md}");
|
||||||
|
|
||||||
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
|
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
|
||||||
if (md.Name == ".cctor" ||
|
if (md.Name == ".cctor" ||
|
||||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||||
{
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip abstract
|
// skip abstract
|
||||||
if (md.IsAbstract)
|
if (md.IsAbstract)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// go through all instructions of this method
|
// go through all instructions of this method
|
||||||
if (md.Body != null && md.Body.Instructions != null)
|
if (md.Body != null && md.Body.Instructions != null)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < md.Body.Instructions.Count;)
|
for (int i = 0; i < md.Body.Instructions.Count;)
|
||||||
{
|
{
|
||||||
var instr = md.Body.Instructions[i];
|
Instruction instr = md.Body.Instructions[i];
|
||||||
i += ProcessInstruction(syncVarAccessLists, md, instr, i);
|
i += ProcessInstruction(syncVarAccessLists, md, instr, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
|
static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
|
||||||
{
|
{
|
||||||
// stfld (sets value of a field)?
|
// stfld (sets value of a field)?
|
||||||
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
||||||
{
|
{
|
||||||
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
|
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ldfld (load value of a field)?
|
// ldfld (load value of a field)?
|
||||||
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
||||||
{
|
{
|
||||||
// this instruction gets the value of a field. cache the field reference.
|
// this instruction gets the value of a field. cache the field reference.
|
||||||
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
|
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ldflda (load field address aka reference)
|
// ldflda (load field address aka reference)
|
||||||
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
||||||
{
|
{
|
||||||
// watch out for initobj instruction
|
// watch out for initobj instruction
|
||||||
// see https://github.com/vis2k/Mirror/issues/696
|
// see https://github.com/vis2k/Mirror/issues/696
|
||||||
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
|
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we processed one instruction (instr)
|
// we processed one instruction (instr)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaces syncvar write access with the NetworkXYZ.set property calls
|
// replaces syncvar write access with the NetworkXYZ.set property calls
|
||||||
private static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||||
{
|
{
|
||||||
// don't replace property call sites in constructors
|
// don't replace property call sites in constructors
|
||||||
if (md.Name == ".ctor")
|
if (md.Name == ".ctor")
|
||||||
{
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// does it set a field that we replaced?
|
// does it set a field that we replaced?
|
||||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out var replacement))
|
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||||
{
|
{
|
||||||
//replace with property
|
//replace with property
|
||||||
//Log.Warning($" replacing {md.Name}:{i}", opField);
|
//Log.Warning($" replacing {md.Name}:{i}", opField);
|
||||||
i.OpCode = OpCodes.Call;
|
i.OpCode = OpCodes.Call;
|
||||||
i.Operand = replacement;
|
i.Operand = replacement;
|
||||||
//Log.Warning($" replaced {md.Name}:{i}", opField);
|
//Log.Warning($" replaced {md.Name}:{i}", opField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||||
private static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||||
{
|
{
|
||||||
// don't replace property call sites in constructors
|
// don't replace property call sites in constructors
|
||||||
if (md.Name == ".ctor")
|
if (md.Name == ".ctor")
|
||||||
{
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// does it set a field that we replaced?
|
// does it set a field that we replaced?
|
||||||
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out var replacement))
|
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||||
{
|
{
|
||||||
//replace with property
|
//replace with property
|
||||||
//Log.Warning($" replacing {md.Name}:{i}");
|
//Log.Warning($" replacing {md.Name}:{i}");
|
||||||
i.OpCode = OpCodes.Call;
|
i.OpCode = OpCodes.Call;
|
||||||
i.Operand = replacement;
|
i.Operand = replacement;
|
||||||
//Log.Warning($" replaced {md.Name}:{i}");
|
//Log.Warning($" replaced {md.Name}:{i}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||||
{
|
{
|
||||||
// don't replace property call sites in constructors
|
// don't replace property call sites in constructors
|
||||||
if (md.Name == ".ctor")
|
if (md.Name == ".ctor")
|
||||||
{
|
return 1;
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// does it set a field that we replaced?
|
// does it set a field that we replaced?
|
||||||
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out var replacement))
|
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||||
{
|
{
|
||||||
// we have a replacement for this property
|
// we have a replacement for this property
|
||||||
// is the next instruction a initobj?
|
// is the next instruction a initobj?
|
||||||
var nextInstr = md.Body.Instructions[iCount + 1];
|
Instruction nextInstr = md.Body.Instructions[iCount + 1];
|
||||||
|
|
||||||
if (nextInstr.OpCode == OpCodes.Initobj)
|
if (nextInstr.OpCode == OpCodes.Initobj)
|
||||||
{
|
{
|
||||||
// we need to replace this code with:
|
// we need to replace this code with:
|
||||||
// var tmp = new MyStruct();
|
// var tmp = new MyStruct();
|
||||||
// this.set_Networkxxxx(tmp);
|
// this.set_Networkxxxx(tmp);
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
var tmpVariable = new VariableDefinition(opField.FieldType);
|
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
|
||||||
md.Body.Variables.Add(tmpVariable);
|
md.Body.Variables.Add(tmpVariable);
|
||||||
|
|
||||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
||||||
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
||||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
||||||
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
||||||
|
|
||||||
worker.Remove(instr);
|
worker.Remove(instr);
|
||||||
worker.Remove(nextInstr);
|
worker.Remove(nextInstr);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,485 +1,490 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using Mono.Cecil.Cil;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// Processes [SyncVar] attribute fields in NetworkBehaviour
|
// Processes [SyncVar] attribute fields in NetworkBehaviour
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
public class SyncVarAttributeProcessor
|
public class SyncVarAttributeProcessor
|
||||||
{
|
{
|
||||||
// ulong = 64 bytes
|
// ulong = 64 bytes
|
||||||
private const int SyncVarLimit = 64;
|
const int SyncVarLimit = 64;
|
||||||
private readonly AssemblyDefinition assembly;
|
|
||||||
private readonly WeaverTypes weaverTypes;
|
AssemblyDefinition assembly;
|
||||||
private readonly SyncVarAccessLists syncVarAccessLists;
|
WeaverTypes weaverTypes;
|
||||||
private readonly Logger Log;
|
SyncVarAccessLists syncVarAccessLists;
|
||||||
|
Logger Log;
|
||||||
private string HookParameterMessage(string hookName, TypeReference ValueType) =>
|
|
||||||
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
|
string HookParameterMessage(string hookName, TypeReference ValueType) =>
|
||||||
|
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
|
||||||
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
|
|
||||||
{
|
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
|
||||||
this.assembly = assembly;
|
{
|
||||||
this.weaverTypes = weaverTypes;
|
this.assembly = assembly;
|
||||||
this.syncVarAccessLists = syncVarAccessLists;
|
this.weaverTypes = weaverTypes;
|
||||||
this.Log = Log;
|
this.syncVarAccessLists = syncVarAccessLists;
|
||||||
}
|
this.Log = Log;
|
||||||
|
}
|
||||||
// Get hook method if any
|
|
||||||
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
|
// Get hook method if any
|
||||||
{
|
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
|
||||||
var syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
|
{
|
||||||
|
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
|
||||||
if (syncVarAttr == null)
|
|
||||||
{
|
if (syncVarAttr == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
|
||||||
var hookFunctionName = syncVarAttr.GetField<string>("hook", null);
|
|
||||||
|
if (hookFunctionName == null)
|
||||||
if (hookFunctionName == null)
|
return null;
|
||||||
{
|
|
||||||
return null;
|
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
|
MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
|
||||||
}
|
{
|
||||||
|
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
|
||||||
private MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
|
|
||||||
{
|
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
||||||
var methods = td.GetMethods(hookFunctionName);
|
|
||||||
|
if (methodsWith2Param.Count == 0)
|
||||||
var methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
|
{
|
||||||
|
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||||
if (methodsWith2Param.Count == 0)
|
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||||
{
|
syncVar);
|
||||||
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
WeavingFailed = true;
|
||||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
|
||||||
syncVar);
|
return null;
|
||||||
WeavingFailed = true;
|
}
|
||||||
|
|
||||||
return null;
|
foreach (MethodDefinition method in methodsWith2Param)
|
||||||
}
|
{
|
||||||
|
if (MatchesParameters(syncVar, method))
|
||||||
foreach (var method in methodsWith2Param)
|
{
|
||||||
{
|
return method;
|
||||||
if (MatchesParameters(syncVar, method))
|
}
|
||||||
{
|
}
|
||||||
return method;
|
|
||||||
}
|
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
||||||
}
|
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
||||||
|
syncVar);
|
||||||
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
|
WeavingFailed = true;
|
||||||
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
|
|
||||||
syncVar);
|
return null;
|
||||||
WeavingFailed = true;
|
}
|
||||||
|
|
||||||
return null;
|
bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
|
||||||
}
|
{
|
||||||
|
// matches void onValueChange(T oldValue, T newValue)
|
||||||
private bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method) =>
|
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
|
||||||
// matches void onValueChange(T oldValue, T newValue)
|
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
|
||||||
method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
|
}
|
||||||
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
|
|
||||||
|
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
|
||||||
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
|
{
|
||||||
{
|
//Create the get method
|
||||||
//Create the get method
|
MethodDefinition get = new MethodDefinition(
|
||||||
var get = new MethodDefinition(
|
$"get_Network{originalName}", MethodAttributes.Public |
|
||||||
$"get_Network{originalName}", MethodAttributes.Public |
|
MethodAttributes.SpecialName |
|
||||||
MethodAttributes.SpecialName |
|
MethodAttributes.HideBySig,
|
||||||
MethodAttributes.HideBySig,
|
fd.FieldType);
|
||||||
fd.FieldType);
|
|
||||||
|
ILProcessor worker = get.Body.GetILProcessor();
|
||||||
var worker = get.Body.GetILProcessor();
|
|
||||||
|
// [SyncVar] GameObject?
|
||||||
// [SyncVar] GameObject?
|
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
{
|
||||||
{
|
// return this.GetSyncVarGameObject(ref field, uint netId);
|
||||||
// return this.GetSyncVarGameObject(ref field, uint netId);
|
// this.
|
||||||
// this.
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, fd);
|
||||||
worker.Emit(OpCodes.Ldflda, fd);
|
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
|
worker.Emit(OpCodes.Ret);
|
||||||
worker.Emit(OpCodes.Ret);
|
}
|
||||||
}
|
// [SyncVar] NetworkIdentity?
|
||||||
// [SyncVar] NetworkIdentity?
|
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
{
|
||||||
{
|
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
// this.
|
||||||
// this.
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, fd);
|
||||||
worker.Emit(OpCodes.Ldflda, fd);
|
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
|
worker.Emit(OpCodes.Ret);
|
||||||
worker.Emit(OpCodes.Ret);
|
}
|
||||||
}
|
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
{
|
||||||
{
|
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
||||||
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
// this.
|
||||||
// this.
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, fd);
|
||||||
worker.Emit(OpCodes.Ldflda, fd);
|
MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||||
var getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
worker.Emit(OpCodes.Call, getFunc);
|
||||||
worker.Emit(OpCodes.Call, getFunc);
|
worker.Emit(OpCodes.Ret);
|
||||||
worker.Emit(OpCodes.Ret);
|
}
|
||||||
}
|
// [SyncVar] int, string, etc.
|
||||||
// [SyncVar] int, string, etc.
|
else
|
||||||
else
|
{
|
||||||
{
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, fd);
|
||||||
worker.Emit(OpCodes.Ldfld, fd);
|
worker.Emit(OpCodes.Ret);
|
||||||
worker.Emit(OpCodes.Ret);
|
}
|
||||||
}
|
|
||||||
|
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
||||||
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
|
get.Body.InitLocals = true;
|
||||||
get.Body.InitLocals = true;
|
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
||||||
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
|
||||||
|
return get;
|
||||||
return get;
|
}
|
||||||
}
|
|
||||||
|
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed)
|
||||||
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed)
|
{
|
||||||
{
|
//Create the set method
|
||||||
//Create the set method
|
MethodDefinition set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
|
||||||
var set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
|
MethodAttributes.SpecialName |
|
||||||
MethodAttributes.SpecialName |
|
MethodAttributes.HideBySig,
|
||||||
MethodAttributes.HideBySig,
|
weaverTypes.Import(typeof(void)));
|
||||||
weaverTypes.Import(typeof(void)));
|
|
||||||
|
ILProcessor worker = set.Body.GetILProcessor();
|
||||||
var worker = set.Body.GetILProcessor();
|
|
||||||
|
// if (!SyncVarEqual(value, ref playerData))
|
||||||
// if (!SyncVarEqual(value, ref playerData))
|
Instruction endOfMethod = worker.Create(OpCodes.Nop);
|
||||||
var endOfMethod = worker.Create(OpCodes.Nop);
|
|
||||||
|
// NOTE: SyncVar...Equal functions are static.
|
||||||
// NOTE: SyncVar...Equal functions are static.
|
// don't Emit Ldarg_0 aka 'this'.
|
||||||
// don't Emit Ldarg_0 aka 'this'.
|
|
||||||
|
// new value to set
|
||||||
// new value to set
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
|
||||||
|
// reference to field to set
|
||||||
// reference to field to set
|
// make generic version of SetSyncVar with field type
|
||||||
// make generic version of SetSyncVar with field type
|
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
|
||||||
|
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference);
|
}
|
||||||
}
|
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
|
||||||
|
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference);
|
}
|
||||||
}
|
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldfld, netFieldId);
|
|
||||||
|
MethodReference getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||||
var getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
worker.Emit(OpCodes.Call, getFunc);
|
||||||
worker.Emit(OpCodes.Call, getFunc);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, fd);
|
||||||
worker.Emit(OpCodes.Ldflda, fd);
|
|
||||||
|
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
|
||||||
var syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
|
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
||||||
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
worker.Emit(OpCodes.Call, syncVarEqualGm);
|
||||||
worker.Emit(OpCodes.Call, syncVarEqualGm);
|
}
|
||||||
}
|
|
||||||
|
worker.Emit(OpCodes.Brtrue, endOfMethod);
|
||||||
worker.Emit(OpCodes.Brtrue, endOfMethod);
|
|
||||||
|
// T oldValue = value;
|
||||||
// T oldValue = value;
|
// TODO for GO/NI we need to backup the netId don't we?
|
||||||
// TODO for GO/NI we need to backup the netId don't we?
|
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
|
||||||
var oldValue = new VariableDefinition(fd.FieldType);
|
set.Body.Variables.Add(oldValue);
|
||||||
set.Body.Variables.Add(oldValue);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldfld, fd);
|
||||||
worker.Emit(OpCodes.Ldfld, fd);
|
worker.Emit(OpCodes.Stloc, oldValue);
|
||||||
worker.Emit(OpCodes.Stloc, oldValue);
|
|
||||||
|
// this
|
||||||
// this
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
|
||||||
|
// new value to set
|
||||||
// new value to set
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
|
||||||
|
// reference to field to set
|
||||||
// reference to field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, fd);
|
||||||
worker.Emit(OpCodes.Ldflda, fd);
|
|
||||||
|
// dirty bit
|
||||||
// dirty bit
|
// 8 byte integer aka long
|
||||||
// 8 byte integer aka long
|
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
|
||||||
|
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
|
||||||
|
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference);
|
}
|
||||||
}
|
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
|
||||||
|
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference);
|
}
|
||||||
}
|
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
{
|
||||||
{
|
// reference to netId Field to set
|
||||||
// reference to netId Field to set
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldflda, netFieldId);
|
||||||
worker.Emit(OpCodes.Ldflda, netFieldId);
|
|
||||||
|
MethodReference getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
||||||
var getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
|
worker.Emit(OpCodes.Call, getFunc);
|
||||||
worker.Emit(OpCodes.Call, getFunc);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
// make generic version of SetSyncVar with field type
|
||||||
// make generic version of SetSyncVar with field type
|
GenericInstanceMethod gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference);
|
||||||
var gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference);
|
gm.GenericArguments.Add(fd.FieldType);
|
||||||
gm.GenericArguments.Add(fd.FieldType);
|
|
||||||
|
// invoke SetSyncVar
|
||||||
// invoke SetSyncVar
|
worker.Emit(OpCodes.Call, gm);
|
||||||
worker.Emit(OpCodes.Call, gm);
|
}
|
||||||
}
|
|
||||||
|
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
|
||||||
var hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
|
|
||||||
|
if (hookMethod != null)
|
||||||
if (hookMethod != null)
|
{
|
||||||
{
|
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
||||||
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
Instruction label = worker.Create(OpCodes.Nop);
|
||||||
var label = worker.Create(OpCodes.Nop);
|
worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive);
|
worker.Emit(OpCodes.Brfalse, label);
|
||||||
worker.Emit(OpCodes.Brfalse, label);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard);
|
worker.Emit(OpCodes.Brtrue, label);
|
||||||
worker.Emit(OpCodes.Brtrue, label);
|
|
||||||
|
// setSyncVarHookGuard(dirtyBit, true);
|
||||||
// setSyncVarHookGuard(dirtyBit, true);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
worker.Emit(OpCodes.Ldc_I4_1);
|
||||||
worker.Emit(OpCodes.Ldc_I4_1);
|
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
|
||||||
|
// call hook (oldValue, newValue)
|
||||||
// call hook (oldValue, newValue)
|
// Generates: OnValueChanged(oldValue, value);
|
||||||
// Generates: OnValueChanged(oldValue, value);
|
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
||||||
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
|
||||||
|
// setSyncVarHookGuard(dirtyBit, false);
|
||||||
// setSyncVarHookGuard(dirtyBit, false);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
||||||
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
|
worker.Emit(OpCodes.Ldc_I4_0);
|
||||||
worker.Emit(OpCodes.Ldc_I4_0);
|
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
|
|
||||||
|
worker.Append(label);
|
||||||
worker.Append(label);
|
}
|
||||||
}
|
|
||||||
|
worker.Append(endOfMethod);
|
||||||
worker.Append(endOfMethod);
|
|
||||||
|
worker.Emit(OpCodes.Ret);
|
||||||
worker.Emit(OpCodes.Ret);
|
|
||||||
|
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
||||||
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
|
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
||||||
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
|
||||||
|
return set;
|
||||||
return set;
|
}
|
||||||
}
|
|
||||||
|
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed)
|
||||||
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit, ref bool WeavingFailed)
|
{
|
||||||
{
|
string originalName = fd.Name;
|
||||||
var originalName = fd.Name;
|
|
||||||
|
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
FieldDefinition netIdField = null;
|
||||||
FieldDefinition netIdField = null;
|
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
||||||
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||||
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
{
|
||||||
{
|
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
FieldAttributes.Private,
|
||||||
FieldAttributes.Private,
|
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
||||||
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
|
||||||
|
syncVarNetIds[fd] = netIdField;
|
||||||
syncVarNetIds[fd] = netIdField;
|
}
|
||||||
}
|
else if (fd.FieldType.IsNetworkIdentityField())
|
||||||
else if (fd.FieldType.IsNetworkIdentityField())
|
{
|
||||||
{
|
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
||||||
netIdField = new FieldDefinition($"___{fd.Name}NetId",
|
FieldAttributes.Private,
|
||||||
FieldAttributes.Private,
|
weaverTypes.Import<uint>());
|
||||||
weaverTypes.Import<uint>());
|
|
||||||
|
syncVarNetIds[fd] = netIdField;
|
||||||
syncVarNetIds[fd] = netIdField;
|
}
|
||||||
}
|
|
||||||
|
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
||||||
var get = GenerateSyncVarGetter(fd, originalName, netIdField);
|
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, ref WeavingFailed);
|
||||||
var set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, ref WeavingFailed);
|
|
||||||
|
//NOTE: is property even needed? Could just use a setter function?
|
||||||
//NOTE: is property even needed? Could just use a setter function?
|
//create the property
|
||||||
//create the property
|
PropertyDefinition propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
|
||||||
var propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
|
{
|
||||||
{
|
GetMethod = get,
|
||||||
GetMethod = get,
|
SetMethod = set
|
||||||
SetMethod = set
|
};
|
||||||
};
|
|
||||||
|
//add the methods and property to the type.
|
||||||
//add the methods and property to the type.
|
td.Methods.Add(get);
|
||||||
td.Methods.Add(get);
|
td.Methods.Add(set);
|
||||||
td.Methods.Add(set);
|
td.Properties.Add(propertyDefinition);
|
||||||
td.Properties.Add(propertyDefinition);
|
syncVarAccessLists.replacementSetterProperties[fd] = set;
|
||||||
syncVarAccessLists.replacementSetterProperties[fd] = set;
|
|
||||||
|
// replace getter field if GameObject/NetworkIdentity so it uses
|
||||||
// replace getter field if GameObject/NetworkIdentity so it uses
|
// netId instead
|
||||||
// netId instead
|
// -> only for GameObjects, otherwise an int syncvar's getter would
|
||||||
// -> only for GameObjects, otherwise an int syncvar's getter would
|
// end up in recursion.
|
||||||
// end up in recursion.
|
if (fd.FieldType.IsNetworkIdentityField())
|
||||||
if (fd.FieldType.IsNetworkIdentityField())
|
{
|
||||||
{
|
syncVarAccessLists.replacementGetterProperties[fd] = get;
|
||||||
syncVarAccessLists.replacementGetterProperties[fd] = get;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
|
||||||
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
|
{
|
||||||
{
|
List<FieldDefinition> syncVars = new List<FieldDefinition>();
|
||||||
var syncVars = new List<FieldDefinition>();
|
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
||||||
var syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
|
|
||||||
|
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
|
||||||
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
|
// start assigning syncvars at the place the base class stopped, if any
|
||||||
// start assigning syncvars at the place the base class stopped, if any
|
int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
|
||||||
var dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
|
|
||||||
|
// find syncvars
|
||||||
// find syncvars
|
foreach (FieldDefinition fd in td.Fields)
|
||||||
foreach (var fd in td.Fields)
|
{
|
||||||
{
|
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
{
|
||||||
{
|
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
||||||
if ((fd.Attributes & FieldAttributes.Static) != 0)
|
{
|
||||||
{
|
Log.Error($"{fd.Name} cannot be static", fd);
|
||||||
Log.Error($"{fd.Name} cannot be static", fd);
|
WeavingFailed = true;
|
||||||
WeavingFailed = true;
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
if (fd.FieldType.IsArray)
|
||||||
if (fd.FieldType.IsArray)
|
{
|
||||||
{
|
Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
|
||||||
Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
|
WeavingFailed = true;
|
||||||
WeavingFailed = true;
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
{
|
||||||
{
|
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
||||||
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
syncVars.Add(fd);
|
||||||
syncVars.Add(fd);
|
|
||||||
|
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed);
|
||||||
ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter, ref WeavingFailed);
|
dirtyBitCounter += 1;
|
||||||
dirtyBitCounter += 1;
|
|
||||||
|
if (dirtyBitCounter > SyncVarLimit)
|
||||||
if (dirtyBitCounter > SyncVarLimit)
|
{
|
||||||
{
|
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
|
||||||
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
|
WeavingFailed = true;
|
||||||
WeavingFailed = true;
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// add all the new SyncVar __netId fields
|
||||||
// add all the new SyncVar __netId fields
|
foreach (FieldDefinition fd in syncVarNetIds.Values)
|
||||||
foreach (var fd in syncVarNetIds.Values)
|
{
|
||||||
{
|
td.Fields.Add(fd);
|
||||||
td.Fields.Add(fd);
|
}
|
||||||
}
|
syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
|
||||||
syncVarAccessLists.SetNumSyncVars(td.FullName, syncVars.Count);
|
|
||||||
|
return (syncVars, syncVarNetIds);
|
||||||
return (syncVars, syncVarNetIds);
|
}
|
||||||
}
|
|
||||||
|
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
|
||||||
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue) => WriteCallHookMethod(worker, hookMethod, oldValue, null);
|
{
|
||||||
|
WriteCallHookMethod(worker, hookMethod, oldValue, null);
|
||||||
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
|
}
|
||||||
{
|
|
||||||
if (newValue == null)
|
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
Log.Error("NewValue field was null when writing SyncVar hook");
|
if (newValue == null)
|
||||||
WeavingFailed = true;
|
{
|
||||||
}
|
Log.Error("NewValue field was null when writing SyncVar hook");
|
||||||
|
WeavingFailed = true;
|
||||||
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
|
}
|
||||||
}
|
|
||||||
|
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
|
||||||
private void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
}
|
||||||
{
|
|
||||||
WriteStartFunctionCall();
|
void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
|
||||||
|
{
|
||||||
// write args
|
WriteStartFunctionCall();
|
||||||
WriteOldValue();
|
|
||||||
WriteNewValue();
|
// write args
|
||||||
|
WriteOldValue();
|
||||||
WriteEndFunctionCall();
|
WriteNewValue();
|
||||||
|
|
||||||
|
WriteEndFunctionCall();
|
||||||
// *** Local functions used to write OpCodes ***
|
|
||||||
// Local functions have access to function variables, no need to pass in args
|
|
||||||
|
// *** Local functions used to write OpCodes ***
|
||||||
void WriteOldValue() => worker.Emit(OpCodes.Ldloc, oldValue);
|
// Local functions have access to function variables, no need to pass in args
|
||||||
|
|
||||||
void WriteNewValue()
|
void WriteOldValue()
|
||||||
{
|
{
|
||||||
// write arg1 or this.field
|
worker.Emit(OpCodes.Ldloc, oldValue);
|
||||||
if (newValue == null)
|
}
|
||||||
{
|
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
void WriteNewValue()
|
||||||
}
|
{
|
||||||
else
|
// write arg1 or this.field
|
||||||
{
|
if (newValue == null)
|
||||||
// this.
|
{
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
// syncvar.get
|
}
|
||||||
worker.Emit(OpCodes.Ldfld, newValue);
|
else
|
||||||
}
|
{
|
||||||
}
|
// this.
|
||||||
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
// Writes this before method if it is not static
|
// syncvar.get
|
||||||
void WriteStartFunctionCall()
|
worker.Emit(OpCodes.Ldfld, newValue);
|
||||||
{
|
}
|
||||||
// don't add this (Ldarg_0) if method is static
|
}
|
||||||
if (!hookMethod.IsStatic)
|
|
||||||
{
|
// Writes this before method if it is not static
|
||||||
// this before method call
|
void WriteStartFunctionCall()
|
||||||
// e.g. this.onValueChanged
|
{
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
// don't add this (Ldarg_0) if method is static
|
||||||
}
|
if (!hookMethod.IsStatic)
|
||||||
}
|
{
|
||||||
|
// this before method call
|
||||||
// Calls method
|
// e.g. this.onValueChanged
|
||||||
void WriteEndFunctionCall()
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
{
|
}
|
||||||
// only use Callvirt when not static
|
}
|
||||||
var opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
|
||||||
worker.Emit(opcode, hookMethod);
|
// Calls method
|
||||||
}
|
void WriteEndFunctionCall()
|
||||||
}
|
{
|
||||||
}
|
// only use Callvirt when not static
|
||||||
|
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
||||||
|
worker.Emit(opcode, hookMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,60 +3,61 @@ using Mono.Cecil.Cil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// Processes [TargetRpc] methods in NetworkBehaviour
|
// Processes [TargetRpc] methods in NetworkBehaviour
|
||||||
public static class TargetRpcProcessor
|
public static class TargetRpcProcessor
|
||||||
{
|
{
|
||||||
// helper functions to check if the method has a NetworkConnection parameter
|
// helper functions to check if the method has a NetworkConnection parameter
|
||||||
public static bool HasNetworkConnectionParameter(MethodDefinition md) => md.Parameters.Count > 0 &&
|
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||||
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
{
|
||||||
|
return md.Parameters.Count > 0 &&
|
||||||
|
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
||||||
|
}
|
||||||
|
|
||||||
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
||||||
MethodAttributes.Static |
|
MethodAttributes.Static |
|
||||||
MethodAttributes.HideBySig,
|
MethodAttributes.HideBySig,
|
||||||
weaverTypes.Import(typeof(void)));
|
weaverTypes.Import(typeof(void)));
|
||||||
|
|
||||||
var worker = rpc.Body.GetILProcessor();
|
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||||
var label = worker.Create(OpCodes.Nop);
|
Instruction label = worker.Create(OpCodes.Nop);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
|
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
|
||||||
|
|
||||||
// setup for reader
|
// setup for reader
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
worker.Emit(OpCodes.Castclass, td);
|
worker.Emit(OpCodes.Castclass, td);
|
||||||
|
|
||||||
// NetworkConnection parameter is optional
|
// NetworkConnection parameter is optional
|
||||||
if (HasNetworkConnectionParameter(md))
|
if (HasNetworkConnectionParameter(md))
|
||||||
{
|
{
|
||||||
// on server, the NetworkConnection parameter is a connection to client.
|
// on server, the NetworkConnection parameter is a connection to client.
|
||||||
// when the rpc is invoked on the client, it still has the same
|
// when the rpc is invoked on the client, it still has the same
|
||||||
// function signature. we pass in the connection to server,
|
// function signature. we pass in the connection to server,
|
||||||
// which is cleaner than just passing null)
|
// which is cleaner than just passing null)
|
||||||
//NetworkClient.readyconnection
|
//NetworkClient.readyconnection
|
||||||
//
|
//
|
||||||
// TODO
|
// TODO
|
||||||
// a) .connectionToServer = best solution. no doubt.
|
// a) .connectionToServer = best solution. no doubt.
|
||||||
// b) NetworkClient.connection for now. add TODO to not use static later.
|
// b) NetworkClient.connection for now. add TODO to not use static later.
|
||||||
worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference);
|
worker.Emit(OpCodes.Call, weaverTypes.NetworkClientConnectionReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
// process reader parameters and skip first one if first one is NetworkConnection
|
// process reader parameters and skip first one if first one is NetworkConnection
|
||||||
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke actual command function
|
// invoke actual command function
|
||||||
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
|
||||||
td.Methods.Add(rpc);
|
td.Methods.Add(rpc);
|
||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* generates code like:
|
/* generates code like:
|
||||||
public void TargetTest (NetworkConnection conn, int param)
|
public void TargetTest (NetworkConnection conn, int param)
|
||||||
{
|
{
|
||||||
NetworkWriter writer = new NetworkWriter ();
|
NetworkWriter writer = new NetworkWriter ();
|
||||||
@ -89,48 +90,46 @@ namespace Mirror.Weaver
|
|||||||
correctly in dependent assemblies
|
correctly in dependent assemblies
|
||||||
|
|
||||||
*/
|
*/
|
||||||
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
|
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
|
||||||
|
|
||||||
var worker = md.Body.GetILProcessor();
|
ILProcessor worker = md.Body.GetILProcessor();
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes);
|
||||||
|
|
||||||
// write all the arguments that the user passed to the TargetRpc call
|
// write all the arguments that the user passed to the TargetRpc call
|
||||||
// (skip first one if first one is NetworkConnection)
|
// (skip first one if first one is NetworkConnection)
|
||||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
|
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
|
||||||
{
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke SendInternal and return
|
// invoke SendInternal and return
|
||||||
// this
|
// this
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
if (HasNetworkConnectionParameter(md))
|
if (HasNetworkConnectionParameter(md))
|
||||||
{
|
{
|
||||||
// connection
|
// connection
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// null
|
// null
|
||||||
worker.Emit(OpCodes.Ldnull);
|
worker.Emit(OpCodes.Ldnull);
|
||||||
}
|
}
|
||||||
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
|
||||||
worker.Emit(OpCodes.Ldstr, md.FullName);
|
worker.Emit(OpCodes.Ldstr, md.FullName);
|
||||||
// writer
|
// writer
|
||||||
worker.Emit(OpCodes.Ldloc_0);
|
worker.Emit(OpCodes.Ldloc_0);
|
||||||
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
|
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
|
||||||
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
|
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
|
||||||
|
|
||||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes);
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
return rpc;
|
return rpc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,386 +1,383 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
// to use Mono.Cecil.Rocks here, we need to 'override references' in the
|
// to use Mono.Cecil.Rocks here, we need to 'override references' in the
|
||||||
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
||||||
// otherwise we get an unknown import exception.
|
// otherwise we get an unknown import exception.
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
public class Readers
|
public class Readers
|
||||||
{
|
{
|
||||||
// Readers are only for this assembly.
|
// Readers are only for this assembly.
|
||||||
// can't be used from another assembly, otherwise we will get:
|
// can't be used from another assembly, otherwise we will get:
|
||||||
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
||||||
private readonly AssemblyDefinition assembly;
|
AssemblyDefinition assembly;
|
||||||
private readonly WeaverTypes weaverTypes;
|
WeaverTypes weaverTypes;
|
||||||
private readonly TypeDefinition GeneratedCodeClass;
|
TypeDefinition GeneratedCodeClass;
|
||||||
private readonly Logger Log;
|
Logger Log;
|
||||||
private readonly Dictionary<TypeReference, MethodReference> readFuncs =
|
|
||||||
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
|
||||||
|
|
||||||
public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
Dictionary<TypeReference, MethodReference> readFuncs =
|
||||||
{
|
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
||||||
this.assembly = assembly;
|
|
||||||
this.weaverTypes = weaverTypes;
|
|
||||||
this.GeneratedCodeClass = GeneratedCodeClass;
|
|
||||||
this.Log = Log;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Register(TypeReference dataType, MethodReference methodReference)
|
public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
||||||
{
|
{
|
||||||
if (readFuncs.ContainsKey(dataType))
|
this.assembly = assembly;
|
||||||
{
|
this.weaverTypes = weaverTypes;
|
||||||
// TODO enable this again later.
|
this.GeneratedCodeClass = GeneratedCodeClass;
|
||||||
// Reader has some obsolete functions that were renamed.
|
this.Log = Log;
|
||||||
// Don't want weaver warnings for all of them.
|
}
|
||||||
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
|
internal void Register(TypeReference dataType, MethodReference methodReference)
|
||||||
var imported = assembly.MainModule.ImportReference(dataType);
|
{
|
||||||
readFuncs[imported] = methodReference;
|
if (readFuncs.ContainsKey(dataType))
|
||||||
}
|
{
|
||||||
|
// TODO enable this again later.
|
||||||
|
// Reader has some obsolete functions that were renamed.
|
||||||
|
// Don't want weaver warnings for all of them.
|
||||||
|
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
|
||||||
|
}
|
||||||
|
|
||||||
private void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
|
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
|
||||||
{
|
TypeReference imported = assembly.MainModule.ImportReference(dataType);
|
||||||
Register(typeReference, newReaderFunc);
|
readFuncs[imported] = methodReference;
|
||||||
GeneratedCodeClass.Methods.Add(newReaderFunc);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Finds existing reader for type, if non exists trys to create one
|
void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
|
||||||
public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
|
{
|
||||||
{
|
Register(typeReference, newReaderFunc);
|
||||||
if (readFuncs.TryGetValue(variable, out var foundFunc))
|
GeneratedCodeClass.Methods.Add(newReaderFunc);
|
||||||
{
|
}
|
||||||
return foundFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
var importedVariable = assembly.MainModule.ImportReference(variable);
|
// Finds existing reader for type, if non exists trys to create one
|
||||||
return GenerateReader(importedVariable, ref WeavingFailed);
|
public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||||
}
|
{
|
||||||
|
if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
|
||||||
|
return foundFunc;
|
||||||
|
|
||||||
private MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed)
|
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
|
||||||
{
|
return GenerateReader(importedVariable, ref WeavingFailed);
|
||||||
// Arrays are special, if we resolve them, we get the element type,
|
}
|
||||||
// so the following ifs might choke on it for scriptable objects
|
|
||||||
// or other objects that require a custom serializer
|
|
||||||
// thus check if it is an array and skip all the checks.
|
|
||||||
if (variableReference.IsArray)
|
|
||||||
{
|
|
||||||
if (variableReference.IsMultidimensionalArray())
|
|
||||||
{
|
|
||||||
Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
|
MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed)
|
||||||
}
|
{
|
||||||
|
// Arrays are special, if we resolve them, we get the element type,
|
||||||
|
// so the following ifs might choke on it for scriptable objects
|
||||||
|
// or other objects that require a custom serializer
|
||||||
|
// thus check if it is an array and skip all the checks.
|
||||||
|
if (variableReference.IsArray)
|
||||||
|
{
|
||||||
|
if (variableReference.IsMultidimensionalArray())
|
||||||
|
{
|
||||||
|
Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var variableDefinition = variableReference.Resolve();
|
return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
|
||||||
|
}
|
||||||
|
|
||||||
// check if the type is completely invalid
|
TypeDefinition variableDefinition = variableReference.Resolve();
|
||||||
if (variableDefinition == null)
|
|
||||||
{
|
|
||||||
Log.Error($"{variableReference.Name} is not a supported type", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (variableReference.IsByReference)
|
|
||||||
{
|
|
||||||
// error??
|
|
||||||
Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use existing func for known types
|
// check if the type is completely invalid
|
||||||
if (variableDefinition.IsEnum)
|
if (variableDefinition == null)
|
||||||
{
|
{
|
||||||
return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
|
Log.Error($"{variableReference.Name} is not a supported type", variableReference);
|
||||||
}
|
WeavingFailed = true;
|
||||||
else if (variableDefinition.Is(typeof(ArraySegment<>)))
|
return null;
|
||||||
{
|
}
|
||||||
return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed);
|
else if (variableReference.IsByReference)
|
||||||
}
|
{
|
||||||
else if (variableDefinition.Is(typeof(List<>)))
|
// error??
|
||||||
{
|
Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
|
||||||
var genericInstance = (GenericInstanceType)variableReference;
|
WeavingFailed = true;
|
||||||
var elementType = genericInstance.GenericArguments[0];
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
|
// use existing func for known types
|
||||||
}
|
if (variableDefinition.IsEnum)
|
||||||
else if (variableReference.IsDerivedFrom<NetworkBehaviour>())
|
{
|
||||||
{
|
return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
|
||||||
return GetNetworkBehaviourReader(variableReference);
|
}
|
||||||
}
|
else if (variableDefinition.Is(typeof(ArraySegment<>)))
|
||||||
|
{
|
||||||
|
return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed);
|
||||||
|
}
|
||||||
|
else if (variableDefinition.Is(typeof(List<>)))
|
||||||
|
{
|
||||||
|
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||||
|
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||||
|
|
||||||
// check if reader generation is applicable on this type
|
return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
|
||||||
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
|
}
|
||||||
{
|
else if (variableReference.IsDerivedFrom<NetworkBehaviour>())
|
||||||
Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
{
|
||||||
WeavingFailed = true;
|
return GetNetworkBehaviourReader(variableReference);
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
if (variableReference.Is<UnityEngine.Object>())
|
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (variableReference.Is<UnityEngine.ScriptableObject>())
|
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (variableDefinition.HasGenericParameters)
|
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (variableDefinition.IsInterface)
|
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (variableDefinition.IsAbstract)
|
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
|
// check if reader generation is applicable on this type
|
||||||
}
|
if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (variableReference.Is<UnityEngine.Object>())
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (variableReference.Is<UnityEngine.ScriptableObject>())
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (variableDefinition.HasGenericParameters)
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (variableDefinition.IsInterface)
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (variableDefinition.IsAbstract)
|
||||||
|
{
|
||||||
|
Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
|
return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
|
||||||
{
|
}
|
||||||
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
|
|
||||||
var generic = weaverTypes.readNetworkBehaviourGeneric;
|
|
||||||
|
|
||||||
var readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
|
MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
|
||||||
|
{
|
||||||
|
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
|
||||||
|
MethodReference generic = weaverTypes.readNetworkBehaviourGeneric;
|
||||||
|
|
||||||
// register function so it is added to Reader<T>
|
MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
|
||||||
// use Register instead of RegisterWriteFunc because this is not a generated function
|
|
||||||
Register(variableReference, readFunc);
|
|
||||||
|
|
||||||
return readFunc;
|
// register function so it is added to Reader<T>
|
||||||
}
|
// use Register instead of RegisterWriteFunc because this is not a generated function
|
||||||
|
Register(variableReference, readFunc);
|
||||||
|
|
||||||
private MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
|
return readFunc;
|
||||||
{
|
}
|
||||||
var readerFunc = GenerateReaderFunction(variable);
|
|
||||||
|
|
||||||
var worker = readerFunc.Body.GetILProcessor();
|
MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||||
|
|
||||||
var underlyingType = variable.Resolve().GetEnumUnderlyingType();
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
var underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Call, underlyingFunc);
|
TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
|
||||||
worker.Emit(OpCodes.Ret);
|
MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
|
||||||
return readerFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Call, underlyingFunc);
|
||||||
{
|
worker.Emit(OpCodes.Ret);
|
||||||
var genericInstance = (GenericInstanceType)variable;
|
return readerFunc;
|
||||||
var elementType = genericInstance.GenericArguments[0];
|
}
|
||||||
|
|
||||||
var readerFunc = GenerateReaderFunction(variable);
|
MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
GenericInstanceType genericInstance = (GenericInstanceType)variable;
|
||||||
|
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||||
|
|
||||||
var worker = readerFunc.Body.GetILProcessor();
|
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||||
|
|
||||||
// $array = reader.Read<[T]>()
|
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||||
var arrayType = elementType.MakeArrayType();
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
|
||||||
worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
|
|
||||||
|
|
||||||
// return new ArraySegment<T>($array);
|
// $array = reader.Read<[T]>()
|
||||||
worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
|
ArrayType arrayType = elementType.MakeArrayType();
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
return readerFunc;
|
worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
|
||||||
}
|
|
||||||
|
|
||||||
private MethodDefinition GenerateReaderFunction(TypeReference variable)
|
// return new ArraySegment<T>($array);
|
||||||
{
|
worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
|
||||||
var functionName = $"_Read_{variable.FullName}";
|
worker.Emit(OpCodes.Ret);
|
||||||
|
return readerFunc;
|
||||||
|
}
|
||||||
|
|
||||||
// create new reader for this type
|
MethodDefinition GenerateReaderFunction(TypeReference variable)
|
||||||
var readerFunc = new MethodDefinition(functionName,
|
{
|
||||||
MethodAttributes.Public |
|
string functionName = $"_Read_{variable.FullName}";
|
||||||
MethodAttributes.Static |
|
|
||||||
MethodAttributes.HideBySig,
|
|
||||||
variable);
|
|
||||||
|
|
||||||
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
|
// create new reader for this type
|
||||||
readerFunc.Body.InitLocals = true;
|
MethodDefinition readerFunc = new MethodDefinition(functionName,
|
||||||
RegisterReadFunc(variable, readerFunc);
|
MethodAttributes.Public |
|
||||||
|
MethodAttributes.Static |
|
||||||
|
MethodAttributes.HideBySig,
|
||||||
|
variable);
|
||||||
|
|
||||||
return readerFunc;
|
readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
|
||||||
}
|
readerFunc.Body.InitLocals = true;
|
||||||
|
RegisterReadFunc(variable, readerFunc);
|
||||||
|
|
||||||
private MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
|
return readerFunc;
|
||||||
{
|
}
|
||||||
var readerFunc = GenerateReaderFunction(variable);
|
|
||||||
// generate readers for the element
|
|
||||||
GetReadFunc(elementType, ref WeavingFailed);
|
|
||||||
|
|
||||||
var module = assembly.MainModule;
|
MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
|
||||||
var readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
|
{
|
||||||
var listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
|
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||||
|
// generate readers for the element
|
||||||
|
GetReadFunc(elementType, ref WeavingFailed);
|
||||||
|
|
||||||
var methodRef = new GenericInstanceMethod(listReader);
|
ModuleDefinition module = assembly.MainModule;
|
||||||
methodRef.GenericArguments.Add(elementType);
|
TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
|
||||||
|
MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
|
||||||
|
|
||||||
// generates
|
GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
|
||||||
// return reader.ReadList<T>();
|
methodRef.GenericArguments.Add(elementType);
|
||||||
|
|
||||||
var worker = readerFunc.Body.GetILProcessor();
|
// generates
|
||||||
worker.Emit(OpCodes.Ldarg_0); // reader
|
// return reader.ReadList<T>();
|
||||||
worker.Emit(OpCodes.Call, methodRef); // Read
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||||
|
worker.Emit(OpCodes.Ldarg_0); // reader
|
||||||
|
worker.Emit(OpCodes.Call, methodRef); // Read
|
||||||
|
|
||||||
return readerFunc;
|
worker.Emit(OpCodes.Ret);
|
||||||
}
|
|
||||||
|
|
||||||
private MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
|
return readerFunc;
|
||||||
{
|
}
|
||||||
var readerFunc = GenerateReaderFunction(variable);
|
|
||||||
|
|
||||||
// create local for return value
|
MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
|
||||||
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
|
{
|
||||||
|
MethodDefinition readerFunc = GenerateReaderFunction(variable);
|
||||||
|
|
||||||
var worker = readerFunc.Body.GetILProcessor();
|
// create local for return value
|
||||||
|
readerFunc.Body.Variables.Add(new VariableDefinition(variable));
|
||||||
|
|
||||||
var td = variable.Resolve();
|
ILProcessor worker = readerFunc.Body.GetILProcessor();
|
||||||
|
|
||||||
if (!td.IsValueType)
|
TypeDefinition td = variable.Resolve();
|
||||||
{
|
|
||||||
GenerateNullCheck(worker, ref WeavingFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateNew(variable, worker, td, ref WeavingFailed);
|
if (!td.IsValueType)
|
||||||
ReadAllFields(variable, worker, ref WeavingFailed);
|
GenerateNullCheck(worker, ref WeavingFailed);
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ldloc_0);
|
CreateNew(variable, worker, td, ref WeavingFailed);
|
||||||
worker.Emit(OpCodes.Ret);
|
ReadAllFields(variable, worker, ref WeavingFailed);
|
||||||
return readerFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Ldloc_0);
|
||||||
{
|
worker.Emit(OpCodes.Ret);
|
||||||
// if (!reader.ReadBoolean()) {
|
return readerFunc;
|
||||||
// return null;
|
}
|
||||||
// }
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
|
||||||
worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
|
||||||
|
|
||||||
var labelEmptyArray = worker.Create(OpCodes.Nop);
|
void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
||||||
worker.Emit(OpCodes.Brtrue, labelEmptyArray);
|
{
|
||||||
// return null
|
// if (!reader.ReadBoolean()) {
|
||||||
worker.Emit(OpCodes.Ldnull);
|
// return null;
|
||||||
worker.Emit(OpCodes.Ret);
|
// }
|
||||||
worker.Append(labelEmptyArray);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
}
|
worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
||||||
|
|
||||||
// Initialize the local variable with a new instance
|
Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
|
||||||
private void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Brtrue, labelEmptyArray);
|
||||||
{
|
// return null
|
||||||
if (variable.IsValueType)
|
worker.Emit(OpCodes.Ldnull);
|
||||||
{
|
worker.Emit(OpCodes.Ret);
|
||||||
// structs are created with Initobj
|
worker.Append(labelEmptyArray);
|
||||||
worker.Emit(OpCodes.Ldloca, 0);
|
}
|
||||||
worker.Emit(OpCodes.Initobj, variable);
|
|
||||||
}
|
|
||||||
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
|
|
||||||
{
|
|
||||||
var genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod);
|
|
||||||
genericInstanceMethod.GenericArguments.Add(variable);
|
|
||||||
worker.Emit(OpCodes.Call, genericInstanceMethod);
|
|
||||||
worker.Emit(OpCodes.Stloc_0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// classes are created with their constructor
|
|
||||||
var ctor = Resolvers.ResolveDefaultPublicCtor(variable);
|
|
||||||
if (ctor == null)
|
|
||||||
{
|
|
||||||
Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ctorRef = assembly.MainModule.ImportReference(ctor);
|
// Initialize the local variable with a new instance
|
||||||
|
void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
if (variable.IsValueType)
|
||||||
|
{
|
||||||
|
// structs are created with Initobj
|
||||||
|
worker.Emit(OpCodes.Ldloca, 0);
|
||||||
|
worker.Emit(OpCodes.Initobj, variable);
|
||||||
|
}
|
||||||
|
else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
|
||||||
|
{
|
||||||
|
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod);
|
||||||
|
genericInstanceMethod.GenericArguments.Add(variable);
|
||||||
|
worker.Emit(OpCodes.Call, genericInstanceMethod);
|
||||||
|
worker.Emit(OpCodes.Stloc_0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// classes are created with their constructor
|
||||||
|
MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
|
||||||
|
if (ctor == null)
|
||||||
|
{
|
||||||
|
Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
worker.Emit(OpCodes.Newobj, ctorRef);
|
MethodReference ctorRef = assembly.MainModule.ImportReference(ctor);
|
||||||
worker.Emit(OpCodes.Stloc_0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Newobj, ctorRef);
|
||||||
{
|
worker.Emit(OpCodes.Stloc_0);
|
||||||
foreach (var field in variable.FindAllPublicFields())
|
}
|
||||||
{
|
}
|
||||||
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
|
|
||||||
var opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
|
|
||||||
worker.Emit(opcode, 0);
|
|
||||||
var readFunc = GetReadFunc(field.FieldType, ref WeavingFailed);
|
|
||||||
if (readFunc != null)
|
|
||||||
{
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
|
||||||
worker.Emit(OpCodes.Call, readFunc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Error($"{field.Name} has an unsupported type", field);
|
|
||||||
WeavingFailed = true;
|
|
||||||
}
|
|
||||||
var fieldRef = assembly.MainModule.ImportReference(field);
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Stfld, fieldRef);
|
void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
||||||
}
|
{
|
||||||
}
|
foreach (FieldDefinition field in variable.FindAllPublicFields())
|
||||||
|
{
|
||||||
|
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
|
||||||
|
OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
|
||||||
|
worker.Emit(opcode, 0);
|
||||||
|
MethodReference readFunc = GetReadFunc(field.FieldType, ref WeavingFailed);
|
||||||
|
if (readFunc != null)
|
||||||
|
{
|
||||||
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
|
worker.Emit(OpCodes.Call, readFunc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error($"{field.Name} has an unsupported type", field);
|
||||||
|
WeavingFailed = true;
|
||||||
|
}
|
||||||
|
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
|
||||||
|
|
||||||
// Save a delegate for each one of the readers into Reader<T>.read
|
worker.Emit(OpCodes.Stfld, fieldRef);
|
||||||
internal void InitializeReaders(ILProcessor worker)
|
}
|
||||||
{
|
}
|
||||||
var module = assembly.MainModule;
|
|
||||||
|
|
||||||
var genericReaderClassRef = module.ImportReference(typeof(Reader<>));
|
// Save a delegate for each one of the readers into Reader<T>.read
|
||||||
|
internal void InitializeReaders(ILProcessor worker)
|
||||||
|
{
|
||||||
|
ModuleDefinition module = assembly.MainModule;
|
||||||
|
|
||||||
var fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
|
TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
|
||||||
var fieldRef = module.ImportReference(fieldInfo);
|
|
||||||
var networkReaderRef = module.ImportReference(typeof(NetworkReader));
|
|
||||||
var funcRef = module.ImportReference(typeof(Func<,>));
|
|
||||||
var funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
|
|
||||||
|
|
||||||
foreach (var kvp in readFuncs)
|
System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
|
||||||
{
|
FieldReference fieldRef = module.ImportReference(fieldInfo);
|
||||||
var targetType = kvp.Key;
|
TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
|
||||||
var readFunc = kvp.Value;
|
TypeReference funcRef = module.ImportReference(typeof(Func<,>));
|
||||||
|
MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
|
||||||
|
|
||||||
// create a Func<NetworkReader, T> delegate
|
foreach (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
|
||||||
worker.Emit(OpCodes.Ldnull);
|
{
|
||||||
worker.Emit(OpCodes.Ldftn, readFunc);
|
TypeReference targetType = kvp.Key;
|
||||||
var funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
|
MethodReference readFunc = kvp.Value;
|
||||||
var funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
|
|
||||||
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
|
|
||||||
|
|
||||||
// save it in Reader<T>.read
|
// create a Func<NetworkReader, T> delegate
|
||||||
var genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
|
worker.Emit(OpCodes.Ldnull);
|
||||||
var specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
|
worker.Emit(OpCodes.Ldftn, readFunc);
|
||||||
worker.Emit(OpCodes.Stsfld, specializedField);
|
GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
|
||||||
}
|
MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
|
||||||
}
|
worker.Emit(OpCodes.Newobj, funcConstructorInstance);
|
||||||
}
|
|
||||||
|
// save it in Reader<T>.read
|
||||||
|
GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
|
||||||
|
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
|
||||||
|
worker.Emit(OpCodes.Stsfld, specializedField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,82 +8,82 @@ using Mono.Cecil;
|
|||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
public static class Resolvers
|
public static class Resolvers
|
||||||
{
|
{
|
||||||
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
|
public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition assembly, Logger Log, string name, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
if (tr == null)
|
if (tr == null)
|
||||||
{
|
{
|
||||||
Log.Error($"Cannot resolve method {name} without a class");
|
Log.Error($"Cannot resolve method {name} without a class");
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
|
MethodReference method = ResolveMethod(tr, assembly, Log, m => m.Name == name, ref WeavingFailed);
|
||||||
if (method == null)
|
if (method == null)
|
||||||
{
|
{
|
||||||
Log.Error($"Method not found with name {name} in type {tr.Name}", tr);
|
Log.Error($"Method not found with name {name} in type {tr.Name}", tr);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
}
|
}
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed)
|
public static MethodReference ResolveMethod(TypeReference t, AssemblyDefinition assembly, Logger Log, System.Func<MethodDefinition, bool> predicate, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
foreach (var methodRef in t.Resolve().Methods)
|
foreach (MethodDefinition methodRef in t.Resolve().Methods)
|
||||||
{
|
{
|
||||||
if (predicate(methodRef))
|
if (predicate(methodRef))
|
||||||
{
|
{
|
||||||
return assembly.MainModule.ImportReference(methodRef);
|
return assembly.MainModule.ImportReference(methodRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Error($"Method not found in type {t.Name}", t);
|
Log.Error($"Method not found in type {t.Name}", t);
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name)
|
public static MethodReference TryResolveMethodInParents(TypeReference tr, AssemblyDefinition assembly, string name)
|
||||||
{
|
{
|
||||||
if (tr == null)
|
if (tr == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
foreach (var methodRef in tr.Resolve().Methods)
|
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
|
||||||
{
|
{
|
||||||
if (methodRef.Name == name)
|
if (methodRef.Name == name)
|
||||||
{
|
{
|
||||||
return assembly.MainModule.ImportReference(methodRef);
|
return assembly.MainModule.ImportReference(methodRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could not find the method in this class, try the parent
|
// Could not find the method in this class, try the parent
|
||||||
return TryResolveMethodInParents(tr.Resolve().BaseType, assembly, name);
|
return TryResolveMethodInParents(tr.Resolve().BaseType, assembly, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
|
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
|
||||||
{
|
{
|
||||||
foreach (var methodRef in variable.Resolve().Methods)
|
foreach (MethodDefinition methodRef in variable.Resolve().Methods)
|
||||||
{
|
{
|
||||||
if (methodRef.Name == ".ctor" &&
|
if (methodRef.Name == ".ctor" &&
|
||||||
methodRef.Resolve().IsPublic &&
|
methodRef.Resolve().IsPublic &&
|
||||||
methodRef.Parameters.Count == 0)
|
methodRef.Parameters.Count == 0)
|
||||||
{
|
{
|
||||||
return methodRef;
|
return methodRef;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name)
|
public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition assembly, string name)
|
||||||
{
|
{
|
||||||
foreach (var pd in tr.Resolve().Properties)
|
foreach (PropertyDefinition pd in tr.Resolve().Properties)
|
||||||
{
|
{
|
||||||
if (pd.Name == name)
|
if (pd.Name == name)
|
||||||
{
|
{
|
||||||
return assembly.MainModule.ImportReference(pd.GetMethod);
|
return assembly.MainModule.ImportReference(pd.GetMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,32 @@
|
|||||||
// tracks SyncVar read/write access when processing NetworkBehaviour,
|
// tracks SyncVar read/write access when processing NetworkBehaviour,
|
||||||
// to later be replaced by SyncVarAccessReplacer.
|
// to later be replaced by SyncVarAccessReplacer.
|
||||||
using Mono.Cecil;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// This data is flushed each time - if we are run multiple times in the same process/domain
|
// This data is flushed each time - if we are run multiple times in the same process/domain
|
||||||
public class SyncVarAccessLists
|
public class SyncVarAccessLists
|
||||||
{
|
{
|
||||||
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
||||||
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties =
|
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties =
|
||||||
new Dictionary<FieldDefinition, MethodDefinition>();
|
new Dictionary<FieldDefinition, MethodDefinition>();
|
||||||
|
|
||||||
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
|
||||||
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties =
|
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties =
|
||||||
new Dictionary<FieldDefinition, MethodDefinition>();
|
new Dictionary<FieldDefinition, MethodDefinition>();
|
||||||
|
|
||||||
// amount of SyncVars per class. dict<className, amount>
|
// amount of SyncVars per class. dict<className, amount>
|
||||||
// necessary for SyncVar dirty bits, where inheriting classes start
|
// necessary for SyncVar dirty bits, where inheriting classes start
|
||||||
// their dirty bits at base class SyncVar amount.
|
// their dirty bits at base class SyncVar amount.
|
||||||
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
|
public Dictionary<string, int> numSyncVars = new Dictionary<string, int>();
|
||||||
|
|
||||||
public int GetSyncVarStart(string className) =>
|
public int GetSyncVarStart(string className) =>
|
||||||
numSyncVars.TryGetValue(className, out var value) ? value : 0;
|
numSyncVars.TryGetValue(className, out int value) ? value : 0;
|
||||||
|
|
||||||
public void SetNumSyncVars(string className, int num) => numSyncVars[className] = num;
|
public void SetNumSyncVars(string className, int num)
|
||||||
}
|
{
|
||||||
|
numSyncVars[className] = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// Compares TypeReference using FullName
|
// Compares TypeReference using FullName
|
||||||
public class TypeReferenceComparer : IEqualityComparer<TypeReference>
|
public class TypeReferenceComparer : IEqualityComparer<TypeReference>
|
||||||
{
|
{
|
||||||
public bool Equals(TypeReference x, TypeReference y) =>
|
public bool Equals(TypeReference x, TypeReference y) =>
|
||||||
x.FullName == y.FullName;
|
x.FullName == y.FullName;
|
||||||
|
|
||||||
public int GetHashCode(TypeReference obj) =>
|
public int GetHashCode(TypeReference obj) =>
|
||||||
obj.FullName.GetHashCode();
|
obj.FullName.GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,213 +1,214 @@
|
|||||||
using MirrorWeaver;
|
using MirrorWeaver;
|
||||||
using Mono.Cecil;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
internal class Weaver
|
internal class Weaver
|
||||||
{
|
{
|
||||||
public const string InvokeRpcPrefix = "InvokeUserCode_";
|
public const string InvokeRpcPrefix = "InvokeUserCode_";
|
||||||
|
|
||||||
// generated code class
|
// generated code class
|
||||||
public const string GeneratedCodeNamespace = "Mirror";
|
public const string GeneratedCodeNamespace = "Mirror";
|
||||||
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
|
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
|
||||||
private TypeDefinition GeneratedCodeClass;
|
TypeDefinition GeneratedCodeClass;
|
||||||
|
|
||||||
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know
|
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know
|
||||||
// Mirror.dll name
|
// Mirror.dll name
|
||||||
public const string MirrorAssemblyName = "Mirror";
|
public const string MirrorAssemblyName = "Mirror";
|
||||||
private WeaverTypes weaverTypes;
|
|
||||||
private SyncVarAccessLists syncVarAccessLists;
|
|
||||||
private AssemblyDefinition CurrentAssembly;
|
|
||||||
private Writers writers;
|
|
||||||
private Readers readers;
|
|
||||||
private bool WeavingFailed;
|
|
||||||
|
|
||||||
// logger functions can be set from the outside.
|
WeaverTypes weaverTypes;
|
||||||
// for example, Debug.Log or ILPostProcessor Diagnostics log for
|
SyncVarAccessLists syncVarAccessLists;
|
||||||
// multi threaded logging.
|
AssemblyDefinition CurrentAssembly;
|
||||||
public Logger Log;
|
Writers writers;
|
||||||
|
Readers readers;
|
||||||
|
bool WeavingFailed;
|
||||||
|
|
||||||
public Weaver(Logger Log) => this.Log = Log;
|
// logger functions can be set from the outside.
|
||||||
|
// for example, Debug.Log or ILPostProcessor Diagnostics log for
|
||||||
|
// multi threaded logging.
|
||||||
|
public Logger Log;
|
||||||
|
|
||||||
// returns 'true' if modified (=if we did anything)
|
public Weaver(Logger Log)
|
||||||
private bool WeaveNetworkBehavior(TypeDefinition td)
|
{
|
||||||
{
|
this.Log = Log;
|
||||||
if (!td.IsClass)
|
}
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!td.IsDerivedFrom<NetworkBehaviour>())
|
// returns 'true' if modified (=if we did anything)
|
||||||
{
|
bool WeaveNetworkBehavior(TypeDefinition td)
|
||||||
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
|
{
|
||||||
{
|
if (!td.IsClass)
|
||||||
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
if (!td.IsDerivedFrom<NetworkBehaviour>())
|
||||||
}
|
{
|
||||||
|
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
|
||||||
|
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// process this and base classes from parent to child order
|
// process this and base classes from parent to child order
|
||||||
|
|
||||||
var behaviourClasses = new List<TypeDefinition>();
|
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
|
||||||
|
|
||||||
var parent = td;
|
TypeDefinition parent = td;
|
||||||
while (parent != null)
|
while (parent != null)
|
||||||
{
|
{
|
||||||
if (parent.Is<NetworkBehaviour>())
|
if (parent.Is<NetworkBehaviour>())
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
behaviourClasses.Insert(0, parent);
|
behaviourClasses.Insert(0, parent);
|
||||||
parent = parent.BaseType.Resolve();
|
parent = parent.BaseType.Resolve();
|
||||||
}
|
}
|
||||||
catch (AssemblyResolutionException)
|
catch (AssemblyResolutionException)
|
||||||
{
|
{
|
||||||
// this can happen for plugins.
|
// this can happen for plugins.
|
||||||
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
foreach (var behaviour in behaviourClasses)
|
foreach (TypeDefinition behaviour in behaviourClasses)
|
||||||
{
|
{
|
||||||
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
|
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool WeaveModule(ModuleDefinition moduleDefinition)
|
bool WeaveModule(ModuleDefinition moduleDefinition)
|
||||||
{
|
{
|
||||||
var modified = false;
|
bool modified = false;
|
||||||
|
|
||||||
var watch = Stopwatch.StartNew();
|
Stopwatch watch = Stopwatch.StartNew();
|
||||||
watch.Start();
|
watch.Start();
|
||||||
|
|
||||||
foreach (var td in moduleDefinition.Types)
|
foreach (TypeDefinition td in moduleDefinition.Types)
|
||||||
{
|
{
|
||||||
if (td.IsClass && td.BaseType.CanBeResolved())
|
if (td.IsClass && td.BaseType.CanBeResolved())
|
||||||
{
|
{
|
||||||
modified |= WeaveNetworkBehavior(td);
|
modified |= WeaveNetworkBehavior(td);
|
||||||
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
|
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch.Stop();
|
watch.Stop();
|
||||||
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
|
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
|
||||||
|
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateGeneratedCodeClass() =>
|
void CreateGeneratedCodeClass()
|
||||||
// create "Mirror.GeneratedNetworkCode" class which holds all
|
{
|
||||||
// Readers<T> and Writers<T>
|
// create "Mirror.GeneratedNetworkCode" class which holds all
|
||||||
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
|
// Readers<T> and Writers<T>
|
||||||
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
|
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
|
||||||
weaverTypes.Import<object>());
|
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
|
||||||
|
weaverTypes.Import<object>());
|
||||||
|
}
|
||||||
|
|
||||||
// Weave takes an AssemblyDefinition to be compatible with both old and
|
// Weave takes an AssemblyDefinition to be compatible with both old and
|
||||||
// new weavers:
|
// new weavers:
|
||||||
// * old takes a filepath, new takes a in-memory byte[]
|
// * old takes a filepath, new takes a in-memory byte[]
|
||||||
// * old uses DefaultAssemblyResolver with added dependencies paths,
|
// * old uses DefaultAssemblyResolver with added dependencies paths,
|
||||||
// new uses ...?
|
// new uses ...?
|
||||||
//
|
//
|
||||||
// => assembly: the one we are currently weaving (MyGame.dll)
|
// => assembly: the one we are currently weaving (MyGame.dll)
|
||||||
// => resolver: useful in case we need to resolve any of the assembly's
|
// => resolver: useful in case we need to resolve any of the assembly's
|
||||||
// assembly.MainModule.AssemblyReferences.
|
// assembly.MainModule.AssemblyReferences.
|
||||||
// -> we can resolve ANY of them given that the resolver
|
// -> we can resolve ANY of them given that the resolver
|
||||||
// works properly (need custom one for ILPostProcessor)
|
// works properly (need custom one for ILPostProcessor)
|
||||||
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
|
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
|
||||||
// those from assembly.MainModule.AssemblyReferences are
|
// those from assembly.MainModule.AssemblyReferences are
|
||||||
// guaranteed to be resolve-able.
|
// guaranteed to be resolve-able.
|
||||||
// Parsing from a string for Library/.../Mirror.dll
|
// Parsing from a string for Library/.../Mirror.dll
|
||||||
// would not be guaranteed to be resolve-able because
|
// would not be guaranteed to be resolve-able because
|
||||||
// for ILPostProcessor we can't assume where Mirror.dll
|
// for ILPostProcessor we can't assume where Mirror.dll
|
||||||
// is etc.
|
// is etc.
|
||||||
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
|
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
|
||||||
{
|
{
|
||||||
WeavingFailed = false;
|
WeavingFailed = false;
|
||||||
modified = false;
|
modified = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CurrentAssembly = assembly;
|
CurrentAssembly = assembly;
|
||||||
|
|
||||||
// fix "No writer found for ..." error
|
// fix "No writer found for ..." error
|
||||||
// https://github.com/vis2k/Mirror/issues/2579
|
// https://github.com/vis2k/Mirror/issues/2579
|
||||||
// -> when restarting Unity, weaver would try to weave a DLL
|
// -> when restarting Unity, weaver would try to weave a DLL
|
||||||
// again
|
// again
|
||||||
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
|
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
|
||||||
// -> the second one wouldn't have all the writer types setup
|
// -> the second one wouldn't have all the writer types setup
|
||||||
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
|
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
|
||||||
{
|
{
|
||||||
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
|
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
|
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
|
||||||
|
|
||||||
// weaverTypes are needed for CreateGeneratedCodeClass
|
// weaverTypes are needed for CreateGeneratedCodeClass
|
||||||
CreateGeneratedCodeClass();
|
CreateGeneratedCodeClass();
|
||||||
|
|
||||||
// WeaverList depends on WeaverTypes setup because it uses Import
|
// WeaverList depends on WeaverTypes setup because it uses Import
|
||||||
syncVarAccessLists = new SyncVarAccessLists();
|
syncVarAccessLists = new SyncVarAccessLists();
|
||||||
|
|
||||||
// initialize readers & writers with this assembly.
|
// initialize readers & writers with this assembly.
|
||||||
// we need to do this in every Process() call.
|
// we need to do this in every Process() call.
|
||||||
// otherwise we would get
|
// otherwise we would get
|
||||||
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
||||||
// errors when still using the previous module's reader/writer funcs.
|
// errors when still using the previous module's reader/writer funcs.
|
||||||
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
|
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
|
||||||
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
|
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
|
||||||
|
|
||||||
var rwstopwatch = Stopwatch.StartNew();
|
Stopwatch rwstopwatch = Stopwatch.StartNew();
|
||||||
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
|
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
|
||||||
modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
|
||||||
QSBReaderWriterProcessor.Process(CurrentAssembly, writers, readers, ref WeavingFailed);
|
QSBReaderWriterProcessor.Process(CurrentAssembly, writers, readers, ref WeavingFailed);
|
||||||
rwstopwatch.Stop();
|
rwstopwatch.Stop();
|
||||||
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
|
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
|
||||||
|
|
||||||
var moduleDefinition = CurrentAssembly.MainModule;
|
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
|
||||||
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
|
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
|
||||||
|
|
||||||
modified |= WeaveModule(moduleDefinition);
|
modified |= WeaveModule(moduleDefinition);
|
||||||
|
|
||||||
if (WeavingFailed)
|
if (WeavingFailed)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modified)
|
if (modified)
|
||||||
{
|
{
|
||||||
SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists);
|
SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists);
|
||||||
|
|
||||||
// add class that holds read/write functions
|
// add class that holds read/write functions
|
||||||
moduleDefinition.Types.Add(GeneratedCodeClass);
|
moduleDefinition.Types.Add(GeneratedCodeClass);
|
||||||
|
|
||||||
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
|
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
|
||||||
|
|
||||||
// DO NOT WRITE here.
|
// DO NOT WRITE here.
|
||||||
// CompilationFinishedHook writes to the file.
|
// CompilationFinishedHook writes to the file.
|
||||||
// ILPostProcessor writes to in-memory assembly.
|
// ILPostProcessor writes to in-memory assembly.
|
||||||
// it depends on the caller.
|
// it depends on the caller.
|
||||||
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
|
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error($"Exception :{e}");
|
Log.Error($"Exception :{e}");
|
||||||
WeavingFailed = true;
|
WeavingFailed = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public abstract class WeaverException : Exception
|
public abstract class WeaverException : Exception
|
||||||
{
|
{
|
||||||
public MemberReference MemberReference { get; }
|
public MemberReference MemberReference { get; }
|
||||||
|
|
||||||
protected WeaverException(string message, MemberReference member) : base(message) => MemberReference = member;
|
protected WeaverException(string message, MemberReference member) : base(message)
|
||||||
|
{
|
||||||
|
MemberReference = member;
|
||||||
|
}
|
||||||
|
|
||||||
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
|
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class GenerateWriterException : WeaverException
|
public class GenerateWriterException : WeaverException
|
||||||
{
|
{
|
||||||
public GenerateWriterException(string message, MemberReference member) : base(message, member) { }
|
public GenerateWriterException(string message, MemberReference member) : base(message, member) {}
|
||||||
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { }
|
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,150 +1,152 @@
|
|||||||
using Mono.Cecil;
|
|
||||||
using System;
|
using System;
|
||||||
|
using Mono.Cecil;
|
||||||
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
public class WeaverTypes
|
public class WeaverTypes
|
||||||
{
|
{
|
||||||
public MethodReference ScriptableObjectCreateInstanceMethod;
|
public MethodReference ScriptableObjectCreateInstanceMethod;
|
||||||
|
|
||||||
public MethodReference NetworkBehaviourDirtyBitsReference;
|
public MethodReference NetworkBehaviourDirtyBitsReference;
|
||||||
public MethodReference GetPooledWriterReference;
|
public MethodReference GetPooledWriterReference;
|
||||||
public MethodReference RecycleWriterReference;
|
public MethodReference RecycleWriterReference;
|
||||||
|
|
||||||
public MethodReference NetworkClientConnectionReference;
|
public MethodReference NetworkClientConnectionReference;
|
||||||
|
|
||||||
public MethodReference RemoteCallDelegateConstructor;
|
public MethodReference RemoteCallDelegateConstructor;
|
||||||
|
|
||||||
public MethodReference NetworkServerGetActive;
|
public MethodReference NetworkServerGetActive;
|
||||||
public MethodReference NetworkServerGetLocalClientActive;
|
public MethodReference NetworkServerGetLocalClientActive;
|
||||||
public MethodReference NetworkClientGetActive;
|
public MethodReference NetworkClientGetActive;
|
||||||
|
|
||||||
// custom attribute types
|
// custom attribute types
|
||||||
public MethodReference InitSyncObjectReference;
|
public MethodReference InitSyncObjectReference;
|
||||||
|
|
||||||
// array segment
|
// array segment
|
||||||
public MethodReference ArraySegmentConstructorReference;
|
public MethodReference ArraySegmentConstructorReference;
|
||||||
|
|
||||||
// syncvar
|
// syncvar
|
||||||
public MethodReference syncVarEqualReference;
|
public MethodReference syncVarEqualReference;
|
||||||
public MethodReference syncVarNetworkIdentityEqualReference;
|
public MethodReference syncVarNetworkIdentityEqualReference;
|
||||||
public MethodReference syncVarGameObjectEqualReference;
|
public MethodReference syncVarGameObjectEqualReference;
|
||||||
public MethodReference setSyncVarReference;
|
public MethodReference setSyncVarReference;
|
||||||
public MethodReference setSyncVarHookGuard;
|
public MethodReference setSyncVarHookGuard;
|
||||||
public MethodReference getSyncVarHookGuard;
|
public MethodReference getSyncVarHookGuard;
|
||||||
public MethodReference setSyncVarGameObjectReference;
|
public MethodReference setSyncVarGameObjectReference;
|
||||||
public MethodReference getSyncVarGameObjectReference;
|
public MethodReference getSyncVarGameObjectReference;
|
||||||
public MethodReference setSyncVarNetworkIdentityReference;
|
public MethodReference setSyncVarNetworkIdentityReference;
|
||||||
public MethodReference getSyncVarNetworkIdentityReference;
|
public MethodReference getSyncVarNetworkIdentityReference;
|
||||||
public MethodReference syncVarNetworkBehaviourEqualReference;
|
public MethodReference syncVarNetworkBehaviourEqualReference;
|
||||||
public MethodReference setSyncVarNetworkBehaviourReference;
|
public MethodReference setSyncVarNetworkBehaviourReference;
|
||||||
public MethodReference getSyncVarNetworkBehaviourReference;
|
public MethodReference getSyncVarNetworkBehaviourReference;
|
||||||
public MethodReference registerCommandReference;
|
public MethodReference registerCommandReference;
|
||||||
public MethodReference registerRpcReference;
|
public MethodReference registerRpcReference;
|
||||||
public MethodReference getTypeFromHandleReference;
|
public MethodReference getTypeFromHandleReference;
|
||||||
public MethodReference logErrorReference;
|
public MethodReference logErrorReference;
|
||||||
public MethodReference logWarningReference;
|
public MethodReference logWarningReference;
|
||||||
public MethodReference sendCommandInternal;
|
public MethodReference sendCommandInternal;
|
||||||
public MethodReference sendRpcInternal;
|
public MethodReference sendRpcInternal;
|
||||||
public MethodReference sendTargetRpcInternal;
|
public MethodReference sendTargetRpcInternal;
|
||||||
|
|
||||||
public MethodReference readNetworkBehaviourGeneric;
|
public MethodReference readNetworkBehaviourGeneric;
|
||||||
|
|
||||||
// attributes
|
// attributes
|
||||||
public TypeDefinition initializeOnLoadMethodAttribute;
|
public TypeDefinition initializeOnLoadMethodAttribute;
|
||||||
public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
|
public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
|
||||||
private readonly AssemblyDefinition assembly;
|
|
||||||
|
|
||||||
public TypeReference Import<T>() => Import(typeof(T));
|
AssemblyDefinition assembly;
|
||||||
|
|
||||||
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
|
public TypeReference Import<T>() => Import(typeof(T));
|
||||||
|
|
||||||
// constructor resolves the types and stores them in fields
|
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
|
||||||
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
|
|
||||||
{
|
|
||||||
// system types
|
|
||||||
this.assembly = assembly;
|
|
||||||
|
|
||||||
var ArraySegmentType = Import(typeof(ArraySegment<>));
|
// constructor resolves the types and stores them in fields
|
||||||
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
|
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
// system types
|
||||||
|
this.assembly = assembly;
|
||||||
|
|
||||||
var NetworkServerType = Import(typeof(NetworkServer));
|
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
|
||||||
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
|
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
|
||||||
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localClientActive", ref WeavingFailed);
|
|
||||||
var NetworkClientType = Import(typeof(NetworkClient));
|
|
||||||
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
|
|
||||||
|
|
||||||
var RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
|
TypeReference NetworkServerType = Import(typeof(NetworkServer));
|
||||||
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
|
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
|
||||||
|
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localClientActive", ref WeavingFailed);
|
||||||
|
TypeReference NetworkClientType = Import(typeof(NetworkClient));
|
||||||
|
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
|
||||||
|
|
||||||
var NetworkBehaviourType = Import<NetworkBehaviour>();
|
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
|
||||||
var RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
|
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
|
||||||
|
|
||||||
var ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
|
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
|
||||||
|
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
|
||||||
|
|
||||||
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
|
TypeReference ScriptableObjectType = Import<UnityEngine.ScriptableObject>();
|
||||||
ScriptableObjectType, assembly, Log,
|
|
||||||
md => md.Name == "CreateInstance" && md.HasGenericParameters,
|
|
||||||
ref WeavingFailed);
|
|
||||||
|
|
||||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits");
|
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
|
||||||
var NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
|
ScriptableObjectType, assembly, Log,
|
||||||
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed);
|
md => md.Name == "CreateInstance" && md.HasGenericParameters,
|
||||||
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed);
|
ref WeavingFailed);
|
||||||
|
|
||||||
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
|
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits");
|
||||||
|
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
|
||||||
|
GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed);
|
||||||
|
RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed);
|
||||||
|
|
||||||
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed);
|
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
|
||||||
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkIdentityEqual", ref WeavingFailed);
|
|
||||||
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarGameObjectEqual", ref WeavingFailed);
|
|
||||||
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVar", ref WeavingFailed);
|
|
||||||
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarHookGuard", ref WeavingFailed);
|
|
||||||
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarHookGuard", ref WeavingFailed);
|
|
||||||
|
|
||||||
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarGameObject", ref WeavingFailed);
|
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed);
|
||||||
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
|
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkIdentityEqual", ref WeavingFailed);
|
||||||
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkIdentity", ref WeavingFailed);
|
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarGameObjectEqual", ref WeavingFailed);
|
||||||
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
|
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVar", ref WeavingFailed);
|
||||||
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkBehaviourEqual", ref WeavingFailed);
|
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarHookGuard", ref WeavingFailed);
|
||||||
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkBehaviour", ref WeavingFailed);
|
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarHookGuard", ref WeavingFailed);
|
||||||
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
|
|
||||||
|
|
||||||
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
|
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarGameObject", ref WeavingFailed);
|
||||||
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
|
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
|
||||||
|
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkIdentity", ref WeavingFailed);
|
||||||
|
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
|
||||||
|
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkBehaviourEqual", ref WeavingFailed);
|
||||||
|
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkBehaviour", ref WeavingFailed);
|
||||||
|
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
|
||||||
|
|
||||||
var unityDebug = Import(typeof(UnityEngine.Debug));
|
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
|
||||||
// these have multiple methods with same name, so need to check parameters too
|
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
|
||||||
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
|
||||||
md.Name == "LogError" &&
|
|
||||||
md.Parameters.Count == 1 &&
|
|
||||||
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
|
||||||
ref WeavingFailed);
|
|
||||||
|
|
||||||
logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
|
||||||
md.Name == "LogWarning" &&
|
// these have multiple methods with same name, so need to check parameters too
|
||||||
md.Parameters.Count == 1 &&
|
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
||||||
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
md.Name == "LogError" &&
|
||||||
ref WeavingFailed);
|
md.Parameters.Count == 1 &&
|
||||||
|
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
||||||
|
ref WeavingFailed);
|
||||||
|
|
||||||
var typeType = Import(typeof(Type));
|
logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
|
||||||
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
|
md.Name == "LogWarning" &&
|
||||||
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
|
md.Parameters.Count == 1 &&
|
||||||
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
|
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
|
||||||
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
|
ref WeavingFailed);
|
||||||
|
|
||||||
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
|
TypeReference typeType = Import(typeof(Type));
|
||||||
|
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
|
||||||
|
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
|
||||||
|
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
|
||||||
|
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
|
||||||
|
|
||||||
var readerExtensions = Import(typeof(NetworkReaderExtensions));
|
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
|
||||||
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
|
|
||||||
{
|
|
||||||
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
|
|
||||||
md.HasGenericParameters;
|
|
||||||
}),
|
|
||||||
ref WeavingFailed);
|
|
||||||
|
|
||||||
/*
|
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
|
||||||
|
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
|
||||||
|
{
|
||||||
|
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
|
||||||
|
md.HasGenericParameters;
|
||||||
|
}),
|
||||||
|
ref WeavingFailed);
|
||||||
|
|
||||||
|
/*
|
||||||
// [InitializeOnLoadMethod]
|
// [InitializeOnLoadMethod]
|
||||||
// 'UnityEditor' is not available in builds.
|
// 'UnityEditor' is not available in builds.
|
||||||
// we can only import this attribute if we are in an Editor assembly.
|
// we can only import this attribute if we are in an Editor assembly.
|
||||||
@ -155,9 +157,9 @@ namespace Mirror.Weaver
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// [RuntimeInitializeOnLoadMethod]
|
// [RuntimeInitializeOnLoadMethod]
|
||||||
var runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
|
TypeReference runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
|
||||||
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
|
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,345 +1,339 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
// to use Mono.Cecil.Rocks here, we need to 'override references' in the
|
// to use Mono.Cecil.Rocks here, we need to 'override references' in the
|
||||||
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
|
||||||
// otherwise we get an unknown import exception.
|
// otherwise we get an unknown import exception.
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Mirror.Weaver
|
namespace Mirror.Weaver
|
||||||
{
|
{
|
||||||
// not static, because ILPostProcessor is multithreaded
|
// not static, because ILPostProcessor is multithreaded
|
||||||
public class Writers
|
public class Writers
|
||||||
{
|
{
|
||||||
// Writers are only for this assembly.
|
// Writers are only for this assembly.
|
||||||
// can't be used from another assembly, otherwise we will get:
|
// can't be used from another assembly, otherwise we will get:
|
||||||
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
|
||||||
private readonly AssemblyDefinition assembly;
|
AssemblyDefinition assembly;
|
||||||
private readonly WeaverTypes weaverTypes;
|
WeaverTypes weaverTypes;
|
||||||
private readonly TypeDefinition GeneratedCodeClass;
|
TypeDefinition GeneratedCodeClass;
|
||||||
private readonly Logger Log;
|
Logger Log;
|
||||||
private readonly Dictionary<TypeReference, MethodReference> writeFuncs =
|
|
||||||
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
|
||||||
|
|
||||||
public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
Dictionary<TypeReference, MethodReference> writeFuncs =
|
||||||
{
|
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
|
||||||
this.assembly = assembly;
|
|
||||||
this.weaverTypes = weaverTypes;
|
|
||||||
this.GeneratedCodeClass = GeneratedCodeClass;
|
|
||||||
this.Log = Log;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Register(TypeReference dataType, MethodReference methodReference)
|
public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
|
||||||
{
|
{
|
||||||
if (writeFuncs.ContainsKey(dataType))
|
this.assembly = assembly;
|
||||||
{
|
this.weaverTypes = weaverTypes;
|
||||||
// TODO enable this again later.
|
this.GeneratedCodeClass = GeneratedCodeClass;
|
||||||
// Writer has some obsolete functions that were renamed.
|
this.Log = Log;
|
||||||
// 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
|
public void Register(TypeReference dataType, MethodReference methodReference)
|
||||||
var imported = assembly.MainModule.ImportReference(dataType);
|
{
|
||||||
writeFuncs[imported] = 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);
|
||||||
|
}
|
||||||
|
|
||||||
private void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
|
// 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);
|
||||||
Register(typeReference, newWriterFunc);
|
writeFuncs[imported] = methodReference;
|
||||||
GeneratedCodeClass.Methods.Add(newWriterFunc);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Finds existing writer for type, if non exists trys to create one
|
void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
|
||||||
public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
{
|
||||||
{
|
Register(typeReference, newWriterFunc);
|
||||||
if (writeFuncs.TryGetValue(variable, out var foundFunc))
|
GeneratedCodeClass.Methods.Add(newWriterFunc);
|
||||||
{
|
}
|
||||||
return foundFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
|
// Finds existing writer for type, if non exists trys to create one
|
||||||
try
|
public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
||||||
{
|
{
|
||||||
var importedVariable = assembly.MainModule.ImportReference(variable);
|
if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
|
||||||
return GenerateWriter(importedVariable, ref WeavingFailed);
|
return foundFunc;
|
||||||
}
|
|
||||||
catch (GenerateWriterException e)
|
|
||||||
{
|
|
||||||
Log.Error(e.Message, e.MemberReference);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Throws GenerateWriterException when writer could not be generated for type
|
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
|
||||||
private MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
|
try
|
||||||
{
|
{
|
||||||
if (variableReference.IsByReference)
|
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
|
||||||
{
|
return GenerateWriter(importedVariable, ref WeavingFailed);
|
||||||
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
|
}
|
||||||
}
|
catch (GenerateWriterException e)
|
||||||
|
{
|
||||||
|
Log.Error(e.Message, e.MemberReference);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Arrays are special, if we resolve them, we get the element type,
|
//Throws GenerateWriterException when writer could not be generated for type
|
||||||
// e.g. int[] resolves to int
|
MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
|
||||||
// therefore process this before checks below
|
{
|
||||||
if (variableReference.IsArray)
|
if (variableReference.IsByReference)
|
||||||
{
|
{
|
||||||
if (variableReference.IsMultidimensionalArray())
|
throw new GenerateWriterException($"Cannot pass {variableReference.Name} by reference", variableReference);
|
||||||
{
|
}
|
||||||
throw new GenerateWriterException($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
|
|
||||||
}
|
|
||||||
var elementType = variableReference.GetElementType();
|
|
||||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArray), ref WeavingFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variableReference.Resolve()?.IsEnum ?? false)
|
// Arrays are special, if we resolve them, we get the element type,
|
||||||
{
|
// e.g. int[] resolves to int
|
||||||
// serialize enum as their base type
|
// therefore process this before checks below
|
||||||
return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// check for collections
|
if (variableReference.Resolve()?.IsEnum ?? false)
|
||||||
if (variableReference.Is(typeof(ArraySegment<>)))
|
{
|
||||||
{
|
// serialize enum as their base type
|
||||||
var genericInstance = (GenericInstanceType)variableReference;
|
return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
|
||||||
var elementType = genericInstance.GenericArguments[0];
|
}
|
||||||
|
|
||||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
|
// check for collections
|
||||||
}
|
if (variableReference.Is(typeof(ArraySegment<>)))
|
||||||
if (variableReference.Is(typeof(List<>)))
|
{
|
||||||
{
|
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||||
var genericInstance = (GenericInstanceType)variableReference;
|
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||||
var elementType = genericInstance.GenericArguments[0];
|
|
||||||
|
|
||||||
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
|
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
|
||||||
}
|
}
|
||||||
|
if (variableReference.Is(typeof(List<>)))
|
||||||
|
{
|
||||||
|
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
|
||||||
|
TypeReference elementType = genericInstance.GenericArguments[0];
|
||||||
|
|
||||||
if (variableReference.IsDerivedFrom<NetworkBehaviour>())
|
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
|
||||||
{
|
}
|
||||||
return GetNetworkBehaviourWriter(variableReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for invalid types
|
if (variableReference.IsDerivedFrom<NetworkBehaviour>())
|
||||||
var variableDefinition = variableReference.Resolve();
|
{
|
||||||
if (variableDefinition == null)
|
return GetNetworkBehaviourWriter(variableReference);
|
||||||
{
|
}
|
||||||
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
|
// check for invalid types
|
||||||
return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
private MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
|
// generate writer for class/struct
|
||||||
{
|
return GenerateClassOrStructWriterFunction(variableReference, ref WeavingFailed);
|
||||||
// all NetworkBehaviours can use the same write function
|
}
|
||||||
if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out var 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;
|
MethodReference GetNetworkBehaviourWriter(TypeReference variableReference)
|
||||||
}
|
{
|
||||||
else
|
// all NetworkBehaviours can use the same write function
|
||||||
{
|
if (writeFuncs.TryGetValue(weaverTypes.Import<NetworkBehaviour>(), out MethodReference func))
|
||||||
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
|
{
|
||||||
throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
|
// register function so it is added to writer<T>
|
||||||
}
|
// use Register instead of RegisterWriteFunc because this is not a generated function
|
||||||
}
|
Register(variableReference, func);
|
||||||
|
|
||||||
private MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
return func;
|
||||||
{
|
}
|
||||||
var writerFunc = GenerateWriterFunc(variable);
|
else
|
||||||
|
{
|
||||||
|
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
|
||||||
|
throw new MissingMethodException($"Could not find writer for NetworkBehaviour");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var worker = writerFunc.Body.GetILProcessor();
|
MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||||
|
|
||||||
var underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
|
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
|
||||||
worker.Emit(OpCodes.Call, underlyingWriter);
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
return writerFunc;
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
}
|
worker.Emit(OpCodes.Call, underlyingWriter);
|
||||||
|
|
||||||
private MethodDefinition GenerateWriterFunc(TypeReference variable)
|
worker.Emit(OpCodes.Ret);
|
||||||
{
|
return writerFunc;
|
||||||
var functionName = $"_Write_{variable.FullName}";
|
}
|
||||||
// create new writer for this type
|
|
||||||
var 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>()));
|
MethodDefinition GenerateWriterFunc(TypeReference variable)
|
||||||
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
|
{
|
||||||
writerFunc.Body.InitLocals = true;
|
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)));
|
||||||
|
|
||||||
RegisterWriteFunc(variable, writerFunc);
|
writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, weaverTypes.Import<NetworkWriter>()));
|
||||||
return writerFunc;
|
writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
|
||||||
}
|
writerFunc.Body.InitLocals = true;
|
||||||
|
|
||||||
private MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
|
RegisterWriteFunc(variable, writerFunc);
|
||||||
{
|
return writerFunc;
|
||||||
var writerFunc = GenerateWriterFunc(variable);
|
}
|
||||||
|
|
||||||
var worker = writerFunc.Body.GetILProcessor();
|
MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||||
|
|
||||||
if (!variable.Resolve().IsValueType)
|
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||||
{
|
|
||||||
WriteNullCheck(worker, ref WeavingFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!WriteAllFields(variable, worker, ref WeavingFailed))
|
if (!variable.Resolve().IsValueType)
|
||||||
{
|
WriteNullCheck(worker, ref WeavingFailed);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
if (!WriteAllFields(variable, worker, ref WeavingFailed))
|
||||||
return writerFunc;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Ret);
|
||||||
{
|
return writerFunc;
|
||||||
// if (value == null)
|
}
|
||||||
// {
|
|
||||||
// writer.WriteBoolean(false);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
var labelNotNull = worker.Create(OpCodes.Nop);
|
void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
{
|
||||||
worker.Emit(OpCodes.Brtrue, labelNotNull);
|
// if (value == null)
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
// {
|
||||||
worker.Emit(OpCodes.Ldc_I4_0);
|
// writer.WriteBoolean(false);
|
||||||
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
// return;
|
||||||
worker.Emit(OpCodes.Ret);
|
// }
|
||||||
worker.Append(labelNotNull);
|
//
|
||||||
|
|
||||||
// write.WriteBoolean(true);
|
Instruction labelNotNull = worker.Create(OpCodes.Nop);
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
worker.Emit(OpCodes.Ldc_I4_1);
|
worker.Emit(OpCodes.Brtrue, labelNotNull);
|
||||||
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
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);
|
||||||
|
|
||||||
// Find all fields in type and write them
|
// write.WriteBoolean(true);
|
||||||
private bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
{
|
worker.Emit(OpCodes.Ldc_I4_1);
|
||||||
foreach (var field in variable.FindAllPublicFields())
|
worker.Emit(OpCodes.Call, GetWriteFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
|
||||||
{
|
}
|
||||||
var writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
|
|
||||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
|
||||||
if (writeFunc == null)
|
|
||||||
{ return false; }
|
|
||||||
|
|
||||||
var fieldRef = assembly.MainModule.ImportReference(field);
|
// Find all fields in type and write them
|
||||||
|
bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ldarg_0);
|
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
|
||||||
worker.Emit(OpCodes.Ldarg_1);
|
|
||||||
worker.Emit(OpCodes.Ldfld, fieldRef);
|
|
||||||
worker.Emit(OpCodes.Call, writeFunc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
worker.Emit(OpCodes.Ldarg_0);
|
||||||
}
|
worker.Emit(OpCodes.Ldarg_1);
|
||||||
|
worker.Emit(OpCodes.Ldfld, fieldRef);
|
||||||
|
worker.Emit(OpCodes.Call, writeFunc);
|
||||||
|
}
|
||||||
|
|
||||||
private MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
|
return true;
|
||||||
{
|
}
|
||||||
|
|
||||||
var writerFunc = GenerateWriterFunc(variable);
|
MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
|
||||||
|
{
|
||||||
|
|
||||||
var elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
|
MethodDefinition writerFunc = GenerateWriterFunc(variable);
|
||||||
var intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
|
|
||||||
|
|
||||||
// need this null check till later PR when GetWriteFunc throws exception instead
|
MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
|
||||||
if (elementWriteFunc == null)
|
MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
|
||||||
{
|
|
||||||
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
|
|
||||||
WeavingFailed = true;
|
|
||||||
return writerFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
var module = assembly.MainModule;
|
// need this null check till later PR when GetWriteFunc throws exception instead
|
||||||
var readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
|
if (elementWriteFunc == null)
|
||||||
var collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
|
{
|
||||||
|
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
|
||||||
|
WeavingFailed = true;
|
||||||
|
return writerFunc;
|
||||||
|
}
|
||||||
|
|
||||||
var methodRef = new GenericInstanceMethod(collectionWriter);
|
ModuleDefinition module = assembly.MainModule;
|
||||||
methodRef.GenericArguments.Add(elementType);
|
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
|
||||||
|
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
|
||||||
|
|
||||||
// generates
|
GenericInstanceMethod methodRef = new GenericInstanceMethod(collectionWriter);
|
||||||
// reader.WriteArray<T>(array);
|
methodRef.GenericArguments.Add(elementType);
|
||||||
|
|
||||||
var worker = writerFunc.Body.GetILProcessor();
|
// generates
|
||||||
worker.Emit(OpCodes.Ldarg_0); // writer
|
// reader.WriteArray<T>(array);
|
||||||
worker.Emit(OpCodes.Ldarg_1); // collection
|
|
||||||
|
|
||||||
worker.Emit(OpCodes.Call, methodRef); // WriteArray
|
ILProcessor worker = writerFunc.Body.GetILProcessor();
|
||||||
|
worker.Emit(OpCodes.Ldarg_0); // writer
|
||||||
|
worker.Emit(OpCodes.Ldarg_1); // collection
|
||||||
|
|
||||||
worker.Emit(OpCodes.Ret);
|
worker.Emit(OpCodes.Call, methodRef); // WriteArray
|
||||||
|
|
||||||
return writerFunc;
|
worker.Emit(OpCodes.Ret);
|
||||||
}
|
|
||||||
|
|
||||||
// Save a delegate for each one of the writers into Writer{T}.write
|
return writerFunc;
|
||||||
internal void InitializeWriters(ILProcessor worker)
|
}
|
||||||
{
|
|
||||||
var module = assembly.MainModule;
|
|
||||||
|
|
||||||
var genericWriterClassRef = module.ImportReference(typeof(Writer<>));
|
// Save a delegate for each one of the writers into Writer{T}.write
|
||||||
|
internal void InitializeWriters(ILProcessor worker)
|
||||||
|
{
|
||||||
|
ModuleDefinition module = assembly.MainModule;
|
||||||
|
|
||||||
var fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
|
TypeReference genericWriterClassRef = module.ImportReference(typeof(Writer<>));
|
||||||
var fieldRef = module.ImportReference(fieldInfo);
|
|
||||||
var networkWriterRef = module.ImportReference(typeof(NetworkWriter));
|
|
||||||
var actionRef = module.ImportReference(typeof(Action<,>));
|
|
||||||
var actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
|
|
||||||
|
|
||||||
foreach (var kvp in writeFuncs)
|
System.Reflection.FieldInfo fieldInfo = typeof(Writer<>).GetField(nameof(Writer<object>.write));
|
||||||
{
|
FieldReference fieldRef = module.ImportReference(fieldInfo);
|
||||||
var targetType = kvp.Key;
|
TypeReference networkWriterRef = module.ImportReference(typeof(NetworkWriter));
|
||||||
var writeFunc = kvp.Value;
|
TypeReference actionRef = module.ImportReference(typeof(Action<,>));
|
||||||
|
MethodReference actionConstructorRef = module.ImportReference(typeof(Action<,>).GetConstructors()[0]);
|
||||||
|
|
||||||
// create a Action<NetworkWriter, T> delegate
|
foreach (KeyValuePair<TypeReference, MethodReference> kvp in writeFuncs)
|
||||||
worker.Emit(OpCodes.Ldnull);
|
{
|
||||||
worker.Emit(OpCodes.Ldftn, writeFunc);
|
TypeReference targetType = kvp.Key;
|
||||||
var actionGenericInstance = actionRef.MakeGenericInstanceType(networkWriterRef, targetType);
|
MethodReference writeFunc = kvp.Value;
|
||||||
var actionRefInstance = actionConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, actionGenericInstance);
|
|
||||||
worker.Emit(OpCodes.Newobj, actionRefInstance);
|
|
||||||
|
|
||||||
// save it in Writer<T>.write
|
// create a Action<NetworkWriter, T> delegate
|
||||||
var genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
|
worker.Emit(OpCodes.Ldnull);
|
||||||
var specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
|
worker.Emit(OpCodes.Ldftn, writeFunc);
|
||||||
worker.Emit(OpCodes.Stsfld, specializedField);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user