using FishNet.Broadcast; using FishNet.Broadcast.Helping; using FishNet.Managing.Logging; using FishNet.Managing.Utility; using FishNet.Object.Helping; using FishNet.Serializing; using FishNet.Serializing.Helping; using FishNet.Transporting; using FishNet.Utility.Extension; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Managing.Client { public sealed partial class ClientManager : MonoBehaviour { #region Private. /// /// Delegate to read received broadcasts. /// /// private delegate void ServerBroadcastDelegate(PooledReader reader); /// /// Delegates for each key. /// private readonly Dictionary> _broadcastHandlers = new Dictionary>(); /// /// Delegate targets for each key. /// private Dictionary> _handlerTargets = new Dictionary>(); #endregion /// /// Registers a method to call when a Broadcast arrives. /// /// Type of broadcast being registered. /// Method to call. public void RegisterBroadcast(Action handler) where T : struct, IBroadcast { ushort key = typeof(T).FullName.GetStableHash16(); /* Create delegate and add for * handler method. */ HashSet handlers; if (!_broadcastHandlers.TryGetValueIL2CPP(key, out handlers)) { handlers = new HashSet(); _broadcastHandlers.Add(key, handlers); } ServerBroadcastDelegate del = CreateBroadcastDelegate(handler); handlers.Add(del); /* Add hashcode of target for handler. * This is so we can unregister the target later. */ int handlerHashCode = handler.GetHashCode(); HashSet<(int, ServerBroadcastDelegate)> targetHashCodes; if (!_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes)) { targetHashCodes = new HashSet<(int, ServerBroadcastDelegate)>(); _handlerTargets.Add(key, targetHashCodes); } targetHashCodes.Add((handlerHashCode, del)); } /// /// Unregisters a method call from a Broadcast type. /// /// Type of broadcast being unregistered. /// Method to unregister. public void UnregisterBroadcast(Action handler) where T : struct, IBroadcast { ushort key = BroadcastHelper.GetKey(); /* If key is found for T then look for * the appropriate handler to remove. */ if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet handlers)) { HashSet<(int, ServerBroadcastDelegate)> targetHashCodes; if (_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes)) { int handlerHashCode = handler.GetHashCode(); ServerBroadcastDelegate result = null; foreach ((int targetHashCode, ServerBroadcastDelegate del) in targetHashCodes) { if (targetHashCode == handlerHashCode) { result = del; targetHashCodes.Remove((targetHashCode, del)); break; } } //If no more in targetHashCodes then remove from handlerTarget. if (targetHashCodes.Count == 0) _handlerTargets.Remove(key); if (result != null) handlers.Remove(result); } //If no more in handlers then remove broadcastHandlers. if (handlers.Count == 0) _broadcastHandlers.Remove(key); } } /// /// Creates a ServerBroadcastDelegate. /// /// /// /// /// private ServerBroadcastDelegate CreateBroadcastDelegate(Action handler) { void LogicContainer(PooledReader reader) { T broadcast = reader.Read(); handler?.Invoke(broadcast); } return LogicContainer; } /// /// Parses a received broadcast. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ParseBroadcast(PooledReader reader, Channel channel) { ushort key = reader.ReadUInt16(); int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel); // try to invoke the handler for that message if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet handlers)) { int readerStartPosition = reader.Position; /* //muchlater resetting the position could be better by instead reading once and passing in * the object to invoke with. */ foreach (ServerBroadcastDelegate handler in handlers) { reader.Position = readerStartPosition; handler.Invoke(reader); } } else { reader.Skip(dataLength); } } /// /// Sends a Broadcast to the server. /// /// Type of broadcast to send. /// Broadcast data being sent; for example: an instance of your broadcast type. /// Channel to send on. public void Broadcast(T message, Channel channel = Channel.Reliable) where T : struct, IBroadcast { //Check local connection state. if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to server because client is not active."); return; } using (PooledWriter writer = WriterPool.GetWriter()) { Broadcasts.WriteBroadcast(writer, message, channel); ArraySegment segment = writer.GetArraySegment(); NetworkManager.TransportManager.SendToServer((byte)channel, segment); } } } }