using System.Collections; using System.Collections.Generic; using System.Threading; using UnityEngine; using CSVReader; using Processor; using ConfigReader; using Logger; using Networkreader; using System.Diagnostics; using System; namespace ObjectScripts { public class ManagerWithProzessor : AbstractManager { /// /// File to Read /// [SerializeField] public string EntityFilename; /// /// /// [SerializeField] public double starttime; /// /// /// [SerializeField] public bool UseStream; /// /// /// [SerializeField] public string ipAddress; /// /// /// [SerializeField] public AbstractSensorHandler SensorHandler; /// /// /// [SerializeField] public AbstractEventHandler EventHandler; /// /// /// [SerializeField] public string EventFilename; /// /// ObjectHandler that handles InputObjects /// [SerializeField] public ObjectHandler Handler; /// /// InputProzessor to read from /// private IProcessor InputProzessor; /// /// /// private float StopedGameVeloctiy; /// /// /// private float StepSizeGameVelocity; /// /// /// private int NumberDigitsGameVelocity; /// /// /// private int NumberDigitsTimeStamps; /// /// /// private InputObject NewestGameInputObject; /// /// boolean for coroutines /// private bool IsRunning; /// /// /// private IMapSensor MapSensor; /// /// /// EventCSVReader EventCSV; /// /// /// EventObject[] Events; /// /// /// private NWEventReader NWEventReader; /// /// /// private void Start() { MapSensor = new MapSensor(); if (!ConfigParser.ParseConfig()) CityLogger.LogWarning("Unable to parse config"); IsRunning = true; this.UpdateRateInSeconds = 1 / this.UpdateRate; this.GameVelocity = 1f; StepSizeGameVelocity = 0.1f; NumberDigitsGameVelocity = 1; NumberDigitsTimeStamps = 2; forward = true; NewestTimestamp = 0; EarliestTimestamp = 0; GameTimestamp = starttime; ProzessorSetup(); } /// /// Setup the InputProzessor. /// Starts InputProzessor and NetworkEventReader with ipAddress and port: 13000,13001 /// /// void ProzessorSetup() { //Create InputProzessor and start streaming/reading. if (UseStream) { InputProzessor = new Processor.Processor(ipAddress, 13000); NWEventReader = new NWEventReader(ipAddress, 13001); InputProzessor.StartStreaming(); this.EventFilename = NWEventReader.StartNWRead(); } else { if (!EntityFilename.Equals(string.Empty)) InputProzessor = new Processor.Processor(EntityFilename); InputProzessor.StartStreaming(); } Stopwatch watch = new Stopwatch(); double starttime = double.NaN; //Try to start stream watch.Start(); while (double.IsNaN(starttime)) { starttime = InputProzessor.GetOldestTimeStamp(); Thread.Sleep(10); if (watch.ElapsedMilliseconds > 5000) { CityLogger.LogError("Starting stream on " + ipAddress + ":" + 13000 + " has timed out."); InputProzessor.StopStreaming(); CriticalError(); } } watch.Stop(); EarliestTimestamp = starttime; if (InputProzessor.JumpToTimestamp(starttime, out _)) { CityLogger.Log("Stream started", LogLevel.INFO); StartCoroutine(ReadData()); } else { CityLogger.LogWarning("Unable to do initial jump on timestamp: " + starttime); InputProzessor.StopStreaming(); CriticalError(); } } /// /// Coroutine to read from InputProzessor every (1/updateRate)*Velocity seconds. /// IEnumerator ReadData() { float gv; //Get Event CSV Reader for reading Events EventCSV = new EventCSVReader(EventFilename); CityLogger.Log("EventFileName " + EventFilename, 0); MapSensor.SetSensorList(SensorHandler.SensorList); CityLogger.Log("Start ReadingData", LogLevel.INFO); endChunk = InputProzessor.GetChunkEndtimestamp(); startChunk = InputProzessor.GetChunkStarttimestamp(); //Start EventHandler EventHandler.SetUp(EventCSV, MapSensor); //Start Coroutine which runs every 1/UpdateRate seconds StartCoroutine(UpdateWithProzessorUpdateRate()); Stopwatch watch = new Stopwatch(); //Start Reading InputObjects while (this.IsRunning) { //Only read data if simulation is running if (this.GameVelocity > 0) { gv = this.GameVelocity; watch.Restart(); HandleNextInput(); watch.Stop(); float updateTime = this.UpdateRateInSeconds / gv; float waittime = updateTime - (watch.ElapsedMilliseconds / 1000.0f); if (waittime < 0) { CityLogger.LogWarning("Unable to stick to update timing. Program is behind by: " + waittime * (-1)); waittime = 0; } yield return new WaitForSecondsRealtime(waittime); } else { yield return null; } } } /// /// Reads values from the next timestamp /// void HandleNextInput() { List inputBuffer; inputBuffer = InputProzessor.ReadNextValues(); if (inputBuffer.Count > 0) { //GameTimestamp is the newest read Timestamp this.NewestGameInputObject = inputBuffer[inputBuffer.Count - 1]; this.GameTimestamp = this.NewestGameInputObject.Time; Handler.SensorList = SensorHandler.SensorList; if (double.IsNaN(startChunk)) { startChunk = EarliestTimestamp; } if (!double.IsNaN(endChunk) && this.GameTimestamp >= endChunk) { //if next timestamp is greater then endchunk or is the last value in the Intervall [startChunk,endChunk] //stop the Simulation and jump to the end of the Intervall StopPressed(); this.JumpToTimestamp(endChunk); } else if (this.GameTimestamp >= NewestTimestamp) { //if the simulation reads faster than it gets updates //set the GameVelocity to 1 and handle the input this.GameVelocity = 1; this.Handler.Handle(inputBuffer); } else if (!forward && this.GameTimestamp <= startChunk) { //if the simulation runs backwards and the next timestamp is smaller or the first value in the intervall [startChunk,endChunk] //stop the Simulation and jump to the start of the intervall StopPressed(); this.JumpToTimestamp(startChunk); } else { //if next timestamp is in the invall handle the input Handler.Handle(inputBuffer); } } } /// /// Sets the starttime of the timeintervall the Simulation is running in. /// /// Also sets the starttime in the Inputprozessor /// /// public override void SetChunkStarttimestamp(double time) { InputProzessor.SetChunkStarttimestamp(time); double newTime = InputProzessor.GetChunkStarttimestamp(); startChunk = newTime; } /// /// Sets the endtime of the timeintervall the Simulation is running in. /// /// Also sets the endtime in the Inputprozessor /// /// public override void SetChunkEndtimestamp(double time) { InputProzessor.SetChunkEndtimestamp(time); double newTime = InputProzessor.GetChunkEndtimestamp(); endChunk = newTime; } /// /// coroutine which runs every 1/UpdateRate seconds /// IEnumerator UpdateWithProzessorUpdateRate() { while (this.IsRunning) { this.NewestTimestamp = this.InputProzessor.GetNewestTimeStamp(); yield return new WaitForSecondsRealtime(this.UpdateRateInSeconds); } } /// /// Jump to timestamp.Start JumpToTimestampRoutine /// public override void JumpToTimestamp(double timestamp) { if (!double.IsNaN(timestamp)) { if (this.InputProzessor != null) { StartCoroutine(JumpToTimestampRoutine(timestamp)); } } } /// /// Start to timestamp by calling InputProzessor.JumpToTimestamp /// private IEnumerator JumpToTimestampRoutine(double timestamp) { if (this.InputProzessor.JumpToTimestamp(timestamp, out double x)) { //Clear the scene //Destroying an objects needs 1 frame this.Handler.ClearAll(); yield return null; //InputProzessor needs some time to jump to the timestamp yield return new WaitForSecondsRealtime(0.25f); if (x != -1) this.GameTimestamp = x; //Handle the next Input. HandleNextInput(); CityLogger.Log(string.Format("Timestamp returned from Prozessor {0}, timestamp Parameter {1}, InputBufferTimestamp{2}", x, timestamp, this.NewestGameInputObject.Time), LogLevel.DEBUG); } else CityLogger.Log("Couldn't Jump " + timestamp, LogLevel.INFO); yield return null; } /// /// Reverse Simulation /// public void ReverseTime() { this.forward = !this.forward; this.InputProzessor.ReverseTime(); } private void OnDestroy() { //if the GameObject gets destroyed //stop streaming if (InputProzessor != null) { InputProzessor.StopStreaming(); } if (NWEventReader != null) { NWEventReader.StopReading(); } IsRunning = false; } private void CriticalError() { CityLogger.LogError("Encountered critical error. Shutting down application..."); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; UnityEditor.EditorApplication.Exit(0); #else Application.Quit(); #endif Environment.Exit(0); } /// /// Set Velocity to the last Velocity known before stop was pressed /// public override void PlayPressed() { if (this.GameVelocity == 0) { //if the GameVelocity is 0 //change the GameVelocity to the savedGameVelocity //if the GameVeloctiy was changed to 0 by using the plus and minus button //the saved GameVelocity will be the velocity from the last StopPressed() this.GameVelocity = this.StopedGameVeloctiy; CityLogger.Log("PlayPressed" + this.GameVelocity.ToString(), LogLevel.DEBUG); } } /// /// Save Veloctiy and set velocity to 0. /// public override void StopPressed() { if (this.GameVelocity > 0) { // if the GameVelocity is greater 0 // save the current GameVelocity and change the GameVelocity to 0 this.StopedGameVeloctiy = this.GameVelocity; this.GameVelocity = 0; CityLogger.Log("StopPressed" + this.StopedGameVeloctiy.ToString(), LogLevel.DEBUG); } } /// /// Reverse Simulation /// public override void ReversePressed() { this.ReverseTime(); CityLogger.Log("ReversePressed", LogLevel.DEBUG); } /// /// Increase velocity by StepSizeGameVelocity /// public override void PlusPressed() { //Increase GameVelocity //no limit this.GameVelocity = this.RoundWithDigits(this.GameVelocity + this.StepSizeGameVelocity, this.NumberDigitsGameVelocity); CityLogger.Log("PlusPressed" + this.GameVelocity.ToString(), LogLevel.DEBUG); } /// /// Decrease velocity by StepSizeGameVelocity. /// public override void MinusPressed() { //Decrease GameVelocity // always greater 0 this.GameVelocity = this.RoundWithDigits(this.GameVelocity - this.StepSizeGameVelocity, this.NumberDigitsGameVelocity); if (this.GameVelocity < 0) { this.GameVelocity = 0; } CityLogger.Log("MinusPressed" + this.GameVelocity.ToString(), LogLevel.DEBUG); } /// /// Rounds number /// private float RoundWithDigits(float value, int NumberDigits) { //try to prevent round errors //for example 0.1 - 0.1 returned a extrem small number and //yield return new WaitForSecondsRealtime(1 / (this.UpdateRate * this.GameVelocity)); // would be a very long time. return Mathf.Round(value * Mathf.Pow(10, NumberDigits)) / (Mathf.Pow(10, NumberDigits)); } /// /// Rounds number /// private double RoundWithDigits(double value, int NumberDigits) { return (double)Mathf.Round((float)value * Mathf.Pow(10, NumberDigits)) / (Mathf.Pow(10, NumberDigits)); } } }