update mirror

This commit is contained in:
JohnCorby 2022-02-24 21:09:14 -08:00
parent 05ea4fa1c3
commit 7893c593a6
11 changed files with 330 additions and 399 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -18,8 +18,6 @@ namespace Mirror.Weaver
public static bool IsDerivedFrom(this TypeReference tr, Type baseClass)
{
if (tr == null)
return false;
TypeDefinition td = tr.Resolve();
if (!td.IsClass)
return false;
@ -142,6 +140,18 @@ namespace Mirror.Weaver
return module.ImportReference(reference);
}
// needed for NetworkBehaviour<T> support
// https://github.com/vis2k/Mirror/pull/3073/
public static FieldReference MakeHostInstanceGeneric(this FieldReference self)
{
var declaringType = new GenericInstanceType(self.DeclaringType);
foreach (var parameter in self.DeclaringType.GenericParameters)
{
declaringType.GenericArguments.Add(parameter);
}
return new FieldReference(self.Name, self.FieldType, declaringType);
}
// Given a field of a generic class such as Writer<T>.write,
// and a generic instance such as ArraySegment`int
// Creates a reference to the specialized method ArraySegment`int`.get_Count
@ -253,5 +263,75 @@ namespace Mirror.Weaver
}
return null;
}
// Takes generic arguments from child class and applies them to parent reference, if possible
// eg makes `Base<T>` in Child<int> : Base<int> have `int` instead of `T`
// Originally by James-Frowen under MIT
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
public static TypeReference ApplyGenericParameters(this TypeReference parentReference,
TypeReference childReference)
{
// If the parent is not generic, we got nothing to apply
if (!parentReference.IsGenericInstance)
return parentReference;
GenericInstanceType parentGeneric = (GenericInstanceType)parentReference;
// make new type so we can replace the args on it
// resolve it so we have non-generic instance (eg just instance with <T> instead of <int>)
// if we don't cecil will make it double generic (eg INVALID IL)
GenericInstanceType generic = new GenericInstanceType(parentReference.Resolve());
foreach (TypeReference arg in parentGeneric.GenericArguments)
generic.GenericArguments.Add(arg);
for (int i = 0; i < generic.GenericArguments.Count; i++)
{
// if arg is not generic
// eg List<int> would be int so not generic.
// But List<T> would be T so is generic
if (!generic.GenericArguments[i].IsGenericParameter)
continue;
// get the generic name, eg T
string name = generic.GenericArguments[i].Name;
// find what type T is, eg turn it into `int` if `List<int>`
TypeReference arg = FindMatchingGenericArgument(childReference, name);
// import just to be safe
TypeReference imported = parentReference.Module.ImportReference(arg);
// set arg on generic, parent ref will be Base<int> instead of just Base<T>
generic.GenericArguments[i] = imported;
}
return generic;
}
// Finds the type reference for a generic parameter with the provided name in the child reference
// Originally by James-Frowen under MIT
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
static TypeReference FindMatchingGenericArgument(TypeReference childReference, string paramName)
{
TypeDefinition def = childReference.Resolve();
// child class must be generic if we are in this part of the code
// eg Child<T> : Base<T> <--- child must have generic if Base has T
// vs Child : Base<int> <--- wont be here if Base has int (we check if T exists before calling this)
if (!def.HasGenericParameters)
throw new InvalidOperationException(
"Base class had generic parameters, but could not find them in child class");
// go through parameters in child class, and find the generic that matches the name
for (int i = 0; i < def.GenericParameters.Count; i++)
{
GenericParameter param = def.GenericParameters[i];
if (param.Name == paramName)
{
GenericInstanceType generic = (GenericInstanceType)childReference;
// return generic arg with same index
return generic.GenericArguments[i];
}
}
// this should never happen, if it does it means that this code is bugged
throw new InvalidOperationException("Did not find matching generic");
}
}
}

View File

