Skip to content

Commit

Permalink
perf: serverrpc bypasses network on host mode (#708)
Browse files Browse the repository at this point in the history
When calling a server rpc on server mode (whether host or not), there is no need to go through the network. Simply invoke the user code.

This changes behaviour a little. When you call a ServerRpc, it now executes immediately. Before, it executed on the next frame.

This also allows the ServerRpc to be invoked from the server

BREAKING CHANGE: ServerRpc execute synchronous in host mode
  • Loading branch information
paulpach committed Mar 19, 2021
1 parent bbb7e6c commit 695eb46
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 20 deletions.
10 changes: 10 additions & 0 deletions Assets/Mirage/Weaver/Processors/RpcProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Reflection;
using Cysharp.Threading.Tasks;
Expand Down Expand Up @@ -374,5 +375,14 @@ static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledM
}
}

protected void InvokeBody(ILProcessor worker, MethodDefinition rpc)
{
for (int i = 0; i <= rpc.Parameters.Count; i++)
{
worker.Append(worker.Create(OpCodes.Ldarg, i));
}
worker.Append(worker.Create(OpCodes.Call, rpc));
}

}
}
31 changes: 31 additions & 0 deletions Assets/Mirage/Weaver/Processors/ServerRpcProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ MethodDefinition GenerateStub(MethodDefinition md, CustomAttribute serverRpcAttr

ILProcessor worker = md.Body.GetILProcessor();

// if (IsServer)
// {
// call the body
// return;
// }
CallBody(worker, cmd);

// NetworkWriter writer = NetworkWriterPool.GetWriter()
VariableDefinition writer = md.AddLocal<PooledNetworkWriter>();
worker.Append(worker.Create(OpCodes.Call, md.Module.ImportReference(() => NetworkWriterPool.GetWriter())));
Expand Down Expand Up @@ -99,6 +106,30 @@ MethodDefinition GenerateStub(MethodDefinition md, CustomAttribute serverRpcAttr
return cmd;
}

public void IsServer(ILProcessor worker, Action body)
{
// if (IsLocalClient) {
Instruction endif = worker.Create(OpCodes.Nop);
worker.Append(worker.Create(OpCodes.Ldarg_0));
worker.Append(worker.Create(OpCodes.Call, (NetworkBehaviour nb) => nb.IsServer));
worker.Append(worker.Create(OpCodes.Brfalse, endif));

body();

// }
worker.Append(endif);

}

private void CallBody(ILProcessor worker, MethodDefinition rpc)
{
IsServer(worker, () =>
{
InvokeBody(worker, rpc);
worker.Append(worker.Create(OpCodes.Ret));
});
}

private void CallSendServerRpc(MethodDefinition md, ILProcessor worker)
{
if (md.ReturnType.Is(typeof(void)))
Expand Down
20 changes: 0 additions & 20 deletions Assets/Tests/Runtime/Host/HostComponentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,6 @@ namespace Mirage.Tests.Host
{
public class HostComponentTests : HostSetup<MockComponent>
{
[Test]
public void ServerRpcWithoutAuthority()
{
var gameObject2 = new GameObject("rpcObject", typeof(NetworkIdentity), typeof(MockComponent));
MockComponent rpcComponent2 = gameObject2.GetComponent<MockComponent>();

// spawn it without client authority
serverObjectManager.Spawn(gameObject2);

// process spawn message from server
client.Update();

// only authorized clients can call ServerRpc
Assert.Throws<UnauthorizedAccessException>(() =>
{
rpcComponent2.Test(1, "hello");
});

}

[UnityTest]
public IEnumerator ServerRpc() => UniTask.ToCoroutine(async () =>
{
Expand Down

0 comments on commit 695eb46

Please sign in to comment.