Mirror Networking
RemoteCalls.cs
1using System;
2using System.Collections.Generic;
3using UnityEngine;
4
6{
7 // invoke type for Cmd/Rpc
8 public enum RemoteCallType { Command, ClientRpc }
9
10 // remote call function delegate
11 public delegate void RemoteCallDelegate(NetworkBehaviour obj, NetworkReader reader, NetworkConnectionToClient senderConnection);
12
13 class Invoker
14 {
15 // GameObjects might have multiple components of TypeA.CommandA().
16 // when invoking, we check if 'TypeA' is an instance of the type.
17 // the hash itself isn't enough because we wouldn't know which component
18 // to invoke it on if there are multiple of the same type.
19 public Type componentType;
20 public RemoteCallType callType;
21 public RemoteCallDelegate function;
22 public bool cmdRequiresAuthority;
23
24 public bool AreEqual(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate invokeFunction) =>
25 this.componentType == componentType &&
26 this.callType == remoteCallType &&
27 this.function == invokeFunction;
28 }
29
31 public static class RemoteProcedureCalls
32 {
33 // one lookup for all remote calls.
34 // allows us to easily add more remote call types without duplicating code.
35 // note: do not clear those with [RuntimeInitializeOnLoad]
36 //
37 // IMPORTANT: cmd/rpc functions are identified via **HASHES**.
38 // an index would requires half the bandwidth, but introduces issues
39 // where static constructors are lazily called, so index order isn't
40 // guaranteed. keep hashes to avoid:
41 // https://github.com/vis2k/Mirror/pull/3135
42 // https://github.com/vis2k/Mirror/issues/3138
43 // BUT: 2 byte hash is enough if we check for collisions. that's what we
44 // do for NetworkMessage as well.
45 static readonly Dictionary<ushort, Invoker> remoteCallDelegates = new Dictionary<ushort, Invoker>();
46
47 static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, ushort functionHash)
48 {
49 if (remoteCallDelegates.ContainsKey(functionHash))
50 {
51 // something already registered this hash.
52 // it's okay if it was the same function.
53 Invoker oldInvoker = remoteCallDelegates[functionHash];
54 if (oldInvoker.AreEqual(componentType, remoteCallType, func))
55 {
56 return true;
57 }
58
59 // otherwise notify user. there is a rare chance of string
60 // hash collisions.
61 Debug.LogError($"Function {oldInvoker.componentType}.{oldInvoker.function.GetMethodName()} and {componentType}.{func.GetMethodName()} have the same hash. Please rename one of them");
62 }
63
64 return false;
65 }
66
67 // pass full function name to avoid ClassA.Func & ClassB.Func collisions
68 internal static ushort RegisterDelegate(Type componentType, string functionFullName, RemoteCallType remoteCallType, RemoteCallDelegate func, bool cmdRequiresAuthority = true)
69 {
70 // type+func so Inventory.RpcUse != Equipment.RpcUse
71 ushort hash = (ushort)(functionFullName.GetStableHashCode() & 0xFFFF);
72
73 if (CheckIfDelegateExists(componentType, remoteCallType, func, hash))
74 return hash;
75
76 remoteCallDelegates[hash] = new Invoker
77 {
78 callType = remoteCallType,
79 componentType = componentType,
80 function = func,
81 cmdRequiresAuthority = cmdRequiresAuthority
82 };
83 return hash;
84 }
85
86 // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
87 // need to pass componentType to support invoking on GameObjects with
88 // multiple components of same type with same remote call.
89 public static void RegisterCommand(Type componentType, string functionFullName, RemoteCallDelegate func, bool requiresAuthority) =>
90 RegisterDelegate(componentType, functionFullName, RemoteCallType.Command, func, requiresAuthority);
91
92 // pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
93 // need to pass componentType to support invoking on GameObjects with
94 // multiple components of same type with same remote call.
95 public static void RegisterRpc(Type componentType, string functionFullName, RemoteCallDelegate func) =>
96 RegisterDelegate(componentType, functionFullName, RemoteCallType.ClientRpc, func);
97
98 // to clean up tests
99 internal static void RemoveDelegate(ushort hash) =>
100 remoteCallDelegates.Remove(hash);
101
102 // note: no need to throw an error if not found.
103 // an attacker might just try to call a cmd with an rpc's hash etc.
104 // returning false is enough.
105 static bool GetInvokerForHash(ushort functionHash, RemoteCallType remoteCallType, out Invoker invoker) =>
106 remoteCallDelegates.TryGetValue(functionHash, out invoker) &&
107 invoker != null &&
108 invoker.callType == remoteCallType;
109
110 // InvokeCmd/Rpc Delegate can all use the same function here
111 internal static bool Invoke(ushort functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null)
112 {
113 // IMPORTANT: we check if the message's componentIndex component is
114 // actually of the right type. prevents attackers trying
115 // to invoke remote calls on wrong components.
116 if (GetInvokerForHash(functionHash, remoteCallType, out Invoker invoker) &&
117 invoker.componentType.IsInstanceOfType(component))
118 {
119 // invoke function on this component
120 invoker.function(component, reader, senderConnection);
121 return true;
122 }
123 return false;
124 }
125
126 // check if the command 'requiresAuthority' which is set in the attribute
127 internal static bool CommandRequiresAuthority(ushort cmdHash) =>
128 GetInvokerForHash(cmdHash, RemoteCallType.Command, out Invoker invoker) &&
129 invoker.cmdRequiresAuthority;
130
132 public static RemoteCallDelegate GetDelegate(ushort functionHash) =>
133 remoteCallDelegates.TryGetValue(functionHash, out Invoker invoker)
134 ? invoker.function
135 : null;
136 }
137}
138
Base class for networked components.
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
Used to help manage remote calls for NetworkBehaviours
Definition: RemoteCalls.cs:32
static RemoteCallDelegate GetDelegate(ushort functionHash)
Gets the handler function by hash. Useful for profilers and debuggers.