2022-03-19 14:53:20 -04:00

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