#if UNITY_EDITOR || DEVELOPMENT_BUILD #define DEVELOPMENT #endif using System; using System.Collections.Generic; using FishNet.Editing; using FishNet.Transporting; using GameKit.Dependencies.Utilities; using Unity.Profiling; using UnityEngine; namespace FishNet.Managing.Statistic { [Serializable] public partial class NetworkTrafficStatistics { #region Types. public enum EnabledMode : byte { /// /// Not enabled. /// Disabled = 0, /// /// Enabled for development only. /// Development = 1, /// /// Enabled for release and development. /// Release = 2, /// /// Enable for release, development, and headless. /// Headless = 3, } #endregion #region Public. /// /// Called when NetworkTraffic is updated. /// /// This API is for internal use and may change at any time. public event NetworkTrafficUpdateDel OnNetworkTraffic; public delegate void NetworkTrafficUpdateDel(uint tick, BidirectionalNetworkTraffic serverTraffic, BidirectionalNetworkTraffic clientTraffic); #endregion #region Serialized. /// /// When to enable network traffic statistics. /// public EnabledMode EnableMode => _enableMode; [Tooltip("When to enable network traffic statistics.")] [SerializeField] private EnabledMode _enableMode = EnabledMode.Disabled; /// /// True to update client statistics. /// public bool UpdateClient { get => _updateClient; private set => _updateClient = value; } [Tooltip("True to update client statistics.")] [SerializeField] private bool _updateClient; /// /// Sets UpdateClient value. /// /// public void SetUpdateClient(bool update) => UpdateClient = update; /// /// True to update client statistics. /// public bool UpdateServer { get => _updateServer; private set => _updateServer = value; } [Tooltip("True to update server statistics.")] [SerializeField] private bool _updateServer; /// /// Sets UpdateServer value. /// /// public void SetUpdateServer(bool update) => UpdateServer = update; #endregion #region Private. /// /// NetworkManager for this statistics. /// private NetworkManager _networkManager; /// /// Latest tick statistics for data on the local server. /// private BidirectionalNetworkTraffic _serverTraffic; /// /// Latest tick statistics for data on the local client. /// private BidirectionalNetworkTraffic _clientTraffic; /// /// Size suffixes as text. /// private static readonly string[] _sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; /// /// True if initialized. /// private bool _initializedOnce; #endregion #region Private Profiler Markers private static readonly ProfilerMarker _pm_OnPreTick = new("NetworkTrafficStatistics.TimeManager_OnPreTick()"); #endregion #region Consts. /// /// Id for unspecified packets. /// internal const PacketId UNSPECIFIED_PACKETID = (PacketId)ushort.MaxValue; #endregion internal void InitializeOnce_Internal(NetworkManager manager) { if (_initializedOnce) return; /* Only subscribe if enabled. Always unsubscribe even if not enabled -- doing so is safe. */ if (!IsEnabled()) return; manager.TimeManager.OnPreTick += TimeManager_OnPreTick; _networkManager = manager; /* Do not bother caching once destroyed. Losing a single instance of each * isn't going to hurt anything, and if destroyed everything is probably * shutting down anyway. */ _serverTraffic = ResettableObjectCaches.Retrieve(); _clientTraffic = ResettableObjectCaches.Retrieve(); _initializedOnce = true; } /// /// Called before the TimeManager ticks. /// private void TimeManager_OnPreTick() { using (_pm_OnPreTick.Auto()) { /* Since we are sending last ticks data at the end of the tick, * the tick used will always be 1 less than current tick. */ long trafficTick = _networkManager.TimeManager.LocalTick - 1; //Invalid tick. if (trafficTick <= 0) return; if (_networkManager.IsClientStarted || _networkManager.IsServerStarted) OnNetworkTraffic?.Invoke((uint)trafficTick, _serverTraffic, _clientTraffic); /* It's important to remember that after actions are invoked * the traffic stat fields are reset. Each listener should use * the MultiwayTrafficCollection.Clone method to get a copy, * and should cache that copy when done. */ _clientTraffic.Reinitialize(); _serverTraffic.Reinitialize(); } } /// /// Called when a packet bundle is received. This is any number of packets bundled into a single transmission. /// internal void PacketBundleReceived(bool asServer) { //Debug.LogError("Inbound and outbound bidirection datas should count up how many packet bundles are received. This is so the bundle headers can be calculated appropriately."); } /// /// Called when data is being sent from the local server or client for a specific packet. /// internal void AddOutboundPacketIdData(PacketId typeSource, string details, int bytes, GameObject gameObject, bool asServer) { if (bytes <= 0) return; if (TryGetBidirectionalNetworkTraffic(asServer, out BidirectionalNetworkTraffic networkTraffic)) networkTraffic.OutboundTraffic.AddPacketIdData(typeSource, details, (ulong)bytes, gameObject); } /// /// Called when data is being sent from the local server or client as it's going to the socket. /// internal void AddOutboundSocketData(ulong bytes, bool asServer) { if (bytes > int.MaxValue) bytes = int.MaxValue; else if (bytes <= 0) return; if (TryGetBidirectionalNetworkTraffic(asServer, out BidirectionalNetworkTraffic networkTraffic)) networkTraffic.OutboundTraffic.AddSocketData(bytes); } /// /// Called when data is being received on the local server or client for a specific packet. /// internal void AddInboundPacketIdData(PacketId typeSource, string details, int bytes, GameObject gameObject, bool asServer) { if (bytes <= 0) return; if (TryGetBidirectionalNetworkTraffic(asServer, out BidirectionalNetworkTraffic networkTraffic)) networkTraffic.InboundTraffic.AddPacketIdData(typeSource, details, (ulong)bytes, gameObject); } /// /// Called when data is being received on the local server or client as it's coming from the socket. /// internal void AddInboundSocketData(ulong bytes, bool asServer) { if (bytes > int.MaxValue) bytes = int.MaxValue; else if (bytes <= 0) return; if (TryGetBidirectionalNetworkTraffic(asServer, out BidirectionalNetworkTraffic networkTraffic)) networkTraffic.InboundTraffic.AddSocketData(bytes); } /// /// Gets current statistics for server or client. /// private bool TryGetBidirectionalNetworkTraffic(bool asServer, out BidirectionalNetworkTraffic networkTraffic) { networkTraffic = asServer ? _serverTraffic : _clientTraffic; return networkTraffic != null; } /// /// Formats passed in bytes value to the largest possible data type with 2 decimals. /// public static string FormatBytesToLargest(float bytes) { string[] units = { "B", "kB", "MB", "GB", "TB", "PB" }; int unitIndex = 0; while (bytes >= 1024 && unitIndex < units.Length - 1) { bytes /= 1024; unitIndex++; } return $"{bytes:0.00} {units[unitIndex]}"; } /// /// Returns if enabled or not. /// public bool IsEnabled() { if (_enableMode == EnabledMode.Disabled) return false; int modeValue = (int)_enableMode; //Never enabled for server builds. #if UNITY_SERVER return modeValue >= (int)EnabledMode.Headless; #endif if (_enableMode == EnabledMode.Disabled) return false; // If not in dev mode then return true if to run in release. #if !DEVELOPMENT return modeValue >= (int)EnabledMode.Release; // Always run in dev mode if not disabled. #else return true; #endif } } }