namespace Fusion {
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using StatsInternal;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif
///
/// Creates and controls a Canvas with one or multiple telemetry graphs. Can be created as a scene object or prefab,
/// or be created at runtime using the methods. If created as the child of a
/// then will automatically be set to true.
///
[ScriptHelp(BackColor = ScriptHeaderBackColor.Olive)]
[ExecuteAlways]
public partial class FusionStats : Fusion.Behaviour {
///
/// Options for displaying stats as screen overlays or world GameObjects.
///
public enum StatCanvasTypes {
Overlay,
GameObject,
}
///
/// Predefined layout default options.
///
public enum DefaultLayouts {
Custom,
Left,
Right,
UpperLeft,
UpperRight,
Full,
}
///
/// Interval (in seconds) between Graph redraws. Higher values (longer intervals) reduce CPU overhead, draw calls and garbage collection.
///
[InlineHelp]
[Unit(Units.Seconds)]//, DecimalPlaces = 2)]
[Range(0f, 1f)]
public float RedrawInterval = .1f;
///
/// Selects between displaying Canvas as screen overlay, or a world GameObject.
///
[Header("Layout")]
[InlineHelp]
[SerializeField]
StatCanvasTypes _canvasType;
///
/// Selects between displaying Canvas as screen overlay, or a world GameObject.
///
public StatCanvasTypes CanvasType {
get => _canvasType;
set {
_canvasType = value;
//_canvas.enabled = false;
DirtyLayout(2);
}
}
///
/// Enables text labels for the control buttons.
///
[InlineHelp]
[SerializeField]
bool _showButtonLabels = true;
///
/// Enables text labels for the control buttons.
///
public bool ShowButtonLabels {
get => _showButtonLabels;
set {
_showButtonLabels = value;
DirtyLayout();
}
}
///
/// Height of button region at top of the stats panel. Values less than or equal to 0 hide the buttons, and reduce the header size.
///
[InlineHelp]
[SerializeField]
[Range(0, 200)]
int _maxHeaderHeight = 70;
///
/// Height of button region at top of the stats panel. Values less than or equal to 0 hide the buttons, and reduce the header size.
///
public int MaxHeaderHeight {
get => _maxHeaderHeight;
set {
_maxHeaderHeight = value;
DirtyLayout();
}
}
///
/// The size of the canvas when is set to .
///
[InlineHelp]
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.GameObject, Hide = true)]
[Range(0, 20f)]
public float CanvasScale = 5f;
///
/// The distance on the Z axis the canvas will be positioned. Allows moving the canvas in front of or behind the parent GameObject.
///
[InlineHelp]
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.GameObject, Hide = true)]
[Range(-10, 10f)]
public float CanvasDistance = 0f;
///
/// The Rect which defines the position of the stats canvas on a GameObject. Sizes are normalized percentages.(ranges of 0f-1f).
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.GameObject, Hide = true)]
[NormalizedRect(aspectRatio: 1)]
Rect _gameObjectRect = new Rect(0.0f, 0.0f, 0.3f, 1.0f);
public Rect GameObjectRect {
get => _gameObjectRect;
set {
_gameObjectRect = value;
DirtyLayout();
}
}
///
/// The Rect which defines the position of the stats canvas overlay on the screen. Sizes are normalized percentages.(ranges of 0f-1f).
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.Overlay, Hide = true)]
[NormalizedRect]
Rect _overlayRect = new Rect(0.0f, 0.0f, 0.3f, 1.0f);
public Rect OverlayRect {
get => _overlayRect;
set {
_overlayRect = value;
DirtyLayout();
}
}
///
/// value which all child components will use if their value is set to Auto.
///
[Header("Fusion Graphs Layout")]
[InlineHelp]
[SerializeField]
FusionStatsGraph.Layouts _defaultLayout;
public FusionStatsGraph.Layouts DefaultLayout {
get => _defaultLayout;
set {
_defaultLayout = value;
DirtyLayout();
}
}
///
/// UI Text on FusionGraphs can only overlay the bar graph if the canvas is perfectly facing the camera.
/// Any other angles will result in ZBuffer fighting between the text and the graph bar shader.
/// For uses where perfect camera billboarding is not possible (such as VR), this toggle prevents FusionGraph layouts being used where text and graphs overlap.
/// Normally leave this unchecked, unless you are experiencing corrupted text rendering.
///
[InlineHelp]
[SerializeField]
bool _noTextOverlap;
public bool NoTextOverlap {
get => _noTextOverlap;
set {
_noTextOverlap = value;
DirtyLayout();
}
}
///
/// Disables the bar graph in , and uses a text only layout.
/// Enable this if is not rendering correctly in VR.
///
[InlineHelp]
[SerializeField]
bool _noGraphShader;
public bool NoGraphShader {
get => _noGraphShader;
set {
_noGraphShader = value;
DirtyLayout();
}
}
///
/// Force graphs layout to use X number of columns.
///
[InlineHelp]
[Range(0, 16)]
public int GraphColumnCount = 1;
///
/// If is set to zero, then columns will automatically be added as needed to limit graphs to this width or less.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(GraphColumnCount), 0)]
[Range(30, SCREEN_SCALE_W)]
int _graphMaxWidth = SCREEN_SCALE_W / 4;
///
/// If is set to zero, then columns will automatically be added as needed to limit graphs to this width or less.
///
public int GraphMaxWidth {
get => _graphMaxWidth;
set {
_graphMaxWidth = value;
DirtyLayout();
}
}
[Header("Network Object Stats")] [SerializeField]
private int _playerRef;
public PlayerRef PlayerRef {
get => PlayerRef.FromIndex(_playerRef);
set {
_playerRef = value.AsIndex;
// TODO: Not needed?
DirtyLayout();
}
}
///
/// Enables/Disables all NetworkObject related elements.
///
[Header("Network Object Stats")]
[InlineHelp]
[SerializeField]
bool _enableObjectStats;
public bool EnableObjectStats {
get => _enableObjectStats;
set {
_enableObjectStats = value;
DirtyLayout();
}
}
///
/// The source for any specific telemetry.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
internal NetworkObject _object;
///
/// Returns the set serialized for this stat window. If that is null, returns the static MonitoredNetworkObject,
/// which can be set using .
///
public NetworkObject Object {
get {
if (_object) {
return _object;
}
// no local object set - fallback to the global one.
if (_runner == null) {
// Will not be ble to lookup a network object without a valid runner. null for now.
return default;
}
if (EnableObjectStats) {
return _runner.FindObject(MonitoredNetworkObjectId);
}
return default;
}
}
///
/// Height of Object title region at top of the stats panel.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[Range(0, 200)]
int _objectTitleHeight = 48;
public int ObjectTitleHeight {
get => _objectTitleHeight;
set {
_objectTitleHeight = value;
DirtyLayout();
}
}
///
/// Height of Object info region at top of the stats panel.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[Range(0, 200)]
int _objectIdsHeight = 60;
public int ObjectIdsHeight {
get => _objectIdsHeight;
set {
_objectIdsHeight = value;
DirtyLayout();
}
}
///
/// Height of Object info region at top of the stats panel.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[Range(0, 200)]
int _objectMetersHeight = 90;
public int ObjectMetersHeight {
get => _objectMetersHeight;
set {
_objectIdsHeight = value;
DirtyLayout();
}
}
///
/// The currently associated with this component and graphs.
///
[Header("Data")]
[SerializeField]
[InlineHelp]
[ReadOnly]
NetworkRunner _runner;
public NetworkRunner Runner {
get {
if (Application.isPlaying == false) {
return null;
}
// Be sure the current runner is the correct runner.
this.ValidateRunner(_runner);
return _runner;
}
}
public void SetRunner(NetworkRunner value) {
if (_runner == value) {
return;
}
// Keep track of which runners have active stats windows - needed so pause/unpause can affect all (since pause affects other panels)
DisassociateWithRunner(_runner);
_runner = value;
AssociateWithRunner(value);
UpdateTitle();
}
///
/// Editor-Only. If no is set, this FusionStats will attempt to connect to the NetworkRunner for the current selected GameObject.
///
[InlineHelp]
[SerializeField]
public bool RunnerFromSelected;
///
/// Initializes a for all available stats, even if not initially included.
/// If disabled, graphs added after initialization will be added to the bottom of the interface stack.
///
[InlineHelp]
public bool InitializeAllGraphs;
///
/// When is null and no exists in the current scene, FusionStats will continuously attempt to find and connect to an active which matches these indicated modes.
///
[InlineHelp]
[ExpandableEnum(ShowInlineHelp = true)]
public SimulationModes ConnectTo = SimulationModes.Host | SimulationModes.Server | SimulationModes.Client;
///
/// Selects which NetworkObject stats should be displayed.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask _includedObjStats = new (typeof(NetworkObjectStats).GetDefaults);
///
/// Selects which NetConnection stats should be displayed.
///
[InlineHelp]
[SerializeField]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask _includedNetStats = new(typeof(SimulationConnectionStats).GetDefaults);
///
/// Selects which Simulation stats should be displayed.
///
[InlineHelp]
[SerializeField]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask _includedSimStats = new(typeof(SimulationStats).GetDefaults);
///
/// Automatically destroys this GameObject if the associated runner is null or inactive.
/// Otherwise attempts will continuously be made to find an new active runner which is running in specified by , and connect to that.
///
[Header("Life-Cycle")]
[InlineHelp]
[SerializeField]
public bool AutoDestroy;
///
/// Only one instance with the can exist if there is no associated . Will destroy any additional instances on Awake.
///
[InlineHelp]
[SerializeField]
public bool EnforceSingle = true;
///
/// Identifier used to enforce single instances of when running in Multi-Peer mode.
/// When is enabled, only one instance of with this GUID will be active at any time,
/// regardless of the total number of peers running.
///
[InlineHelp]
[DrawIf(nameof(EnforceSingle))]
[SerializeField]
public string Guid;
///
/// The font to be used for all non-number labels.
///
[Header("Customization")]
[SerializeField]
public Font LabelFont;
///
/// The font to be used for all number labels.
///
[InlineHelp]
[SerializeField]
public Font ValueFont;
[SerializeField][HideInInspector]
internal Shader GraphShader;
///
/// Shows/hides controls in the inspector for defining element colors.
///
[InlineHelp]
[SerializeField]
private bool _modifyColors;
///
/// The color used for the telemetry graph data.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorGood = new Color(0.1f, 0.5f, 0.1f, 1.0f);
///
/// The color used for the telemetry graph data.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorWarn = new Color(0.75f, 0.75f, 0.2f, 1.0f);
///
/// The color used for the telemetry graph data.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorBad = new Color(0.9f, 0.2f, 0.2f, 1.0f);
///
/// The color used for the telemetry graph data.
///
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorFlag = new Color(0.8f, 0.75f, 0.0f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _fontColor = new Color(1.0f, 1.0f, 1.0f, 1f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color PanelColor = new Color(0.3f, 0.3f, 0.3f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _simDataBackColor = new Color(0.1f, 0.08f, 0.08f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _netDataBackColor = new Color(0.15f, 0.14f, 0.09f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _objDataBackColor = new Color(0.0f, 0.2f, 0.4f, 1.0f);
// IFusionStats interface requirements
public Color FontColor => _fontColor;
public Color GraphColorGood => _graphColorGood;
public Color GraphColorWarn => _graphColorWarn;
public Color GraphColorBad => _graphColorBad;
public Color GraphColorFlag => _graphColorFlag;
public Color SimDataBackColor => _simDataBackColor;
public Color NetDataBackColor => _netDataBackColor;
public Color ObjDataBackColor => _objDataBackColor;
public Rect CurrentRect => _canvasType == StatCanvasTypes.GameObject ? _gameObjectRect : _overlayRect;
Font _font;
bool _hidden;
bool _paused;
int _layoutDirty;
bool _activeDirty;
double _currentDrawTime;
double _delayDrawUntil;
#if UNITY_EDITOR
void OnValidate() {
if (EnforceSingle && Guid == "") {
Guid = System.Guid.NewGuid().ToString().Substring(0, 13);
}
_activeDirty = true;
if (_layoutDirty <= 0) {
_layoutDirty = 2;
// Some aspects of Layout will throw warnings if run from OnValidate, so defer.
// Stop deferring when entering play mode, as this will cause null errors (thanks unity).
if (Application.isPlaying) {
UnityEditor.EditorApplication.delayCall += CalculateLayout;
} else {
UnityEditor.EditorApplication.delayCall -= CalculateLayout;
}
}
}
void Reset() {
ResetLayout();
}
#endif
///
/// Resets the layout of the stats panel to the default layout for the current .
///
/// Optional parameter to enable or disable object stats. If null, the current setting is used.
/// Optional parameter to set the layout for the object stats. If null, the current setting is used.
/// Optional parameter to set the layout for the screen stats. If null, the current setting is used.
public void ResetLayout(bool? enableObjectStats = null, DefaultLayouts? objectLayout = null, DefaultLayouts? screenLayout = null) {
// Destroy existing built graphs
var canv = GetComponentInChildren