@@ -10,6 +10,35 @@ public static class CommandProcessor
10
10
// generates code like:
11
11
public void CmdThrust(float thrusting, int spin)
12
12
{
13
+ // no host, invoke the original function immediately.
14
+ // -> Mirror's Host mode is just a server, we can't simulate an independent client in Unity
15
+ // -> delaying this for later introduces cooldown & prediction issues in games.
16
+ // for example, assume CmdFireWeapon function with 100ms cooldown between shots.
17
+ //
18
+ // client-only mode:
19
+ // simply calling CmdFireWeapon and waiting for [SyncVar] cooldown would be too irregular (i.e. 150ms)
20
+ // client has to predict the cooldown locally in order to call CmdFireWeapon every 100ms
21
+ // this works fine, and that's how games need to predict weapon firing / skill usage / etc.
22
+ //
23
+ // host mode:
24
+ // Cmds usued to be queued up for network message processing to 'simulate' a client
25
+ // this introduces a massive headache:
26
+ // firing 3x would queue up CmdFireWeapon 3 times, without ever setting the cooldown yet
27
+ // eventually messages are processed:
28
+ // CmdFireWeapon first call goes through, sets cooldown
29
+ // CmdFireWeapon second/third call would be rejected: "user attempted to fire on cooldown"
30
+ // in other words, we would need to predict cooldowns on host too, which is super weird since host is the server
31
+ //
32
+ // common sense: on host, calling a Cmd should happen immediately, anything else is too much magic
33
+ // and causes edge cases until Unity supports true server/client separation on host!
34
+ //
35
+ if (isServer && isClient) // isHost
36
+ {
37
+ UserCode_CmdThrust(value);
38
+ return;
39
+ }
40
+
41
+ // otherwise send a command message over the network
13
42
NetworkWriterPooled writer = NetworkWriterPool.Get();
14
43
writer.Write(thrusting);
15
44
writer.WritePackedUInt32((uint)spin);
@@ -38,6 +67,32 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write
38
67
39
68
NetworkBehaviourProcessor . WriteSetupLocals ( worker , weaverTypes ) ;
40
69
70
+ Instruction skipIfNotHost = worker . Create ( OpCodes . Nop ) ;
71
+
72
+ // Check if isServer && isClient
73
+ // note that we don't use NetworkServer/Client.active here,
74
+ // otherwise [Command] tests which simulate server/client separation would fail.
75
+ worker . Emit ( OpCodes . Ldarg_0 ) ; // loads this. for isServer check later
76
+ worker . Emit ( OpCodes . Call , weaverTypes . NetworkBehaviourIsServerReference ) ;
77
+ worker . Emit ( OpCodes . Brfalse , skipIfNotHost ) ;
78
+
79
+ worker . Emit ( OpCodes . Ldarg_0 ) ; // loads this. for isClient check later
80
+ worker . Emit ( OpCodes . Call , weaverTypes . NetworkBehaviourIsClientReference ) ;
81
+ worker . Emit ( OpCodes . Brfalse , skipIfNotHost ) ;
82
+
83
+ // Load 'this' reference (Ldarg_0)
84
+ worker . Emit ( OpCodes . Ldarg_0 ) ;
85
+
86
+ // Load all the remaining arguments (Ldarg_1, Ldarg_2, ...)
87
+ for ( int i = 1 ; i < md . Parameters . Count + 1 ; i ++ )
88
+ worker . Emit ( OpCodes . Ldarg , i ) ;
89
+
90
+ // Call the original function directly (UserCode_CmdTest__Int32)
91
+ worker . Emit ( OpCodes . Call , cmd ) ;
92
+ worker . Emit ( OpCodes . Ret ) ;
93
+
94
+ worker . Append ( skipIfNotHost ) ;
95
+
41
96
// NetworkWriter writer = new NetworkWriter();
42
97
NetworkBehaviourProcessor . WriteGetWriter ( worker , weaverTypes ) ;
43
98
0 commit comments