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

VisualizationHelpers.cs 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. using System;
  2. using UnityEngine.InputSystem.Utilities;
  3. using Unity.Collections.LowLevel.Unsafe;
  4. using UnityEngine.InputSystem.LowLevel;
  5. ////REVIEW: for vector2 visualizers of sticks, it could be useful to also visualize deadzones and raw values
  6. namespace UnityEngine.InputSystem.Samples
  7. {
  8. internal static class VisualizationHelpers
  9. {
  10. public enum Axis { X, Y, Z }
  11. public abstract class Visualizer
  12. {
  13. public abstract void OnDraw(Rect rect);
  14. public abstract void AddSample(object value, double time);
  15. }
  16. public abstract class ValueVisualizer<TValue> : Visualizer
  17. where TValue : struct
  18. {
  19. public RingBuffer<TValue> samples;
  20. public RingBuffer<GUIContent> samplesText;
  21. protected ValueVisualizer(int numSamples = 10)
  22. {
  23. samples = new RingBuffer<TValue>(numSamples);
  24. samplesText = new RingBuffer<GUIContent>(numSamples);
  25. }
  26. public override void AddSample(object value, double time)
  27. {
  28. var v = default(TValue);
  29. if (value != null)
  30. {
  31. if (!(value is TValue val))
  32. throw new ArgumentException(
  33. $"Expecting value of type '{typeof(TValue).Name}' but value of type '{value?.GetType().Name}' instead",
  34. nameof(value));
  35. v = val;
  36. }
  37. samples.Append(v);
  38. samplesText.Append(new GUIContent(v.ToString()));
  39. }
  40. }
  41. // Visualizes integer and real type primitives.
  42. public class ScalarVisualizer<TValue> : ValueVisualizer<TValue>
  43. where TValue : struct
  44. {
  45. public TValue limitMin;
  46. public TValue limitMax;
  47. public TValue min;
  48. public TValue max;
  49. public ScalarVisualizer(int numSamples = 10)
  50. : base(numSamples)
  51. {
  52. }
  53. public override void OnDraw(Rect rect)
  54. {
  55. // For now, only draw the current value.
  56. DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
  57. if (samples.count == 0)
  58. return;
  59. var sample = samples[samples.count - 1];
  60. if (Compare(sample, default) == 0)
  61. return;
  62. if (Compare(limitMin, default) != 0)
  63. {
  64. // Two-way visualization with positive and negative side.
  65. throw new NotImplementedException();
  66. }
  67. else
  68. {
  69. // One-way visualization with only positive side.
  70. var ratio = Divide(sample, limitMax);
  71. var fillRect = rect;
  72. fillRect.width = rect.width * ratio;
  73. DrawRectangle(fillRect, new Color(0, 1, 0, 0.75f));
  74. var valuePos = new Vector2(fillRect.xMax, fillRect.y + fillRect.height / 2);
  75. DrawText(samplesText[samples.count - 1], valuePos, ValueTextStyle);
  76. }
  77. }
  78. public override void AddSample(object value, double time)
  79. {
  80. base.AddSample(value, time);
  81. if (value != null)
  82. {
  83. var val = (TValue)value;
  84. if (Compare(min, val) > 0)
  85. min = val;
  86. if (Compare(max, val) < 0)
  87. max = val;
  88. }
  89. }
  90. private static unsafe int Compare(TValue left, TValue right)
  91. {
  92. var leftPtr = UnsafeUtility.AddressOf(ref left);
  93. var rightPtr = UnsafeUtility.AddressOf(ref right);
  94. if (typeof(TValue) == typeof(int))
  95. return ((int*)leftPtr)->CompareTo(*(int*)rightPtr);
  96. if (typeof(TValue) == typeof(float))
  97. return ((float*)leftPtr)->CompareTo(*(float*)rightPtr);
  98. throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
  99. }
  100. private static unsafe void Subtract(ref TValue left, TValue right)
  101. {
  102. var leftPtr = UnsafeUtility.AddressOf(ref left);
  103. var rightPtr = UnsafeUtility.AddressOf(ref right);
  104. if (typeof(TValue) == typeof(int))
  105. *(int*)leftPtr = *(int*)leftPtr - *(int*)rightPtr;
  106. if (typeof(TValue) == typeof(float))
  107. *(float*)leftPtr = *(float*)leftPtr - *(float*)rightPtr;
  108. throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
  109. }
  110. private static unsafe float Divide(TValue left, TValue right)
  111. {
  112. var leftPtr = UnsafeUtility.AddressOf(ref left);
  113. var rightPtr = UnsafeUtility.AddressOf(ref right);
  114. if (typeof(TValue) == typeof(int))
  115. return (float)*(int*)leftPtr / *(int*)rightPtr;
  116. if (typeof(TValue) == typeof(float))
  117. return *(float*)leftPtr / *(float*)rightPtr;
  118. throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
  119. }
  120. }
  121. // Visualizes .current device value
  122. public class CurrentDeviceVisualizer : Visualizer
  123. {
  124. private InputDevice m_CurrentDevice = null;
  125. public override void OnDraw(Rect rect)
  126. {
  127. // For now, only draw the current value.
  128. DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
  129. var name = m_CurrentDevice != null ? m_CurrentDevice.name : "null";
  130. DrawText(name, new Vector2(rect.xMin + 4, (rect.yMin + rect.yMax) / 2.0f), ValueTextStyle);
  131. }
  132. public override void AddSample(object value, double time)
  133. {
  134. var device = (InputDevice)value;
  135. if (device is Gamepad)
  136. m_CurrentDevice = Gamepad.current;
  137. else if (device is Mouse)
  138. m_CurrentDevice = Mouse.current;
  139. else if (device is Pen)
  140. m_CurrentDevice = Pen.current;
  141. else if (device is Pointer) // should be last, because it's a base class for Mouse and Pen
  142. m_CurrentDevice = Pointer.current;
  143. else
  144. throw new ArgumentException(
  145. $"Expected device type that implements .current, but got '{device.name}' (deviceId: {device.deviceId}) instead ");
  146. }
  147. }
  148. ////TODO: allow asymmetric center (i.e. center not being a midpoint of rectangle)
  149. ////TODO: enforce proper proportion between X and Y; it's confusing that X and Y can have different units yet have the same length
  150. public class Vector2Visualizer : ValueVisualizer<Vector2>
  151. {
  152. // Our value space extends radially from the center, i.e. we have
  153. // 360 discrete directions. Sampling at that granularity doesn't work
  154. // super well in visualizations so we quantize to 3 degree increments.
  155. public Vector2[] maximums = new Vector2[360 / 3];
  156. public Vector2 limits = new Vector2(1, 1);
  157. private GUIContent limitsXText;
  158. private GUIContent limitsYText;
  159. public Vector2Visualizer(int numSamples = 10)
  160. : base(numSamples)
  161. {
  162. }
  163. public override void AddSample(object value, double time)
  164. {
  165. base.AddSample(value, time);
  166. if (value != null)
  167. {
  168. // Keep track of radial maximums.
  169. var vector = (Vector2)value;
  170. var angle = Vector2.SignedAngle(Vector2.right, vector);
  171. if (angle < 0)
  172. angle = 360 + angle;
  173. var angleInt = Mathf.FloorToInt(angle) / 3;
  174. if (vector.sqrMagnitude > maximums[angleInt].sqrMagnitude)
  175. maximums[angleInt] = vector;
  176. // Extend limits if value is out of range.
  177. var limitX = Mathf.Max(Mathf.Abs(vector.x), limits.x);
  178. var limitY = Mathf.Max(Mathf.Abs(vector.y), limits.y);
  179. if (!Mathf.Approximately(limitX, limits.x))
  180. {
  181. limits.x = limitX;
  182. limitsXText = null;
  183. }
  184. if (!Mathf.Approximately(limitY, limits.y))
  185. {
  186. limits.y = limitY;
  187. limitsYText = null;
  188. }
  189. }
  190. }
  191. public override void OnDraw(Rect rect)
  192. {
  193. DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
  194. DrawAxis(Axis.X, rect, new Color(0, 1, 0, 0.75f));
  195. DrawAxis(Axis.Y, rect, new Color(0, 1, 0, 0.75f));
  196. var sampleCount = samples.count;
  197. if (sampleCount == 0)
  198. return;
  199. // If limits aren't (1,1), show the actual values.
  200. if (limits != new Vector2(1, 1))
  201. {
  202. if (limitsXText == null)
  203. limitsXText = new GUIContent(limits.x.ToString());
  204. if (limitsYText == null)
  205. limitsYText = new GUIContent(limits.y.ToString());
  206. var limitsXSize = ValueTextStyle.CalcSize(limitsXText);
  207. var limitsXPos = new Vector2(rect.x - limitsXSize.x, rect.y - 5);
  208. var limitsYPos = new Vector2(rect.xMax, rect.yMax);
  209. DrawText(limitsXText, limitsXPos, ValueTextStyle);
  210. DrawText(limitsYText, limitsYPos, ValueTextStyle);
  211. }
  212. // Draw maximums.
  213. var numMaximums = 0;
  214. var firstMaximumPos = default(Vector2);
  215. var lastMaximumPos = default(Vector2);
  216. for (var i = 0; i < 360 / 3; ++i)
  217. {
  218. var value = maximums[i];
  219. if (value == default)
  220. continue;
  221. var valuePos = PixelPosForValue(value, rect);
  222. if (numMaximums > 0)
  223. DrawLine(lastMaximumPos, valuePos, new Color(1, 1, 1, 0.25f));
  224. else
  225. firstMaximumPos = valuePos;
  226. lastMaximumPos = valuePos;
  227. ++numMaximums;
  228. }
  229. if (numMaximums > 1)
  230. DrawLine(lastMaximumPos, firstMaximumPos, new Color(1, 1, 1, 0.25f));
  231. // Draw samples.
  232. var alphaStep = 1f / sampleCount;
  233. var alpha = 1f;
  234. for (var i = sampleCount - 1; i >= 0; --i) // Go newest to oldest.
  235. {
  236. var value = samples[i];
  237. var valueRect = RectForValue(value, rect);
  238. DrawRectangle(valueRect, new Color(1, 0, 0, alpha));
  239. alpha -= alphaStep;
  240. }
  241. // Print value of most recent sample. Draw last so
  242. // we draw over the other stuff.
  243. var lastSample = samples[sampleCount - 1];
  244. var lastSamplePos = PixelPosForValue(lastSample, rect);
  245. lastSamplePos.x += 3;
  246. lastSamplePos.y += 3;
  247. DrawText(samplesText[sampleCount - 1], lastSamplePos, ValueTextStyle);
  248. }
  249. private Rect RectForValue(Vector2 value, Rect rect)
  250. {
  251. var pos = PixelPosForValue(value, rect);
  252. return new Rect(pos.x - 1, pos.y - 1, 2, 2);
  253. }
  254. private Vector2 PixelPosForValue(Vector2 value, Rect rect)
  255. {
  256. var center = rect.center;
  257. var x = Mathf.Abs(value.x) / limits.x * Mathf.Sign(value.x);
  258. var y = Mathf.Abs(value.y) / limits.y * Mathf.Sign(value.y) * -1; // GUI Y is upside down.
  259. var xInPixels = x * rect.width / 2;
  260. var yInPixels = y * rect.height / 2;
  261. return new Vector2(center.x + xInPixels,
  262. center.y + yInPixels);
  263. }
  264. }
  265. // Y axis is time, X axis can be multiple visualizations.
  266. public class TimelineVisualizer : Visualizer
  267. {
  268. public bool showLegend { get; set; }
  269. public bool showLimits { get; set; }
  270. public TimeUnit timeUnit { get; set; } = TimeUnit.Seconds;
  271. public GUIContent valueUnit { get; set; }
  272. ////REVIEW: should this be per timeline?
  273. public int timelineCount => m_Timelines != null ? m_Timelines.Length : 0;
  274. public int historyDepth { get; set; } = 100;
  275. public Vector2 limitsY
  276. {
  277. get => m_LimitsY;
  278. set
  279. {
  280. m_LimitsY = value;
  281. m_LimitsYMin = null;
  282. m_LimitsYMax = null;
  283. }
  284. }
  285. public TimelineVisualizer(float totalTimeUnitsShown = 4)
  286. {
  287. m_TotalTimeUnitsShown = totalTimeUnitsShown;
  288. }
  289. public override void OnDraw(Rect rect)
  290. {
  291. var endTime = Time.realtimeSinceStartup;
  292. var startTime = endTime - m_TotalTimeUnitsShown;
  293. var endFrame = InputState.updateCount;
  294. var startFrame = endFrame - (int)m_TotalTimeUnitsShown;
  295. for (var i = 0; i < timelineCount; ++i)
  296. {
  297. var timeline = m_Timelines[i];
  298. var sampleCount = timeUnit == TimeUnit.Frames
  299. ? timeline.frameSamples.count
  300. : timeline.timeSamples.count;
  301. // Set up clip rect so that we can do stuff like render lines to samples
  302. // falling outside the render rectangle and have them get clipped.
  303. GUI.BeginGroup(rect);
  304. var plotType = timeline.plotType;
  305. var lastPos = default(Vector2);
  306. var timeUnitsPerPixel = rect.width / m_TotalTimeUnitsShown;
  307. var color = m_Timelines[i].color;
  308. for (var n = sampleCount - 1; n >= 0; --n)
  309. {
  310. var sample = timeUnit == TimeUnit.Frames
  311. ? timeline.frameSamples[n].value
  312. : timeline.timeSamples[n].value;
  313. ////TODO: respect limitsY
  314. float y;
  315. if (sample.isEmpty)
  316. y = 0.5f;
  317. else
  318. y = sample.ToSingle();
  319. y /= limitsY.y;
  320. var deltaTime = timeUnit == TimeUnit.Frames
  321. ? timeline.frameSamples[n].frame - startFrame
  322. : timeline.timeSamples[n].time - startTime;
  323. var pos = new Vector2(deltaTime * timeUnitsPerPixel, rect.height - y * rect.height);
  324. if (plotType == PlotType.LineGraph)
  325. {
  326. if (n != sampleCount - 1)
  327. {
  328. DrawLine(lastPos, pos, color, 2);
  329. if (pos.x < 0)
  330. break;
  331. }
  332. }
  333. else if (plotType == PlotType.BarChart)
  334. {
  335. ////TODO: make rectangles have a progressively stronger hue or saturation
  336. var barRect = new Rect(pos.x, pos.y, timeUnitsPerPixel, y * limitsY.y * rect.height);
  337. DrawRectangle(barRect, color);
  338. }
  339. lastPos = pos;
  340. }
  341. GUI.EndGroup();
  342. }
  343. if (showLegend && timelineCount > 0)
  344. {
  345. var legendRect = rect;
  346. legendRect.x += rect.width + 2;
  347. legendRect.width = 400;
  348. legendRect.height = ValueTextStyle.CalcHeight(m_Timelines[0].name, 400);
  349. for (var i = 0; i < m_Timelines.Length; ++i)
  350. {
  351. var colorTagRect = legendRect;
  352. colorTagRect.width = 5;
  353. var labelRect = legendRect;
  354. labelRect.x += 8;
  355. labelRect.width -= 8;
  356. DrawRectangle(colorTagRect, m_Timelines[i].color);
  357. DrawText(m_Timelines[i].name, labelRect.position, ValueTextStyle);
  358. legendRect.y += labelRect.height + 2;
  359. }
  360. }
  361. if (showLimits)
  362. {
  363. if (m_LimitsYMax == null)
  364. m_LimitsYMax = new GUIContent(m_LimitsY.y.ToString());
  365. if (m_LimitsYMin == null)
  366. m_LimitsYMin = new GUIContent(m_LimitsY.x.ToString());
  367. DrawText(m_LimitsYMax, new Vector2(rect.x + rect.width, rect.y), ValueTextStyle);
  368. DrawText(m_LimitsYMin, new Vector2(rect.x + rect.width, rect.y + rect.height), ValueTextStyle);
  369. }
  370. }
  371. public override void AddSample(object value, double time)
  372. {
  373. if (timelineCount == 0)
  374. throw new InvalidOperationException("Must have set up a timeline first");
  375. AddSample(0, PrimitiveValue.FromObject(value), (float)time);
  376. }
  377. public int AddTimeline(string name, Color color, PlotType plotType = default)
  378. {
  379. var timeline = new Timeline
  380. {
  381. name = new GUIContent(name),
  382. color = color,
  383. plotType = plotType,
  384. };
  385. if (timeUnit == TimeUnit.Frames)
  386. timeline.frameSamples = new RingBuffer<FrameSample>(historyDepth);
  387. else
  388. timeline.timeSamples = new RingBuffer<TimeSample>(historyDepth);
  389. var index = timelineCount;
  390. Array.Resize(ref m_Timelines, timelineCount + 1);
  391. m_Timelines[index] = timeline;
  392. return index;
  393. }
  394. public int GetTimeline(string name)
  395. {
  396. for (var i = 0; i < timelineCount; ++i)
  397. if (string.Compare(m_Timelines[i].name.text, name, StringComparison.InvariantCultureIgnoreCase) == 0)
  398. return i;
  399. return -1;
  400. }
  401. // Add a time-based sample.
  402. public void AddSample(int timelineIndex, PrimitiveValue value, float time)
  403. {
  404. m_Timelines[timelineIndex].timeSamples.Append(new TimeSample
  405. {
  406. value = value,
  407. time = time
  408. });
  409. }
  410. // Add a frame-based sample.
  411. public ref PrimitiveValue GetOrCreateSample(int timelineIndex, int frame)
  412. {
  413. ref var timeline = ref m_Timelines[timelineIndex];
  414. ref var samples = ref timeline.frameSamples;
  415. var count = samples.count;
  416. if (count > 0)
  417. {
  418. if (samples[count - 1].frame == frame)
  419. return ref samples[count - 1].value;
  420. Debug.Assert(samples[count - 1].frame < frame, "Frame numbers must be ascending");
  421. }
  422. return ref samples.Append(new FrameSample {frame = frame}).value;
  423. }
  424. private float m_TotalTimeUnitsShown;
  425. private Vector2 m_LimitsY = new Vector2(-1, 1);
  426. private GUIContent m_LimitsYMin;
  427. private GUIContent m_LimitsYMax;
  428. private Timeline[] m_Timelines;
  429. private struct TimeSample
  430. {
  431. public PrimitiveValue value;
  432. public float time;
  433. }
  434. private struct FrameSample
  435. {
  436. public PrimitiveValue value;
  437. public int frame;
  438. }
  439. private struct Timeline
  440. {
  441. public GUIContent name;
  442. public Color color;
  443. public RingBuffer<TimeSample> timeSamples;
  444. public RingBuffer<FrameSample> frameSamples;
  445. public PrimitiveValue minValue;
  446. public PrimitiveValue maxValue;
  447. public PlotType plotType;
  448. }
  449. public enum PlotType
  450. {
  451. LineGraph,
  452. BarChart,
  453. }
  454. public enum TimeUnit
  455. {
  456. Seconds,
  457. Frames,
  458. }
  459. }
  460. public static void DrawAxis(Axis axis, Rect rect, Color color = default, float width = 1)
  461. {
  462. Vector2 start, end, tickOffset;
  463. switch (axis)
  464. {
  465. case Axis.X:
  466. start = new Vector2(rect.x, rect.y + rect.height / 2);
  467. end = new Vector2(start.x + rect.width, rect.y + rect.height / 2);
  468. tickOffset = new Vector2(0, 3);
  469. break;
  470. case Axis.Y:
  471. start = new Vector2(rect.x + rect.width / 2, rect.y);
  472. end = new Vector2(start.x, rect.y + rect.height);
  473. tickOffset = new Vector2(3, 0);
  474. break;
  475. case Axis.Z:
  476. // From bottom left corner to upper right corner.
  477. start = new Vector2(rect.x, rect.yMax);
  478. end = new Vector2(rect.xMax, rect.y);
  479. tickOffset = new Vector2(1.5f, 1.5f);
  480. break;
  481. default:
  482. throw new NotImplementedException();
  483. }
  484. ////TODO: label limits
  485. DrawLine(start, end, color, width);
  486. DrawLine(start - tickOffset, start + tickOffset, color, width);
  487. DrawLine(end - tickOffset, end + tickOffset, color, width);
  488. }
  489. public static void DrawRectangle(Rect rect, Color color)
  490. {
  491. var savedColor = GUI.color;
  492. GUI.color = color;
  493. GUI.DrawTexture(rect, OnePixTex);
  494. GUI.color = savedColor;
  495. }
  496. public static void DrawText(string text, Vector2 pos, GUIStyle style)
  497. {
  498. var content = new GUIContent(text);
  499. DrawText(content, pos, style);
  500. }
  501. public static void DrawText(GUIContent text, Vector2 pos, GUIStyle style)
  502. {
  503. var content = new GUIContent(text);
  504. var size = style.CalcSize(content);
  505. var rect = new Rect(pos.x, pos.y, size.x, size.y);
  506. style.Draw(rect, content, false, false, false, false);
  507. }
  508. // Adapted from http://wiki.unity3d.com/index.php?title=DrawLine
  509. public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color = default, float width = 1)
  510. {
  511. // Save the current GUI matrix, since we're going to make changes to it.
  512. var matrix = GUI.matrix;
  513. // Store current GUI color, so we can switch it back later,
  514. // and set the GUI color to the color parameter
  515. var savedColor = GUI.color;
  516. GUI.color = color;
  517. // Determine the angle of the line.
  518. var angle = Vector3.Angle(pointB - pointA, Vector2.right);
  519. // Vector3.Angle always returns a positive number.
  520. // If pointB is above pointA, then angle needs to be negative.
  521. if (pointA.y > pointB.y)
  522. angle = -angle;
  523. // Use ScaleAroundPivot to adjust the size of the line.
  524. // We could do this when we draw the texture, but by scaling it here we can use
  525. // non-integer values for the width and length (such as sub 1 pixel widths).
  526. // Note that the pivot point is at +.5 from pointA.y, this is so that the width of the line
  527. // is centered on the origin at pointA.
  528. GUIUtility.ScaleAroundPivot(new Vector2((pointB - pointA).magnitude, width), new Vector2(pointA.x, pointA.y + 0.5f));
  529. // Set the rotation for the line.
  530. // The angle was calculated with pointA as the origin.
  531. GUIUtility.RotateAroundPivot(angle, pointA);
  532. // Finally, draw the actual line.
  533. // We're really only drawing a 1x1 texture from pointA.
  534. // The matrix operations done with ScaleAroundPivot and RotateAroundPivot will make this
  535. // render with the proper width, length, and angle.
  536. GUI.DrawTexture(new Rect(pointA.x, pointA.y, 1, 1), OnePixTex);
  537. // We're done. Restore the GUI matrix and GUI color to whatever they were before.
  538. GUI.matrix = matrix;
  539. GUI.color = savedColor;
  540. }
  541. private static Texture2D s_OnePixTex;
  542. private static GUIStyle s_ValueTextStyle;
  543. internal static GUIStyle ValueTextStyle
  544. {
  545. get
  546. {
  547. if (s_ValueTextStyle == null)
  548. {
  549. s_ValueTextStyle = new GUIStyle();
  550. s_ValueTextStyle.fontSize -= 2;
  551. s_ValueTextStyle.normal.textColor = Color.white;
  552. }
  553. return s_ValueTextStyle;
  554. }
  555. }
  556. internal static Texture2D OnePixTex
  557. {
  558. get
  559. {
  560. if (s_OnePixTex == null)
  561. s_OnePixTex = new Texture2D(1, 1);
  562. return s_OnePixTex;
  563. }
  564. }
  565. public struct RingBuffer<TValue>
  566. {
  567. public TValue[] array;
  568. public int head;
  569. public int count;
  570. public RingBuffer(int size)
  571. {
  572. array = new TValue[size];
  573. head = 0;
  574. count = 0;
  575. }
  576. public ref TValue Append(TValue value)
  577. {
  578. int index;
  579. var bufferSize = array.Length;
  580. if (count < bufferSize)
  581. {
  582. Debug.Assert(head == 0, "Head can't have moved if buffer isn't full yet");
  583. index = count;
  584. ++count;
  585. }
  586. else
  587. {
  588. // Buffer is full. Bump head.
  589. index = (head + count) % bufferSize;
  590. ++head;
  591. }
  592. array[index] = value;
  593. return ref array[index];
  594. }
  595. public ref TValue this[int index]
  596. {
  597. get
  598. {
  599. if (index < 0 || index >= count)
  600. throw new ArgumentOutOfRangeException(nameof(index));
  601. return ref array[(head + index) % array.Length];
  602. }
  603. }
  604. }
  605. }
  606. }