mirror of
https://github.com/ConjureETS/ProjectClubCore.git
synced 2026-03-24 17:40:59 +00:00
1626 lines
51 KiB
C#
1626 lines
51 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UI = UnityEngine.UI;
|
|
using Fusion;
|
|
using Stats = Fusion.Simulation.Statistics;
|
|
using Fusion.StatsInternal;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="Create"/> methods. If created as the child of a <see cref="NetworkObject"/>
|
|
/// then <see cref="EnableObjectStats"/> will automatically be set to true.
|
|
/// </summary>
|
|
[ScriptHelp(BackColor = EditorHeaderBackColor.Olive)]
|
|
[ExecuteAlways]
|
|
public class FusionStats : Fusion.Behaviour {
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
[MenuItem("Fusion/Add Fusion Stats", false, 1000)]
|
|
[MenuItem("GameObject/Fusion/Add Fusion Stats")]
|
|
public static void AddFusionStatsToScene() {
|
|
|
|
var selected = Selection.activeGameObject;
|
|
|
|
if (selected && PrefabUtility.IsPartOfPrefabAsset(selected)) {
|
|
Debug.LogWarning("Open prefabs before running 'Add Fusion Stats' on them.");
|
|
return;
|
|
}
|
|
|
|
var fs = new GameObject("FusionStats");
|
|
|
|
if (selected) {
|
|
fs.transform.SetParent(Selection.activeGameObject.transform);
|
|
}
|
|
|
|
fs.transform.localPosition = default;
|
|
fs.transform.localRotation = default;
|
|
fs.transform.localScale = Vector3.one;
|
|
|
|
fs.AddComponent<FusionStatsBillboard>();
|
|
fs.AddComponent<FusionStats>();
|
|
EditorGUIUtility.PingObject(fs.gameObject);
|
|
Selection.activeGameObject = fs.gameObject;
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Options for displaying stats as screen overlays or world GameObjects.
|
|
/// </summary>
|
|
public enum StatCanvasTypes {
|
|
Overlay,
|
|
GameObject,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Predefined layout default options.
|
|
/// </summary>
|
|
public enum DefaultLayouts {
|
|
Custom,
|
|
Left,
|
|
Right,
|
|
UpperLeft,
|
|
UpperRight,
|
|
Full,
|
|
}
|
|
|
|
|
|
// Lookup for all FusionStats associated with active runners.
|
|
static Dictionary<NetworkRunner, List<FusionStats>> _statsForRunnerLookup = new Dictionary<NetworkRunner, List<FusionStats>>();
|
|
|
|
// Record of active SimStats, used to prevent more than one _guid version from existing (in the case of SimStats existing in a scene that gets cloned in Multi-Peer).
|
|
static Dictionary<string, FusionStats> _activeGuids = new Dictionary<string, FusionStats>();
|
|
|
|
// Added to make calling by reflection cleaner internally. Used in RunnerVisibilityControls.
|
|
internal static FusionStats CreateInternal(NetworkRunner runner = null, DefaultLayouts layout = DefaultLayouts.Left, Stats.NetStatFlags? netStatsMask = null, Stats.SimStatFlags? simStatsMask = null) {
|
|
return Create(null, runner, layout, layout, netStatsMask, simStatsMask);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new GameObject with a <see cref="FusionStats"/> component, attaches it to any supplied parent, and generates Canvas/Graphs.
|
|
/// </summary>
|
|
/// <param name="runner"></param>
|
|
/// <param name="parent">Generated FusionStats component and GameObject will be added as a child of this transform.</param>
|
|
/// <param name="objectLayout">Uses a predefined position.</param>
|
|
/// <param name="netStatsMask">The network stats to be enabled. If left null, default statistics will be used.</param>
|
|
/// <param name="simStatsMask">The simulation stats to be enabled. If left null, default statistics will be used.</param>
|
|
/// <returns></returns>
|
|
public static FusionStats Create(Transform parent = null, NetworkRunner runner = null, DefaultLayouts? screenLayout = null, DefaultLayouts? objectLayout = null, Stats.NetStatFlags? netStatsMask = null, Stats.SimStatFlags? simStatsMask = null) {
|
|
|
|
var go = new GameObject($"{nameof(FusionStats)} {(runner ? runner.name : "null")}");
|
|
FusionStats stats;
|
|
if (parent) {
|
|
go.transform.SetParent(parent);
|
|
}
|
|
|
|
stats = go.AddComponent<FusionStats>();
|
|
|
|
stats.ResetInternal(null, netStatsMask, simStatsMask, objectLayout, screenLayout);
|
|
|
|
stats.Runner = runner;
|
|
|
|
if (runner != null) {
|
|
stats.AutoDestroy = true;
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
public static Stats.NetStatFlags DefaultNetStatsMask => Stats.NetStatFlags.RoundTripTime | Stats.NetStatFlags.ReceivedPacketSizes | Stats.NetStatFlags.SentPacketSizes;
|
|
|
|
/// <summary>
|
|
/// The gets the default SimStats. Some are only intended for Fusion internal development and aren't useful to users.
|
|
/// </summary>
|
|
#if FUSION_DEV
|
|
public const Stats.SimStatFlags DefaultSimStatsMask = (Stats.SimStatFlags)(-1);
|
|
#else
|
|
public const Stats.SimStatFlags DefaultSimStatsMask =
|
|
~(
|
|
Stats.SimStatFlags.InterpDiff |
|
|
Stats.SimStatFlags.InterpUncertainty |
|
|
Stats.SimStatFlags.InterpMultiplier |
|
|
Stats.SimStatFlags.InputOffsetTarget |
|
|
Stats.SimStatFlags.InputOffsetDeviation |
|
|
Stats.SimStatFlags.InputReceiveDeltaDeviation
|
|
);
|
|
#endif
|
|
|
|
|
|
const int SCREEN_SCALE_W = 1080;
|
|
const int SCREEN_SCALE_H = 1080;
|
|
const float TEXT_MARGIN = 0.25f;
|
|
const float TITLE_HEIGHT = 20f;
|
|
const int MARGIN = FusionStatsUtilities.MARGIN;
|
|
const int PAD = FusionStatsUtilities.PAD;
|
|
|
|
const string PLAY_TEXT = "PLAY";
|
|
const string PAUS_TEXT = "PAUSE";
|
|
const string SHOW_TEXT = "SHOW";
|
|
const string HIDE_TEXT = "HIDE";
|
|
const string CLER_TEXT = "CLEAR";
|
|
const string CNVS_TEXT = "CANVAS";
|
|
const string CLSE_TEXT = "CLOSE";
|
|
|
|
const string PLAY_ICON = "\u25ba";
|
|
const string PAUS_ICON = "\u05f0";
|
|
const string HIDE_ICON = "\u25bc";
|
|
const string SHOW_ICON = "\u25b2";
|
|
const string CLER_ICON = "\u1d13";
|
|
const string CNVS_ICON = "\ufb26"; //"\u2261";
|
|
const string CLSE_ICON = "x";
|
|
|
|
// Used by DrawIfAttribute to determine inspector visibility of fields are runtime.
|
|
bool ShowColorControls => !Application.isPlaying && _modifyColors;
|
|
bool IsPlaying => Application.isPlaying;
|
|
|
|
|
|
/// <summary>
|
|
/// Interval (in seconds) between Graph redraws. Higher values (longer intervals) reduce CPU overhead, draw calls and garbage collection.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[Unit(Units.Seconds, 1f, 0f, DecimalPlaces = 2)]
|
|
public float RedrawInterval = .1f;
|
|
|
|
|
|
/// <summary>
|
|
/// Selects between displaying Canvas as screen overlay, or a world GameObject.
|
|
/// </summary>
|
|
[Header("Layout")]
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
StatCanvasTypes _canvasType;
|
|
/// <summary>
|
|
/// Selects between displaying Canvas as screen overlay, or a world GameObject.
|
|
/// </summary>
|
|
public StatCanvasTypes CanvasType {
|
|
get => _canvasType;
|
|
set {
|
|
_canvasType = value;
|
|
//_canvas.enabled = false;
|
|
DirtyLayout(2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables text labels for the control buttons.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
bool _showButtonLabels = true;
|
|
/// <summary>
|
|
/// Enables text labels for the control buttons.
|
|
/// </summary>
|
|
public bool ShowButtonLabels {
|
|
get => _showButtonLabels;
|
|
set {
|
|
_showButtonLabels = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[Range(0, 200)]
|
|
int _maxHeaderHeight = 80;
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public int MaxHeaderHeight {
|
|
get => _maxHeaderHeight;
|
|
set {
|
|
_maxHeaderHeight = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The size of the canvas when <see cref="CanvasType"/> is set to <see cref="StatCanvasTypes.GameObject"/>.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.GameObject, DrawIfHideType.Hide)]
|
|
[Range(0, 20f)]
|
|
public float CanvasScale = 5f;
|
|
|
|
/// <summary>
|
|
/// The distance on the Z axis the canvas will be positioned. Allows moving the canvas in front of or behind the parent GameObject.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[DrawIf(nameof(_canvasType), (long)StatCanvasTypes.GameObject, DrawIfHideType.Hide)]
|
|
[Range(-10, 10f)]
|
|
public float CanvasDistance = 0f;
|
|
|
|
/// <summary>
|
|
/// The Rect which defines the position of the stats canvas on a GameObject. Sizes are normalized percentages.(ranges of 0f-1f).
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(CanvasType), (long)StatCanvasTypes.GameObject, DrawIfHideType.Hide)]
|
|
[NormalizedRect(aspectRatio: 1)]
|
|
Rect _gameObjectRect = new Rect(0.0f, 0.0f, 0.3f, 1.0f);
|
|
public Rect GameObjectRect {
|
|
get => _gameObjectRect;
|
|
set {
|
|
_gameObjectRect = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// The Rect which defines the position of the stats canvas overlay on the screen. Sizes are normalized percentages.(ranges of 0f-1f).
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(CanvasType), (long)StatCanvasTypes.Overlay, DrawIfHideType.Hide)]
|
|
[NormalizedRect]
|
|
Rect _overlayRect = new Rect(0.0f, 0.0f, 0.3f, 1.0f);
|
|
public Rect OverlayRect {
|
|
get => _overlayRect;
|
|
set {
|
|
_overlayRect = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <see cref="FusionGraph.Layouts"/> value which all child <see cref="FusionGraph"/> components will use if their <see cref="FusionGraph.Layouts"/> value is set to Auto.
|
|
/// </summary>
|
|
[Header("Fusion Graphs Layout")]
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
FusionGraph.Layouts _defaultLayout;
|
|
public FusionGraph.Layouts DefaultLayout {
|
|
get => _defaultLayout;
|
|
set {
|
|
_defaultLayout = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
bool _noTextOverlap;
|
|
public bool NoTextOverlap {
|
|
get => _noTextOverlap;
|
|
set {
|
|
_noTextOverlap = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disables the bar graph in <see cref="FusionGraph"/>, and uses a text only layout.
|
|
/// Enable this if <see cref="FusionGraph"/> is not rendering correctly in VR.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
bool _noGraphShader;
|
|
public bool NoGraphShader {
|
|
get => _noGraphShader;
|
|
set {
|
|
_noGraphShader = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force graphs layout to use X number of columns.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[Range(0, 16)]
|
|
public int GraphColumnCount = 1;
|
|
|
|
/// <summary>
|
|
/// If <see cref="GraphColumnCount"/> is set to zero, then columns will automatically be added as needed to limit graphs to this width or less.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(GraphColumnCount), compareToValue: (long)0, DrawIfHideType.ReadOnly)]
|
|
[Range(30, SCREEN_SCALE_W)]
|
|
int _graphMaxWidth = SCREEN_SCALE_W / 4;
|
|
|
|
/// <summary>
|
|
/// If <see cref="GraphColumnCount"/> is set to zero, then columns will automatically be added as needed to limit graphs to this width or less.
|
|
/// </summary>
|
|
public int GraphMaxWidth {
|
|
get => _graphMaxWidth;
|
|
set {
|
|
_graphMaxWidth = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables/Disables all NetworkObject related elements.
|
|
/// </summary>
|
|
[Header("Network Object Stats")]
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[WarnIf(nameof(ShowMissingNetObjWarning), true, "No NetworkObject found on this GameObject, nor parent. Object stats will be unavailable.")]
|
|
bool _enableObjectStats;
|
|
public bool EnableObjectStats {
|
|
get => _enableObjectStats;
|
|
set {
|
|
_enableObjectStats = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
bool ShowMissingNetObjWarning {
|
|
get => _enableObjectStats && this.Object == null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="NetworkObject"/> source for any <see cref="Stats.ObjStats"/> specific telemetry.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(EnableObjectStats), true)]
|
|
NetworkObject _object;
|
|
public NetworkObject Object {
|
|
get {
|
|
if (_object == null) {
|
|
_object = GetComponentInParent<NetworkObject>();
|
|
}
|
|
return _object;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Height of Object title region at top of the stats panel.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(EnableObjectStats), true)]
|
|
[Range(0, 200)]
|
|
int _objectTitleHeight = 48;
|
|
public int ObjectTitleHeight {
|
|
get => _objectTitleHeight;
|
|
set {
|
|
_objectTitleHeight = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Height of Object info region at top of the stats panel.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(EnableObjectStats), true)]
|
|
[Range(0, 200)]
|
|
int _objectIdsHeight = 60;
|
|
public int ObjectIdsHeight {
|
|
get => _objectIdsHeight;
|
|
set {
|
|
_objectIdsHeight = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Height of Object info region at top of the stats panel.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(EnableObjectStats), true)]
|
|
[Range(0, 200)]
|
|
int _objectMetersHeight = 90;
|
|
public int ObjectMetersHeight {
|
|
get => _objectMetersHeight;
|
|
set {
|
|
_objectIdsHeight = value;
|
|
DirtyLayout();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="NetworkRunner"/> currently associated with this <see cref="FusionStats"/> component and graphs.
|
|
/// </summary>
|
|
[Header("Data")]
|
|
[SerializeField]
|
|
[InlineHelp]
|
|
[EditorDisabled]
|
|
NetworkRunner _runner;
|
|
public NetworkRunner Runner {
|
|
get {
|
|
|
|
if (Application.isPlaying == false) {
|
|
return null;
|
|
}
|
|
|
|
// If the current runner shutdown, reset the runner so a new one can be found
|
|
if (_runner) {
|
|
if (_runner.IsShutdown) {
|
|
Runner = null;
|
|
} else {
|
|
return _runner;
|
|
}
|
|
}
|
|
|
|
if (Object) {
|
|
var runner = _object.Runner;
|
|
|
|
if (runner && (!EnforceSingle || (runner.Mode & ConnectTo) != 0)) {
|
|
Runner = runner;
|
|
return _runner;
|
|
}
|
|
}
|
|
|
|
FusionStatsUtilities.TryFindActiveRunner(this, out var found, ConnectTo);
|
|
|
|
Runner = found;
|
|
return found;
|
|
}
|
|
set {
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a <see cref="FusionGraph"/> 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.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
public bool InitializeAllGraphs;
|
|
|
|
/// <summary>
|
|
/// When <see cref="_runner"/> is null and no <see cref="NetworkRunner"/> exists in the current scene, FusionStats will continuously attempt to find and connect to an active <see cref="NetworkRunner"/> which matches these indicated modes.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[VersaMask]
|
|
public SimulationModes ConnectTo = /*SimulationModes.Host | SimulationModes.Server | */SimulationModes.Client;
|
|
|
|
/// <summary>
|
|
/// Selects which NetworkObject stats should be displayed.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[VersaMask]
|
|
[DrawIf(nameof(EnableObjectStats), true)]
|
|
Stats.ObjStatFlags _includedObjStats;
|
|
public Stats.ObjStatFlags IncludedObjectStats {
|
|
get => _includedObjStats;
|
|
set {
|
|
_includedObjStats = value;
|
|
_activeDirty = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects which NetConnection stats should be displayed.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[VersaMask]
|
|
Stats.NetStatFlags _includedNetStats;
|
|
public Stats.NetStatFlags IncludedNetStats {
|
|
get => _includedNetStats;
|
|
set {
|
|
_includedNetStats = value;
|
|
_activeDirty = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects which Simulation stats should be displayed.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[VersaMask]
|
|
Stats.SimStatFlags _includedSimStats;
|
|
public Stats.SimStatFlags IncludedSimStats {
|
|
get => _includedSimStats;
|
|
set {
|
|
_includedSimStats = value;
|
|
_activeDirty = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Automatically destroys this <see cref="FusionStats"/> 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 <see cref="SimulationModes"/> specified by <see cref="ConnectTo"/>, and connect to that.
|
|
/// </summary>
|
|
[Header("Life-Cycle")]
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
public bool AutoDestroy;
|
|
|
|
/// <summary>
|
|
/// Only one instance with the <see cref="Guid"/> can exist. Will destroy any clones on Awake.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
public bool EnforceSingle = true;
|
|
|
|
/// <summary>
|
|
/// Identifier used to enforce single instances of <see cref="FusionStats"/> when running in Multi-Peer mode.
|
|
/// When <see cref="EnforceSingle"/> is enabled, only one instance of <see cref="FusionStats"/> with this GUID will be active at any time,
|
|
/// regardless of the total number of peers running.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[DrawIf(nameof(EnforceSingle), true)]
|
|
[SerializeField]
|
|
public string Guid;
|
|
|
|
[Header("Customization")]
|
|
/// <summary>
|
|
/// Shows/hides controls in the inspector for defining element colors.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(IsPlaying), false, DrawIfHideType.Hide)]
|
|
private bool _modifyColors;
|
|
public bool ModifyColors => _modifyColors;
|
|
|
|
/// <summary>
|
|
/// The color used for the telemetry graph data.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _graphColorGood = new Color(0.1f, 0.5f, 0.1f, 0.9f);
|
|
|
|
/// <summary>
|
|
/// The color used for the telemetry graph data.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _graphColorWarn = new Color(0.75f, 0.75f, 0.2f, 0.9f);
|
|
|
|
/// <summary>
|
|
/// The color used for the telemetry graph data.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _graphColorBad = new Color(0.9f, 0.2f, 0.2f, 0.9f);
|
|
|
|
/// <summary>
|
|
/// The color used for the telemetry graph data.
|
|
/// </summary>
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _graphColorFlag = new Color(0.8f, 0.75f, 0.0f, 1.0f);
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _fontColor = new Color(1.0f, 1.0f, 1.0f, 1f);
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color PanelColor = new Color(0.3f, 0.3f, 0.3f, 0.9f);
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _simDataBackColor = new Color(0.1f, 0.08f, 0.08f, 1.0f);
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
Color _netDataBackColor = new Color(0.15f, 0.14f, 0.09f, 1.0f);
|
|
|
|
[InlineHelp]
|
|
[SerializeField]
|
|
[DrawIf(nameof(ShowColorControls), true, DrawIfHideType.Hide)]
|
|
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;
|
|
|
|
//[Header("Graph Connections")]
|
|
[SerializeField] [HideInInspector] FusionGraph[] _simGraphs;
|
|
[SerializeField] [HideInInspector] FusionGraph[] _objGraphs;
|
|
[SerializeField] [HideInInspector] FusionGraph[] _netGraphs;
|
|
[NonSerialized] List<IFusionStatsView> _foundViews;
|
|
[NonSerialized] List<FusionGraph> _foundGraphs;
|
|
|
|
[SerializeField] [HideInInspector] UI.Text _titleText;
|
|
|
|
[SerializeField] [HideInInspector] UI.Text _clearIcon;
|
|
[SerializeField] [HideInInspector] UI.Text _pauseIcon;
|
|
[SerializeField] [HideInInspector] UI.Text _togglIcon;
|
|
[SerializeField] [HideInInspector] UI.Text _closeIcon;
|
|
[SerializeField] [HideInInspector] UI.Text _canvsIcon;
|
|
|
|
[SerializeField] [HideInInspector] UI.Text _clearLabel;
|
|
[SerializeField] [HideInInspector] UI.Text _pauseLabel;
|
|
[SerializeField] [HideInInspector] UI.Text _togglLabel;
|
|
[SerializeField] [HideInInspector] UI.Text _closeLabel;
|
|
[SerializeField] [HideInInspector] UI.Text _canvsLabel;
|
|
[SerializeField] [HideInInspector] UI.Text _objectNameText;
|
|
|
|
[SerializeField] [HideInInspector] UI.GridLayoutGroup _graphGridLayoutGroup;
|
|
|
|
[SerializeField] [HideInInspector] Canvas _canvas;
|
|
[SerializeField] [HideInInspector] RectTransform _canvasRT;
|
|
[SerializeField] [HideInInspector] RectTransform _rootPanelRT;
|
|
[SerializeField] [HideInInspector] RectTransform _guidesRT;
|
|
[SerializeField] [HideInInspector] RectTransform _headerRT;
|
|
[SerializeField] [HideInInspector] RectTransform _statsPanelRT;
|
|
[SerializeField] [HideInInspector] RectTransform _graphsLayoutRT;
|
|
[SerializeField] [HideInInspector] RectTransform _titleRT;
|
|
[SerializeField] [HideInInspector] RectTransform _buttonsRT;
|
|
[SerializeField] [HideInInspector] RectTransform _objectTitlePanelRT;
|
|
[SerializeField] [HideInInspector] RectTransform _objectIdsGroupRT;
|
|
[SerializeField] [HideInInspector] RectTransform _objectMetersPanelRT;
|
|
[SerializeField] [HideInInspector] RectTransform _clientIdPanelRT;
|
|
[SerializeField] [HideInInspector] RectTransform _authorityPanelRT;
|
|
|
|
[SerializeField] [HideInInspector] UI.Button _titleButton;
|
|
[SerializeField] [HideInInspector] UI.Button _objctButton;
|
|
[SerializeField] [HideInInspector] UI.Button _clearButton;
|
|
[SerializeField] [HideInInspector] UI.Button _togglButton;
|
|
[SerializeField] [HideInInspector] UI.Button _pauseButton;
|
|
[SerializeField] [HideInInspector] UI.Button _closeButton;
|
|
[SerializeField] [HideInInspector] UI.Button _canvsButton;
|
|
|
|
public Rect CurrentRect => _canvasType == StatCanvasTypes.GameObject ? _gameObjectRect : _overlayRect;
|
|
|
|
void UpdateTitle() {
|
|
var runnername = _runner ? _runner.name : "Disconnected";
|
|
if (_titleText) {
|
|
_titleText.text = runnername;
|
|
}
|
|
}
|
|
|
|
Shader Shader {
|
|
get => Resources.Load<Shader>("FusionGraphShader");
|
|
}
|
|
|
|
Font _font;
|
|
bool _hidden;
|
|
bool _paused;
|
|
int _layoutDirty;
|
|
bool _activeDirty;
|
|
|
|
double _currentDrawTime;
|
|
double _delayDrawUntil;
|
|
|
|
void DirtyLayout(int minimumRefreshes = 1) {
|
|
if (_layoutDirty < minimumRefreshes) {
|
|
_layoutDirty = minimumRefreshes;
|
|
}
|
|
}
|
|
|
|
#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() {
|
|
ResetInternal();
|
|
}
|
|
|
|
#endif
|
|
|
|
void ResetInternal(
|
|
bool? enableObjectStats = null,
|
|
Stats.NetStatFlags? netStatsMask = null,
|
|
Stats.SimStatFlags? simStatsMask = null,
|
|
DefaultLayouts? objectLayout = null,
|
|
DefaultLayouts? screenLayout = null
|
|
) {
|
|
// Destroy existing built graphs
|
|
var canv = GetComponentInChildren<Canvas>();
|
|
if (canv) {
|
|
DestroyImmediate(canv.gameObject);
|
|
}
|
|
|
|
if (TryGetComponent<FusionStatsBillboard>(out var _) == false) {
|
|
gameObject.AddComponent<FusionStatsBillboard>().UpdateLookAt();
|
|
}
|
|
|
|
bool hasNetworkObject = GetComponentInParent<NetworkObject>();
|
|
// If attached to a NetObject
|
|
if (enableObjectStats.GetValueOrDefault() || (enableObjectStats.GetValueOrDefault(true) && hasNetworkObject)) {
|
|
EnableObjectStats = true;
|
|
_includedObjStats = Stats.ObjStatFlags.Buffer;
|
|
_includedSimStats = simStatsMask.GetValueOrDefault();
|
|
_includedNetStats = netStatsMask.GetValueOrDefault();
|
|
_canvasType = StatCanvasTypes.GameObject;
|
|
EnforceSingle = false;
|
|
GraphColumnCount = 1;
|
|
}
|
|
else {
|
|
// If not attached to a GameObject (sim only)
|
|
|
|
GraphColumnCount = 0;
|
|
|
|
if (transform.parent) {
|
|
_canvasType = StatCanvasTypes.GameObject;
|
|
EnforceSingle = false;
|
|
} else {
|
|
_canvasType = StatCanvasTypes.Overlay;
|
|
EnforceSingle = true;
|
|
}
|
|
_includedSimStats = simStatsMask.GetValueOrDefault(DefaultSimStatsMask);
|
|
_includedNetStats = netStatsMask.GetValueOrDefault(
|
|
Stats.NetStatFlags.RoundTripTime |
|
|
Stats.NetStatFlags.SentPacketSizes |
|
|
Stats.NetStatFlags.ReceivedPacketSizes);
|
|
|
|
}
|
|
|
|
|
|
ApplyDefaultLayout(objectLayout.GetValueOrDefault(hasNetworkObject ? DefaultLayouts.UpperRight : DefaultLayouts.Full), StatCanvasTypes.GameObject);
|
|
ApplyDefaultLayout(screenLayout.GetValueOrDefault(DefaultLayouts.Right), StatCanvasTypes.Overlay);
|
|
|
|
Guid = System.Guid.NewGuid().ToString().Substring(0, 13);
|
|
GenerateGraphs();
|
|
}
|
|
|
|
void Awake() {
|
|
|
|
#if !UNITY_EDITOR
|
|
if (_guidesRT) {
|
|
Destroy(_guidesRT.gameObject);
|
|
}
|
|
#endif
|
|
|
|
if (Application.isPlaying == false) {
|
|
#if UNITY_EDITOR
|
|
if (_canvas) {
|
|
//// Hide canvas for rebuild, Unity makes this ugly.
|
|
if (EditorApplication.isCompiling == false) {
|
|
//_canvas.enabled = false;
|
|
UnityEditor.EditorApplication.delayCall += CalculateLayout;
|
|
|
|
}
|
|
_layoutDirty = 2;
|
|
//CalculateLayout();
|
|
}
|
|
return;
|
|
#endif
|
|
|
|
} else {
|
|
_foundViews = new List<IFusionStatsView>();
|
|
GetComponentsInChildren(true, _foundViews);
|
|
|
|
}
|
|
|
|
if (Guid == "") {
|
|
Guid = System.Guid.NewGuid().ToString().Substring(0, 13);
|
|
}
|
|
|
|
if (EnforceSingle && Guid != null) {
|
|
if (_activeGuids.ContainsKey(Guid)) {
|
|
Destroy(this.gameObject);
|
|
return;
|
|
}
|
|
_activeGuids.Add(Guid, this);
|
|
}
|
|
|
|
if (EnforceSingle && transform.parent == null && _canvasType == StatCanvasTypes.Overlay) {
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
}
|
|
|
|
void Start() {
|
|
if (Application.isPlaying) {
|
|
Initialize();
|
|
_activeDirty = true;
|
|
_layoutDirty = 2;
|
|
//_canvas.enabled = false;
|
|
|
|
}
|
|
}
|
|
|
|
void OnDestroy() {
|
|
// Try to unregister this Stats in case it hasn't already.
|
|
DisassociateWithRunner(_runner);
|
|
|
|
// If this is the current enforce single instance of this GUID, remove it from the record.
|
|
if (Guid != null) {
|
|
if (_activeGuids.TryGetValue(Guid, out var stats)) {
|
|
if (stats == this) {
|
|
_activeGuids.Remove(Guid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[BehaviourButtonAction("Destroy Graphs", conditionMember: nameof(_canvasRT), ConditionFlags = BehaviourActionAttribute.ActionFlags.ShowAtNotRuntime)]
|
|
void DestroyGraphs() {
|
|
if (_canvasRT) {
|
|
DestroyImmediate(_canvasRT.gameObject);
|
|
}
|
|
_canvasRT = null;
|
|
}
|
|
|
|
|
|
void Initialize() {
|
|
|
|
// Only add an event system if no active event systems exist.
|
|
if (Application.isPlaying && FindObjectOfType<EventSystem>() == null) {
|
|
var eventSystemGO = new GameObject("Event System");
|
|
eventSystemGO.AddComponent<EventSystem>();
|
|
eventSystemGO.AddComponent<StandaloneInputModule>();
|
|
if (Application.isPlaying) {
|
|
DontDestroyOnLoad(eventSystemGO);
|
|
}
|
|
}
|
|
|
|
if (_canvasRT == false) {
|
|
GenerateGraphs();
|
|
}
|
|
|
|
// Already existed before runtime. (Scene object)
|
|
if (_canvasRT) {
|
|
// Listener connections are not retained with serialization and always need to be connected at startup.
|
|
// Remove listeners in case this is a copy of a runtime generated graph.
|
|
_togglButton?.onClick.RemoveListener(Toggle);
|
|
_canvsButton?.onClick.RemoveListener(ToggleCanvasType);
|
|
_clearButton?.onClick.RemoveListener(Clear);
|
|
_pauseButton?.onClick.RemoveListener(Pause);
|
|
_closeButton?.onClick.RemoveListener(Close);
|
|
_titleButton?.onClick.RemoveListener(PingSelectFusionStats);
|
|
_objctButton?.onClick.RemoveListener(PingSelectObject);
|
|
|
|
_togglButton?.onClick.AddListener(Toggle);
|
|
_canvsButton?.onClick.AddListener(ToggleCanvasType);
|
|
_clearButton?.onClick.AddListener(Clear);
|
|
_pauseButton?.onClick.AddListener(Pause);
|
|
_closeButton?.onClick.AddListener(Close);
|
|
_titleButton?.onClick.AddListener(PingSelectFusionStats);
|
|
_objctButton?.onClick.AddListener(PingSelectObject);
|
|
// Run Unity first frame layout failure hack.
|
|
|
|
GetComponentsInChildren(true, _foundViews);
|
|
|
|
foreach (var g in _foundViews) {
|
|
g.Initialize();
|
|
}
|
|
|
|
_layoutDirty = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool _graphsAreMissing => _canvasRT == null;
|
|
|
|
[BehaviourButtonAction("Generate Graphs", conditionMember: nameof(_graphsAreMissing), ConditionFlags = BehaviourActionAttribute.ActionFlags.ShowAtNotRuntime)]
|
|
void GenerateGraphs() {
|
|
var rootRectTr = gameObject.GetComponent<Transform>();
|
|
_canvasRT = rootRectTr.CreateRectTransform("Stats Canvas");
|
|
_canvas = _canvasRT.gameObject.AddComponent<Canvas>();
|
|
_canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
|
|
// If the runner has already started, the root FusionStats has been added to the VisNodes registration for the runner,
|
|
// But any generated children GOs here will not. Add the generated components to the visibility system.
|
|
if (Runner && Runner.IsRunning) {
|
|
RunnerVisibilityNode.AddVisibilityNodes(_canvasRT.gameObject, Runner);
|
|
}
|
|
var scaler = _canvasRT.gameObject.AddComponent<UI.CanvasScaler>();
|
|
scaler.uiScaleMode = UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
scaler.referenceResolution = new Vector2(SCREEN_SCALE_W, SCREEN_SCALE_H);
|
|
scaler.matchWidthOrHeight = .4f;
|
|
|
|
_canvasRT.gameObject.AddComponent<UI.GraphicRaycaster>();
|
|
|
|
#if UNITY_EDITOR
|
|
_guidesRT = _canvasRT.MakeGuides();
|
|
#endif
|
|
|
|
_rootPanelRT = _canvasRT
|
|
.CreateRectTransform("Root Panel");
|
|
|
|
_headerRT = _rootPanelRT
|
|
.CreateRectTransform("Header Panel")
|
|
.AddCircleSprite(PanelColor);
|
|
|
|
_titleRT = _headerRT
|
|
.CreateRectTransform("Runner Title")
|
|
.SetAnchors(0.0f, 1.0f, 0.75f, 1.0f)
|
|
.SetOffsets(MARGIN, -MARGIN, 0.0f, -MARGIN);
|
|
|
|
_titleButton = _titleRT.gameObject.AddComponent<UI.Button>();
|
|
_titleText = _titleRT.AddText(_runner ? _runner.name : "Disconnected", TextAnchor.UpperCenter, _fontColor);
|
|
_titleText.raycastTarget = true;
|
|
|
|
// Buttons
|
|
_buttonsRT = _headerRT
|
|
.CreateRectTransform("Buttons")
|
|
.SetAnchors(0.0f, 1.0f, 0.0f, 0.75f)
|
|
.SetOffsets(MARGIN, -MARGIN, MARGIN, 0);
|
|
|
|
var buttonsGrid = _buttonsRT.gameObject.AddComponent<UI.HorizontalLayoutGroup>();
|
|
buttonsGrid.childControlHeight = true;
|
|
buttonsGrid.childControlWidth = true;
|
|
buttonsGrid.spacing = MARGIN;
|
|
_buttonsRT.MakeButton(ref _togglButton, HIDE_ICON, HIDE_TEXT, out _togglIcon, out _togglLabel, Toggle);
|
|
_buttonsRT.MakeButton(ref _canvsButton, CNVS_ICON, CNVS_TEXT, out _canvsIcon, out _canvsLabel, ToggleCanvasType);
|
|
_buttonsRT.MakeButton(ref _pauseButton, PAUS_ICON, PAUS_TEXT, out _pauseIcon, out _pauseLabel, Pause);
|
|
_buttonsRT.MakeButton(ref _clearButton, CLER_ICON, CLER_TEXT, out _clearIcon, out _clearLabel, Clear);
|
|
_buttonsRT.MakeButton(ref _closeButton, CLSE_ICON, CLSE_TEXT, out _closeIcon, out _closeLabel, Close);
|
|
|
|
// Minor tweak to foldout arrow icon, since its too tall.
|
|
_togglIcon.rectTransform.anchorMax = new Vector2(1, 0.85f);
|
|
|
|
// Stats stack
|
|
|
|
_statsPanelRT = _rootPanelRT
|
|
.CreateRectTransform("Stats Panel")
|
|
.AddCircleSprite(PanelColor);
|
|
|
|
// Object Name, IDs and Meters
|
|
|
|
_objectTitlePanelRT = _statsPanelRT
|
|
.CreateRectTransform("Object Name Panel")
|
|
.ExpandTopAnchor(MARGIN)
|
|
.AddCircleSprite(_objDataBackColor);
|
|
|
|
_objctButton = _objectTitlePanelRT.gameObject.AddComponent<UI.Button>();
|
|
|
|
var objectTitleRT = _objectTitlePanelRT
|
|
.CreateRectTransform("Object Name")
|
|
.SetAnchors(0.0f, 1.0f, 0.15f, 0.85f)
|
|
.SetOffsets(PAD, -PAD, 0, 0);
|
|
|
|
_objectNameText = objectTitleRT.AddText("Object Name", TextAnchor.MiddleCenter, _fontColor);
|
|
_objectNameText.alignByGeometry = false;
|
|
_objectNameText.raycastTarget = false;
|
|
|
|
_objectIdsGroupRT = FusionStatsObjectIds.Create(_statsPanelRT, this);
|
|
|
|
_objectMetersPanelRT = _statsPanelRT
|
|
.CreateRectTransform("Object Meters Layout")
|
|
.ExpandTopAnchor(MARGIN)
|
|
.AddVerticalLayoutGroup(MARGIN);
|
|
|
|
FusionStatsMeterBar.Create(_objectMetersPanelRT, this, Stats.StatSourceTypes.NetworkObject, (int)Stats.ObjStats.Bandwidth, 15, 30);
|
|
FusionStatsMeterBar.Create(_objectMetersPanelRT, this, Stats.StatSourceTypes.NetworkObject, (int)Stats.ObjStats.RPC, 3, 6);
|
|
|
|
// Graphs
|
|
_graphsLayoutRT = _statsPanelRT
|
|
.CreateRectTransform("Graphs Layout")
|
|
.ExpandAnchor()
|
|
.SetOffsets(MARGIN, 0,0,0);
|
|
|
|
//.AddGridlLayoutGroup(MRGN);
|
|
_graphGridLayoutGroup = _graphsLayoutRT.AddGridlLayoutGroup(MARGIN);
|
|
|
|
_objGraphs = new FusionGraph[Stats.OBJ_STAT_TYPE_COUNT];
|
|
for (int i = 0; i < Stats.OBJ_STAT_TYPE_COUNT; ++i) {
|
|
if (InitializeAllGraphs == false) {
|
|
var statFlag = (Stats.ObjStatFlags)(1 << i);
|
|
if ((statFlag & _includedObjStats) == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
CreateGraph(Stats.StatSourceTypes.NetworkObject, i, _graphsLayoutRT);
|
|
}
|
|
|
|
_netGraphs = new FusionGraph[Stats.NET_STAT_TYPE_COUNT];
|
|
for (int i = 0; i < Stats.NET_STAT_TYPE_COUNT; ++i) {
|
|
if (InitializeAllGraphs == false) {
|
|
var statFlag = (Stats.NetStatFlags)(1 << i);
|
|
if ((statFlag & _includedNetStats) == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
CreateGraph(Stats.StatSourceTypes.NetConnection, i, _graphsLayoutRT);
|
|
}
|
|
|
|
_simGraphs = new FusionGraph[Stats.SIM_STAT_TYPE_COUNT];
|
|
for (int i = 0; i < Stats.SIM_STAT_TYPE_COUNT; ++i) {
|
|
if (InitializeAllGraphs == false) {
|
|
var statFlag = (Stats.SimStatFlags)(1 << i);
|
|
if ((statFlag & _includedSimStats) == 0) {
|
|
continue;
|
|
}
|
|
}
|
|
CreateGraph(Stats.StatSourceTypes.Simulation, i, _graphsLayoutRT);
|
|
}
|
|
|
|
// Hide canvas for a tick. Unity makes some ugliness on the first update.
|
|
//_canvas.enabled = false;
|
|
_activeDirty = true;
|
|
|
|
_layoutDirty = 2;
|
|
}
|
|
|
|
void AssociateWithRunner(NetworkRunner runner) {
|
|
if (runner != null) {
|
|
if (_statsForRunnerLookup.TryGetValue(runner, out var runnerStats) == false) {
|
|
_statsForRunnerLookup.Add(runner, new List<FusionStats>() { this });
|
|
} else {
|
|
runnerStats.Add(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisassociateWithRunner(NetworkRunner runner) {
|
|
if (runner != null && _statsForRunnerLookup.TryGetValue(runner, out var oldrunnerstats)) {
|
|
if (oldrunnerstats.Contains(this)) {
|
|
oldrunnerstats.Remove(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Pause() {
|
|
if (_runner && _runner.Simulation != null) {
|
|
_paused = !_paused;
|
|
|
|
var icon = _paused ? PLAY_ICON : PAUS_ICON;
|
|
var label = _paused ? PLAY_TEXT : PAUS_TEXT;
|
|
_pauseIcon.text = icon;
|
|
_pauseLabel.text = label;
|
|
|
|
// Pause for all SimStats tied to this runner if all related FusionStats are paused.
|
|
if (_statsForRunnerLookup.TryGetValue(_runner, out var stats)) {
|
|
|
|
bool statsAreBeingUsed = false;
|
|
foreach (var stat in stats) {
|
|
if (stat._paused == false) {
|
|
statsAreBeingUsed = true;
|
|
break;
|
|
}
|
|
}
|
|
_runner.Simulation.Stats.Pause(statsAreBeingUsed == false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Toggle() {
|
|
_hidden = !_hidden;
|
|
|
|
_togglIcon.text = _hidden ? SHOW_ICON : HIDE_ICON;
|
|
_togglLabel.text = _hidden ? SHOW_TEXT : HIDE_TEXT;
|
|
|
|
_statsPanelRT.gameObject.SetActive(!_hidden);
|
|
|
|
for (int i = 0; i < _simGraphs.Length; ++i) {
|
|
var graph = _simGraphs[i];
|
|
if (graph) {
|
|
_simGraphs[i].gameObject.SetActive(!_hidden && (1 << i & (int)_includedSimStats) != 0);
|
|
}
|
|
}
|
|
for (int i = 0; i < _objGraphs.Length; ++i) {
|
|
var graph = _objGraphs[i];
|
|
if (graph) {
|
|
_objGraphs[i].gameObject.SetActive(!_hidden && (1 << i & (int)_includedObjStats) != 0);
|
|
}
|
|
}
|
|
for (int i = 0; i < _netGraphs.Length; ++i) {
|
|
var graph = _netGraphs[i];
|
|
if (graph) {
|
|
_netGraphs[i].gameObject.SetActive(!_hidden && (1 << i & (int)_includedNetStats) != 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Clear() {
|
|
if (_runner && _runner.Simulation != null) {
|
|
_runner.Simulation.Stats.Clear();
|
|
}
|
|
|
|
for (int i = 0; i < _simGraphs.Length; ++i) {
|
|
var graph = _simGraphs[i];
|
|
if (graph) {
|
|
_simGraphs[i].Clear();
|
|
}
|
|
}
|
|
for (int i = 0; i < _objGraphs.Length; ++i) {
|
|
var graph = _objGraphs[i];
|
|
if (graph) {
|
|
_objGraphs[i].Clear();
|
|
}
|
|
}
|
|
for (int i = 0; i < _netGraphs.Length; ++i) {
|
|
var graph = _netGraphs[i];
|
|
if (graph) {
|
|
_netGraphs[i].Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToggleCanvasType() {
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorGUIUtility.PingObject(gameObject);
|
|
if (Selection.activeGameObject == null) {
|
|
Selection.activeGameObject = gameObject;
|
|
}
|
|
#endif
|
|
_canvasType = (_canvasType == StatCanvasTypes.GameObject) ? StatCanvasTypes.Overlay : StatCanvasTypes.GameObject;
|
|
//_canvas.enabled = false;
|
|
_layoutDirty = 3;
|
|
CalculateLayout();
|
|
}
|
|
|
|
void Close() {
|
|
Destroy(this.gameObject);
|
|
}
|
|
|
|
void PingSelectObject() {
|
|
|
|
#if UNITY_EDITOR
|
|
var obj = Object;
|
|
if (obj) {
|
|
EditorGUIUtility.PingObject(Object.gameObject);
|
|
Selection.activeGameObject = Object.gameObject;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PingSelectFusionStats() {
|
|
|
|
#if UNITY_EDITOR
|
|
EditorGUIUtility.PingObject(gameObject);
|
|
Selection.activeGameObject = gameObject;
|
|
#endif
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private void OnDrawGizmos() {
|
|
AutoGuideVisibility();
|
|
}
|
|
|
|
void AutoGuideVisibility() {
|
|
if (_canvasRT == null) {
|
|
return;
|
|
}
|
|
|
|
if (CanvasType == StatCanvasTypes.GameObject) {
|
|
if (_guidesRT == null) {
|
|
_guidesRT = FusionStatsUtilities.MakeGuides(_canvasRT);
|
|
}
|
|
if (Selection.activeGameObject == gameObject) {
|
|
_guidesRT.gameObject.SetActive(true);
|
|
_guidesRT.localRotation = default;
|
|
|
|
} else {
|
|
_guidesRT.gameObject.SetActive(false);
|
|
|
|
}
|
|
} else {
|
|
if (_guidesRT) {
|
|
DestroyImmediate(_guidesRT.gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void LateUpdate() {
|
|
|
|
// Use of the Runner getter here is intentional - this forces a test of the existing Runner having gone null or inactive.
|
|
var runner = Runner;
|
|
bool runnerIsNull = runner == null;
|
|
|
|
if (AutoDestroy && runnerIsNull) {
|
|
Destroy(this.gameObject);
|
|
return;
|
|
}
|
|
|
|
if (_activeDirty) {
|
|
ReapplyEnabled();
|
|
}
|
|
|
|
if (_layoutDirty > 0) {
|
|
CalculateLayout();
|
|
}
|
|
|
|
if (Application.isPlaying == false) {
|
|
return;
|
|
}
|
|
|
|
// NetConnection stats do not like being polled after shutdown and will throw assert fails.
|
|
if (runnerIsNull || runner.IsShutdown) {
|
|
return;
|
|
}
|
|
|
|
if (_paused) {
|
|
return;
|
|
}
|
|
|
|
// Cap redraw rate - rate of 0 = disabled.
|
|
if (RedrawInterval > 0) {
|
|
var currentime = Time.timeAsDouble;
|
|
if (currentime > _delayDrawUntil) {
|
|
_currentDrawTime = currentime;
|
|
while (_delayDrawUntil <= currentime) {
|
|
_delayDrawUntil += RedrawInterval;
|
|
}
|
|
}
|
|
|
|
if (currentime != _currentDrawTime) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (EnableObjectStats) {
|
|
RefreshObjectValues();
|
|
}
|
|
|
|
foreach (var graph in _foundViews) {
|
|
if (graph != null && graph.isActiveAndEnabled) {
|
|
graph.Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
string _previousObjectTitle;
|
|
|
|
void RefreshObjectValues() {
|
|
|
|
var obj = Object;
|
|
if (obj == null) {
|
|
return;
|
|
}
|
|
|
|
var objectName = obj.name;
|
|
if (_previousObjectTitle != objectName) {
|
|
_objectNameText.text = objectName;
|
|
_previousObjectTitle = objectName;
|
|
}
|
|
}
|
|
|
|
public FusionGraph CreateGraph(Stats.StatSourceTypes type, int statId, RectTransform parentRT) {
|
|
|
|
var fg = FusionGraph.Create(this, type, statId, parentRT);
|
|
|
|
if (type == Stats.StatSourceTypes.Simulation) {
|
|
_simGraphs[statId] = fg;
|
|
if (((int)_includedSimStats & (1 << statId)) == 0) {
|
|
fg.gameObject.SetActive(false);
|
|
}
|
|
} else if (type == Stats.StatSourceTypes.NetworkObject) {
|
|
_objGraphs[statId] = fg;
|
|
if (((int)_includedObjStats & (1 << statId)) == 0) {
|
|
fg.gameObject.SetActive(false);
|
|
}
|
|
} else {
|
|
_netGraphs[statId] = fg;
|
|
if (((int)_includedNetStats & (1 << statId)) == 0) {
|
|
fg.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
return fg;
|
|
}
|
|
|
|
// returns true if a graph has been added.
|
|
void ReapplyEnabled() {
|
|
|
|
_activeDirty = false;
|
|
|
|
if (_simGraphs == null || _simGraphs.Length < 0) {
|
|
return;
|
|
}
|
|
|
|
// This is null if the children were deleted. Stop execution, or new Graphs will be created without a parent.
|
|
if (_graphsLayoutRT == null) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < _simGraphs.Length; ++i) {
|
|
var graph = _simGraphs[i];
|
|
bool enabled = ((Stats.SimStatFlags)(1 << i) & _includedSimStats) != 0;
|
|
if (graph == null) {
|
|
if (enabled) {
|
|
graph = CreateGraph(Stats.StatSourceTypes.Simulation, i, _graphsLayoutRT);
|
|
_simGraphs[i] = graph;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
graph.gameObject.SetActive(enabled);
|
|
}
|
|
|
|
for (int i = 0; i < _objGraphs.Length; ++i) {
|
|
var graph = _objGraphs[i];
|
|
bool enabled = _enableObjectStats && ((Stats.ObjStatFlags)(1 << i) & _includedObjStats) != 0;
|
|
if (graph == null) {
|
|
if (enabled) {
|
|
graph = CreateGraph(Stats.StatSourceTypes.NetworkObject, i, _graphsLayoutRT);
|
|
_objGraphs[i] = graph;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (_objGraphs[i] != null) {
|
|
graph.gameObject.SetActive(enabled);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _netGraphs.Length; ++i) {
|
|
var graph = _netGraphs[i];
|
|
bool enabled = ((Stats.NetStatFlags)(1 << i) & _includedNetStats) != 0;
|
|
if (graph == null) {
|
|
if (enabled) {
|
|
graph = CreateGraph(Stats.StatSourceTypes.NetConnection, i, _graphsLayoutRT);
|
|
_netGraphs[i] = graph;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (_netGraphs[i] != null) {
|
|
graph.gameObject.SetActive(enabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
float _lastLayoutUpdate;
|
|
|
|
void CalculateLayout() {
|
|
|
|
if (_rootPanelRT == null || _graphsLayoutRT == null) {
|
|
return;
|
|
}
|
|
|
|
if (_foundGraphs == null) {
|
|
_foundGraphs = new List<FusionGraph>(_graphsLayoutRT.GetComponentsInChildren<FusionGraph>(false));
|
|
} else {
|
|
GetComponentsInChildren(false, _foundGraphs);
|
|
}
|
|
|
|
// Don't count multiple executions of CalculateLayout in the same Update as reducing the dirty count.
|
|
// _layoutDirty can be set to values greater than 1 to force a recalculate for several consecutive Updates.
|
|
var time = Time.time;
|
|
|
|
if (_lastLayoutUpdate < time) {
|
|
_layoutDirty--;
|
|
_lastLayoutUpdate = time;
|
|
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (Application.isPlaying == false && _layoutDirty > 0) {
|
|
UnityEditor.EditorApplication.delayCall -= CalculateLayout;
|
|
UnityEditor.EditorApplication.delayCall += CalculateLayout;
|
|
}
|
|
#endif
|
|
|
|
if (_layoutDirty <= 0 && _canvas.enabled == false) {
|
|
//_canvas.enabled = true;
|
|
}
|
|
|
|
if (_rootPanelRT) {
|
|
|
|
#if UNITY_EDITOR
|
|
AutoGuideVisibility();
|
|
#endif
|
|
|
|
var maxHeaderHeight = Math.Min(_maxHeaderHeight, _rootPanelRT.rect.width / 4);
|
|
|
|
if (_canvasType == StatCanvasTypes.GameObject) {
|
|
_canvas.renderMode = RenderMode.WorldSpace;
|
|
var scale = CanvasScale / SCREEN_SCALE_H; // (1f / SCREEN_SCALE_H) * Scale;
|
|
_canvasRT.localScale = new Vector3(scale, scale, scale);
|
|
_canvasRT.sizeDelta = new Vector2(1024, 1024);
|
|
_canvasRT.localPosition = new Vector3(0, 0, CanvasDistance);
|
|
|
|
// TODO: Cache this
|
|
if (_canvasRT.GetComponent<FusionStatsBillboard>() == false) {
|
|
_canvasRT.localRotation = default;
|
|
}
|
|
} else {
|
|
_canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
}
|
|
|
|
_objectTitlePanelRT.gameObject.SetActive(_enableObjectStats);
|
|
_objectIdsGroupRT.gameObject.SetActive(_enableObjectStats);
|
|
_objectMetersPanelRT.gameObject.SetActive(_enableObjectStats);
|
|
|
|
Vector2 icoMinAnchor;
|
|
|
|
if (_showButtonLabels) {
|
|
icoMinAnchor = new Vector2(0.0f, FusionStatsUtilities.BTTN_LBL_NORM_HGHT * .5f);
|
|
} else {
|
|
icoMinAnchor = new Vector2(0.0f, 0.0f);
|
|
}
|
|
|
|
_togglIcon.rectTransform.anchorMin = icoMinAnchor + new Vector2(0, .15f);
|
|
_canvsIcon.rectTransform.anchorMin = icoMinAnchor;
|
|
_clearIcon.rectTransform.anchorMin = icoMinAnchor;
|
|
_pauseIcon.rectTransform.anchorMin = icoMinAnchor;
|
|
_closeIcon.rectTransform.anchorMin = icoMinAnchor;
|
|
|
|
_togglLabel.gameObject.SetActive(_showButtonLabels);
|
|
_canvsLabel.gameObject.SetActive(_showButtonLabels);
|
|
_clearLabel.gameObject.SetActive(_showButtonLabels);
|
|
_pauseLabel.gameObject.SetActive(_showButtonLabels);
|
|
_closeLabel.gameObject.SetActive(_showButtonLabels);
|
|
|
|
var rect = CurrentRect;
|
|
|
|
_rootPanelRT.anchorMax = new Vector2(rect.xMax, rect.yMax);
|
|
_rootPanelRT.anchorMin = new Vector2(rect.xMin, rect.yMin);
|
|
_rootPanelRT.sizeDelta = new Vector2(0.0f, 0.0f);
|
|
_rootPanelRT.pivot = new Vector2(0.5f, 0.5f);
|
|
_rootPanelRT.anchoredPosition3D = default;
|
|
|
|
_headerRT.anchorMin = new Vector2(0.0f, 1);
|
|
_headerRT.anchorMax = new Vector2(1.0f, 1);
|
|
_headerRT.pivot = new Vector2(0.5f, 1);
|
|
_headerRT.anchoredPosition3D = default;
|
|
_headerRT.sizeDelta = new Vector2(0, /*TITLE_HEIGHT +*/ maxHeaderHeight);
|
|
|
|
_objectTitlePanelRT.offsetMax = new Vector2(-MARGIN, -MARGIN);
|
|
_objectTitlePanelRT.offsetMin = new Vector2( MARGIN, -(ObjectTitleHeight));
|
|
_objectIdsGroupRT.offsetMax = new Vector2(-MARGIN, -(ObjectTitleHeight + MARGIN));
|
|
_objectIdsGroupRT.offsetMin = new Vector2( MARGIN, -(ObjectTitleHeight + ObjectIdsHeight));
|
|
_objectMetersPanelRT.offsetMax = new Vector2(-MARGIN, -(ObjectTitleHeight + ObjectIdsHeight + MARGIN));
|
|
_objectMetersPanelRT.offsetMin = new Vector2( MARGIN, -(ObjectTitleHeight + ObjectIdsHeight + ObjectMetersHeight ));
|
|
|
|
// Disable object sections that have been minimized to 0
|
|
_objectTitlePanelRT .gameObject.SetActive(EnableObjectStats && ObjectTitleHeight > 0);
|
|
_objectIdsGroupRT .gameObject.SetActive(EnableObjectStats && ObjectIdsHeight > 0);
|
|
_objectMetersPanelRT.gameObject.SetActive(EnableObjectStats && ObjectMetersHeight > 0);
|
|
|
|
_statsPanelRT.ExpandAnchor().SetOffsets(0, 0, 0, -(/*TITLE_HEIGHT + */maxHeaderHeight));
|
|
|
|
if (_enableObjectStats && _statsPanelRT.rect.height < (ObjectTitleHeight + ObjectIdsHeight + ObjectMetersHeight)) {
|
|
_statsPanelRT.offsetMin = new Vector2(0.0f, _statsPanelRT.rect.height -(ObjectTitleHeight + ObjectIdsHeight + ObjectMetersHeight + MARGIN));
|
|
}
|
|
|
|
var graphColCount = GraphColumnCount > 0 ? GraphColumnCount : (int)(_graphsLayoutRT.rect.width / (_graphMaxWidth + MARGIN));
|
|
if (graphColCount < 1) {
|
|
graphColCount = 1;
|
|
}
|
|
|
|
var graphRowCount = (int)Math.Ceiling((double)_foundGraphs.Count / graphColCount);
|
|
if (graphRowCount < 1) {
|
|
graphRowCount = 1;
|
|
}
|
|
|
|
if (graphRowCount == 1) {
|
|
graphColCount = _foundGraphs.Count;
|
|
}
|
|
|
|
_graphGridLayoutGroup.constraint = UI.GridLayoutGroup.Constraint.FixedColumnCount;
|
|
_graphGridLayoutGroup.constraintCount = graphColCount;
|
|
|
|
var cellwidth = _graphsLayoutRT.rect.width / graphColCount - MARGIN;
|
|
var cellheight = _graphsLayoutRT.rect.height / graphRowCount - (/*(graphRowCount - 1) **/ MARGIN);
|
|
|
|
_graphGridLayoutGroup.cellSize = new Vector2(cellwidth, cellheight);
|
|
_graphsLayoutRT.offsetMax = new Vector2(0, _enableObjectStats ? -(ObjectTitleHeight + ObjectIdsHeight + ObjectMetersHeight + MARGIN) : -MARGIN);
|
|
|
|
|
|
if (_foundViews == null) {
|
|
_foundViews = new List<IFusionStatsView>(GetComponentsInChildren<IFusionStatsView>(false));
|
|
} else {
|
|
GetComponentsInChildren(false, _foundViews);
|
|
}
|
|
|
|
if (_objGraphs != null) {
|
|
// enabled/disable any object graphs based on _enabledObjectStats setting
|
|
foreach (var objGraph in _objGraphs) {
|
|
if (objGraph) {
|
|
objGraph.gameObject.SetActive(((int)_includedObjStats & (1 << objGraph.StatId)) != 0 && _enableObjectStats);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _foundViews.Count; ++i) {
|
|
var graph = _foundViews[i];
|
|
if (graph == null || graph.isActiveAndEnabled == false) {
|
|
continue;
|
|
}
|
|
graph.CalculateLayout();
|
|
graph.transform.localRotation = default;
|
|
graph.transform.localScale = new Vector3(1, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ApplyDefaultLayout(DefaultLayouts defaults, StatCanvasTypes? applyForCanvasType = null) {
|
|
bool applyToGO = applyForCanvasType.HasValue == false || applyForCanvasType.Value == StatCanvasTypes.GameObject;
|
|
bool applyToOL = applyForCanvasType.HasValue == false || applyForCanvasType.Value == StatCanvasTypes.Overlay;
|
|
|
|
if (defaults == DefaultLayouts.Custom) {
|
|
return;
|
|
}
|
|
|
|
Rect screenrect;
|
|
Rect objectrect;
|
|
bool isTall;
|
|
#if UNITY_EDITOR
|
|
var currentRes = UnityEditor.Handles.GetMainGameViewSize();
|
|
isTall = (currentRes.y > currentRes.x);
|
|
#else
|
|
isTall = Screen.height > Screen.width;
|
|
#endif
|
|
|
|
switch (defaults) {
|
|
case DefaultLayouts.Left: {
|
|
objectrect = Rect.MinMaxRect(0.0f, 0.0f, 0.3f, 1.0f);
|
|
screenrect = objectrect;
|
|
break;
|
|
}
|
|
case DefaultLayouts.Right: {
|
|
objectrect = Rect.MinMaxRect(0.7f, 0.0f, 1.0f, 1.0f);
|
|
screenrect = objectrect;
|
|
break;
|
|
}
|
|
case DefaultLayouts.UpperLeft: {
|
|
objectrect = Rect.MinMaxRect(0.0f, 0.5f, 0.3f, 1.0f);
|
|
screenrect = isTall ? Rect.MinMaxRect(0.0f, 0.7f, 0.3f, 1.0f) : objectrect ;
|
|
break;
|
|
}
|
|
case DefaultLayouts.UpperRight: {
|
|
objectrect = Rect.MinMaxRect(0.7f, 0.5f, 1.0f, 1.0f);
|
|
screenrect = isTall ? Rect.MinMaxRect(0.7f, 0.7f, 1.0f, 1.0f) : objectrect;
|
|
break;
|
|
}
|
|
case DefaultLayouts.Full: {
|
|
objectrect = Rect.MinMaxRect(0.0f, 0.0f, 1.0f, 1.0f);
|
|
screenrect = objectrect;
|
|
break;
|
|
}
|
|
default: {
|
|
objectrect = Rect.MinMaxRect(0.0f, 0.5f, 0.3f, 1.0f);
|
|
screenrect = objectrect;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (applyToGO) {
|
|
GameObjectRect = objectrect;
|
|
}
|
|
if (applyToOL) {
|
|
OverlayRect = screenrect;
|
|
}
|
|
|
|
_layoutDirty += 1;
|
|
}
|
|
} |