|
|
#if !FUSION_DEV
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/AssemblyInfo.cs
|
|
|
|
|
|
[assembly: Fusion.NetworkAssemblyIgnore]
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ForLoopMacro.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Reflection;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using static ILWeaverOpCodes;
|
|
|
using MethodBody = Mono.Cecil.Cil.MethodBody;
|
|
|
|
|
|
public readonly struct NewArrayWithLengthEqualToOtherArrayOrZero : ILProcessorMacro {
|
|
|
public readonly Action<ILProcessor> GetArray;
|
|
|
public readonly TypeReference ArrayElementType;
|
|
|
|
|
|
public NewArrayWithLengthEqualToOtherArrayOrZero(TypeReference arrayElementType, Action<ILProcessor> getArray) {
|
|
|
GetArray = getArray;
|
|
|
ArrayElementType = arrayElementType;
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
var brNotNul = Nop();
|
|
|
var brNewArr = Nop();
|
|
|
|
|
|
il.Append(Dup());
|
|
|
il.Append(Brtrue_S(brNotNul));
|
|
|
il.Append(Pop());
|
|
|
il.Append(Ldc_I4(0));
|
|
|
il.Append(Br_S(brNewArr));
|
|
|
|
|
|
il.Append(brNotNul);
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
|
|
|
il.Append(brNewArr);
|
|
|
il.Append(Newarr(ArrayElementType));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public readonly struct GetCollectionCountOrZero : ILProcessorMacro {
|
|
|
public readonly TypeReference CollectionType;
|
|
|
|
|
|
public GetCollectionCountOrZero(TypeReference collectionType) {
|
|
|
CollectionType = collectionType;
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
|
|
|
var brNotNul = Nop();
|
|
|
var done = Nop();
|
|
|
|
|
|
il.Append(Dup());
|
|
|
il.Append(Brtrue_S(brNotNul));
|
|
|
il.Append(Pop());
|
|
|
il.Append(Ldc_I4(0));
|
|
|
il.Append(Br_S(done));
|
|
|
|
|
|
il.Append(brNotNul);
|
|
|
il.Append(Call(new MethodReference("get_Count", il.Body.Method.Module.TypeSystem.Int32, CollectionType) { HasThis = true }));
|
|
|
|
|
|
il.Append(done);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public readonly struct ForLoopMacro : ILProcessorMacro {
|
|
|
public readonly MethodBody Body;
|
|
|
public readonly Action<ILProcessor, VariableDefinition> Generator;
|
|
|
public readonly Action<ILProcessor> Start;
|
|
|
public readonly Action<ILProcessor> Stop;
|
|
|
|
|
|
public ForLoopMacro(MethodBody body, Action<ILProcessor, VariableDefinition> generator, Action<ILProcessor> start, Action<ILProcessor> stop) {
|
|
|
Body = body;
|
|
|
Generator = generator;
|
|
|
Start = start;
|
|
|
Stop = stop;
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
var varId = Body.Variables.Count;
|
|
|
var indexVariable = new VariableDefinition(Body.Method.Module.TypeSystem.Int32);
|
|
|
Body.Variables.Add(indexVariable);
|
|
|
|
|
|
Start(il);
|
|
|
il.Append(Stloc(Body, varId));
|
|
|
|
|
|
var loopConditionStart = Ldloc(Body, varId);
|
|
|
il.Append(Br_S(loopConditionStart));
|
|
|
{
|
|
|
var loopBodyBegin = il.AppendReturn(Nop());
|
|
|
Generator(il, indexVariable);
|
|
|
|
|
|
il.Append(Ldloc(Body, varId));
|
|
|
il.Append(Ldc_I4(1));
|
|
|
il.Append(Add());
|
|
|
il.Append(Stloc(Body, varId));
|
|
|
|
|
|
il.Append(loopConditionStart);
|
|
|
Stop(il);
|
|
|
il.Append(Blt_S(loopBodyBegin));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public readonly struct DictionaryForEachMacro : ILProcessorMacro {
|
|
|
public readonly MethodBody Body;
|
|
|
public readonly Action<ILProcessor, VariableDefinition> Generator;
|
|
|
public readonly TypeReference EnumerableType;
|
|
|
public readonly ModuleDefinition Module;
|
|
|
|
|
|
public DictionaryForEachMacro(ModuleDefinition module, MethodBody body, Action<ILProcessor, VariableDefinition> generator, TypeReference enumerableType) {
|
|
|
Module = module;
|
|
|
Body = body;
|
|
|
Generator = generator;
|
|
|
EnumerableType = enumerableType;
|
|
|
}
|
|
|
|
|
|
(TypeReference variableType, TypeReference depententType) GetDependentType(Type type, TypeReference provider) {
|
|
|
var enumeratorDef = Module.ImportReference(type).Resolve();
|
|
|
|
|
|
var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!0", provider);
|
|
|
parameterTypeReference0.SetPosition(0);
|
|
|
var parameterTypeReference1 = new Mono.Cecil.GenericParameter($"!1", provider);
|
|
|
parameterTypeReference1.SetPosition(1);
|
|
|
|
|
|
var returnRef = new GenericInstanceType(Module.ImportReference(enumeratorDef)) {
|
|
|
GenericArguments = {
|
|
|
parameterTypeReference0,
|
|
|
parameterTypeReference1
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var variableTypeRef = Module.ImportReference(enumeratorDef)
|
|
|
.MakeGenericInstanceType(((GenericInstanceType)EnumerableType).GenericArguments.ToArray());
|
|
|
|
|
|
return (variableTypeRef, returnRef);
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
|
|
|
var enumeratorType = GetDependentType(typeof(Dictionary<,>.Enumerator), EnumerableType);
|
|
|
var enumeratorVariable = new VariableDefinition(enumeratorType.variableType);
|
|
|
Body.Variables.Add(enumeratorVariable);
|
|
|
|
|
|
var keyValueType = GetDependentType(typeof(KeyValuePair<,>), enumeratorType.variableType);
|
|
|
var keyValueVariable = new VariableDefinition(keyValueType.variableType);
|
|
|
Body.Variables.Add(keyValueVariable);
|
|
|
|
|
|
il.Append(Callvirt(new MethodReference("GetEnumerator", enumeratorType.depententType, EnumerableType) { HasThis = true }));
|
|
|
il.Append(Stloc(enumeratorVariable));
|
|
|
|
|
|
var moveNextStart = Ldloca(enumeratorVariable);
|
|
|
var getCurrentStart = Ldloca(enumeratorVariable);
|
|
|
|
|
|
il.Append(Br(moveNextStart));
|
|
|
|
|
|
il.Append(getCurrentStart);
|
|
|
il.Append(Callvirt(new MethodReference("get_Current", keyValueType.depententType, enumeratorType.variableType) { HasThis = true }));
|
|
|
il.Append(Stloc(keyValueVariable));
|
|
|
Generator(il, keyValueVariable);
|
|
|
|
|
|
il.Append(moveNextStart);
|
|
|
il.Append(Callvirt(new MethodReference("MoveNext", Module.TypeSystem.Boolean, enumeratorType.variableType) { HasThis = true }));
|
|
|
il.Append(Brtrue_S(getCurrentStart));
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILMacroStruct.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using Mono.Cecil.Cil;
|
|
|
|
|
|
internal struct ILMacroStruct : ILProcessorMacro {
|
|
|
Action<ILProcessor> generator;
|
|
|
Instruction[] instructions;
|
|
|
public static implicit operator ILMacroStruct(Instruction[] instructions) {
|
|
|
if (instructions == null) {
|
|
|
throw new ArgumentNullException(nameof(instructions));
|
|
|
}
|
|
|
return new ILMacroStruct() {
|
|
|
instructions = instructions
|
|
|
};
|
|
|
}
|
|
|
|
|
|
public static implicit operator ILMacroStruct(Action<ILProcessor> generator) {
|
|
|
if (generator == null) {
|
|
|
throw new ArgumentNullException(nameof(generator));
|
|
|
}
|
|
|
return new ILMacroStruct() {
|
|
|
generator = generator
|
|
|
};
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
if (generator != null) {
|
|
|
generator(il);
|
|
|
} else {
|
|
|
foreach (var instruction in instructions) {
|
|
|
il.Append(instruction);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaver.Cache.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
using System.Linq;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using static Fusion.CodeGen.ILWeaverOpCodes;
|
|
|
|
|
|
partial class ILWeaver {
|
|
|
|
|
|
private TypeReference MakeFixedBuffer(ILWeaverAssembly asm, int wordCount) {
|
|
|
|
|
|
FieldDefinition CreateFixedBufferField (TypeDefinition type, string fieldName, TypeReference elementType, int elementCount) {
|
|
|
var fixedBufferFieldType = new TypeDefinition("", $"<{fieldName}>e__FixedBuffer", TypeAttributes.SequentialLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic) {
|
|
|
BaseType = asm.Import(typeof(ValueType)),
|
|
|
PackingSize = 0,
|
|
|
ClassSize = elementCount * elementType.GetPrimitiveSize(),
|
|
|
};
|
|
|
fixedBufferFieldType.AddAttribute<CompilerGeneratedAttribute>(asm);
|
|
|
fixedBufferFieldType.AddAttribute<UnsafeValueTypeAttribute>(asm);
|
|
|
fixedBufferFieldType.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
fixedBufferFieldType.AddTo(type);
|
|
|
|
|
|
var elementField = new FieldDefinition("FixedElementField", FieldAttributes.Public, elementType);
|
|
|
elementField.AddTo(fixedBufferFieldType);
|
|
|
|
|
|
var field = new FieldDefinition(fieldName, FieldAttributes.Public, fixedBufferFieldType);
|
|
|
field.AddAttribute<FixedBufferAttribute, TypeReference, int>(asm, elementType, elementCount);
|
|
|
field.AddTo(type);
|
|
|
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
string typeName = $"FixedStorage@{wordCount}";
|
|
|
var fixedBufferType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", typeName);
|
|
|
if (fixedBufferType == null) {
|
|
|
// fixed buffers could be included directly in structs, but then again it would be impossible to provide a custom drawer;
|
|
|
// that's why there's this proxy struct
|
|
|
var storageType = new TypeDefinition("Fusion.CodeGen", typeName,
|
|
|
TypeAttributes.ExplicitLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable,
|
|
|
asm.ValueType);
|
|
|
|
|
|
storageType.AddTo(asm.CecilAssembly);
|
|
|
storageType.AddInterface<INetworkStruct>(asm);
|
|
|
storageType.AddAttribute<NetworkStructWeavedAttribute, int>(asm, wordCount);
|
|
|
storageType.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
|
|
|
FieldDefinition bufferField;
|
|
|
if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) {
|
|
|
bufferField = CreateFixedBufferField(storageType, $"Data", asm.Import(typeof(int)), wordCount);
|
|
|
bufferField.Offset = 0;
|
|
|
|
|
|
// Unity debugger seems to copy only the first element of a buffer,
|
|
|
// the rest is garbage when inspected; let's add some additional
|
|
|
// fields to help it
|
|
|
for (int i = 1; i < wordCount; ++i) {
|
|
|
var unityDebuggerWorkaroundField = new FieldDefinition($"_{i}", FieldAttributes.Private | FieldAttributes.NotSerialized, asm.Import<int>());
|
|
|
unityDebuggerWorkaroundField.Offset = Allocator.REPLICATE_WORD_SIZE * i;
|
|
|
unityDebuggerWorkaroundField.AddTo(storageType);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
fixedBufferType = storageType;
|
|
|
}
|
|
|
return fixedBufferType;
|
|
|
}
|
|
|
|
|
|
private string TypeNameToIdentifier(TypeReference type, string prefix) {
|
|
|
string result = type.FullName;
|
|
|
result = result.Replace("`1", "");
|
|
|
result = result.Replace("`2", "");
|
|
|
result = result.Replace("`3", "");
|
|
|
result = result.Replace(".", "_");
|
|
|
result = prefix + result;
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
private TypeDefinition MakeUnitySurrogate(ILWeaverAssembly asm, PropertyDefinition property) {
|
|
|
var type = property.PropertyType;
|
|
|
|
|
|
GenericInstanceType baseType;
|
|
|
string surrogateName;
|
|
|
|
|
|
TypeReference dataType;
|
|
|
Instruction initCall = null;
|
|
|
|
|
|
if (type.IsNetworkDictionary(out var keyType, out var valueType)) {
|
|
|
keyType = asm.Import(keyType);
|
|
|
valueType = asm.Import(valueType);
|
|
|
var keyReaderWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, keyType);
|
|
|
var valueReaderWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, valueType);
|
|
|
baseType = asm.Import(typeof(Fusion.Internal.UnityDictionarySurrogate<,,,>)).MakeGenericInstanceType(keyType, keyReaderWriterType, valueType, valueReaderWriterType);
|
|
|
surrogateName = "UnityDictionarySurrogate@" + keyReaderWriterType.Name + "@" + valueReaderWriterType.Name;
|
|
|
dataType = TypeReferenceRocks.MakeGenericInstanceType(asm.Import(typeof(SerializableDictionary<,>)), keyType, valueType);
|
|
|
initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(SerializableDictionary)).Resolve().GetMethodOrThrow("Create"))) {
|
|
|
GenericArguments = { keyType, valueType }
|
|
|
});
|
|
|
} else if (type.IsNetworkArray(out var elementType)) {
|
|
|
elementType = asm.Import(elementType);
|
|
|
var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, elementType);
|
|
|
baseType = asm.Import(typeof(Fusion.Internal.UnityArraySurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType);
|
|
|
surrogateName = "UnityArraySurrogate@" + readerWriterType.Name;
|
|
|
dataType = elementType.MakeArrayType();
|
|
|
initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) {
|
|
|
GenericArguments = { elementType }
|
|
|
});
|
|
|
} else if (type.IsNetworkList(out elementType)) {
|
|
|
elementType = asm.Import(elementType);
|
|
|
var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, elementType);
|
|
|
baseType = asm.Import(typeof(Fusion.Internal.UnityLinkedListSurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType);
|
|
|
surrogateName = "UnityLinkedListSurrogate@" + readerWriterType.Name;
|
|
|
dataType = elementType.MakeArrayType();
|
|
|
initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) {
|
|
|
GenericArguments = { elementType }
|
|
|
});
|
|
|
} else {
|
|
|
var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, property.PropertyType);
|
|
|
baseType = asm.Import(typeof(Fusion.Internal.UnityValueSurrogate<,>)).MakeGenericInstanceType(property.PropertyType, readerWriterType);
|
|
|
surrogateName = "UnityValueSurrogate@" + readerWriterType.Name;
|
|
|
dataType = property.PropertyType;
|
|
|
}
|
|
|
|
|
|
int attributesHash = HashCodeUtilities.InitialHash;
|
|
|
VisitPropertyMovableAttributes(property, (ctor, blob) => {
|
|
|
attributesHash = HashCodeUtilities.GetHashDeterministic(ctor.FullName, attributesHash);
|
|
|
attributesHash = HashCodeUtilities.GetHashCodeDeterministic(blob, attributesHash);
|
|
|
});
|
|
|
if (attributesHash != HashCodeUtilities.InitialHash) {
|
|
|
surrogateName += $"@Attributes_0x{attributesHash:X8}";
|
|
|
}
|
|
|
|
|
|
var surrogateType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", surrogateName);
|
|
|
if (surrogateType == null) {
|
|
|
surrogateType = new TypeDefinition("Fusion.CodeGen", surrogateName,
|
|
|
TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.Serializable | TypeAttributes.BeforeFieldInit,
|
|
|
baseType);
|
|
|
|
|
|
surrogateType.AddTo(asm.CecilAssembly);
|
|
|
|
|
|
|
|
|
var dataProp = new PropertyDefinition("DataProperty", PropertyAttributes.None, dataType);
|
|
|
dataProp.AddTo(surrogateType);
|
|
|
|
|
|
var dataField = new FieldDefinition("Data", FieldAttributes.Public, dataType);
|
|
|
dataField.AddTo(surrogateType);
|
|
|
|
|
|
var getMethod = new MethodDefinition($"get_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, dataType);
|
|
|
{
|
|
|
var il = getMethod.Body.GetILProcessor();
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldfld(dataField));
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
getMethod.AddTo(surrogateType);
|
|
|
|
|
|
var setMethod = new MethodDefinition($"set_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, asm.Void);
|
|
|
setMethod.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, dataType));
|
|
|
{
|
|
|
var il = setMethod.Body.GetILProcessor();
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldarg_1());
|
|
|
il.Append(Stfld(dataField));
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
setMethod.AddTo(surrogateType);
|
|
|
|
|
|
dataProp.GetMethod = getMethod;
|
|
|
dataProp.SetMethod = setMethod;
|
|
|
|
|
|
MovePropertyAttributesToBackingField(asm, property, dataField, addSerializeField: false);
|
|
|
|
|
|
surrogateType.AddDefaultConstructor(il => {
|
|
|
if (initCall != null) {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(initCall);
|
|
|
il.Append(Stfld(dataField));
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
return surrogateType;
|
|
|
}
|
|
|
|
|
|
private TypeDefinition MakeElementReaderWriter(ILWeaverAssembly asm, TypeReference declaringType, ICustomAttributeProvider member, TypeReference elementType) {
|
|
|
|
|
|
elementType = asm.Import(elementType);
|
|
|
|
|
|
var interfaceType = asm.Import(typeof(IElementReaderWriter<>)).MakeGenericInstanceType(elementType);
|
|
|
|
|
|
void AddIElementReaderWriterImplementation(TypeDefinition readerWriterType, int elementWordCount, bool isExplicit = false) {
|
|
|
|
|
|
var dataType = asm.Import(typeof(byte*));
|
|
|
var indexType = asm.Import(typeof(int));
|
|
|
|
|
|
readerWriterType.Interfaces.Add(new InterfaceImplementation(interfaceType));
|
|
|
|
|
|
var visibility = isExplicit ? MethodAttributes.Private : MethodAttributes.Public;
|
|
|
var namePrefix = isExplicit ? $"CodeGen@ElementReaderWriter<{elementType.FullName}>." : "";
|
|
|
|
|
|
var readMethod = new MethodDefinition($"{namePrefix}Read",
|
|
|
visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
|
|
|
elementType);
|
|
|
|
|
|
readMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType));
|
|
|
readMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType));
|
|
|
readMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
readMethod.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
readMethod.AddTo(readerWriterType);
|
|
|
|
|
|
var readRefMethod = new MethodDefinition($"{namePrefix}ReadRef",
|
|
|
visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
|
|
|
elementType.MakeByReferenceType());
|
|
|
|
|
|
readRefMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType));
|
|
|
readRefMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType));
|
|
|
readRefMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
readRefMethod.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
readRefMethod.AddTo(readerWriterType);
|
|
|
|
|
|
var writeMethod = new MethodDefinition($"{namePrefix}Write",
|
|
|
visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
|
|
|
asm.Void);
|
|
|
|
|
|
writeMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType));
|
|
|
writeMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType));
|
|
|
writeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType));
|
|
|
writeMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
writeMethod.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
writeMethod.AddTo(readerWriterType);
|
|
|
|
|
|
var getElementWordCountMethod = new MethodDefinition($"{namePrefix}GetElementWordCount",
|
|
|
visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
|
|
|
asm.Import(typeof(int)));
|
|
|
|
|
|
getElementWordCountMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
getElementWordCountMethod.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
getElementWordCountMethod.AddTo(readerWriterType);
|
|
|
|
|
|
var getElementHashCodeMethod = new MethodDefinition($"{namePrefix}GetElementHashCode",
|
|
|
visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
|
|
|
asm.Import(typeof(int)));
|
|
|
|
|
|
getElementHashCodeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType));
|
|
|
getElementHashCodeMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
getElementHashCodeMethod.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
getElementHashCodeMethod.AddTo(readerWriterType);
|
|
|
|
|
|
if (isExplicit) {
|
|
|
readMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter<int>.Read)));
|
|
|
readRefMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter<int>.ReadRef)));
|
|
|
writeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter<int>.Write)));
|
|
|
getElementWordCountMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter<int>.GetElementWordCount)));
|
|
|
getElementHashCodeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter<int>.GetElementHashCode)));
|
|
|
}
|
|
|
|
|
|
Action<ILProcessor> addressGetter = il => {
|
|
|
il.Append(Instruction.Create(OpCodes.Ldarg_1));
|
|
|
il.Append(Instruction.Create(OpCodes.Ldarg_2));
|
|
|
il.Append(Instruction.Create(OpCodes.Ldc_I4, elementWordCount * Allocator.REPLICATE_WORD_SIZE));
|
|
|
il.Append(Instruction.Create(OpCodes.Mul));
|
|
|
il.Append(Instruction.Create(OpCodes.Add));
|
|
|
};
|
|
|
|
|
|
|
|
|
EmitRead(asm, readMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, emitRet: true);
|
|
|
EmitRead(asm, readRefMethod.Body.GetILProcessor(), readRefMethod.ReturnType, readerWriterType, member, addressGetter, emitRet: true, throwForNonUnmanagedRefs: true);
|
|
|
EmitWrite(asm, writeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, OpCodes.Ldarg_3, emitRet: true);
|
|
|
EmitGetHashCode(asm, getElementHashCodeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter,
|
|
|
valueGetter: il => {
|
|
|
il.Append(Ldarg_1());
|
|
|
},
|
|
|
valueAddrGetter: il => {
|
|
|
il.Append(Ldarga_S(getElementHashCodeMethod.Parameters[0]));
|
|
|
},
|
|
|
emitRet: true);
|
|
|
|
|
|
{
|
|
|
var il = getElementWordCountMethod.Body.GetILProcessor();
|
|
|
il.Append(Ldc_I4(elementWordCount));
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var typeInfo = TypeRegistry.GetInfo(elementType);
|
|
|
|
|
|
if (!typeInfo.CanBeUsedInStructs) {
|
|
|
if (!declaringType.Is<NetworkBehaviour>()) {
|
|
|
throw new ILWeaverException($"{elementType} needs wrapping - such types are only supported as NetworkBehaviour properties.");
|
|
|
}
|
|
|
|
|
|
// let's add an interface!
|
|
|
var behaviour = declaringType.Resolve();
|
|
|
var wordCount = typeInfo.GetMemberWordCount(member, behaviour);
|
|
|
|
|
|
if (!behaviour.Interfaces.Any(x => x.InterfaceType.FullName == interfaceType.FullName)) {
|
|
|
Log.Debug($"Adding interface {behaviour} {interfaceType}");
|
|
|
AddIElementReaderWriterImplementation(behaviour, wordCount, isExplicit: true);
|
|
|
}
|
|
|
|
|
|
return behaviour;
|
|
|
} else {
|
|
|
var readerWriterName = "ReaderWriter@" + elementType.FullName.Replace(".", "_").Replace("/", "__");
|
|
|
|
|
|
if (typeInfo.TryGetCapacity(member, out int capacity)) {
|
|
|
readerWriterName += $"@Capacity_{capacity}";
|
|
|
}
|
|
|
|
|
|
var readerWriterTypeDef = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", readerWriterName);
|
|
|
|
|
|
const string GetInstanceMethodName = "GetInstance";
|
|
|
|
|
|
|
|
|
if (readerWriterTypeDef == null) {
|
|
|
readerWriterTypeDef = new TypeDefinition("Fusion.CodeGen", readerWriterName,
|
|
|
TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.SequentialLayout | TypeAttributes.BeforeFieldInit, asm.ValueType);
|
|
|
|
|
|
// without this, VS debugger will crash
|
|
|
readerWriterTypeDef.PackingSize = 0;
|
|
|
readerWriterTypeDef.ClassSize = 1;
|
|
|
|
|
|
readerWriterTypeDef.AddTo(asm.CecilAssembly);
|
|
|
readerWriterTypeDef.AddAttribute<PreserveInPluginAttribute>(asm);
|
|
|
|
|
|
var wordCount = typeInfo.GetMemberWordCount(member, readerWriterTypeDef);
|
|
|
AddIElementReaderWriterImplementation(readerWriterTypeDef, wordCount);
|
|
|
|
|
|
var instanceField = new FieldDefinition("Instance", FieldAttributes.Public | FieldAttributes.Static, interfaceType);
|
|
|
instanceField.AddTo(readerWriterTypeDef);
|
|
|
|
|
|
var initializeMethod = new MethodDefinition(GetInstanceMethodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, interfaceType);
|
|
|
initializeMethod.AddAttribute<MethodImplAttribute, MethodImplOptions>(asm, MethodImplOptions.AggressiveInlining);
|
|
|
initializeMethod.AddTo(readerWriterTypeDef);
|
|
|
|
|
|
{
|
|
|
var il = initializeMethod.Body.GetILProcessor();
|
|
|
var loadFld = Ldsfld(instanceField);
|
|
|
|
|
|
var tmpVar = new VariableDefinition(readerWriterTypeDef);
|
|
|
il.Body.Variables.Add(tmpVar);
|
|
|
|
|
|
il.Append(Ldsfld(instanceField));
|
|
|
il.Append(Brtrue_S(loadFld));
|
|
|
|
|
|
il.Append(Ldloca_S(tmpVar));
|
|
|
il.Append(Initobj(readerWriterTypeDef));
|
|
|
il.Append(Ldloc_0());
|
|
|
il.Append(Box(readerWriterTypeDef));
|
|
|
il.Append(Stsfld(instanceField));
|
|
|
|
|
|
il.Append(loadFld);
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return readerWriterTypeDef;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void EmitElementReaderWriterLoad(ILProcessor il, TypeDefinition readerWriterType) {
|
|
|
if (readerWriterType.Is<NetworkBehaviour>()) {
|
|
|
il.Append(Ldarg_0());
|
|
|
} else {
|
|
|
var getInstanceMethod = readerWriterType.GetMethodOrThrow("GetInstance");
|
|
|
il.Append(Call(getInstanceMethod));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaver.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using Mono.Collections.Generic;
|
|
|
using static Fusion.CodeGen.ILWeaverOpCodes;
|
|
|
using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider;
|
|
|
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
|
|
using ParameterAttributes = Mono.Cecil.ParameterAttributes;
|
|
|
|
|
|
public unsafe partial class ILWeaver {
|
|
|
|
|
|
Dictionary<TypeReference, int> _rpcCount = new Dictionary<TypeReference, int>(new MemberReferenceFullNameComparer());
|
|
|
|
|
|
|
|
|
internal readonly ILWeaverLog Log;
|
|
|
internal readonly ILWeaverSettings Settings;
|
|
|
|
|
|
public ILWeaver(ILWeaverSettings settings, ILWeaverLog log) {
|
|
|
if (log == null) {
|
|
|
throw new ArgumentNullException(nameof(log));
|
|
|
}
|
|
|
Log = log;
|
|
|
Settings = settings;
|
|
|
}
|
|
|
|
|
|
public ILWeaver(ILWeaverSettings settings, ILWeaverLogger logger) : this(settings, new ILWeaverLog(logger)) {
|
|
|
}
|
|
|
|
|
|
private void EnsureTypeRegistry(ILWeaverAssembly asm) {
|
|
|
if (TypeRegistry == null) {
|
|
|
TypeRegistry = new NetworkTypeInfoRegistry(asm.CecilAssembly.MainModule, Settings, Log.Logger, typeRef => CalculateStructWordCount(typeRef));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void InjectPtrNullCheck(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property) {
|
|
|
if (Settings.NullChecksForNetworkedProperties) {
|
|
|
var nop = Instruction.Create(OpCodes.Nop);
|
|
|
|
|
|
il.Append(Instruction.Create(OpCodes.Ldarg_0));
|
|
|
var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr));
|
|
|
il.Append(Instruction.Create(OpCodes.Ldfld, ptrGetter));
|
|
|
//var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr));
|
|
|
//il.Append(Instruction.Create(OpCodes.Ldfld, ptrField));
|
|
|
il.Append(Instruction.Create(OpCodes.Ldc_I4_0));
|
|
|
il.Append(Instruction.Create(OpCodes.Conv_U));
|
|
|
il.Append(Instruction.Create(OpCodes.Ceq));
|
|
|
il.Append(Instruction.Create(OpCodes.Brfalse, nop));
|
|
|
|
|
|
var ctor = typeof(InvalidOperationException).GetConstructors().First(x => x.GetParameters().Length == 1);
|
|
|
var exnCtor = asm.Import(ctor);
|
|
|
|
|
|
il.Append(Instruction.Create(OpCodes.Ldstr, $"Error when accessing {property.DeclaringType.Name}.{property.Name}. Networked properties can only be accessed when Spawned() has been called."));
|
|
|
il.Append(Instruction.Create(OpCodes.Newobj, exnCtor));
|
|
|
il.Append(Instruction.Create(OpCodes.Throw));
|
|
|
il.Append(nop);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void EmitRead(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action<ILProcessor> addressGetter) {
|
|
|
EmitRead(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, true);
|
|
|
}
|
|
|
|
|
|
void EmitRead(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action<ILProcessor> addressGetter, bool emitRet = false, bool throwForNonUnmanagedRefs = false) {
|
|
|
// for pointer types we can simply just return the address we loaded on the stack
|
|
|
if (type.IsPointer || type.IsByReference) {
|
|
|
// load address
|
|
|
if (throwForNonUnmanagedRefs == false || TypeRegistry.GetInfo(type.GetElementTypeWithGenerics()).IsTriviallyCopyable) {
|
|
|
addressGetter(il);
|
|
|
} else {
|
|
|
il.Append(Ldstr($"Only supported for trivially copyable types. {type.GetElementTypeWithGenerics()} is not trivially copyable."));
|
|
|
il.Append(Newobj(asm.Import(typeof(NotSupportedException).GetConstructor(new[] { typeof(string) }))));
|
|
|
il.Append(Throw());
|
|
|
return;
|
|
|
}
|
|
|
} else {
|
|
|
using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter)) {
|
|
|
ctx.LoadElementReaderWriterImpl = (il, type, member) => {
|
|
|
EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type));
|
|
|
};
|
|
|
|
|
|
TypeRegistry.EmitRead(type, il, ctx, member);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (emitRet) {
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void EmitWrite(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action<ILProcessor> addressGetter, OpCode valueOpCode) {
|
|
|
EmitWrite(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, valueOpCode, true);
|
|
|
}
|
|
|
|
|
|
void EmitWrite(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action<ILProcessor> addressGetter, OpCode valueOpCode, bool emitRet = false) {
|
|
|
|
|
|
if (type.IsPointer || type.IsByReference) {
|
|
|
throw new ILWeaverException($"Pointer and reference members are read-only");
|
|
|
}
|
|
|
|
|
|
using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter, valueGetter: (il) => il.Append(Instruction.Create(valueOpCode)))) {
|
|
|
ctx.LoadElementReaderWriterImpl = (il, type, member) => {
|
|
|
EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type));
|
|
|
};
|
|
|
TypeRegistry.EmitWrite(type, il, ctx, member);
|
|
|
if (emitRet) {
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void EmitGetHashCode(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action<ILProcessor> addressGetter, Action<ILProcessor> valueGetter, Action<ILProcessor> valueAddrGetter, bool emitRet = false) {
|
|
|
|
|
|
using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: null, valueGetter: valueGetter, valueAddrGetter: valueAddrGetter)) {
|
|
|
ctx.LoadElementReaderWriterImpl = (il, type, member) => {
|
|
|
EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type));
|
|
|
};
|
|
|
TypeRegistry.EmitGetHashCode(type, il, ctx, member);
|
|
|
if (emitRet) {
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void ThrowIfPropertyNotEmptyOrCompilerGenerated(PropertyDefinition property) {
|
|
|
Collection<Instruction> instructions;
|
|
|
int idx;
|
|
|
|
|
|
var getter = property.GetMethod;
|
|
|
var setter = property.SetMethod;
|
|
|
|
|
|
void ExpectNext(params OpCode[] opCodes) {
|
|
|
foreach (var opCode in opCodes) {
|
|
|
// skip nops
|
|
|
for (; idx < instructions.Count && instructions[idx].OpCode.Equals(OpCodes.Nop); ++idx) {
|
|
|
}
|
|
|
|
|
|
if (idx >= instructions.Count) {
|
|
|
throw new InvalidOperationException($"Expected {opCode}, but run out of instructions");
|
|
|
} else if (!instructions[idx].OpCode.Equals(opCode)) {
|
|
|
throw new InvalidOperationException($"Expected {opCode}, got {instructions[idx].OpCode} at {idx}. Full IL: {string.Join(", ", instructions)}");
|
|
|
}
|
|
|
++idx;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (getter != null && !getter.TryGetAttribute<CompilerGeneratedAttribute>(out _)) {
|
|
|
instructions = getter.Body.Instructions;
|
|
|
idx = 0;
|
|
|
|
|
|
bool expectLocalVariable = false;
|
|
|
var returnType = getter.ReturnType;
|
|
|
|
|
|
switch (returnType.MetadataType) {
|
|
|
case MetadataType.SByte:
|
|
|
case MetadataType.Byte:
|
|
|
case MetadataType.Int16:
|
|
|
case MetadataType.UInt16:
|
|
|
case MetadataType.Int32:
|
|
|
case MetadataType.UInt32:
|
|
|
case MetadataType.Boolean:
|
|
|
case MetadataType.Char:
|
|
|
ExpectNext(OpCodes.Ldc_I4_0);
|
|
|
break;
|
|
|
case MetadataType.Int64:
|
|
|
case MetadataType.UInt64:
|
|
|
ExpectNext(OpCodes.Ldc_I4_0, OpCodes.Conv_I8);
|
|
|
break;
|
|
|
case MetadataType.Single:
|
|
|
ExpectNext(OpCodes.Ldc_R4);
|
|
|
break;
|
|
|
case MetadataType.Double:
|
|
|
ExpectNext(OpCodes.Ldc_R8);
|
|
|
break;
|
|
|
case MetadataType.String:
|
|
|
case MetadataType.Object:
|
|
|
ExpectNext(OpCodes.Ldnull);
|
|
|
break;
|
|
|
default:
|
|
|
expectLocalVariable = true;
|
|
|
ExpectNext(OpCodes.Ldloca_S, OpCodes.Initobj, OpCodes.Ldloc_0);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (getter.Body.Variables.Count > (expectLocalVariable ? 1 : 0)) {
|
|
|
if (expectLocalVariable) {
|
|
|
ExpectNext(OpCodes.Stloc_1, OpCodes.Br_S, OpCodes.Ldloc_1);
|
|
|
} else {
|
|
|
ExpectNext(OpCodes.Stloc_0, OpCodes.Br_S, OpCodes.Ldloc_0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ExpectNext(OpCodes.Ret);
|
|
|
}
|
|
|
|
|
|
if (setter != null && !setter.TryGetAttribute<CompilerGeneratedAttribute>(out _)) {
|
|
|
instructions = setter.Body.Instructions;
|
|
|
idx = 0;
|
|
|
ExpectNext(OpCodes.Ret);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
(MethodDefinition getter, MethodDefinition setter) PreparePropertyForWeaving(PropertyDefinition property) {
|
|
|
|
|
|
var getter = property.GetMethod;
|
|
|
var setter = property.SetMethod;
|
|
|
|
|
|
// clear getter
|
|
|
getter.CustomAttributes.Clear();
|
|
|
getter.Body.Instructions.Clear();
|
|
|
|
|
|
// clear setter if it exists
|
|
|
setter?.CustomAttributes?.Clear();
|
|
|
setter?.Body?.Instructions?.Clear();
|
|
|
|
|
|
return (getter, setter);
|
|
|
}
|
|
|
|
|
|
struct WeavablePropertyMeta {
|
|
|
public string DefaultFieldName;
|
|
|
public FieldDefinition BackingField;
|
|
|
public bool ReatainIL;
|
|
|
public string OnChanged;
|
|
|
}
|
|
|
|
|
|
bool IsWeavableProperty(PropertyDefinition property, out WeavablePropertyMeta meta) {
|
|
|
if (property.TryGetAttribute<NetworkedAttribute>(out var attr) == false) {
|
|
|
meta = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// check getter ... it has to exist
|
|
|
var getter = property.GetMethod;
|
|
|
if (getter == null) {
|
|
|
meta = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// check setter ...
|
|
|
var setter = property.SetMethod;
|
|
|
if (setter == null) {
|
|
|
// if it doesn't exist we allow either array or pointer
|
|
|
if (property.PropertyType.IsByReference == false && property.PropertyType.IsPointer == false && !property.PropertyType.IsNetworkCollection()) {
|
|
|
throw new ILWeaverException($"Simple properties need a setter.");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (getter.IsStatic) {
|
|
|
throw new ILWeaverException($"Networked properties can't be static.");
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check for backing field ...
|
|
|
if (property.TryGetBackingField(out var backing)) {
|
|
|
var il = attr.Properties.FirstOrDefault(x => x.Name == "RetainIL");
|
|
|
|
|
|
if (il.Argument.Value is bool retainIL && retainIL) {
|
|
|
meta = new WeavablePropertyMeta() {
|
|
|
ReatainIL = true
|
|
|
};
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
meta = new WeavablePropertyMeta() {
|
|
|
BackingField = backing,
|
|
|
ReatainIL = false,
|
|
|
};
|
|
|
|
|
|
attr.TryGetAttributeProperty(nameof(NetworkedAttribute.Default), out meta.DefaultFieldName);
|
|
|
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ThrowIfNotRpcCompatible(TypeReference type) {
|
|
|
NetworkTypeInfo typeInfo;
|
|
|
typeInfo = TypeRegistry.GetInfo(type);
|
|
|
if (!typeInfo.CanBeUsedInRpc) {
|
|
|
throw new ArgumentException($"Can't be used in RPC");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
MethodReference GetBaseMethodReference(ILWeaverAssembly asm, MethodDefinition overridingDefinition, TypeReference baseType) {
|
|
|
|
|
|
var baseMethod = new MethodReference(overridingDefinition.Name, overridingDefinition.ReturnType, baseType) {
|
|
|
HasThis = overridingDefinition.HasThis,
|
|
|
ExplicitThis = overridingDefinition.ExplicitThis,
|
|
|
CallingConvention = overridingDefinition.CallingConvention,
|
|
|
};
|
|
|
|
|
|
foreach (var parameter in overridingDefinition.Parameters) {
|
|
|
baseMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
|
|
}
|
|
|
|
|
|
if (baseMethod.DeclaringType is GenericInstanceType genericTypeRef) {
|
|
|
baseMethod = baseMethod.GetCallable(genericTypeRef);
|
|
|
}
|
|
|
|
|
|
return asm.Import(baseMethod);
|
|
|
}
|
|
|
|
|
|
string InvokerMethodName(string method, Dictionary<string, int> nameCache) {
|
|
|
nameCache.TryGetValue(method, out var count);
|
|
|
nameCache[method] = ++count;
|
|
|
return $"{method}@Invoker{(count == 1 ? "" : count.ToString())}";
|
|
|
}
|
|
|
|
|
|
bool HasRpcPrefixOrSuffix(MethodDefinition def) {
|
|
|
return def.Name.StartsWith("rpc", StringComparison.OrdinalIgnoreCase) || def.Name.EndsWith("rpc", StringComparison.OrdinalIgnoreCase);
|
|
|
}
|
|
|
|
|
|
void WeaveRpcs(ILWeaverAssembly asm, TypeDefinition type, bool allowInstanceRpcs = true) {
|
|
|
// rpc list
|
|
|
var rpcs = new List<(MethodDefinition, CustomAttribute)>();
|
|
|
|
|
|
|
|
|
bool hasStaticRpc = false;
|
|
|
|
|
|
foreach (var rpc in type.Methods) {
|
|
|
if (rpc.TryGetAttribute<RpcAttribute>(out var attr)) {
|
|
|
|
|
|
if (HasRpcPrefixOrSuffix(rpc) == false) {
|
|
|
throw new ILWeaverException($"{rpc}: name needs to start or end with the \"Rpc\" prefix or suffix");
|
|
|
}
|
|
|
|
|
|
if (rpc.IsStatic && rpc.Parameters.FirstOrDefault()?.ParameterType.FullName != asm.NetworkRunner.Reference.FullName) {
|
|
|
throw new ILWeaverException($"{rpc}: Static RPC needs {nameof(NetworkRunner)} as the first parameter");
|
|
|
}
|
|
|
|
|
|
hasStaticRpc |= rpc.IsStatic;
|
|
|
|
|
|
if (!allowInstanceRpcs && !rpc.IsStatic) {
|
|
|
throw new ILWeaverException($"{rpc}: Instance RPCs not allowed for this type");
|
|
|
}
|
|
|
|
|
|
foreach (var parameter in rpc.Parameters) {
|
|
|
if (rpc.IsStatic && parameter == rpc.Parameters[0]) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (IsInvokeOnlyParameter(parameter)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
var parameterType = parameter.ParameterType.IsArray ? parameter.ParameterType.GetElementTypeWithGenerics() : parameter.ParameterType;
|
|
|
|
|
|
try {
|
|
|
ThrowIfNotRpcCompatible(parameterType);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"{rpc}: parameter {parameter.Name} is not Rpc-compatible.", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!rpc.ReturnType.Is(asm.RpcInvokeInfo) && !rpc.ReturnType.IsVoid()) {
|
|
|
throw new ILWeaverException($"{rpc}: RPCs can't return a value.");
|
|
|
}
|
|
|
|
|
|
rpcs.Add((rpc, attr));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!rpcs.Any()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
int instanceRpcKeys = GetInstanceRpcCount(type.BaseType);
|
|
|
|
|
|
Dictionary<string, int> invokerNameCounter = new Dictionary<string, int>();
|
|
|
|
|
|
foreach (var (rpc, attr) in rpcs) {
|
|
|
int sources;
|
|
|
int targets;
|
|
|
|
|
|
if (attr.ConstructorArguments.Count == 2) {
|
|
|
sources = attr.GetAttributeArgument<int>(0);
|
|
|
targets = attr.GetAttributeArgument<int>(1);
|
|
|
} else {
|
|
|
sources = AuthorityMasks.ALL;
|
|
|
targets = AuthorityMasks.ALL;
|
|
|
}
|
|
|
|
|
|
ParameterDefinition rpcTargetParameter = rpc.Parameters.SingleOrDefault(x => x.HasAttribute<RpcTargetAttribute>());
|
|
|
if (rpcTargetParameter != null && !rpcTargetParameter.ParameterType.Is<PlayerRef>()) {
|
|
|
throw new ILWeaverException($"{rpcTargetParameter}: {nameof(RpcTargetAttribute)} can only be used for {nameof(PlayerRef)} type argument");
|
|
|
}
|
|
|
|
|
|
attr.TryGetAttributeProperty<bool>(nameof(RpcAttribute.InvokeLocal), out var invokeLocal, defaultValue: true);
|
|
|
attr.TryGetAttributeProperty<RpcChannel>(nameof(RpcAttribute.Channel), out var channel);
|
|
|
attr.TryGetAttributeProperty<bool>(nameof(RpcAttribute.TickAligned), out var tickAligned, defaultValue: true);
|
|
|
attr.TryGetAttributeProperty<RpcHostMode>(nameof(RpcAttribute.HostMode), out var hostMode);
|
|
|
|
|
|
// rpc key
|
|
|
int instanceRpcKey = -1;
|
|
|
var returnsRpcInvokeInfo = rpc.ReturnType.Is(asm.RpcInvokeInfo);
|
|
|
|
|
|
|
|
|
using (var ctx = new RpcMethodContext(asm, rpc, rpc.IsStatic)) {
|
|
|
|
|
|
// local variables
|
|
|
ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType());
|
|
|
ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int)));
|
|
|
var message = new VariableDefinition(asm.SimulationMessage.Reference.MakePointerType());
|
|
|
VariableDefinition localAuthorityMask = null;
|
|
|
|
|
|
rpc.Body.Variables.Add(ctx.DataVariable);
|
|
|
rpc.Body.Variables.Add(ctx.OffsetVariable);
|
|
|
rpc.Body.Variables.Add(message);
|
|
|
rpc.Body.InitLocals = true;
|
|
|
|
|
|
// get il processes and our jump instruction
|
|
|
var il = rpc.Body.GetILProcessor();
|
|
|
var jmp = Nop();
|
|
|
var inv = Nop();
|
|
|
var prepareInv = Nop();
|
|
|
|
|
|
Instruction targetedInvokeLocal = null;
|
|
|
|
|
|
|
|
|
// instructions for our branch
|
|
|
var ins = new List<Instruction>();
|
|
|
|
|
|
if (returnsRpcInvokeInfo) {
|
|
|
// find local variable that's used for return(default);
|
|
|
ctx.RpcInvokeInfoVariable = new VariableDefinition(asm.RpcInvokeInfo);
|
|
|
rpc.Body.Variables.Add(ctx.RpcInvokeInfoVariable);
|
|
|
ins.Add(Ldloca(ctx.RpcInvokeInfoVariable));
|
|
|
ins.Add(Initobj(ctx.RpcInvokeInfoVariable.VariableType));
|
|
|
|
|
|
// fix each ret
|
|
|
var returns = il.Body.Instructions.Where(x => x.OpCode == OpCodes.Ret).ToList();
|
|
|
foreach (var retInstruction in returns) {
|
|
|
// need to pop the original value and load our new one
|
|
|
il.InsertBefore(retInstruction, Pop());
|
|
|
il.InsertBefore(retInstruction, Ldloc(ctx.RpcInvokeInfoVariable));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (rpc.IsStatic) {
|
|
|
ins.Add(Ldsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc))));
|
|
|
ins.Add(Brfalse(jmp));
|
|
|
ins.Add(Ldc_I4(0));
|
|
|
ins.Add(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc))));
|
|
|
} else {
|
|
|
ins.Add(Ldarg_0());
|
|
|
ins.Add(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc))));
|
|
|
ins.Add(Brfalse(jmp));
|
|
|
ins.Add(Ldarg_0());
|
|
|
ins.Add(Ldc_I4(0));
|
|
|
ins.Add(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc))));
|
|
|
}
|
|
|
ins.Add(inv);
|
|
|
|
|
|
|
|
|
// insert instruction into method body
|
|
|
var prev = rpc.Body.Instructions[0]; //.OpCode == OpCodes.Nop ? rpc.Body.Instructions[1] : rpc.Body.Instructions[0];
|
|
|
|
|
|
for (int i = ins.Count - 1; i >= 0; --i) {
|
|
|
il.InsertBefore(prev, ins[i]);
|
|
|
prev = ins[i];
|
|
|
}
|
|
|
|
|
|
// jump target
|
|
|
il.Append(jmp);
|
|
|
|
|
|
|
|
|
|
|
|
var returnInstructions = returnsRpcInvokeInfo
|
|
|
? new[] { Ldloc(ctx.RpcInvokeInfoVariable), Ret() }
|
|
|
: new[] { Ret() };
|
|
|
|
|
|
var ret = returnInstructions.First();
|
|
|
|
|
|
// check if runner's ok
|
|
|
if (rpc.IsStatic) {
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
var checkDone = Nop();
|
|
|
il.Append(Brtrue_S(checkDone));
|
|
|
il.Append(Ldstr(rpc.Parameters[0].Name));
|
|
|
il.Append(Newobj(typeof(ArgumentNullException).GetConstructor(asm, 1)));
|
|
|
il.Append(Throw());
|
|
|
il.Append(checkDone);
|
|
|
} else {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.ThrowIfBehaviourNotInitialized))));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(!invokeLocal, RpcLocalInvokeResult.NotInvokableLocally));
|
|
|
|
|
|
// if we shouldn't invoke during resim
|
|
|
{
|
|
|
var checkDone = Nop();
|
|
|
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
|
|
|
il.Append(Call(asm.NetworkRunner.GetGetterOrThrow("Stage")));
|
|
|
il.Append(Ldc_I4((int)SimulationStages.Resimulate));
|
|
|
il.Append(Bne_Un_S(checkDone));
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.NotInvokableDuringResim));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotInvokableDuringResim));
|
|
|
il.Append(Br(ret));
|
|
|
|
|
|
il.Append(checkDone);
|
|
|
}
|
|
|
|
|
|
if (!rpc.IsStatic) {
|
|
|
localAuthorityMask = new VariableDefinition(asm.Import(typeof(int)));
|
|
|
rpc.Body.Variables.Add(localAuthorityMask);
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object))));
|
|
|
il.Append(Call(asm.NetworkedObject.GetMethod(nameof(NetworkObject.GetLocalAuthorityMask))));
|
|
|
il.Append(Stloc(localAuthorityMask));
|
|
|
}
|
|
|
|
|
|
// check if target is reachable or not
|
|
|
if (rpcTargetParameter != null) {
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
|
|
|
il.Append(Ldarg(rpcTargetParameter));
|
|
|
il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.GetRpcTargetStatus))));
|
|
|
il.Append(Dup());
|
|
|
|
|
|
// check for being unreachable
|
|
|
{
|
|
|
var done = Nop();
|
|
|
il.Append(Ldc_I4((int)RpcTargetStatus.Unreachable));
|
|
|
il.Append(Bne_Un_S(done));
|
|
|
|
|
|
if (!returnsRpcInvokeInfo) {
|
|
|
il.Append(Ldarg(rpcTargetParameter));
|
|
|
il.Append(Ldstr(rpc.ToString()));
|
|
|
il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcTargetUnreachable))));
|
|
|
}
|
|
|
|
|
|
il.Append(Pop()); // pop the GetRpcTargetStatus
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.TargetPlayerIsNotLocal));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerUnreachable));
|
|
|
il.Append(Br(ret));
|
|
|
|
|
|
il.Append(done);
|
|
|
}
|
|
|
|
|
|
// check for self
|
|
|
{
|
|
|
il.Append(Ldc_I4((int)RpcTargetStatus.Self));
|
|
|
if (invokeLocal) {
|
|
|
// straight to the invoke; this will prohibit any sending
|
|
|
Log.Assert(targetedInvokeLocal == null);
|
|
|
targetedInvokeLocal = Nop();
|
|
|
il.Append(Beq(targetedInvokeLocal));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.TargetPlayerIsNotLocal));
|
|
|
} else {
|
|
|
// will never get called
|
|
|
var checkDone = Nop();
|
|
|
il.Append(Bne_Un_S(checkDone));
|
|
|
|
|
|
if (!returnsRpcInvokeInfo && NetworkRunner.BuildType == NetworkRunner.BuildTypes.Debug) {
|
|
|
il.Append(Ldarg(rpcTargetParameter));
|
|
|
il.Append(Ldstr(rpc.ToString()));
|
|
|
il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalTargetedRpcCulled))));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerIsLocalButRpcIsNotInvokableLocally));
|
|
|
il.Append(Br(ret));
|
|
|
|
|
|
il.Append(checkDone);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// check if sender flags make sense
|
|
|
if (!rpc.IsStatic) {
|
|
|
var checkDone = Nop();
|
|
|
|
|
|
il.Append(Ldloc(localAuthorityMask));
|
|
|
il.Append(Ldc_I4(sources));
|
|
|
il.Append(And());
|
|
|
il.Append(Brtrue_S(checkDone));
|
|
|
|
|
|
if (!returnsRpcInvokeInfo) {
|
|
|
// source is not valid, notify
|
|
|
il.Append(Ldstr(rpc.ToString()));
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object))));
|
|
|
il.Append(Ldc_I4(sources));
|
|
|
il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalSimulationNotAllowedToSendRpc))));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.InsufficientSourceAuthority));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.InsufficientSourceAuthority));
|
|
|
|
|
|
il.Append(Br(ret));
|
|
|
|
|
|
il.Append(checkDone);
|
|
|
|
|
|
if (invokeLocal) {
|
|
|
// how about the target? does it match only the local client?
|
|
|
if (targets != 0 && (targets & AuthorityMasks.PROXY) == 0) {
|
|
|
il.Append(Ldloc(localAuthorityMask));
|
|
|
il.Append(Ldc_I4(targets));
|
|
|
il.Append(And());
|
|
|
il.Append(Ldc_I4(targets));
|
|
|
il.Append(Beq(prepareInv));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var messageSizeVar = ctx.CreateVariable(asm.Import<int>());
|
|
|
{
|
|
|
il.Append(Ldc_I4(RpcHeader.SIZE));
|
|
|
il.Append(Stloc(messageSizeVar));
|
|
|
|
|
|
|
|
|
for (int i = 0; i < rpc.Parameters.Count; ++i) {
|
|
|
var para = rpc.Parameters[i];
|
|
|
|
|
|
if (rpc.IsStatic && i == 0) {
|
|
|
Log.Assert(para.ParameterType.IsSame<NetworkRunner>());
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (IsInvokeOnlyParameter(para)) {
|
|
|
continue;
|
|
|
}
|
|
|
if (para == rpcTargetParameter) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
il.Append(Ldloc(messageSizeVar));
|
|
|
|
|
|
using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) {
|
|
|
if (para.ParameterType.IsArray) {
|
|
|
// do nothing
|
|
|
EmitRpcArrayByteSize(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics());
|
|
|
} else {
|
|
|
TypeRegistry.EmitRpcByteCount(para.ParameterType, il, ctx, para, wordAligned: true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
il.Append(Add());
|
|
|
il.Append(Stloc(messageSizeVar));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// check the size
|
|
|
var sizeOk = Nop();
|
|
|
il.Append(Ldloc(messageSizeVar));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.CanAllocateUserPayload))));
|
|
|
il.Append(Brtrue_S(sizeOk));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.PayloadSizeExceeded));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.PayloadSizeExceeded));
|
|
|
if (!returnsRpcInvokeInfo) {
|
|
|
il.Append(Ldstr(rpc.ToString()));
|
|
|
il.Append(Ldloc(messageSizeVar));
|
|
|
il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcPayloadSizeExceeded))));
|
|
|
}
|
|
|
il.Append(Br(ret));
|
|
|
il.Append(sizeOk);
|
|
|
|
|
|
// check if sending makes sense at all
|
|
|
var afterSend = Nop();
|
|
|
|
|
|
// if not targeted (already handled earlier) check if it can be sent at all
|
|
|
if (rpcTargetParameter == null) {
|
|
|
var checkDone = Nop();
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.HasAnyActiveConnections))));
|
|
|
il.Append(Brtrue(checkDone));
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NoActiveConnections));
|
|
|
il.Append(Br(afterSend));
|
|
|
il.Append(checkDone);
|
|
|
}
|
|
|
|
|
|
// create simulation message
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
il.Append(Call(asm.NetworkRunner.GetGetterOrThrow(nameof(NetworkRunner.Simulation))));
|
|
|
il.Append(Ldloc(messageSizeVar));
|
|
|
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.Allocate), 2)));
|
|
|
il.Append(Stloc(message));
|
|
|
|
|
|
// get data for messages
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.GetData), 1)));
|
|
|
il.Append(Stloc(ctx.DataVariable));
|
|
|
|
|
|
// create RpcHeader
|
|
|
// il.Append(Ldloc(data));
|
|
|
|
|
|
if (rpc.IsStatic) {
|
|
|
il.Append(Ldstr(rpc.ToString()));
|
|
|
il.Append(Call(asm.Import(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.GetRpcStaticIndexOrThrow)))));
|
|
|
il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 1)));
|
|
|
} else {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object))));
|
|
|
il.Append(Call(asm.NetworkedObject.GetGetterOrThrow(nameof(NetworkObject.Id))));
|
|
|
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.ObjectIndex))));
|
|
|
|
|
|
instanceRpcKey = ++instanceRpcKeys;
|
|
|
il.Append(Ldc_I4(instanceRpcKey));
|
|
|
il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 3)));
|
|
|
}
|
|
|
|
|
|
il.Append(Ldloc(ctx.DataVariable));
|
|
|
il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Write))));
|
|
|
// no need to align this
|
|
|
//il.AppendMacro(AlignToWordSize());
|
|
|
//il.Append(Stobj(asm.RpcHeader.Reference));
|
|
|
|
|
|
//il.Append(Ldc_I4(RpcHeader.SIZE));
|
|
|
il.Append(Stloc(ctx.OffsetVariable));
|
|
|
|
|
|
// write parameters
|
|
|
for (int i = 0; i < rpc.Parameters.Count; ++i) {
|
|
|
var para = rpc.Parameters[i];
|
|
|
|
|
|
if (rpc.IsStatic && i == 0) {
|
|
|
continue;
|
|
|
}
|
|
|
if (IsInvokeOnlyParameter(para)) {
|
|
|
continue;
|
|
|
}
|
|
|
if (para == rpcTargetParameter) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) {
|
|
|
if (para.ParameterType.IsArray) {
|
|
|
//WeaveRpcArrayInput(asm, ctx, il, para);
|
|
|
EmitRpcWriteArray(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics());
|
|
|
} else {
|
|
|
TypeRegistry.EmitWrite(para.ParameterType, il, ctx, para);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// update message offset
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Ldflda(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Offset))));
|
|
|
il.Append(Ldloc(ctx.OffsetVariable));
|
|
|
il.Append(Ldc_I4(8));
|
|
|
il.Append(Mul());
|
|
|
il.Append(Stind_I4());
|
|
|
|
|
|
// send message
|
|
|
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
|
|
|
if (rpcTargetParameter != null) {
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Ldarg(rpcTargetParameter));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetTarget))));
|
|
|
}
|
|
|
|
|
|
if (channel == RpcChannel.Unreliable) {
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetUnreliable))));
|
|
|
}
|
|
|
|
|
|
if (!tickAligned) {
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetNotTickAligned))));
|
|
|
}
|
|
|
|
|
|
if (rpc.IsStatic) {
|
|
|
il.Append(Ldloc(message));
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetStatic))));
|
|
|
}
|
|
|
|
|
|
// send the rpc
|
|
|
il.Append(Ldloc(message));
|
|
|
|
|
|
if (ctx.RpcInvokeInfoVariable != null) {
|
|
|
il.Append(Ldloca(ctx.RpcInvokeInfoVariable));
|
|
|
il.Append(Ldflda(asm.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendResult))));
|
|
|
il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 2)));
|
|
|
} else {
|
|
|
il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 1)));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotCulled));
|
|
|
|
|
|
il.Append(afterSend);
|
|
|
|
|
|
// .. hmm
|
|
|
if (invokeLocal) {
|
|
|
|
|
|
if (targetedInvokeLocal != null) {
|
|
|
il.Append(Br(ret));
|
|
|
il.Append(targetedInvokeLocal);
|
|
|
}
|
|
|
|
|
|
if (!rpc.IsStatic) {
|
|
|
var checkDone = Nop();
|
|
|
il.Append(Ldloc(localAuthorityMask));
|
|
|
il.Append(Ldc_I4(targets));
|
|
|
il.Append(And());
|
|
|
il.Append(Brtrue_S(checkDone));
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.InsufficientTargetAuthority));
|
|
|
|
|
|
il.Append(Br(ret));
|
|
|
|
|
|
il.Append(checkDone);
|
|
|
}
|
|
|
|
|
|
il.Append(prepareInv);
|
|
|
|
|
|
foreach (var param in rpc.Parameters) {
|
|
|
if (param.ParameterType.IsSame<RpcInfo>()) {
|
|
|
// need to fill it now
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
il.Append(Ldc_I4((int)channel));
|
|
|
il.Append(Ldc_I4((int)hostMode));
|
|
|
il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromLocal))));
|
|
|
il.Append(Starg_S(param));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.Invoked));
|
|
|
|
|
|
// invoke
|
|
|
il.Append(Br(inv));
|
|
|
}
|
|
|
|
|
|
foreach (var instruction in returnInstructions) {
|
|
|
il.Append(instruction);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var invoker = new MethodDefinition(InvokerMethodName(rpc.Name, invokerNameCounter), MethodAttributes.Family | MethodAttributes.Static, asm.Import(typeof(void)));
|
|
|
using (var ctx = new RpcMethodContext(asm, invoker, rpc.IsStatic)) {
|
|
|
|
|
|
// create invoker delegate
|
|
|
if (rpc.IsStatic) {
|
|
|
var runner = new ParameterDefinition("runner", ParameterAttributes.None, asm.NetworkRunner.Reference);
|
|
|
invoker.Parameters.Add(runner);
|
|
|
} else {
|
|
|
var behaviour = new ParameterDefinition("behaviour", ParameterAttributes.None, asm.NetworkedBehaviour.Reference);
|
|
|
invoker.Parameters.Add(behaviour);
|
|
|
}
|
|
|
var message = new ParameterDefinition("message", ParameterAttributes.None, asm.SimulationMessage.Reference.MakePointerType());
|
|
|
invoker.Parameters.Add(message);
|
|
|
|
|
|
// add attribute
|
|
|
if (rpc.IsStatic) {
|
|
|
Log.Assert(instanceRpcKey < 0);
|
|
|
invoker.AddAttribute<NetworkRpcStaticWeavedInvokerAttribute, string>(asm, rpc.ToString());
|
|
|
} else {
|
|
|
Log.Assert(instanceRpcKey >= 0);
|
|
|
invoker.AddAttribute<NetworkRpcWeavedInvokerAttribute, int, int, int>(asm, instanceRpcKey, sources, targets);
|
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
invoker.AddAttribute<UnityEngine.Scripting.PreserveAttribute>(asm);
|
|
|
#endif
|
|
|
|
|
|
// put on type
|
|
|
invoker.AddTo(type);
|
|
|
|
|
|
// local variables
|
|
|
ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType());
|
|
|
ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int)));
|
|
|
var parameters = new VariableDefinition[rpc.Parameters.Count];
|
|
|
|
|
|
for (int i = 0; i < parameters.Length; ++i) {
|
|
|
invoker.Body.Variables.Add(parameters[i] = new VariableDefinition(rpc.Parameters[i].ParameterType));
|
|
|
}
|
|
|
|
|
|
invoker.Body.Variables.Add(ctx.DataVariable);
|
|
|
invoker.Body.Variables.Add(ctx.OffsetVariable);
|
|
|
invoker.Body.InitLocals = true;
|
|
|
|
|
|
var il = invoker.Body.GetILProcessor();
|
|
|
|
|
|
// grab data from message and store in local
|
|
|
il.Append(Ldarg_1());
|
|
|
il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.GetData), 1)));
|
|
|
il.Append(Stloc(ctx.DataVariable));
|
|
|
|
|
|
il.Append(Ldloc(ctx.DataVariable));
|
|
|
il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.ReadSize))));
|
|
|
il.AppendMacro(ctx.AlignToWordSize());
|
|
|
il.Append(Stloc(ctx.OffsetVariable));
|
|
|
|
|
|
for (int i = 0; i < parameters.Length; ++i) {
|
|
|
var para = parameters[i];
|
|
|
|
|
|
if (rpc.IsStatic && i == 0) {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Stloc(para));
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (rpcTargetParameter == rpc.Parameters[i]) {
|
|
|
il.Append(Ldarg_1());
|
|
|
il.Append(Ldfld(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Target))));
|
|
|
il.Append(Stloc(para));
|
|
|
} else if (para.VariableType.IsSame<RpcInfo>()) {
|
|
|
il.AppendMacro(ctx.LoadRunner());
|
|
|
il.Append(Ldarg_1());
|
|
|
il.Append(Ldc_I4((int)hostMode));
|
|
|
il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromMessage))));
|
|
|
il.Append(Stloc(para));
|
|
|
} else if (para.VariableType.IsArray) {
|
|
|
EmitRpcReadArray(il, ctx, rpc.Parameters[i], para.VariableType.GetElementTypeWithGenerics(), para);
|
|
|
} else {
|
|
|
using (ctx.TargetVariableAddr(para)) {
|
|
|
TypeRegistry.EmitRead(para.VariableType, il, ctx, rpc.Parameters[i]);
|
|
|
if (!ctx.TargetAddrUsed) {
|
|
|
il.Append(Stloc(para));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (rpc.IsStatic) {
|
|
|
il.Append(Ldc_I4(1));
|
|
|
il.Append(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc))));
|
|
|
} else {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldc_I4(1));
|
|
|
il.Append(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc))));
|
|
|
}
|
|
|
|
|
|
var callableRpc = rpc.GetCallable();
|
|
|
if (!rpc.IsStatic) {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Instruction.Create(OpCodes.Castclass, callableRpc.DeclaringType));
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < parameters.Length; ++i) {
|
|
|
il.Append(Ldloc(parameters[i]));
|
|
|
}
|
|
|
il.Append(Call(callableRpc));
|
|
|
if (returnsRpcInvokeInfo) {
|
|
|
il.Append(Pop());
|
|
|
}
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
{
|
|
|
Log.Assert(_rpcCount.TryGetValue(type, out int count) == false || count == instanceRpcKeys);
|
|
|
_rpcCount[type] = instanceRpcKeys;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private int GetInstanceRpcCount(TypeReference type) {
|
|
|
if (_rpcCount.TryGetValue(type, out int result)) {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
result = 0;
|
|
|
|
|
|
var typeDef = type.Resolve();
|
|
|
|
|
|
if (typeDef.BaseType != null) {
|
|
|
result += GetInstanceRpcCount(typeDef.BaseType);
|
|
|
}
|
|
|
|
|
|
result += typeDef.GetMethods()
|
|
|
.Where(x => !x.IsStatic)
|
|
|
.Where(x => x.HasAttribute<RpcAttribute>())
|
|
|
.Count();
|
|
|
|
|
|
_rpcCount.Add(type, result);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
private bool IsInvokeOnlyParameter(ParameterDefinition para) {
|
|
|
if (para.ParameterType.IsSame<RpcInfo>()) {
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void EmitRpcWriteArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) {
|
|
|
// store array length
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
il.Append(Stind_I4());
|
|
|
il.AppendMacro(context.AddOffset(sizeof(int)));
|
|
|
|
|
|
if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyFromArray), 2));
|
|
|
memCpy.GenericArguments.Add(elementType);
|
|
|
il.Append(Call(memCpy));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
} else {
|
|
|
il.AppendMacro(
|
|
|
context.For(
|
|
|
start: il => il.Append(Ldc_I4(0)),
|
|
|
stop: il => {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
},
|
|
|
body: (il, i) => {
|
|
|
using (context.ValueGetter((il, old) => {
|
|
|
old(il);
|
|
|
il.Append(Ldloc(i));
|
|
|
il.Append(Ldelem(elementType));
|
|
|
})) {
|
|
|
TypeRegistry.EmitWrite(elementType, il, context, member);
|
|
|
}
|
|
|
}
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void EmitRpcReadArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType, VariableDefinition arrayVar) {
|
|
|
// alloc array
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.Append(Ldind_I4());
|
|
|
il.Append(Instruction.Create(OpCodes.Newarr, elementType));
|
|
|
il.Append(Stloc(arrayVar));
|
|
|
il.AppendMacro(context.AddOffset(sizeof(int)));
|
|
|
|
|
|
if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) {
|
|
|
il.Append(Ldloc(arrayVar));
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
|
|
|
var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyToArray), 2));
|
|
|
memCpy.GenericArguments.Add(elementType);
|
|
|
il.Append(Call(memCpy));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
|
|
|
} else {
|
|
|
il.AppendMacro(
|
|
|
context.For(
|
|
|
start: il => il.Append(Ldc_I4(0)),
|
|
|
stop: il => {
|
|
|
il.Append(Ldloc(arrayVar));
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
},
|
|
|
body: (il, i) => {
|
|
|
var placeholder = Nop();
|
|
|
il.Append(placeholder);
|
|
|
|
|
|
using (context.TargetVariableAddr(arrayVar, i, elementType)) {
|
|
|
TypeRegistry.EmitRead(elementType, il, context, member);
|
|
|
if (!context.TargetAddrUsed) {
|
|
|
il.InsertAfter(placeholder, Ldloc(i));
|
|
|
il.InsertAfter(placeholder, Ldloc(arrayVar));
|
|
|
il.Append(Stelem(elementType));
|
|
|
}
|
|
|
}
|
|
|
il.Remove(placeholder);
|
|
|
|
|
|
}
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void EmitRpcArrayByteSize(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) {
|
|
|
var elementTypeData = TypeRegistry.GetInfo(elementType);
|
|
|
|
|
|
if (elementTypeData.HasDynamicRpcSize) {
|
|
|
var totalSize = context.CreateVariable(context.Assembly.Import<int>());
|
|
|
|
|
|
il.Append(Ldc_I4(sizeof(Int32)));
|
|
|
il.Append(Stloc(totalSize));
|
|
|
|
|
|
il.AppendMacro(
|
|
|
context.For(
|
|
|
start: il => il.Append(Ldc_I4(0)),
|
|
|
stop: il =>
|
|
|
{
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
},
|
|
|
(il, counter) => {
|
|
|
il.Append(Ldloc(totalSize));
|
|
|
|
|
|
using (context.ValueGetter((il, old) => {
|
|
|
old(il);
|
|
|
il.Append(Ldloc(counter));
|
|
|
il.Append(Ldelem(elementType));
|
|
|
})) {
|
|
|
TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: true);
|
|
|
}
|
|
|
|
|
|
il.Append(Add());
|
|
|
il.Append(Stloc(totalSize));
|
|
|
}
|
|
|
)
|
|
|
);
|
|
|
|
|
|
il.Append(Ldloc(totalSize));
|
|
|
|
|
|
} else {
|
|
|
// array length
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldlen());
|
|
|
il.Append(Conv_I4());
|
|
|
TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: false);
|
|
|
il.Append(Mul());
|
|
|
|
|
|
// store length as well
|
|
|
il.Append(Ldc_I4(sizeof(Int32)));
|
|
|
il.Append(Add());
|
|
|
|
|
|
// align
|
|
|
il.AppendMacro(context.AlignToWordSize());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void WeaveSimulation(ILWeaverAssembly asm, TypeDefinition type) {
|
|
|
EnsureTypeRegistry(asm);
|
|
|
WeaveRpcs(asm, type, allowInstanceRpcs: false);
|
|
|
WeaveUnityMessages(asm, type);
|
|
|
}
|
|
|
|
|
|
public static bool IsFieldOperand(Instruction instruction, FieldDefinition field, TypeReference declaringType) {
|
|
|
var storeField = instruction.Operand as FieldReference;
|
|
|
if (storeField == null) {
|
|
|
return false;
|
|
|
}
|
|
|
if (storeField == field) {
|
|
|
return true;
|
|
|
}
|
|
|
if (storeField.Name == field.Name && storeField.DeclaringType.Is(declaringType)) {
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private Instruction[] GetInlineFieldInit(MethodDefinition constructor, FieldDefinition field, TypeDefinition declaringType) {
|
|
|
if (field == null) {
|
|
|
throw new ArgumentNullException(nameof(field));
|
|
|
}
|
|
|
if (constructor == null) {
|
|
|
throw new ArgumentNullException(nameof(constructor));
|
|
|
}
|
|
|
|
|
|
var instructions = constructor.Body.Instructions;
|
|
|
|
|
|
int ldarg0Index = 0;
|
|
|
for (int i = 0; i < instructions.Count; ++i) {
|
|
|
var instruction = instructions[i];
|
|
|
if (instruction.OpCode == OpCodes.Ldarg_0) {
|
|
|
ldarg0Index = i;
|
|
|
} else if (instruction.OpCode == OpCodes.Stfld && IsFieldOperand(instruction, field, declaringType)) {
|
|
|
// regular init
|
|
|
return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray();
|
|
|
} else if (instruction.OpCode == OpCodes.Initobj && instruction.Previous?.OpCode == OpCodes.Ldflda && IsFieldOperand(instruction.Previous, field, declaringType)) {
|
|
|
// init with default constructor
|
|
|
return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray();
|
|
|
} else if (instruction.IsBaseConstructorCall(constructor.DeclaringType)) {
|
|
|
// base constructor init
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return Array.Empty<Instruction>();
|
|
|
}
|
|
|
|
|
|
private Instruction[] RemoveInlineFieldInit(TypeDefinition type, FieldDefinition field) {
|
|
|
var constructors = type.GetConstructors().Where(x => !x.IsStatic);
|
|
|
if (!constructors.Any()) {
|
|
|
return Array.Empty<Instruction>();
|
|
|
}
|
|
|
|
|
|
var firstConstructor = constructors.First();
|
|
|
var firstInlineInit = GetInlineFieldInit(firstConstructor, field, type).ToArray();
|
|
|
if (firstInlineInit.Length != 0) {
|
|
|
Log.Debug($"Found {field} inline init: {(string.Join("; ", firstInlineInit.Cast<object>()))}");
|
|
|
}
|
|
|
|
|
|
foreach (var constructor in constructors.Skip(1)) {
|
|
|
var otherInlineInit = GetInlineFieldInit(constructor, field, type);
|
|
|
if (!firstInlineInit.SequenceEqual(otherInlineInit, new InstructionEqualityComparer())) {
|
|
|
throw new ILWeaverException($"Expect inline init of {field} to be the same in all constructors," +
|
|
|
$" but there's a difference between {firstConstructor} and {constructor}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach (var constructor in constructors) {
|
|
|
Log.Debug($"Removing inline init of {field} from {constructor}");
|
|
|
var il = constructor.Body.GetILProcessor();
|
|
|
var otherInlineInit = GetInlineFieldInit(constructor, field, type);
|
|
|
foreach (var instruction in otherInlineInit.Reverse()) {
|
|
|
Log.Debug($"Removing {instruction}");
|
|
|
il.Remove(instruction);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return firstInlineInit;
|
|
|
}
|
|
|
|
|
|
private static bool IsMakeInitializerCall(Instruction instruction) {
|
|
|
if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) {
|
|
|
if (method.DeclaringType.IsSame<NetworkBehaviour>() && method.Name == nameof(NetworkBehaviour.MakeInitializer)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private static bool IsMakeRefOrMakePtrCall(Instruction instruction) {
|
|
|
if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) {
|
|
|
if (method.DeclaringType.IsSame<NetworkBehaviour>() && (
|
|
|
method.Name == nameof(NetworkBehaviour.MakeRef) || method.Name == nameof(NetworkBehaviour.MakePtr)
|
|
|
)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private void CheckIfMakeInitializerImplicitCast(Instruction instruction) {
|
|
|
if (instruction.OpCode == OpCodes.Call && (instruction.Operand as MethodReference)?.Name == "op_Implicit") {
|
|
|
// all good
|
|
|
} else {
|
|
|
throw new ILWeaverException($"Expected an implicit cast, got {instruction}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void ReplaceBackingFieldInInlineInit(ILWeaverAssembly asm, FieldDefinition backingField, FieldReference field, ILProcessor il, Instruction[] instructions) {
|
|
|
bool nextImplicitCast = false;
|
|
|
foreach (var instruction in instructions) {
|
|
|
if (nextImplicitCast) {
|
|
|
CheckIfMakeInitializerImplicitCast(instruction);
|
|
|
nextImplicitCast = false;
|
|
|
il.Remove(instruction);
|
|
|
} else if (IsFieldOperand(instruction, backingField, field.DeclaringType)) {
|
|
|
instruction.Operand = field;
|
|
|
} else if (IsMakeInitializerCall(instruction)) {
|
|
|
// dictionaries need one extra step, if using SerializableDictionary :(
|
|
|
if (Settings.UseSerializableDictionary && backingField.FieldType.IsNetworkDictionary(out var keyType, out var valueType)) {
|
|
|
var m = new GenericInstanceMethod(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.MakeSerializableDictionary)));
|
|
|
m.GenericArguments.Add(keyType);
|
|
|
m.GenericArguments.Add(valueType);
|
|
|
Log.Debug($"Inline init for {field}, replacing {instruction.Operand} with {m}");
|
|
|
instruction.Operand = m;
|
|
|
} else {
|
|
|
// remove the op, it will be fine
|
|
|
Log.Debug($"Inline init for {field}, removing {instruction}");
|
|
|
il.Remove(instruction);
|
|
|
}
|
|
|
nextImplicitCast = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static IEnumerable<TypeDefinition> AllTypeDefs(TypeDefinition definitions) {
|
|
|
yield return definitions;
|
|
|
|
|
|
if (definitions.HasNestedTypes) {
|
|
|
foreach (var nested in definitions.NestedTypes.SelectMany(AllTypeDefs)) {
|
|
|
yield return nested;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static IEnumerable<TypeDefinition> AllTypeDefs(Collection<TypeDefinition> definitions) {
|
|
|
return definitions.SelectMany(AllTypeDefs);
|
|
|
}
|
|
|
|
|
|
public bool Weave(ILWeaverAssembly asm) {
|
|
|
// if we don't have the weaved assembly attribute, we need to do weaving and insert the attribute
|
|
|
if (asm.CecilAssembly.HasAttribute<NetworkAssemblyWeavedAttribute>() != false) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
using (Log.ScopeAssembly(asm.CecilAssembly)) {
|
|
|
// grab main module .. this contains all the types we need
|
|
|
var module = asm.CecilAssembly.MainModule;
|
|
|
var moduleAllTypes = AllTypeDefs(module.Types).ToArray();
|
|
|
|
|
|
// go through all types and check for network behaviours
|
|
|
foreach (var t in moduleAllTypes) {
|
|
|
if (t.IsValueType && t.Is<INetworkStruct>()) {
|
|
|
try {
|
|
|
WeaveStruct(asm, t);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave struct {t}", ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach (var t in moduleAllTypes) {
|
|
|
if (t.IsValueType && t.Is<INetworkInput>()) {
|
|
|
try {
|
|
|
WeaveInput(asm, t);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave input {t}", ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach (var t in moduleAllTypes) {
|
|
|
if (t.IsSubclassOf<NetworkBehaviour>()) {
|
|
|
try {
|
|
|
WeaveBehaviour(asm, t);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave behaviour {t}", ex);
|
|
|
}
|
|
|
} else if (t.IsSubclassOf<SimulationBehaviour>()) {
|
|
|
try {
|
|
|
WeaveSimulation(asm, t);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave behaviour {t}", ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (Settings.CheckRpcAttributeUsage) {
|
|
|
using (Log.Scope("Checking RpcAttribute usage")) {
|
|
|
foreach (var t in moduleAllTypes) {
|
|
|
if (t.IsSubclassOf<SimulationBehaviour>()) {
|
|
|
continue;
|
|
|
}
|
|
|
foreach (var method in t.Methods) {
|
|
|
if (method.TryGetAttribute<RpcAttribute>(out _)) {
|
|
|
Log.Warn(method, $"Incorrect {nameof(RpcAttribute)} usage on {method}: only types derived from {nameof(SimulationBehaviour)} and {nameof(NetworkBehaviour)} are supported");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// only if it was modified
|
|
|
if (asm.Modified) {
|
|
|
// add weaved assembly attribute to this assembly
|
|
|
asm.CecilAssembly.CustomAttributes.Add(new CustomAttribute(typeof(NetworkAssemblyWeavedAttribute).GetConstructor(asm)));
|
|
|
}
|
|
|
|
|
|
return asm.Modified;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaver.INetworkedStruct.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using static Fusion.CodeGen.ILWeaverOpCodes;
|
|
|
using FieldAttributes = Mono.Cecil.FieldAttributes;
|
|
|
|
|
|
unsafe partial class ILWeaver {
|
|
|
|
|
|
const int WordSize = Allocator.REPLICATE_WORD_SIZE;
|
|
|
NetworkTypeInfoRegistry TypeRegistry;
|
|
|
|
|
|
public int CalculateStructWordCount(TypeReference typeRef) {
|
|
|
var type = typeRef.Resolve();
|
|
|
Log.Assert(type.TryGetAttribute<NetworkStructWeavedAttribute>(out _) == false);
|
|
|
|
|
|
var wordCount = 0;
|
|
|
|
|
|
foreach (var property in type.Properties) {
|
|
|
if (!IsWeavableProperty(property, out var propertyInfo)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
wordCount += TypeRegistry.GetPropertyWordCount(property);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to get word count of property {property}", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// figure out word counts for everything
|
|
|
foreach (var field in type.Fields) {
|
|
|
// skip statics
|
|
|
if (field.IsStatic) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// increase block count
|
|
|
wordCount += TypeRegistry.GetTypeWordCount(field.FieldType);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to get word count of field {field}", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return wordCount;
|
|
|
}
|
|
|
|
|
|
public bool WeaveInput(ILWeaverAssembly asm, TypeReference typeRef) {
|
|
|
ILWeaverException.DebugThrowIf(!typeRef.Is<INetworkInput>(), $"Not a {nameof(INetworkInput)}");
|
|
|
|
|
|
|
|
|
if (typeRef.Module != asm.CecilAssembly.MainModule) {
|
|
|
throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}");
|
|
|
}
|
|
|
|
|
|
var type = typeRef.Resolve();
|
|
|
if (type.TryGetAttribute<NetworkStructWeavedAttribute>(out _)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
EnsureTypeRegistry(asm);
|
|
|
using (Log.ScopeInput(type)) {
|
|
|
int wordCount = WeaveStructInner(asm, type);
|
|
|
// add new attribute
|
|
|
type.AddAttribute<NetworkInputWeavedAttribute, int>(asm, wordCount);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public bool WeaveStruct(ILWeaverAssembly asm, TypeReference typeRef) {
|
|
|
ILWeaverException.DebugThrowIf(!typeRef.Is<INetworkStruct>(), $"Not a {nameof(INetworkStruct)}");
|
|
|
|
|
|
if (typeRef.Module != asm.CecilAssembly.MainModule) {
|
|
|
throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}");
|
|
|
}
|
|
|
|
|
|
var type = typeRef.Resolve();
|
|
|
if (type.TryGetAttribute<NetworkStructWeavedAttribute>(out _)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
EnsureTypeRegistry(asm);
|
|
|
|
|
|
using (Log.ScopeStruct(type)) {
|
|
|
int wordCount = WeaveStructInner(asm, type);
|
|
|
// add new attribute
|
|
|
type.AddAttribute<NetworkStructWeavedAttribute, int>(asm, wordCount);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int WeaveStructInner(ILWeaverAssembly asm, TypeDefinition type) {
|
|
|
|
|
|
// flag asm as modified
|
|
|
asm.Modified = true;
|
|
|
|
|
|
// set as explicit layout
|
|
|
type.IsExplicitLayout = true;
|
|
|
|
|
|
Log.Assert(type.IsValueType);
|
|
|
|
|
|
if (!type.Is<INetworkStruct>() && !type.Is<INetworkInput>()) {
|
|
|
throw new ILWeaverException($"Structs need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)}");
|
|
|
}
|
|
|
|
|
|
// clear all backing fields
|
|
|
foreach (var property in type.Properties) {
|
|
|
if (!IsWeavableProperty(property, out var propertyInfo)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
if (TypeRegistry.GetInfo(property.PropertyType).IsTriviallyCopyable) {
|
|
|
Log.Warn(property, $"Networked property {property} should be replaced with a regular field. For structs, " +
|
|
|
$"[Networked] attribute should to be applied only on collections, booleans, floats and vectors.");
|
|
|
}
|
|
|
|
|
|
int fieldIndex = type.Fields.Count;
|
|
|
|
|
|
if (propertyInfo.BackingField != null) {
|
|
|
if (!propertyInfo.BackingField.FieldType.IsValueType) {
|
|
|
Log.Warn(property, $"Networked property {property} has a backing field that is not a value type. To keep unmanaged status," +
|
|
|
$" the accessor should follow \"{{ get => default; set {{}} }}\" pattern");
|
|
|
}
|
|
|
|
|
|
fieldIndex = type.Fields.IndexOf(propertyInfo.BackingField);
|
|
|
if (fieldIndex >= 0) {
|
|
|
type.Fields.RemoveAt(fieldIndex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (Settings.CheckNetworkedPropertiesBeingEmpty) {
|
|
|
try {
|
|
|
ThrowIfPropertyNotEmptyOrCompilerGenerated(property);
|
|
|
} catch (Exception ex) {
|
|
|
Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var propertyWordCount = TypeRegistry.GetPropertyWordCount(property);
|
|
|
|
|
|
property.GetMethod?.RemoveAttribute<CompilerGeneratedAttribute>(asm);
|
|
|
property.SetMethod?.RemoveAttribute<CompilerGeneratedAttribute>(asm);
|
|
|
|
|
|
|
|
|
var getIL = property.GetMethod.Body.GetILProcessor();
|
|
|
getIL.Clear();
|
|
|
getIL.Body.Variables.Clear();
|
|
|
|
|
|
var setIL = property.SetMethod?.Body.GetILProcessor();
|
|
|
if (setIL != null) {
|
|
|
setIL.Clear();
|
|
|
setIL.Body.Variables.Clear();
|
|
|
}
|
|
|
|
|
|
var backingFieldName = $"_{property.Name}";
|
|
|
var fixedBufferInfo = MakeFixedBuffer(asm, propertyWordCount);
|
|
|
var surrogateType = MakeUnitySurrogate(asm, property);
|
|
|
var storageField = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fixedBufferInfo);
|
|
|
|
|
|
TypeRegistry.GetInfo(property.PropertyType).TryGetCapacity(property, out var capacity);
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
var fixedBufferAttribute = storageField.AddAttribute<FixedBufferPropertyAttribute, TypeReference, TypeReference, int>(asm, property.PropertyType, surrogateType, capacity);
|
|
|
|
|
|
// this attribute should always be used first; depending on Unity version this means different order
|
|
|
fixedBufferAttribute.Properties.Add(new CustomAttributeNamedArgument(nameof(PropertyAttribute.order), new CustomAttributeArgument(asm.Import<int>(),
|
|
|
#if UNITY_2021_1_OR_NEWER
|
|
|
-int.MaxValue
|
|
|
#else
|
|
|
int.MaxValue
|
|
|
#endif
|
|
|
)));
|
|
|
#endif
|
|
|
|
|
|
|
|
|
storageField.InsertTo(type, fieldIndex);
|
|
|
|
|
|
// move field attributes, if any
|
|
|
if (propertyInfo.BackingField != null) {
|
|
|
MoveBackingFieldAttributes(asm, propertyInfo.BackingField, storageField);
|
|
|
}
|
|
|
MovePropertyAttributesToBackingField(asm, property, storageField);
|
|
|
|
|
|
Action<ILProcessor> addressGetter = il => {
|
|
|
var m = new GenericInstanceMethod(asm.Native.GetMethod(nameof(Native.ReferenceToPointer)));
|
|
|
m.GenericArguments.Add(storageField.FieldType);
|
|
|
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldflda(storageField));
|
|
|
|
|
|
il.Append(Call(m));
|
|
|
};
|
|
|
|
|
|
EmitRead(asm, getIL, property, addressGetter);
|
|
|
if (setIL != null) {
|
|
|
EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1);
|
|
|
}
|
|
|
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave property {property}", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// figure out word counts for everything
|
|
|
var wordCount = 0;
|
|
|
|
|
|
foreach (var field in type.Fields) {
|
|
|
|
|
|
// skip statics
|
|
|
if (field.IsStatic) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// set offset
|
|
|
field.Offset = wordCount * Allocator.REPLICATE_WORD_SIZE;
|
|
|
|
|
|
try {
|
|
|
// increase block count
|
|
|
wordCount += TypeRegistry.GetTypeWordCount(field.FieldType);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to get word count of field {field}", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
type.PackingSize = 0;
|
|
|
type.ClassSize = wordCount * Allocator.REPLICATE_WORD_SIZE;
|
|
|
return wordCount;
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaver.NetworkBehaviour.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using static Fusion.CodeGen.ILWeaverOpCodes;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using FieldAttributes = Mono.Cecil.FieldAttributes;
|
|
|
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
using UnityEngine;
|
|
|
using UnityEngine.Scripting;
|
|
|
#endif
|
|
|
|
|
|
unsafe partial class ILWeaver {
|
|
|
|
|
|
FieldDefinition AddNetworkBehaviourBackingField(PropertyDefinition property) {
|
|
|
|
|
|
var fieldType = TypeRegistry.GetInfo(property.PropertyType).GetUnityBackingFieldType(true);
|
|
|
var field = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fieldType);
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
private void MoveBackingFieldAttributes(ILWeaverAssembly asm, FieldDefinition backingField, FieldDefinition storageField) {
|
|
|
if (backingField.IsNotSerialized) {
|
|
|
storageField.IsNotSerialized = true;
|
|
|
}
|
|
|
|
|
|
foreach (var attrib in backingField.CustomAttributes) {
|
|
|
if (attrib.AttributeType.Is<CompilerGeneratedAttribute>() ||
|
|
|
attrib.AttributeType.Is<System.Diagnostics.DebuggerBrowsableAttribute>()) {
|
|
|
continue;
|
|
|
}
|
|
|
storageField.CustomAttributes.Add(attrib);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void VisitPropertyMovableAttributes(PropertyDefinition property, Action<MethodReference, byte[]> onAttribute) {
|
|
|
foreach (var attribute in property.CustomAttributes) {
|
|
|
if (attribute.AttributeType.IsSame<NetworkedAttribute>() ||
|
|
|
attribute.AttributeType.IsSame<NetworkedWeavedAttribute>() ||
|
|
|
attribute.AttributeType.IsSame<CapacityAttribute>()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
var attribDef = attribute.AttributeType.Resolve();
|
|
|
|
|
|
if (attribDef.TryGetAttribute<UnityPropertyAttributeProxyAttribute>(out var proxy)) {
|
|
|
#if UNITY_EDITOR
|
|
|
var attribTypeRef = proxy.GetAttributeArgument<TypeReference>(0);
|
|
|
var attribTypeDef = attribTypeRef.Resolve();
|
|
|
|
|
|
if (attribTypeDef.TryGetMatchingConstructor(attribute.Constructor.Resolve(), out var constructor)) {
|
|
|
onAttribute(constructor, attribute.GetBlob());
|
|
|
} else {
|
|
|
Log.Warn(property, $"Failed to find matching constructor of {attribTypeDef} for {attribute.Constructor} (property {property})");
|
|
|
}
|
|
|
#endif
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (attribDef.TryGetAttribute<AttributeUsageAttribute>(out var usage)) {
|
|
|
var targets = usage.GetAttributeArgument<AttributeTargets>(0);
|
|
|
if ((targets & AttributeTargets.Field) != AttributeTargets.Field) {
|
|
|
Log.Debug($"Attribute {attribute.AttributeType} can't be applied on a field, skipping.");
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
onAttribute(attribute.Constructor, attribute.GetBlob());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void MovePropertyAttributesToBackingField(ILWeaverAssembly asm, PropertyDefinition property, FieldDefinition field, bool addSerializeField = true) {
|
|
|
bool hasNonSerialized = false;
|
|
|
|
|
|
VisitPropertyMovableAttributes(property, (ctor, blob) => {
|
|
|
Log.Debug($"Adding {ctor.DeclaringType} to {field}");
|
|
|
field.CustomAttributes.Add(new CustomAttribute(asm.Import(ctor), blob));
|
|
|
if (ctor.DeclaringType.IsSame<UnityNonSerializedAttribute>()) {
|
|
|
Log.Debug($"{field} marked as NonSerialized, SerializeField will not be applied");
|
|
|
hasNonSerialized = true;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
if (addSerializeField) {
|
|
|
if (!hasNonSerialized && property.GetMethod.IsPublic) {
|
|
|
if (field.IsNotSerialized) {
|
|
|
// prohibited
|
|
|
} else if (field.HasAttribute<UnityEngine.SerializeField>()) {
|
|
|
// already added
|
|
|
} else {
|
|
|
field.AddAttribute<UnityEngine.SerializeField>(asm);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
|
|
|
// bool TryGetNetworkBehaviourTGenericArgument(ILWeaverAssembly asm, TypeReference type, out TypeReference genericArgument) {
|
|
|
// var outerType = type;
|
|
|
// while (!type.IsSame<NetworkBehaviour>()) {
|
|
|
//
|
|
|
// var typeDef = type.Resolve();
|
|
|
// if (typeDef.IsSame(asm.NetworkedBehaviourT)) {
|
|
|
// var genericInstance = (GenericInstanceType)type;
|
|
|
// genericArgument = genericInstance.GenericArguments[0];
|
|
|
// if (genericArgument is GenericParameter gp && gp.TryResolve(outerType, out var resolved)) {
|
|
|
// genericArgument = resolved;
|
|
|
// }
|
|
|
// return true;
|
|
|
// }
|
|
|
//
|
|
|
// type = typeDef.BaseType;
|
|
|
// }
|
|
|
//
|
|
|
// genericArgument = null;
|
|
|
// return false;
|
|
|
// }
|
|
|
|
|
|
public int GetBehaviourWordCount(ILWeaverAssembly asm, TypeReference type) {
|
|
|
int wordCount = 0;
|
|
|
var outerType = type;
|
|
|
|
|
|
while (!type.IsSame<NetworkBehaviour>()) {
|
|
|
|
|
|
var typeDef = type.Resolve();
|
|
|
|
|
|
if (typeDef.TryGetAttribute<NetworkBehaviourWeavedAttribute>(out var weavedAttribute)) {
|
|
|
var result = weavedAttribute.GetAttributeArgument<int>(0);
|
|
|
if (result > 0) {
|
|
|
wordCount += result;
|
|
|
}
|
|
|
|
|
|
// else if (TryGetNetworkBehaviourTGenericArgument(asm, outerType, out var genericArgument)) {
|
|
|
// if (genericArgument is GenericParameter) {
|
|
|
// Log.Assert(wordCount == 0);
|
|
|
// return -1;
|
|
|
// } else {
|
|
|
// var genericTypeDef = genericArgument.Resolve();
|
|
|
// if (genericTypeDef == null) {
|
|
|
// throw new ILWeaverException($"Failed to resolve generic argument {genericArgument} of {outerType}");
|
|
|
// }
|
|
|
//
|
|
|
// wordCount += TypeRegistry.GetTypeWordCount(genericTypeDef);
|
|
|
// }
|
|
|
// }
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
foreach (var property in typeDef.Properties) {
|
|
|
if (!IsWeavableProperty(property, out var propertyInfo)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
wordCount += TypeRegistry.GetPropertyWordCount(property);
|
|
|
}
|
|
|
|
|
|
type = typeDef.BaseType;
|
|
|
}
|
|
|
|
|
|
return wordCount;
|
|
|
}
|
|
|
|
|
|
public int WeaveBehaviour(ILWeaverAssembly asm, TypeDefinition type) {
|
|
|
|
|
|
if (type.IsSame<NetworkBehaviour>()) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
if (!type.IsSubclassOf<NetworkBehaviour>()) {
|
|
|
throw new ILWeaverException($"Not a {nameof(NetworkBehaviour)}");
|
|
|
}
|
|
|
|
|
|
if (type.Module != asm.CecilAssembly.MainModule) {
|
|
|
throw new ILWeaverException($"Type {type} is not in the main module of assembly {asm.CecilAssembly}");
|
|
|
}
|
|
|
|
|
|
if (type.TryGetAttribute<NetworkBehaviourWeavedAttribute>(out var weavedAttribute)) {
|
|
|
return weavedAttribute.GetAttributeArgument<int>(0);
|
|
|
}
|
|
|
|
|
|
bool isOpenGenericNB = false;
|
|
|
|
|
|
// if (TryGetNetworkBehaviourTGenericArgument(asm, type, out var genericArgument)) {
|
|
|
// if (genericArgument is GenericParameter) {
|
|
|
// isOpenGenericNB = true;
|
|
|
// }
|
|
|
// }
|
|
|
|
|
|
var baseType = type.BaseType.Resolve();
|
|
|
|
|
|
if (baseType.Module == type.Module) {
|
|
|
//Log.Warn($"Weaving base ASAP {baseType} {type}");
|
|
|
WeaveBehaviour(asm, baseType);
|
|
|
}
|
|
|
|
|
|
EnsureTypeRegistry(asm);
|
|
|
|
|
|
// flag as modified
|
|
|
asm.Modified = true;
|
|
|
|
|
|
|
|
|
using (Log.ScopeBehaviour(type)) {
|
|
|
// get block count of parent as starting point for ourselves
|
|
|
var wordCount = GetBehaviourWordCount(asm, type.BaseType);
|
|
|
|
|
|
// this is the data field which holds this behaviours root pointer
|
|
|
//var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr));
|
|
|
var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr));
|
|
|
|
|
|
var setDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyBackingFieldsToState));
|
|
|
var getDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyStateToBackingFields));
|
|
|
|
|
|
FieldDefinition lastAddedFieldWithKnownPosition = null;
|
|
|
List<FieldDefinition> fieldsWithUncertainPosition = new List<FieldDefinition>();
|
|
|
|
|
|
foreach (var property in type.Properties) {
|
|
|
if (!IsWeavableProperty(property, out var propertyInfo)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (isOpenGenericNB) {
|
|
|
throw new ILWeaverException($"Open generic {nameof(NetworkBehaviour)} can't have [Networked] properties.");
|
|
|
}
|
|
|
|
|
|
if (!string.IsNullOrEmpty(propertyInfo.OnChanged)) {
|
|
|
WeaveChangedHandler(asm, property, propertyInfo.OnChanged);
|
|
|
}
|
|
|
|
|
|
if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) {
|
|
|
var elementType = property.PropertyType.GetElementTypeWithGenerics();
|
|
|
if (!TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) {
|
|
|
throw new ILWeaverException($"{property}: type {elementType} can't be used in pointer/reference properties.");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
try {
|
|
|
// try to maintain fields order
|
|
|
int backingFieldIndex = type.Fields.Count;
|
|
|
if (propertyInfo.BackingField != null) {
|
|
|
backingFieldIndex = type.Fields.IndexOf(propertyInfo.BackingField);
|
|
|
if (backingFieldIndex >= 0) {
|
|
|
type.Fields.RemoveAt(backingFieldIndex);
|
|
|
} else {
|
|
|
Log.Warn(property, $"Unable to find backing field for {property}");
|
|
|
backingFieldIndex = type.Fields.Count;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var readOnlyInit = GetReadOnlyPropertyInitializer(property);
|
|
|
|
|
|
// prepare getter/setter methods
|
|
|
if (readOnlyInit == null && Settings.CheckNetworkedPropertiesBeingEmpty) {
|
|
|
try {
|
|
|
ThrowIfPropertyNotEmptyOrCompilerGenerated(property);
|
|
|
} catch (Exception ex){
|
|
|
Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var (getter, setter) = PreparePropertyForWeaving(property);
|
|
|
var getterRef = getter.GetCallable();
|
|
|
var setterRef = setter?.GetCallable();
|
|
|
|
|
|
// capture word count in case we re-use the lambda that is created later on ...
|
|
|
var wordOffset = wordCount;
|
|
|
|
|
|
var getIL = getter.Body.GetILProcessor();
|
|
|
var setIL = setter?.Body?.GetILProcessor();
|
|
|
|
|
|
Action<ILProcessor> addressGetter = il => {
|
|
|
il.Append(Ldarg_0());
|
|
|
//il.Append(Ldfld(ptrField));
|
|
|
il.Append(Ldfld(ptrGetter));
|
|
|
il.Append(Ldc_I4(wordOffset * Allocator.REPLICATE_WORD_SIZE));
|
|
|
il.Append(Add());
|
|
|
};
|
|
|
|
|
|
// emit accessors
|
|
|
InjectPtrNullCheck(asm, getIL, property);
|
|
|
EmitRead(asm, getIL, property, addressGetter);
|
|
|
|
|
|
if (setIL != null) {
|
|
|
InjectPtrNullCheck(asm, setIL, property);
|
|
|
EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1);
|
|
|
}
|
|
|
|
|
|
var propertyWordCount = TypeRegistry.GetPropertyWordCount(property);
|
|
|
|
|
|
// step up wordcount
|
|
|
Log.Assert(wordCount >= 0);
|
|
|
wordCount += propertyWordCount;
|
|
|
|
|
|
var typeInfo = TypeRegistry.GetInfo(property.PropertyType);
|
|
|
|
|
|
// inject attribute to poll weaver data during runtime
|
|
|
property.AddAttribute<NetworkedWeavedAttribute, int, int>(asm, wordOffset, propertyWordCount);
|
|
|
|
|
|
if (property.HasAttribute<UnityNonSerializedAttribute>() || propertyInfo.BackingField?.IsNotSerialized == true) {
|
|
|
// so the property is not serialized, so there will be no backing field.
|
|
|
|
|
|
Instruction[] fieldInit = null;
|
|
|
VariableDefinition[] fieldInitLocalVariables = null;
|
|
|
|
|
|
if (readOnlyInit?.Instructions.Length > 0) {
|
|
|
fieldInit = readOnlyInit.Value.Instructions;
|
|
|
fieldInitLocalVariables = readOnlyInit.Value.Variables;
|
|
|
} else if (propertyInfo.BackingField != null) {
|
|
|
fieldInit = RemoveInlineFieldInit(type, propertyInfo.BackingField);
|
|
|
fieldInitLocalVariables = Array.Empty<VariableDefinition>();
|
|
|
}
|
|
|
|
|
|
if (fieldInit?.Any() == true) {
|
|
|
|
|
|
var storeIndex = Array.FindIndex(fieldInit, x => IsMakeInitializerCall(x) ||
|
|
|
x.OpCode == OpCodes.Stfld && IsFieldOperand(x, propertyInfo.BackingField, type));
|
|
|
|
|
|
if (storeIndex >= 0) {
|
|
|
Log.Assert(fieldInit[0].OpCode == OpCodes.Ldarg_0);
|
|
|
fieldInit = fieldInit.Skip(1).Take(storeIndex - 1).ToArray();
|
|
|
} else {
|
|
|
// keep as it is
|
|
|
}
|
|
|
|
|
|
// create initializer method
|
|
|
var initializeMethod = new MethodDefinition($"FusionCodeGen@Initialize@{property.Name}", MethodAttributes.Private, typeInfo.GetUnityBackingFieldType(false));
|
|
|
initializeMethod.AddTo(type);
|
|
|
initializeMethod.AddAttribute<DefaultForPropertyAttribute, string, int, int>(asm, property.Name, wordOffset, propertyWordCount);
|
|
|
|
|
|
{
|
|
|
var (initClone, _) = MonoCecilExtensions.CloneAndFixUp(initializeMethod.Body, fieldInit, fieldInitLocalVariables);
|
|
|
var il = initializeMethod.Body.GetILProcessor();
|
|
|
foreach (var instruction in initClone) {
|
|
|
il.Append(instruction);
|
|
|
}
|
|
|
|
|
|
il.Append(Ret());
|
|
|
}
|
|
|
|
|
|
{
|
|
|
// need to patch defaults with this, but only during the initial set
|
|
|
var il = setDefaults.Item1.Body.GetILProcessor();
|
|
|
var postInit = Nop();
|
|
|
il.Append(Ldarg_1());
|
|
|
il.Append(Brfalse(postInit));
|
|
|
|
|
|
typeInfo.EmitUnityInit(property, il, null, il => { ;
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(initializeMethod));
|
|
|
});
|
|
|
|
|
|
il.Append(postInit);
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
FieldReference defaultField = null;
|
|
|
{
|
|
|
FieldDefinition defaultFieldDef;
|
|
|
|
|
|
if (string.IsNullOrEmpty(propertyInfo.DefaultFieldName)) {
|
|
|
|
|
|
defaultFieldDef = AddNetworkBehaviourBackingField(property);
|
|
|
if (propertyInfo.BackingField != null) {
|
|
|
defaultFieldDef.InsertTo(type, backingFieldIndex);
|
|
|
MoveBackingFieldAttributes(asm, propertyInfo.BackingField, defaultFieldDef);
|
|
|
|
|
|
if (lastAddedFieldWithKnownPosition == null) {
|
|
|
// fixup fields that have been added without knowing their index
|
|
|
foreach (var f in fieldsWithUncertainPosition) {
|
|
|
type.Fields.Remove(f);
|
|
|
}
|
|
|
|
|
|
var index = type.Fields.IndexOf(defaultFieldDef);
|
|
|
fieldsWithUncertainPosition.Reverse();
|
|
|
foreach (var f in fieldsWithUncertainPosition) {
|
|
|
f.InsertTo(type, index);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
lastAddedFieldWithKnownPosition = defaultFieldDef;
|
|
|
|
|
|
} else {
|
|
|
if (lastAddedFieldWithKnownPosition == null) {
|
|
|
// not sure where to put this... append
|
|
|
defaultFieldDef.AddTo(type);
|
|
|
fieldsWithUncertainPosition.Add(defaultFieldDef);
|
|
|
} else {
|
|
|
// add after the previous field
|
|
|
var index = type.Fields.IndexOf(lastAddedFieldWithKnownPosition);
|
|
|
Log.Assert(index >= 0);
|
|
|
|
|
|
defaultFieldDef.InsertTo(type, index + 1);
|
|
|
lastAddedFieldWithKnownPosition = defaultFieldDef;
|
|
|
}
|
|
|
}
|
|
|
MovePropertyAttributesToBackingField(asm, property, defaultFieldDef);
|
|
|
} else {
|
|
|
defaultFieldDef = property.DeclaringType.GetFieldOrThrow(propertyInfo.DefaultFieldName);
|
|
|
}
|
|
|
|
|
|
defaultFieldDef.AddAttribute<DefaultForPropertyAttribute, string, int, int>(asm, property.Name, wordOffset, propertyWordCount);
|
|
|
defaultFieldDef.AddAttribute<DrawIfAttribute, string, bool, CompareOperator, DrawIfMode>(asm, nameof(NetworkBehaviour.IsEditorWritable), true, CompareOperator.Equal, DrawIfMode.ReadOnly);
|
|
|
defaultField = defaultFieldDef.GetLoadable();
|
|
|
}
|
|
|
|
|
|
|
|
|
// in each constructor, replace inline init, if present
|
|
|
foreach (var constructor in type.GetConstructors()) {
|
|
|
|
|
|
if (readOnlyInit?.Instructions.Length > 0) {
|
|
|
var il = constructor.Body.GetILProcessor();
|
|
|
|
|
|
Instruction before = il.Body.Instructions[0];
|
|
|
{
|
|
|
// find where to plug in; after last stfld, but before base constructor call
|
|
|
for (int i = 0; i < il.Body.Instructions.Count; ++i) {
|
|
|
var instruction = il.Body.Instructions[i];
|
|
|
if (instruction.IsBaseConstructorCall(type)) {
|
|
|
break;
|
|
|
} else if (instruction.OpCode == OpCodes.Stfld) {
|
|
|
before = il.Body.Instructions[i + 1];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// clone variables
|
|
|
var (instructions, variables) = CloneInstructions(readOnlyInit.Value.Instructions, readOnlyInit.Value.Variables);
|
|
|
|
|
|
foreach (var variable in variables) {
|
|
|
il.Body.Variables.Add(variable);
|
|
|
}
|
|
|
|
|
|
il.InsertBefore(before, Ldarg_0());
|
|
|
foreach (var instruction in instructions) {
|
|
|
il.InsertBefore(before, instruction);
|
|
|
}
|
|
|
il.InsertBefore(before, Stfld(defaultField));
|
|
|
} else if (propertyInfo.BackingField != null) {
|
|
|
// remove the inline init, if present
|
|
|
var init = GetInlineFieldInit(constructor, propertyInfo.BackingField, type);
|
|
|
if (init.Length > 0) {
|
|
|
ReplaceBackingFieldInInlineInit(asm, propertyInfo.BackingField, defaultField, constructor.Body.GetILProcessor(), init);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
typeInfo.EmitUnityInit(property, setDefaults.Item1.Body.GetILProcessor(), defaultField.FieldType, il => {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldfld(defaultField));
|
|
|
});
|
|
|
|
|
|
typeInfo.EmitUnityStore(property, getDefaults.Item1.Body.GetILProcessor(), defaultField);
|
|
|
}
|
|
|
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Failed to weave property {property}", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
{
|
|
|
var (method, instruction) = setDefaults;
|
|
|
method.Body.GetILProcessor().Append(instruction);
|
|
|
}
|
|
|
|
|
|
{
|
|
|
var (method, instruction) = getDefaults;
|
|
|
method.Body.GetILProcessor().Append(instruction);
|
|
|
}
|
|
|
|
|
|
if (wordCount != GetBehaviourWordCount(asm, type)) {
|
|
|
throw new ILWeaverException($"Failed to weave {type} - word count mismatch {wordCount} vs {GetBehaviourWordCount(asm, type)}");
|
|
|
}
|
|
|
|
|
|
// add meta attribute
|
|
|
type.AddAttribute<NetworkBehaviourWeavedAttribute, int>(asm, wordCount);
|
|
|
|
|
|
WeaveRpcs(asm, type);
|
|
|
WeaveUnityMessages(asm, type);
|
|
|
return wordCount;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void WeaveUnityMessages(ILWeaverAssembly asm, TypeDefinition type) {
|
|
|
|
|
|
WeaveUnityMessage("OnDestroy", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDestroy)));
|
|
|
WeaveUnityMessage("OnEnable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnEnable)));
|
|
|
WeaveUnityMessage("OnDisable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDisable)));
|
|
|
|
|
|
void WeaveUnityMessage(string methodName, MethodReference internalMethod) {
|
|
|
if (!type.TryGetMethod(methodName, out var method)) {
|
|
|
// all good, nothing to do
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
var instructions = method.Body.Instructions;
|
|
|
bool hasCleanUp = instructions
|
|
|
.Where(x => x.OpCode == OpCodes.Call)
|
|
|
.Select(x => (MethodReference)x.Operand)
|
|
|
.Any(m => m.Name == nameof(internalMethod.Name) && m.DeclaringType.Is(internalMethod.DeclaringType));
|
|
|
|
|
|
if (hasCleanUp) {
|
|
|
Log.Debug($"Type {type} has a custom {methodName} method that calls {internalMethod.Name}, skipping");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
asm.Modified = true;
|
|
|
|
|
|
Log.Debug($"Type {type} has a custom {methodName} method, weaving it");
|
|
|
instructions.Insert(0, Ldarg_0());
|
|
|
instructions.Insert(1, Call(internalMethod));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void WeaveChangedHandler(ILWeaverAssembly asm, PropertyDefinition property, string handlerName) {
|
|
|
|
|
|
// find the handler
|
|
|
{
|
|
|
foreach (var declaringType in property.DeclaringType.GetHierarchy()) {
|
|
|
var candidates = declaringType.GetMethods()
|
|
|
.Where(x => x.IsStatic)
|
|
|
.Where(x => x.Name == handlerName)
|
|
|
.Where(x => x.HasParameters && x.Parameters.Count == 1)
|
|
|
.Where(x => x.Parameters[0].ParameterType.IsSubclassOf<NetworkBehaviour>())
|
|
|
.ToList();
|
|
|
|
|
|
if (candidates.Count > 1) {
|
|
|
throw new ILWeaverException($"Ambiguous match for OnChanged handler for {property}: {string.Join("; ", candidates)}");
|
|
|
}
|
|
|
|
|
|
if (candidates.Count == 1) {
|
|
|
var handler = candidates[0];
|
|
|
|
|
|
Log.Debug($"OnChanged handler for {property}: {handler}");
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
// add preserve attribute, if not added already
|
|
|
if (!handler.TryGetAttribute<PreserveAttribute>(out _)) {
|
|
|
handler.AddAttribute<PreserveAttribute>(asm);
|
|
|
Log.Debug($"Added {nameof(PreserveAttribute)} to {handler}");
|
|
|
}
|
|
|
#endif
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw new ILWeaverException($"No match found for OnChanged handler for {property}");
|
|
|
}
|
|
|
|
|
|
struct ReadOnlyInitializer {
|
|
|
public Instruction[] Instructions;
|
|
|
public VariableDefinition[] Variables;
|
|
|
}
|
|
|
|
|
|
private ReadOnlyInitializer? GetReadOnlyPropertyInitializer(PropertyDefinition property) {
|
|
|
if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) {
|
|
|
// need to check if there's MakeRef/Ptr before getter gets obliterated
|
|
|
var instructions = property.GetMethod.Body.Instructions;
|
|
|
|
|
|
|
|
|
for (int i = 0; i < instructions.Count; ++i) {
|
|
|
var instr = instructions[i];
|
|
|
if (IsMakeRefOrMakePtrCall(instr)) {
|
|
|
// found it!
|
|
|
return new ReadOnlyInitializer() {
|
|
|
Instructions = instructions.Take(i).ToArray(),
|
|
|
Variables = property.GetMethod.Body.Variables.ToArray()
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
private (Instruction[], VariableDefinition[]) CloneInstructions(Instruction[] source, VariableDefinition[] sourceVariables) {
|
|
|
|
|
|
var constructor = typeof(Instruction).GetConstructor(
|
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null,
|
|
|
new[] { typeof(OpCode), typeof(object) }, null);
|
|
|
|
|
|
// shallow copy
|
|
|
var result = source.Select(x => (Instruction)constructor.Invoke(new[] { x.OpCode, x.Operand }))
|
|
|
.ToArray();
|
|
|
|
|
|
var variableMapping = new Dictionary<VariableDefinition, VariableDefinition>();
|
|
|
|
|
|
// now need to resolve local variables and jump targets
|
|
|
foreach (var instruction in result) {
|
|
|
|
|
|
if (instruction.IsLdlocWithIndex(out var locIndex) || instruction.IsStlocWithIndex(out locIndex)) {
|
|
|
var variable = sourceVariables[locIndex];
|
|
|
if (!variableMapping.TryGetValue(variable, out var replacement)) {
|
|
|
replacement = new VariableDefinition(variable.VariableType);
|
|
|
variableMapping.Add(variable, replacement);
|
|
|
}
|
|
|
if (instruction.IsLdlocWithIndex(out _)) {
|
|
|
instruction.OpCode = OpCodes.Ldloc;
|
|
|
} else {
|
|
|
instruction.OpCode = OpCodes.Stloc;
|
|
|
}
|
|
|
instruction.Operand = replacement;
|
|
|
} else if (instruction.Operand is VariableDefinition variable) {
|
|
|
if (!variableMapping.TryGetValue(variable, out var replacement)) {
|
|
|
replacement = new VariableDefinition(variable.VariableType);
|
|
|
variableMapping.Add(variable, replacement);
|
|
|
}
|
|
|
instruction.Operand = replacement;
|
|
|
} else if (instruction.Operand is Instruction target) {
|
|
|
var targetIndex = Array.IndexOf(source, target);
|
|
|
Log.Assert(targetIndex >= 0);
|
|
|
instruction.Operand = result[targetIndex];
|
|
|
} else if (instruction.Operand is Instruction[] targets) {
|
|
|
instruction.Operand = targets.Select(x => {
|
|
|
var targetIndex = Array.IndexOf(source, x);
|
|
|
Log.Assert(targetIndex >= 0);
|
|
|
return result[targetIndex];
|
|
|
});
|
|
|
} else if (instruction.Operand is ParameterDefinition) {
|
|
|
throw new NotSupportedException();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return (result, variableMapping.Values.ToArray());
|
|
|
}
|
|
|
|
|
|
private (MethodDefinition method, Instruction returnInstruction) CreateOverride(ILWeaverAssembly asm, TypeDefinition type, string name) {
|
|
|
var rootMethod = asm.NetworkedBehaviour.GetMethod(name);
|
|
|
|
|
|
var result = type.Methods.FirstOrDefault(x => x.Name == name);
|
|
|
if (result != null) {
|
|
|
// need to find the placeholder method
|
|
|
var placeholderMethodName = asm.NetworkedBehaviour.GetMethod(nameof(NetworkBehaviour.InvokeWeavedCode)).FullName;
|
|
|
var placeholders = result.Body.Instructions
|
|
|
.Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.FullName == placeholderMethodName)
|
|
|
.ToList();
|
|
|
|
|
|
if (placeholders.Count != 1) {
|
|
|
throw new ILWeaverException($"When overriding {name} in a type with [Networked] properties, make sure to call {placeholderMethodName} exactly once somewhere.");
|
|
|
}
|
|
|
|
|
|
var baseCalls = result.Body.Instructions
|
|
|
.Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.Name == name)
|
|
|
.ToList();
|
|
|
|
|
|
if (baseCalls.Count == 0 && !type.BaseType.IsSame<NetworkBehaviour>()) {
|
|
|
throw new ILWeaverException($"When overriding {name} in a type derived from a type derived from {nameof(NetworkBehaviour)}, invoke the base method.");
|
|
|
}
|
|
|
|
|
|
foreach (var baseCall in baseCalls) {
|
|
|
var baseMethod = (MethodReference)baseCall.Operand;
|
|
|
if (!baseMethod.DeclaringType.IsSame(type.BaseType)) {
|
|
|
Log.Debug($"Changing base declaring type from {baseMethod.DeclaringType} to {type.BaseType}");
|
|
|
baseCall.Operand = GetBaseMethodReference(asm, result, type.BaseType);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var placeholder = placeholders[0];
|
|
|
var il = result.Body.GetILProcessor();
|
|
|
|
|
|
var jumpTarget = Nop();
|
|
|
var returnTarget = Nop();
|
|
|
|
|
|
// this is where to jump after weaved code's done
|
|
|
il.InsertAfter(placeholder, returnTarget);
|
|
|
il.InsertAfter(placeholder, Br(jumpTarget));
|
|
|
|
|
|
il.Append(jumpTarget);
|
|
|
return (result, Br(returnTarget));
|
|
|
}
|
|
|
|
|
|
result = new MethodDefinition(name, MethodAttributes.Public, rootMethod.ReturnType) {
|
|
|
IsVirtual = true,
|
|
|
IsHideBySig = true,
|
|
|
IsReuseSlot = true
|
|
|
};
|
|
|
|
|
|
foreach (var parameter in rootMethod.Parameters) {
|
|
|
result.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
|
|
}
|
|
|
|
|
|
result.AddTo(type);
|
|
|
|
|
|
// call base method if it exists, unless it is a NB
|
|
|
var baseType = type.BaseType;
|
|
|
while (!baseType.IsSame<NetworkBehaviour>()) {
|
|
|
|
|
|
var baseMethod = GetBaseMethodReference(asm, result, baseType);
|
|
|
var bodyIL = result.Body.GetILProcessor();
|
|
|
|
|
|
bodyIL.Append(Instruction.Create(OpCodes.Ldarg_0));
|
|
|
|
|
|
foreach (var parameter in result.Parameters) {
|
|
|
bodyIL.Append(Ldarg(parameter));
|
|
|
}
|
|
|
|
|
|
bodyIL.Append(Call(baseMethod));
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return (result, Ret());
|
|
|
}
|
|
|
|
|
|
private Lazy<T> MakeLazy<T>(Func<T> func) {
|
|
|
return new Lazy<T>(func);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverAssembly.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Reflection;
|
|
|
|
|
|
using Mono.Cecil;
|
|
|
|
|
|
public class ILWeaverImportedType {
|
|
|
public Type ClrType;
|
|
|
public ILWeaverAssembly Assembly;
|
|
|
public List<TypeDefinition> BaseDefinitions;
|
|
|
public TypeReference Reference;
|
|
|
|
|
|
Dictionary<string, FieldReference> _fields = new Dictionary<string, FieldReference>();
|
|
|
Dictionary<(string, int?), MethodReference> _methods = new Dictionary<(string, int?), MethodReference>();
|
|
|
Dictionary<string, MethodReference> _propertiesGet = new Dictionary<string, MethodReference>();
|
|
|
Dictionary<string, MethodReference> _propertiesSet = new Dictionary<string, MethodReference>();
|
|
|
|
|
|
public static implicit operator TypeReference(ILWeaverImportedType type) => type.Reference;
|
|
|
|
|
|
public ILWeaverImportedType(ILWeaverAssembly asm, Type type) {
|
|
|
ClrType = type;
|
|
|
Assembly = asm;
|
|
|
|
|
|
Type baseType = type;
|
|
|
BaseDefinitions = new List<TypeDefinition>();
|
|
|
// Store the type, and each of its base types - so we can later find fields/properties/methods in the base class.
|
|
|
while (baseType != null) {
|
|
|
BaseDefinitions.Add(asm.CecilAssembly.MainModule.ImportReference(baseType).Resolve());
|
|
|
baseType = baseType.BaseType;
|
|
|
}
|
|
|
|
|
|
Reference = asm.CecilAssembly.MainModule.ImportReference(BaseDefinitions[0]);
|
|
|
}
|
|
|
|
|
|
public FieldReference GetFieldOrThrow(string name) {
|
|
|
bool found = _fields.TryGetValue(name, out var fieldRef);
|
|
|
if (found == false) {
|
|
|
for (int i = 0; i < BaseDefinitions.Count; ++i) {
|
|
|
FieldDefinition typeDef = BaseDefinitions[i].Fields.FirstOrDefault(x => x.Name == name);
|
|
|
if (typeDef != null) {
|
|
|
fieldRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef);
|
|
|
_fields.Add(name, fieldRef);
|
|
|
return fieldRef;
|
|
|
}
|
|
|
}
|
|
|
throw new Exception($"Field {name} not found on type {ClrType}");
|
|
|
}
|
|
|
|
|
|
return fieldRef;
|
|
|
}
|
|
|
|
|
|
public MethodReference GetGetterOrThrow(string name) {
|
|
|
bool found = _propertiesGet.TryGetValue(name, out var methRef);
|
|
|
if (found == false) {
|
|
|
for (int i = 0; i < BaseDefinitions.Count; ++i) {
|
|
|
PropertyDefinition typeDef = BaseDefinitions[i].Properties.FirstOrDefault(x => x.Name == name);
|
|
|
if (typeDef != null) {
|
|
|
methRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef.GetMethod);
|
|
|
_propertiesGet.Add(name, methRef);
|
|
|
return methRef;
|
|
|
}
|
|
|
}
|
|
|
throw new Exception($"Property {name} not found on type {ClrType}");
|
|
|
}
|
|
|
|
|
|
return methRef;
|
|
|
}
|
|
|
|
|
|
public MethodReference GetMethod(string name, int? argsCount = null, int? genericArgsCount = null) {
|
|
|
if (!TryGetMethod(name, out var methRef, argsCount, genericArgsCount)) {
|
|
|
throw new InvalidOperationException($"Not found: {name}");
|
|
|
}
|
|
|
return methRef;
|
|
|
}
|
|
|
|
|
|
public bool TryGetMethod(string name, out MethodReference method, int? argsCount = null, int? genericArgsCount = null) {
|
|
|
if (_methods.TryGetValue((name, argsCount), out method)) {
|
|
|
return method != null;
|
|
|
}
|
|
|
|
|
|
foreach (var t in BaseDefinitions) {
|
|
|
var typeDef = t.Methods.FirstOrDefault(
|
|
|
x => x.Name == name &&
|
|
|
(argsCount.HasValue == false || x.Parameters.Count == argsCount.Value) &&
|
|
|
(genericArgsCount == null || x.GenericParameters.Count == genericArgsCount.Value));
|
|
|
|
|
|
if (typeDef != null) {
|
|
|
method = Assembly.CecilAssembly.MainModule.ImportReference(typeDef);
|
|
|
_methods.Add((name, argsCount), method);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_methods.Add((name, argsCount), null);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
public GenericInstanceMethod GetGenericMethod(string name, int? argsCount = null, params TypeReference[] types) {
|
|
|
var method = GetMethod(name, argsCount);
|
|
|
var generic = new GenericInstanceMethod(method);
|
|
|
|
|
|
foreach (var t in types) {
|
|
|
generic.GenericArguments.Add(t);
|
|
|
}
|
|
|
|
|
|
return generic;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class ILWeaverAssembly {
|
|
|
public bool Modified;
|
|
|
public List<String> Errors = new List<string>();
|
|
|
|
|
|
public AssemblyDefinition CecilAssembly;
|
|
|
|
|
|
ILWeaverImportedType _networkRunner;
|
|
|
ILWeaverImportedType _readWriteUtils;
|
|
|
ILWeaverImportedType _nativeUtils;
|
|
|
ILWeaverImportedType _rpcInfo;
|
|
|
ILWeaverImportedType _rpcInvokeInfo;
|
|
|
ILWeaverImportedType _rpcHeader;
|
|
|
ILWeaverImportedType _networkBehaviourUtils;
|
|
|
|
|
|
ILWeaverImportedType _simulation;
|
|
|
ILWeaverImportedType _networkedObject;
|
|
|
ILWeaverImportedType _networkedObjectId;
|
|
|
ILWeaverImportedType _networkedBehaviour;
|
|
|
ILWeaverImportedType _networkedBehaviourT;
|
|
|
ILWeaverImportedType _networkedBehaviourId;
|
|
|
ILWeaverImportedType _simulationBehaviour;
|
|
|
ILWeaverImportedType _simulationMessage;
|
|
|
|
|
|
ILWeaverImportedType _object;
|
|
|
ILWeaverImportedType _valueType;
|
|
|
ILWeaverImportedType _void;
|
|
|
ILWeaverImportedType _int;
|
|
|
ILWeaverImportedType _float;
|
|
|
|
|
|
Dictionary<Type, TypeReference> _types = new Dictionary<Type, TypeReference>();
|
|
|
|
|
|
private ILWeaverImportedType MakeImportedType<T>(ref ILWeaverImportedType field) {
|
|
|
return MakeImportedType(ref field, typeof(T));
|
|
|
}
|
|
|
|
|
|
private ILWeaverImportedType MakeImportedType(ref ILWeaverImportedType field, Type type) {
|
|
|
if (field == null) {
|
|
|
field = new ILWeaverImportedType(this, type);
|
|
|
}
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
public ILWeaverImportedType WordSizedPrimitive => MakeImportedType<int>(ref _int);
|
|
|
|
|
|
public ILWeaverImportedType Void => MakeImportedType(ref _void, typeof(void));
|
|
|
|
|
|
public ILWeaverImportedType Object => MakeImportedType<object>(ref _object);
|
|
|
|
|
|
public ILWeaverImportedType ValueType => MakeImportedType<ValueType>(ref _valueType);
|
|
|
|
|
|
public ILWeaverImportedType Float => MakeImportedType<float>(ref _float);
|
|
|
|
|
|
public ILWeaverImportedType NetworkedObject => MakeImportedType<NetworkObject>(ref _networkedObject);
|
|
|
|
|
|
public ILWeaverImportedType Simulation => MakeImportedType<Simulation>(ref _simulation);
|
|
|
|
|
|
public ILWeaverImportedType SimulationMessage => MakeImportedType<SimulationMessage>(ref _simulationMessage);
|
|
|
|
|
|
public ILWeaverImportedType NetworkedBehaviour => MakeImportedType<NetworkBehaviour>(ref _networkedBehaviour);
|
|
|
|
|
|
public ILWeaverImportedType SimulationBehaviour => MakeImportedType<SimulationBehaviour>(ref _simulationBehaviour);
|
|
|
|
|
|
public ILWeaverImportedType NetworkId => MakeImportedType<NetworkId>(ref _networkedObjectId);
|
|
|
|
|
|
public ILWeaverImportedType NetworkedBehaviourId => MakeImportedType<NetworkBehaviourId>(ref _networkedBehaviourId);
|
|
|
|
|
|
public ILWeaverImportedType NetworkRunner => MakeImportedType<NetworkRunner>(ref _networkRunner);
|
|
|
|
|
|
public ILWeaverImportedType ReadWriteUtils => MakeImportedType(ref _readWriteUtils, typeof(ReadWriteUtilsForWeaver));
|
|
|
|
|
|
public ILWeaverImportedType Native => MakeImportedType(ref _nativeUtils, typeof(Native));
|
|
|
|
|
|
public ILWeaverImportedType NetworkBehaviourUtils => MakeImportedType(ref _networkBehaviourUtils, typeof(NetworkBehaviourUtils));
|
|
|
|
|
|
public ILWeaverImportedType RpcHeader => MakeImportedType<RpcHeader>(ref _rpcHeader);
|
|
|
|
|
|
public ILWeaverImportedType RpcInfo => MakeImportedType<RpcInfo>(ref _rpcInfo);
|
|
|
|
|
|
public ILWeaverImportedType RpcInvokeInfo => MakeImportedType<RpcInvokeInfo>(ref _rpcInvokeInfo);
|
|
|
|
|
|
public TypeReference Import(TypeReference type) {
|
|
|
return CecilAssembly.MainModule.ImportReference(type);
|
|
|
}
|
|
|
|
|
|
public MethodReference Import(MethodInfo method) {
|
|
|
return CecilAssembly.MainModule.ImportReference(method);
|
|
|
}
|
|
|
|
|
|
public MethodReference Import(MethodReference method) {
|
|
|
return CecilAssembly.MainModule.ImportReference(method);
|
|
|
}
|
|
|
|
|
|
public MethodReference Import(ConstructorInfo method) {
|
|
|
return CecilAssembly.MainModule.ImportReference(method);
|
|
|
}
|
|
|
|
|
|
public TypeReference Import(Type type) {
|
|
|
if (_types.TryGetValue(type, out var reference) == false) {
|
|
|
_types.Add(type, reference = CecilAssembly.MainModule.ImportReference(type));
|
|
|
}
|
|
|
|
|
|
return reference;
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
CecilAssembly?.Dispose();
|
|
|
|
|
|
Modified = false;
|
|
|
Errors.Clear();
|
|
|
CecilAssembly = null;
|
|
|
}
|
|
|
|
|
|
public TypeReference Import<T>() {
|
|
|
return Import(typeof(T));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.ILPostProcessor.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System.Collections.Generic;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using Mono.Cecil;
|
|
|
|
|
|
internal class ILWeaverAssemblyResolver : IAssemblyResolver {
|
|
|
private List<string> _lookInDirectories;
|
|
|
private Dictionary<string, string> _assemblyNameToPath;
|
|
|
private Dictionary<string, AssemblyDefinition> _resolvedAssemblies = new Dictionary<string, AssemblyDefinition>();
|
|
|
private string _compiledAssemblyName;
|
|
|
private ILWeaverLog _log;
|
|
|
|
|
|
public AssemblyDefinition WeavedAssembly;
|
|
|
|
|
|
public ILWeaverAssemblyResolver(ILWeaverLog log, string compiledAssemblyName, string[] references, string[] weavedAssemblies) {
|
|
|
_log = log;
|
|
|
_compiledAssemblyName = compiledAssemblyName;
|
|
|
_assemblyNameToPath = new Dictionary<string, string>();
|
|
|
|
|
|
foreach (var referencePath in references) {
|
|
|
var assemblyName = Path.GetFileNameWithoutExtension(referencePath);
|
|
|
if (_assemblyNameToPath.TryGetValue(assemblyName, out var existingPath)) {
|
|
|
_log.Warn($"Assembly {assemblyName} (full path: {referencePath}) already referenced by {compiledAssemblyName} at {existingPath}");
|
|
|
} else {
|
|
|
_log.Debug($"Adding {assemblyName}->{referencePath}");
|
|
|
_assemblyNameToPath.Add(assemblyName, referencePath);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_lookInDirectories = references.Select(x => Path.GetDirectoryName(x)).Distinct().ToList();
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
}
|
|
|
|
|
|
public AssemblyDefinition Resolve(AssemblyNameReference name) {
|
|
|
return Resolve(name, new ReaderParameters(ReadingMode.Deferred));
|
|
|
}
|
|
|
|
|
|
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) {
|
|
|
{
|
|
|
if (name.Name == _compiledAssemblyName)
|
|
|
return WeavedAssembly;
|
|
|
|
|
|
var path = GetAssemblyPath(name);
|
|
|
if (string.IsNullOrEmpty(path))
|
|
|
return null;
|
|
|
|
|
|
if (_resolvedAssemblies.TryGetValue(path, out var result))
|
|
|
return result;
|
|
|
|
|
|
parameters.AssemblyResolver = this;
|
|
|
|
|
|
var pdb = path + ".pdb";
|
|
|
if (File.Exists(pdb)) {
|
|
|
parameters.SymbolStream = CreateAssemblyStream(pdb);
|
|
|
}
|
|
|
|
|
|
var assemblyDefinition = AssemblyDefinition.ReadAssembly(CreateAssemblyStream(path), parameters);
|
|
|
_resolvedAssemblies.Add(path, assemblyDefinition);
|
|
|
return assemblyDefinition;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private string GetAssemblyPath(AssemblyNameReference name) {
|
|
|
if (_assemblyNameToPath.TryGetValue(name.Name, out var path)) {
|
|
|
return path;
|
|
|
}
|
|
|
|
|
|
// fallback for second-order references
|
|
|
foreach (var parentDir in _lookInDirectories) {
|
|
|
var fullPath = Path.Combine(parentDir, name.Name + ".dll");
|
|
|
if (File.Exists(fullPath)) {
|
|
|
_assemblyNameToPath.Add(name.Name, fullPath);
|
|
|
return fullPath;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
private static MemoryStream CreateAssemblyStream(string fileName) {
|
|
|
var bytes = File.ReadAllBytes(fileName);
|
|
|
return new MemoryStream(bytes);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.UnityEditor.cs
|
|
|
|
|
|
#if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using Mono.Cecil;
|
|
|
using CompilerAssembly = UnityEditor.Compilation.Assembly;
|
|
|
|
|
|
class ILWeaverAssemblyResolver : BaseAssemblyResolver {
|
|
|
Dictionary<string, ILWeaverAssembly> _assemblies;
|
|
|
Dictionary<string, ILWeaverAssembly> _assembliesByPath;
|
|
|
|
|
|
public IEnumerable<ILWeaverAssembly> Assemblies => _assemblies.Values;
|
|
|
|
|
|
public ILWeaverAssemblyResolver() {
|
|
|
_assemblies = new Dictionary<string, ILWeaverAssembly>(StringComparer.Ordinal);
|
|
|
_assembliesByPath = new Dictionary<string, ILWeaverAssembly>();
|
|
|
}
|
|
|
|
|
|
public sealed override AssemblyDefinition Resolve(AssemblyNameReference name) {
|
|
|
if (_assemblies.TryGetValue(name.FullName, out var asm) == false) {
|
|
|
asm = new ILWeaverAssembly();
|
|
|
asm.CecilAssembly = base.Resolve(name, ReaderParameters(false, false));
|
|
|
|
|
|
_assemblies.Add(name.FullName, asm);
|
|
|
}
|
|
|
|
|
|
return asm.CecilAssembly;
|
|
|
}
|
|
|
|
|
|
public void Clear() {
|
|
|
_assemblies.Clear();
|
|
|
}
|
|
|
|
|
|
public bool Contains(CompilerAssembly compilerAssembly) {
|
|
|
return _assembliesByPath.ContainsKey(compilerAssembly.outputPath);
|
|
|
}
|
|
|
|
|
|
public ILWeaverAssembly AddAssembly(string path, bool readWrite = true, bool readSymbols = true) {
|
|
|
return AddAssembly(AssemblyDefinition.ReadAssembly(path, ReaderParameters(readWrite, readSymbols)), null);
|
|
|
}
|
|
|
|
|
|
public ILWeaverAssembly AddAssembly(CompilerAssembly compilerAssembly, bool readWrite = true, bool readSymbols = true) {
|
|
|
return AddAssembly(AssemblyDefinition.ReadAssembly(compilerAssembly.outputPath, ReaderParameters(readWrite, readSymbols)), compilerAssembly);
|
|
|
}
|
|
|
|
|
|
public ILWeaverAssembly AddAssembly(AssemblyDefinition assembly, CompilerAssembly compilerAssembly) {
|
|
|
if (assembly == null) {
|
|
|
throw new ArgumentNullException(nameof(assembly));
|
|
|
}
|
|
|
|
|
|
if (_assemblies.TryGetValue(assembly.Name.FullName, out var asm) == false) {
|
|
|
asm = new ILWeaverAssembly();
|
|
|
asm.CecilAssembly = assembly;
|
|
|
|
|
|
_assemblies.Add(assembly.Name.FullName, asm);
|
|
|
|
|
|
if (compilerAssembly != null) {
|
|
|
Assert.Always(_assembliesByPath.ContainsKey(compilerAssembly.outputPath) == false);
|
|
|
_assembliesByPath.Add(compilerAssembly.outputPath, asm);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return asm;
|
|
|
}
|
|
|
|
|
|
protected override void Dispose(bool disposing) {
|
|
|
foreach (var asm in _assemblies.Values) {
|
|
|
asm.CecilAssembly?.Dispose();
|
|
|
}
|
|
|
|
|
|
_assemblies.Clear();
|
|
|
|
|
|
base.Dispose(disposing);
|
|
|
}
|
|
|
|
|
|
ReaderParameters ReaderParameters(bool readWrite, bool readSymbols) {
|
|
|
ReaderParameters p;
|
|
|
p = new ReaderParameters(ReadingMode.Immediate);
|
|
|
p.ReadWrite = readWrite;
|
|
|
p.ReadSymbols = readSymbols;
|
|
|
p.AssemblyResolver = this;
|
|
|
return p;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.ILPostProcessor.cs
|
|
|
|
|
|
#if FUSION_WEAVER_ILPOSTPROCESSOR
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
|
|
|
|
|
#if FUSION_WEAVER
|
|
|
using System.Collections.Generic;
|
|
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
|
|
#if FUSION_HAS_MONO_CECIL
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using System.Reflection;
|
|
|
|
|
|
using System.Runtime.Serialization.Json;
|
|
|
using System.Xml.Linq;
|
|
|
|
|
|
class ILWeaverBindings : ILPostProcessor {
|
|
|
|
|
|
const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt";
|
|
|
const string MainAssemblyName = "Assembly-CSharp";
|
|
|
const string OverrideMethodName = nameof(ILWeaverSettings) + ".OverrideNetworkProjectConfigPath";
|
|
|
const string UserFileName = "Fusion.CodeGen.User.cs";
|
|
|
|
|
|
enum ConfigPathSource {
|
|
|
User,
|
|
|
PathFile,
|
|
|
Find
|
|
|
}
|
|
|
|
|
|
Lazy<(string, ConfigPathSource)> _configPath;
|
|
|
Lazy<XDocument> _config;
|
|
|
|
|
|
public ILWeaverBindings() {
|
|
|
_configPath = new Lazy<(string, ConfigPathSource)>(() => {
|
|
|
|
|
|
// try the user-provided path
|
|
|
var defaultPath = ILWeaverSettings.DefaultConfigPath;
|
|
|
if (!string.IsNullOrEmpty(defaultPath) && File.Exists(defaultPath)) {
|
|
|
return (defaultPath, ConfigPathSource.User);
|
|
|
}
|
|
|
|
|
|
// try the editor-provided path
|
|
|
if (File.Exists(ConfigPathCachePath)) {
|
|
|
var path = File.ReadAllText(ConfigPathCachePath);
|
|
|
if (File.Exists(path)) {
|
|
|
return (path, ConfigPathSource.PathFile);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// last resort: grep
|
|
|
string[] paths = Directory.GetFiles("Assets", "*.fusion", SearchOption.AllDirectories);
|
|
|
if (paths.Length == 0) {
|
|
|
throw new InvalidOperationException($"No {nameof(NetworkProjectConfig)} file found (.fusion extension) in {Path.GetFullPath("Assets")}");
|
|
|
}
|
|
|
if (paths.Length > 1) {
|
|
|
throw new InvalidOperationException($"Multiple config files found: {string.Join(", ", paths)}");
|
|
|
}
|
|
|
return (paths[0], ConfigPathSource.Find);
|
|
|
});
|
|
|
|
|
|
_config = new Lazy<XDocument>(() => {
|
|
|
string configPath = _configPath.Value.Item1;
|
|
|
using (var stream = File.OpenRead(configPath)) {
|
|
|
var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, new System.Xml.XmlDictionaryReaderQuotas());
|
|
|
return XDocument.Load(jsonReader);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
public override ILPostProcessor GetInstance() {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) {
|
|
|
// try to load the config
|
|
|
ILWeaverSettings settings;
|
|
|
try {
|
|
|
settings = ReadSettings(_config.Value);
|
|
|
} catch (Exception ex) {
|
|
|
|
|
|
string message;
|
|
|
DiagnosticType messageType = DiagnosticType.Error;
|
|
|
try {
|
|
|
var (configPath, source) = _configPath.Value;
|
|
|
message = $"Failed to load config from \"{configPath}\". ";
|
|
|
if (source == ConfigPathSource.User) {
|
|
|
message += $"This is path comes from the default location ({ILWeaverSettings.DefaultConfigPath}). " +
|
|
|
$"Implement {OverrideMethodName} in {UserFileName} to override. ";
|
|
|
} else if (source == ConfigPathSource.PathFile) {
|
|
|
message += $"The path comes from {ConfigPathCachePath} file that is generated by editor scripts each time compilation starts. " +
|
|
|
$"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist. " +
|
|
|
$"Implement {OverrideMethodName} in {UserFileName} to override. ";
|
|
|
} else if (source == ConfigPathSource.Find) {
|
|
|
message += $"The path comes searching Assets directory for *.fusion files. " +
|
|
|
$"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist and " +
|
|
|
$"{ConfigPathCachePath} was not properly generated by editor scripts. ";
|
|
|
}
|
|
|
message += $"Details: {ex}";
|
|
|
} catch (Exception configPathEx) {
|
|
|
message =
|
|
|
$"Failed to locate a valid config. " +
|
|
|
$"The weaver first checks the default location - {ILWeaverSettings.DefaultConfigPath} (implement {OverrideMethodName} in {UserFileName} to override). " +
|
|
|
$"If the file does not exist, editor-generated {ConfigPathCachePath} is checked (there might be a scenario where weaving is triggered without the editor scripts being compiled yet). " +
|
|
|
$"If that fails, the weaver searches for *.fusion files in Assets directory. ";
|
|
|
message += $"Details: {configPathEx}";
|
|
|
messageType = DiagnosticType.Warning;
|
|
|
}
|
|
|
return new ILPostProcessResult(null, new List<DiagnosticMessage>() {
|
|
|
{
|
|
|
new DiagnosticMessage() {
|
|
|
MessageData = message,
|
|
|
DiagnosticType = messageType,
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
InMemoryAssembly resultAssembly = null;
|
|
|
var logger = new ILWeaverLoggerDiagnosticMessages();
|
|
|
var log = new ILWeaverLog(logger);
|
|
|
|
|
|
using (log.Scope($"Process {compiledAssembly.Name}")) {
|
|
|
|
|
|
{
|
|
|
var (configPath, configSource) = _configPath.Value;
|
|
|
log.Debug($"Using config at {configPath} (from {configSource})");
|
|
|
if (compiledAssembly.Name == MainAssemblyName && configSource == ConfigPathSource.Find) {
|
|
|
log.Warn(
|
|
|
$"The weaver had to use Directory.GetFiles to locate the config {configPath}. " +
|
|
|
$"This is potentially slow and might happen if you moved config to a non-standard location and editor scripts did not get the chance to run yet. " +
|
|
|
$"If you see this message while running in a batch mode, implement {OverrideMethodName} in {UserFileName}.");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
ILWeaverAssembly asm;
|
|
|
ILWeaver weaver;
|
|
|
|
|
|
using (log.Scope("Resolving")) {
|
|
|
asm = CreateWeaverAssembly(settings, log, compiledAssembly);
|
|
|
}
|
|
|
|
|
|
using (log.Scope("Init")) {
|
|
|
weaver = new ILWeaver(settings, log);
|
|
|
}
|
|
|
|
|
|
weaver.Weave(asm);
|
|
|
|
|
|
if (asm.Modified) {
|
|
|
var pe = new MemoryStream();
|
|
|
var pdb = new MemoryStream();
|
|
|
var writerParameters = new WriterParameters {
|
|
|
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
|
|
SymbolStream = pdb,
|
|
|
WriteSymbols = true
|
|
|
};
|
|
|
|
|
|
using (log.Scope("Writing")) {
|
|
|
asm.CecilAssembly.Write(pe, writerParameters);
|
|
|
resultAssembly = new InMemoryAssembly(pe.ToArray(), pdb.ToArray());
|
|
|
}
|
|
|
}
|
|
|
} catch (Exception ex) {
|
|
|
log.Error($"Exception thrown when weaving {compiledAssembly.Name}");
|
|
|
log.Exception(ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
logger.FixNewLinesInMessages();
|
|
|
return new ILPostProcessResult(resultAssembly, logger.Messages);
|
|
|
}
|
|
|
|
|
|
public override bool WillProcess(ICompiledAssembly compiledAssembly) {
|
|
|
|
|
|
string[] assembliesToWeave;
|
|
|
|
|
|
try {
|
|
|
assembliesToWeave = ReadSettings(_config.Value, full: false).AssembliesToWeave;
|
|
|
} catch {
|
|
|
// need to go to the next stage for some assembly, main is good enough
|
|
|
return compiledAssembly.Name == MainAssemblyName;
|
|
|
}
|
|
|
|
|
|
if (!ILWeaverSettings.IsAssemblyWeavable(assembliesToWeave, compiledAssembly.Name)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (!ILWeaverSettings.ContainsRequiredReferences(compiledAssembly.References)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
static ILWeaverAssembly CreateWeaverAssembly(ILWeaverSettings settings, ILWeaverLog log, ICompiledAssembly compiledAssembly) {
|
|
|
var resolver = new ILWeaverAssemblyResolver(log, compiledAssembly.Name, compiledAssembly.References, settings.AssembliesToWeave);
|
|
|
|
|
|
var readerParameters = new ReaderParameters {
|
|
|
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()),
|
|
|
SymbolReaderProvider = new PortablePdbReaderProvider(),
|
|
|
AssemblyResolver = resolver,
|
|
|
ReadingMode = ReadingMode.Immediate,
|
|
|
ReadWrite = true,
|
|
|
ReadSymbols = true,
|
|
|
ReflectionImporterProvider = new ReflectionImporterProvider(log)
|
|
|
};
|
|
|
|
|
|
var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray());
|
|
|
var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream, readerParameters);
|
|
|
|
|
|
resolver.WeavedAssembly = assemblyDefinition;
|
|
|
|
|
|
return new ILWeaverAssembly() {
|
|
|
CecilAssembly = assemblyDefinition,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
static ILWeaverSettings ReadSettings(XDocument config, bool full = true) {
|
|
|
|
|
|
void SetIfExists(ref bool field, string name) {
|
|
|
var b = (bool?)config.Root.Element(name);
|
|
|
if (b != null) {
|
|
|
field = b.Value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var result = new ILWeaverSettings();
|
|
|
|
|
|
if (full) {
|
|
|
SetIfExists(ref result.NullChecksForNetworkedProperties, nameof(NetworkProjectConfig.NullChecksForNetworkedProperties));
|
|
|
SetIfExists(ref result.UseSerializableDictionary, nameof(NetworkProjectConfig.UseSerializableDictionary));
|
|
|
SetIfExists(ref result.CheckRpcAttributeUsage, nameof(NetworkProjectConfig.CheckRpcAttributeUsage));
|
|
|
SetIfExists(ref result.CheckNetworkedPropertiesBeingEmpty, nameof(NetworkProjectConfig.CheckNetworkedPropertiesBeingEmpty));
|
|
|
}
|
|
|
|
|
|
result.AssembliesToWeave = config.Root.Element(nameof(NetworkProjectConfig.AssembliesToWeave))?
|
|
|
.Elements()
|
|
|
.Select(x => x.Value)
|
|
|
.ToArray() ?? Array.Empty<string>();
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
class ReflectionImporterProvider : IReflectionImporterProvider {
|
|
|
private ILWeaverLog _log;
|
|
|
|
|
|
public ReflectionImporterProvider(ILWeaverLog log) {
|
|
|
_log = log;
|
|
|
}
|
|
|
|
|
|
public IReflectionImporter GetReflectionImporter(ModuleDefinition module) {
|
|
|
return new ReflectionImporter(_log, module);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class ReflectionImporter : DefaultReflectionImporter {
|
|
|
private ILWeaverLog _log;
|
|
|
|
|
|
public ReflectionImporter(ILWeaverLog log, ModuleDefinition module) : base(module) {
|
|
|
_log = log;
|
|
|
}
|
|
|
|
|
|
public override AssemblyNameReference ImportReference(AssemblyName name) {
|
|
|
if (name.Name == "System.Private.CoreLib") {
|
|
|
// seems weaver is run with .net core, but we need to stick to .net framework
|
|
|
var candidates = module.AssemblyReferences
|
|
|
.Where(x => x.Name == "mscorlib" || x.Name == "netstandard")
|
|
|
.OrderBy(x => x.Name)
|
|
|
.ThenByDescending(x => x.Version)
|
|
|
.ToList();
|
|
|
|
|
|
// in Unity 2020.1 and .NET 4.x mode when building with IL2CPP apparently both mscrolib and netstandard can be loaded
|
|
|
if (candidates.Count > 0) {
|
|
|
return candidates[0];
|
|
|
}
|
|
|
|
|
|
throw new ILWeaverException("Could not locate mscrolib or netstandard assemblies");
|
|
|
}
|
|
|
|
|
|
return base.ImportReference(name);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
#else
|
|
|
class ILWeaverBindings : ILPostProcessor {
|
|
|
public override ILPostProcessor GetInstance() {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) {
|
|
|
return new ILPostProcessResult(null, new List<DiagnosticMessage>() {
|
|
|
new DiagnosticMessage() {
|
|
|
DiagnosticType = DiagnosticType.Warning,
|
|
|
MessageData = "Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed."
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
public override bool WillProcess(ICompiledAssembly compiledAssembly) {
|
|
|
return compiledAssembly.Name == "Assembly-CSharp";
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
#else
|
|
|
class ILWeaverBindings : ILPostProcessor {
|
|
|
public override ILPostProcessor GetInstance() {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) {
|
|
|
throw new NotImplementedException();
|
|
|
}
|
|
|
|
|
|
public override bool WillProcess(ICompiledAssembly compiledAssembly) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.UnityEditor.cs
|
|
|
|
|
|
#if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
#if FUSION_HAS_MONO_CECIL
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Diagnostics;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
using Mono.Cecil;
|
|
|
using UnityEditor;
|
|
|
using UnityEditor.Build;
|
|
|
using UnityEditor.Build.Reporting;
|
|
|
using UnityEditor.Compilation;
|
|
|
using CompilerAssembly = UnityEditor.Compilation.Assembly;
|
|
|
|
|
|
class ILWeaverBindings {
|
|
|
|
|
|
public static bool IsEditorAssemblyPath(string path) {
|
|
|
return path.Contains("-Editor") || path.Contains(".Editor");
|
|
|
}
|
|
|
|
|
|
[UnityEditor.InitializeOnLoadMethod]
|
|
|
public static void InitializeOnLoad() {
|
|
|
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
|
|
|
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|
|
}
|
|
|
|
|
|
static void OnPlayModeStateChanged(PlayModeStateChange state) {
|
|
|
var projectConfig = NetworkProjectConfig.Global;
|
|
|
|
|
|
// exit edit mode means play mode is about to start ...
|
|
|
if (state == PlayModeStateChange.ExitingEditMode) {
|
|
|
foreach (var assembly in CompilationPipeline.GetAssemblies()) {
|
|
|
var name = Path.GetFileNameWithoutExtension(assembly.outputPath);
|
|
|
if (ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) {
|
|
|
OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static void OnCompilationFinished(string path, CompilerMessage[] messages) {
|
|
|
#if FUSION_DEV
|
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
|
Log.Debug($"OnCompilationFinished({path})");
|
|
|
#endif
|
|
|
|
|
|
// never modify editor assemblies
|
|
|
if (IsEditorAssemblyPath(path)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
var projectConfig = NetworkProjectConfig.Global;
|
|
|
if (projectConfig != null) {
|
|
|
// name of assembly on disk
|
|
|
var name = Path.GetFileNameWithoutExtension(path);
|
|
|
if (!ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) {
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// errors means we should exit
|
|
|
if (messages.Any(x => x.type == CompilerMessageType.Error)) {
|
|
|
#if FUSION_DEV
|
|
|
Log.Error($"Can't execute ILWeaver on {path}, compilation errors exist.");
|
|
|
#endif
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// grab compiler pipe assembly
|
|
|
var asm = CompilationPipeline.GetAssemblies().First(x => x.outputPath == path);
|
|
|
|
|
|
// needs to reference phoenix runtime
|
|
|
if (ILWeaverSettings.ContainsRequiredReferences(asm.allReferences) == false) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// perform weaving
|
|
|
try {
|
|
|
var settings = new ILWeaverSettings() {
|
|
|
AccuracyDefaults = projectConfig.AccuracyDefaults,
|
|
|
CheckNetworkedPropertiesBeingEmpty = projectConfig.CheckNetworkedPropertiesBeingEmpty,
|
|
|
CheckRpcAttributeUsage = projectConfig.CheckRpcAttributeUsage,
|
|
|
NullChecksForNetworkedProperties = projectConfig.NullChecksForNetworkedProperties,
|
|
|
UseSerializableDictionary = projectConfig.UseSerializableDictionary,
|
|
|
AssembliesToWeave = projectConfig.AssembliesToWeave,
|
|
|
};
|
|
|
|
|
|
var weaver = new ILWeaver(settings, new ILWeaverLoggerUnityDebug());
|
|
|
Weave(weaver, asm);
|
|
|
} catch (Exception ex) {
|
|
|
UnityEngine.Debug.LogError(ex);
|
|
|
}
|
|
|
|
|
|
#if FUSION_DEV
|
|
|
UnityEngine.Debug.Log($"OnCompilationFinished took: {sw.Elapsed}");
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
|
|
|
static void Weave(ILWeaver weaver, Assembly compilerAssembly) {
|
|
|
using (weaver.Log.Scope("Processing")) {
|
|
|
|
|
|
using (var resolver = new ILWeaverAssemblyResolver()) {
|
|
|
// if we're already weaving this don't do anything
|
|
|
if (resolver.Contains(compilerAssembly)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// make sure we can load all dlls
|
|
|
foreach (string path in compilerAssembly.allReferences) {
|
|
|
resolver.AddSearchDirectory(Path.GetDirectoryName(path));
|
|
|
}
|
|
|
|
|
|
// make sure we have the runtime dll loaded
|
|
|
if (!ILWeaverSettings.ContainsRequiredReferences(compilerAssembly.allReferences)) {
|
|
|
throw new InvalidOperationException($"Weaving: Could not find required assembly references");
|
|
|
}
|
|
|
|
|
|
ILWeaverAssembly asm;
|
|
|
|
|
|
using (weaver.Log.Scope("Resolving")) {
|
|
|
asm = resolver.AddAssembly(compilerAssembly);
|
|
|
}
|
|
|
|
|
|
if (weaver.Weave(asm)) {
|
|
|
|
|
|
using (weaver.Log.Scope("Writing")) {
|
|
|
// write asm to disk
|
|
|
asm.CecilAssembly.Write(new WriterParameters {
|
|
|
WriteSymbols = true
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#else
|
|
|
class ILWeaverBindings {
|
|
|
[UnityEditor.InitializeOnLoadMethod]
|
|
|
public static void InitializeOnLoad() {
|
|
|
UnityEngine.Debug.LogError("Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed.");
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverException.cs
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
public class ILWeaverException : Exception {
|
|
|
public ILWeaverException(string error) : base(error) {
|
|
|
}
|
|
|
|
|
|
public ILWeaverException(string error, Exception innerException) : base(error, innerException) {
|
|
|
}
|
|
|
|
|
|
|
|
|
[Conditional("UNITY_EDITOR")]
|
|
|
public static void DebugThrowIf(bool condition, string message) {
|
|
|
if (condition) {
|
|
|
throw new ILWeaverException(message);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverExtensions.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using BindingFlags = System.Reflection.BindingFlags;
|
|
|
|
|
|
public static class ILWeaverExtensions {
|
|
|
|
|
|
public static bool IsIntegral(this TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
case MetadataType.SByte:
|
|
|
case MetadataType.UInt16:
|
|
|
case MetadataType.Int16:
|
|
|
case MetadataType.UInt32:
|
|
|
case MetadataType.Int32:
|
|
|
case MetadataType.UInt64:
|
|
|
case MetadataType.Int64:
|
|
|
return true;
|
|
|
default:
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static int GetPrimitiveSize(this TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
case MetadataType.SByte:
|
|
|
return sizeof(byte);
|
|
|
case MetadataType.UInt16:
|
|
|
case MetadataType.Int16:
|
|
|
return sizeof(short);
|
|
|
case MetadataType.UInt32:
|
|
|
case MetadataType.Int32:
|
|
|
return sizeof(int);
|
|
|
case MetadataType.UInt64:
|
|
|
case MetadataType.Int64:
|
|
|
return sizeof(long);
|
|
|
case MetadataType.Single:
|
|
|
return sizeof(float);
|
|
|
case MetadataType.Double:
|
|
|
return sizeof(double);
|
|
|
default:
|
|
|
throw new ArgumentException($"Unknown primitive type: {type}", nameof(type));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Type GetPrimitiveType(this TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
return typeof(byte);
|
|
|
case MetadataType.SByte:
|
|
|
return typeof(sbyte);
|
|
|
case MetadataType.UInt16:
|
|
|
return typeof(ushort);
|
|
|
case MetadataType.Int16:
|
|
|
return typeof(short);
|
|
|
case MetadataType.UInt32:
|
|
|
return typeof(uint);
|
|
|
case MetadataType.Int32:
|
|
|
return typeof(int);
|
|
|
case MetadataType.UInt64:
|
|
|
return typeof(ulong);
|
|
|
case MetadataType.Int64:
|
|
|
return typeof(long);
|
|
|
case MetadataType.Single:
|
|
|
return typeof(float);
|
|
|
case MetadataType.Double:
|
|
|
return typeof(double);
|
|
|
case MetadataType.Boolean:
|
|
|
return typeof(bool);
|
|
|
case MetadataType.Char:
|
|
|
return typeof(char);
|
|
|
default:
|
|
|
throw new ArgumentException($"Unknown primitive type: {type}", nameof(type));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool IsString(this TypeReference type) {
|
|
|
return type.MetadataType == MetadataType.String;
|
|
|
}
|
|
|
|
|
|
public static bool IsFloat(this TypeReference type) {
|
|
|
return type.MetadataType == MetadataType.Single;
|
|
|
}
|
|
|
|
|
|
public static bool IsBool(this TypeReference type) {
|
|
|
return type.MetadataType == MetadataType.Boolean;
|
|
|
}
|
|
|
|
|
|
public static bool IsVector2(this TypeReference type) {
|
|
|
return type.FullName == "UnityEngine.Vector2";
|
|
|
}
|
|
|
|
|
|
public static bool IsVector3(this TypeReference type) {
|
|
|
return type.FullName == "UnityEngine.Vector3";
|
|
|
}
|
|
|
|
|
|
public static bool IsVector4(this TypeReference type) {
|
|
|
return type.FullName == "UnityEngine.Vector4";
|
|
|
}
|
|
|
|
|
|
public static bool IsQuaternion(this TypeReference type) {
|
|
|
return type.FullName == "UnityEngine.Quaternion";
|
|
|
}
|
|
|
|
|
|
public static bool IsVoid(this TypeReference type) {
|
|
|
return type.MetadataType == MetadataType.Void;
|
|
|
}
|
|
|
|
|
|
public static TypeReference GetElementTypeWithGenerics(this TypeReference type) {
|
|
|
if (type.IsPointer) {
|
|
|
return ((Mono.Cecil.PointerType)type).ElementType;
|
|
|
} else if (type.IsByReference) {
|
|
|
return ((Mono.Cecil.ByReferenceType)type).ElementType;
|
|
|
} else if (type.IsArray) {
|
|
|
return ((Mono.Cecil.ArrayType)type).ElementType;
|
|
|
} else {
|
|
|
return type.GetElementType();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool IsSubclassOf<T>(this TypeReference type) {
|
|
|
return !IsSame<T>(type) && Is<T>(type);
|
|
|
}
|
|
|
|
|
|
public static bool IsNetworkCollection(this TypeReference type) {
|
|
|
return type.IsNetworkArray(out _) || type.IsNetworkList(out _) || type.IsNetworkDictionary(out _, out _);
|
|
|
}
|
|
|
|
|
|
public static bool IsNetworkList(this TypeReference type, out TypeReference elementType) {
|
|
|
if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkLinkedList<>).FullName) {
|
|
|
elementType = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var git = (GenericInstanceType)type;
|
|
|
elementType = git.GenericArguments[0];
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static bool IsNetworkArray(this TypeReference type, out TypeReference elementType) {
|
|
|
if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkArray<>).FullName) {
|
|
|
elementType = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var git = (GenericInstanceType)type;
|
|
|
elementType = git.GenericArguments[0];
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public static bool IsNetworkDictionary(this TypeReference type, out TypeReference keyType, out TypeReference valueType) {
|
|
|
if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkDictionary<,>).FullName) {
|
|
|
keyType = default;
|
|
|
valueType = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var git = (GenericInstanceType)type;
|
|
|
keyType = git.GenericArguments[0];
|
|
|
valueType = git.GenericArguments[1];
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public static bool Is<T>(this TypeReference type) {
|
|
|
return Is(type, typeof(T));
|
|
|
}
|
|
|
|
|
|
public static bool Is(this TypeReference type, Type t) {
|
|
|
if (IsSame(type, t)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
var resolvedType = type.Resolve();
|
|
|
if (resolvedType == null) {
|
|
|
throw new InvalidOperationException($"Failed to resolve {type}");
|
|
|
}
|
|
|
|
|
|
if (t.IsInterface) {
|
|
|
|
|
|
foreach (var interf in resolvedType.Interfaces) {
|
|
|
if (interf.InterfaceType.IsSame(t)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
} else {
|
|
|
if (resolvedType.BaseType == null) {
|
|
|
return false;
|
|
|
}
|
|
|
return Is(resolvedType.BaseType, t);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool Is(this TypeReference type, TypeReference t) {
|
|
|
|
|
|
if (IsSame(type, t)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
var resolvedType = type.Resolve();
|
|
|
if (IsSame(resolvedType, t)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (t is GenericParameter genericParameter) {
|
|
|
foreach (var constraint in genericParameter.Constraints) {
|
|
|
#if FUSION_CECIL_1_11_OR_NEWER
|
|
|
if (!Is(type, constraint.ConstraintType)) {
|
|
|
#else
|
|
|
if (!Is(type, constraint)) {
|
|
|
#endif
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (t.Resolve().IsInterface == true) {
|
|
|
if (resolvedType == null) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
foreach (var interf in resolvedType.Interfaces) {
|
|
|
if (interf.InterfaceType.IsSame(t)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
} else {
|
|
|
if (resolvedType.BaseType == null) {
|
|
|
return false;
|
|
|
}
|
|
|
return Is(resolvedType.BaseType, t);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool IsSame<T>(this TypeReference type) {
|
|
|
return IsSame(type, typeof(T));
|
|
|
}
|
|
|
|
|
|
public static bool IsSame(this TypeReference type, Type t) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
if (type.IsByReference) {
|
|
|
type = type.GetElementTypeWithGenerics();
|
|
|
}
|
|
|
if (type.IsVoid() && t == typeof(void)) {
|
|
|
return true;
|
|
|
}
|
|
|
if (type.IsValueType != t.IsValueType) {
|
|
|
return false;
|
|
|
}
|
|
|
if (type.IsNested != t.IsNested) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (t.IsNested) {
|
|
|
if (type.Name != t.Name || !IsSame(type.DeclaringType, t.DeclaringType)) {
|
|
|
return false;
|
|
|
}
|
|
|
} else {
|
|
|
if (type.FullName != t.FullName) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public static bool IsSame(this TypeReference type, TypeOrTypeRef t) {
|
|
|
if (t.Type != null) {
|
|
|
return IsSame(type, t.Type);
|
|
|
} else if (t.TypeReference != null) {
|
|
|
return IsSame(type, t.TypeReference);
|
|
|
} else {
|
|
|
throw new InvalidOperationException();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool IsSame(this TypeReference type, TypeReference t) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
if (type.IsByReference) {
|
|
|
type = type.GetElementTypeWithGenerics();
|
|
|
}
|
|
|
if (type.IsValueType != t.IsValueType) {
|
|
|
return false;
|
|
|
}
|
|
|
if (type.FullName != t.FullName) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static IEnumerable<TypeDefinition> GetHierarchy(this TypeDefinition type, TypeReference stopAtBaseType = null) {
|
|
|
if (stopAtBaseType?.IsSame(type) == true) {
|
|
|
yield break;
|
|
|
}
|
|
|
|
|
|
for (; ; ) {
|
|
|
yield return type;
|
|
|
if (type.BaseType == null || stopAtBaseType?.IsSame(type.BaseType) == true) {
|
|
|
break;
|
|
|
}
|
|
|
type = type.BaseType.Resolve();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool Remove(this FieldDefinition field) {
|
|
|
return field.DeclaringType.Fields.Remove(field);
|
|
|
}
|
|
|
|
|
|
public static FieldDefinition GetFieldOrThrow(this TypeDefinition type, string fieldName) {
|
|
|
foreach (var field in type.Fields) {
|
|
|
if ( field.Name == fieldName ) {
|
|
|
return field;
|
|
|
}
|
|
|
}
|
|
|
throw new ArgumentOutOfRangeException(nameof(fieldName), $"Field {fieldName} not found in {type}");
|
|
|
}
|
|
|
|
|
|
public static MethodReference GetGenericInstanceMethodOrThrow(this GenericInstanceType type, string name) {
|
|
|
var methodRef = type.Resolve().GetMethodOrThrow(name);
|
|
|
|
|
|
var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType) {
|
|
|
HasThis = methodRef.HasThis,
|
|
|
ExplicitThis = methodRef.ExplicitThis,
|
|
|
DeclaringType = type,
|
|
|
CallingConvention = methodRef.CallingConvention,
|
|
|
};
|
|
|
|
|
|
foreach (var parameter in methodRef.Parameters) {
|
|
|
newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType));
|
|
|
}
|
|
|
|
|
|
foreach (var genericParameter in methodRef.GenericParameters) {
|
|
|
newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef));
|
|
|
}
|
|
|
|
|
|
return newMethodRef;
|
|
|
}
|
|
|
|
|
|
public static MethodReference GetCallable(this MethodReference methodRef, GenericInstanceType declaringType = null) {
|
|
|
|
|
|
if (declaringType == null) {
|
|
|
if (!methodRef.DeclaringType.HasGenericParameters) {
|
|
|
return methodRef;
|
|
|
}
|
|
|
|
|
|
declaringType = new GenericInstanceType(methodRef.DeclaringType);
|
|
|
foreach (var parameter in methodRef.DeclaringType.GenericParameters) {
|
|
|
declaringType.GenericArguments.Add(parameter);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType, declaringType) {
|
|
|
HasThis = methodRef.HasThis,
|
|
|
ExplicitThis = methodRef.ExplicitThis,
|
|
|
CallingConvention = methodRef.CallingConvention
|
|
|
};
|
|
|
|
|
|
foreach (var parameter in methodRef.Parameters) {
|
|
|
newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType));
|
|
|
}
|
|
|
|
|
|
foreach (var genericParameter in methodRef.GenericParameters) {
|
|
|
newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef));
|
|
|
}
|
|
|
|
|
|
return newMethodRef;
|
|
|
}
|
|
|
|
|
|
public static FieldReference GetLoadable(this FieldDefinition field) {
|
|
|
|
|
|
if (!field.DeclaringType.HasGenericParameters) {
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
var declaringType = new GenericInstanceType(field.DeclaringType);
|
|
|
foreach (var parameter in field.DeclaringType.GenericParameters) {
|
|
|
declaringType.GenericArguments.Add(parameter);
|
|
|
}
|
|
|
return new FieldReference(field.Name, field.FieldType, declaringType);
|
|
|
}
|
|
|
|
|
|
public static void AddInterface<T>(this TypeDefinition type, ILWeaverAssembly asm) {
|
|
|
type.Interfaces.Add(new InterfaceImplementation(asm.Import(typeof(T))));
|
|
|
}
|
|
|
|
|
|
public static bool RemoveAttribute<T>(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute {
|
|
|
for (int i = 0; i < member.CustomAttributes.Count; ++i) {
|
|
|
var attr = member.CustomAttributes[i];
|
|
|
if ( attr.AttributeType.Is<T>() ) {
|
|
|
member.CustomAttributes.RemoveAt(i);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T>(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
attr = new CustomAttribute(typeof(T).GetConstructor(asm));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T>(this IMemberDefinition member, ModuleDefinition module = null) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
attr = new CustomAttribute(typeof(T).GetConstructor(module ?? member.DeclaringType.Module));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T, A0>(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
attr = new CustomAttribute(typeof(T).GetConstructor(asm, 1));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A0>(), arg0));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T, A0, A1>(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
attr = new CustomAttribute(typeof(T).GetConstructor(asm, 2));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A0>(), arg0));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A1>(), arg1));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T, A0, A1, A2>(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
attr = new CustomAttribute(typeof(T).GetConstructor(asm, 3));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A0>(), arg0));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A1>(), arg1));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A2>(), arg2));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
public static CustomAttribute AddAttribute<T, A0, A1, A2, A3>(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2, A3 arg3) where T : Attribute {
|
|
|
CustomAttribute attr;
|
|
|
|
|
|
// TODO: this is inconsistent with other AddAttribute, but needed for DrawIfAttribute to work
|
|
|
var constructor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(A0), typeof(A1), typeof(A2), typeof(A3) }, null);
|
|
|
attr = new CustomAttribute(asm.CecilAssembly.MainModule.ImportReference(constructor));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A0>(), arg0));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A1>(), arg1));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A2>(), arg2));
|
|
|
attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType<A3>(), arg3));
|
|
|
member.CustomAttributes.Add(attr);
|
|
|
return attr;
|
|
|
}
|
|
|
|
|
|
private static TypeReference ImportAttributeType<T>(this ILWeaverAssembly asm) {
|
|
|
if (typeof(T) == typeof(TypeReference)) {
|
|
|
return asm.Import<Type>();
|
|
|
} else {
|
|
|
return asm.Import<T>();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static MethodReference GetConstructor(this Type type, ILWeaverAssembly asm, int argCount = 0) {
|
|
|
foreach (var ctor in type.GetConstructors()) {
|
|
|
if (ctor.GetParameters().Length == argCount) {
|
|
|
return asm.CecilAssembly.MainModule.ImportReference(ctor);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}");
|
|
|
}
|
|
|
|
|
|
public static MethodReference GetConstructor(this Type type, ModuleDefinition module, int argCount = 0) {
|
|
|
foreach (var ctor in type.GetConstructors()) {
|
|
|
if (ctor.GetParameters().Length == argCount) {
|
|
|
return module.ImportReference(ctor);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}");
|
|
|
}
|
|
|
|
|
|
public static void AddTo(this MethodDefinition method, TypeDefinition type) {
|
|
|
type.Methods.Add(method);
|
|
|
method.AddAttribute<WeaverGeneratedAttribute>();
|
|
|
}
|
|
|
|
|
|
public static void AddTo(this PropertyDefinition property, TypeDefinition type) {
|
|
|
type.Properties.Add(property);
|
|
|
property.AddAttribute<WeaverGeneratedAttribute>();
|
|
|
}
|
|
|
|
|
|
public static FieldDefinition AddTo(this FieldDefinition field, TypeDefinition type) {
|
|
|
type.Fields.Add(field);
|
|
|
field.AddAttribute<WeaverGeneratedAttribute>();
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
public static void InsertTo(this FieldDefinition field, TypeDefinition type, int index) {
|
|
|
type.Fields.Insert(index, field);
|
|
|
field.AddAttribute<WeaverGeneratedAttribute>();
|
|
|
}
|
|
|
|
|
|
public static void AddTo(this TypeDefinition type, AssemblyDefinition assembly) {
|
|
|
assembly.MainModule.Types.Add(type);
|
|
|
type.AddAttribute<WeaverGeneratedAttribute>(assembly.MainModule);
|
|
|
}
|
|
|
|
|
|
public static void AddTo(this TypeDefinition type, TypeDefinition parentType) {
|
|
|
parentType.NestedTypes.Add(type);
|
|
|
type.AddAttribute<WeaverGeneratedAttribute>();
|
|
|
}
|
|
|
|
|
|
public static Instruction AppendReturn(this ILProcessor il, Instruction instruction) {
|
|
|
il.Append(instruction);
|
|
|
return instruction;
|
|
|
}
|
|
|
|
|
|
public static void Clear(this ILProcessor il) {
|
|
|
var instructions = il.Body.Instructions;
|
|
|
foreach (var instruction in instructions.Reverse()) {
|
|
|
il.Remove(instruction);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static void AppendMacro<T>(this ILProcessor il, in T macro) where T : struct, ILProcessorMacro {
|
|
|
macro.Emit(il);
|
|
|
}
|
|
|
|
|
|
public static bool GetSingleOrDefaultMethodWithAttribute<T>(this TypeDefinition type, out CustomAttribute attribute, out MethodDefinition method) where T : Attribute {
|
|
|
|
|
|
MethodDefinition resultMethod = null;
|
|
|
CustomAttribute resultAttribute = null;
|
|
|
|
|
|
foreach (var m in type.Methods) {
|
|
|
if (m.TryGetAttribute<T>(out var attr)) {
|
|
|
if (resultMethod != null) {
|
|
|
throw new ILWeaverException($"Only one method with attribute {typeof(T)} allowed per class: {type}");
|
|
|
} else {
|
|
|
resultMethod = m;
|
|
|
resultAttribute = attr;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
method = resultMethod;
|
|
|
attribute = resultAttribute;
|
|
|
return method != null;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static PropertyDefinition ThrowIfStatic(this PropertyDefinition property) {
|
|
|
if (property.GetMethod?.IsStatic == true ||
|
|
|
property.SetMethod?.IsStatic == true) {
|
|
|
throw new ILWeaverException($"Property is static: {property.FullName}");
|
|
|
}
|
|
|
return property;
|
|
|
}
|
|
|
|
|
|
public static PropertyDefinition ThrowIfNoGetter(this PropertyDefinition property) {
|
|
|
if (property.GetMethod == null) {
|
|
|
throw new ILWeaverException($"Property does not have a getter: {property.FullName}");
|
|
|
}
|
|
|
return property;
|
|
|
}
|
|
|
|
|
|
public static PropertyDefinition ThrowIfNoSetter(this PropertyDefinition property) {
|
|
|
if (property.GetMethod == null) {
|
|
|
throw new ILWeaverException($"Property does not have a getter: {property.FullName}");
|
|
|
}
|
|
|
return property;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static MethodDefinition ThrowIfStatic(this MethodDefinition method) {
|
|
|
if (method.IsStatic) {
|
|
|
throw new ILWeaverException($"Method is static: {method.FullName}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfNotStatic(this MethodDefinition method) {
|
|
|
if (!method.IsStatic) {
|
|
|
throw new ILWeaverException($"Method is not static: {method}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfNotPublic(this MethodDefinition method) {
|
|
|
if (!method.IsPublic) {
|
|
|
throw new ILWeaverException($"Method is not public: {method}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfReturnType(this MethodDefinition method, TypeOrTypeRef type) {
|
|
|
if (!method.ReturnType.IsSame(type)) {
|
|
|
|
|
|
throw new ILWeaverException($"Method has an invalid return type (expected {type}): {method}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfParameterCount(this MethodDefinition method, int count) {
|
|
|
if (method.Parameters.Count != count) {
|
|
|
throw new ILWeaverException($"Method has invalid parameter count (expected {count}): {method}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfParameterCountLessThan(this MethodDefinition method, int count) {
|
|
|
if (method.Parameters.Count < count) {
|
|
|
throw new ILWeaverException($"Method has invalid parameter count (expected at leaset {count}): {method}");
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition ThrowIfParameter(this MethodDefinition method, int index, TypeOrTypeRef type = null, bool isByReference = false, bool ignore = false) {
|
|
|
if (ignore) {
|
|
|
return method;
|
|
|
}
|
|
|
var p = method.Parameters[index];
|
|
|
if (type != null && !p.ParameterType.IsSame(type)) {
|
|
|
throw new ILWeaverException($"Parameter {p} ({index}) has an invalid type (expected {type}): {method}");
|
|
|
}
|
|
|
if (p.ParameterType.IsByReference != isByReference) {
|
|
|
if (p.IsOut) {
|
|
|
throw new ILWeaverException($"Parameter {p} ({index}) is a ref parameter: {method}");
|
|
|
} else {
|
|
|
throw new ILWeaverException($"Parameter {p} ({index}) is not a ref parameter: {method}");
|
|
|
}
|
|
|
}
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static bool IsBaseConstructorCall(this Instruction instruction, TypeDefinition type) {
|
|
|
if (instruction.OpCode == OpCodes.Call) {
|
|
|
var m = ((MethodReference)instruction.Operand).Resolve();
|
|
|
if (m.IsConstructor && m.DeclaringType.IsSame(type.BaseType)) {
|
|
|
// base constructor init
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool IsLdloca(this Instruction instruction, out VariableDefinition variable, out bool isShort) {
|
|
|
if (instruction.OpCode == OpCodes.Ldloca) {
|
|
|
variable = (VariableDefinition)instruction.Operand;
|
|
|
isShort = false;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Ldloca_S) {
|
|
|
variable = (VariableDefinition)instruction.Operand;
|
|
|
isShort = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
variable = default;
|
|
|
isShort = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool IsLdlocWithIndex(this Instruction instruction, out int index) {
|
|
|
if (instruction.OpCode == OpCodes.Ldloc_0) {
|
|
|
index = 0;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Ldloc_1) {
|
|
|
index = 1;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Ldloc_2) {
|
|
|
index = 2;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Ldloc_3) {
|
|
|
index = 3;
|
|
|
return true;
|
|
|
}
|
|
|
index = -1;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool IsStlocWithIndex(this Instruction instruction, out int index) {
|
|
|
if (instruction.OpCode == OpCodes.Stloc_0) {
|
|
|
index = 0;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Stloc_1) {
|
|
|
index = 1;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Stloc_2) {
|
|
|
index = 2;
|
|
|
return true;
|
|
|
}
|
|
|
if (instruction.OpCode == OpCodes.Stloc_3) {
|
|
|
index = 3;
|
|
|
return true;
|
|
|
}
|
|
|
index = -1;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class TypeOrTypeRef {
|
|
|
|
|
|
public Type Type { get; }
|
|
|
public TypeReference TypeReference { get; }
|
|
|
|
|
|
|
|
|
public TypeOrTypeRef(Type type, bool isOut = false) {
|
|
|
Type = type;
|
|
|
}
|
|
|
|
|
|
public TypeOrTypeRef(TypeReference type, bool isOut = false) {
|
|
|
TypeReference = type;
|
|
|
}
|
|
|
|
|
|
public static implicit operator TypeOrTypeRef(Type type) {
|
|
|
return new TypeOrTypeRef(type);
|
|
|
}
|
|
|
|
|
|
public static implicit operator TypeOrTypeRef(TypeReference type) {
|
|
|
return new TypeOrTypeRef(type);
|
|
|
}
|
|
|
|
|
|
public override string ToString() {
|
|
|
if (Type != null) {
|
|
|
return Type.FullName;
|
|
|
} else if (TypeReference != null) {
|
|
|
return TypeReference.ToString();
|
|
|
} else {
|
|
|
return "AnyType";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public interface ILProcessorMacro {
|
|
|
void Emit(ILProcessor il);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverLog.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
using System.Diagnostics;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Mono.Cecil;
|
|
|
|
|
|
public interface ILWeaverLogger {
|
|
|
void Log(LogType logType, string message, string filePath, int lineNumber);
|
|
|
void Log(Exception ex);
|
|
|
}
|
|
|
|
|
|
|
|
|
public sealed class ILWeaverLog {
|
|
|
|
|
|
private ILWeaverLogger _logger;
|
|
|
|
|
|
public ILWeaverLogger Logger => _logger;
|
|
|
|
|
|
public ILWeaverLog(ILWeaverLogger logger) {
|
|
|
if (logger == null) {
|
|
|
throw new ArgumentNullException(nameof(logger));
|
|
|
}
|
|
|
_logger = logger;
|
|
|
}
|
|
|
|
|
|
public void AssertMessage(bool condition, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
if (!condition) {
|
|
|
_logger.Log(LogType.Error, $"Assert failed: {message}", filePath, lineNumber);
|
|
|
throw new AssertException($"{message}{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Assert(bool condition, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
if (!condition) {
|
|
|
_logger.Log(LogType.Error, $"Assert failed", filePath, lineNumber);
|
|
|
throw new AssertException($"Assert failed{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
[Conditional("FUSION_WEAVER_DEBUG")]
|
|
|
public void Debug(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
_logger.Log(LogType.Debug, message, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Warn(MethodDefinition method, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
TryOverrideLocation(method, ref filePath, ref lineNumber);
|
|
|
_logger.Log(LogType.Warn, message, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public void Warn(PropertyDefinition property, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
TryOverrideLocation(property.GetMethod, ref filePath, ref lineNumber);
|
|
|
_logger.Log(LogType.Warn, message, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public void Warn(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
_logger.Log(LogType.Warn, message, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public void Error(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
_logger.Log(LogType.Error, message, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public void Exception(Exception ex) {
|
|
|
_logger.Log(ex);
|
|
|
}
|
|
|
|
|
|
#if !FUSION_WEAVER_DEBUG
|
|
|
public struct LogScope : IDisposable {
|
|
|
public void Dispose() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public LogScope Scope(string name) {
|
|
|
return default;
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly) {
|
|
|
return default;
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeBehaviour(TypeDefinition type) {
|
|
|
return default;
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeInput(TypeDefinition type) {
|
|
|
return default;
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeStruct(TypeDefinition type) {
|
|
|
return default;
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
public LogScope Scope(string name, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
return new LogScope(this, name, filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
return new LogScope(this, $"Assembly: {cecilAssembly.FullName}", filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeBehaviour(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
return new LogScope(this, $"Behaviour: {type.FullName}", filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeInput(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
return new LogScope(this, $"Input: {type.FullName}", filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
public LogScope ScopeStruct(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) {
|
|
|
return new LogScope(this, $"Struct: {type.FullName}", filePath, lineNumber);
|
|
|
}
|
|
|
|
|
|
#pragma warning disable CS0282 // There is no defined ordering between fields in multiple declarations of partial struct
|
|
|
public partial struct LogScope : IDisposable {
|
|
|
#pragma warning restore CS0282 // There is no defined ordering between fields in multiple declarations of partial struct
|
|
|
public string Message;
|
|
|
|
|
|
public TimeSpan Elapsed => _stopwatch.Elapsed;
|
|
|
|
|
|
private ILWeaverLog _log;
|
|
|
private Stopwatch _stopwatch;
|
|
|
|
|
|
public int LineNumber;
|
|
|
public string FilePath;
|
|
|
|
|
|
public LogScope(ILWeaverLog log, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) : this() {
|
|
|
_log = log;
|
|
|
_stopwatch = Stopwatch.StartNew();
|
|
|
Message = message;
|
|
|
LineNumber = lineNumber;
|
|
|
FilePath = filePath;
|
|
|
_log.Debug($"{Message} start", FilePath, LineNumber);
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
_stopwatch.Stop();
|
|
|
_log.Debug($"{Message} end {Elapsed}", FilePath, LineNumber);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
private static bool TryOverrideLocation(MethodDefinition method, ref string filePath, ref int lineNumber) {
|
|
|
var debugInformation = method?.DebugInformation;
|
|
|
if (debugInformation?.HasSequencePoints == true) {
|
|
|
var sequencePoint = debugInformation.SequencePoints[0];
|
|
|
filePath = sequencePoint.Document.Url;
|
|
|
lineNumber = sequencePoint.StartLine;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerDiagnosticMessages.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
|
|
|
|
|
class ILWeaverLoggerDiagnosticMessages : ILWeaverLogger {
|
|
|
|
|
|
public List<DiagnosticMessage> Messages { get; } = new List<DiagnosticMessage>();
|
|
|
|
|
|
public void Log(LogType logType, string message, string filePath, int lineNumber) {
|
|
|
|
|
|
DiagnosticType diagnosticType;
|
|
|
|
|
|
if (logType == LogType.Debug) {
|
|
|
// there are no debug diagnostic messages, make pretend warnings
|
|
|
message = $"DEBUG: {message}";
|
|
|
diagnosticType = DiagnosticType.Warning;
|
|
|
} else if (logType == LogType.Info) {
|
|
|
message = $"INFO: {message}";
|
|
|
diagnosticType = DiagnosticType.Warning;
|
|
|
} else if (logType == LogType.Trace) {
|
|
|
message = $"TRACE: {message}";
|
|
|
diagnosticType = DiagnosticType.Warning;
|
|
|
} else if (logType == LogType.Warn) {
|
|
|
diagnosticType = DiagnosticType.Warning;
|
|
|
} else {
|
|
|
diagnosticType = DiagnosticType.Error;
|
|
|
}
|
|
|
|
|
|
// newlines in messagedata will need to be escaped, but let's not slow things down now
|
|
|
Messages.Add(new DiagnosticMessage() {
|
|
|
File = filePath,
|
|
|
Line = lineNumber,
|
|
|
DiagnosticType = diagnosticType,
|
|
|
MessageData = message
|
|
|
});
|
|
|
}
|
|
|
|
|
|
public void Log(Exception ex) {
|
|
|
var lines = ex.ToString().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
foreach (var line in lines) {
|
|
|
Log(LogType.Error, line, null, 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void FixNewLinesInMessages() {
|
|
|
// fix the messages
|
|
|
foreach (var msg in Messages) {
|
|
|
msg.MessageData = msg.MessageData.Replace('\r', ';').Replace('\n', ';');
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerUnityDebug.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
|
|
|
public class ILWeaverLoggerUnityDebug : ILWeaverLogger {
|
|
|
|
|
|
public void Log(LogType logType, string message, string filePath, int lineNumber) {
|
|
|
switch (logType) {
|
|
|
case LogType.Debug:
|
|
|
case LogType.Trace:
|
|
|
case LogType.Info:
|
|
|
UnityEngine.Debug.Log(message);
|
|
|
break;
|
|
|
case LogType.Warn:
|
|
|
UnityEngine.Debug.LogWarning(message);
|
|
|
break;
|
|
|
case LogType.Error:
|
|
|
UnityEngine.Debug.LogError(message);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Log(Exception ex) {
|
|
|
UnityEngine.Debug.unityLogger.LogException(ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverMethodContext.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using static Fusion.CodeGen.ILWeaverOpCodes;
|
|
|
|
|
|
class MethodContext : IDisposable {
|
|
|
|
|
|
public Action<ILProcessor, TypeReference, ICustomAttributeProvider> LoadElementReaderWriterImpl;
|
|
|
private Action<ILProcessor> _addressGetter;
|
|
|
private Dictionary<(string, string), VariableDefinition> _fields = new Dictionary<(string, string), VariableDefinition>();
|
|
|
private bool _runnerIsLdarg0 = false;
|
|
|
protected Action<ILProcessor> _valueGetter;
|
|
|
protected Action<ILProcessor> _valueAddrGetter;
|
|
|
private TargetVariableAddrInfo _targetVariable;
|
|
|
|
|
|
public MethodContext(ILWeaverAssembly assembly, MethodDefinition method, bool staticRunnerAccessor = false,
|
|
|
Action<ILProcessor> addressGetter = null,
|
|
|
Action<ILProcessor> valueGetter = null,
|
|
|
Action<ILProcessor> valueAddrGetter = null) {
|
|
|
if (assembly == null) {
|
|
|
throw new ArgumentNullException(nameof(assembly));
|
|
|
}
|
|
|
if (method == null) {
|
|
|
throw new ArgumentNullException(nameof(method));
|
|
|
}
|
|
|
|
|
|
this.Assembly = assembly;
|
|
|
this.Method = method;
|
|
|
this._runnerIsLdarg0 = staticRunnerAccessor;
|
|
|
this._addressGetter = addressGetter;
|
|
|
this._valueGetter = valueGetter;
|
|
|
this._valueAddrGetter = valueAddrGetter;
|
|
|
}
|
|
|
|
|
|
public ILWeaverAssembly Assembly { get; private set; }
|
|
|
public virtual bool HasOffset => false;
|
|
|
public virtual bool IsWriteCompact => false;
|
|
|
public MethodDefinition Method { get; private set; }
|
|
|
|
|
|
public VariableDefinition AddVariable(TypeReference variableType) {
|
|
|
var variable = new VariableDefinition(variableType);
|
|
|
Method.Body.Variables.Add(variable);
|
|
|
return variable;
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
}
|
|
|
|
|
|
public AddOffsetMacro AddOffset() => new AddOffsetMacro(this, null);
|
|
|
public AddOffsetMacro AddOffset(int value) => new AddOffsetMacro(this, Ldc_I4(value), isAligned: (value % Allocator.REPLICATE_WORD_SIZE) == 0);
|
|
|
|
|
|
public ILMacroStruct AlignToWordSize() => new[] {
|
|
|
Ldc_I4(Allocator.REPLICATE_WORD_SIZE - 1),
|
|
|
Add(),
|
|
|
Ldc_I4(~(Allocator.REPLICATE_WORD_SIZE - 1)),
|
|
|
And()
|
|
|
};
|
|
|
|
|
|
public ForLoopMacro For(Action<ILProcessor> start, Action<ILProcessor> stop, Action<ILProcessor, VariableDefinition> body) => new ForLoopMacro(this, body, start, stop);
|
|
|
|
|
|
public VariableDefinition GetOrCreateVariable(string id, TypeReference type, ILProcessor il = null) {
|
|
|
if (_fields.TryGetValue((id, type.FullName), out var val)) {
|
|
|
return val;
|
|
|
}
|
|
|
var result = CreateVariable(type, il);
|
|
|
_fields.Add((id, type.FullName), result);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public VariableDefinition CreateVariable(TypeReference type, ILProcessor il = null, Instruction before = null) {
|
|
|
var result = new VariableDefinition(type);
|
|
|
Method.Body.Variables.Add(result);
|
|
|
if (il != null) {
|
|
|
if (before == null) {
|
|
|
if (type.IsValueType) {
|
|
|
il.Append(Ldloca(result));
|
|
|
il.Append(Initobj(type));
|
|
|
} else {
|
|
|
il.Append(Ldnull());
|
|
|
il.Append(Stloc(result));
|
|
|
}
|
|
|
} else {
|
|
|
if (type.IsValueType) {
|
|
|
il.InsertBefore(before, Ldloca(result));
|
|
|
il.InsertBefore(before, Initobj(type));
|
|
|
} else {
|
|
|
il.InsertBefore(before, Ldnull());
|
|
|
il.InsertBefore(before, Stloc(result));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public virtual ILMacroStruct LoadAddress() => _addressGetter;
|
|
|
|
|
|
public virtual ILMacroStruct LoadElementReaderWriter(TypeReference type, ICustomAttributeProvider member) => new Action<ILProcessor>(il => LoadElementReaderWriterImpl(il, type, member));
|
|
|
|
|
|
public ILMacroStruct LoadFixedBufferAddress(FieldDefinition fixedBufferField) => new Action<ILProcessor>(il => {
|
|
|
|
|
|
var elementField = fixedBufferField.FieldType.Resolve().Fields[0];
|
|
|
|
|
|
int pointerLoc = il.Body.Variables.Count;
|
|
|
il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakePointerType()));
|
|
|
int pinnedRefLoc = il.Body.Variables.Count;
|
|
|
il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakeByReferenceType().MakePinnedType()));
|
|
|
|
|
|
il.Append(Ldflda(fixedBufferField));
|
|
|
il.Append(Ldflda(elementField));
|
|
|
il.Append(Stloc(il.Body, pinnedRefLoc));
|
|
|
il.Append(Ldloc(il.Body, pinnedRefLoc));
|
|
|
il.Append(Conv_U());
|
|
|
il.Append(Stloc(il.Body, pointerLoc));
|
|
|
il.Append(Ldloc(il.Body, pointerLoc));
|
|
|
});
|
|
|
|
|
|
public ILMacroStruct LoadRunner() {
|
|
|
return _runnerIsLdarg0 ?
|
|
|
new[] { Ldarg_0() } :
|
|
|
new[] { Ldarg_0(), Call(Assembly.SimulationBehaviour.GetGetterOrThrow(nameof(SimulationBehaviour.Runner))) };
|
|
|
}
|
|
|
|
|
|
public virtual ILMacroStruct LoadValue() => _valueGetter;
|
|
|
public virtual ILMacroStruct LoadValueAddr() => _valueAddrGetter;
|
|
|
public bool HasValueGetter => _valueGetter != null;
|
|
|
public bool HasValueAddrGetter => _valueAddrGetter != null;
|
|
|
|
|
|
public ValueGetterScope ValueGetter(Action<ILProcessor> valueGetter) => new ValueGetterScope(this, valueGetter);
|
|
|
|
|
|
public ValueGetterScope ValueGetter(Action<ILProcessor, Action<ILProcessor>> valueGetter) {
|
|
|
var current = _valueGetter;
|
|
|
return new ValueGetterScope(this, il => valueGetter(il, current));
|
|
|
}
|
|
|
|
|
|
public LoadVariableAddressMacro GetTargetVariableAddrOrTemp(TypeReference type, ILProcessor il, out VariableDefinition variable, Instruction before = null) {
|
|
|
if (_targetVariable.Variable == null || !_targetVariable.Type.IsSame(type)) {
|
|
|
variable = CreateVariable(type, il, before);
|
|
|
return new LoadVariableAddressMacro(variable, null, null);
|
|
|
}
|
|
|
|
|
|
TargetAddrUsed = true;
|
|
|
variable = null;
|
|
|
|
|
|
return new LoadVariableAddressMacro(_targetVariable.Variable, _targetVariable.IndexVariable, _targetVariable.Type);
|
|
|
}
|
|
|
|
|
|
|
|
|
public TargetVariableScope TargetVariableAddr(VariableDefinition variable) => new TargetVariableScope(this, new TargetVariableAddrInfo(variable));
|
|
|
public TargetVariableScope TargetVariableAddr(VariableDefinition arrayVariable, VariableDefinition indexVariable, TypeReference type) => new TargetVariableScope(this, new TargetVariableAddrInfo(arrayVariable, indexVariable, type));
|
|
|
|
|
|
|
|
|
public bool TargetAddrUsed { get; set; }
|
|
|
|
|
|
|
|
|
public ILMacroStruct VerifyRawNetworkUnwrap(TypeReference type, int maxByteCount) => new[] {
|
|
|
Ldc_I4(maxByteCount),
|
|
|
Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkUnwrap), genericArgsCount: 1)) {
|
|
|
GenericArguments = { type }
|
|
|
}),
|
|
|
};
|
|
|
|
|
|
public ILMacroStruct VerifyRawNetworkWrap(TypeReference type, int maxByteCount) => new[] {
|
|
|
Ldc_I4(maxByteCount),
|
|
|
Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkWrap), genericArgsCount: 1)) {
|
|
|
GenericArguments = { type }
|
|
|
}),
|
|
|
};
|
|
|
|
|
|
protected virtual void EmitAddOffsetAfter(ILProcessor il) {
|
|
|
}
|
|
|
|
|
|
protected virtual void EmitAddOffsetBefore(ILProcessor il) {
|
|
|
}
|
|
|
public readonly struct AddOffsetMacro : ILProcessorMacro {
|
|
|
public readonly MethodContext Context;
|
|
|
public readonly Instruction Instruction;
|
|
|
public readonly bool IsAligned;
|
|
|
|
|
|
|
|
|
public AddOffsetMacro(MethodContext context, Instruction instruction = null, bool isAligned = false) {
|
|
|
Context = context;
|
|
|
Instruction = instruction;
|
|
|
IsAligned = isAligned;
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
if (Context.HasOffset) {
|
|
|
if (Instruction == null) {
|
|
|
if (!IsAligned) {
|
|
|
il.AppendMacro(Context.AlignToWordSize());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Context.EmitAddOffsetBefore(il);
|
|
|
if (Instruction != null) {
|
|
|
il.Append(Instruction);
|
|
|
if (!IsAligned) {
|
|
|
il.AppendMacro(Context.AlignToWordSize());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Context.EmitAddOffsetAfter(il);
|
|
|
} else {
|
|
|
if (Instruction == null) {
|
|
|
// means variant with size already pushed has been used, pop it
|
|
|
il.Append(Pop());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public readonly struct TargetVariableAddrInfo {
|
|
|
public readonly VariableDefinition Variable;
|
|
|
public readonly VariableDefinition IndexVariable;
|
|
|
public readonly TypeReference Type;
|
|
|
|
|
|
public TargetVariableAddrInfo(VariableDefinition variable) {
|
|
|
Variable = variable;
|
|
|
IndexVariable = null;
|
|
|
Type = variable.VariableType;
|
|
|
}
|
|
|
|
|
|
public TargetVariableAddrInfo(VariableDefinition variable, VariableDefinition indexVariable, TypeReference elementType) {
|
|
|
Variable = variable;
|
|
|
IndexVariable = indexVariable;
|
|
|
Type = elementType;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public readonly struct ForLoopMacro : ILProcessorMacro {
|
|
|
public readonly MethodContext Context;
|
|
|
public readonly Action<ILProcessor, VariableDefinition> Generator;
|
|
|
public readonly Action<ILProcessor> Start;
|
|
|
public readonly Action<ILProcessor> Stop;
|
|
|
|
|
|
public ForLoopMacro(MethodContext context, Action<ILProcessor, VariableDefinition> generator, Action<ILProcessor> start, Action<ILProcessor> stop) {
|
|
|
Context = context;
|
|
|
Generator = generator;
|
|
|
Start = start;
|
|
|
Stop = stop;
|
|
|
}
|
|
|
|
|
|
public void Emit(ILProcessor il) {
|
|
|
var body = Context.Method.Body;
|
|
|
var varId = body.Variables.Count;
|
|
|
var indexVariable = new VariableDefinition(Context.Assembly.Import(typeof(int)));
|
|
|
body.Variables.Add(indexVariable);
|
|
|
|
|
|
Start(il);
|
|
|
il.Append(Stloc(body, varId));
|
|
|
|
|
|
var loopConditionStart = Ldloc(body, varId);
|
|
|
il.Append(Br_S(loopConditionStart));
|
|
|
{
|
|
|
var loopBodyBegin = il.AppendReturn(Nop());
|
|
|
Generator(il, indexVariable);
|
|
|
|
|
|
il.Append(Ldloc(body, varId));
|
|
|
il.Append(Ldc_I4(1));
|
|
|
il.Append(Add());
|
|
|
il.Append(Stloc(body, varId));
|
|
|
|
|
|
il.Append(loopConditionStart);
|
|
|
Stop(il);
|
|
|
il.Append(Blt_S(loopBodyBegin));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public struct ValueGetterScope : IDisposable {
|
|
|
MethodContext _context;
|
|
|
Action<ILProcessor> _oldValueGetter;
|
|
|
|
|
|
public ValueGetterScope(MethodContext context, Action<ILProcessor> valueGetter) {
|
|
|
_context = context;
|
|
|
_oldValueGetter = context._valueGetter;
|
|
|
context._valueGetter = valueGetter;
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
_context._valueGetter = _oldValueGetter;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public struct TargetVariableScope : IDisposable {
|
|
|
MethodContext _context;
|
|
|
TargetVariableAddrInfo _oldTargetVariable;
|
|
|
bool _wasUsed;
|
|
|
|
|
|
public TargetVariableScope(MethodContext context, TargetVariableAddrInfo variable) {
|
|
|
_context = context;
|
|
|
_oldTargetVariable = context._targetVariable;
|
|
|
_wasUsed = context.TargetAddrUsed;
|
|
|
context._targetVariable = variable;
|
|
|
context.TargetAddrUsed = false;
|
|
|
}
|
|
|
|
|
|
public void Dispose() {
|
|
|
_context._targetVariable = _oldTargetVariable;
|
|
|
_context.TargetAddrUsed = _wasUsed;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public struct LoadVariableAddressMacro : ILProcessorMacro {
|
|
|
VariableDefinition _variable;
|
|
|
VariableDefinition _index;
|
|
|
TypeReference _elemType;
|
|
|
|
|
|
public LoadVariableAddressMacro(VariableDefinition variable, VariableDefinition index, TypeReference elemType) {
|
|
|
_variable = variable;
|
|
|
_index = index;
|
|
|
_elemType = elemType;
|
|
|
}
|
|
|
|
|
|
void ILProcessorMacro.Emit(ILProcessor il) {
|
|
|
if (_index == null) {
|
|
|
il.Append(Ldloca(_variable));
|
|
|
} else {
|
|
|
il.Append(Ldloc(_variable));
|
|
|
il.Append(Ldloc(_index));
|
|
|
il.Append(Ldelema(_elemType));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class RpcMethodContext : MethodContext {
|
|
|
public VariableDefinition DataVariable;
|
|
|
public VariableDefinition OffsetVariable;
|
|
|
public VariableDefinition RpcInvokeInfoVariable;
|
|
|
|
|
|
public RpcMethodContext(ILWeaverAssembly asm, MethodDefinition definition, bool staticRunnerAccessor)
|
|
|
: base(asm, definition, staticRunnerAccessor) {
|
|
|
}
|
|
|
|
|
|
public override bool HasOffset => true;
|
|
|
public override bool IsWriteCompact => true;
|
|
|
|
|
|
public override ILMacroStruct LoadAddress() => new[] {
|
|
|
Ldloc(DataVariable),
|
|
|
Ldloc(OffsetVariable),
|
|
|
Add(),
|
|
|
};
|
|
|
|
|
|
public ILMacroStruct SetRpcInvokeInfoStatus(bool emitIf, RpcLocalInvokeResult reason) => RpcInvokeInfoVariable == null || !emitIf ? new Instruction[0] :
|
|
|
new[] {
|
|
|
Ldloca(RpcInvokeInfoVariable),
|
|
|
Ldc_I4((int)reason),
|
|
|
Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.LocalInvokeResult)))
|
|
|
};
|
|
|
|
|
|
public ILMacroStruct SetRpcInvokeInfoStatus(RpcSendCullResult reason) => RpcInvokeInfoVariable == null ? new Instruction[0] :
|
|
|
new[] {
|
|
|
Ldloca(RpcInvokeInfoVariable),
|
|
|
Ldc_I4((int)reason),
|
|
|
Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendCullResult)))
|
|
|
};
|
|
|
|
|
|
protected override void EmitAddOffsetAfter(ILProcessor il) {
|
|
|
il.Append(Add());
|
|
|
il.Append(Stloc(OffsetVariable));
|
|
|
}
|
|
|
|
|
|
protected override void EmitAddOffsetBefore(ILProcessor il) {
|
|
|
il.Append(Ldloc(OffsetVariable));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverOpCodes.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System.Reflection;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using MethodBody = Mono.Cecil.Cil.MethodBody;
|
|
|
|
|
|
static class ILWeaverOpCodes {
|
|
|
// utils
|
|
|
public static Instruction Nop() => Instruction.Create(OpCodes.Nop);
|
|
|
public static Instruction Ret() => Instruction.Create(OpCodes.Ret);
|
|
|
public static Instruction Dup() => Instruction.Create(OpCodes.Dup);
|
|
|
public static Instruction Pop() => Instruction.Create(OpCodes.Pop);
|
|
|
public static Instruction Ldnull() => Instruction.Create(OpCodes.Ldnull);
|
|
|
public static Instruction Throw() => Instruction.Create(OpCodes.Throw);
|
|
|
|
|
|
public static Instruction Cast(TypeReference type) => Instruction.Create(OpCodes.Castclass, type);
|
|
|
|
|
|
// breaks
|
|
|
public static Instruction Brfalse(Instruction target) => Instruction.Create(OpCodes.Brfalse, target);
|
|
|
public static Instruction Brtrue(Instruction target) => Instruction.Create(OpCodes.Brtrue, target);
|
|
|
public static Instruction Brfalse_S(Instruction target) => Instruction.Create(OpCodes.Brfalse_S, target);
|
|
|
public static Instruction Brtrue_S(Instruction target) => Instruction.Create(OpCodes.Brtrue_S, target);
|
|
|
public static Instruction Br_S(Instruction target) => Instruction.Create(OpCodes.Br_S, target);
|
|
|
public static Instruction Br(Instruction target) => Instruction.Create(OpCodes.Br, target);
|
|
|
public static Instruction Blt_S(Instruction target) => Instruction.Create(OpCodes.Blt_S, target);
|
|
|
public static Instruction Ble_S(Instruction target) => Instruction.Create(OpCodes.Ble_S, target);
|
|
|
public static Instruction Beq(Instruction target) => Instruction.Create(OpCodes.Beq, target);
|
|
|
public static Instruction Bne_Un_S(Instruction target) => Instruction.Create(OpCodes.Bne_Un_S, target);
|
|
|
public static Instruction Beq_S(Instruction target) => Instruction.Create(OpCodes.Beq_S, target);
|
|
|
|
|
|
// math
|
|
|
public static Instruction Add() => Instruction.Create(OpCodes.Add);
|
|
|
public static Instruction Sub() => Instruction.Create(OpCodes.Sub);
|
|
|
public static Instruction Mul() => Instruction.Create(OpCodes.Mul);
|
|
|
public static Instruction Div() => Instruction.Create(OpCodes.Div);
|
|
|
public static Instruction And() => Instruction.Create(OpCodes.And);
|
|
|
|
|
|
// obj
|
|
|
public static Instruction Ldobj(TypeReference type) => Instruction.Create(OpCodes.Ldobj, type);
|
|
|
public static Instruction Stobj(TypeReference type) => Instruction.Create(OpCodes.Stobj, type);
|
|
|
|
|
|
public static Instruction Newobj(MethodReference constructor) => Instruction.Create(OpCodes.Newobj, constructor);
|
|
|
public static Instruction Newarr(TypeReference type) => Instruction.Create(OpCodes.Newarr, type);
|
|
|
|
|
|
public static Instruction Initobj(TypeReference type) => Instruction.Create(OpCodes.Initobj, type);
|
|
|
|
|
|
public static Instruction Box(TypeReference type) => Instruction.Create(OpCodes.Box, type);
|
|
|
|
|
|
|
|
|
// fields
|
|
|
public static Instruction Ldflda(FieldReference field) => Instruction.Create(OpCodes.Ldflda, field);
|
|
|
|
|
|
public static Instruction Ldfld(FieldReference field) => Instruction.Create(OpCodes.Ldfld, field);
|
|
|
public static Instruction Stfld(FieldReference field) => Instruction.Create(OpCodes.Stfld, field);
|
|
|
|
|
|
public static Instruction Ldsfld(FieldReference field) => Instruction.Create(OpCodes.Ldsfld, field);
|
|
|
public static Instruction Stsfld(FieldReference field) => Instruction.Create(OpCodes.Stsfld, field);
|
|
|
|
|
|
// locals
|
|
|
|
|
|
public static Instruction Ldloc_or_const(VariableDefinition var, int val) => var != null ? Ldloc(var) : Ldc_I4(val);
|
|
|
|
|
|
public static Instruction Ldloc(VariableDefinition var, MethodDefinition method) => Ldloc(method.Body, method.Body.Variables.IndexOf(var));
|
|
|
|
|
|
public static Instruction Ldloc(VariableDefinition var) => Instruction.Create(OpCodes.Ldloc, var);
|
|
|
public static Instruction Ldloca(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca, var);
|
|
|
public static Instruction Ldloca_S(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca_S, var);
|
|
|
public static Instruction Stloc(VariableDefinition var) => Instruction.Create(OpCodes.Stloc, var);
|
|
|
|
|
|
public static Instruction Stloc_0() => Instruction.Create(OpCodes.Stloc_0);
|
|
|
public static Instruction Stloc_1() => Instruction.Create(OpCodes.Stloc_1);
|
|
|
public static Instruction Stloc_2() => Instruction.Create(OpCodes.Stloc_2);
|
|
|
public static Instruction Stloc_3() => Instruction.Create(OpCodes.Stloc_3);
|
|
|
|
|
|
public static Instruction Ldloc_0() => Instruction.Create(OpCodes.Ldloc_0);
|
|
|
public static Instruction Ldloc_1() => Instruction.Create(OpCodes.Ldloc_1);
|
|
|
public static Instruction Ldloc_2() => Instruction.Create(OpCodes.Ldloc_2);
|
|
|
public static Instruction Ldloc_3() => Instruction.Create(OpCodes.Ldloc_3);
|
|
|
|
|
|
public static Instruction Stloc(MethodBody body, int index) {
|
|
|
switch (index) {
|
|
|
case 0:
|
|
|
return Stloc_0();
|
|
|
case 1:
|
|
|
return Stloc_1();
|
|
|
case 2:
|
|
|
return Stloc_2();
|
|
|
case 3:
|
|
|
return Stloc_3();
|
|
|
default:
|
|
|
return Stloc(body.Variables[index]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Ldloc(MethodBody body, int index) {
|
|
|
switch (index) {
|
|
|
case 0:
|
|
|
return Ldloc_0();
|
|
|
case 1:
|
|
|
return Ldloc_1();
|
|
|
case 2:
|
|
|
return Ldloc_2();
|
|
|
case 3:
|
|
|
return Ldloc_3();
|
|
|
default:
|
|
|
return Ldloc(body.Variables[index]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
// ldarg
|
|
|
public static Instruction Ldarg(ParameterDefinition arg) => Instruction.Create(OpCodes.Ldarg, arg);
|
|
|
public static Instruction Ldarg_0() => Instruction.Create(OpCodes.Ldarg_0);
|
|
|
public static Instruction Ldarg_1() => Instruction.Create(OpCodes.Ldarg_1);
|
|
|
public static Instruction Ldarg_2() => Instruction.Create(OpCodes.Ldarg_2);
|
|
|
public static Instruction Ldarg_3() => Instruction.Create(OpCodes.Ldarg_3);
|
|
|
|
|
|
public static Instruction Ldarga_S(ParameterDefinition p) => Instruction.Create(OpCodes.Ldarga_S, p);
|
|
|
|
|
|
// starg
|
|
|
|
|
|
public static Instruction Starg_S(ParameterDefinition arg) => Instruction.Create(OpCodes.Starg_S, arg);
|
|
|
|
|
|
// array
|
|
|
public static Instruction Ldlen() => Instruction.Create(OpCodes.Ldlen);
|
|
|
|
|
|
public static Instruction Ldelem(TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
return Instruction.Create(OpCodes.Ldelem_U1);
|
|
|
case MetadataType.SByte:
|
|
|
return Instruction.Create(OpCodes.Ldelem_I1);
|
|
|
case MetadataType.UInt16:
|
|
|
return Instruction.Create(OpCodes.Ldelem_U2);
|
|
|
case MetadataType.Int16:
|
|
|
return Instruction.Create(OpCodes.Ldelem_I2);
|
|
|
case MetadataType.UInt32:
|
|
|
return Instruction.Create(OpCodes.Ldelem_U4);
|
|
|
case MetadataType.Int32:
|
|
|
return Instruction.Create(OpCodes.Ldelem_I4);
|
|
|
case MetadataType.UInt64:
|
|
|
return Instruction.Create(OpCodes.Ldelem_I8);
|
|
|
case MetadataType.Int64:
|
|
|
return Instruction.Create(OpCodes.Ldelem_I8);
|
|
|
case MetadataType.Single:
|
|
|
return Instruction.Create(OpCodes.Ldelem_R4);
|
|
|
case MetadataType.Double:
|
|
|
return Instruction.Create(OpCodes.Ldelem_R8);
|
|
|
|
|
|
default:
|
|
|
if (type.IsValueType) {
|
|
|
return Instruction.Create(OpCodes.Ldelem_Any, type);
|
|
|
} else {
|
|
|
return Instruction.Create(OpCodes.Ldelem_Ref);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Stelem(TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
case MetadataType.SByte:
|
|
|
return Instruction.Create(OpCodes.Stelem_I1);
|
|
|
case MetadataType.UInt16:
|
|
|
case MetadataType.Int16:
|
|
|
return Instruction.Create(OpCodes.Stelem_I2);
|
|
|
case MetadataType.UInt32:
|
|
|
case MetadataType.Int32:
|
|
|
return Instruction.Create(OpCodes.Stelem_I4);
|
|
|
case MetadataType.UInt64:
|
|
|
case MetadataType.Int64:
|
|
|
return Instruction.Create(OpCodes.Stelem_I8);
|
|
|
case MetadataType.Single:
|
|
|
return Instruction.Create(OpCodes.Stelem_R4);
|
|
|
case MetadataType.Double:
|
|
|
return Instruction.Create(OpCodes.Stelem_R8);
|
|
|
default:
|
|
|
if (type.IsValueType) {
|
|
|
return Instruction.Create(OpCodes.Stelem_Any, type);
|
|
|
} else {
|
|
|
return Instruction.Create(OpCodes.Stelem_Ref);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Ldelema(TypeReference arg) => Instruction.Create(OpCodes.Ldelema, arg);
|
|
|
|
|
|
// conversions
|
|
|
public static Instruction Conv_R4() => Instruction.Create(OpCodes.Conv_R4);
|
|
|
public static Instruction Conv_I4() => Instruction.Create(OpCodes.Conv_I4);
|
|
|
public static Instruction Conv_U() => Instruction.Create(OpCodes.Conv_U);
|
|
|
|
|
|
// functions
|
|
|
public static Instruction Call(MethodReference method) => Instruction.Create(OpCodes.Call, method);
|
|
|
public static Instruction Callvirt(MethodReference method) => Instruction.Create(OpCodes.Callvirt, method);
|
|
|
public static Instruction Ldftn(MethodReference method) => Instruction.Create(OpCodes.Ldftn, method);
|
|
|
|
|
|
// constants
|
|
|
|
|
|
public static Instruction Ldstr(string value) => Instruction.Create(OpCodes.Ldstr, value);
|
|
|
public static Instruction Ldc_R4(float value) => Instruction.Create(OpCodes.Ldc_R4, value);
|
|
|
public static Instruction Ldc_R8(float value) => Instruction.Create(OpCodes.Ldc_R8, value);
|
|
|
|
|
|
public static Instruction Ldc_I4(int value) {
|
|
|
switch (value) {
|
|
|
case 0: return Instruction.Create(OpCodes.Ldc_I4_0);
|
|
|
case 1: return Instruction.Create(OpCodes.Ldc_I4_1);
|
|
|
case 2: return Instruction.Create(OpCodes.Ldc_I4_2);
|
|
|
case 3: return Instruction.Create(OpCodes.Ldc_I4_3);
|
|
|
case 4: return Instruction.Create(OpCodes.Ldc_I4_4);
|
|
|
case 5: return Instruction.Create(OpCodes.Ldc_I4_5);
|
|
|
case 6: return Instruction.Create(OpCodes.Ldc_I4_6);
|
|
|
case 7: return Instruction.Create(OpCodes.Ldc_I4_7);
|
|
|
case 8: return Instruction.Create(OpCodes.Ldc_I4_8);
|
|
|
default:
|
|
|
return Instruction.Create(OpCodes.Ldc_I4, value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Stind_I4() => Instruction.Create(OpCodes.Stind_I4);
|
|
|
public static Instruction Ldind_I4() => Instruction.Create(OpCodes.Ldind_I4);
|
|
|
|
|
|
public static Instruction Stind_R4() => Instruction.Create(OpCodes.Stind_R4);
|
|
|
public static Instruction Ldind_R4() => Instruction.Create(OpCodes.Ldind_R4);
|
|
|
|
|
|
|
|
|
|
|
|
public static Instruction Stind_or_Stobj(TypeReference type) {
|
|
|
if (type.IsPrimitive) {
|
|
|
return Stind(type);
|
|
|
} else {
|
|
|
return Stobj(type);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Ldind_or_Ldobj(TypeReference type) {
|
|
|
if (type.IsPrimitive) {
|
|
|
return Ldind(type);
|
|
|
} else {
|
|
|
return Ldobj(type);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Stind(TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
case MetadataType.SByte:
|
|
|
return Instruction.Create(OpCodes.Stind_I1);
|
|
|
case MetadataType.UInt16:
|
|
|
case MetadataType.Int16:
|
|
|
return Instruction.Create(OpCodes.Stind_I2);
|
|
|
case MetadataType.UInt32:
|
|
|
case MetadataType.Int32:
|
|
|
return Instruction.Create(OpCodes.Stind_I4);
|
|
|
case MetadataType.UInt64:
|
|
|
case MetadataType.Int64:
|
|
|
return Instruction.Create(OpCodes.Stind_I8);
|
|
|
case MetadataType.Single:
|
|
|
return Instruction.Create(OpCodes.Stind_R4);
|
|
|
case MetadataType.Double:
|
|
|
return Instruction.Create(OpCodes.Stind_R8);
|
|
|
default:
|
|
|
throw new ILWeaverException($"Unknown primitive type {type.FullName}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Instruction Ldind(TypeReference type) {
|
|
|
switch (type.MetadataType) {
|
|
|
case MetadataType.Byte:
|
|
|
return Instruction.Create(OpCodes.Ldind_U1);
|
|
|
case MetadataType.SByte:
|
|
|
return Instruction.Create(OpCodes.Ldind_I1);
|
|
|
case MetadataType.UInt16:
|
|
|
return Instruction.Create(OpCodes.Ldind_U2);
|
|
|
case MetadataType.Int16:
|
|
|
return Instruction.Create(OpCodes.Ldind_I2);
|
|
|
case MetadataType.UInt32:
|
|
|
return Instruction.Create(OpCodes.Ldind_U4);
|
|
|
case MetadataType.Int32:
|
|
|
return Instruction.Create(OpCodes.Ldind_I4);
|
|
|
case MetadataType.UInt64:
|
|
|
return Instruction.Create(OpCodes.Ldind_I8);
|
|
|
case MetadataType.Int64:
|
|
|
return Instruction.Create(OpCodes.Ldind_I8);
|
|
|
case MetadataType.Single:
|
|
|
return Instruction.Create(OpCodes.Ldind_R4);
|
|
|
case MetadataType.Double:
|
|
|
return Instruction.Create(OpCodes.Ldind_R8);
|
|
|
default:
|
|
|
throw new ILWeaverException($"Unknown primitive type {type.FullName}");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/ILWeaverSettings.cs
|
|
|
|
|
|
#if FUSION_WEAVER
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
|
|
|
public partial class ILWeaverSettings {
|
|
|
|
|
|
public static string DefaultConfigPath {
|
|
|
get {
|
|
|
string result = "Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion";
|
|
|
OverrideNetworkProjectConfigPath(ref result);
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static partial void OverrideNetworkProjectConfigPath(ref string path);
|
|
|
|
|
|
public static bool IsAssemblyWeavable(string[] assembliesToWeave, string assemblyName) {
|
|
|
return Array.FindIndex(assembliesToWeave, x => assemblyName.Equals(x, StringComparison.OrdinalIgnoreCase)) >= 0;
|
|
|
}
|
|
|
|
|
|
public static bool ContainsRequiredReferences(string[] references) {
|
|
|
return Array.FindIndex(references, x => x.Contains("Fusion.Runtime")) >= 0;
|
|
|
}
|
|
|
|
|
|
public bool NullChecksForNetworkedProperties;
|
|
|
public bool UseSerializableDictionary;
|
|
|
public bool CheckRpcAttributeUsage;
|
|
|
public bool CheckNetworkedPropertiesBeingEmpty;
|
|
|
public string[] AssembliesToWeave;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/InstructionEqualityComparer.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System.Collections.Generic;
|
|
|
using Mono.Cecil.Cil;
|
|
|
|
|
|
internal class InstructionEqualityComparer : IEqualityComparer<Instruction> {
|
|
|
public bool Equals(Instruction x, Instruction y) {
|
|
|
if (x.OpCode != y.OpCode) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (x.Operand != y.Operand) {
|
|
|
if (x.Operand?.GetType() != y?.Operand.GetType()) {
|
|
|
return false;
|
|
|
}
|
|
|
// there needs to be a better way to do this
|
|
|
if (x.Operand.ToString() != y.Operand.ToString()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public int GetHashCode(Instruction obj) {
|
|
|
return obj.GetHashCode();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/MemberReferenceFullNameComparer.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System.Collections.Generic;
|
|
|
using Mono.Cecil;
|
|
|
|
|
|
class MemberReferenceFullNameComparer : IEqualityComparer<MemberReference> {
|
|
|
bool IEqualityComparer<MemberReference>.Equals(MemberReference x, MemberReference y) {
|
|
|
if ( x == y ) {
|
|
|
return true;
|
|
|
}
|
|
|
if ( x == null || y == null ) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return GetFullName(x).Equals(GetFullName(y));
|
|
|
}
|
|
|
|
|
|
int IEqualityComparer<MemberReference>.GetHashCode(MemberReference obj) {
|
|
|
if ( obj == null ) {
|
|
|
return 0;
|
|
|
}
|
|
|
return GetFullName(obj).GetHashCode();
|
|
|
}
|
|
|
|
|
|
string GetFullName(MemberReference member) {
|
|
|
if (member is TypeReference type) {
|
|
|
if (type.IsGenericParameter) {
|
|
|
return $"{type.FullName} of {GetFullName(type.DeclaringType)}";
|
|
|
}
|
|
|
}
|
|
|
return member.FullName;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/MonoCecilExtensions.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
using System.Linq.Expressions;
|
|
|
using System.Reflection;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
using UnityEditor;
|
|
|
using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider;
|
|
|
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
|
|
using MethodBody = Mono.Cecil.Cil.MethodBody;
|
|
|
|
|
|
public static class MonoCecilExtensions {
|
|
|
public static MethodDefinition AddDefaultConstructor(this TypeDefinition type, Action<ILProcessor> initializers = null) {
|
|
|
var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
|
|
|
var method = new MethodDefinition(".ctor", methodAttributes, type.Module.ImportReference(typeof(void)));
|
|
|
method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
|
|
|
|
|
|
MethodReference baseConstructor = new MethodReference(method.Name, method.ReturnType) {
|
|
|
HasThis = method.HasThis,
|
|
|
ExplicitThis = method.ExplicitThis,
|
|
|
DeclaringType = type.BaseType,
|
|
|
CallingConvention = method.CallingConvention,
|
|
|
};
|
|
|
|
|
|
if (type.BaseType.IsGenericInstance) {
|
|
|
var gi = (GenericInstanceType)type.BaseType;
|
|
|
foreach (var genericParameter in gi.GenericParameters) {
|
|
|
baseConstructor.GenericParameters.Add(new GenericParameter(genericParameter.Name, baseConstructor));
|
|
|
}
|
|
|
|
|
|
}
|
|
|
method.AddTo(type);
|
|
|
|
|
|
var il = method.Body.GetILProcessor();
|
|
|
|
|
|
if (initializers != null) {
|
|
|
initializers(il);
|
|
|
}
|
|
|
|
|
|
il.Append(Instruction.Create(OpCodes.Call, type.Module.ImportReference(baseConstructor)));
|
|
|
il.Append(Instruction.Create(OpCodes.Ret));
|
|
|
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static T GetAttributeArgument<T>(this CustomAttribute attr, int index) {
|
|
|
if (TryGetAttributeArgument<T>(attr, index, out var result)) {
|
|
|
return result;
|
|
|
} else {
|
|
|
throw new ArgumentOutOfRangeException($"Argument {index} not found in {attr}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static MethodDefinition GetMethodOrThrow(this TypeDefinition type, string methodName, int? argCount = null) {
|
|
|
var query = type.Methods.Where(x => x.Name == methodName);
|
|
|
if (argCount != null) {
|
|
|
query = query.Where(x => x.Parameters.Count == argCount.Value);
|
|
|
}
|
|
|
|
|
|
var results = query.ToList();
|
|
|
if (results.Count == 0) {
|
|
|
throw new ArgumentOutOfRangeException(nameof(methodName), $"Method {methodName} not found in {type}");
|
|
|
} else if (results.Count > 1) {
|
|
|
throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}");
|
|
|
} else {
|
|
|
return results[0];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool TryGetMethod(this TypeDefinition type, string methodName, out MethodDefinition method, int? argCount = null) {
|
|
|
var query = type.Methods.Where(x => x.Name == methodName);
|
|
|
if (argCount != null) {
|
|
|
query = query.Where(x => x.Parameters.Count == argCount.Value);
|
|
|
}
|
|
|
|
|
|
var results = query.ToList();
|
|
|
if (results.Count == 0) {
|
|
|
method = default;
|
|
|
return false;
|
|
|
} else if (results.Count > 1) {
|
|
|
throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}");
|
|
|
} else {
|
|
|
method = results[0];
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static bool HasAttribute<T>(this ICustomAttributeProvider type) where T : Attribute {
|
|
|
return TryGetAttribute<T>(type, out _);
|
|
|
}
|
|
|
|
|
|
public static bool TryGetBackingField(this PropertyDefinition property, out FieldDefinition field) {
|
|
|
const string Prefix = "<";
|
|
|
const string Suffix = ">k__BackingField";
|
|
|
|
|
|
var fieldName = $"{Prefix}{property.Name}{Suffix}";
|
|
|
|
|
|
foreach (var f in property.DeclaringType.Fields) {
|
|
|
if (!f.IsPrivate) {
|
|
|
continue;
|
|
|
}
|
|
|
if (f.Name == fieldName) {
|
|
|
field = f;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
field = null;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool IsBackingField(this FieldDefinition field, out PropertyDefinition property) {
|
|
|
var fieldName = field.Name;
|
|
|
|
|
|
const string Prefix = "<";
|
|
|
const string Suffix = ">k__BackingField";
|
|
|
|
|
|
if (!fieldName.StartsWith(Prefix) || !fieldName.EndsWith(Suffix)) {
|
|
|
property = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var propertyName = fieldName.Substring(Prefix.Length, fieldName.Length - Prefix.Length - Suffix.Length);
|
|
|
foreach (var prop in field.DeclaringType.Properties) {
|
|
|
if (prop.Name == propertyName) {
|
|
|
property = prop;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw new InvalidOperationException($"Field {field} matches backing field name, but property {propertyName} is not found");
|
|
|
}
|
|
|
|
|
|
public static bool IsEnumType(this TypeReference type, out TypeReference valueType) {
|
|
|
var typeDef = type.Resolve();
|
|
|
|
|
|
if (!typeDef.IsEnum) {
|
|
|
valueType = default;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
foreach (var field in typeDef.Fields) {
|
|
|
if (field.Name == "value__" && field.IsSpecialName && field.IsRuntimeSpecialName && !field.IsStatic) {
|
|
|
valueType = field.FieldType;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw new InvalidOperationException($"Matching value__ field not found on {type}");
|
|
|
}
|
|
|
|
|
|
public static bool IsFixedBuffer(this TypeReference type, out int size) {
|
|
|
size = default;
|
|
|
if (!type.IsValueType) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (!type.Name.EndsWith("e__FixedBuffer")) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
var definition = type.Resolve();
|
|
|
|
|
|
// this is a bit of a guesswork
|
|
|
if (HasAttribute<CompilerGeneratedAttribute>(definition) &&
|
|
|
HasAttribute<UnsafeValueTypeAttribute>(definition) &&
|
|
|
definition.ClassSize > 0) {
|
|
|
size = definition.ClassSize;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool TryGetAttribute<T>(this ICustomAttributeProvider type, out CustomAttribute attribute) where T : Attribute {
|
|
|
for (int i = 0; i < type.CustomAttributes.Count; ++i) {
|
|
|
var attr = type.CustomAttributes[i];
|
|
|
if (attr.AttributeType.Is(typeof(T))) {
|
|
|
attribute = attr;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
attribute = null;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool TryGetAttributeArgument<T>(this CustomAttribute attr, int index, out T value, T defaultValue = default) {
|
|
|
if (index < attr.ConstructorArguments.Count) {
|
|
|
var val = attr.ConstructorArguments[index].Value;
|
|
|
if (val is T t) {
|
|
|
value = t;
|
|
|
return true;
|
|
|
} else if ( typeof(T).IsEnum && val.GetType().IsPrimitive ) {
|
|
|
value = (T)Enum.ToObject(typeof(T), val);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
value = defaultValue;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool TryGetAttributeProperty<T>(this CustomAttribute attr, string name, out T value, T defaultValue = default) {
|
|
|
if (attr.HasProperties) {
|
|
|
var prop = attr.Properties.FirstOrDefault(x => x.Name == name);
|
|
|
|
|
|
if (prop.Argument.Value != null) {
|
|
|
value = (T)prop.Argument.Value;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
value = defaultValue;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static bool TryGetMatchingConstructor(this TypeDefinition type, MethodDefinition constructor, out MethodDefinition matchingConstructor) {
|
|
|
return TryGetMatchingMethod(type.GetConstructors(), constructor.Parameters, out matchingConstructor);
|
|
|
}
|
|
|
|
|
|
public static bool TryGetMethod(this TypeDefinition type, string methodName, IList<ParameterDefinition> parameters, out MethodDefinition method) {
|
|
|
var methods = type.Methods.Where(x => x.Name == methodName);
|
|
|
|
|
|
if (TryGetMatchingMethod(methods, parameters, out method)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
//if (type.BaseType != null) {
|
|
|
// if (stopAtBaseType == null || !stopAtBaseType.IsSame(type.BaseType)) {
|
|
|
// return TryGetMethod(type.BaseType.Resolve(), methodName, parameters, out method, stopAtBaseType);
|
|
|
// }
|
|
|
//}
|
|
|
|
|
|
method = null;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static VariableDefinition Clone(this VariableDefinition variable) {
|
|
|
return new VariableDefinition(variable.VariableType);
|
|
|
}
|
|
|
|
|
|
public static Instruction Clone(this Instruction instruction) {
|
|
|
return (Instruction)Activator.CreateInstance(typeof(Instruction), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { instruction.OpCode, instruction.Operand }, null);
|
|
|
}
|
|
|
|
|
|
|
|
|
public static (Instruction[], VariableDefinition[]) CloneAndFixUp(MethodBody targetBody, Instruction[] instructions, VariableDefinition[] localVariables) {
|
|
|
var resultInstructions = new Instruction[instructions.Length];
|
|
|
for (int i = 0; i < instructions.Length; ++i) {
|
|
|
resultInstructions[i] = instructions[i].Clone();
|
|
|
}
|
|
|
|
|
|
var resultVariables = new VariableDefinition[localVariables.Length];
|
|
|
for (int i = 0; i < localVariables.Length; ++i) {
|
|
|
resultVariables[i] = localVariables[i].Clone();
|
|
|
targetBody.Variables.Add(resultVariables[i]);
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < instructions.Length; ++i) {
|
|
|
if (instructions[i].Operand is Instruction referencedInstruction) {
|
|
|
var referencedIndex = Array.IndexOf(instructions, referencedInstruction);
|
|
|
if (referencedIndex >= 0) {
|
|
|
resultInstructions[i].Operand = resultInstructions[referencedIndex];
|
|
|
} else {
|
|
|
throw new InvalidOperationException();
|
|
|
}
|
|
|
} else if (instructions[i].Operand is VariableDefinition referencedVariable) {
|
|
|
var referencedIndex = Array.IndexOf(localVariables, referencedVariable);
|
|
|
if (referencedIndex >= 0) {
|
|
|
resultInstructions[i].Operand = resultVariables[referencedIndex];
|
|
|
} else {
|
|
|
throw new InvalidOperationException();
|
|
|
|
|
|
}
|
|
|
} else {
|
|
|
var opCode = instructions[i].OpCode;
|
|
|
int index = -1;
|
|
|
if (opCode == OpCodes.Ldloc_0) {
|
|
|
index = 0;
|
|
|
} else if (opCode == OpCodes.Ldloc_1) {
|
|
|
index = 1;
|
|
|
} else if (opCode == OpCodes.Ldloc_2) {
|
|
|
index = 2;
|
|
|
} else if (opCode == OpCodes.Ldloc_3) {
|
|
|
index = 3;
|
|
|
}
|
|
|
|
|
|
if (index >= 0) {
|
|
|
var varIndex = Array.FindIndex(localVariables, x => x.Index == index);
|
|
|
if (varIndex >= 0) {
|
|
|
var replacementOp = resultInstructions[i];
|
|
|
replacementOp.OpCode = OpCodes.Ldloc;
|
|
|
replacementOp.Operand = resultVariables[varIndex];
|
|
|
} else {
|
|
|
throw new InvalidOperationException($"Using ldloc with index {index} but no variable with that index exists");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return (resultInstructions, resultVariables);
|
|
|
}
|
|
|
|
|
|
private static bool TryGetMatchingMethod(IEnumerable<MethodDefinition> methods, IList<ParameterDefinition> parameters, out MethodDefinition result) {
|
|
|
foreach (var c in methods) {
|
|
|
if (c.Parameters.Count != parameters.Count) {
|
|
|
continue;
|
|
|
}
|
|
|
int i;
|
|
|
for (i = 0; i < c.Parameters.Count; ++i) {
|
|
|
if (!c.Parameters[i].ParameterType.IsSame(parameters[i].ParameterType)) {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (i == c.Parameters.Count) {
|
|
|
result = c;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
result = null;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
public static void SetPosition(this GenericParameter parameter, int position) {
|
|
|
var positionField = parameter.GetType().GetField("position", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
|
Debug.Assert(positionField != null, nameof(positionField) + " != null");
|
|
|
positionField.SetValue(parameter, position);
|
|
|
}
|
|
|
|
|
|
public static TypeReference GetGenericParameter(this TypeReference provider, int position) {
|
|
|
var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!{position}", provider);
|
|
|
parameterTypeReference0.SetPosition(position);
|
|
|
return parameterTypeReference0;
|
|
|
}
|
|
|
|
|
|
public static MethodReference ImportGetter<T, TResult>(this ModuleDefinition module, System.Linq.Expressions.Expression<Func<T, TResult>> methodExpression, TypeReference genericParameterProvider = null) {
|
|
|
var body = (System.Linq.Expressions.MemberExpression)methodExpression.Body;
|
|
|
var property = (PropertyInfo)body.Member;
|
|
|
var method = module.ImportReference(property.GetMethod);
|
|
|
MakeCallable(method, genericParameterProvider);
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static FieldReference ImportField<T, TResult>(this ModuleDefinition module, System.Linq.Expressions.Expression<Func<T, TResult>> fieldExpression, TypeReference genericParameterProvider = null) {
|
|
|
var body = (System.Linq.Expressions.MemberExpression)fieldExpression.Body;
|
|
|
var member = (FieldInfo)body.Member;
|
|
|
var result = module.ImportReference(member);
|
|
|
MakeCallable(result, genericParameterProvider);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public static MethodReference ImportMethod<TResult>(this ModuleDefinition module, System.Linq.Expressions.Expression<Func<TResult>> methodExpression, TypeReference genericParameterProvider = null) {
|
|
|
MethodInfo methodInfo;
|
|
|
switch (methodExpression.Body) {
|
|
|
case MethodCallExpression mce:
|
|
|
methodInfo = mce.Method;
|
|
|
break;
|
|
|
case UnaryExpression ue:
|
|
|
methodInfo = ue.Method;
|
|
|
break;
|
|
|
default:
|
|
|
throw new NotSupportedException($"{methodExpression.Body.GetType()} is not supported");
|
|
|
}
|
|
|
|
|
|
var method = module.ImportReference(methodInfo);
|
|
|
MakeCallable(method, genericParameterProvider);
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodReference ImportMethod<T, TResult>(this ModuleDefinition module, System.Linq.Expressions.Expression<Func<T, TResult>> methodExpression, TypeReference genericParameterProvider = null) {
|
|
|
var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body;
|
|
|
var method = module.ImportReference(body.Method);
|
|
|
MakeCallable(method, genericParameterProvider);
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
public static MethodReference ImportMethod<T>(this ModuleDefinition module, System.Linq.Expressions.Expression<Action<T>> methodExpression, TypeReference genericParameterProvider = null) {
|
|
|
var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body;
|
|
|
var method = module.ImportReference(body.Method);
|
|
|
MakeCallable(method, genericParameterProvider);
|
|
|
return method;
|
|
|
}
|
|
|
|
|
|
private static void MakeCallable(this MethodReference method, TypeReference genericParameterProvider) {
|
|
|
if (method.ReturnType.IsGenericParameter || method.Parameters.Any(x => x.ParameterType.IsGenericParameter)) {
|
|
|
// needs some bullshit processing
|
|
|
if (genericParameterProvider == null) {
|
|
|
throw new ArgumentException("Generic parameter provider must be specified when importing generic methods");
|
|
|
}
|
|
|
|
|
|
// this is iffy!
|
|
|
method.DeclaringType = genericParameterProvider;
|
|
|
|
|
|
if (method.ReturnType.IsGenericParameter) {
|
|
|
method.ReturnType = genericParameterProvider.GetGenericParameter(((GenericParameter)method.ReturnType).Position);
|
|
|
}
|
|
|
|
|
|
foreach (var parameter in method.Parameters) {
|
|
|
if (parameter.ParameterType.IsGenericParameter) {
|
|
|
parameter.ParameterType = genericParameterProvider.GetGenericParameter(((GenericParameter)parameter.ParameterType).Position);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
} else if (genericParameterProvider != null) {
|
|
|
// iffy
|
|
|
method.DeclaringType = genericParameterProvider;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static void MakeCallable(this FieldReference field, TypeReference genericParameterProvider) {
|
|
|
if (genericParameterProvider == null) {
|
|
|
throw new ArgumentNullException(nameof(genericParameterProvider));
|
|
|
}
|
|
|
|
|
|
if (field.FieldType.IsGenericParameter) {
|
|
|
field.FieldType = genericParameterProvider.GetGenericParameter(((GenericParameter)field.FieldType).Position);
|
|
|
}
|
|
|
|
|
|
// iffy
|
|
|
field.DeclaringType = genericParameterProvider;
|
|
|
}
|
|
|
|
|
|
public static TypeReference ImportType<T>(this ModuleDefinition module) {
|
|
|
return module.ImportReference(typeof(T));
|
|
|
}
|
|
|
|
|
|
public static TypeReference MakeGenericInstance(this TypeReference self, params TypeReference[] arguments) {
|
|
|
if (self.GenericParameters.Count != arguments.Length) {
|
|
|
throw new ArgumentException();
|
|
|
}
|
|
|
|
|
|
var instance = new GenericInstanceType(self);
|
|
|
foreach (var argument in arguments) {
|
|
|
instance.GenericArguments.Add(argument);
|
|
|
}
|
|
|
|
|
|
return instance;
|
|
|
}
|
|
|
|
|
|
public static MethodReference MakeGenericInstance(this MethodReference self, params TypeReference[] arguments) {
|
|
|
var reference = new MethodReference(self.Name, self.ReturnType) {
|
|
|
DeclaringType = self.DeclaringType.MakeGenericInstance(arguments),
|
|
|
HasThis = self.HasThis,
|
|
|
ExplicitThis = self.ExplicitThis,
|
|
|
CallingConvention = self.CallingConvention,
|
|
|
};
|
|
|
|
|
|
foreach (var parameter in self.Parameters) {
|
|
|
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
|
|
|
}
|
|
|
|
|
|
foreach (var parameter in self.GenericParameters) {
|
|
|
reference.GenericParameters.Add(new GenericParameter(parameter.Name, reference));
|
|
|
}
|
|
|
|
|
|
return reference;
|
|
|
}
|
|
|
|
|
|
public static bool TryResolve(this GenericParameter gp, TypeReference context, out TypeReference typeReference) {
|
|
|
var declaringType = gp.DeclaringType;
|
|
|
if (declaringType is TypeDefinition declaringTypeDef) {
|
|
|
if (!declaringTypeDef.HasGenericParameters) {
|
|
|
throw new ArgumentException("Generic parameter must be declared on a generic type");
|
|
|
}
|
|
|
|
|
|
var genericArgumentIndex = declaringTypeDef.GenericParameters.IndexOf(gp);
|
|
|
if (genericArgumentIndex < 0) {
|
|
|
throw new ArgumentException($"Generic parameter not found in declaring type {declaringTypeDef}");
|
|
|
}
|
|
|
|
|
|
// find type ref pointing to this
|
|
|
var type = context;
|
|
|
|
|
|
while (!type.IsSame<NetworkBehaviour>()) {
|
|
|
var typeDef = type.Resolve();
|
|
|
if (typeDef.IsSame(declaringType)) {
|
|
|
if (type is TypeDefinition) {
|
|
|
// impossible to resolve
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (!type.IsGenericInstance) {
|
|
|
throw new NotSupportedException($"Expected generic instance of {declaringType}");
|
|
|
}
|
|
|
|
|
|
var genericInstance = (GenericInstanceType)type;
|
|
|
var genericArg = genericInstance.GenericArguments[genericArgumentIndex];
|
|
|
if (genericArg is GenericParameter newGenericParameter) {
|
|
|
return TryResolve(newGenericParameter, context, out typeReference);
|
|
|
} else {
|
|
|
typeReference = genericArg;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
type = typeDef.BaseType;
|
|
|
}
|
|
|
|
|
|
typeReference = null;
|
|
|
return false;
|
|
|
|
|
|
} else {
|
|
|
throw new NotSupportedException();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static class TmpVariable<T> {
|
|
|
public static T variable;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/NetworkTypeInfo.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Linq;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using static ILWeaverOpCodes;
|
|
|
|
|
|
[Flags]
|
|
|
public enum NetworkTypeInfoFlags {
|
|
|
IsTriviallyCopyable = 1 << 0,
|
|
|
CantBeUsedInStructs = 1 << 1,
|
|
|
CantBeUsedInRpcs = 1 << 2,
|
|
|
HasDynamicRpcSize = 1 << 3,
|
|
|
}
|
|
|
|
|
|
public class NetworkTypeInfo {
|
|
|
|
|
|
internal delegate void EmitDelegate(ICustomAttributeProvider member, ILProcessor processor, MethodContext context);
|
|
|
internal delegate int GetMemberWordCountDelegate(ICustomAttributeProvider member, TypeReference declaringType);
|
|
|
internal delegate int GetCapacityDelegate(ICustomAttributeProvider member);
|
|
|
internal delegate void EmitInitDelegate(PropertyDefinition property, ILProcessor processor, TypeReference initType, Action<ILProcessor> emitArg);
|
|
|
internal delegate void EmitStoreDelegate(PropertyDefinition property, ILProcessor processor, FieldReference field);
|
|
|
internal delegate TypeReference GetUnitySerializableTypeDelegate(bool isSerializable);
|
|
|
|
|
|
internal static NetworkTypeInfo Create(TypeReference type,
|
|
|
GetUnitySerializableTypeDelegate unitySerializableType = null,
|
|
|
EmitDelegate read = null,
|
|
|
EmitDelegate write = null,
|
|
|
EmitDelegate compactByteCount = null,
|
|
|
EmitDelegate getHashCode = null,
|
|
|
GetMemberWordCountDelegate wordCount = null,
|
|
|
int typeByteSize = -1,
|
|
|
GetCapacityDelegate capacity = null,
|
|
|
EmitInitDelegate unityInit = null,
|
|
|
EmitStoreDelegate unityStore = null,
|
|
|
NetworkTypeInfoFlags flags = 0) {
|
|
|
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
|
|
|
if (typeByteSize <= 0 && wordCount == null) {
|
|
|
throw new ArgumentNullException(nameof(wordCount));
|
|
|
}
|
|
|
|
|
|
return new NetworkTypeInfo(type, flags) {
|
|
|
_emitRead = read,
|
|
|
_emitWrite = write,
|
|
|
_emitGetHashCode = getHashCode,
|
|
|
_unitySerializableType = unitySerializableType,
|
|
|
_typeByteSize = typeByteSize,
|
|
|
_getMemberWordCount = wordCount,
|
|
|
_getCapacity = capacity,
|
|
|
_emitRpcByteCount = compactByteCount,
|
|
|
_emitUnityInit = unityInit,
|
|
|
_emitUnityStore = unityStore,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
protected NetworkTypeInfo(TypeReference type, NetworkTypeInfoFlags flags) {
|
|
|
TypeRef = type;
|
|
|
_flags = flags;
|
|
|
}
|
|
|
|
|
|
public TypeReference TypeRef { get; private set; }
|
|
|
|
|
|
public bool HasStaticSize => _typeByteSize > 0;
|
|
|
|
|
|
public int StaticByteCount {
|
|
|
get {
|
|
|
if (_typeByteSize <= 0) {
|
|
|
throw new InvalidOperationException($"{TypeRef} does not have a static type size");
|
|
|
}
|
|
|
return _typeByteSize;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public int StaticWordCount => Native.WordCount(StaticByteCount, Allocator.REPLICATE_WORD_SIZE);
|
|
|
|
|
|
public bool HasDynamicRpcSize => (_flags & NetworkTypeInfoFlags.HasDynamicRpcSize) != 0;
|
|
|
|
|
|
public bool CanBeUsedInRpc => (_flags & NetworkTypeInfoFlags.CantBeUsedInRpcs) == 0;
|
|
|
|
|
|
public bool CanBeUsedInStructs => (_flags & NetworkTypeInfoFlags.CantBeUsedInStructs) == 0;
|
|
|
|
|
|
public bool IsTriviallyCopyable => (_flags & NetworkTypeInfoFlags.IsTriviallyCopyable) != 0;
|
|
|
|
|
|
private NetworkTypeInfoFlags _flags;
|
|
|
private int _typeByteSize;
|
|
|
private EmitDelegate _emitWrite;
|
|
|
private EmitDelegate _emitRead;
|
|
|
private EmitDelegate _emitRpcByteCount;
|
|
|
private EmitDelegate _emitGetHashCode;
|
|
|
private GetMemberWordCountDelegate _getMemberWordCount;
|
|
|
private GetCapacityDelegate _getCapacity;
|
|
|
private GetUnitySerializableTypeDelegate _unitySerializableType;
|
|
|
private EmitInitDelegate _emitUnityInit;
|
|
|
private EmitStoreDelegate _emitUnityStore;
|
|
|
|
|
|
|
|
|
internal virtual void EmitUnityInit(PropertyDefinition property, ILProcessor il, TypeReference initType, Action<ILProcessor> emitArg) {
|
|
|
if (_emitUnityInit != null) {
|
|
|
_emitUnityInit(property, il, initType, emitArg);
|
|
|
} else {
|
|
|
var setterRef = property.SetMethod.GetCallable();
|
|
|
il.Append(Ldarg_0());
|
|
|
emitArg(il);
|
|
|
il.Append(Call(setterRef));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual void EmitUnityStore(PropertyDefinition property, ILProcessor il, FieldReference field) {
|
|
|
if (_emitUnityStore != null) {
|
|
|
_emitUnityStore(property, il, field);
|
|
|
} else {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(property.GetMethod.GetCallable()));
|
|
|
il.Append(Stfld(field));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual int GetMemberWordCount(ICustomAttributeProvider member, TypeReference declaringType) {
|
|
|
int result;
|
|
|
if (_getMemberWordCount != null) {
|
|
|
if (member == null) {
|
|
|
throw new InvalidOperationException($"Member is needed to get word count");
|
|
|
}
|
|
|
result = _getMemberWordCount(member, declaringType);
|
|
|
} else {
|
|
|
result = StaticWordCount;
|
|
|
}
|
|
|
|
|
|
if (result <= 0) {
|
|
|
throw new InvalidOperationException($"Expected word count of {member} to be greater than 0");
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
internal virtual bool TryGetCapacity(ICustomAttributeProvider member, out int capacity) {
|
|
|
if (_getCapacity != null) {
|
|
|
capacity = _getCapacity(member);
|
|
|
return true;
|
|
|
} else {
|
|
|
capacity = 0;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual void EmitRpcByteCount(ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) {
|
|
|
if (_emitRpcByteCount != null) {
|
|
|
_emitRpcByteCount(member, il, context);
|
|
|
if (wordAligned) {
|
|
|
il.AppendMacro(context.AlignToWordSize());
|
|
|
}
|
|
|
} else if (_getMemberWordCount != null) {
|
|
|
il.Append(Ldc_I4(_getMemberWordCount(member, context.Method.DeclaringType) * Allocator.REPLICATE_WORD_SIZE));
|
|
|
} else {
|
|
|
if (wordAligned) {
|
|
|
il.Append(Ldc_I4(Native.RoundToAlignment(StaticByteCount, Allocator.REPLICATE_WORD_ALIGN)));
|
|
|
} else {
|
|
|
il.Append(Ldc_I4(StaticByteCount));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual void EmitWrite(ILProcessor il, MethodContext context, ICustomAttributeProvider member) {
|
|
|
if (_emitWrite != null) {
|
|
|
_emitWrite(member, il, context);
|
|
|
} else {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Stind_or_Stobj(TypeRef));
|
|
|
il.AppendMacro(context.AddOffset(StaticByteCount));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual void EmitGetHashCode(ILProcessor il, MethodContext context, ICustomAttributeProvider member) {
|
|
|
if (_emitGetHashCode != null) {
|
|
|
_emitGetHashCode(member, il, context);
|
|
|
} else {
|
|
|
var tmp = context.AddVariable(TypeRef);
|
|
|
|
|
|
if (context.HasValueAddrGetter) {
|
|
|
il.AppendMacro(context.LoadValueAddr());
|
|
|
} else {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Stloc(tmp));
|
|
|
il.Append(Ldloca(tmp));
|
|
|
}
|
|
|
|
|
|
if (TypeRef.IsPrimitive) {
|
|
|
var getHashCode = context.Assembly.Import(TypeRef.GetPrimitiveType().GetMethod(nameof(object.GetHashCode)));
|
|
|
il.Append(Call(getHashCode));
|
|
|
} else {
|
|
|
if (!TypeRef.IsValueType) {
|
|
|
throw new InvalidOperationException($"Expected {TypeRef} to be a value type");
|
|
|
}
|
|
|
var getHashCode = context.Assembly.Object.GetMethod(nameof(object.GetHashCode));
|
|
|
if (TypeRef.IsValueType) {
|
|
|
il.Append(Instruction.Create(OpCodes.Constrained, TypeRef));
|
|
|
}
|
|
|
il.Append(Callvirt(getHashCode));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual void EmitRead(ILProcessor il, MethodContext context, ICustomAttributeProvider member) {
|
|
|
if (_emitRead != null) {
|
|
|
_emitRead(member, il, context);
|
|
|
} else {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.Append(Ldind_or_Ldobj(TypeRef));
|
|
|
il.AppendMacro(context.AddOffset(StaticByteCount));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal virtual TypeReference GetUnityBackingFieldType(bool isSerializable) {
|
|
|
return _unitySerializableType?.Invoke(isSerializable) ?? TypeRef;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/NetworkTypeInfoRegistry.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Reflection;
|
|
|
using Mono.Cecil;
|
|
|
using Mono.Cecil.Cil;
|
|
|
using Mono.Cecil.Rocks;
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
using UnityEngine;
|
|
|
#endif
|
|
|
|
|
|
using static ILWeaverOpCodes;
|
|
|
using Behaviour = Fusion.Behaviour;
|
|
|
using FieldAttributes = Mono.Cecil.FieldAttributes;
|
|
|
using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider;
|
|
|
|
|
|
public class NetworkTypeInfoRegistry {
|
|
|
|
|
|
public delegate int CalculateWordCountDelegate(TypeReference type);
|
|
|
|
|
|
private Dictionary<TypeReference, NetworkTypeInfo> _types = new Dictionary<TypeReference, NetworkTypeInfo>(new MemberReferenceFullNameComparer());
|
|
|
private ModuleDefinition _module;
|
|
|
private CalculateWordCountDelegate _calculateValueTypeWordCount;
|
|
|
private ILWeaverSettings _settings;
|
|
|
|
|
|
internal ILWeaverLog Log { get; }
|
|
|
|
|
|
public NetworkTypeInfoRegistry(ModuleDefinition module, ILWeaverSettings settings, ILWeaverLogger log, CalculateWordCountDelegate getWordCount) {
|
|
|
_module = module;
|
|
|
_settings = settings;
|
|
|
_calculateValueTypeWordCount = getWordCount;
|
|
|
Log = new ILWeaverLog(log);
|
|
|
AddBuiltInTypes();
|
|
|
}
|
|
|
|
|
|
public int GetTypeWordCount(TypeReference type) => GetInfo(type).StaticWordCount;
|
|
|
public int GetPropertyWordCount(PropertyDefinition property) => GetMemberWordCount(property.PropertyType, property, property.DeclaringType);
|
|
|
public int GetMemberWordCount(TypeReference type, ICustomAttributeProvider member, TypeReference declaringType) => GetInfo(type).GetMemberWordCount(member, declaringType);
|
|
|
|
|
|
internal void EmitRead(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitRead(il, context, member);
|
|
|
internal void EmitWrite(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitWrite(il, context, member);
|
|
|
internal void EmitGetHashCode(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitGetHashCode(il, context, member);
|
|
|
internal void EmitRpcByteCount(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) => GetInfo(type).EmitRpcByteCount(il, context, member, wordAligned);
|
|
|
|
|
|
|
|
|
public NetworkTypeInfo GetInfo<T>() => GetInfo(typeof(T));
|
|
|
|
|
|
public NetworkTypeInfo GetInfo(Type type) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
var t = _module.ImportReference(type);
|
|
|
if (t == null) {
|
|
|
throw new InvalidOperationException($"Failed to resolve: {type.FullName}");
|
|
|
}
|
|
|
return GetInfo(t);
|
|
|
}
|
|
|
|
|
|
|
|
|
public NetworkTypeInfo GetInfo(TypeReference type) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
if (_types.TryGetValue(type, out var result)) {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
return AddType(type);
|
|
|
}
|
|
|
|
|
|
const int DefaultArrayCapacity = 1;
|
|
|
|
|
|
public const int DefaultContainerCapacity = 1;
|
|
|
public const int DefaultStringCapacity = 16;
|
|
|
|
|
|
private NetworkTypeInfo AddType(TypeReference type) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
|
|
|
if (_types.ContainsKey(type)) {
|
|
|
throw new InvalidOperationException($"Type {type} already added");
|
|
|
}
|
|
|
|
|
|
var meta = MakeTypeData(type);
|
|
|
|
|
|
_types.Add(type, meta);
|
|
|
return meta;
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo MakeTypeData(TypeReference type) {
|
|
|
|
|
|
var resolved = type.Resolve();
|
|
|
var imported = _module.ImportReference(type);
|
|
|
|
|
|
if (type is GenericParameter) {
|
|
|
throw new ArgumentException($"Generic parameters are not supported", nameof(type));
|
|
|
}
|
|
|
|
|
|
{
|
|
|
if (type is Mono.Cecil.PointerType ptrType) {
|
|
|
return CreatePointerOrRefMeta(imported, ptrType.ElementType);
|
|
|
} else if (type is ByReferenceType refType) {
|
|
|
return CreatePointerOrRefMeta(imported, refType.ElementType);
|
|
|
} else if (type is TypeSpecification && !(type is GenericInstanceType)) {
|
|
|
throw new ArgumentException($"Invalid TypeReference type: {type.FullName} ({type.GetType()})", nameof(type));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
{
|
|
|
if (type.IsNetworkArray(out var elementType)) {
|
|
|
return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, false);
|
|
|
}
|
|
|
if (type.IsNetworkList(out elementType)) {
|
|
|
return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, true);
|
|
|
}
|
|
|
if (type.IsNetworkDictionary(out var keyType, out var valueType)) {
|
|
|
return CreateNetworkDictionaryMeta(imported, keyType, valueType);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (TryGetNetworkWrapperType(type, out var wrapInfo)) {
|
|
|
return CreateWrappedMeta(imported, wrapInfo);
|
|
|
} else if (resolved.IsValueType) {
|
|
|
if (resolved.IsFixedBuffer(out var byteCount)) {
|
|
|
return CreateUnmanagedTypeMeta(imported, byteCount);
|
|
|
} else if (resolved.IsEnumType(out var enumValueType)) {
|
|
|
return CreateUnmanagedTypeMeta(imported, GetInfo(enumValueType).StaticByteCount);
|
|
|
} else if (resolved.Is<INetworkStruct>() || resolved.Is<INetworkInput>()) {
|
|
|
int wordCount;
|
|
|
try {
|
|
|
wordCount = GetUserValueTypeWordCount(resolved, type);
|
|
|
} catch (Exception ex) {
|
|
|
throw new ArgumentException($"Failed to get user type word count: {type.FullName}", nameof(type), ex);
|
|
|
}
|
|
|
return CreateUnmanagedTypeMeta(imported, Math.Max(1, wordCount) * Allocator.REPLICATE_WORD_SIZE);
|
|
|
} else {
|
|
|
// TODO: unmanaged portable structs
|
|
|
throw new ArgumentException($"Value types need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)} interface (type: {type.FullName})", nameof(type));
|
|
|
}
|
|
|
} else {
|
|
|
// check for wapper?
|
|
|
throw new ArgumentException($"Type {type.FullName} is a reference type but does not implement Wrap pattern.", nameof(type));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private int GetUserValueTypeWordCount(TypeDefinition type, TypeReference typeRef) {
|
|
|
int wordCount;
|
|
|
|
|
|
// is the type already weaved?
|
|
|
if (type.TryGetAttribute<NetworkStructWeavedAttribute>(out var attribute)) {
|
|
|
|
|
|
wordCount = attribute.GetAttributeArgument<int>(0);
|
|
|
|
|
|
// is this a generic composite type?
|
|
|
if (attribute.TryGetAttributeArgument(1, out bool value, false) && value) {
|
|
|
Log.Assert(typeRef.IsGenericInstance == true);
|
|
|
foreach (var gen in ((GenericInstanceType)typeRef).GenericArguments) {
|
|
|
if (gen.IsValueType && gen.Is<INetworkStruct>()) {
|
|
|
wordCount += GetTypeWordCount(typeRef);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
wordCount = _calculateValueTypeWordCount(type);
|
|
|
}
|
|
|
|
|
|
return wordCount;
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo CreateUnmanagedTypeMeta(TypeReference type, int byteCount, NetworkTypeInfo.EmitDelegate read = null, NetworkTypeInfo.EmitDelegate write = null, bool isTriviallyCopyable = true) {
|
|
|
return NetworkTypeInfo.Create(type,
|
|
|
typeByteSize: byteCount,
|
|
|
flags: isTriviallyCopyable ? NetworkTypeInfoFlags.IsTriviallyCopyable : 0,
|
|
|
read: read,
|
|
|
write: write
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo CreatePointerOrRefMeta(TypeReference type, TypeReference elementType) {
|
|
|
var elementInfo = GetInfo(elementType);
|
|
|
return NetworkTypeInfo.Create(type,
|
|
|
read: (member, il, context) => il.AppendMacro(context.LoadAddress()),
|
|
|
write: (member, il, context) => throw new NotSupportedException($"Pointers and references can't have setters"),
|
|
|
wordCount: (member, declaringType) => elementInfo.GetMemberWordCount(member, declaringType),
|
|
|
unitySerializableType: _ => elementType,
|
|
|
unityInit: (prop, il, _, emitArg) => {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
emitArg(il);
|
|
|
il.Append(Stind_or_Stobj(prop.PropertyType.GetElementTypeWithGenerics()));
|
|
|
},
|
|
|
unityStore: (prop, il, field) => {
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
il.Append(Ldind_or_Ldobj(prop.PropertyType.GetElementType()));
|
|
|
il.Append(Stfld(field));
|
|
|
},
|
|
|
flags: NetworkTypeInfoFlags.CantBeUsedInRpcs | NetworkTypeInfoFlags.CantBeUsedInRpcs
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo CreateWrappedMeta(TypeReference type, NetworkTypeWrapInfo WrapInfo) {
|
|
|
if (WrapInfo == null) {
|
|
|
throw new ArgumentNullException(nameof(WrapInfo));
|
|
|
}
|
|
|
|
|
|
NetworkTypeInfoFlags flags = 0;
|
|
|
if (WrapInfo.MaxByteCount <= 0 && !WrapInfo.WrapperTypeInfo.CanBeUsedInRpc) {
|
|
|
flags |= NetworkTypeInfoFlags.CantBeUsedInRpcs;
|
|
|
}
|
|
|
if (WrapInfo.WrapNeedsRunner || WrapInfo.UnwrapNeedsRunner) {
|
|
|
flags |= NetworkTypeInfoFlags.CantBeUsedInStructs;
|
|
|
}
|
|
|
|
|
|
return NetworkTypeInfo.Create(type,
|
|
|
wordCount: (member, declaringType) => {
|
|
|
if (WrapInfo.MaxByteCount > 0) {
|
|
|
return Native.WordCount(WrapInfo.MaxByteCount, Allocator.REPLICATE_WORD_SIZE);
|
|
|
} else {
|
|
|
return WrapInfo.WrapperTypeInfo.GetMemberWordCount(member, declaringType);
|
|
|
}
|
|
|
},
|
|
|
read: (member, il, context) => {
|
|
|
if (WrapInfo.IsRaw || WrapInfo.UnwrapByRef) {
|
|
|
|
|
|
|
|
|
var nop = il.AppendReturn(Nop());
|
|
|
|
|
|
if (WrapInfo.UnwrapNeedsRunner) {
|
|
|
il.AppendMacro(context.LoadRunner());
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
|
|
|
if (!WrapInfo.IsRaw) {
|
|
|
il.Append(Ldind_or_Ldobj(WrapInfo.WrapperType));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.GetTargetVariableAddrOrTemp(WrapInfo.TargetType, il, out var variable, before: nop));
|
|
|
il.Append(Call(WrapInfo.UnwrapMethod.GetCallable()));
|
|
|
|
|
|
if (WrapInfo.IsRaw) {
|
|
|
il.AppendMacro(context.VerifyRawNetworkUnwrap(type, WrapInfo.MaxByteCount));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
} else {
|
|
|
il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount));
|
|
|
}
|
|
|
|
|
|
if (variable != null) {
|
|
|
il.Append(Ldloc(variable));
|
|
|
if (!WrapInfo.TargetType.Is(type)) {
|
|
|
il.Append(Cast(type));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
il.Remove(nop);
|
|
|
|
|
|
} else {
|
|
|
if (WrapInfo.UnwrapNeedsRunner) {
|
|
|
il.AppendMacro(context.LoadRunner());
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.Append(Ldind_or_Ldobj(WrapInfo.WrapperType));
|
|
|
il.Append(Call(WrapInfo.UnwrapMethod.GetCallable()));
|
|
|
|
|
|
if (!WrapInfo.TargetType.Is(type)) {
|
|
|
il.Append(Cast(type));
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount));
|
|
|
}
|
|
|
},
|
|
|
write: (member, il, context) => {
|
|
|
// this is to do the store later on
|
|
|
if (!WrapInfo.IsRaw) {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
}
|
|
|
|
|
|
// actual args start here
|
|
|
|
|
|
if (WrapInfo.WrapNeedsRunner) {
|
|
|
il.AppendMacro(context.LoadRunner());
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
if (WrapInfo.IsRaw) {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
}
|
|
|
|
|
|
il.Append(Call(WrapInfo.WrapMethod.GetCallable()));
|
|
|
|
|
|
if (WrapInfo.IsRaw) {
|
|
|
il.AppendMacro(context.VerifyRawNetworkWrap(type, WrapInfo.MaxByteCount));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
} else {
|
|
|
il.Append(Stind_or_Stobj(WrapInfo.WrapperType));
|
|
|
il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount));
|
|
|
}
|
|
|
},
|
|
|
getHashCode: (member, il, context) => {
|
|
|
|
|
|
if (WrapInfo.IsRaw) {
|
|
|
if (WrapInfo.WrapNeedsRunner) {
|
|
|
il.AppendMacro(context.LoadRunner());
|
|
|
}
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
|
|
|
var bufferVariable = context.AddVariable(context.Assembly.Import(typeof(byte)).MakePointerType());
|
|
|
var bytesWrittenVariable = context.AddVariable(context.Assembly.Import(typeof(int)));
|
|
|
il.Append(Ldc_I4(WrapInfo.MaxByteCount));
|
|
|
il.Append(Conv_U());
|
|
|
il.Append(Instruction.Create(OpCodes.Localloc));
|
|
|
il.Append(Stloc(bufferVariable));
|
|
|
il.Append(Ldloc(bufferVariable));
|
|
|
|
|
|
il.Append(Call(WrapInfo.WrapMethod.GetCallable()));
|
|
|
il.Append(Stloc(bytesWrittenVariable));
|
|
|
|
|
|
il.Append(Ldloc(bufferVariable));
|
|
|
il.Append(Ldloc(bytesWrittenVariable));
|
|
|
|
|
|
var getArrayHashCode = context.Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.GetByteArrayHashCode), argsCount: 2);
|
|
|
il.Append(Call(getArrayHashCode));
|
|
|
|
|
|
} else {
|
|
|
using var nestedContext = new MethodContext(context.Assembly, context.Method, valueGetter: il => {
|
|
|
if (WrapInfo.WrapNeedsRunner) {
|
|
|
il.AppendMacro(context.LoadRunner());
|
|
|
}
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Call(WrapInfo.WrapMethod.GetCallable()));
|
|
|
});
|
|
|
WrapInfo.WrapperTypeInfo.EmitGetHashCode(il, nestedContext, member);
|
|
|
}
|
|
|
},
|
|
|
flags: flags
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo CreateNetworkArrayOrNetworkLinkedListMeta(TypeReference type, TypeReference elementType, bool isList = false) {
|
|
|
var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters));
|
|
|
|
|
|
ctor.DeclaringType = type;
|
|
|
|
|
|
elementType = _module.ImportReference(elementType);
|
|
|
|
|
|
var elementInfo = GetInfo(elementType);
|
|
|
var unitySerializableType = elementType.MakeArrayType();
|
|
|
|
|
|
return NetworkTypeInfo.Create(type,
|
|
|
wordCount: (member, declaringType) => {
|
|
|
var capacity = GetCapacity(member, DefaultCollectionCapacity);
|
|
|
var elementWordCount = elementInfo.GetMemberWordCount(member, declaringType);
|
|
|
if (isList) {
|
|
|
return NetworkLinkedList<int>.META_WORDS + capacity * (elementWordCount + NetworkLinkedList<int>.ELEMENT_WORDS);
|
|
|
} else {
|
|
|
return capacity * elementWordCount;
|
|
|
}
|
|
|
},
|
|
|
read: (member, il, context) => {
|
|
|
var capacity = GetCapacity(member, DefaultContainerCapacity);
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.Append(Ldc_I4(capacity));
|
|
|
il.AppendMacro(context.LoadElementReaderWriter(elementType, member));
|
|
|
il.Append(Newobj(ctor));
|
|
|
},
|
|
|
write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"),
|
|
|
capacity: (member) => GetCapacity(member, DefaultContainerCapacity),
|
|
|
unitySerializableType: _ => unitySerializableType,
|
|
|
unityInit: (prop, il, _, emitArg) => {
|
|
|
var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.InitializeNetworkList) : nameof(NetworkBehaviourUtils.InitializeNetworkArray)));
|
|
|
var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } };
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
emitArg(il);
|
|
|
il.Append(Ldstr(prop.Name));
|
|
|
il.Append(Call(m));
|
|
|
},
|
|
|
unityStore: (prop, il, field) => {
|
|
|
var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.CopyFromNetworkList) : nameof(NetworkBehaviourUtils.CopyFromNetworkArray)));
|
|
|
var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } };
|
|
|
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldflda(field));
|
|
|
il.Append(Call(m));
|
|
|
},
|
|
|
flags: NetworkTypeInfoFlags.CantBeUsedInRpcs
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private NetworkTypeInfo CreateNetworkDictionaryMeta(TypeReference type, TypeReference keyType, TypeReference valueType) {
|
|
|
var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters));
|
|
|
ctor.DeclaringType = type;
|
|
|
|
|
|
keyType = _module.ImportReference(keyType);
|
|
|
valueType = _module.ImportReference(valueType);
|
|
|
|
|
|
var keyInfo = GetInfo(keyType);
|
|
|
var valueInfo = GetInfo(valueType);
|
|
|
|
|
|
NetworkTypeInfo.GetUnitySerializableTypeDelegate unitySerializableType = isSerializable => {
|
|
|
if (isSerializable && _settings.UseSerializableDictionary) {
|
|
|
return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(SerializableDictionary<,>)), keyType, valueType);
|
|
|
} else {
|
|
|
return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(Dictionary<,>)), keyType, valueType);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
NetworkTypeInfo.GetCapacityDelegate getCapacity = member => Primes.GetNextPrime(Math.Max(1, GetCapacity(member, DefaultCollectionCapacity)));
|
|
|
return NetworkTypeInfo.Create(type,
|
|
|
wordCount: (member, declaringType) => {
|
|
|
var capacity = getCapacity(member);
|
|
|
return
|
|
|
// meta data (counts, etc)
|
|
|
NetworkDictionary<int, int>.META_WORD_COUNT +
|
|
|
// buckets
|
|
|
(capacity) +
|
|
|
// entry
|
|
|
// next
|
|
|
(capacity) +
|
|
|
// key
|
|
|
(capacity * keyInfo.GetMemberWordCount(member, declaringType)) +
|
|
|
// value
|
|
|
(capacity * valueInfo.GetMemberWordCount(member, declaringType));
|
|
|
},
|
|
|
read: (member, il, context) => {
|
|
|
var capacity = getCapacity(member);
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
il.Append(Ldc_I4(capacity));
|
|
|
il.AppendMacro(context.LoadElementReaderWriter(keyType, member));
|
|
|
il.AppendMacro(context.LoadElementReaderWriter(valueType, member));
|
|
|
il.Append(Newobj(ctor));
|
|
|
},
|
|
|
write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"),
|
|
|
capacity: getCapacity,
|
|
|
unitySerializableType: unitySerializableType,
|
|
|
unityInit: (prop, il, initType, emitArg) => {
|
|
|
if (initType == null) {
|
|
|
initType = unitySerializableType(false);
|
|
|
}
|
|
|
var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.InitializeNetworkDictionary)));
|
|
|
var m = new GenericInstanceMethod(baseMethod) {
|
|
|
GenericArguments = { initType, keyType, valueType }
|
|
|
};
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
emitArg(il);
|
|
|
il.Append(Ldstr(prop.Name));
|
|
|
il.Append(Call(m));
|
|
|
},
|
|
|
unityStore: (prop, il, field) => {
|
|
|
var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.CopyFromNetworkDictionary)));
|
|
|
var m = new GenericInstanceMethod(baseMethod) {
|
|
|
GenericArguments = { field.FieldType, keyType, valueType }
|
|
|
};
|
|
|
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Call(prop.GetMethod.GetCallable()));
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldflda(field));
|
|
|
il.Append(Call(m));
|
|
|
},
|
|
|
flags: NetworkTypeInfoFlags.CantBeUsedInRpcs
|
|
|
);
|
|
|
}
|
|
|
|
|
|
void AddBuiltInType(NetworkTypeInfo meta) {
|
|
|
_types.Add(meta.TypeRef, meta);
|
|
|
}
|
|
|
|
|
|
unsafe void AddBuiltInType<T>(MethodReference readMethod = null, MethodReference writeMethod = null, bool isTriviallyCopyable = true) where T : unmanaged {
|
|
|
|
|
|
var imported = _module.ImportReference(typeof(T));
|
|
|
|
|
|
var size = sizeof(T);
|
|
|
int alignedByteCount = Native.WordCount(size, Allocator.REPLICATE_WORD_SIZE) * Allocator.REPLICATE_WORD_SIZE;
|
|
|
|
|
|
if (readMethod != null) {
|
|
|
readMethod = _module.ImportReference(readMethod);
|
|
|
}
|
|
|
if (writeMethod != null) {
|
|
|
writeMethod = _module.ImportReference(writeMethod);
|
|
|
}
|
|
|
|
|
|
AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), size,
|
|
|
read: readMethod != null ? (member, il, c) => {
|
|
|
il.AppendMacro(c.LoadAddress());
|
|
|
il.Append(Call(readMethod));
|
|
|
il.AppendMacro(c.AddOffset(alignedByteCount));
|
|
|
} : null,
|
|
|
write: writeMethod != null ? (member, il, c) => {
|
|
|
il.AppendMacro(c.LoadAddress());
|
|
|
il.AppendMacro(c.LoadValue());
|
|
|
il.Append(Call(writeMethod));
|
|
|
il.AppendMacro(c.AddOffset(alignedByteCount));
|
|
|
} : null,
|
|
|
isTriviallyCopyable: isTriviallyCopyable
|
|
|
));
|
|
|
}
|
|
|
|
|
|
|
|
|
unsafe void AddUnityType<T>() where T : unmanaged {
|
|
|
AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), sizeof(T)));
|
|
|
}
|
|
|
|
|
|
unsafe void AddBuiltInTypes() {
|
|
|
|
|
|
var readWriteUtils = _module.ImportReference(typeof(ReadWriteUtilsForWeaver)).Resolve();
|
|
|
|
|
|
// safe primitive types
|
|
|
AddBuiltInType<byte>();
|
|
|
AddBuiltInType<sbyte>();
|
|
|
AddBuiltInType<Int16>();
|
|
|
AddBuiltInType<UInt16>();
|
|
|
AddBuiltInType<Int32>();
|
|
|
AddBuiltInType<UInt32>();
|
|
|
AddBuiltInType<Int64>();
|
|
|
AddBuiltInType<UInt64>();
|
|
|
AddBuiltInType<float>();
|
|
|
AddBuiltInType<double>();
|
|
|
AddBuiltInType<char>();
|
|
|
|
|
|
{
|
|
|
var imported = _module.ImportReference(typeof(bool));
|
|
|
var readMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadBoolean), 1));
|
|
|
var writeMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteBoolean), 2));
|
|
|
|
|
|
AddBuiltInType(NetworkTypeInfo.Create(imported,
|
|
|
read: (member, il, c) => {
|
|
|
il.AppendMacro(c.LoadAddress());
|
|
|
il.Append(Call(readMethod));
|
|
|
il.AppendMacro(c.AddOffset(sizeof(int)));
|
|
|
},
|
|
|
write: (member, il, c) => {
|
|
|
il.AppendMacro(c.LoadAddress());
|
|
|
il.AppendMacro(c.LoadValue());
|
|
|
il.Append(Call(writeMethod));
|
|
|
il.AppendMacro(c.AddOffset(sizeof(int)));
|
|
|
},
|
|
|
typeByteSize: sizeof(int)
|
|
|
));
|
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
// Unity types
|
|
|
// TODO: restore AccuracyAttribute support for migrating from 1.1
|
|
|
AddBuiltInType<Quaternion>();
|
|
|
AddBuiltInType<Vector2>();
|
|
|
AddBuiltInType<Vector3>();
|
|
|
AddBuiltInType<Vector4>();
|
|
|
AddUnityType<Matrix4x4>();
|
|
|
AddUnityType<Vector2Int>();
|
|
|
AddUnityType<Vector3Int>();
|
|
|
AddUnityType<BoundingSphere>();
|
|
|
AddUnityType<Bounds>();
|
|
|
AddUnityType<Rect>();
|
|
|
AddUnityType<Color>();
|
|
|
AddUnityType<BoundsInt>();
|
|
|
AddUnityType<RectInt>();
|
|
|
AddUnityType<Color32>();
|
|
|
#endif
|
|
|
|
|
|
{
|
|
|
AddBuiltInType<NetworkString<_2>> ();
|
|
|
AddBuiltInType<NetworkString<_4>> ();
|
|
|
AddBuiltInType<NetworkString<_8>> ();
|
|
|
AddBuiltInType<NetworkString<_16>> ();
|
|
|
AddBuiltInType<NetworkString<_32>> ();
|
|
|
AddBuiltInType<NetworkString<_64>> ();
|
|
|
AddBuiltInType<NetworkString<_128>>();
|
|
|
AddBuiltInType<NetworkString<_256>>();
|
|
|
AddBuiltInType<NetworkString<_512>>();
|
|
|
}
|
|
|
|
|
|
{
|
|
|
var getUtf8ByteCount = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetByteCountUtf8NoHash), 1));
|
|
|
var writeUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf8NoHash), 2));
|
|
|
var readUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf8NoHash), 2));
|
|
|
var getHashCode = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetStringHashCode), 2));
|
|
|
|
|
|
var readNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32NoHash), 3));
|
|
|
var writeNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32NoHash), 3));
|
|
|
|
|
|
var readWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32WithHash), 3));
|
|
|
var writeWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32WithHash), 4));
|
|
|
|
|
|
var t = _module.ImportReference(typeof(string));
|
|
|
|
|
|
FieldDefinition GetCacheField(PropertyDefinition prop) {
|
|
|
var name = $"cache_{prop.Name}";
|
|
|
var field = prop.DeclaringType.Fields.SingleOrDefault(x => x.Name == name && x.FieldType.IsSame(t));
|
|
|
if (field == null) {
|
|
|
field = new FieldDefinition($"cache_{prop.Name}", FieldAttributes.Private, t);
|
|
|
field.AddTo(prop.DeclaringType);
|
|
|
field.AddAttribute<PreserveInPluginAttribute>();
|
|
|
}
|
|
|
return field;
|
|
|
}
|
|
|
|
|
|
AddBuiltInType(NetworkTypeInfo.Create(t,
|
|
|
wordCount: (member, declaringType) => {
|
|
|
return GetCapacity(member, DefaultStringCapacity) + (declaringType.IsValueType ? 1 : 2);
|
|
|
},
|
|
|
read: (member, il, context) => {
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
if (context.IsWriteCompact) {
|
|
|
il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable));
|
|
|
il.Append(Call(readUtf8));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
if (variable != null) {
|
|
|
il.Append(Ldloc(variable));
|
|
|
}
|
|
|
} else {
|
|
|
il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity)));
|
|
|
if (context.Method.DeclaringType.IsValueType) {
|
|
|
il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable));
|
|
|
il.Append(Call(readNoHash));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
if (variable != null) {
|
|
|
il.Append(Ldloc(variable));
|
|
|
}
|
|
|
} else {
|
|
|
var field = GetCacheField((PropertyDefinition)member);
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldflda(field));
|
|
|
il.Append(Call(readWithHash));
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldfld(field));
|
|
|
}
|
|
|
|
|
|
}
|
|
|
},
|
|
|
write: (member, il, context) => {
|
|
|
|
|
|
il.AppendMacro(context.LoadAddress());
|
|
|
|
|
|
if (context.IsWriteCompact) {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Call(writeUtf8));
|
|
|
} else {
|
|
|
il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity)));
|
|
|
|
|
|
if (context.Method.DeclaringType.IsValueType) {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Call(writeNoHash));
|
|
|
} else {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldarg_0());
|
|
|
il.Append(Ldflda(GetCacheField((PropertyDefinition)member)));
|
|
|
il.Append(Call(writeWithHash));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
il.AppendMacro(context.AddOffset());
|
|
|
},
|
|
|
getHashCode: (member, il, context) => {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity)));
|
|
|
il.Append(Call(getHashCode));
|
|
|
},
|
|
|
compactByteCount: (member, il, context) => {
|
|
|
il.AppendMacro(context.LoadValue());
|
|
|
il.Append(Call(getUtf8ByteCount));
|
|
|
},
|
|
|
capacity: member => GetCapacity(member, DefaultStringCapacity),
|
|
|
flags: NetworkTypeInfoFlags.HasDynamicRpcSize
|
|
|
));
|
|
|
}
|
|
|
|
|
|
// System types
|
|
|
AddBuiltInType<Guid>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// reference types
|
|
|
foreach (var t in new [] { typeof(NetworkObject), typeof(NetworkBehaviour) }) {
|
|
|
var typeRef = _module.ImportReference(t);
|
|
|
TryGetNetworkWrapperType(typeRef, out var wrapInfo);
|
|
|
AddBuiltInType(CreateWrappedMeta(typeRef, wrapInfo));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const int DefaultCollectionCapacity = 1;
|
|
|
|
|
|
|
|
|
internal static int GetCapacity(PropertyDefinition property, int defaultCapacity) {
|
|
|
if (property.TryGetAttribute<CapacityAttribute>(out var attr)) {
|
|
|
return attr.GetAttributeArgument<int>(0);
|
|
|
} else {
|
|
|
return defaultCapacity;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool TryGetNetworkWrapperType(TypeReference type, out NetworkTypeWrapInfo result) {
|
|
|
if (type == null) {
|
|
|
throw new ArgumentNullException(nameof(type));
|
|
|
}
|
|
|
|
|
|
var definition = type.Resolve();
|
|
|
|
|
|
int rawByteCount = 0;
|
|
|
|
|
|
bool wrapNeedsRunner = false;
|
|
|
|
|
|
if (definition.GetSingleOrDefaultMethodWithAttribute<NetworkSerializeMethodAttribute>(out var wrapAttribute, out var wrapMethod)) {
|
|
|
int argsStart = 0;
|
|
|
wrapAttribute.TryGetAttributeProperty<int>(nameof(NetworkSerializeMethodAttribute.MaxSize), out rawByteCount);
|
|
|
|
|
|
try {
|
|
|
if (wrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is<NetworkRunner>()) {
|
|
|
wrapNeedsRunner = true;
|
|
|
argsStart = 1;
|
|
|
}
|
|
|
|
|
|
if (rawByteCount > 0) {
|
|
|
wrapMethod.ThrowIfNotStatic()
|
|
|
.ThrowIfNotPublic()
|
|
|
.ThrowIfParameterCount(argsStart + 2)
|
|
|
.ThrowIfParameter(argsStart + 0, type)
|
|
|
.ThrowIfParameter(argsStart + 1, typeof(byte*))
|
|
|
.ThrowIfReturnType(typeof(int));
|
|
|
} else {
|
|
|
wrapMethod.ThrowIfNotStatic()
|
|
|
.ThrowIfNotPublic()
|
|
|
.ThrowIfParameterCount(argsStart + 1)
|
|
|
.ThrowIfParameter(argsStart + 0, type);
|
|
|
}
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)} has an invalid signature", ex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool unwrapByRef = false;
|
|
|
bool unwrapNeedsRunner = false;
|
|
|
|
|
|
if (definition.GetSingleOrDefaultMethodWithAttribute<NetworkDeserializeMethodAttribute>(out var unwrapAttribute, out var unwrapMethod)) {
|
|
|
if (wrapMethod == null) {
|
|
|
throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)}, but there is no method marked with {nameof(NetworkSerializeMethodAttribute)}: {unwrapMethod}");
|
|
|
}
|
|
|
|
|
|
int argsStart = 0;
|
|
|
|
|
|
try {
|
|
|
if (unwrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is<NetworkRunner>()) {
|
|
|
unwrapNeedsRunner = true;
|
|
|
argsStart = 1;
|
|
|
}
|
|
|
|
|
|
if (wrapNeedsRunner) {
|
|
|
unwrapMethod.ThrowIfParameterCountLessThan(1)
|
|
|
.ThrowIfParameter(0, typeof(NetworkRunner));
|
|
|
}
|
|
|
|
|
|
if (rawByteCount > 0) {
|
|
|
unwrapMethod.ThrowIfNotStatic()
|
|
|
.ThrowIfNotPublic()
|
|
|
.ThrowIfReturnType(typeof(int))
|
|
|
.ThrowIfParameterCount(argsStart + 2)
|
|
|
.ThrowIfParameter(argsStart + 0, typeof(byte*))
|
|
|
.ThrowIfParameter(argsStart + 1, type, isByReference: true);
|
|
|
unwrapByRef = true;
|
|
|
} else {
|
|
|
unwrapMethod.ThrowIfNotStatic()
|
|
|
.ThrowIfNotPublic()
|
|
|
.ThrowIfParameter(argsStart + 0, wrapMethod.ReturnType);
|
|
|
|
|
|
if (unwrapMethod.Parameters.Count == 2 + argsStart) {
|
|
|
unwrapMethod.ThrowIfReturnType(typeof(void))
|
|
|
.ThrowIfParameter(argsStart + 1, type, isByReference: true);
|
|
|
unwrapByRef = true;
|
|
|
} else {
|
|
|
unwrapMethod.ThrowIfParameterCount(argsStart + 1);
|
|
|
unwrapMethod.ThrowIfReturnType(type);
|
|
|
}
|
|
|
}
|
|
|
} catch (Exception ex) {
|
|
|
throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)} has an invalid signature", ex);
|
|
|
}
|
|
|
} else if (wrapMethod != null) {
|
|
|
throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)}, but there is no method marked with {nameof(NetworkDeserializeMethodAttribute)}: {wrapMethod}");
|
|
|
}
|
|
|
|
|
|
|
|
|
if (wrapMethod != null && unwrapMethod != null) {
|
|
|
result = new NetworkTypeWrapInfo() {
|
|
|
WrapNeedsRunner = wrapNeedsRunner,
|
|
|
UnwrapNeedsRunner = unwrapNeedsRunner,
|
|
|
UnwrapMethod = _module.ImportReference(unwrapMethod),
|
|
|
WrapMethod = _module.ImportReference(wrapMethod),
|
|
|
MaxByteCount = rawByteCount,
|
|
|
UnwrapByRef = unwrapByRef,
|
|
|
WrapperType = rawByteCount > 0 ? null : _module.ImportReference(wrapMethod.ReturnType),
|
|
|
WrapperTypeInfo = rawByteCount > 0 ? null : GetInfo(wrapMethod.ReturnType),
|
|
|
TargetType = _module.ImportReference(definition),
|
|
|
};
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (definition.BaseType == null) {
|
|
|
result = default;
|
|
|
return false;
|
|
|
} else {
|
|
|
return TryGetNetworkWrapperType(definition.BaseType, out result);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static int GetCapacity(ICustomAttributeProvider member, int defaultCapacity) {
|
|
|
if (member.TryGetAttribute<CapacityAttribute>(out var attr)) {
|
|
|
if (attr.TryGetAttributeArgument<int>(0, out var result)) {
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
return defaultCapacity;
|
|
|
}
|
|
|
|
|
|
public static bool TryGetCapacity(ICustomAttributeProvider member, out int capacity) {
|
|
|
if (member.TryGetAttribute<CapacityAttribute>(out var attr)) {
|
|
|
if (attr.TryGetAttributeArgument<int>(0, out var result)) {
|
|
|
capacity = result;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
capacity = 0;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
#region Assets/Photon/Fusion/CodeGen/NetworkTypeWrapInfo.cs
|
|
|
|
|
|
#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL
|
|
|
namespace Fusion.CodeGen {
|
|
|
using Mono.Cecil;
|
|
|
|
|
|
public class NetworkTypeWrapInfo {
|
|
|
public NetworkTypeInfo WrapperTypeInfo;
|
|
|
public TypeReference WrapperType;
|
|
|
public TypeReference TargetType;
|
|
|
public MethodReference WrapMethod;
|
|
|
public MethodReference UnwrapMethod;
|
|
|
public bool WrapNeedsRunner;
|
|
|
public bool UnwrapNeedsRunner;
|
|
|
public bool UnwrapByRef;
|
|
|
public int MaxByteCount;
|
|
|
public bool IsRaw => MaxByteCount > 0;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#endif
|