Weaver: move [SyncVar] generated setter to C# (#3070)

* WeaverSyncVarSetter<T>

* resolve

* no ref

* rename

* simple types without hook

* hook WIP

* stll need 'new Action' type

* pass hook

* ilnine

* remove now unused types

* remove unused

* comment

* no default

* cases

* GeneratedSyncVarSetter_GameObject/NetworkIdentity

* resolve

* GO/NI setters

* use the right equals

* NB setter

* comment
This commit is contained in:
vis2k 2022-01-26 18:40:56 +08:00 committed by GitHub
parent 48df9ec287
commit 5369b8f47c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 213 additions and 127 deletions

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
@ -148,6 +150,15 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina
return get;
}
// for [SyncVar] health, weaver generates
//
// NetworkHealth
// {
// get => health;
// set => GeneratedSyncVarSetter(...)
// }
//
// the setter used to be manually IL generated, but we moved it to C# :)
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, ref bool WeavingFailed)
{
//Create the set method
@ -164,135 +175,94 @@ public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition
// NOTE: SyncVar...Equal functions are static.
// don't Emit Ldarg_0 aka 'this'.
// new value to set
worker.Emit(OpCodes.Ldarg_1);
// call WeaverSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged = null)
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: ldarg.0
// IL_0003: ldflda int32 Mirror.Examples.Tanks.Tank::health
// IL_0008: ldc.i4.1
// IL_0009: conv.i8
// IL_000a: ldnull
// IL_000b: call instance void [Mirror]Mirror.NetworkBehaviour::GeneratedSyncVarSetter<int32>(!!0, !!0&, uint64, class [netstandard]System.Action`2<!!0, !!0>)
// IL_0010: ret
//
// TODO GameObject/NetworkBehaviour special cases
// reference to field to set
// make generic version of SetSyncVar with field type
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// reference to netId Field to set
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.syncVarGameObjectEqualReference);
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.syncVarNetworkIdentityEqualReference);
}
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
// reference to netId Field to set
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
MethodReference getFunc = weaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
}
else
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
worker.Emit(OpCodes.Call, syncVarEqualGm);
}
worker.Emit(OpCodes.Brtrue, endOfMethod);
// T oldValue = value;
// TODO for GO/NI we need to backup the netId don't we?
VariableDefinition oldValue = new VariableDefinition(fd.FieldType);
set.Body.Variables.Add(oldValue);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Stloc, oldValue);
// this
// 'this.' for the call
worker.Emit(OpCodes.Ldarg_0);
// new value to set
// first push 'value'
worker.Emit(OpCodes.Ldarg_1);
// reference to field to set
// push 'ref T this.field'
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
// dirty bit
// 8 byte integer aka long
// push the dirty bit for this SyncVar
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
// hook?
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
if (hookMethod != null)
{
// IL_000a: ldarg.0
// IL_000b: ldftn instance void Mirror.Examples.Tanks.Tank::ExampleHook(int32, int32)
// IL_0011: newobj instance void class [netstandard]System.Action`2<int32, int32>::.ctor(object, native int)
// this.
worker.Emit(OpCodes.Ldarg_0);
// the function
worker.Emit(OpCodes.Ldftn, hookMethod);
// call 'new Action<T,T>()' constructor to convert the function to an action
// we need to make an instance of the generic Action<T,T>.
//
// TODO this allocates a new 'Action' for every SyncVar hook call.
// we should allocate it once and store it somewhere in the future.
// hooks are only called on the client though, so it's not too bad for now.
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
GenericInstanceType genericInstance = actionRef.MakeGenericInstanceType(fd.FieldType, fd.FieldType);
worker.Emit(OpCodes.Newobj, weaverTypes.ActionT_T.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
}
// pass 'null' as hook
else worker.Emit(OpCodes.Ldnull);
// call GeneratedSyncVarSetter<T>.
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
// passing netId too for persistence.
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// reference to netId Field to set
// GameObject setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarGameObjectReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject);
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// reference to netId Field to set
// NetworkIdentity setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
}
// TODO this only uses the persistent netId for types DERIVED FROM NB.
// not if the type is just 'NetworkBehaviour'.
// this is what original implementation did too. fix it after.
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
// reference to netId Field to set
// NetworkIdentity setter needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
MethodReference getFunc = weaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
}
else
{
// make generic version of SetSyncVar with field type
GenericInstanceMethod gm = new GenericInstanceMethod(weaverTypes.setSyncVarReference);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
worker.Emit(OpCodes.Call, gm);
}
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
if (hookMethod != null)
{
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
Instruction label = worker.Create(OpCodes.Nop);
worker.Emit(OpCodes.Call, weaverTypes.NetworkServerGetLocalClientActive);
worker.Emit(OpCodes.Brfalse, label);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarHookGuard);
worker.Emit(OpCodes.Brtrue, label);
// setSyncVarHookGuard(dirtyBit, true);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I4_1);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
// call hook (oldValue, newValue)
// Generates: OnValueChanged(oldValue, value);
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
// setSyncVarHookGuard(dirtyBit, false);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
worker.Emit(OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, weaverTypes.setSyncVarHookGuard);
worker.Append(label);
// make generic version of GeneratedSyncVarSetter<T>
MethodReference generic = weaverTypes.generatedSyncVarSetter.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, generic);
}
worker.Append(endOfMethod);
@ -415,11 +385,6 @@ public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<Fie
return (syncVars, syncVarNetIds);
}
public void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
{
WriteCallHookMethod(worker, hookMethod, oldValue, null);
}
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
{
if (newValue == null)

View File

@ -19,7 +19,6 @@ public class WeaverTypes
public MethodReference RemoteCallDelegateConstructor;
public MethodReference NetworkServerGetActive;
public MethodReference NetworkServerGetLocalClientActive;
public MethodReference NetworkClientGetActive;
// custom attribute types
@ -28,19 +27,17 @@ public class WeaverTypes
// array segment
public MethodReference ArraySegmentConstructorReference;
// Action<T,T> for SyncVar Hooks
public MethodReference ActionT_T;
// syncvar
public MethodReference generatedSyncVarSetter;
public MethodReference generatedSyncVarSetter_GameObject;
public MethodReference generatedSyncVarSetter_NetworkIdentity;
public MethodReference generatedSyncVarSetter_NetworkBehaviour_T;
public MethodReference syncVarEqualReference;
public MethodReference syncVarNetworkIdentityEqualReference;
public MethodReference syncVarGameObjectEqualReference;
public MethodReference setSyncVarReference;
public MethodReference setSyncVarHookGuard;
public MethodReference getSyncVarHookGuard;
public MethodReference setSyncVarGameObjectReference;
public MethodReference getSyncVarGameObjectReference;
public MethodReference setSyncVarNetworkIdentityReference;
public MethodReference getSyncVarNetworkIdentityReference;
public MethodReference syncVarNetworkBehaviourEqualReference;
public MethodReference setSyncVarNetworkBehaviourReference;
public MethodReference getSyncVarNetworkBehaviourReference;
public MethodReference registerCommandReference;
public MethodReference registerRpcReference;
@ -72,9 +69,11 @@ public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFail
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ActionType = Import(typeof(Action<,>));
ActionT_T = Resolvers.ResolveMethod(ActionType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localClientActive", ref WeavingFailed);
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
@ -98,19 +97,15 @@ public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFail
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed);
syncVarNetworkIdentityEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkIdentityEqual", ref WeavingFailed);
syncVarGameObjectEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarGameObjectEqual", ref WeavingFailed);
setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVar", ref WeavingFailed);
setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarHookGuard", ref WeavingFailed);
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarHookGuard", ref WeavingFailed);
generatedSyncVarSetter = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter", ref WeavingFailed);
generatedSyncVarSetter_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_GameObject", ref WeavingFailed);
generatedSyncVarSetter_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkIdentity", ref WeavingFailed);
generatedSyncVarSetter_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkBehaviour", ref WeavingFailed);
syncVarEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarEqual", ref WeavingFailed);
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarGameObject", ref WeavingFailed);
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkIdentity", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SyncVarNetworkBehaviourEqual", ref WeavingFailed);
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SetSyncVarNetworkBehaviour", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Mirror
@ -326,6 +327,131 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull
conn.Send(message, channelId);
}
// move the [SyncVar] generated property's .set into C# to avoid much IL
//
// public int health = 42;
//
// public int Networkhealth
// {
// get
// {
// return health;
// }
// [param: In]
// set
// {
// if (!NetworkBehaviour.SyncVarEqual(value, ref health))
// {
// int oldValue = health;
// SetSyncVar(value, ref health, 1uL);
// if (NetworkServer.localClientActive && !GetSyncVarHookGuard(1uL))
// {
// SetSyncVarHookGuard(1uL, value: true);
// OnChanged(oldValue, value);
// SetSyncVarHookGuard(1uL, value: false);
// }
// }
// }
// }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GeneratedSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged)
{
if (!SyncVarEqual(value, ref field))
{
T oldValue = field;
SetSyncVar(value, ref field, dirtyBit);
// call hook (if any)
if (OnChanged != null)
{
// we use hook guard to protect against deadlock where hook
// changes syncvar, calling hook again.
if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
{
SetSyncVarHookGuard(dirtyBit, true);
OnChanged(oldValue, value);
SetSyncVarHookGuard(dirtyBit, false);
}
}
}
}
// GameObject needs custom handling for persistence via netId.
// has one extra parameter.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GeneratedSyncVarSetter_GameObject(GameObject value, ref GameObject field, ulong dirtyBit, Action<GameObject, GameObject> OnChanged, ref uint netIdField)
{
if (!SyncVarGameObjectEqual(value, netIdField))
{
GameObject oldValue = field;
SetSyncVarGameObject(value, ref field, dirtyBit, ref netIdField);
// call hook (if any)
if (OnChanged != null)
{
// we use hook guard to protect against deadlock where hook
// changes syncvar, calling hook again.
if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
{
SetSyncVarHookGuard(dirtyBit, true);
OnChanged(oldValue, value);
SetSyncVarHookGuard(dirtyBit, false);
}
}
}
}
// NetworkIdentity needs custom handling for persistence via netId.
// has one extra parameter.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GeneratedSyncVarSetter_NetworkIdentity(NetworkIdentity value, ref NetworkIdentity field, ulong dirtyBit, Action<NetworkIdentity, NetworkIdentity> OnChanged, ref uint netIdField)
{
if (!SyncVarNetworkIdentityEqual(value, netIdField))
{
NetworkIdentity oldValue = field;
SetSyncVarNetworkIdentity(value, ref field, dirtyBit, ref netIdField);
// call hook (if any)
if (OnChanged != null)
{
// we use hook guard to protect against deadlock where hook
// changes syncvar, calling hook again.
if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
{
SetSyncVarHookGuard(dirtyBit, true);
OnChanged(oldValue, value);
SetSyncVarHookGuard(dirtyBit, false);
}
}
}
}
// NetworkBehaviour needs custom handling for persistence via netId.
// has one extra parameter.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GeneratedSyncVarSetter_NetworkBehaviour<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged, ref NetworkBehaviourSyncVar netIdField)
where T : NetworkBehaviour
{
if (!SyncVarNetworkBehaviourEqual(value, netIdField))
{
T oldValue = field;
SetSyncVarNetworkBehaviour(value, ref field, dirtyBit, ref netIdField);
// call hook (if any)
if (OnChanged != null)
{
// we use hook guard to protect against deadlock where hook
// changes syncvar, calling hook again.
if (NetworkServer.localClientActive && !GetSyncVarHookGuard(dirtyBit))
{
SetSyncVarHookGuard(dirtyBit, true);
OnChanged(oldValue, value);
SetSyncVarHookGuard(dirtyBit, false);
}
}
}
}
// helper function for [SyncVar] GameObjects.
// needs to be public so that tests & NetworkBehaviours from other
// assemblies both find it