Loading files
c30bb7b85This file is pinned to the selected release. Download the original to open it in your design tool.
/******************************************************************************
* Copyright (C) Leap Motion, Inc. 2011-2018. *
* Leap Motion proprietary and confidential. *
* *
* Use subject to the terms of the Leap Motion SDK Agreement available at *
* https://developer.leapmotion.com/sdk_agreement, or another agreement *
* between Leap Motion and you, your company or other organization. *
******************************************************************************/
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Profiling;
using System;
using System.Collections;
using System.Collections.Generic;
using Leap.Unity.Query;
namespace Leap.Unity.Graphing {
public class RealtimeGraph : MonoBehaviour {
private static RealtimeGraph _cachedInstance = null;
public static RealtimeGraph Instance {
get {
if (_cachedInstance == null) {
_cachedInstance = FindObjectOfType<RealtimeGraph>();
}
return _cachedInstance;
}
}
public struct GraphKey {
public string name;
public GraphUnits units;
public long tick;
public GraphKey(string name, GraphUnits units, long tick = 0) {
this.name = name;
this.units = units;
this.tick = tick;
}
}
public enum GraphUnits {
Miliseconds,
Framerate
}
public enum GraphMode {
Inclusive,
Exclusive
}
[SerializeField]
protected string _defaultGraph = "Framerate";
[SerializeField]
protected string[] _unitySamplerNames = new string[0];
[SerializeField]
protected GraphMode _graphMode = GraphMode.Exclusive;
[SerializeField]
protected int _historyLength = 128;
[SerializeField]
protected int _updatePeriod = 10;
[SerializeField]
protected int _samplesPerFrame = 1;
[SerializeField]
protected float _framerateLineSpacing = 60;
[SerializeField]
protected float _deltaLineSpacing = 10;
[SerializeField]
protected float _maxSmoothingDelay = 0.1f;
[SerializeField]
protected float _valueSmoothingDelay = 1;
[Header("References")]
[SerializeField]
protected LeapServiceProvider _provider;
[SerializeField]
protected Renderer _graphRenderer;
[SerializeField]
protected Text titleLabel;
[SerializeField]
protected TextMesh valueMesh;
[SerializeField]
protected GameObject customGraphPrefab;
public float UpdatePeriodFloat {
set {
_updatePeriod = Mathf.RoundToInt(Mathf.Lerp(1, 10, value));
}
}
public float BatchSizeFloat {
set {
_samplesPerFrame = Mathf.RoundToInt(Mathf.Lerp(1, 10, value));
}
}
public float GraphModeFloat {
set {
_graphMode = value > 0.5f ? GraphMode.Inclusive : GraphMode.Exclusive;
}
}
protected System.Diagnostics.Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
protected int _sampleIndex = 0;
protected int _updateCount = 0;
protected bool _paused = false;
protected Texture2D _texture;
protected Color32[] _colors;
protected SmoothedFloat _smoothedValue;
protected SmoothedFloat _smoothedMax;
protected Graph _currentGraph;
protected Dictionary<string, Graph> _graphs;
protected Stack<Graph> _currentGraphStack = new Stack<Graph>();
protected Deque<GraphKey> _keyBuffer = new Deque<GraphKey>();
protected List<Recorder> _unityRecorders = new List<Recorder>();
//Custom sample timers
protected long _preCullTicks, _renderTicks, _fixedTicks = -1;
public void BeginSample(GraphKey key) {
key.tick = _stopwatch.ElapsedTicks;
_keyBuffer.PushFront(key);
}
public void BeginSample(string sampleName, GraphUnits units) {
GraphKey key = new GraphKey(sampleName, units, _stopwatch.ElapsedTicks);
_keyBuffer.PushFront(key);
}
public void EndSample() {
_keyBuffer.PushFront(new GraphKey(null, GraphUnits.Miliseconds, _stopwatch.ElapsedTicks));
}
public void AddSample(string sampleName, GraphUnits units, long ticks) {
Graph graph = getGraph(sampleName, units);
graph.AddSample(ticks);
}
public void AddSample(string sampleName, GraphUnits units, float ms) {
Graph graph = getGraph(sampleName, units);
graph.AddSample((long)(ms * System.Diagnostics.Stopwatch.Frequency * 0.001f));
}
public void SwtichGraph(string graphName) {
_currentGraph = _graphs[graphName];
titleLabel.text = graphName;
}
public void TogglePaused() {
_paused = !_paused;
}
protected virtual void OnValidate() {
_historyLength = Mathf.Max(1, _historyLength);
_updatePeriod = Mathf.Max(1, _updatePeriod);
}
protected virtual void Awake() {
_graphs = new Dictionary<string, Graph>();
_smoothedMax = new SmoothedFloat();
_smoothedMax.delay = _maxSmoothingDelay;
_smoothedValue = new SmoothedFloat();
_smoothedValue.delay = _valueSmoothingDelay;
}
protected virtual void Start() {
_provider = _provider ?? FindObjectOfType<LeapServiceProvider>();
_texture = new Texture2D(_historyLength, 1, TextureFormat.Alpha8, false, true);
_texture.filterMode = FilterMode.Point;
_texture.wrapMode = TextureWrapMode.Clamp;
_colors = new Color32[_historyLength];
_graphRenderer.material.SetTexture("_GraphTexture", _texture);
_unitySamplerNames.Query().Select(n => {
getGraph(n, GraphUnits.Miliseconds);
return Recorder.Get(n);
}).FillList(_unityRecorders);
_stopwatch.Start();
}
protected virtual void OnEnable() {
Camera.onPreCull += onPreCull;
Camera.onPostRender += onPostRender;
StartCoroutine(endOfFrameWaiter());
}
protected virtual void OnDisable() {
Camera.onPreCull -= onPreCull;
Camera.onPostRender -= onPostRender;
}
protected virtual void Update() {
if (_fixedTicks != -1) {
AddSample("Physics Delta", GraphUnits.Miliseconds, _stopwatch.ElapsedTicks - _fixedTicks);
_fixedTicks = -1;
}
_preCullTicks = -1;
if (_provider != null) {
AddSample("Tracking Framerate", GraphUnits.Framerate, 1000.0f / _provider.CurrentFrame.CurrentFramesPerSecond);
}
for (int i = 0; i < _unityRecorders.Count; i++) {
var recorder = _unityRecorders[i];
AddSample(_unitySamplerNames[i], GraphUnits.Miliseconds, recorder.elapsedNanoseconds / 1000000.0f);
}
if (_currentGraph == null) {
return;
}
_sampleIndex++;
if (_sampleIndex < _samplesPerFrame) {
return;
}
replayKeys();
for (var it = _graphs.GetEnumerator(); it.MoveNext();) {
var graph = it.Current.Value;
if (_paused) {
graph.ClearSample();
} else {
graph.RecordSample(_sampleIndex);
}
}
_sampleIndex = 0;
float currValue, currMax;
switch (_graphMode) {
case GraphMode.Exclusive:
currValue = _currentGraph.exclusive.Back;
currMax = _currentGraph.exclusiveMax.Max;
break;
case GraphMode.Inclusive:
currValue = _currentGraph.inclusive.Back;
currMax = _currentGraph.inclusiveMax.Max;
break;
default:
throw new Exception("Unexpected graph mode");
}
_smoothedValue.Update(currValue, Time.deltaTime);
_smoothedMax.Update(currMax, Time.deltaTime);
_updateCount++;
if (_updateCount >= _updatePeriod) {
UpdateTexture();
_updateCount = 0;
}
}
protected virtual void FixedUpdate() {
if (_fixedTicks == -1) {
_fixedTicks = _stopwatch.ElapsedTicks;
}
}
private IEnumerator endOfFrameWaiter() {
WaitForEndOfFrame waiter = new WaitForEndOfFrame();
long endOfFrameTicks = _stopwatch.ElapsedTicks;
while (true) {
yield return waiter;
long newTicks = _stopwatch.ElapsedTicks;
AddSample("Frame Delta", GraphUnits.Miliseconds, newTicks - endOfFrameTicks);
AddSample("Framerate", GraphUnits.Framerate, newTicks - endOfFrameTicks);
endOfFrameTicks = newTicks;
AddSample("Render Delta", GraphUnits.Miliseconds, _renderTicks);
float gpuTime = XRSupportUtil.GetGPUTime();
AddSample("GPU Time", GraphUnits.Miliseconds, gpuTime);
if (_provider != null) {
AddSample("Tracking Latency", GraphUnits.Miliseconds, (_provider.GetLeapController().Now() - _provider.CurrentFrame.Timestamp) * 0.001f);
}
}
}
private void onPreCull(Camera camera) {
if (_preCullTicks == -1) {
_preCullTicks = _stopwatch.ElapsedTicks;
}
}
private void onPostRender(Camera camera) {
_renderTicks = _stopwatch.ElapsedTicks - _preCullTicks;
}
protected static float ticksToMs(long ticks) {
return (float)(ticks / (System.Diagnostics.Stopwatch.Frequency / 1000.0));
}
private string msToString(float ms) {
return (Mathf.Round(ms * 10) * 0.1f).ToString();
}
private long msToTicks(float ms) {
return (long)(ms * System.Diagnostics.Stopwatch.Frequency * 1000);
}
private float getGraphSpacing() {
switch (_currentGraph.units) {
case GraphUnits.Framerate:
return _framerateLineSpacing;
case GraphUnits.Miliseconds:
return _deltaLineSpacing;
default:
throw new Exception("Unexpected graph units");
}
}
private void UpdateTexture() {
float max = _smoothedMax.value * 1.5f;
Deque<float> history;
switch (_graphMode) {
case GraphMode.Exclusive:
history = _currentGraph.exclusive;
break;
case GraphMode.Inclusive:
history = _currentGraph.inclusive;
break;
default:
throw new Exception("Unexpected graph mode");
}
for (int i = 0; i < history.Count; i++) {
float percent = Mathf.Clamp01(history[i] / max);
byte percentByte = (byte)(percent * 255.9999f);
_colors[i] = new Color32(percentByte, percentByte, percentByte, percentByte);
}
_graphRenderer.material.SetFloat("_GraphScale", max / getGraphSpacing());
_texture.SetPixels32(_colors);
_texture.Apply();
valueMesh.text = msToString(_smoothedValue.value);
Vector3 localP = valueMesh.transform.localPosition;
localP.y = _smoothedValue.value / max - 0.5f;
valueMesh.transform.localPosition = localP;
}
protected Graph getGraph(string name, GraphUnits units) {
Graph graph;
if (!_graphs.TryGetValue(name, out graph)) {
graph = new Graph(name, units, _historyLength);
_graphs[name] = graph;
GameObject buttonObj = Instantiate(customGraphPrefab);
buttonObj.transform.SetParent(customGraphPrefab.transform.parent, false);
Text buttonText = buttonObj.GetComponentInChildren<Text>();
buttonText.text = name.Replace(' ', '\n');
Button button = buttonObj.GetComponentInChildren<Button>();
addCallback(button, name);
buttonObj.SetActive(true);
if (_currentGraph == null && name == _defaultGraph) {
SwtichGraph(name);
}
}
return graph;
}
protected void addCallback(Button button, string name) {
button.onClick.AddListener(() => SwtichGraph(name));
}
protected void replayKeys() {
GraphKey key;
while (_keyBuffer.Count > 0) {
_keyBuffer.PopBack(out key);
if (key.name != null) {
Graph graph = getGraph(key.name, key.units);
if (_currentGraphStack.Count != 0) {
_currentGraphStack.Peek().PauseSample(key.tick);
}
graph.BeginSample(key.tick);
_currentGraphStack.Push(graph);
} else {
Graph graph = _currentGraphStack.Pop();
graph.EndSample(key.tick);
if (_currentGraphStack.Count != 0) {
_currentGraphStack.Peek().ResumeSample(key.tick);
}
}
}
}
protected class Graph {
public string name;
public GraphUnits units;
public Deque<float> exclusive;
public Deque<float> inclusive;
public SlidingMax exclusiveMax, inclusiveMax;
private int maxHistory;
private long accumulatedInclusiveTicks, accumulatedExclusiveTicks;
private long inclusiveStart, exclusiveStart;
public Graph(string name, GraphUnits units, int maxHistory) {
this.name = name;
this.units = units;
this.maxHistory = maxHistory;
exclusive = new Deque<float>(maxHistory);
inclusive = new Deque<float>(maxHistory);
exclusiveMax = new SlidingMax(maxHistory);
inclusiveMax = new SlidingMax(maxHistory);
}
public void BeginSample(long currTicks) {
inclusiveStart = exclusiveStart = currTicks;
}
public void PauseSample(long currTicks) {
accumulatedInclusiveTicks += currTicks - inclusiveStart;
}
public void ResumeSample(long currTicks) {
inclusiveStart = currTicks;
}
public void EndSample(long currTicks) {
accumulatedInclusiveTicks += currTicks - inclusiveStart;
accumulatedExclusiveTicks += currTicks - exclusiveStart;
}
public void AddSample(long ticks) {
accumulatedInclusiveTicks += ticks;
accumulatedExclusiveTicks += ticks;
}
public void ClearSample() {
accumulatedExclusiveTicks = accumulatedInclusiveTicks = 0;
}
public void RecordSample(int sampleCount) {
float inclusiveMs = ticksToMs(accumulatedInclusiveTicks / sampleCount);
float exclusiveMs = ticksToMs(accumulatedExclusiveTicks / sampleCount);
ClearSample();
switch (units) {
case GraphUnits.Miliseconds:
inclusive.PushBack(inclusiveMs);
exclusive.PushBack(exclusiveMs);
inclusiveMax.AddValue(inclusiveMs);
exclusiveMax.AddValue(exclusiveMs);
break;
case GraphUnits.Framerate:
inclusive.PushBack(1000.0f / inclusiveMs);
exclusive.PushBack(1000.0f / exclusiveMs);
inclusiveMax.AddValue(1000.0f / inclusiveMs);
exclusiveMax.AddValue(1000.0f / exclusiveMs);
break;
default:
throw new Exception("Unexpected units type");
}
while (inclusive.Count > maxHistory) {
inclusive.PopFront();
}
while (exclusive.Count > maxHistory) {
exclusive.PopFront();
}
}
}
}
}