SensorMonitoringWindow.xaml.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Client.Computer;
  4. using Content.Client.Stylesheets;
  5. using Content.Client.UserInterface.Controls;
  6. using Content.Shared.SensorMonitoring;
  7. using JetBrains.Annotations;
  8. using Robust.Client.AutoGenerated;
  9. using Robust.Client.Graphics;
  10. using Robust.Client.UserInterface;
  11. using Robust.Client.UserInterface.Controls;
  12. using Robust.Client.UserInterface.XAML;
  13. using Robust.Shared.Timing;
  14. using ConsoleUIState = Content.Shared.SensorMonitoring.SensorMonitoringConsoleBoundInterfaceState;
  15. using IncrementalUIState = Content.Shared.SensorMonitoring.SensorMonitoringIncrementalUpdate;
  16. namespace Content.Client.SensorMonitoring;
  17. [GenerateTypedNameReferences]
  18. public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindow<ConsoleUIState>
  19. {
  20. [Dependency] private readonly IGameTiming _gameTiming = default!;
  21. [Dependency] private readonly ILocalizationManager _loc = default!;
  22. private TimeSpan _retentionTime;
  23. private readonly Dictionary<int, SensorData> _sensorData = new();
  24. public SensorMonitoringWindow()
  25. {
  26. RobustXamlLoader.Load(this);
  27. IoCManager.InjectDependencies(this);
  28. }
  29. public void UpdateState(ConsoleUIState state)
  30. {
  31. _retentionTime = state.RetentionTime;
  32. _sensorData.Clear();
  33. foreach (var netSensor in state.Sensors)
  34. {
  35. var sensor = new SensorData
  36. {
  37. Name = netSensor.Name,
  38. Address = netSensor.Address,
  39. DeviceType = netSensor.DeviceType
  40. };
  41. _sensorData.Add(netSensor.NetId, sensor);
  42. foreach (var netStream in netSensor.Streams)
  43. {
  44. var stream = new SensorStream
  45. {
  46. Name = netStream.Name,
  47. Unit = netStream.Unit
  48. };
  49. sensor.Streams.Add(netStream.NetId, stream);
  50. foreach (var sample in netStream.Samples)
  51. {
  52. stream.Samples.Enqueue(sample);
  53. }
  54. }
  55. }
  56. Update();
  57. }
  58. public void ReceiveMessage(BoundUserInterfaceMessage message)
  59. {
  60. if (message is not IncrementalUIState incremental)
  61. return;
  62. foreach (var removed in incremental.RemovedSensors)
  63. {
  64. _sensorData.Remove(removed);
  65. }
  66. foreach (var netSensor in incremental.Sensors)
  67. {
  68. // TODO: Fuck this doesn't work if a sensor is added while the UI is open.
  69. if (!_sensorData.TryGetValue(netSensor.NetId, out var sensor))
  70. continue;
  71. foreach (var netStream in netSensor.Streams)
  72. {
  73. // TODO: Fuck this doesn't work if a stream is added while the UI is open.
  74. if (!sensor.Streams.TryGetValue(netStream.NetId, out var stream))
  75. continue;
  76. foreach (var (time, value) in netStream.Samples)
  77. {
  78. stream.Samples.Enqueue(new SensorSample(time + incremental.RelTime, value));
  79. }
  80. }
  81. }
  82. CullOldSamples();
  83. Update();
  84. }
  85. private void Update()
  86. {
  87. Asdf.RemoveAllChildren();
  88. var curTime = _gameTiming.CurTime;
  89. var startTime = curTime - _retentionTime;
  90. foreach (var sensor in _sensorData.Values)
  91. {
  92. var labelName = new Label { Text = sensor.Name, StyleClasses = { StyleBase.StyleClassLabelHeading } };
  93. var labelAddress = new Label
  94. {
  95. Text = sensor.Address,
  96. Margin = new Thickness(4, 0),
  97. VerticalAlignment = VAlignment.Bottom,
  98. StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }
  99. };
  100. Asdf.AddChild(new BoxContainer
  101. {
  102. Orientation = BoxContainer.LayoutOrientation.Horizontal, Children =
  103. {
  104. labelName,
  105. labelAddress
  106. }
  107. });
  108. foreach (var stream in sensor.Streams.Values)
  109. {
  110. var maxValue = stream.Samples.Max(x => x.Value);
  111. // TODO: Better way to do this?
  112. var lastSample = stream.Samples.Last();
  113. Asdf.AddChild(new BoxContainer
  114. {
  115. Orientation = BoxContainer.LayoutOrientation.Horizontal,
  116. Children =
  117. {
  118. new Label { Text = stream.Name, StyleClasses = { "monospace" }, HorizontalExpand = true },
  119. new Label { Text = FormatValue(stream.Unit, lastSample.Value) }
  120. }
  121. });
  122. Asdf.AddChild(new GraphView(stream.Samples, startTime, curTime, maxValue * 1.1f) { MinHeight = 150 });
  123. Asdf.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
  124. }
  125. }
  126. }
  127. private string FormatValue(SensorUnit unit, float value)
  128. {
  129. return _loc.GetString(
  130. "sensor-monitoring-value-display",
  131. ("unit", unit.ToString()),
  132. ("value", value));
  133. }
  134. private void CullOldSamples()
  135. {
  136. var startTime = _gameTiming.CurTime - _retentionTime;
  137. foreach (var sensor in _sensorData.Values)
  138. {
  139. foreach (var stream in sensor.Streams.Values)
  140. {
  141. while (stream.Samples.TryPeek(out var sample) && sample.Time < startTime)
  142. {
  143. stream.Samples.Dequeue();
  144. }
  145. }
  146. }
  147. }
  148. private sealed class SensorData
  149. {
  150. public string Name = "";
  151. public string Address = "";
  152. public SensorDeviceType DeviceType;
  153. public readonly Dictionary<int, SensorStream> Streams = new();
  154. }
  155. private sealed class SensorStream
  156. {
  157. public string Name = "";
  158. public SensorUnit Unit;
  159. public readonly Queue<SensorSample> Samples = new();
  160. }
  161. private sealed class GraphView : Control
  162. {
  163. private readonly Queue<SensorSample> _samples;
  164. private readonly TimeSpan _startTime;
  165. private readonly TimeSpan _curTime;
  166. private readonly float _maxY;
  167. public GraphView(Queue<SensorSample> samples, TimeSpan startTime, TimeSpan curTime, float maxY)
  168. {
  169. _samples = samples;
  170. _startTime = startTime;
  171. _curTime = curTime;
  172. _maxY = maxY;
  173. RectClipContent = true;
  174. }
  175. protected override void Draw(DrawingHandleScreen handle)
  176. {
  177. base.Draw(handle);
  178. var window = (float) (_curTime - _startTime).TotalSeconds;
  179. // TODO: omg this is terrible don't fucking hardcode this size to something uncached huge omfg.
  180. var vertices = new Vector2[25000];
  181. var countVtx = 0;
  182. var lastPoint = new Vector2(float.NaN, float.NaN);
  183. foreach (var (time, sample) in _samples)
  184. {
  185. var relTime = (float) (time - _startTime).TotalSeconds;
  186. var posY = PixelHeight - (sample / _maxY) * PixelHeight;
  187. var posX = (relTime / window) * PixelWidth;
  188. var newPoint = new Vector2(posX, posY);
  189. if (float.IsFinite(lastPoint.X))
  190. {
  191. handle.DrawLine(lastPoint, newPoint, Color.White);
  192. vertices[countVtx++] = lastPoint;
  193. vertices[countVtx++] = lastPoint with { Y = PixelHeight };
  194. vertices[countVtx++] = newPoint;
  195. vertices[countVtx++] = newPoint;
  196. vertices[countVtx++] = lastPoint with { Y = PixelHeight };
  197. vertices[countVtx++] = newPoint with { Y = PixelHeight };
  198. }
  199. lastPoint = newPoint;
  200. }
  201. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, vertices.AsSpan(0, countVtx), Color.White.WithAlpha(0.1f));
  202. }
  203. }
  204. }
  205. [UsedImplicitly]
  206. public sealed class
  207. SensorMonitoringConsoleBoundUserInterface : ComputerBoundUserInterface<SensorMonitoringWindow, ConsoleUIState>
  208. {
  209. public SensorMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
  210. {
  211. }
  212. }