mirror of
https://github.com/smyalygames/monopoly.git
synced 2025-12-29 07:48:48 +01:00
Added multiplayer plugin
This commit is contained in:
127
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
127
Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Command] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class CommandProcessor
|
||||
{
|
||||
/*
|
||||
// generates code like:
|
||||
public void CmdThrust(float thrusting, int spin)
|
||||
{
|
||||
NetworkWriter networkWriter = new NetworkWriter();
|
||||
networkWriter.Write(thrusting);
|
||||
networkWriter.WritePackedUInt32((uint)spin);
|
||||
base.SendCommandInternal(cmdName, networkWriter, cmdName);
|
||||
}
|
||||
|
||||
public void CallCmdThrust(float thrusting, int spin)
|
||||
{
|
||||
// whatever the user was doing before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to CmdTrust with CallCmdTrust
|
||||
|
||||
This method moves all the user's code into the "CallCmd" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr)
|
||||
{
|
||||
MethodDefinition cmd = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
// NetworkWriter writer = new NetworkWriter();
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Cmd call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
string cmdName = md.Name;
|
||||
int channel = commandAttr.GetField("channel", 0);
|
||||
bool ignoreAuthority = commandAttr.GetField("ignoreAuthority", false);
|
||||
|
||||
// invoke internal send and return
|
||||
// load 'base.' to call the SendCommand function with
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, cmdName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
|
||||
worker.Append(worker.Create(ignoreAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.sendCommandInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
|
||||
{
|
||||
if (!NetworkServer.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
|
||||
}
|
||||
*/
|
||||
public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc)
|
||||
{
|
||||
MethodDefinition cmd = new MethodDefinition(Weaver.InvokeRpcPrefix + method.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = cmd.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, method.Name, label, "Command");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(method, worker, RemoteCallType.Command))
|
||||
return null;
|
||||
|
||||
AddSenderConnection(method, worker);
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, cmdCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
|
||||
{
|
||||
foreach (ParameterDefinition param in method.Parameters)
|
||||
{
|
||||
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
|
||||
{
|
||||
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
|
||||
// exmaple: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd67b3f7c2d66074a9bc7a23787e2ffb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// removed Oct 5 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e25c00c88fc134f6ea7ab00ae4db8083
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
125
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
125
Assets/Mirror/Editor/Weaver/Processors/MethodProcessor.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class MethodProcessor
|
||||
{
|
||||
private const string RpcPrefix = "UserCode_";
|
||||
|
||||
// creates a method substitute
|
||||
// For example, if we have this:
|
||||
// public void CmdThrust(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// it will substitute the method and move the code to a new method with a provided name
|
||||
// for example:
|
||||
//
|
||||
// public void CmdTrust(float thrusting, int spin)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// public void <newName>(float thrusting, int spin)
|
||||
// {
|
||||
// xxxxx
|
||||
// }
|
||||
//
|
||||
// Note that all the calls to the method remain untouched
|
||||
//
|
||||
// the original method definition loses all code
|
||||
// this returns the newly created method with all the user provided code
|
||||
public static MethodDefinition SubstituteMethod(TypeDefinition td, MethodDefinition md)
|
||||
{
|
||||
string newName = RpcPrefix + md.Name;
|
||||
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
|
||||
|
||||
// add parameters
|
||||
foreach (ParameterDefinition pd in md.Parameters)
|
||||
{
|
||||
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
|
||||
}
|
||||
|
||||
// swap bodies
|
||||
(cmd.Body, md.Body) = (md.Body, cmd.Body);
|
||||
|
||||
// Move over all the debugging information
|
||||
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
|
||||
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
|
||||
md.DebugInformation.SequencePoints.Clear();
|
||||
|
||||
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
|
||||
cmd.CustomDebugInformations.Add(customInfo);
|
||||
md.CustomDebugInformations.Clear();
|
||||
|
||||
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
|
||||
|
||||
td.Methods.Add(cmd);
|
||||
|
||||
FixRemoteCallToBaseMethod(td, cmd);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and fixes call to base methods within remote calls
|
||||
/// <para>For example, changes `base.CmdDoSomething` to `base.CallCmdDoSomething` within `this.CallCmdDoSomething`</para>
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="method"></param>
|
||||
public static void FixRemoteCallToBaseMethod(TypeDefinition type, MethodDefinition method)
|
||||
{
|
||||
string callName = method.Name;
|
||||
|
||||
// Cmd/rpc start with Weaver.RpcPrefix
|
||||
// eg CallCmdDoSomething
|
||||
if (!callName.StartsWith(RpcPrefix))
|
||||
return;
|
||||
|
||||
// eg CmdDoSomething
|
||||
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
|
||||
|
||||
foreach (Instruction instruction in method.Body.Instructions)
|
||||
{
|
||||
// if call to base.CmdDoSomething within this.CallCmdDoSomething
|
||||
if (IsCallToMethod(instruction, out MethodDefinition calledMethod) &&
|
||||
calledMethod.Name == baseRemoteCallName)
|
||||
{
|
||||
TypeDefinition baseType = type.BaseType.Resolve();
|
||||
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
|
||||
|
||||
if (baseMethod == null)
|
||||
{
|
||||
Weaver.Error($"Could not find base method for {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!baseMethod.IsVirtual)
|
||||
{
|
||||
Weaver.Error($"Could not find base method that was virutal {callName}", method);
|
||||
return;
|
||||
}
|
||||
|
||||
instruction.Operand = baseMethod;
|
||||
|
||||
Weaver.DLog(type, "Replacing call to '{0}' with '{1}' inside '{2}'", calledMethod.FullName, baseMethod.FullName, method.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
|
||||
{
|
||||
if (instruction.OpCode == OpCodes.Call &&
|
||||
instruction.Operand is MethodDefinition method)
|
||||
{
|
||||
calledMethod = method;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
calledMethod = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661e1af528e3441f79e1552fb5ec4e0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// only shows warnings in case we use SyncVars etc. for MonoBehaviour.
|
||||
/// </summary>
|
||||
static class MonoBehaviourProcessor
|
||||
{
|
||||
public static void Process(TypeDefinition td)
|
||||
{
|
||||
ProcessSyncVars(td);
|
||||
ProcessMethods(td);
|
||||
}
|
||||
|
||||
static void ProcessSyncVars(TypeDefinition td)
|
||||
{
|
||||
// find syncvars
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.HasCustomAttribute<SyncVarAttribute>())
|
||||
Weaver.Error($"SyncVar {fd.Name} must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
|
||||
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
|
||||
{
|
||||
Weaver.Error($"{fd.Name} is a SyncObject and must be inside a NetworkBehaviour. {td.Name} is not a NetworkBehaviour", fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessMethods(TypeDefinition td)
|
||||
{
|
||||
// find command and RPC functions
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
if (md.HasCustomAttribute<CommandAttribute>())
|
||||
Weaver.Error($"Command {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<ClientRpcAttribute>())
|
||||
Weaver.Error($"ClientRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
if (md.HasCustomAttribute<TargetRpcAttribute>())
|
||||
Weaver.Error($"TargetRpc {md.Name} must be declared inside a NetworkBehaviour", md);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c16722912b64af894e4f6668f2e54c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1099
Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
Normal file
1099
Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8118d606be3214e5d99943ec39530dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
160
Assets/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
Normal file
160
Assets/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class PropertySiteProcessor
|
||||
{
|
||||
public static void Process(ModuleDefinition moduleDef)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
//Search through the types
|
||||
foreach (TypeDefinition td in moduleDef.Types)
|
||||
{
|
||||
if (td.IsClass)
|
||||
{
|
||||
ProcessSiteClass(td);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
|
||||
}
|
||||
|
||||
static void ProcessSiteClass(TypeDefinition td)
|
||||
{
|
||||
//Console.WriteLine(" ProcessSiteClass " + td);
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
ProcessSiteClass(nested);
|
||||
}
|
||||
}
|
||||
|
||||
static void ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
// process all references to replaced members with properties
|
||||
//Weaver.DLog(td, " ProcessSiteMethod " + md);
|
||||
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
for (int iCount = 0; iCount < md.Body.Instructions.Count;)
|
||||
{
|
||||
Instruction instr = md.Body.Instructions[iCount];
|
||||
iCount += ProcessInstruction(md, instr, iCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar write access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
// replaces syncvar read access with the NetworkXYZ.get property calls
|
||||
static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
//replace with property
|
||||
//DLog(td, " replacing " + md.Name + ":" + i);
|
||||
i.OpCode = OpCodes.Call;
|
||||
i.Operand = replacement;
|
||||
//DLog(td, " replaced " + md.Name + ":" + i);
|
||||
}
|
||||
}
|
||||
|
||||
static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
|
||||
{
|
||||
if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
|
||||
{
|
||||
// this instruction sets the value of a field. cache the field reference.
|
||||
ProcessInstructionSetterField(md, instr, opFieldst);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
|
||||
{
|
||||
// this instruction gets the value of a field. cache the field reference.
|
||||
ProcessInstructionGetterField(md, instr, opFieldld);
|
||||
}
|
||||
|
||||
if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
|
||||
{
|
||||
// loading a field by reference, watch out for initobj instruction
|
||||
// see https://github.com/vis2k/Mirror/issues/696
|
||||
return ProcessInstructionLoadAddress(md, instr, opFieldlda, iCount);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
|
||||
{
|
||||
// dont replace property call sites in constructors
|
||||
if (md.Name == ".ctor")
|
||||
return 1;
|
||||
|
||||
// does it set a field that we replaced?
|
||||
if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
|
||||
{
|
||||
// we have a replacement for this property
|
||||
// is the next instruction a initobj?
|
||||
Instruction nextInstr = md.Body.Instructions[iCount + 1];
|
||||
|
||||
if (nextInstr.OpCode == OpCodes.Initobj)
|
||||
{
|
||||
// we need to replace this code with:
|
||||
// var tmp = new MyStruct();
|
||||
// this.set_Networkxxxx(tmp);
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
|
||||
md.Body.Variables.Add(tmpVariable);
|
||||
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
|
||||
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
|
||||
|
||||
worker.Remove(instr);
|
||||
worker.Remove(nextInstr);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d48f1ab125e9940a995603796bccc59e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
175
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
175
Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
// finds all readers and writers and register them
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class ReaderWriterProcessor
|
||||
{
|
||||
public static bool Process(AssemblyDefinition CurrentAssembly)
|
||||
{
|
||||
Readers.Init();
|
||||
Writers.Init();
|
||||
foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
|
||||
{
|
||||
if (unityAsm.name == "Mirror")
|
||||
{
|
||||
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
|
||||
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
|
||||
{
|
||||
ProcessAssemblyClasses(CurrentAssembly, assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
|
||||
}
|
||||
|
||||
static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// extension methods only live in static classes
|
||||
// static classes are represented as sealed and abstract
|
||||
if (klass.IsAbstract && klass.IsSealed)
|
||||
{
|
||||
// if asmebly has any declared writers then it is "modified"
|
||||
modified |= LoadDeclaredWriters(CurrentAssembly, klass);
|
||||
modified |= LoadDeclaredReaders(CurrentAssembly, klass);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TypeDefinition klass in assembly.MainModule.Types)
|
||||
{
|
||||
// if asmebly has any network message then it is modified
|
||||
modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, klass);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadMessageReadWriter(ModuleDefinition module, TypeDefinition klass)
|
||||
{
|
||||
bool modified = false;
|
||||
if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
|
||||
{
|
||||
Readers.GetReadFunc(module.ImportReference(klass));
|
||||
Writers.GetWriteFunc(module.ImportReference(klass));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
foreach (TypeDefinition td in klass.NestedTypes)
|
||||
{
|
||||
modified |= LoadMessageReadWriter(module, td);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
|
||||
{
|
||||
// register all the writers in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 2)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
|
||||
continue;
|
||||
|
||||
if (!method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
TypeReference dataType = method.Parameters[1].ParameterType;
|
||||
Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
|
||||
{
|
||||
// register all the reader in this class. Skip the ones with wrong signature
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition method in klass.Methods)
|
||||
{
|
||||
if (method.Parameters.Count != 1)
|
||||
continue;
|
||||
|
||||
if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
|
||||
continue;
|
||||
|
||||
if (method.ReturnType.Is(typeof(void)))
|
||||
continue;
|
||||
|
||||
if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
|
||||
continue;
|
||||
|
||||
if (method.HasGenericParameters)
|
||||
continue;
|
||||
|
||||
Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool IsEditorAssembly(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
return currentAssembly.MainModule.AssemblyReferences.Any(assemblyReference =>
|
||||
assemblyReference.Name == nameof(UnityEditor)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a method that will store all the readers and writers into
|
||||
/// <see cref="Writer{T}.write"/> and <see cref="Reader{T}.read"/>
|
||||
///
|
||||
/// The method will be marked InitializeOnLoadMethodAttribute so it gets
|
||||
/// executed before mirror runtime code
|
||||
/// </summary>
|
||||
/// <param name="currentAssembly"></param>
|
||||
public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly)
|
||||
{
|
||||
MethodDefinition rwInitializer = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
|
||||
MethodAttributes.Static,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
System.Reflection.ConstructorInfo attributeconstructor = typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(RuntimeInitializeLoadType) });
|
||||
|
||||
CustomAttribute customAttributeRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(attributeconstructor));
|
||||
customAttributeRef.ConstructorArguments.Add(new CustomAttributeArgument(WeaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
|
||||
rwInitializer.CustomAttributes.Add(customAttributeRef);
|
||||
|
||||
if (IsEditorAssembly(currentAssembly))
|
||||
{
|
||||
// editor assembly, add InitializeOnLoadMethod too. Useful for the editor tests
|
||||
System.Reflection.ConstructorInfo initializeOnLoadConstructor = typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[0]);
|
||||
CustomAttribute initializeCustomConstructorRef = new CustomAttribute(currentAssembly.MainModule.ImportReference(initializeOnLoadConstructor));
|
||||
rwInitializer.CustomAttributes.Add(initializeCustomConstructorRef);
|
||||
}
|
||||
|
||||
ILProcessor worker = rwInitializer.Body.GetILProcessor();
|
||||
|
||||
Writers.InitializeWriters(worker);
|
||||
Readers.InitializeReaders(worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
TypeDefinition generateClass = Weaver.WeaveLists.generateContainerClass;
|
||||
|
||||
generateClass.Methods.Add(rwInitializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3263602f0a374ecd8d08588b1fc2f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
105
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
105
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [Rpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class RpcProcessor
|
||||
{
|
||||
public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(
|
||||
Weaver.InvokeRpcPrefix + md.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "RPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/*
|
||||
* generates code like:
|
||||
|
||||
public void RpcTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32((uint)param);
|
||||
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
|
||||
}
|
||||
public void CallRpcTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to RpcTest with CallRpcTest
|
||||
|
||||
This method moves all the user's code into the "CallRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
*/
|
||||
public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
if (Weaver.GenerateLogErrors)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, "Call ClientRpc function " + md.Name));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.logErrorReference));
|
||||
}
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the Rpc call
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.ClientRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
int channel = clientRpcAttr.GetField("channel", 0);
|
||||
bool excludeOwner = clientRpcAttr.GetField("excludeOwner", false);
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
|
||||
worker.Append(worker.Create(excludeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendRpcInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
Normal file
11
Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3cb7051ff41947e59bba58bdd2b73fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,153 @@
|
||||
// Injects server/client active checks for [Server/Client] attributes
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class ServerClientAttributeProcessor
|
||||
{
|
||||
public static bool Process(TypeDefinition td)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (MethodDefinition md in td.Methods)
|
||||
{
|
||||
modified |= ProcessSiteMethod(md);
|
||||
}
|
||||
|
||||
foreach (TypeDefinition nested in td.NestedTypes)
|
||||
{
|
||||
modified |= Process(nested);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static bool ProcessSiteMethod(MethodDefinition md)
|
||||
{
|
||||
if (md.Name == ".cctor" ||
|
||||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
|
||||
md.Name.StartsWith(Weaver.InvokeRpcPrefix))
|
||||
return false;
|
||||
|
||||
if (md.IsAbstract)
|
||||
{
|
||||
if (HasServerClientAttribute(md))
|
||||
{
|
||||
Weaver.Error("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md.Body != null && md.Body.Instructions != null)
|
||||
{
|
||||
return ProcessMethodAttributes(md);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasServerClientAttribute(MethodDefinition md)
|
||||
{
|
||||
foreach (CustomAttribute attr in md.CustomAttributes)
|
||||
{
|
||||
switch (attr.Constructor.DeclaringType.ToString())
|
||||
{
|
||||
case "Mirror.ServerAttribute":
|
||||
case "Mirror.ServerCallbackAttribute":
|
||||
case "Mirror.ClientAttribute":
|
||||
case "Mirror.ClientCallbackAttribute":
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ProcessMethodAttributes(MethodDefinition md)
|
||||
{
|
||||
if (md.HasCustomAttribute<ServerAttribute>())
|
||||
InjectServerGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ServerCallbackAttribute>())
|
||||
InjectServerGuard(md, false);
|
||||
else if (md.HasCustomAttribute<ClientAttribute>())
|
||||
InjectClientGuard(md, true);
|
||||
else if (md.HasCustomAttribute<ClientCallbackAttribute>())
|
||||
InjectClientGuard(md, false);
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectServerGuard(MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Server] function '{md.FullName}' called when server was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
|
||||
}
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
static void InjectClientGuard(MethodDefinition md, bool logWarning)
|
||||
{
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
Instruction top = md.Body.Instructions[0];
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.NetworkClientGetActive));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
|
||||
if (logWarning)
|
||||
{
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, $"[Client] function '{md.FullName}' called when client was not active"));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Call, WeaverTypes.logWarningReference));
|
||||
}
|
||||
|
||||
InjectGuardParameters(md, worker, top);
|
||||
InjectGuardReturnValue(md, worker, top);
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with "ref" or "out" parameters
|
||||
static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
int offset = md.Resolve().IsStatic ? 0 : 1;
|
||||
for (int index = 0; index < md.Parameters.Count; index++)
|
||||
{
|
||||
ParameterDefinition param = md.Parameters[index];
|
||||
if (param.IsOut)
|
||||
{
|
||||
TypeReference elementType = param.ParameterType.GetElementType();
|
||||
|
||||
md.Body.Variables.Add(new VariableDefinition(elementType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is required to early-out from a function with a return value.
|
||||
static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
|
||||
{
|
||||
if (!md.ReturnType.Is(typeof(void)))
|
||||
{
|
||||
md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
|
||||
md.Body.InitLocals = true;
|
||||
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
|
||||
worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 024f251bf693bb345b90b9177892d534
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29e4a45f69822462ab0b15adda962a29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// removed 2020-09
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5d8b25543a624384944b599e5a832a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// Removed Oct 1 2020
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f3445268e45d437fac325837aff3246
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectInitializer
|
||||
{
|
||||
public static void GenerateSyncObjectInitializer(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
// register syncobject in network behaviour
|
||||
GenerateSyncObjectRegistration(worker, fd);
|
||||
}
|
||||
|
||||
public static bool ImplementsSyncObject(TypeReference typeRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
// value types cant inherit from SyncObject
|
||||
if (typeRef.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeRef.Resolve().ImplementsInterface<SyncObject>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// generates code like:
|
||||
this.InitSyncObject(m_sizes);
|
||||
*/
|
||||
static void GenerateSyncObjectRegistration(ILProcessor worker, FieldDefinition fd)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.InitSyncObjectReference));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02219b00b3674e59a2151f41e791688
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.CecilX;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
public static class SyncObjectProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds SyncObjects fields in a type
|
||||
/// <para>Type should be a NetworkBehaviour</para>
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
|
||||
{
|
||||
List<FieldDefinition> syncObjects = new List<FieldDefinition>();
|
||||
|
||||
foreach (FieldDefinition fd in td.Fields)
|
||||
{
|
||||
if (fd.FieldType.Resolve().ImplementsInterface<SyncObject>())
|
||||
{
|
||||
if (fd.IsStatic)
|
||||
{
|
||||
Weaver.Error($"{fd.Name} cannot be static", fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateReadersAndWriters(fd.FieldType);
|
||||
|
||||
syncObjects.Add(fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return syncObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates serialization methods for synclists
|
||||
/// </summary>
|
||||
/// <param name="td">The synclist class</param>
|
||||
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
|
||||
static void GenerateReadersAndWriters(TypeReference tr)
|
||||
{
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
foreach (TypeReference argument in genericInstance.GenericArguments)
|
||||
{
|
||||
if (!argument.IsGenericParameter)
|
||||
{
|
||||
Readers.GetReadFunc(argument);
|
||||
Writers.GetWriteFunc(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr != null)
|
||||
{
|
||||
GenerateReadersAndWriters(tr.Resolve().BaseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f71efc83cde4917b7d21efa90bcc9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
434
Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
Normal file
434
Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
Normal file
@@ -0,0 +1,434 @@
|
||||
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.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarGameObjectReference));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
// [SyncVar] NetworkIdentity?
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// new value to set
|
||||
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarGameObjectEqualReference));
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference));
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
|
||||
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
|
||||
syncVarEqualGm.GenericArguments.Add(fd.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm));
|
||||
}
|
||||
|
||||
worker.Append(worker.Create(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.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, fd));
|
||||
worker.Append(worker.Create(OpCodes.Stloc, oldValue));
|
||||
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
|
||||
// new value to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
|
||||
// reference to field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
|
||||
// dirty bit
|
||||
// 8 byte integer aka long
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarGameObjectReference));
|
||||
}
|
||||
else if (fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference));
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of SetSyncVar with field type
|
||||
GenericInstanceMethod gm = new GenericInstanceMethod(WeaverTypes.setSyncVarReference);
|
||||
gm.GenericArguments.Add(fd.FieldType);
|
||||
|
||||
// invoke SetSyncVar
|
||||
worker.Append(worker.Create(OpCodes.Call, gm));
|
||||
}
|
||||
|
||||
MethodDefinition hookMethod = GetHookMethod(td, fd);
|
||||
|
||||
if (hookMethod != null)
|
||||
{
|
||||
//if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.NetworkServerGetLocalClientActive));
|
||||
worker.Append(worker.Create(OpCodes.Brfalse, label));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarHookGuard));
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, label));
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, true);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_1));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
|
||||
|
||||
// call hook (oldValue, newValue)
|
||||
// Generates: OnValueChanged(oldValue, value);
|
||||
WriteCallHookMethodUsingArgument(worker, hookMethod, oldValue);
|
||||
|
||||
// setSyncVarHookGuard(dirtyBit, false);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I8, dirtyBit));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarHookGuard));
|
||||
|
||||
worker.Append(label);
|
||||
}
|
||||
|
||||
worker.Append(endOfMethod);
|
||||
|
||||
worker.Append(worker.Create(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;
|
||||
if (fd.FieldType.Is<UnityEngine.GameObject>() ||
|
||||
fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
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.Is<UnityEngine.GameObject>() ||
|
||||
fd.FieldType.Is<NetworkIdentity>())
|
||||
{
|
||||
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.Append(worker.Create(OpCodes.Ldloc, oldValue));
|
||||
}
|
||||
|
||||
void WriteNewValue()
|
||||
{
|
||||
// write arg1 or this.field
|
||||
if (newValue == null)
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// syncvar.get
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, newValue));
|
||||
}
|
||||
}
|
||||
|
||||
// Writes this before method if it is not static
|
||||
void WriteStartFunctionCall()
|
||||
{
|
||||
// dont add this (Ldarg_0) if method is static
|
||||
if (!hookMethod.IsStatic)
|
||||
{
|
||||
// this before method call
|
||||
// eg this.onValueChanged
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
}
|
||||
}
|
||||
|
||||
// Calls method
|
||||
void WriteEndFunctionCall()
|
||||
{
|
||||
// only use Callvirt when not static
|
||||
OpCode opcode = hookMethod.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
|
||||
worker.Append(worker.Create(opcode, hookMethod));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f52c39bddd95d42b88f9cd554dfd9198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
134
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
134
Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Mono.CecilX;
|
||||
using Mono.CecilX.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes [TargetRpc] methods in NetworkBehaviour
|
||||
/// </summary>
|
||||
public static class TargetRpcProcessor
|
||||
{
|
||||
// helper functions to check if the method has a NetworkConnection parameter
|
||||
public static bool HasNetworkConnectionParameter(MethodDefinition md)
|
||||
{
|
||||
return md.Parameters.Count > 0 &&
|
||||
md.Parameters[0].ParameterType.Is<NetworkConnection>();
|
||||
}
|
||||
|
||||
public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc)
|
||||
{
|
||||
MethodDefinition rpc = new MethodDefinition(Weaver.InvokeRpcPrefix + md.Name, MethodAttributes.Family |
|
||||
MethodAttributes.Static |
|
||||
MethodAttributes.HideBySig,
|
||||
WeaverTypes.Import(typeof(void)));
|
||||
|
||||
ILProcessor worker = rpc.Body.GetILProcessor();
|
||||
Instruction label = worker.Create(OpCodes.Nop);
|
||||
|
||||
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, md.Name, label, "TargetRPC");
|
||||
|
||||
// setup for reader
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Castclass, td));
|
||||
|
||||
// NetworkConnection parameter is optional
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// if call has NetworkConnection write clients connection as first arg
|
||||
//ClientScene.readyconnection
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.ReadyConnectionReference));
|
||||
}
|
||||
|
||||
// process reader parameters and skip first one if first one is NetworkConnection
|
||||
if (!NetworkBehaviourProcessor.ReadArguments(md, worker, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
// invoke actual command function
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, rpcCallFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
|
||||
td.Methods.Add(rpc);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
/* generates code like:
|
||||
public void TargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (NetworkConnection conn, int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
or if optional:
|
||||
public void TargetTest (int param)
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter ();
|
||||
writer.WritePackedUInt32 ((uint)param);
|
||||
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
|
||||
}
|
||||
public void CallTargetTest (int param)
|
||||
{
|
||||
// whatever the user did before
|
||||
}
|
||||
|
||||
Originally HLAPI put the send message code inside the Call function
|
||||
and then proceeded to replace every call to TargetTest with CallTargetTest
|
||||
|
||||
This method moves all the user's code into the "CallTargetRpc" method
|
||||
and replaces the body of the original method with the send message code.
|
||||
This way we do not need to modify the code anywhere else, and this works
|
||||
correctly in dependent assemblies
|
||||
|
||||
*/
|
||||
public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr)
|
||||
{
|
||||
MethodDefinition rpc = MethodProcessor.SubstituteMethod(td, md);
|
||||
|
||||
ILProcessor worker = md.Body.GetILProcessor();
|
||||
|
||||
NetworkBehaviourProcessor.WriteSetupLocals(worker);
|
||||
|
||||
NetworkBehaviourProcessor.WriteCreateWriter(worker);
|
||||
|
||||
// write all the arguments that the user passed to the TargetRpc call
|
||||
// (skip first one if first one is NetworkConnection)
|
||||
if (!NetworkBehaviourProcessor.WriteArguments(worker, md, RemoteCallType.TargetRpc))
|
||||
return null;
|
||||
|
||||
string rpcName = md.Name;
|
||||
|
||||
// invoke SendInternal and return
|
||||
// this
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
if (HasNetworkConnectionParameter(md))
|
||||
{
|
||||
// connection
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// null
|
||||
worker.Append(worker.Create(OpCodes.Ldnull));
|
||||
}
|
||||
worker.Append(worker.Create(OpCodes.Ldtoken, td));
|
||||
// invokerClass
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getTypeFromHandleReference));
|
||||
worker.Append(worker.Create(OpCodes.Ldstr, rpcName));
|
||||
// writer
|
||||
worker.Append(worker.Create(OpCodes.Ldloc_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)));
|
||||
worker.Append(worker.Create(OpCodes.Callvirt, WeaverTypes.sendTargetRpcInternal));
|
||||
|
||||
NetworkBehaviourProcessor.WriteRecycleWriter(worker);
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb3ce6c6f3f2942ae88178b86f5a8282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user