@ -68,16 +68,6 @@ namespace Mirror.Weaver
return false;
}
/*
if (netBehaviourSubclass.HasGenericParameters)
{
Log.Error($"{netBehaviourSubclass.Name} cannot have generic parameters", netBehaviourSubclass);
WeavingFailed = true;
// originally Process returned true in every case, except if already processed.
// maybe return false here in the future.
return true;
}
*/
MarkAsProcessed(netBehaviourSubclass);
// deconstruct tuple and set fields
@ -439,8 +429,13 @@ namespace Mirror.Weaver
worker.Emit(OpCodes.Ldarg_2);
worker.Emit(OpCodes.Brfalse, initialStateLabel);
foreach (FieldDefinition syncVar in syncVars)
foreach (FieldDefinition syncVarDef in syncVars)
{
FieldReference syncVar = syncVarDef;
if (netBehaviourSubclass.HasGenericParameters)
{
syncVar = syncVarDef.MakeHostInstanceGeneric();
}
// Generates a writer call for each sync variable
// writer
worker.Emit(OpCodes.Ldarg_1);
@ -483,8 +478,14 @@ namespace Mirror.Weaver
// start at number of syncvars in parent
int dirtyBit = syncVarAccessLists.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName);
foreach (FieldDefinition syncVar in syncVars)
foreach (FieldDefinition syncVarDef in syncVars)
{
FieldReference syncVar = syncVarDef;
if (netBehaviourSubclass.HasGenericParameters)
{
syncVar = syncVarDef.MakeHostInstanceGeneric();
}
Instruction varLabel = worker.Create(OpCodes.Nop);
// Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL)
@ -534,303 +535,98 @@ namespace Mirror.Weaver
netBehaviourSubclass.Methods.Add(serialize);
}
void DeserializeField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, ref bool WeavingFailed)
void DeserializeField(FieldDefinition syncVar, ILProcessor worker, ref bool WeavingFailed)
{
// check for Hook function
MethodDefinition hookMethod = syncVarAttributeProcessor.GetHookMethod(netBehaviourSubclass, syncVar, ref WeavingFailed);
// put 'this.' onto stack for 'this.syncvar' below
worker.Append(worker.Create(OpCodes.Ldarg_0));
if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
// push 'ref T this.field'
worker.Emit(OpCodes.Ldarg_0);
// if the netbehaviour class is generic, we need to make the field reference generic as well for correct IL
if (netBehaviourSubclass.HasGenericParameters)
{
DeserializeNetworkBehaviourField(syncVar, worker, deserialize, hookMethod, ref WeavingFailed);
}
else if (syncVar.FieldType.IsNetworkIdentityField())
{
DeserializeNetworkIdentityField(syncVar, worker, deserialize, hookMethod, ref WeavingFailed);
worker.Emit(OpCodes.Ldflda, syncVar.MakeHostInstanceGeneric());
}
else
{
DeserializeNormalField(syncVar, worker, deserialize, hookMethod, ref WeavingFailed);
worker.Emit(OpCodes.Ldflda, syncVar);
}
}
/// [SyncVar] GameObject/NetworkIdentity?
void DeserializeNetworkIdentityField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, MethodDefinition hookMethod, ref bool WeavingFailed)
{
/*
Generates code like:
uint oldNetId = ___qNetId;
// returns GetSyncVarGameObject(___qNetId)
GameObject oldSyncVar = syncvar.getter;
___qNetId = reader.ReadPackedUInt32();
if (!SyncVarEqual(oldNetId, ref ___goNetId))
{
// getter returns GetSyncVarGameObject(___qNetId)
OnSetQ(oldSyncVar, syncvar.getter);
}
*/
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = syncVarNetIds[syncVar];
// uint oldNetId = ___qNetId;
VariableDefinition oldNetId = new VariableDefinition(weaverTypes.Import<uint>());
deserialize.Body.Variables.Add(oldNetId);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdField);
worker.Emit(OpCodes.Stloc, oldNetId);
// GameObject/NetworkIdentity oldSyncVar = syncvar.getter;
VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType);
deserialize.Body.Variables.Add(oldSyncVar);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, syncVar);
worker.Emit(OpCodes.Stloc, oldSyncVar);
// read id and store in netId field BEFORE calling the hook
// -> this makes way more sense. by definition, the hook is
// supposed to be called after it was changed. not before.
// -> setting it BEFORE calling the hook fixes the following bug:
// https://github.com/vis2k/Mirror/issues/1151 in host mode
// where the value during the Hook call would call Cmds on
// the host server, and they would all happen and compare
// values BEFORE the hook even returned and hence BEFORE the
// actual value was even set.
// put 'this.' onto stack for 'this.netId' below
worker.Emit(OpCodes.Ldarg_0);
// reader. for 'reader.Read()' below
worker.Emit(OpCodes.Ldarg_1);
// Read()
worker.Emit(OpCodes.Call, readers.GetReadFunc(weaverTypes.Import<uint>(), ref WeavingFailed));
// netId
worker.Emit(OpCodes.Stfld, netIdField);
// hook? then push 'new Action<T,T>(Hook)' onto stack
MethodDefinition hookMethod = syncVarAttributeProcessor.GetHookMethod(netBehaviourSubclass, syncVar, ref WeavingFailed);
if (hookMethod != null)
{
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
// because we send/receive the netID, not the GameObject/NetworkIdentity
// but only if SyncVar changed. otherwise a client would
// get hook calls for all initial values, even if they
// didn't change from the default values on the client.
// see also: https://github.com/vis2k/Mirror/issues/1278
// IMPORTANT: for GameObjects/NetworkIdentities we usually
// use SyncVarGameObjectEqual to compare equality.
// in this case however, we can just use
// SyncVarEqual with the two uint netIds.
// => this is easier weaver code because we don't
// have to get the GameObject/NetworkIdentity
// from the uint netId
// => this is faster because we void one
// GetComponent call for GameObjects to get
// their NetworkIdentity when comparing.
// Generates: if (!SyncVarEqual);
Instruction syncVarEqualLabel = worker.Create(OpCodes.Nop);
// NOTE: static function. don't Emit Ldarg_0 aka 'this'.
// 'oldNetId'
worker.Emit(OpCodes.Ldloc, oldNetId);
// 'ref this.__netId'
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdField);
// call the function
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(netIdField.FieldType);
worker.Emit(OpCodes.Call, syncVarEqualGm);
worker.Emit(OpCodes.Brtrue, syncVarEqualLabel);
// call the hook
// Generates: OnValueChanged(oldValue, this.syncVar);
syncVarAttributeProcessor.WriteCallHookMethodUsingField(worker, hookMethod, oldSyncVar, syncVar, ref WeavingFailed);
// Generates: end if (!SyncVarEqual);
worker.Append(syncVarEqualLabel);
syncVarAttributeProcessor.GenerateNewActionFromHookMethod(syncVar, worker, hookMethod);
}
}
// [SyncVar] NetworkBehaviour
void DeserializeNetworkBehaviourField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, MethodDefinition hookMethod, ref bool WeavingFailed)
{
/*
Generates code like:
uint oldNetId = ___qNetId.netId;
byte oldCompIndex = ___qNetId.componentIndex;
T oldSyncVar = syncvar.getter;
___qNetId.netId = reader.ReadPackedUInt32();
___qNetId.componentIndex = reader.ReadByte();
if (!SyncVarEqual(oldNetId, ref ___goNetId))
{
// getter returns GetSyncVarGameObject(___qNetId)
OnSetQ(oldSyncVar, syncvar.getter);
}
*/
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = syncVarNetIds[syncVar];
// uint oldNetId = ___qNetId;
VariableDefinition oldNetId = new VariableDefinition(weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
deserialize.Body.Variables.Add(oldNetId);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdField);
worker.Emit(OpCodes.Stloc, oldNetId);
// GameObject/NetworkIdentity oldSyncVar = syncvar.getter;
VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType);
deserialize.Body.Variables.Add(oldSyncVar);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, syncVar);
worker.Emit(OpCodes.Stloc, oldSyncVar);
// read id and store in netId field BEFORE calling the hook
// -> this makes way more sense. by definition, the hook is
// supposed to be called after it was changed. not before.
// -> setting it BEFORE calling the hook fixes the following bug:
// https://github.com/vis2k/Mirror/issues/1151 in host mode
// where the value during the Hook call would call Cmds on
// the host server, and they would all happen and compare
// values BEFORE the hook even returned and hence BEFORE the
// actual value was even set.
// put 'this.' onto stack for 'this.netId' below
worker.Emit(OpCodes.Ldarg_0);
// reader. for 'reader.Read()' below
worker.Emit(OpCodes.Ldarg_1);
// Read()
worker.Emit(OpCodes.Call, readers.GetReadFunc(weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>(), ref WeavingFailed));
// netId
worker.Emit(OpCodes.Stfld, netIdField);
if (hookMethod != null)
// otherwise push 'null' as hook
else
{
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
// because we send/receive the netID, not the GameObject/NetworkIdentity
// but only if SyncVar changed. otherwise a client would
// get hook calls for all initial values, even if they
// didn't change from the default values on the client.
// see also: https://github.com/vis2k/Mirror/issues/1278
worker.Emit(OpCodes.Ldnull);
}
// IMPORTANT: for GameObjects/NetworkIdentities we usually
// use SyncVarGameObjectEqual to compare equality.
// in this case however, we can just use
// SyncVarEqual with the two uint netIds.
// => this is easier weaver code because we don't
// have to get the GameObject/NetworkIdentity
// from the uint netId
// => this is faster because we void one
// GetComponent call for GameObjects to get
// their NetworkIdentity when comparing.
// call GeneratedSyncVarDeserialize<T>.
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
// passing netId too for persistence.
if (syncVar.FieldType.Is<UnityEngine.GameObject>())
{
// reader
worker.Emit(OpCodes.Ldarg_1);
// Generates: if (!SyncVarEqual);
Instruction syncVarEqualLabel = worker.Create(OpCodes.Nop);
// NOTE: static function. don't Emit Ldarg_0 aka 'this'.
// 'oldNetId'
worker.Emit(OpCodes.Ldloc, oldNetId);
// 'ref this.__netId'
// GameObject setter needs one more parameter: netId field ref
FieldDefinition netIdField = syncVarNetIds[syncVar];
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdField);
// call the function
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(netIdField.FieldType);
worker.Emit(OpCodes.Call, syncVarEqualGm);
worker.Emit(OpCodes.Brtrue, syncVarEqualLabel);
// call the hook
// Generates: OnValueChanged(oldValue, this.syncVar);
syncVarAttributeProcessor.WriteCallHookMethodUsingField(worker, hookMethod, oldSyncVar, syncVar, ref WeavingFailed);
// Generates: end if (!SyncVarEqual);
worker.Append(syncVarEqualLabel);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarDeserialize_GameObject);
}
}
else if (syncVar.FieldType.Is<NetworkIdentity>())
{
// reader
worker.Emit(OpCodes.Ldarg_1);
// [SyncVar] int/float/struct/etc.?
void DeserializeNormalField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize, MethodDefinition hookMethod, ref bool WeavingFailed)
{
/*
Generates code like:
// for hook
int oldValue = a;
Networka = reader.ReadPackedInt32();
if (!SyncVarEqual(oldValue, ref a))
// NetworkIdentity deserialize needs one more parameter: netId field ref
FieldDefinition netIdField = syncVarNetIds[syncVar];
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdField);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarDeserialize_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 (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
// reader
worker.Emit(OpCodes.Ldarg_1);
// NetworkIdentity deserialize needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
FieldDefinition netIdField = syncVarNetIds[syncVar];
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdField);
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
MethodReference getFunc = weaverTypes.generatedSyncVarDeserialize_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, syncVar.FieldType);
worker.Emit(OpCodes.Call, getFunc);
}
else
{
// T value = reader.ReadT();
// this is still in IL because otherwise weaver generated
// readers/writers don't seem to work in tests.
// besides, this also avoids reader.Read<T> overhead.
MethodReference readFunc = readers.GetReadFunc(syncVar.FieldType, ref WeavingFailed);
if (readFunc == null)
{
OnSetA(oldValue, Networka);
Log.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar);
WeavingFailed = true;
return;
}
*/
// reader. for 'reader.Read()' below
worker.Emit(OpCodes.Ldarg_1);
// reader.Read()
worker.Emit(OpCodes.Call, readFunc);
MethodReference readFunc = readers.GetReadFunc(syncVar.FieldType, ref WeavingFailed);
if (readFunc == null)
{
Log.Error($"{syncVar.Name} has unsupported type. Use a supported Mirror type instead", syncVar);
WeavingFailed = true;
return;
}
// T oldValue = value;
VariableDefinition oldValue = new VariableDefinition(syncVar.FieldType);
deserialize.Body.Variables.Add(oldValue);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
serWorker.Append(serWorker.Create(OpCodes.Stloc, oldValue));
// read value and store in syncvar BEFORE calling the hook
// -> this makes way more sense. by definition, the hook is
// supposed to be called after it was changed. not before.
// -> setting it BEFORE calling the hook fixes the following bug:
// https://github.com/vis2k/Mirror/issues/1151 in host mode
// where the value during the Hook call would call Cmds on
// the host server, and they would all happen and compare
// values BEFORE the hook even returned and hence BEFORE the
// actual value was even set.
// put 'this.' onto stack for 'this.syncvar' below
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
// reader. for 'reader.Read()' below
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
// reader.Read()
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
// syncvar
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
if (hookMethod != null)
{
// call hook
// but only if SyncVar changed. otherwise a client would
// get hook calls for all initial values, even if they
// didn't change from the default values on the client.
// see also: https://github.com/vis2k/Mirror/issues/1278
// Generates: if (!SyncVarEqual);
Instruction syncVarEqualLabel = serWorker.Create(OpCodes.Nop);
// NOTE: static function. don't Emit Ldarg_0 aka 'this'.
// 'oldValue'
serWorker.Append(serWorker.Create(OpCodes.Ldloc, oldValue));
// 'ref this.syncVar'
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
// call the function
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(weaverTypes.syncVarEqualReference);
syncVarEqualGm.GenericArguments.Add(syncVar.FieldType);
serWorker.Append(serWorker.Create(OpCodes.Call, syncVarEqualGm));
serWorker.Append(serWorker.Create(OpCodes.Brtrue, syncVarEqualLabel));
// call the hook
// Generates: OnValueChanged(oldValue, this.syncVar);
syncVarAttributeProcessor.WriteCallHookMethodUsingField(serWorker, hookMethod, oldValue, syncVar, ref WeavingFailed);
// Generates: end if (!SyncVarEqual);
serWorker.Append(syncVarEqualLabel);
// make generic version of GeneratedSyncVarDeserialize<T>
MethodReference generic = weaverTypes.generatedSyncVarDeserialize.MakeGeneric(assembly.MainModule, syncVar.FieldType);
worker.Emit(OpCodes.Call, generic);
}
}
@ -878,7 +674,7 @@ namespace Mirror.Weaver
foreach (FieldDefinition syncVar in syncVars)
{
DeserializeField(syncVar, serWorker, serialize, ref WeavingFailed);
DeserializeField(syncVar, serWorker, ref WeavingFailed);
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
@ -904,7 +700,7 @@ namespace Mirror.Weaver
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
DeserializeField(syncVar, serWorker, serialize, ref WeavingFailed);
DeserializeField(syncVar, serWorker, ref WeavingFailed);
serWorker.Append(varLabel);
dirtyBit += 1;
@ -1036,6 +832,14 @@ namespace Mirror.Weaver
// validate parameters for a remote function call like Rpc/Cmd
bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam, ref bool WeavingFailed)
{
// need to check this before any type lookups since those will fail since generic types don't resolve
if (param.ParameterType.IsGenericParameter)
{
Log.Error($"{method.Name} cannot have generic parameters", method);
WeavingFailed = true;
return false;
}
bool isNetworkConnection = param.ParameterType.Is<NetworkConnection>();
bool isSenderConnection = IsSenderConnection(param, callType);

View File

@ -16,6 +16,12 @@ namespace Mirror.Weaver
foreach (FieldDefinition fd in td.Fields)
{
if (fd.FieldType.IsGenericParameter)
{
// can't call .Resolve on generic ones
continue;
}
if (fd.FieldType.Resolve().IsDerivedFrom<SyncObject>())
{
if (fd.IsStatic)

View File

@ -46,6 +46,64 @@ namespace Mirror.Weaver
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
}
// push hook from GetHookMethod() onto the stack as a new Action<T,T>.
// allows for reuse without handling static/virtual cases every time.
public void GenerateNewActionFromHookMethod(FieldDefinition syncVar, ILProcessor worker, MethodDefinition hookMethod)
{
// 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)
// we support static hook sand instance hooks.
if (hookMethod.IsStatic)
{
// for static hooks, we need to push 'null' first.
// we can't just push nothing.
// stack would get out of balance because we already pushed
// other stuff above.
worker.Emit(OpCodes.Ldnull);
}
else
{
// for instance hooks, we need to push 'this.' first.
worker.Emit(OpCodes.Ldarg_0);
}
MethodReference hookMethodReference;
// if the network behaviour class is generic, we need to make the method reference generic for correct IL
if (hookMethod.DeclaringType.HasGenericParameters)
{
hookMethodReference = hookMethod.MakeHostInstanceGeneric(hookMethod.Module, hookMethod.DeclaringType.MakeGenericInstanceType(hookMethod.DeclaringType.GenericParameters.ToArray()));
}
else
{
hookMethodReference = hookMethod;
}
// we support regular and virtual hook functions.
if (hookMethod.IsVirtual)
{
// for virtual / overwritten hooks, we need different IL.
// this is from simply testing Action = VirtualHook; in C#.
worker.Emit(OpCodes.Dup);
worker.Emit(OpCodes.Ldvirtftn, hookMethodReference);
}
else
{
worker.Emit(OpCodes.Ldftn, hookMethodReference);
}
// 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(syncVar.FieldType, syncVar.FieldType);
worker.Emit(OpCodes.Newobj, weaverTypes.ActionT_T.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
}
MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
{
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
@ -96,6 +154,29 @@ namespace Mirror.Weaver
ILProcessor worker = get.Body.GetILProcessor();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// [SyncVar] GameObject?
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
@ -103,9 +184,9 @@ namespace Mirror.Weaver
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fr);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
worker.Emit(OpCodes.Ret);
}
@ -116,9 +197,9 @@ namespace Mirror.Weaver
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fr);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Ret);
}
@ -128,9 +209,9 @@ namespace Mirror.Weaver
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netFieldId);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fr);
MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
worker.Emit(OpCodes.Ret);
@ -139,7 +220,7 @@ namespace Mirror.Weaver
else
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fd);
worker.Emit(OpCodes.Ldfld, fr);
worker.Emit(OpCodes.Ret);
}
@ -168,6 +249,28 @@ namespace Mirror.Weaver
weaverTypes.Import(typeof(void)));
ILProcessor worker = set.Body.GetILProcessor();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// if (!SyncVarEqual(value, ref playerData))
Instruction endOfMethod = worker.Create(OpCodes.Nop);
@ -194,37 +297,22 @@ namespace Mirror.Weaver
// push 'ref T this.field'
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fd);
worker.Emit(OpCodes.Ldflda, fr);
// push the dirty bit for this SyncVar
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
// hook?
// hook? then push 'new Action<T,T>(Hook)' onto stack
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));
GenerateNewActionFromHookMethod(fd, worker, hookMethod);
}
// otherwise push 'null' as hook
else
{
worker.Emit(OpCodes.Ldnull);
}
// pass 'null' as hook
else worker.Emit(OpCodes.Ldnull);
// call GeneratedSyncVarSetter<T>.
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
@ -233,14 +321,14 @@ namespace Mirror.Weaver
{
// GameObject setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject);
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
}
// TODO this only uses the persistent netId for types DERIVED FROM NB.
@ -251,7 +339,7 @@ namespace Mirror.Weaver
// NetworkIdentity setter needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netFieldId);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
@ -283,16 +371,18 @@ namespace Mirror.Weaver
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Private,
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
else if (fd.FieldType.IsNetworkIdentityField())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Private,
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<uint>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
@ -345,6 +435,13 @@ namespace Mirror.Weaver
continue;
}
if (fd.FieldType.IsGenericParameter)
{
Log.Error($"{fd.Name} has generic type. Generic SyncVars are not supported", fd);
WeavingFailed = true;
continue;
}
if (fd.FieldType.IsArray)
{
Log.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
@ -382,72 +479,5 @@ namespace Mirror.Weaver
return (syncVars, syncVarNetIds);
}
public void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue, ref bool WeavingFailed)
{
if (newValue == null)
{
Log.Error("NewValue field was null when writing SyncVar hook");
WeavingFailed = true;
}
WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
}
void WriteCallHookMethod(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
{
WriteStartFunctionCall();
// write args
WriteOldValue();
WriteNewValue();
WriteEndFunctionCall();
// *** Local functions used to write OpCodes ***
// Local functions have access to function variables, no need to pass in args
void WriteOldValue()
{
worker.Emit(OpCodes.Ldloc, oldValue);
}
void WriteNewValue()
{
// write arg1 or this.field
if (newValue == null)
{
worker.Emit(OpCodes.Ldarg_1);
}
else
{
// this.
worker.Emit(OpCodes.Ldarg_0);
// syncvar.get
worker.Emit(OpCodes.Ldfld, newValue);
}
}
// Writes this before method if it is not static
void WriteStartFunctionCall()
{
// don't add this (Ldarg_0) if method is static
if (!hookMethod.IsStatic)
{
// this before method call
// e.g. this.onValueChanged
worker.Emit(OpCodes.Ldarg_0);
}
}
// Calls method
void WriteEndFunctionCall()
{
// only use Callvirt when not static
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
worker.Emit(opcode, hookMethod);
}
}
}
}

