using System; using System.Collections.Generic; using Unity.Collections; namespace Unity.Netcode { /// /// The manager class to manage custom messages, note that this is different from the NetworkManager custom messages. /// These are named and are much easier to use. /// public class CustomMessagingManager { private readonly NetworkManager m_NetworkManager; internal CustomMessagingManager(NetworkManager networkManager) { m_NetworkManager = networkManager; } /// /// Delegate used for incoming unnamed messages /// /// The clientId that sent the message /// The stream containing the message data public delegate void UnnamedMessageDelegate(ulong clientId, FastBufferReader reader); /// /// Event invoked when unnamed messages arrive /// public event UnnamedMessageDelegate OnUnnamedMessage; internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize) { if (OnUnnamedMessage != null) { var pos = reader.Position; var delegates = OnUnnamedMessage.GetInvocationList(); foreach (var handler in delegates) { reader.Seek(pos); ((UnnamedMessageDelegate)handler).Invoke(clientId, reader); } } m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize); } /// /// Sends unnamed message to all clients /// /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendUnnamedMessageToAll(FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery); } /// /// Sends unnamed message to a list of clients /// /// The clients to send to, sends to everyone if null /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendUnnamedMessage(IReadOnlyList clientIds, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { if (!m_NetworkManager.IsServer) { throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client"); } if (clientIds == null) { throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); } if (m_NetworkManager.IsHost) { for (var i = 0; i < clientIds.Count; ++i) { if (clientIds[i] == m_NetworkManager.LocalClientId) { InvokeUnnamedMessage( m_NetworkManager.LocalClientId, new FastBufferReader(messageBuffer, Allocator.None), 0 ); } } } var message = new UnnamedMessage { SendData = messageBuffer }; var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) { m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientIds, size); } } /// /// Sends a unnamed message to a specific client /// /// The client to send the message to /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { if (m_NetworkManager.IsHost) { if (clientId == m_NetworkManager.LocalClientId) { InvokeUnnamedMessage( m_NetworkManager.LocalClientId, new FastBufferReader(messageBuffer, Allocator.None), 0 ); return; } } var message = new UnnamedMessage { SendData = messageBuffer }; var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) { m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientId, size); } } /// /// Delegate used to handle named messages /// public delegate void HandleNamedMessageDelegate(ulong senderClientId, FastBufferReader messagePayload); private Dictionary m_NamedMessageHandlers32 = new Dictionary(); private Dictionary m_NamedMessageHandlers64 = new Dictionary(); private Dictionary m_MessageHandlerNameLookup32 = new Dictionary(); private Dictionary m_MessageHandlerNameLookup64 = new Dictionary(); internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize) { var bytesCount = reader.Length + serializedHeaderSize; if (m_NetworkManager == null) { // We dont know what size to use. Try every (more collision prone) if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { messageHandler32(sender, reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { messageHandler64(sender, reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } } else { // Only check the right size. switch (m_NetworkManager.NetworkConfig.RpcHashSize) { case HashSize.VarIntFourBytes: if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) { messageHandler32(sender, reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); } break; case HashSize.VarIntEightBytes: if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) { messageHandler64(sender, reader); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); } break; } } } /// /// Registers a named message handler delegate. /// /// Name of the message. /// The callback to run when a named message is received. public void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate callback) { var hash32 = XXHash.Hash32(name); var hash64 = XXHash.Hash64(name); m_NamedMessageHandlers32[hash32] = callback; m_NamedMessageHandlers64[hash64] = callback; m_MessageHandlerNameLookup32[hash32] = name; m_MessageHandlerNameLookup64[hash64] = name; } /// /// Unregisters a named message handler. /// /// The name of the message. public void UnregisterNamedMessageHandler(string name) { var hash32 = XXHash.Hash32(name); var hash64 = XXHash.Hash64(name); m_NamedMessageHandlers32.Remove(hash32); m_NamedMessageHandlers64.Remove(hash64); m_MessageHandlerNameLookup32.Remove(hash32); m_MessageHandlerNameLookup64.Remove(hash64); } /// /// Sends a named message to all clients /// /// The message name to send /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { SendNamedMessage(messageName, m_NetworkManager.ConnectedClientsIds, messageStream, networkDelivery); } /// /// Sends a named message /// /// The message name to send /// The client to send the message to /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendNamedMessage(string messageName, ulong clientId, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { ulong hash = 0; switch (m_NetworkManager.NetworkConfig.RpcHashSize) { case HashSize.VarIntFourBytes: hash = XXHash.Hash32(messageName); break; case HashSize.VarIntEightBytes: hash = XXHash.Hash64(messageName); break; } if (m_NetworkManager.IsHost) { if (clientId == m_NetworkManager.LocalClientId) { InvokeNamedMessage( hash, m_NetworkManager.LocalClientId, new FastBufferReader(messageStream, Allocator.None), 0 ); return; } } var message = new NamedMessage { Hash = hash, SendData = messageStream, }; var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) { m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientId, messageName, size); } } /// /// Sends the named message /// /// The message name to send /// The clients to send to /// The message stream containing the data /// The delivery type (QoS) to send data with public void SendNamedMessage(string messageName, IReadOnlyList clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) { if (!m_NetworkManager.IsServer) { throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client"); } if (clientIds == null) { throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List"); } ulong hash = 0; switch (m_NetworkManager.NetworkConfig.RpcHashSize) { case HashSize.VarIntFourBytes: hash = XXHash.Hash32(messageName); break; case HashSize.VarIntEightBytes: hash = XXHash.Hash64(messageName); break; } if (m_NetworkManager.IsHost) { for (var i = 0; i < clientIds.Count; ++i) { if (clientIds[i] == m_NetworkManager.LocalClientId) { InvokeNamedMessage( hash, m_NetworkManager.LocalClientId, new FastBufferReader(messageStream, Allocator.None), 0 ); } } } var message = new NamedMessage { Hash = hash, SendData = messageStream }; var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) { m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size); } } } }