Files
TheDeclineOfWarriors/Assets/FishNet/Runtime/Managing/Statistic/NetworkTrafficStatistics.cs
T
2026-04-07 03:11:52 +07:00

291 lines
10 KiB
C#

#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEVELOPMENT
#endif
using System;
using System.Collections.Generic;
using FishNet.Editing;
using FishNet.Editing.NetworkProfiler;
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
{
/// <summary>
/// Not enabled.
/// </summary>
Disabled = 0,
/// <summary>
/// Enabled for development only.
/// </summary>
Development = 1,
/// <summary>
/// Enabled for release and development.
/// </summary>
Release = 2,
/// <summary>
/// Enable for release, development, and headless.
/// </summary>
Headless = 3,
}
#endregion
#region Public.
/// <summary>
/// Called when NetworkTraffic is updated.
/// </summary>
/// <remarks>This API is for internal use and may change at any time.</remarks>
public event NetworkTrafficUpdateDel OnNetworkTraffic;
public delegate void NetworkTrafficUpdateDel(uint tick, BidirectionalNetworkTraffic serverTraffic, BidirectionalNetworkTraffic clientTraffic);
#endregion
#region Serialized.
/// <summary>
/// When to enable network traffic statistics.
/// </summary>
public EnabledMode EnableMode => _enableMode;
[Tooltip("When to enable network traffic statistics.")]
[SerializeField]
private EnabledMode _enableMode = EnabledMode.Disabled;
/// <summary>
/// True to update client statistics.
/// </summary>
public bool UpdateClient
{
get => _updateClient;
private set => _updateClient = value;
}
[Tooltip("True to update client statistics.")]
[SerializeField]
private bool _updateClient;
/// <summary>
/// Sets UpdateClient value.
/// </summary>
/// <param name = "update"></param>
public void SetUpdateClient(bool update) => UpdateClient = update;
/// <summary>
/// True to update client statistics.
/// </summary>
public bool UpdateServer
{
get => _updateServer;
private set => _updateServer = value;
}
[Tooltip("True to update server statistics.")]
[SerializeField]
private bool _updateServer;
/// <summary>
/// Sets UpdateServer value.
/// </summary>
/// <param name = "update"></param>
public void SetUpdateServer(bool update) => UpdateServer = update;
#endregion
#region Private.
/// <summary>
/// NetworkManager for this statistics.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// Latest tick statistics for data on the local server.
/// </summary>
private BidirectionalNetworkTraffic _serverTraffic;
/// <summary>
/// Latest tick statistics for data on the local client.
/// </summary>
private BidirectionalNetworkTraffic _clientTraffic;
/// <summary>
/// Size suffixes as text.
/// </summary>
private static readonly string[] _sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
/// <summary>
/// True if initialized.
/// </summary>
private bool _initializedOnce;
#endregion
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_OnPreTick = new("NetworkTrafficStatistics.TimeManager_OnPreTick()");
#endregion
#region Consts.
/// <summary>
/// Id for unspecified packets.
/// </summary>
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<BidirectionalNetworkTraffic>.Retrieve();
_clientTraffic = ResettableObjectCaches<BidirectionalNetworkTraffic>.Retrieve();
_initializedOnce = true;
}
/// <summary>
/// Called before the TimeManager ticks.
/// </summary>
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();
}
}
/// <summary>
/// Called when a packet bundle is received. This is any number of packets bundled into a single transmission.
/// </summary>
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.");
}
/// <summary>
/// Called when data is being sent from the local server or client for a specific packet.
/// </summary>
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);
}
/// <summary>
/// Called when data is being sent from the local server or client as it's going to the socket.
/// </summary>
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);
}
/// <summary>
/// Called when data is being received on the local server or client for a specific packet.
/// </summary>
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);
}
/// <summary>
/// Called when data is being received on the local server or client as it's coming from the socket.
/// </summary>
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);
}
/// <summary>
/// Gets current statistics for server or client.
/// </summary>
private bool TryGetBidirectionalNetworkTraffic(bool asServer, out BidirectionalNetworkTraffic networkTraffic)
{
networkTraffic = asServer ? _serverTraffic : _clientTraffic;
return networkTraffic != null;
}
/// <summary>
/// Formats passed in bytes value to the largest possible data type with 2 decimals.
/// </summary>
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]}";
}
/// <summary>
/// Returns if enabled or not.
/// </summary>
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
}
}
}