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

914 lines
27 KiB
C#

3 weeks ago
namespace Fusion {
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using StatsInternal;
using UnityEngine.Serialization;
#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 = ScriptHeaderBackColor.Olive)]
[ExecuteAlways]
public partial class FusionStats : Fusion.Behaviour {
/// <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,
}
/// <summary>
/// Interval (in seconds) between Graph redraws. Higher values (longer intervals) reduce CPU overhead, draw calls and garbage collection.
/// </summary>
[InlineHelp]
[Unit(Units.Seconds)]//, DecimalPlaces = 2)]
[Range(0f, 1f)]
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 = 70;
/// <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, Hide = true)]
[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, Hide = true)]
[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, Hide = true)]
[NormalizedRect(aspectRatio: 1)]
Rect _gameObjectRect = new Rect(0.0f, 0.0f, 0.3f, 1.0f);
public Rect GameObjectRect {
get => _gameObjectRect;
set {
_gameObjectRect = value;
DirtyLayout();
}
}
/// <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, Hide = true)]
[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="FusionStatsGraph.Layouts"/> value which all child <see cref="FusionStatsGraph"/> components will use if their <see cref="FusionStatsGraph.Layouts"/> value is set to Auto.
/// </summary>
[Header("Fusion Graphs Layout")]
[InlineHelp]
[SerializeField]
FusionStatsGraph.Layouts _defaultLayout;
public FusionStatsGraph.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="FusionStatsGraph"/>, and uses a text only layout.
/// Enable this if <see cref="FusionStatsGraph"/> 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), 0)]
[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();
}
}
[Header("Network Object Stats")] [SerializeField]
private int _playerRef;
public PlayerRef PlayerRef {
get => PlayerRef.FromIndex(_playerRef);
set {
_playerRef = value.AsIndex;
// TODO: Not needed?
DirtyLayout();
}
}
/// <summary>
/// Enables/Disables all NetworkObject related elements.
/// </summary>
[Header("Network Object Stats")]
[InlineHelp]
[SerializeField]
bool _enableObjectStats;
public bool EnableObjectStats {
get => _enableObjectStats;
set {
_enableObjectStats = value;
DirtyLayout();
}
}
/// <summary>
/// The <see cref="NetworkObject"/> source for any <see cref="Stats.ObjStats"/> specific telemetry.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
internal NetworkObject _object;
/// <summary>
/// Returns the set serialized <see cref="NetworkObject"/> for this stat window. If that is null, returns the static MonitoredNetworkObject,
/// which can be set using <see cref="SetMonitoredNetworkObject"/>.
/// </summary>
public NetworkObject Object {
get {
if (_object) {
return _object;
}
// no local object set - fallback to the global one.
if (_runner == null) {
// Will not be ble to lookup a network object without a valid runner. null for now.
return default;
}
if (EnableObjectStats) {
return _runner.FindObject(MonitoredNetworkObjectId);
}
return default;
}
}
/// <summary>
/// Height of Object title region at top of the stats panel.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[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))]
[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))]
[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]
[ReadOnly]
NetworkRunner _runner;
public NetworkRunner Runner {
get {
if (Application.isPlaying == false) {
return null;
}
// Be sure the current runner is the correct runner.
this.ValidateRunner(_runner);
return _runner;
}
}
public void SetRunner(NetworkRunner value) {
if (_runner == value) {
return;
}
// Keep track of which runners have active stats windows - needed so pause/unpause can affect all (since pause affects other panels)
DisassociateWithRunner(_runner);
_runner = value;
AssociateWithRunner(value);
UpdateTitle();
}
/// <summary>
/// Editor-Only. If no <see cref="Object"/> is set, this FusionStats will attempt to connect to the NetworkRunner for the current selected GameObject.
/// </summary>
[InlineHelp]
[SerializeField]
public bool RunnerFromSelected;
/// <summary>
/// Initializes a <see cref="FusionStatsGraph"/> 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]
[ExpandableEnum(ShowInlineHelp = true)]
public SimulationModes ConnectTo = SimulationModes.Host | SimulationModes.Server | SimulationModes.Client;
/// <summary>
/// Selects which NetworkObject stats should be displayed.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_enableObjectStats))]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask<NetworkObjectStats> _includedObjStats = new (typeof(NetworkObjectStats).GetDefaults);
/// <summary>
/// Selects which NetConnection stats should be displayed.
/// </summary>
[InlineHelp]
[SerializeField]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask<SimulationConnectionStats> _includedNetStats = new(typeof(SimulationConnectionStats).GetDefaults);
/// <summary>
/// Selects which Simulation stats should be displayed.
/// </summary>
[InlineHelp]
[SerializeField]
[ExpandableEnum(ShowInlineHelp = true)]
public FieldsMask<SimulationStats> _includedSimStats = new(typeof(SimulationStats).GetDefaults);
/// <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 if there is no associated <see cref="NetworkObject"/> <see cref="_object"/>. Will destroy any additional instances 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))]
[SerializeField]
public string Guid;
/// <summary>
/// The font to be used for all non-number labels.
/// </summary>
[Header("Customization")]
[SerializeField]
public Font LabelFont;
/// <summary>
/// The font to be used for all number labels.
/// </summary>
[InlineHelp]
[SerializeField]
public Font ValueFont;
[SerializeField][HideInInspector]
internal Shader GraphShader;
/// <summary>
/// Shows/hides controls in the inspector for defining element colors.
/// </summary>
[InlineHelp]
[SerializeField]
private bool _modifyColors;
/// <summary>
/// The color used for the telemetry graph data.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorGood = new Color(0.1f, 0.5f, 0.1f, 1.0f);
/// <summary>
/// The color used for the telemetry graph data.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorWarn = new Color(0.75f, 0.75f, 0.2f, 1.0f);
/// <summary>
/// The color used for the telemetry graph data.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorBad = new Color(0.9f, 0.2f, 0.2f, 1.0f);
/// <summary>
/// The color used for the telemetry graph data.
/// </summary>
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _graphColorFlag = new Color(0.8f, 0.75f, 0.0f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _fontColor = new Color(1.0f, 1.0f, 1.0f, 1f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color PanelColor = new Color(0.3f, 0.3f, 0.3f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _simDataBackColor = new Color(0.1f, 0.08f, 0.08f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _netDataBackColor = new Color(0.15f, 0.14f, 0.09f, 1.0f);
[InlineHelp]
[SerializeField]
[DrawIf(nameof(_modifyColors), Hide = true)]
Color _objDataBackColor = new Color(0.0f, 0.2f, 0.4f, 1.0f);
// IFusionStats interface requirements
public Color FontColor => _fontColor;
public Color GraphColorGood => _graphColorGood;
public Color GraphColorWarn => _graphColorWarn;
public Color GraphColorBad => _graphColorBad;
public Color GraphColorFlag => _graphColorFlag;
public Color SimDataBackColor => _simDataBackColor;
public Color NetDataBackColor => _netDataBackColor;
public Color ObjDataBackColor => _objDataBackColor;
public Rect CurrentRect => _canvasType == StatCanvasTypes.GameObject ? _gameObjectRect : _overlayRect;
Font _font;
bool _hidden;
bool _paused;
int _layoutDirty;
bool _activeDirty;
double _currentDrawTime;
double _delayDrawUntil;
#if UNITY_EDITOR
void OnValidate() {
if (EnforceSingle && Guid == "") {
Guid = System.Guid.NewGuid().ToString().Substring(0, 13);
}
_activeDirty = true;
if (_layoutDirty <= 0) {
_layoutDirty = 2;
// Some aspects of Layout will throw warnings if run from OnValidate, so defer.
// Stop deferring when entering play mode, as this will cause null errors (thanks unity).
if (Application.isPlaying) {
UnityEditor.EditorApplication.delayCall += CalculateLayout;
} else {
UnityEditor.EditorApplication.delayCall -= CalculateLayout;
}
}
}
void Reset() {
ResetLayout();
}
#endif
/// <summary>
/// Resets the layout of the stats panel to the default layout for the current <see cref="CanvasType"/>.
/// </summary>
/// <param name="enableObjectStats">Optional parameter to enable or disable object stats. If null, the current setting is used.</param>
/// <param name="objectLayout">Optional parameter to set the layout for the object stats. If null, the current setting is used.</param>
/// <param name="screenLayout">Optional parameter to set the layout for the screen stats. If null, the current setting is used.</param>
public void ResetLayout(bool? enableObjectStats = 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;
_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;
}
}
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 (_object == null) {
if (TryGetComponent<NetworkObject>(out var no)) {
_object = no;
} else {
_object = GetComponentInParent<NetworkObject>(true);
}
}
if (Application.isPlaying == false) {
#if UNITY_EDITOR
if (_canvas) {
// Hide canvas for rebuild, Unity makes this ugly.
if (EditorApplication.isCompiling == false) {
UnityEditor.EditorApplication.delayCall += CalculateLayout;
}
_layoutDirty = 2;
}
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 && Object == null && _canvasType == StatCanvasTypes.Overlay) {
DontDestroyOnLoad(gameObject);
}
}
void Start() {
if (Application.isPlaying) {
Initialize();
_activeDirty = true;
_layoutDirty = 2;
}
}
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);
}
}
}
}
private bool _graphCanvasExists => _canvasRT != null;
[EditorButton("Destroy Graphs", dirtyObject: true)]
[DrawIf(nameof(_graphCanvasExists), Hide = true)]
void DestroyGraphs() {
if (_canvasRT) {
DestroyImmediate(_canvasRT.gameObject);
}
_canvasRT = null;
}
static bool? _newInputSystemFound;
public static bool NewInputSystemFound {
get {
if (_newInputSystemFound == null) {
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
var asmtypes = asm.GetTypes();
foreach (var type in asmtypes) {
if (type.Namespace == "UnityEngine.InputSystem") {
_newInputSystemFound = true;
return true;
}
}
}
_newInputSystemFound = false;
return false;
}
return _newInputSystemFound.Value;
}
}
void Initialize() {
// Only add an event system if no active event systems exist.
if (Application.isPlaying) {
if (NewInputSystemFound) {
// New Input System
} else {
if (FindFirstObjectByType<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) {
InitializeControls();
GetComponentsInChildren(true, _foundViews);
foreach (var g in _foundViews) {
g.Initialize();
}
_layoutDirty = 1;
}
}
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);
}
// Notify FusionGraphs that they need to reconnect to a new runner.
if (_foundGraphs != null) {
foreach (var graph in _foundGraphs) {
graph.Disconnect();
}
}
}
}
void DisassociateWithRunner(NetworkRunner runner) {
if (runner != null && _statsForRunnerLookup.TryGetValue(runner, out var oldrunnerstats)) {
if (oldrunnerstats.Contains(this)) {
oldrunnerstats.Remove(this);
}
}
}
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) {
_objectNameText.text = "No Object";
_previousObjectTitle = "No Object";
return;
}
var objectName = obj.name;
if (_previousObjectTitle != objectName) {
_objectNameText.text = objectName;
_previousObjectTitle = objectName;
}
}
// 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 = (((long)1 << i) & _includedSimStats.Mask) != 0;
if (graph == null) {
if (enabled) {
graph = CreateGraph(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 && (((long)1 << i) & _includedObjStats.Mask) != 0;
if (graph == null) {
if (enabled) {
graph = CreateGraph(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 = (((long)1 << i) & _includedNetStats.Mask) != 0;
if (graph == null) {
if (enabled) {
graph = CreateGraph(StatSourceTypes.NetConnection, i, _graphsLayoutRT);
_netGraphs[i] = graph;
} else {
continue;
}
}
if (_netGraphs[i] != null) {
graph.gameObject.SetActive(enabled);
}
}
}
}
}