| | 1 | | using UnityEngine; |
| | 2 | | using System.Collections; |
| | 3 | | using System.IO; |
| | 4 | | using System; |
| | 5 | | using System.Linq; |
| | 6 | | using System.Collections.Generic; |
| | 7 | | using System.Threading; |
| | 8 | |
|
| | 9 | | public class SimMonitor : MonoBehaviour { |
| | 10 | | private const float _updateRate = 0.1f; // 100 Hz |
| | 11 | | private string _telemetryBinPath; |
| | 12 | | private string _eventLogPath; |
| | 13 | | private Coroutine _monitorRoutine; |
| | 14 | |
|
| | 15 | | private string _sessionDirectory; |
| | 16 | |
|
| | 17 | | private FileStream _telemetryFileStream; |
| | 18 | | private BinaryWriter _telemetryBinaryWriter; |
| | 19 | |
|
| | 20 | | [SerializeField] |
| | 21 | | private List<EventRecord> _eventLogCache; |
| | 22 | |
|
| | 23 | | [System.Serializable] |
| | 24 | | private class EventRecord { |
| | 25 | | public float Time; |
| | 26 | | public float PositionX; |
| | 27 | | public float PositionY; |
| | 28 | | public float PositionZ; |
| | 29 | | public string EventType; |
| | 30 | | public string Details; |
| | 31 | | } |
| | 32 | |
|
| 1 | 33 | | private void Awake() { |
| 1 | 34 | | InitializeSessionDirectory(); |
| 1 | 35 | | } |
| | 36 | |
|
| 1 | 37 | | private void Start() { |
| 1 | 38 | | SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted; |
| 1 | 39 | | SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded; |
| 1 | 40 | | SimManager.Instance.OnNewThreat += RegisterNewThreat; |
| 1 | 41 | | SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor; |
| 1 | 42 | | } |
| | 43 | |
|
| 1 | 44 | | private void InitializeSessionDirectory() { |
| 1 | 45 | | string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); |
| 1 | 46 | | _sessionDirectory = Application.persistentDataPath + $"\\Telemetry\\Logs\\{timestamp}"; |
| 1 | 47 | | Directory.CreateDirectory(_sessionDirectory); |
| 1 | 48 | | Debug.Log($"Monitoring simulation logs to {_sessionDirectory}"); |
| 1 | 49 | | } |
| | 50 | |
|
| 0 | 51 | | private void InitializeTelemetryLogFiles() { |
| 0 | 52 | | string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); |
| 0 | 53 | | _telemetryBinPath = Path.Combine(_sessionDirectory, $"sim_telemetry_{timestamp}.bin"); |
| | 54 | |
|
| | 55 | | // Open the file stream and binary writer for telemetry data |
| 0 | 56 | | _telemetryFileStream = |
| | 57 | | new FileStream(_telemetryBinPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); |
| 0 | 58 | | _telemetryBinaryWriter = new BinaryWriter(_telemetryFileStream); |
| | 59 | |
|
| 0 | 60 | | Debug.Log("Telemetry log file initialized successfully."); |
| 0 | 61 | | } |
| | 62 | |
|
| 2 | 63 | | private void InitializeEventLogFiles() { |
| 2 | 64 | | string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); |
| 2 | 65 | | _eventLogPath = Path.Combine(_sessionDirectory, $"sim_events_{timestamp}.csv"); |
| | 66 | |
|
| | 67 | | // Initialize the event log cache |
| 2 | 68 | | _eventLogCache = new List<EventRecord>(); |
| | 69 | |
|
| 2 | 70 | | Debug.Log("Event log file initialized successfully."); |
| 2 | 71 | | } |
| | 72 | |
|
| 0 | 73 | | private void CloseTelemetryLogFiles() { |
| 0 | 74 | | if (_telemetryBinaryWriter != null) { |
| 0 | 75 | | _telemetryBinaryWriter.Flush(); |
| 0 | 76 | | _telemetryBinaryWriter.Close(); |
| 0 | 77 | | _telemetryBinaryWriter = null; |
| 0 | 78 | | } |
| | 79 | |
|
| 0 | 80 | | if (_telemetryFileStream != null) { |
| 0 | 81 | | _telemetryFileStream.Close(); |
| 0 | 82 | | _telemetryFileStream = null; |
| 0 | 83 | | } |
| 0 | 84 | | } |
| | 85 | |
|
| 0 | 86 | | private IEnumerator MonitorRoutine() { |
| 0 | 87 | | while (true) { |
| 0 | 88 | | RecordTelemetry(); |
| 0 | 89 | | yield return new WaitForSeconds(_updateRate); |
| 0 | 90 | | } |
| | 91 | | } |
| | 92 | |
|
| 0 | 93 | | private void RecordTelemetry() { |
| 0 | 94 | | float time = (float)SimManager.Instance.GetElapsedSimulationTime(); |
| 0 | 95 | | var agents = SimManager.Instance.GetActiveAgents(); |
| 0 | 96 | | if (_telemetryBinaryWriter == null) { |
| 0 | 97 | | Debug.LogWarning("Telemetry binary writer is null"); |
| 0 | 98 | | return; |
| | 99 | | } |
| 0 | 100 | | for (int i = 0; i < agents.Count; i++) { |
| 0 | 101 | | var agent = agents[i]; |
| | 102 | |
|
| 0 | 103 | | if (!agent.gameObject.activeInHierarchy) |
| 0 | 104 | | continue; |
| | 105 | |
|
| 0 | 106 | | Vector3 pos = agent.transform.position; |
| | 107 | |
|
| 0 | 108 | | if (pos == Vector3.zero) |
| 0 | 109 | | continue; |
| | 110 | |
|
| 0 | 111 | | Vector3 vel = agent.GetVelocity(); // Ensure GetVelocity() doesn't allocate |
| | 112 | |
|
| 0 | 113 | | int agentID = agent.GetInstanceID(); |
| 0 | 114 | | int flightPhase = (int)agent.GetFlightPhase(); |
| 0 | 115 | | byte agentType = (byte)(agent is Threat ? 0 : 1); |
| | 116 | |
|
| | 117 | | // Write telemetry data directly to the binary file |
| 0 | 118 | | _telemetryBinaryWriter.Write(time); |
| 0 | 119 | | _telemetryBinaryWriter.Write(agentID); |
| 0 | 120 | | _telemetryBinaryWriter.Write(pos.x); |
| 0 | 121 | | _telemetryBinaryWriter.Write(pos.y); |
| 0 | 122 | | _telemetryBinaryWriter.Write(pos.z); |
| 0 | 123 | | _telemetryBinaryWriter.Write(vel.x); |
| 0 | 124 | | _telemetryBinaryWriter.Write(vel.y); |
| 0 | 125 | | _telemetryBinaryWriter.Write(vel.z); |
| 0 | 126 | | _telemetryBinaryWriter.Write(flightPhase); |
| 0 | 127 | | _telemetryBinaryWriter.Write(agentType); |
| 0 | 128 | | } |
| 0 | 129 | | } |
| | 130 | |
|
| 0 | 131 | | public void ConvertBinaryTelemetryToCsv(string binaryFilePath, string csvFilePath) { |
| 0 | 132 | | try { |
| 0 | 133 | | using FileStream fs = |
| | 134 | | new FileStream(binaryFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |
| 0 | 135 | | using BinaryReader reader = new BinaryReader(fs); |
| 0 | 136 | | using StreamWriter writer = new StreamWriter(csvFilePath, false); |
| 0 | 137 | | { |
| | 138 | | // Write CSV header |
| 0 | 139 | | writer.WriteLine( |
| | 140 | | "Time,AgentID,AgentX,AgentY,AgentZ,AgentVX,AgentVY,AgentVZ,AgentState,AgentType"); |
| | 141 | |
|
| 0 | 142 | | while (reader.BaseStream.Position != reader.BaseStream.Length) { |
| 0 | 143 | | float time = reader.ReadSingle(); |
| 0 | 144 | | int agentID = reader.ReadInt32(); |
| 0 | 145 | | float posX = reader.ReadSingle(); |
| 0 | 146 | | float posY = reader.ReadSingle(); |
| 0 | 147 | | float posZ = reader.ReadSingle(); |
| 0 | 148 | | float velX = reader.ReadSingle(); |
| 0 | 149 | | float velY = reader.ReadSingle(); |
| 0 | 150 | | float velZ = reader.ReadSingle(); |
| 0 | 151 | | int flightPhase = reader.ReadInt32(); |
| 0 | 152 | | byte agentTypeByte = reader.ReadByte(); |
| 0 | 153 | | string agentType = agentTypeByte == 0 ? "T" : "M"; |
| | 154 | |
|
| | 155 | | // Write the data to CSV |
| 0 | 156 | | writer.WriteLine( |
| | 157 | | $"{time:F2},{agentID},{posX:F2},{posY:F2},{posZ:F2},{velX:F2},{velY:F2},{velZ:F2},{flightPhase},{agentType |
| 0 | 158 | | } |
| 0 | 159 | | } |
| 0 | 160 | | } catch (IOException e) { |
| 0 | 161 | | Debug.LogWarning( |
| | 162 | | $"An IO error occurred while converting binary telemetry to CSV: {e.Message}"); |
| 0 | 163 | | } |
| 0 | 164 | | } |
| | 165 | |
|
| 0 | 166 | | private void WriteEventsToFile() { |
| 0 | 167 | | using (StreamWriter writer = new StreamWriter(_eventLogPath, false)) { |
| | 168 | | // Write CSV header |
| 0 | 169 | | writer.WriteLine("Time,PositionX,PositionY,PositionZ,Event,Details"); |
| | 170 | |
|
| 0 | 171 | | foreach (var record in _eventLogCache) { |
| 0 | 172 | | writer.WriteLine( |
| | 173 | | $"{record.Time:F2},{record.PositionX:F2},{record.PositionY:F2},{record.PositionZ:F2},{record.EventType},{rec |
| 0 | 174 | | } |
| 0 | 175 | | } |
| 0 | 176 | | } |
| | 177 | |
|
| 2 | 178 | | private void RegisterSimulationStarted() { |
| 2 | 179 | | if (SimManager.Instance.simulatorConfig.enableTelemetryLogging) { |
| 0 | 180 | | InitializeTelemetryLogFiles(); |
| 0 | 181 | | _monitorRoutine = StartCoroutine(MonitorRoutine()); |
| 0 | 182 | | } |
| 4 | 183 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 2 | 184 | | InitializeEventLogFiles(); |
| 2 | 185 | | } |
| 2 | 186 | | } |
| | 187 | |
|
| 0 | 188 | | private void RegisterSimulationEnded() { |
| 0 | 189 | | if (SimManager.Instance.simulatorConfig.enableTelemetryLogging) { |
| 0 | 190 | | StopCoroutine(_monitorRoutine); |
| 0 | 191 | | CloseTelemetryLogFiles(); |
| 0 | 192 | | StartCoroutine(ConvertBinaryTelemetryToCsvCoroutine( |
| | 193 | | _telemetryBinPath, Path.ChangeExtension(_telemetryBinPath, ".csv"))); |
| 0 | 194 | | } |
| 0 | 195 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 0 | 196 | | WriteEventsToFile(); |
| 0 | 197 | | } |
| 0 | 198 | | } |
| | 199 | |
|
| | 200 | | private IEnumerator ConvertBinaryTelemetryToCsvCoroutine(string binaryFilePath, |
| 0 | 201 | | string csvFilePath) { |
| 0 | 202 | | yield return null; // Wait for the next frame to ensure RecordTelemetry() has finished |
| 0 | 203 | | ConvertBinaryTelemetryToCsv(binaryFilePath, csvFilePath); |
| 0 | 204 | | } |
| | 205 | |
|
| 7 | 206 | | public void RegisterNewThreat(Threat threat) { |
| 14 | 207 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 7 | 208 | | RegisterNewAgent(threat, "NEW_THREAT"); |
| 7 | 209 | | } |
| 7 | 210 | | } |
| | 211 | |
|
| 1 | 212 | | public void RegisterNewInterceptor(Interceptor interceptor) { |
| 2 | 213 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 1 | 214 | | RegisterNewAgent(interceptor, "NEW_INTERCEPTOR"); |
| 1 | 215 | | interceptor.OnInterceptMiss += RegisterInterceptorMiss; |
| 1 | 216 | | interceptor.OnInterceptHit += RegisterInterceptorHit; |
| 1 | 217 | | } |
| 1 | 218 | | } |
| | 219 | |
|
| 8 | 220 | | private void RegisterNewAgent(Agent agent, string eventType) { |
| 8 | 221 | | float time = (float)SimManager.Instance.GetElapsedSimulationTime(); |
| 8 | 222 | | Vector3 pos = agent.transform.position; |
| 8 | 223 | | var record = new EventRecord { Time = time, PositionX = pos.x, PositionY = pos.y, |
| | 224 | | PositionZ = pos.z, EventType = eventType, Details = agent.name }; |
| 8 | 225 | | _eventLogCache.Add(record); |
| 8 | 226 | | } |
| | 227 | |
|
| 0 | 228 | | public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) { |
| 0 | 229 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 0 | 230 | | RegisterInterceptorEvent(interceptor, threat, true); |
| 0 | 231 | | } |
| 0 | 232 | | } |
| | 233 | |
|
| 0 | 234 | | public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) { |
| 0 | 235 | | if (SimManager.Instance.simulatorConfig.enableEventLogging) { |
| 0 | 236 | | RegisterInterceptorEvent(interceptor, threat, false); |
| 0 | 237 | | } |
| 0 | 238 | | } |
| | 239 | |
|
| 0 | 240 | | public void RegisterInterceptorEvent(Interceptor interceptor, Threat threat, bool hit) { |
| 0 | 241 | | float time = (float)SimManager.Instance.GetElapsedSimulationTime(); |
| 0 | 242 | | Vector3 pos = interceptor.transform.position; |
| 0 | 243 | | string eventType = hit ? "INTERCEPTOR_HIT" : "INTERCEPTOR_MISS"; |
| 0 | 244 | | var record = new EventRecord { |
| | 245 | | Time = time, PositionX = pos.x, PositionY = pos.y, |
| | 246 | | PositionZ = pos.z, EventType = eventType, Details = $"{interceptor.name} and {threat.name}" |
| | 247 | | }; |
| 0 | 248 | | _eventLogCache.Add(record); |
| 0 | 249 | | } |
| | 250 | |
|
| 0 | 251 | | private void OnDestroy() { |
| 0 | 252 | | CloseTelemetryLogFiles(); |
| 0 | 253 | | WriteEventsToFile(); |
| 0 | 254 | | } |
| | 255 | | } |