You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7339 lines
270 KiB
C#

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#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