You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
9.3 KiB
C#

namespace Fusion.Statistics {
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.EventSystems;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
[RequireComponent(typeof(NetworkRunner))]
[AddComponentMenu("Fusion/Statistics/Fusion Statistics")]
public class FusionStatistics : SimulationBehaviour, ISpawned {
internal List<FusionStatsGraphBase> ActiveGraphs => _statsGraph;
// Setup prefabs
private GameObject _statsCanvasPrefab;
private FusionNetworkObjectStatsGraphCombine _objectGraphCombinePrefab;
private const string STATS_CANVAS_PREFAB_PATH = "FusionStatsResources/FusionStatsRenderPanel";
private const string STATS_OBJECT_COMBINE_PREFAB_PATH = "FusionStatsResources/NetworkObjectStatistics";
private List<FusionStatsGraphBase> _statsGraph;
private FusionStatsPanelHeader _header;
private FusionStatsConfig _config;
private FusionStatsCanvas _statsCanvas;
private GameObject _statsPanelObject;
private Dictionary<FusionNetworkObjectStatistics, FusionNetworkObjectStatsGraphCombine> _objectStatsGraphCombines;
[InlineHelp]
[ExpandableEnum]
[SerializeField] private RenderSimStats _statsEnabled;
[InlineHelp]
[SerializeField] private CanvasAnchor _canvasAnchor = CanvasAnchor.TopRight;
[FormerlySerializedAs("_statsConfig")] [SerializeField]
[Header("Custom configuration to override default values.\nSelect only one stat flag per configuration.")]
private List<FusionStatisticsStatCustomConfig> _statsCustomConfig = new List<FusionStatisticsStatCustomConfig>();
internal List<FusionStatisticsStatCustomConfig> StatsCustomConfig => _statsCustomConfig;
/// <summary>
/// Gets a value indicating whether the statistics panel is active.
/// </summary>
public bool IsPanelActive => _statsPanelObject != false;
[System.Serializable]
public struct FusionStatisticsStatCustomConfig {
public RenderSimStats Stat;
public float Threshold1;
public float Threshold2;
public float Threshold3;
public bool IgnoreZeroOnBuffer;
public bool IgnoreZeroOnAverageCalculation;
public int AccumulateTimeMs;
}
private void Awake() {
_statsGraph = new List<FusionStatsGraphBase>();
_statsCanvasPrefab = Resources.Load<GameObject>(STATS_CANVAS_PREFAB_PATH);
_objectGraphCombinePrefab = Resources.Load<FusionNetworkObjectStatsGraphCombine>(STATS_OBJECT_COMBINE_PREFAB_PATH);
if (_statsCanvasPrefab == null || _objectGraphCombinePrefab == null) {
Log.Error($"Error loading the required assets for Fusion Statistics, destroying stats instance. Make sure that the following paths are valid for the Fusion Statistics resource assets: \n 1. {STATS_CANVAS_PREFAB_PATH} \n 2. {STATS_OBJECT_COMBINE_PREFAB_PATH}");
Destroy(this);
}
}
void ISpawned.Spawned() {
SetupStatisticsPanel();
}
/// <summary>
/// Sets the custom configuration for Fusion Statistics.
/// </summary>
/// <param name="customConfig">The list of custom configurations for Fusion Statistics.</param>
public void SetStatsCustomConfig(List<FusionStatisticsStatCustomConfig> customConfig) {
if (customConfig == default) {
Log.Warn("Trying to set a null Fusion Statistics custom stats config");
return;
}
_statsCustomConfig = customConfig;
ApplyCustomConfig();
}
/// <summary>
/// Sets the anchor position of the Fusion Statistics canvas.
/// </summary>
/// <param name="anchor">The anchor position of the canvas (TopLeft or TopRight).</param>
public void SetCanvasAnchor(CanvasAnchor anchor) {
_canvasAnchor = anchor;
if (_statsCanvas == false) return;
_statsCanvas.SetCanvasAnchor(anchor);
}
private void ApplyCustomConfig() {
if (!_header) return;
_header.ApplyStatsConfig(_statsCustomConfig);
}
/// <summary>
/// Called from a custom editor script.
/// Will update any editor information into the fusion statistics.
/// </summary>
public void OnEditorChange() {
RenderEnabledStats();
ApplyCustomConfig();
SetCanvasAnchor(_canvasAnchor);
}
private void RenderEnabledStats() {
if (IsPanelActive == false) return;
_header.SetStatsToRender(_statsEnabled);
}
internal void UpdateStatsEnabled(RenderSimStats stats) {
_statsEnabled = stats;
}
/// <summary>
/// Sets up the statistics panel for Fusion statistic tracking.
/// </summary>
public void SetupStatisticsPanel() {
if (IsPanelActive) return;
// Was not registered on the Runner yet
if (Runner == null) {
var runner = GetComponent<NetworkRunner>();
if (runner.IsRunning == false) {
Log.Warn($"Network Runner on ({runner.gameObject}) is not yet running.");
return;
}
runner.AddGlobal(this);
// Return because when spawned is called the setup method will be called again.
return;
}
_objectStatsGraphCombines = new Dictionary<FusionNetworkObjectStatistics, FusionNetworkObjectStatsGraphCombine>();
_statsPanelObject = Instantiate(_statsCanvasPrefab, transform);
_statsCanvas = _statsPanelObject.GetComponentInChildren<FusionStatsCanvas>();
_statsCanvas.SetupStatsCanvas(this, _canvasAnchor, DestroyStatisticsPanel);
_header = _statsPanelObject.GetComponentInChildren<FusionStatsPanelHeader>();
_header.SetupHeader(Runner.LocalPlayer.ToString(), this);
_config = _statsPanelObject.GetComponentInChildren<FusionStatsConfig>(true);
_statsPanelObject.AddComponent<FusionBasicBillboard>();
ApplyCustomConfig();
Runner.AddVisibilityNodes(_statsPanelObject);
if (_statsEnabled != 0)
RenderEnabledStats();
// Setup Event system
if (!EventSystem.current) {
Log.Debug("Fusion Statistics: No event system detected, creating one.");
new GameObject("EventSystem-FusionStatistics", typeof(EventSystem), typeof(StandaloneInputModule));
}
}
/// <summary>
/// Sets the world anchor for Fusion Statistics. Set null to return to screen space overlay.
/// </summary>
/// <param name="anchor">The FusionStatsWorldAnchor component that defines the anchor object. Null to return to screen space overlay.</param>
/// <param name="scale">The scale of the statistics panel.</param>
public void SetWorldAnchor(FusionStatsWorldAnchor anchor, float scale) {
_config.SetWorldCanvasScale(scale);
if (anchor == null) {
_config.ResetToCanvasAnchor();
} else {
_config.SetWorldAnchor(anchor.transform);
}
}
/// <summary>
/// Destroys the statistics panel.
/// </summary>
public void DestroyStatisticsPanel() {
var keys = _objectStatsGraphCombines?.Keys.ToArray();
if (keys != null) {
foreach (var fusionNetworkObjectStatistics in keys) {
MonitorNetworkObject(fusionNetworkObjectStatistics.NetworkObject, fusionNetworkObjectStatistics, false);
}
}
_objectStatsGraphCombines?.Clear();
_statsGraph.Clear();
Destroy(_statsPanelObject);
_statsPanelObject = null;
if (Runner) {
if (Runner.TryGetFusionStatistics(out var statisticsManager)) {
statisticsManager.ObjectStatisticsManager.ClearMonitoredNetworkObjects();
}
}
}
public bool MonitorNetworkObject(NetworkObject networkObject, FusionNetworkObjectStatistics objectStatisticsInstance, bool monitor) {
if (Runner.TryGetFusionStatistics(out var statisticsManager)) {
statisticsManager.ObjectStatisticsManager.MonitorNetworkObjectStatistics(networkObject.Id, monitor);
}
if (monitor) {
// If Id already monitored on the stats, return false to destroy the object statistics instance.
if (_objectStatsGraphCombines.ContainsKey(objectStatisticsInstance))
return false;
var graphCombine = Instantiate(_objectGraphCombinePrefab, _header.ContentRect);
graphCombine.SetupNetworkObject(networkObject, this, objectStatisticsInstance);
_objectStatsGraphCombines.Add(objectStatisticsInstance, graphCombine);
} else {
if (_objectStatsGraphCombines.Remove(objectStatisticsInstance, out var graphCombine)) {
Destroy(graphCombine.gameObject);
Destroy(objectStatisticsInstance);
}
}
return true;
}
void UpdateAllGraphs(FusionStatisticsManager statisticsManager) {
var now = DateTime.Now;
foreach (var statsGraphBase in _statsGraph) {
statsGraphBase.UpdateGraph(Runner, statisticsManager, ref now);
}
}
public void RegisterGraph(FusionStatsGraphBase graph) {
_statsGraph.Add(graph);
}
public void UnregisterGraph(FusionStatsGraphBase graph) {
_statsGraph.Remove(graph);
}
private void Update() {
// Safety exit
if (!Runner) return;
Profiler.BeginSample("Fusion Statistics Update Graph");
// Collect and update
if (Runner.TryGetFusionStatistics(out var statisticsManager)) {
UpdateAllGraphs(statisticsManager);
}
Profiler.EndSample();
}
}
}