Skip to content

Commit 39d0a2e

Browse files
authored
feat: Added a universal RPC attribute [MTT-7482] [MTT-7578] [MTT-7579] [MTT-7580] [MTT-7581] (#2762)
The new attribute is more configurable at both compile time and runtime than the existing ClientRpc and ServerRpc attributes, and also includes support for clients sending to themselves, and for optionally delaying the invocation of local RPCs until the start of the next frame to enable mutually recursive client/server RPCs to function on a host the same as on other clients (as well as mutually recursive Owner/NotOwner RPCs and other similar patterns). This attribute supersedes ClientRpc and ServerRpc and will be the new default recommendation for creating RPCs. ClientRpc and ServerRpc will eventually be deprecated in favor of the new [Rpc] attribute. By necessity of the feature, this also adds NetworkManager.PeerClientIds to allow clients to know the IDs of other connected clients, as well as NetworkManager.OnPeerConnectedCallback and NetworkManager.OnPeerDisconnectCallback events that are fired when this list is updated via messages from the server.
1 parent 436bd45 commit 39d0a2e

File tree

67 files changed

+4252
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+4252
-103
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- Added a new RPC attribute, which is simply `Rpc`. (#2762)
14+
- This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs.
15+
- This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately.
16+
- This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option.
17+
- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762)
18+
- Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762)
1319
- Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735)
1420
- Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735)
1521

