// Struct for individual benchmark locations
class BenchmarkLocation
{
bool m_IsDummyTeleport;
bool m_IsDummyWait;
float m_CamSpeedMultiplier = 1;
string m_Name;
vector m_StartPos;
vector m_StartLookAtPos;
void BenchmarkLocation(string name)
{
m_Name = name;
}
void SetPosition(vector start)
{
m_StartPos = start + "0 2.5 0"; // raise to head level
}
// note that vector[1] (height) is ignored during interpolation and setting it has no effect
void SetLookAtPosition(vector start)
{
m_StartLookAtPos = start;
}
void SetCameraSpeedMultiplier(float multiplier)
{
m_CamSpeedMultiplier = multiplier;
}
void SetDummyTeleport()
{
m_IsDummyTeleport = true;
}
void SetDummyWait()
{
m_IsDummyWait = true;
}
}
// Global settings of benchmark
class BenchmarkConfig
{
bool m_LogToRPT;
bool m_DoDevPrints;
int m_PreloadLength = 5;
float m_TimeMultiplier = 1;
string m_CSVName;
ref array<ref BenchmarkLocation> m_Locations = new array<ref BenchmarkLocation>();
void AddLocation(notnull BenchmarkLocation loc)
{
m_Locations.Insert(loc);
}
void AddQuickLocation(string name, vector pos, vector lookAtPos)
{
BenchmarkLocation loc = new BenchmarkLocation(name);
loc.SetPosition(pos);
loc.SetLookAtPosition(lookAtPos);
AddLocation(loc);
}
void AddTeleport()
{
BenchmarkLocation loc = new BenchmarkLocation("Teleport");
loc.SetDummyTeleport();
AddLocation(loc);
}
void AddWait()
{
BenchmarkLocation loc = new BenchmarkLocation("Wait");
loc.SetDummyWait();
AddLocation(loc);
}
// false = csv, true = rpt
void LogToRPT(bool logRPT)
{
m_LogToRPT = logRPT;
}
void DoDevPrints(bool doPrints)
{
m_DoDevPrints = doPrints;
}
// Preload time of individual locations after teleport
void SetPreloadLengthTime(int seconds)
{
m_PreloadLength = seconds;
}
// Time multiplier for quickly testing flypath
void SetTestTimeMultiplier(float multiplier)
{
m_TimeMultiplier = multiplier;
}
void SetCSVName(string name)
{
m_CSVName = name;
}
}
class MissionBenchmark : MissionGameplay
{
protected const int INITIAL_PRELOAD = 5; // seconds, extra preload seconds to compensate for the game launching
protected const float STEP_INTERVAL = 1; // seconds, how much time passes between steps (fps measurements)
protected bool m_InitialLoadDone; // extra time is added to first preload to make up for intial loading of the game
protected bool m_IsPreloading; // preload time between location measurements
protected bool m_LocationDone; // finished measuring current location
protected int m_LocIndex;
protected int m_MeasuringStep;
protected float m_MeasureStepTimer = 1;
protected float m_SumFPS;
protected float m_TimeCounter;
protected float m_MeasureLength;
protected float m_StepDistance;
protected FileHandle m_CSVLog;
protected BenchmarkLocation m_CurrentLocation;
protected BenchmarkLocation m_NextLocation;
protected ref BenchmarkConfig m_Config;
protected static MissionBenchmark m_Instance;
void MissionBenchmark()
{
m_Instance = this;
}
void ~MissionBenchmark()
{
m_Instance = null;
}
static MissionBenchmark GetInstance()
{
return m_Instance;
}
BenchmarkConfig GetConfig()
{
if (!m_Config)
m_Config = new BenchmarkConfig();
return m_Config;
}
void Start()
{
DisableWeatherChange();
if (!m_Config || m_Config.m_Locations.Count() <= 1)
{
OnBenchmarkEnd("Not enough locations defined");
return;
}
if (!m_Config.m_LogToRPT)
CreateCSVLog();
m_IsPreloading = true;
OnLocationSwitch();
}
override void OnUpdate(float timeslice)
{
super.OnUpdate(timeslice);
m_TimeCounter += timeslice * m_Config.m_TimeMultiplier;
if (!m_CurrentLocation)
return;
else if (m_IsPreloading)
PreloadUpdate();
else
MeasureUpdate(timeslice);
}
// Update logic which runs when location is being preloaded
protected void PreloadUpdate()
{
if (!m_InitialLoadDone)
{
if (m_TimeCounter >= (m_Config.m_PreloadLength + INITIAL_PRELOAD)) // end preload
m_InitialLoadDone = true;
}
else if (m_TimeCounter >= m_Config.m_PreloadLength * (1 / m_Config.m_TimeMultiplier)) // end preload
{
m_TimeCounter = 0;
m_IsPreloading = false;
}
}
// Update logic which runs when measurement is in progress
protected void MeasureUpdate(float timeSlice)
{
m_MeasureStepTimer += timeSlice;
if (m_MeasureStepTimer >= STEP_INTERVAL)
{
float avgFps = GetGame().GetFps();
if (m_Config.m_DoDevPrints)
Print( string.Format("Measure step: %1 | FPS: %2" , m_MeasuringStep + 1, 1/avgFps) );
/*if (m_MeasuringStep >= m_MeasureLength) // end of steps
{ }
else*/ // next step
m_MeasureStepTimer = 0;
m_SumFPS += ( 1/avgFps );
m_MeasuringStep++;
GetGame().GetPlayer().SetPosition(FreeDebugCamera.GetInstance().GetPosition() - "0 2.5 0");
}
LerpCamera();
}
protected void DisableWeatherChange()
{
Weather weather = GetGame().GetWeather();
weather.SetDynVolFogHeightBias(0,0);
weather.SetDynVolFogHeightDensity(0,0);
weather.SetDynVolFogDistanceDensity(0,0);
weather.SetWeatherUpdateFreeze(true);
}
protected void AdvanceLocation()
{
if (m_NextLocation.m_IsDummyWait && m_MeasuringStep < 5)
return;
string locationName = m_NextLocation.m_Name;
FPSLog(locationName, m_SumFPS/m_MeasuringStep);
if (m_Config.m_DoDevPrints)
Print( string.Format("%1 | Measurements: %2 | Average FPS: %3 | Elapsed time: %4 seconds" ,locationName, m_MeasuringStep, m_SumFPS/m_MeasuringStep ,m_TimeCounter) );
m_LocIndex++;
OnLocationSwitch();
}
// logic for interpolating camera between location points
protected void LerpCamera()
{
float lerpX, lerpZ, lerpY;
vector target = m_NextLocation.m_StartPos;
m_StepDistance = vector.Distance(m_CurrentLocation.m_StartPos, target);
float camSpeedAdjust = m_CurrentLocation.m_CamSpeedMultiplier * 5 * m_TimeCounter * 1/m_StepDistance;
float distanceMulti = 1/Math.Max(0.001, m_StepDistance);
lerpX = Math.Lerp(m_CurrentLocation.m_StartPos[0], target[0], camSpeedAdjust);
lerpZ = Math.Lerp(m_CurrentLocation.m_StartPos[1], target[1], camSpeedAdjust);
lerpY = Math.Lerp(m_CurrentLocation.m_StartPos[2], target[2], camSpeedAdjust);
if (camSpeedAdjust >= 1 || m_NextLocation.m_IsDummyWait)
{
AdvanceLocation();
return;
}
FreeDebugCamera.GetInstance().SetPosition( Vector(lerpX, lerpZ, lerpY) );
target = m_NextLocation.m_StartLookAtPos;
lerpX = Math.Lerp(m_CurrentLocation.m_StartLookAtPos[0], target[0], camSpeedAdjust);
//lerpZ = Math.Lerp(m_CurrentLocation.m_StartLookAtPos[1], target[1], camSpeedAdjust); // ignored as it causes issues with lerping between look at points
lerpY = Math.Lerp(m_CurrentLocation.m_StartLookAtPos[2], target[2], camSpeedAdjust);
FreeDebugCamera.GetInstance().LookAt( Vector(lerpX, lerpZ, lerpY) );
}
protected void OnLocationSwitch()
{
if (m_LocIndex >= (m_Config.m_Locations.Count() - 1))
{
OnBenchmarkEnd("Test finished!");
return;
}
m_MeasureStepTimer = 1; // tick first measurement straight after preload
m_SumFPS = 0;
m_MeasuringStep = 0;
m_TimeCounter = 0;
m_CurrentLocation = m_Config.m_Locations[m_LocIndex];
m_NextLocation = m_Config.m_Locations[m_LocIndex+1];
if (!GetGame().GetPlayer())
{
CreatePlayer();
TeleportToPos(m_CurrentLocation);
}
if (m_NextLocation.m_IsDummyTeleport) // flycam teleport
{
m_LocIndex += 2;
if (m_LocIndex >= (m_Config.m_Locations.Count() - 1))
{
OnBenchmarkEnd("Test finished!");
return;
}
else
{
m_CurrentLocation = m_Config.m_Locations[m_LocIndex];
m_NextLocation = m_Config.m_Locations[m_LocIndex+1];
m_StepDistance = vector.Distance(m_CurrentLocation.m_StartPos, m_NextLocation.m_StartPos);
TeleportToPos(m_CurrentLocation);
}
}
if (m_NextLocation.m_IsDummyWait)
{
m_NextLocation.m_Name = m_CurrentLocation.m_Name + " Wait";
m_NextLocation.m_StartPos = m_CurrentLocation.m_StartPos;
m_NextLocation.m_StartLookAtPos = m_CurrentLocation.m_StartLookAtPos;
}
if (m_Config.m_DoDevPrints)
{
Print(string.Format("================"));
Print(string.Format("%1 test begin" , m_CurrentLocation.m_Name + " -> " + m_NextLocation.m_Name));
}
}
protected void TeleportToPos(BenchmarkLocation loc)
{
FreeDebugCamera.GetInstance().SetPosition(loc.m_StartPos);
vector lookAtPos = loc.m_StartLookAtPos;
lookAtPos[1] = loc.m_StartPos[1];
FreeDebugCamera.GetInstance().LookAt(lookAtPos);
GetGame().GetPlayer().SetPosition(m_CurrentLocation.m_StartPos - "0 2.5 0");
m_IsPreloading = true;
}
protected void OnBenchmarkEnd(string reason)
{
if (!m_Config.m_LogToRPT)
{
CloseFile( m_CSVLog );
if (m_Config.m_DoDevPrints)
Print( "Benchmark CSV file closed" );
}
if (m_Config.m_DoDevPrints)
Print(string.Format("%1", reason));
FreeDebugCamera.GetInstance().SetActive(false);
GetGame().RequestExit( IDC_MAIN_QUIT ); // does not work on consoles ?
}
protected void CreatePlayer()
{
Entity playerEnt = GetGame().CreatePlayer(null, "SurvivorF_Eva", m_CurrentLocation.m_StartPos - "0 2.5 0", 0, "NONE");
PlayerBase player = PlayerBase.Cast(playerEnt);
GetGame().SelectPlayer(NULL, player);
player.GetStatWater().Set(3000);
player.GetStatEnergy().Set(3000);
player.SetModifiers(false);
player.SetAllowDamage(false);
player.SetCanBeDestroyed(false);
player.DisableSimulation(true);
FreeDebugCamera.GetInstance().SetFOV(0.72);
FreeDebugCamera.GetInstance().SetActive(true);
}
protected void CreateCSVLog()
{
string fileName = "benchmark";
if (m_Config.m_CSVName != string.Empty)
fileName = m_Config.m_CSVName;
m_CSVLog = OpenFile("$profile:" + fileName + ".csv", FileMode.WRITE);
if ( m_CSVLog == 0 )
OnBenchmarkEnd("Failed to create benchmark .csv");
if (m_Config.m_DoDevPrints)
Print("Benchmark .csv created");
FPrintln(m_CSVLog, "location,FPS,time");
}
protected void FPSLog( string position, float frames )
{
int year, month, day, hour, minute, second;
GetYearMonthDay(year, month, day);
GetHourMinuteSecondUTC(hour, minute, second);
string timestamp = string.Format( "%1-%2-%3-%4-%5-%6", year, month, day, hour, minute, second );
if (m_Config.m_LogToRPT)
PrintToRPT("Average FPS: " + frames);
else
FPrintln( m_CSVLog, position + "," + frames + "," + timestamp );
}
}