123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- using System.Collections.Generic;
- using System.Linq;
- using Mono.CecilX;
- using Mono.CecilX.Cil;
- namespace Mirror.Weaver
- {
- /// <summary>
- /// Processes [SyncVar] in NetworkBehaviour
- /// </summary>
- public static class SyncVarProcessor
- {
- // ulong = 64 bytes
- const int SyncVarLimit = 64;
- static string HookParameterMessage(string hookName, TypeReference ValueType)
- => string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType);
- // Get hook method if any
- public static MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar)
- {
- CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
- if (syncVarAttr == null)
- return null;
- string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
- if (hookFunctionName == null)
- return null;
- return FindHookMethod(td, syncVar, hookFunctionName);
- }
- static MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName)
- {
- List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
- List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
- if (methodsWith2Param.Count == 0)
- {
- Weaver.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
- $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
- syncVar);
- return null;
- }
- foreach (MethodDefinition method in methodsWith2Param)
- {
- if (MatchesParameters(syncVar, method))
- {
- return method;
- }
- }
- Weaver.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
- $"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
- syncVar);
- return null;
- }
- static bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
- {
- // matches void onValueChange(T oldValue, T newValue)
- return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
- method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
- }
- public static MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
- {
- //Create the get method
- MethodDefinition get = new MethodDefinition(
- "get_Network" + originalName, MethodAttributes.Public |
- MethodAttributes.SpecialName |
- MethodAttributes.HideBySig,
- fd.FieldType);
- ILProcessor worker = get.Body.GetILProcessor();
- // [SyncVar] GameObject?
- if (fd.FieldType.Is<UnityEngine.GameObject>())
- {
- // return this.GetSyncVarGameObject(ref field, uint netId);
- // this.
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldfld, netFieldId);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, fd);
- worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference);
- worker.Emit(OpCodes.Ret);
- }
- // [SyncVar] NetworkIdentity?
- else if (fd.FieldType.Is<NetworkIdentity>())
- {
- // return this.GetSyncVarNetworkIdentity(ref field, uint netId);
- // this.
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldfld, netFieldId);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, fd);
- worker.Emit(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference);
- worker.Emit(OpCodes.Ret);
- }
- else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
- {
- // return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
- // this.
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldfld, netFieldId);
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, fd);
- MethodReference getFunc = WeaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
- worker.Emit(OpCodes.Call, getFunc);
- worker.Emit(OpCodes.Ret);
- }
- // [SyncVar] int, string, etc.
- else
- {
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldfld, fd);
- worker.Emit(OpCodes.Ret);
- }
- get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
- get.Body.InitLocals = true;
- get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
- return get;
- }
- public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
- {
- //Create the set method
- MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
- MethodAttributes.SpecialName |
- MethodAttributes.HideBySig,
- WeaverTypes.Import(typeof(void)));
- ILProcessor worker = set.Body.GetILProcessor();
- // if (!SyncVarEqual(value, ref playerData))
- Instruction endOfMethod = worker.Create(OpCodes.Nop);
- // this
- worker.Emit(OpCodes.Ldarg_0);
- // new value to set
- worker.Emit(OpCodes.Ldarg_1);
- // 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(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
- worker.Emit(OpCodes.Ldarg_0);
- // new value to set
- worker.Emit(OpCodes.Ldarg_1);
- // reference to field to set
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, fd);
- // dirty bit
- // 8 byte integer aka long
- worker.Emit(OpCodes.Ldc_I8, dirtyBit);
- if (fd.FieldType.Is<UnityEngine.GameObject>())
- {
- // reference to netId Field to set
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, netFieldId);
- worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference);
- }
- else if (fd.FieldType.Is<NetworkIdentity>())
- {
- // reference to netId Field to set
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, netFieldId);
- worker.Emit(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference);
- }
- else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
- {
- // reference to netId Field to set
- worker.Emit(OpCodes.Ldarg_0);
- worker.Emit(OpCodes.Ldflda, netFieldId);
- MethodReference getFunc = WeaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(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);
- 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);
- }
- worker.Append(endOfMethod);
- worker.Emit(OpCodes.Ret);
- set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
- set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
- return set;
- }
- public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, long dirtyBit)
- {
- string originalName = fd.Name;
- Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType);
- // GameObject/NetworkIdentity SyncVars have a new field for netId
- FieldDefinition netIdField = null;
- // NetworkBehaviour has different field type than other NetworkIdentityFields
- if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
- {
- netIdField = new FieldDefinition("___" + fd.Name + "NetId",
- FieldAttributes.Private,
- WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
- syncVarNetIds[fd] = netIdField;
- }
- else if (fd.FieldType.IsNetworkIdentityField())
- {
- netIdField = new FieldDefinition("___" + fd.Name + "NetId",
- FieldAttributes.Private,
- WeaverTypes.Import<uint>());
- syncVarNetIds[fd] = netIdField;
- }
- MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
- MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField);
- //NOTE: is property even needed? Could just use a setter function?
- //create the property
- PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
- {
- GetMethod = get,
- SetMethod = set
- };
- //add the methods and property to the type.
- td.Methods.Add(get);
- td.Methods.Add(set);
- td.Properties.Add(propertyDefinition);
- Weaver.WeaveLists.replacementSetterProperties[fd] = set;
- // replace getter field if GameObject/NetworkIdentity so it uses
- // netId instead
- // -> only for GameObjects, otherwise an int syncvar's getter would
- // end up in recursion.
- if (fd.FieldType.IsNetworkIdentityField())
- {
- Weaver.WeaveLists.replacementGetterProperties[fd] = get;
- }
- }
- public static (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds) ProcessSyncVars(TypeDefinition td)
- {
- List<FieldDefinition> syncVars = new List<FieldDefinition>();
- Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
- // the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
- // start assigning syncvars at the place the base class stopped, if any
- int dirtyBitCounter = Weaver.WeaveLists.GetSyncVarStart(td.BaseType.FullName);
- // find syncvars
- foreach (FieldDefinition fd in td.Fields)
- {
- if (fd.HasCustomAttribute<SyncVarAttribute>())
- {
- if ((fd.Attributes & FieldAttributes.Static) != 0)
- {
- Weaver.Error($"{fd.Name} cannot be static", fd);
- continue;
- }
- if (fd.FieldType.IsArray)
- {
- Weaver.Error($"{fd.Name} has invalid type. Use SyncLists instead of arrays", fd);
- continue;
- }
- if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
- {
- Weaver.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
- }
- else
- {
- syncVars.Add(fd);
- ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
- dirtyBitCounter += 1;
- if (dirtyBitCounter == SyncVarLimit)
- {
- Weaver.Error($"{td.Name} has too many SyncVars. Consider refactoring your class into multiple components", td);
- continue;
- }
- }
- }
- }
- // add all the new SyncVar __netId fields
- foreach (FieldDefinition fd in syncVarNetIds.Values)
- {
- td.Fields.Add(fd);
- }
- Weaver.WeaveLists.SetNumSyncVars(td.FullName, syncVars.Count);
- return (syncVars, syncVarNetIds);
- }
- public static void WriteCallHookMethodUsingArgument(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue)
- {
- WriteCallHookMethod(worker, hookMethod, oldValue, null);
- }
- public static void WriteCallHookMethodUsingField(ILProcessor worker, MethodDefinition hookMethod, VariableDefinition oldValue, FieldDefinition newValue)
- {
- if (newValue == null)
- {
- Weaver.Error("NewValue field was null when writing SyncVar hook");
- }
- WriteCallHookMethod(worker, hookMethod, oldValue, newValue);
- }
- static 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);
- }
- }
- }
- }
|