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);
}
}
}
}
}