View File

@ -48,16 +48,21 @@ namespace Mirror.Weaver
{
return null;
}
foreach (MethodDefinition methodRef in tr.Resolve().Methods)
foreach (MethodDefinition methodDef in tr.Resolve().Methods)
{
if (methodRef.Name == name)
if (methodDef.Name == name)
{
MethodReference methodRef = methodDef;
if (tr.IsGenericInstance)
{
methodRef = methodRef.MakeHostInstanceGeneric(tr.Module, (GenericInstanceType)tr);
}
return assembly.MainModule.ImportReference(methodRef);
}
}
// Could not find the method in this class, try the parent
return TryResolveMethodInParents(tr.Resolve().BaseType, assembly, name);
return TryResolveMethodInParents(tr.Resolve().BaseType.ApplyGenericParameters(tr), assembly, name);
}
public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)

View File

@ -35,7 +35,10 @@ namespace Mirror.Weaver
public MethodReference generatedSyncVarSetter_GameObject;
public MethodReference generatedSyncVarSetter_NetworkIdentity;
public MethodReference generatedSyncVarSetter_NetworkBehaviour_T;
public MethodReference syncVarEqualReference;
public MethodReference generatedSyncVarDeserialize;
public MethodReference generatedSyncVarDeserialize_GameObject;
public MethodReference generatedSyncVarDeserialize_NetworkIdentity;
public MethodReference generatedSyncVarDeserialize_NetworkBehaviour_T;
public MethodReference getSyncVarGameObjectReference;
public MethodReference getSyncVarNetworkIdentityReference;
public MethodReference getSyncVarNetworkBehaviourReference;
@ -102,7 +105,10 @@ namespace Mirror.Weaver
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);
generatedSyncVarDeserialize_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_GameObject", ref WeavingFailed);
generatedSyncVarDeserialize = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkIdentity", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkBehaviour", ref WeavingFailed);
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);