using System; using UnityEngine; using Fusion; using UI = UnityEngine.UI; using Stats = Fusion.Simulation.Statistics; using Fusion.StatsInternal; [ScriptHelp(BackColor = EditorHeaderBackColor.Olive)] public abstract class FusionGraphBase : Fusion.Behaviour, IFusionStatsView { protected const int PAD = FusionStatsUtilities.PAD; protected const int MRGN = FusionStatsUtilities.MARGIN; protected const int MAX_FONT_SIZE_WITH_GRAPH = 24; [SerializeField] [HideInInspector] protected UI.Text LabelTitle; [SerializeField] [HideInInspector] protected UI.Image BackImage; /// /// Which section of the Fusion engine is being monitored. In combination with StatId, this selects the stat being monitored. /// [InlineHelp] [SerializeField] protected Stats.StatSourceTypes _statSourceType; public Stats.StatSourceTypes StateSourceType { get => _statSourceType; set { _statSourceType = value; TryConnect(); } } /// /// The specific stat being monitored. /// [InlineHelp] [SerializeField] [CastEnum(nameof(CastToStatType))] protected int _statId; public int StatId { get => _statId; set { _statId = value; TryConnect(); } } [InlineHelp] public float WarnThreshold; [InlineHelp] public float ErrorThreshold; protected IStatsBuffer _statsBuffer; public IStatsBuffer StatsBuffer { get { if (_statsBuffer == null) { TryConnect(); } return _statsBuffer; } } protected bool _isOverlay; public bool IsOverlay { set { if (_isOverlay != value) { _isOverlay = value; CalculateLayout(); _layoutDirty = true; } } get { return _isOverlay; } } protected virtual Color BackColor { get { if (_statSourceType == Stats.StatSourceTypes.Simulation) { return _fusionStats.SimDataBackColor; } if (_statSourceType == Stats.StatSourceTypes.NetConnection) { return _fusionStats.NetDataBackColor; } return _fusionStats.ObjDataBackColor; } } protected Type CastToStatType => (_statSourceType == Stats.StatSourceTypes.Simulation) ? typeof(Stats.SimStats) : (_statSourceType == Stats.StatSourceTypes.NetConnection) ? typeof(Stats.NetStats) : typeof(Stats.ObjStats); protected FusionStats _fusionStats; protected FusionStats LocateParentFusionStats() { if (_fusionStats == null) { _fusionStats = GetComponentInParent(); } return _fusionStats; } protected bool _layoutDirty = true; protected Stats.StatsPer CurrentPer; public Stats.StatSourceInfo StatSourceInfo; // Track source values to detect changes in OnValidate. [SerializeField] [HideInInspector] Stats.StatSourceTypes _prevStatSourceType; [SerializeField] [HideInInspector] int _prevStatId; #if UNITY_EDITOR protected virtual void OnValidate() { if (_statSourceType != _prevStatSourceType || _statId != _prevStatId) { WarnThreshold = 0; ErrorThreshold = 0; _prevStatSourceType = _statSourceType; _prevStatId = _statId; } } #endif public virtual void Initialize() { } public virtual void CyclePer() { var flags = StatSourceInfo.PerFlags; switch (CurrentPer) { case Stats.StatsPer.Individual: if ((flags & Stats.StatsPer.Tick) == Stats.StatsPer.Tick) { CurrentPer = Stats.StatsPer.Tick; } else if ((flags & Stats.StatsPer.Second) == Stats.StatsPer.Second) { CurrentPer = Stats.StatsPer.Second; } return; case Stats.StatsPer.Tick: if ((flags & Stats.StatsPer.Second) == Stats.StatsPer.Second) { CurrentPer = Stats.StatsPer.Second; } else if ((flags & Stats.StatsPer.Individual) == Stats.StatsPer.Individual) { CurrentPer = Stats.StatsPer.Individual; } return; case Stats.StatsPer.Second: if ((flags & Stats.StatsPer.Individual) == Stats.StatsPer.Individual) { CurrentPer = Stats.StatsPer.Individual; } else if ((flags & Stats.StatsPer.Tick) == Stats.StatsPer.Tick) { CurrentPer = Stats.StatsPer.Tick; } return; default: return; } } public abstract void CalculateLayout(); public abstract void Refresh(); protected virtual bool TryConnect() { StatSourceInfo = Stats.GetDescription(_statSourceType, _statId); // if (WarnThreshold == 0 && ErrorThreshold == 0) { WarnThreshold = StatSourceInfo.WarnThreshold; ErrorThreshold = StatSourceInfo.ErrorThreshold; } if (gameObject.activeInHierarchy == false) { return false; } if (_fusionStats == null) { _fusionStats = GetComponentInParent(); } // Any data connection requires a runner for the statistics source. var runner = _fusionStats?.Runner; var statistics = runner?.Simulation?.Stats; //Stats.StatSourceInfo info; switch (_statSourceType) { case Stats.StatSourceTypes.Simulation: { _statsBuffer = statistics?.GetStatBuffer((Stats.SimStats)_statId); break; } case Stats.StatSourceTypes.NetworkObject: { if (_statId >= Stats.OBJ_STAT_TYPE_COUNT) { StatId = 0; } if (_fusionStats.Object == null) { _statsBuffer = null; break; } _statsBuffer = statistics?.GetObjectBuffer(_fusionStats.Object.Id, (Stats.ObjStats)_statId, true); break; } case Stats.StatSourceTypes.NetConnection: { //StatSourceInfo = Stats.GetDescription((Stats.NetStats)_statId); if (runner == null) { _statsBuffer = null; break; } _statsBuffer = statistics?.GetStatBuffer((Stats.NetStats)_statId, runner); break; } default: { _statsBuffer = null; break; } } if (BackImage) { BackImage.color = BackColor; } // Update the labels, regardless if a connection can be made. if (LabelTitle) { CheckIfValidIncurrentMode(runner); ApplyTitleText(); } CurrentPer = StatSourceInfo.PerDefault; return (_statsBuffer != null); } protected void ApplyTitleText() { var info = StatSourceInfo; // stat info is invalid if (info.LongName == null) { return; } if (info.InvalidReason != null) { LabelTitle.text = info.InvalidReason; BackImage.gameObject.SetActive(false); LabelTitle.color = _fusionStats.FontColor * new Color(1, 1, 1, 0.2f); } else { var titleRT = LabelTitle.rectTransform; if (titleRT.rect.width < 100) { LabelTitle.text = info.ShortName ?? info.LongName; } else { LabelTitle.text = info.LongName; } BackImage.gameObject.SetActive(true); } } protected void CheckIfValidIncurrentMode(NetworkRunner runner) { if (runner == false) { return; } //var info = StatSourceInfo; var flags = StatSourceInfo.Flags; if ((flags & Stats.StatFlags.ValidForBuildType) == 0) { StatSourceInfo.InvalidReason = "DEBUG DLL ONLY"; return; } var obj = _statSourceType == Stats.StatSourceTypes.NetworkObject ? _fusionStats?.Object : null; if (obj) { bool nonStateAuthOnly = (flags & Stats.StatFlags.ValidOnStateAuthority) == 0; if (nonStateAuthOnly && obj.HasStateAuthority) { StatSourceInfo.InvalidReason = "NON STATE AUTH ONLY"; return; } } if (runner) { bool clientOnly = (flags & Stats.StatFlags.ValidOnServer) == 0; if (clientOnly && runner.IsClient == false) { StatSourceInfo.InvalidReason = "CLIENT ONLY"; return; } bool ecOnly = (flags & Stats.StatFlags.ValidWithDeltaSnapshot) == 0; if (ecOnly && runner.Config.Simulation.ReplicationMode == SimulationConfig.StateReplicationModes.DeltaSnapshots) { StatSourceInfo.InvalidReason = "EC MODE ONLY"; return; } } } }