Skip to content

Commit

Permalink
feat: Added a universal RPC attribute [MTT-7482] [MTT-7578] [MTT-7579] [
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ShadauxCat authored Nov 27, 2023
1 parent 436bd45 commit 39d0a2e
Show file tree
Hide file tree
Showing 67 changed files with 4,252 additions and 103 deletions.
10 changes: 10 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Added

- Added a new RPC attribute, which is simply `Rpc`. (#2762)
- 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.
- 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.
- 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.
- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762)
- 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)
- Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735)
- Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735)

### Changed

- `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)

### Fixed

- 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ internal static class CodeGenHelpers
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
public static readonly string RpcAttribute_FullName = typeof(RpcAttribute).FullName;
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
public static readonly string RpcParams_FullName = typeof(RpcParams).FullName;
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
Expand Down
362 changes: 324 additions & 38 deletions com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
Expand Down Expand Up @@ -52,6 +53,15 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition);
break;
case nameof(RpcAttribute):
foreach (var methodDefinition in typeDefinition.GetConstructors())
{
if (methodDefinition.Parameters.Count == 0)
{
methodDefinition.IsPublic = true;
}
}
break;
case nameof(__RpcParams):
case nameof(RpcFallbackSerialization):
typeDefinition.IsPublic = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,38 @@

namespace Unity.Netcode
{

public enum ConnectionEvent
{
ClientConnected,
PeerConnected,
ClientDisconnected,
PeerDisconnected
}

public struct ConnectionEventData
{
public ConnectionEvent EventType;

/// <summary>
/// The client ID for the client that just connected
/// For the <see cref="ConnectionEvent.ClientConnected"/> and <see cref="ConnectionEvent.ClientDisconnected"/>
/// events on the client side, this will be LocalClientId.
/// On the server side, this will be the ID of the client that just connected.
///
/// For the <see cref="ConnectionEvent.PeerConnected"/> and <see cref="ConnectionEvent.PeerDisconnected"/>
/// events on the client side, this will be the client ID assigned by the server to the remote peer.
/// </summary>
public ulong ClientId;

/// <summary>
/// This is only populated in <see cref="ConnectionEvent.ClientConnected"/> on the client side, and
/// contains the list of other peers who were present before you connected. In all other situations,
/// this array will be uninitialized.
/// </summary>
public NativeArray<ulong> PeerClientIds;
}

/// <summary>
/// The NGO connection manager handles:
/// - Client Connections
Expand Down Expand Up @@ -42,7 +74,105 @@ public sealed class NetworkConnectionManager
/// </summary>
public event Action<ulong> OnClientDisconnectCallback = null;

internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
/// <summary>
/// The callback to invoke once a peer connects. This callback is only ran on the server and on the local client that connects.
/// </summary>
public event Action<NetworkManager, ConnectionEventData> OnConnectionEvent = null;


internal void InvokeOnClientConnectedCallback(ulong clientId)
{
try
{
OnClientConnectedCallback?.Invoke(clientId);
}
catch (Exception exception)
{
Debug.LogException(exception);
}

if (!NetworkManager.IsServer)
{
var peerClientIds = new NativeArray<ulong>(Math.Max(NetworkManager.ConnectedClientsIds.Count - 1, 0), Allocator.Temp);
// `using var peerClientIds` or `using(peerClientIds)` renders it immutable...
using var sentinel = peerClientIds;

var idx = 0;
foreach (var peerId in NetworkManager.ConnectedClientsIds)
{
if (peerId == NetworkManager.LocalClientId)
{
continue;
}

peerClientIds[idx] = peerId;
++idx;
}

try
{
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = NetworkManager.LocalClientId, EventType = ConnectionEvent.ClientConnected, PeerClientIds = peerClientIds });
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}
else
{
try
{
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientConnected });
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}
}

internal void InvokeOnClientDisconnectCallback(ulong clientId)
{
try
{
OnClientDisconnectCallback?.Invoke(clientId);
}
catch (Exception exception)
{
Debug.LogException(exception);
}
try
{
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected });
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}

internal void InvokeOnPeerConnectedCallback(ulong clientId)
{
try
{
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerConnected });
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}
internal void InvokeOnPeerDisconnectedCallback(ulong clientId)
{
try
{
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected });
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}

/// <summary>
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
Expand Down Expand Up @@ -355,13 +485,11 @@ internal void DisconnectEventHandler(ulong transportClientId)
// 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.
MessageManager.ProcessIncomingMessageQueue();

try
{
OnClientDisconnectCallback?.Invoke(clientId);
}
catch (Exception exception)
InvokeOnClientDisconnectCallback(clientId);

if (LocalClient.IsHost)
{
Debug.LogException(exception);
InvokeOnPeerDisconnectedCallback(clientId);
}

if (LocalClient.IsServer)
Expand Down Expand Up @@ -623,8 +751,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne
var message = new ConnectionApprovedMessage
{
OwnerClientId = ownerClientId,
NetworkTick = NetworkManager.LocalTime.Tick
NetworkTick = NetworkManager.LocalTime.Tick,
ConnectedClientIds = new NativeArray<ulong>(ConnectedClientIds.Count, Allocator.Temp)
};

var i = 0;
foreach (var clientId in ConnectedClientIds)
{
message.ConnectedClientIds[i] = clientId;
++i;
}

if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
// Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
Expand All @@ -651,12 +788,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne

SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
message.MessageVersions.Dispose();
message.ConnectedClientIds.Dispose();

// If scene management is disabled, then we are done and notify the local host-server the client is connected
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
NetworkManager.ConnectedClients[ownerClientId].IsConnected = true;
InvokeOnClientConnectedCallback(ownerClientId);
if (LocalClient.IsHost)
{
InvokeOnPeerConnectedCallback(ownerClientId);
}
}
else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
{
Expand Down Expand Up @@ -740,6 +882,8 @@ internal NetworkClient AddClient(ulong clientId)

ConnectedClients.Add(clientId, networkClient);
ConnectedClientsList.Add(networkClient);
var message = new ClientConnectedMessage { ClientId = clientId };
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
ConnectedClientIds.Add(clientId);
return networkClient;
}
Expand Down Expand Up @@ -837,6 +981,8 @@ internal void OnClientDisconnectFromServer(ulong clientId)
}

ConnectedClientIds.Remove(clientId);
var message = new ClientDisconnectedMessage { ClientId = clientId };
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
}

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

try
{
OnClientDisconnectCallback?.Invoke(clientId);
}
catch (Exception exception)
InvokeOnClientDisconnectCallback(clientId);

if (LocalClient.IsHost)
{
Debug.LogException(exception);
InvokeOnPeerDisconnectedCallback(clientId);
}

// Clean up the transport to client (and vice versa) mappings
Expand Down Expand Up @@ -935,6 +1079,9 @@ internal void Shutdown()
{
LocalClient.IsApproved = false;
LocalClient.IsConnected = false;
ConnectedClients.Clear();
ConnectedClientIds.Clear();
ConnectedClientsList.Clear();
if (LocalClient.IsServer)
{
// make sure all messages are flushed before transport disconnect clients
Expand Down
Loading

0 comments on commit 39d0a2e

Please sign in to comment.