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));
}
}
}