This commit is contained in:
2025-06-16 15:14:23 +02:00
commit 074e590073
3174 changed files with 428263 additions and 0 deletions

View File

@ -0,0 +1,217 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Mirror
{
public enum GraphAggregationMode
{
Sum,
Average,
PerSecond,
Min,
Max
}
public abstract class BaseUIGraph : MonoBehaviour
{
static readonly int MaxValue = Shader.PropertyToID("_MaxValue");
static readonly int GraphData = Shader.PropertyToID("_GraphData");
static readonly int CategoryCount = Shader.PropertyToID("_CategoryCount");
static readonly int Colors = Shader.PropertyToID("_CategoryColors");
static readonly int Width = Shader.PropertyToID("_Width");
static readonly int DataStart = Shader.PropertyToID("_DataStart");
public Material Material;
public Graphic Renderer;
[Range(1, 64)]
public int Points = 64;
public float SecondsPerPoint = 1;
public Color[] CategoryColors = new[] { Color.cyan };
public bool IsStacked;
public Text[] LegendTexts;
[Header("Diagnostics")]
[ReadOnly, SerializeField]
Material runtimeMaterial;
float[] graphData;
// graphData is a circular buffer, this is the offset to get the 0-index
int graphDataStartIndex;
// Is graphData dirty and needs to be set to the material
bool isGraphDataDirty;
// currently aggregating data to be added to the graph soon
float[] aggregatingData;
GraphAggregationMode[] aggregatingModes;
// Counts for avg aggregation mode
int[] aggregatingDataCounts;
// How much time has elapsed since the last aggregation finished
float aggregatingTime;
int DataLastIndex => (graphDataStartIndex - 1 + Points) % Points;
void Awake()
{
Renderer.material = runtimeMaterial = Instantiate(Material);
graphData = new float[Points * CategoryColors.Length];
aggregatingData = new float[CategoryColors.Length];
aggregatingDataCounts = new int[CategoryColors.Length];
aggregatingModes = new GraphAggregationMode[CategoryColors.Length];
isGraphDataDirty = true;
}
protected virtual void OnValidate()
{
if (Renderer == null)
Renderer = GetComponent<Graphic>();
}
protected virtual void Update()
{
for (int i = 0; i < CategoryColors.Length; i++)
{
CollectData(i, out float value, out GraphAggregationMode mode);
// we probably don't need negative values, so lets skip supporting it
if (value < 0)
{
Debug.LogWarning("Graphing negative values is not supported.");
value = 0;
}
if (mode != aggregatingModes[i])
{
aggregatingModes[i] = mode;
ResetCurrent(i);
}
switch (mode)
{
case GraphAggregationMode.Average:
case GraphAggregationMode.Sum:
case GraphAggregationMode.PerSecond:
aggregatingData[i] += value;
aggregatingDataCounts[i]++;
break;
case GraphAggregationMode.Min:
if (aggregatingData[i] > value)
aggregatingData[i] = value;
break;
case GraphAggregationMode.Max:
if (value > aggregatingData[i])
aggregatingData[i] = value;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
aggregatingTime += Time.deltaTime;
if (aggregatingTime > SecondsPerPoint)
{
graphDataStartIndex = (graphDataStartIndex + 1) % Points;
ClearDataAt(DataLastIndex);
for (int i = 0; i < CategoryColors.Length; i++)
{
float value = aggregatingData[i];
switch (aggregatingModes[i])
{
case GraphAggregationMode.Sum:
case GraphAggregationMode.Min:
case GraphAggregationMode.Max:
// do nothing!
break;
case GraphAggregationMode.Average:
value /= aggregatingDataCounts[i];
break;
case GraphAggregationMode.PerSecond:
value /= aggregatingTime;
break;
default:
throw new ArgumentOutOfRangeException();
}
SetCurrentGraphData(i, value);
ResetCurrent(i);
}
aggregatingTime = 0;
}
}
void ResetCurrent(int i)
{
switch (aggregatingModes[i])
{
case GraphAggregationMode.Min:
aggregatingData[i] = float.MaxValue;
break;
default:
aggregatingData[i] = 0;
break;
}
aggregatingDataCounts[i] = 0;
}
protected virtual string FormatValue(float value) => $"{value:N1}";
protected abstract void CollectData(int category, out float value, out GraphAggregationMode mode);
void SetCurrentGraphData(int c, float value)
{
graphData[DataLastIndex * CategoryColors.Length + c] = value;
isGraphDataDirty = true;
}
void ClearDataAt(int i)
{
for (int c = 0; c < CategoryColors.Length; c++)
graphData[i * CategoryColors.Length + c] = 0;
isGraphDataDirty = true;
}
public void LateUpdate()
{
if (isGraphDataDirty)
{
runtimeMaterial.SetInt(Width, Points);
runtimeMaterial.SetInt(DataStart, graphDataStartIndex);
float max = 1;
if (IsStacked)
for (int x = 0; x < Points; x++)
{
float total = 0;
for (int c = 0; c < CategoryColors.Length; c++)
total += graphData[x * CategoryColors.Length + c];
if (total > max)
max = total;
}
else
for (int i = 0; i < graphData.Length; i++)
{
float v = graphData[i];
if (v > max)
max = v;
}
max = AdjustMaxValue(max);
for (int i = 0; i < LegendTexts.Length; i++)
{
Text legendText = LegendTexts[i];
float pct = (float)i / (LegendTexts.Length - 1);
legendText.text = FormatValue(max * pct);
}
runtimeMaterial.SetFloat(MaxValue, max);
runtimeMaterial.SetFloatArray(GraphData, graphData);
runtimeMaterial.SetInt(CategoryCount, CategoryColors.Length);
runtimeMaterial.SetColorArray(Colors, CategoryColors);
isGraphDataDirty = false;
}
}
protected virtual float AdjustMaxValue(float max) => Mathf.Ceil(max);
}
}