namespace Fusion.Statistics { using UnityEngine; using UnityEngine.UI; using System; using System.Collections.Generic; /// /// List of all simulation stats able to render on a graph. /// [Flags] public enum RenderSimStats { /// /// Incoming packets. /// InPackets = 1 << 0, /// /// Outgoing packets. /// OutPackets = 1 << 1, /// /// Round Trip Time. /// RTT = 1 << 2, /// /// In Bandwidth in Bytes. /// InBandwidth = 1 << 3, /// /// Out Bandwidth in Bytes. /// OutBandwidth = 1 << 4, /// /// Amount of re-simulation ticks executed. /// Resimulations = 1 << 5, /// /// Amount of forward ticks executed. /// ForwardTicks = 1 << 6, /// /// Average measured time between two input/state packets (from same client) received by the server. /// InputReceiveDelta = 1 << 7, /// /// Time sync abruptly reset count. /// TimeResets = 1 << 8, /// /// Average measured time between two state packets (from server) received by the client. /// StateReceiveDelta = 1 << 9, /// /// Average buffering for prediction. /// SimulationTimeOffset = 1 << 10, /// /// How much the simulation is currently sped up / slowed down. /// SimulationSpeed = 1 << 11, /// /// Average buffering for interpolation. /// InterpolationOffset = 1 << 12, /// /// How much interpolation is currently sped up / slowed down. /// InterpolationSpeed = 1 << 13, /// /// Input in bandwidth. /// InputInBandwidth = 1 << 14, /// /// Input out bandwidth. /// InputOutBandwidth = 1 << 15, /// /// Average size for received packet. /// AverageInPacketSize = 1 << 16, /// /// Average size for sent packet. /// AverageOutPacketSize = 1 << 17, /// /// Amount of object updates received. /// InObjectUpdates = 1 << 18, /// /// Amount of object updates sent. /// OutObjectUpdates = 1 << 19, /// /// Memory in use for the object allocator. /// ObjectsAllocatedMemoryInUse = 1 << 20, /// /// Memory in use for the general allocator. /// GeneralAllocatedMemoryInUse = 1 << 21, /// /// Memory free for the object allocator. /// ObjectsAllocatedMemoryFree = 1 << 22, /// /// Memory free for the general allocator. /// GeneralAllocatedMemoryFree = 1 << 23, /// /// Amount of written words. How many networked changes are being sent. /// WordsWrittenCount = 1 << 24, /// /// Size of all last written words in Bytes. /// WordsWrittenSize = 1 << 25, /// /// Amount of read words. How many networked changes are being received. /// WordsReadCount = 1 << 26, /// /// Size of all last read words in Bytes. /// WordsReadSize = 1 << 27, } public class FusionStatsPanelHeader : MonoBehaviour { public event Action OnRenderStatsUpdate; [SerializeField] private Text _statsHeaderTitle; [SerializeField] private Dropdown _statsDropdown; [SerializeField] private FusionStatsGraphDefault _defaultGraphPrefab; public RectTransform ContentRect; private Dictionary _defaultStatsGraph; private FusionStatistics _fusionStatistics; private RenderSimStats _statsToRender; public void SetupHeader(string title, FusionStatistics fusionStatistics) { _statsHeaderTitle.text = title; _fusionStatistics = fusionStatistics; SetupDropdown(); } private void SetupDropdown() { _defaultStatsGraph = new Dictionary(); List options = new List(); options.Add(new Dropdown.OptionData("Toggle Stats")); foreach (var option in Enum.GetNames(typeof(RenderSimStats))) { options.Add(new Dropdown.OptionData(option)); } _statsDropdown.options = options; _statsDropdown.onValueChanged.AddListener(OnDropDownChanged); } internal void SetStatsToRender(RenderSimStats stats) { // Early exit if (stats == _statsToRender) return; // For each possible stat foreach (RenderSimStats renderSimStat in Enum.GetValues(typeof(RenderSimStats))) { // If it is set on the stats received if ((stats & renderSimStat) == renderSimStat) { // And if it is not already set on the stats to render... add it if ((_statsToRender & renderSimStat) != renderSimStat) { AddStat(renderSimStat); } } // else if is NOT set on the stats received else { // And if it is set on the stats to render... remove if ((_statsToRender & renderSimStat) == renderSimStat) { RemoveStat(renderSimStat); } } } // Make sure they are equal now. _statsToRender = stats; } private void AddStat(RenderSimStats stat) { _statsToRender |= stat; // Set the flag InstantiateStatGraph(stat); InvokeRenderStatsUpdate(); } private void RemoveStat(RenderSimStats stat) { _statsToRender &= ~stat; // Removed the flag DestroyStatGraph(stat); InvokeRenderStatsUpdate(); } private void InvokeRenderStatsUpdate() { OnRenderStatsUpdate?.Invoke(); } private void OnDropDownChanged(int arg0) { if (arg0 <= 0) return; // No stat selected. arg0--; // Remove the first label RenderSimStats stat = (RenderSimStats)(1 << arg0); if ((_statsToRender & stat) == stat) { RemoveStat(stat); } else { AddStat(stat); } // Set the first label again. _statsDropdown.SetValueWithoutNotify(0); _fusionStatistics.UpdateStatsEnabled(_statsToRender); } private void InstantiateStatGraph(RenderSimStats stat) { FusionStatsGraphDefault graph = Instantiate(_defaultGraphPrefab, ContentRect); graph.SetupDefaultGraph(stat); TryApplyCustomStatConfig(graph); _defaultStatsGraph.Add(stat, graph); } private void DestroyStatGraph(RenderSimStats stat) { if (_defaultStatsGraph.Remove(stat, out var statsGraphDefault)) { Destroy(statsGraphDefault.gameObject); } } private void TryApplyCustomStatConfig(FusionStatsGraphDefault graph) { // Need to do this way because unity cannot serialize a dictionary. foreach (var config in _fusionStatistics.StatsCustomConfig) { if (config.Stat == graph.Stat) { ApplyCustomStatsConfig(graph, config); } } } private void ApplyCustomStatsConfig(FusionStatsGraphDefault graph, FusionStatistics.FusionStatisticsStatCustomConfig config) { graph.ApplyCustomStatsConfig(config); } internal void ApplyStatsConfig(List statsConfig) { foreach (var config in statsConfig) { if (_defaultStatsGraph.TryGetValue(config.Stat, out var graph)) { ApplyCustomStatsConfig(graph, config); } } } } }