// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions protected void SendRPCInternal(string functionFullName, NetworkWriter writer, int channelId, bool includeOwner) { // this was in Weaver before if (!NetworkServer.active) { Debug.LogError($"RPC Function {functionFullName} called on Client."); return; } // This cannot use NetworkServer.active, as that is not specific to this object. if (!isServer) { Debug.LogWarning($"ClientRpc {functionFullName} called on un-spawned object: {name}"); return; } // construct the message RpcMessage message = new RpcMessage { netId = netId, componentIndex = (byte)ComponentIndex, functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; NetworkServer.SendToReadyObservers(netIdentity, message, includeOwner, channelId); }
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions protected void SendCommandInternal(string functionFullName, NetworkWriter writer, int channelId, bool requiresAuthority = true) { // this was in Weaver before // NOTE: we could remove this later to allow calling Cmds on Server // to avoid Wrapper functions. a lot of people requested this. if (!NetworkClient.active) { Debug.LogError($"Command Function {functionFullName} called without an active client."); return; } // previously we used NetworkClient.readyConnection. // now we check .ready separately. if (!NetworkClient.ready) { // Unreliable Cmds from NetworkTransform may be generated, // or client may have been set NotReady intentionally, so // only warn if on the reliable channel. if (channelId == Channels.Reliable) { Debug.LogWarning("Send command attempted while NetworkClient is not ready.\nThis may be ignored if client intentionally set NotReady."); } return; } // local players can always send commands, regardless of authority, other objects must have authority. if (!(!requiresAuthority || isLocalPlayer || hasAuthority)) { Debug.LogWarning($"Trying to send command for object without authority. {functionFullName}"); return; } // IMPORTANT: can't use .connectionToServer here because calling // a command on other objects is allowed if requireAuthority is // false. other objects don't have a .connectionToServer. // => so we always need to use NetworkClient.connection instead. // => see also: https://github.com/vis2k/Mirror/issues/2629 if (NetworkClient.connection == null) { Debug.LogError("Send command attempted with no client running."); return; } // construct the message CommandMessage message = new CommandMessage { netId = netId, componentIndex = (byte)ComponentIndex, functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; // IMPORTANT: can't use .connectionToServer here because calling // a command on other objects is allowed if requireAuthority is // false. other objects don't have a .connectionToServer. // => so we always need to use NetworkClient.connection instead. // => see also: https://github.com/vis2k/Mirror/issues/2629 NetworkClient.connection.Send(message, channelId); }
public void GetDelegate() { // registerdelegate is protected, but we can use // RegisterCommandDelegate which calls RegisterDelegate int registeredHash = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, NetworkBehaviourDelegateComponent.Delegate, false); // get handler int cmdHash = nameof(NetworkBehaviourDelegateComponent.Delegate).GetStableHashCode(); RemoteCallDelegate func = RemoteProcedureCalls.GetDelegate(cmdHash); RemoteCallDelegate expected = NetworkBehaviourDelegateComponent.Delegate; Assert.That(func, Is.EqualTo(expected)); // invalid hash should return null handler RemoteCallDelegate funcNull = RemoteProcedureCalls.GetDelegate(1234); Assert.That(funcNull, Is.Null); // clean up RemoteProcedureCalls.RemoveDelegate(registeredHash); }
public void RegisterDelegateDoesntOverwrite() { // registerdelegate is protected, but we can use // RegisterCommandDelegate which calls RegisterDelegate int registeredHash1 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, NetworkBehaviourDelegateComponent.Delegate, false); // registering the exact same one should be fine. it should simply // do nothing. int registeredHash2 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, NetworkBehaviourDelegateComponent.Delegate, false); // registering the same name with a different callback shouldn't // work LogAssert.Expect(LogType.Error, $"Function {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate)} and {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate2)} have the same hash. Please rename one of them"); int registeredHash3 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, NetworkBehaviourDelegateComponent.Delegate2, false); // clean up RemoteProcedureCalls.RemoveDelegate(registeredHash1); RemoteProcedureCalls.RemoveDelegate(registeredHash2); RemoteProcedureCalls.RemoveDelegate(registeredHash3); }
public virtual void TearDown() { NetworkClient.Shutdown(); NetworkServer.Shutdown(); // some tests might modify NetworkServer.connections without ever // starting the server. // NetworkServer.Shutdown() only clears connections if it was started. // so let's do it manually for proper test cleanup here. NetworkServer.connections.Clear(); foreach (GameObject go in instantiated) { if (go != null) { GameObject.DestroyImmediate(go); } } GameObject.DestroyImmediate(transport.gameObject); Transport.activeTransport = null; NetworkManager.singleton = null; // clear rpc lookup caches. // this can cause problems in tests otherwise. RemoteProcedureCalls.ResetStatics(); }
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions protected void SendTargetRPCInternal(NetworkConnection conn, string functionFullName, NetworkWriter writer, int channelId) { if (!NetworkServer.active) { Debug.LogError($"TargetRPC {functionFullName} called when server not active"); return; } if (!isServer) { Debug.LogWarning($"TargetRpc {functionFullName} called on {name} but that object has not been spawned or has been unspawned"); return; } // connection parameter is optional. assign if null. if (conn is null) { conn = connectionToClient; } // if still null if (conn is null) { Debug.LogError($"TargetRPC {functionFullName} was given a null connection, make sure the object has an owner or you pass in the target connection"); return; } if (!(conn is NetworkConnectionToClient)) { Debug.LogError($"TargetRPC {functionFullName} requires a NetworkConnectionToClient but was given {conn.GetType().Name}"); return; } // construct the message RpcMessage message = new RpcMessage { netId = netId, componentIndex = (byte)ComponentIndex, functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; conn.Send(message, channelId); }