22+
### Changed
23+
24+
- `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762)
25+
1626
### Fixed
1727

1828
- Fixed a bug where having a class with Rpcs that inherits from a class without Rpcs that inherits from NetworkVariable would cause a compile error. (#2751)

com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ internal static class CodeGenHelpers
2626
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;
2727
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
2828
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
29+
public static readonly string RpcAttribute_FullName = typeof(RpcAttribute).FullName;
2930
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
3031
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
32+
public static readonly string RpcParams_FullName = typeof(RpcParams).FullName;
3133
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
3234
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
3335
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;

com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Lines changed: 324 additions & 38 deletions
Large diffs are not rendered by default.

com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using Mono.Cecil;
44
using Mono.Cecil.Cil;
5+
using Mono.Cecil.Rocks;
56
using Unity.CompilationPipeline.Common.Diagnostics;
67
using Unity.CompilationPipeline.Common.ILPostProcessing;
78
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
@@ -52,6 +53,15 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
5253
case nameof(NetworkBehaviour):
5354
ProcessNetworkBehaviour(typeDefinition);
5455
break;
56+
case nameof(RpcAttribute):
57+
foreach (var methodDefinition in typeDefinition.GetConstructors())
58+
{
59+
if (methodDefinition.Parameters.Count == 0)
60+
{
61+
methodDefinition.IsPublic = true;
62+
}
63+
}
64+
break;
5565
case nameof(__RpcParams):
5666
case nameof(RpcFallbackSerialization):
5767
typeDefinition.IsPublic = true;

com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs

Lines changed: 161 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,38 @@
1010

1111
namespace Unity.Netcode
1212
{
13+
14+
public enum ConnectionEvent
15+
{
16+
ClientConnected,
17+
PeerConnected,
18+
ClientDisconnected,
19+
PeerDisconnected
20+
}
21+
22+
public struct ConnectionEventData
23+
{
24+
public ConnectionEvent EventType;
25+
26+
/// <summary>
27+
/// The client ID for the client that just connected
28+
/// For the <see cref="ConnectionEvent.ClientConnected"/> and <see cref="ConnectionEvent.ClientDisconnected"/>
29+
/// events on the client side, this will be LocalClientId.
30+
/// On the server side, this will be the ID of the client that just connected.
31+
///
32+
/// For the <see cref="ConnectionEvent.PeerConnected"/> and <see cref="ConnectionEvent.PeerDisconnected"/>
33+
/// events on the client side, this will be the client ID assigned by the server to the remote peer.
34+
/// </summary>
35+
public ulong ClientId;
36+
37+
/// <summary>
38+
/// This is only populated in <see cref="ConnectionEvent.ClientConnected"/> on the client side, and
39+
/// contains the list of other peers who were present before you connected. In all other situations,
40+
/// this array will be uninitialized.
41+
/// </summary>
42+
public NativeArray<ulong> PeerClientIds;
43+
}
44+
1345
/// <summary>
1446
/// The NGO connection manager handles:
1547
/// - Client Connections
@@ -42,7 +74,105 @@ public sealed class NetworkConnectionManager
4274
/// </summary>
4375
public event Action<ulong> OnClientDisconnectCallback = null;
4476

45-
internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
77+
/// <summary>
78+
/// The callback to invoke once a peer connects. This callback is only ran on the server and on the local client that connects.
79+
/// </summary>
80+
public event Action<NetworkManager, ConnectionEventData> OnConnectionEvent = null;
81+
82+
83+
internal void InvokeOnClientConnectedCallback(ulong clientId)
84+
{
85+
try
86+
{
87+
OnClientConnectedCallback?.Invoke(clientId);
88+
}
89+
catch (Exception exception)
90+
{
91+
Debug.LogException(exception);
92+
}
93+
94+
if (!NetworkManager.IsServer)
95+
{
96+
var peerClientIds = new NativeArray<ulong>(Math.Max(NetworkManager.ConnectedClientsIds.Count - 1, 0), Allocator.Temp);
97+
// `using var peerClientIds` or `using(peerClientIds)` renders it immutable...
98+
using var sentinel = peerClientIds;
99+
100+
var idx = 0;
101+
foreach (var peerId in NetworkManager.ConnectedClientsIds)
102+
{
103+
if (peerId == NetworkManager.LocalClientId)
104+
{
105+
continue;
106+
}
107+
108+
peerClientIds[idx] = peerId;
109+
++idx;
110+
}
111+
112+
try
113+
{
114+
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = NetworkManager.LocalClientId, EventType = ConnectionEvent.ClientConnected, PeerClientIds = peerClientIds });
115+
}
116+
catch (Exception exception)
117+
{
118+
Debug.LogException(exception);
119+
}
120+
}
121+
else
122+
{
123+
try
124+
{
125+
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientConnected });
126+
}
127+
catch (Exception exception)
128+
{
129+
Debug.LogException(exception);
130+
}
131+
}
132+
}
133+
134+
internal void InvokeOnClientDisconnectCallback(ulong clientId)
135+
{
136+
try
137+
{
138+
OnClientDisconnectCallback?.Invoke(clientId);
139+
}
140+
catch (Exception exception)
141+
{
142+
Debug.LogException(exception);
143+
}
144+
try
145+
{
146+
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected });
147+
}
148+
catch (Exception exception)
149+
{
150+
Debug.LogException(exception);
151+
}
152+
}
153+
154+
internal void InvokeOnPeerConnectedCallback(ulong clientId)
155+
{
156+
try
157+
{
158+
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerConnected });
159+
}
160+
catch (Exception exception)
161+
{
162+
Debug.LogException(exception);
163+
}
164+
}
165+
internal void InvokeOnPeerDisconnectedCallback(ulong clientId)
166+
{
167+
try
168+
{
169+
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected });
170+
}
171+
catch (Exception exception)
172+
{
173+
Debug.LogException(exception);
174+
}
175+
}
46176

47177
/// <summary>
48178
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
@@ -355,13 +485,11 @@ internal void DisconnectEventHandler(ulong transportClientId)
355485
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
356486
MessageManager.ProcessIncomingMessageQueue();
357487

358-
try
359-
{
360-
OnClientDisconnectCallback?.Invoke(clientId);
361-
}
362-
catch (Exception exception)
488+
InvokeOnClientDisconnectCallback(clientId);
489+
490+
if (LocalClient.IsHost)
363491
{
364-
Debug.LogException(exception);
492+
InvokeOnPeerDisconnectedCallback(clientId);
365493
}
366494

367495
if (LocalClient.IsServer)
@@ -623,8 +751,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
623751
var message = new ConnectionApprovedMessage
624752
{
625753
OwnerClientId = ownerClientId,
626-
NetworkTick = NetworkManager.LocalTime.Tick
754+
NetworkTick = NetworkManager.LocalTime.Tick,
755+
ConnectedClientIds = new NativeArray<ulong>(ConnectedClientIds.Count, Allocator.Temp)
627756
};
757+
758+
var i = 0;
759+
foreach (var clientId in ConnectedClientIds)
760+
{
761+
message.ConnectedClientIds[i] = clientId;
762+
++i;
763+
}
764+
628765
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
629766
{
630767
// Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
@@ -651,12 +788,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
651788

652789
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
653790
message.MessageVersions.Dispose();
791+
message.ConnectedClientIds.Dispose();
654792

655793
// If scene management is disabled, then we are done and notify the local host-server the client is connected
656794
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
657795
{
658796
NetworkManager.ConnectedClients[ownerClientId].IsConnected = true;
659797
InvokeOnClientConnectedCallback(ownerClientId);
798+
if (LocalClient.IsHost)
799+
{
800+
InvokeOnPeerConnectedCallback(ownerClientId);
801+
}
660802
}
661803
else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
662804
{
@@ -740,6 +882,8 @@ internal NetworkClient AddClient(ulong clientId)
740882

741883
ConnectedClients.Add(clientId, networkClient);
742884
ConnectedClientsList.Add(networkClient);
885+
var message = new ClientConnectedMessage { ClientId = clientId };
886+
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
743887
ConnectedClientIds.Add(clientId);
744888
return networkClient;
745889
}
@@ -837,6 +981,8 @@ internal void OnClientDisconnectFromServer(ulong clientId)
837981
}
838982

839983
ConnectedClientIds.Remove(clientId);
984+
var message = new ClientDisconnectedMessage { ClientId = clientId };
985+
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
840986
}
841987

842988
// If the client ID transport map exists
@@ -845,13 +991,11 @@ internal void OnClientDisconnectFromServer(ulong clientId)
845991
var transportId = ClientIdToTransportId(clientId);
846992
NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
847993

848-
try
849-
{
850-
OnClientDisconnectCallback?.Invoke(clientId);
851-
}
852-
catch (Exception exception)
994+
InvokeOnClientDisconnectCallback(clientId);
995+
996+
if (LocalClient.IsHost)
853997
{
854-
Debug.LogException(exception);
998+
InvokeOnPeerDisconnectedCallback(clientId);
855999
}
8561000

8571001
// Clean up the transport to client (and vice versa) mappings
@@ -935,6 +1079,9 @@ internal void Shutdown()
9351079
{
9361080
LocalClient.IsApproved = false;
9371081
LocalClient.IsConnected = false;
1082+
ConnectedClients.Clear();
1083+
ConnectedClientIds.Clear();
1084+
ConnectedClientsList.Clear();
9381085
if (LocalClient.IsServer)
9391086
{
9401087
// make sure all messages are flushed before transport disconnect clients

0 commit comments

Comments
 (0)