using Fusion; using UnityEngine; using UnityEngine.UI; using Stats = Fusion.Simulation.Statistics; using Fusion.StatsInternal; public class FusionStatsMeterBar : FusionGraphBase { //public float WarningValueThreshold; //public float ErrorValueThreshold; public float HoldPeakTime = 0.1f; public float DecayTime = 0.25f; /// /// Values greater than 0 will limit the meter to a range of 0 to MeterMax. /// Value of 0 will adjust the max to the largest value occurance. /// [InlineHelp] public int MeterMax = 0; /// /// Exposes the UI labels and controls of , so they may be modified if customizing this graph. /// [InlineHelp] [SerializeField] bool _showUITargets; [DrawIf(nameof(_showUITargets), true, DrawIfHideType.Hide)] public Text ValueLabel; [DrawIf(nameof(_showUITargets), true, DrawIfHideType.Hide)] public Image Bar; //public string CurrentLabel; //double _currentRawValue; double _currentDisplayValue; double _currentBarValue; Color CurrentColor; protected override Color BackColor => base.BackColor * new Color(.5f, .5f, .5f, 1); #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); if (MeterMax < 0) { MeterMax = 0; } if (Application.isPlaying == false) { TryConnect(); _layoutDirty = true; } } #endif public override void Initialize() { base.Initialize(); _max = MeterMax; // Prefabs lose editor generated sprites - recreate as needed. if (BackImage.sprite == null) { BackImage.sprite = FusionStatsUtilities.MeterSprite; Bar.sprite = BackImage.sprite; } // TODO: Can remove these later. Backwards compat for mask removal on Dec 30 2021 BackImage.type = Image.Type.Simple; if (Bar.rectTransform.parent != BackImage.rectTransform.parent) { var oldMask = Bar.transform.parent; Bar.rectTransform.SetParent(BackImage.rectTransform.parent); Bar.transform.SetSiblingIndex(BackImage.transform.GetSiblingIndex() + 1); //Destroy(oldMask); } Bar.type = Image.Type.Filled; Bar.fillMethod = Image.FillMethod.Horizontal; Bar.fillAmount = 0; } double _lastImportedSampleTickTime; double _max; double _total; float _lastPeakSetTime; public override void Refresh() { if (_layoutDirty) { CalculateLayout(); } var statsBuffer = StatsBuffer; if (statsBuffer == null || statsBuffer.Count < 1) { return; } // Awkward temp RPC handling if (statsBuffer.DefaultVisualization == FusionGraphVisualization.CountHistogram) { if (statsBuffer.Count > 0) { int highestRpcsFoundForTick = 0; float newestSampleTick = statsBuffer.GetSampleAtIndex(statsBuffer.Count - 1).TickValue; var tick = newestSampleTick; // Only look back at ticks we have not yet already looked at on previous updates. if (newestSampleTick > _lastImportedSampleTickTime) { int tickRpcCount = 0; for (int i = statsBuffer.Count - 1; i >= 0; i--) { var sampletick = statsBuffer.GetSampleAtIndex(i).TickValue; if (sampletick > _lastImportedSampleTickTime) { // If we are now looking at samples from a different tick that previous for loop, reset to get count for this tick now. if (sampletick != tick) { tick = sampletick; // Capture the RPC count for the last recorded tick if it is the new high. if (tickRpcCount > highestRpcsFoundForTick) { highestRpcsFoundForTick = tickRpcCount; } tickRpcCount = 0; } tickRpcCount++; _total++; } else { break; } } _lastImportedSampleTickTime = newestSampleTick; } SetValue(highestRpcsFoundForTick); } return; } if (statsBuffer.Count > 0) { var value = statsBuffer.GetSampleAtIndex(statsBuffer.Count - 1); if (value.TickValue == _fusionStats.Runner.Simulation.LatestServerState.Tick) { SetValue(value.FloatValue); } else { SetValue(0); } } } public void LateUpdate() { if (DecayTime <= 0) { return; } if (_currentBarValue <= 0) { return; } if (Time.time < _lastPeakSetTime + HoldPeakTime) { return; } double decayedVal = System.Math.Max(_currentBarValue - Time.deltaTime / DecayTime * _max, 0); SetBar(decayedVal); } public void SetValue(double rawvalue) { var info = StatSourceInfo; double multiplied = rawvalue * info.Multiplier; if (MeterMax == 0) { if (multiplied > _max) { _max = multiplied; } } double clampedValue = System.Math.Max(System.Math.Min(multiplied, _max), 0); var roundedValue = System.Math.Round(clampedValue, info.Decimals); var newDisplayValue = _total > 0 ? _total : roundedValue; if (clampedValue >= _currentBarValue) { _lastPeakSetTime = Time.time; } if (newDisplayValue != _currentDisplayValue) { ValueLabel.text = _total > 0 ? _total.ToString() : clampedValue.ToString(); _currentDisplayValue = newDisplayValue; } // Only set values greater than the current shown value when using decay. if (DecayTime >= 0 && clampedValue <= _currentBarValue) { return; } if (clampedValue != _currentBarValue) { SetBar(clampedValue); } } void SetBar(double value) { var fusionStats = _fusionStats; Bar.fillAmount = (float)(value / _max); _currentBarValue = value; if (value < WarnThreshold) { var GoodColor = fusionStats.GraphColorGood; if (CurrentColor != GoodColor) { CurrentColor = GoodColor; Bar.color = GoodColor; } } else if (value < ErrorThreshold) { var WarnColor = fusionStats.GraphColorWarn; if (CurrentColor != WarnColor) { Bar.color = WarnColor; CurrentColor = WarnColor; } } else { var ErrorColor = fusionStats.GraphColorBad; if (CurrentColor != ErrorColor) { Bar.color = ErrorColor; CurrentColor = ErrorColor; } } } public override void CalculateLayout() { _layoutDirty = false; // Special padding handling because Arial vertically sits below center. var pad = LabelTitle.transform.parent.GetComponent().rect.height * .2f; LabelTitle.rectTransform.offsetMax = new Vector2(0, -pad); LabelTitle.rectTransform.offsetMin = new Vector2(PAD, pad * 1.2f); ValueLabel.rectTransform.offsetMax = new Vector2(-PAD, -pad); ValueLabel.rectTransform.offsetMin = new Vector2(0, pad * 1.2f); ApplyTitleText(); } // unfinished public static FusionStatsMeterBar Create( RectTransform parent, FusionStats fusionStats, Stats.StatSourceTypes statSourceType, int statId, float warnThreshold, float alertThreshold ) { var info = Stats.GetDescription(statSourceType, statId); var barRT = parent.CreateRectTransform(info.LongName, true); var bar = barRT.gameObject.AddComponent(); bar.StatSourceInfo = info; bar._fusionStats = fusionStats; //bar.WarningValueThreshold = warnThreshold; //bar.ErrorValueThreshold = alertThreshold; bar._statSourceType = statSourceType; bar._statId = statId; bar.GenerateMeter(); return bar; } public void GenerateMeter() { var info = Stats.GetDescription(_statSourceType, _statId); var backRT = transform.CreateRectTransform("Back", true); BackImage = backRT.gameObject.AddComponent(); BackImage.raycastTarget = false; BackImage.sprite = FusionStatsUtilities.MeterSprite; BackImage.color = BackColor; BackImage.type = Image.Type.Simple; var barRT = transform.CreateRectTransform("Bar", true); Bar = barRT.gameObject.AddComponent(); Bar.raycastTarget = false; Bar.sprite = BackImage.sprite; Bar.color = _fusionStats.GraphColorGood; Bar.type = Image.Type.Filled; Bar.fillMethod = Image.FillMethod.Horizontal; Bar.fillAmount = 0; var titleRT = transform.CreateRectTransform("Label", true) .ExpandAnchor() .SetAnchors(0.0f, 0.5f, 0.0f,1.0f) .SetOffsets(6, -6, 6, -6); LabelTitle = titleRT.AddText(info.LongName, TextAnchor.MiddleLeft, _fusionStats.FontColor); LabelTitle.alignByGeometry = false; var valueRT = transform.CreateRectTransform("Value", true) .ExpandAnchor() .SetAnchors(0.5f, 1.0f, 0.0f, 1.0f) .SetOffsets(6, -6, 6, -6); ValueLabel = valueRT.AddText("0.0", TextAnchor.MiddleRight, _fusionStats.FontColor); ValueLabel.alignByGeometry = false; } }