#include "pch.hpp"
//===========================================================================//
// File:	Time.cpp                                                         //
// Project: GameOS           Brick: Time Manager	                         //
// Contents: Time Function													 //												 
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 12/07/97  JTR    Created													 //
// 12/15/97  JTR    Changed GetElapsedTime to take PAUSE into account		 //
// 01/05/98  JTR    Time is a DWORD internal to the manager					 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "Time.hpp"
#include "Windows.hpp"
#include "ErrorHandler.hpp"
#include "Debugger.hpp"
#include "perf.h"

extern DWORD gLanguageDLL;

extern long StartSeed;
extern DWORD gDisableRDTSC;
extern DWORD LastModalLoop;
extern DWORD ModalLoop;

__int64 StartOfRenderTime=0;
__int64 frequency = 0;
__int64 QPCfrequency;
__int64 StartOfFrameTime;
__int64 StartQPC;
__int64 StartCount;
__int64 PausedCount;
__int64 StartPause;
__int64 CurrentTimeDate;
extern volatile __int64 StartTimeOutSideLoop;
double MinimumCycleTime;
double MinimumProcessorTime;
DWORD TimePause=0;
float	frameRate;
double ElapsedTime=0.0;
double NonPausedElapsedTime=0.0;
double PreviousTime=0.0;
double TimeSlice=0.0;
double LastFrameTime=0.0;
DWORD WaitUntilTime;
DWORD CurrentFrameNumber;
DWORD WatchDogFrame;
bool NewPerformanceRegister=1;
bool CurrentTimeDateValid=0;

TIMECAPS tc;


void __stdcall StartCycleTiming( gos_CycleData* Time )
{
	#ifdef LAB_ONLY
	gosASSERT( !Time->InsideStartEnd );
	Time->InsideStartEnd=1;
	Time->StartProcessorCycles=(DWORD)GetCycles();
	if( PerfCounters )
	{
		ReadPerformanceMonitorRegisters( &Time->StartCounter[0] );
	}
	#endif
}

void __stdcall EndCycleTiming( gos_CycleData* Time )
{
	#ifdef LAB_ONLY
	__int64 nPerfCntr[2];
	gosASSERT( Time->InsideStartEnd );
	Time->Cycles+=(DWORD)GetCycles()-Time->StartProcessorCycles;
	if( PerfCounters )
	{
		ReadPerformanceMonitorRegisters(nPerfCntr);
		Time->TotalCounter[0]+=nPerfCntr[0]-Time->StartCounter[0];
		Time->TotalCounter[1]+=nPerfCntr[1]-Time->StartCounter[1];
	}
	Time->InsideStartEnd=0;
	Time->Count++;
	#endif
}


extern double FrameTime;
extern volatile __int64 TimeOutSideLoop;








//
// Start time manager
//
void StartTimeManager()
{
//
// Start counters
//
	QueryPerformanceCounter((LARGE_INTEGER*)&StartQPC);
	StartOfRenderTime=StartTimeOutSideLoop=TimeOutSideLoop=StartOfFrameTime=StartCount=GetCycles();
	frameRate=0.0;
	StartOfFrameTime=StartCount+1;
	TimeSlice=0.0;
	PreviousTime=0.0;
	ElapsedTime=0.0;
	NonPausedElapsedTime=0.0;
	LastFrameTime=0.0;
	CurrentFrameNumber=0;
	PausedCount=0;
	WaitUntilTime=0;
	TimePause=0;
}







//
// SPEW not allowed in function - called before it is setup!
//
void TimeManagerInstall()
{
//
// Setup highest res mm timer possible
//
	memset(&tc,0,sizeof(tc));
	if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR)
		STOP(( "No timer available" ));
	timeBeginPeriod(tc.wPeriodMin); 
//
// Find frequency of QPC
//
	QueryPerformanceFrequency((LARGE_INTEGER*)&QPCfrequency);
	if( QPCfrequency==0 )
		STOP(( "QueryPerformanceFrequency returned zero" ));
//
// If machine has RDTSC use it, else QPC
//
	if( HasRDTSC )
	{
		frequency=(__int64)(ProcessorSpeed*1000000);	// 398300000 (Katmai machines)
	}
	else
	{
		frequency=QPCfrequency;
	}
//
// Get the current cycle counter number to seed the random number generator.
//
	StartSeed=(DWORD)GetCycles();
//
// Start time manager
//
	StartTimeManager();
}


void TimeManagerUninstall()
{
	timeEndPeriod(tc.wPeriodMin); 
}






//
// Called once at the start of each frame
//
void TimeManagerUpdate()
{
//
// Just entered a modal loop?
//
	if( ModalLoop && !LastModalLoop )
		StartPauseTime();
//
// Just exited a modal loop?
//
	if( !ModalLoop && LastModalLoop )
		EndPauseTime();
//
// If time was paused, restart it again
//
	if( !ModalLoop && TimePause && 0==gFreezeLogic && !(DebuggerActive && (!gGameLogicActive || gStopSystem)) )
		EndPauseTime();
//
// If the debugger has paused the game and time is not stopped, stop it.
//
	if( !ModalLoop && !TimePause && (gFreezeLogic || (DebuggerActive && (!gGameLogicActive || gStopSystem))) )
		StartPauseTime();

	LastModalLoop=ModalLoop;
//
// If there is a 'wait until time' calc the cycle time to wake up.
//
	__int64 SpintUntil=StartOfFrameTime+(__int64)((WaitUntilTime*frequency)/1000.0f);
//
// Get the timers all up to date (and delay if user requested it)
//
	__int64 tmp;
	do
	{
		tmp=GetCycles();								// Read time once

	} while( 1!=LogMode && (0!=WaitUntilTime && tmp<SpintUntil) );
//
// Read time stamps
//
	if( TimePause )
	{
		ElapsedTime=(tmp-StartCount-PausedCount-(GetCycles()-StartPause)) / (double)frequency;
	}
	else
	{
		ElapsedTime=(tmp-StartCount-PausedCount) / (double)frequency;
	}
	NonPausedElapsedTime=(GetCycles()-StartCount) / (double)frequency;
//
//
//
	WaitUntilTime=0;
	StartOfFrameTime=tmp;							// Update start of frame time
//
// Has the user specified a maximum time delta?
//
	if( Environment.MaxTimeDelta )
	{
		gosASSERT( Environment.MaxTimeDelta>=Environment.MinimumTimeDelta );
		
		if( ElapsedTime-LastFrameTime > Environment.MaxTimeDelta )
		{
			PausedCount+=(__int64)(frequency*(ElapsedTime-(LastFrameTime+Environment.MinimumTimeDelta)));
			ElapsedTime=LastFrameTime+Environment.MinimumTimeDelta;
		}
	}

	LastFrameTime=ElapsedTime;						// Update 'last frame time'
//
// Find base line for performance counters
//
	#ifdef LAB_ONLY
	if( NewPerformanceRegister )
	{
		NewPerformanceRegister=0;
		gos_CycleData	CycleData;
		memset( &CycleData, 0, sizeof(CycleData) );

		for( int t0=0; t0<1000; t0++ )
		{
			StartCycleTiming( &CycleData );
			EndCycleTiming( &CycleData );
		}
		MinimumCycleTime=CycleData.Cycles/CycleData.Count;
		MinimumProcessorTime=DecodePerformanceMonitors( &CycleData.TotalCounter[0] )/CycleData.Count;
	}
	#endif
//
// Reset current system time flag - so read ONCE per frame used
//
	CurrentTimeDateValid=0;
}









void StartPauseTime()
{
	if( !TimePause )
	{
		StartPause=GetCycles();
		TimePause++;
	}
}

void EndPauseTime()
{
	TimePause--;
	if( !TimePause )
	{
		__int64 PausedTime=GetCycles()-StartPause-(__int64)(0.016f*frequency);
		if( PausedTime<0 )
			PausedTime=0;
		PausedCount+=PausedTime;
	}
}


//
// Time at start of current frame
//
double __stdcall gos_GetElapsedTime( int RealTime )
{
	if( RealTime )
		return NonPausedElapsedTime;
	else
		return ElapsedTime;
}



//
// Not to be used in a game - needs to be in ToolOS.hpp
//
double __stdcall gos_GetHiResTime()
{
	//
	// This might get called before the TimeManager is actually
	// initialized, so make sure frequency is set. If not,
	// simply return 0 (which would be correct, anyway).
	//
	if (frequency != 0) 
		return (double)(GetCycles()-StartCount)/frequency;
	else
		return 0;
}


//
// This function ONLY used to see how accurate the RDTSC count is compared to QPC
//
float ReturnRDTSCInfo()
{
	__int64 result;
	QueryPerformanceCounter((LARGE_INTEGER*)&result);
	return (float)(((double)(GetCycles()-StartCount)/frequency)-((double)(result-StartQPC)/QPCfrequency));
}



static BYTE result[8];
//
// Returns a 'cycle count' - on machine without RDTSC this will be QPC
//
__int64 __declspec(naked) __stdcall GetCycles()
{
	if( HasRDTSC )
	{
		_asm
		{
			rdtsc
			ret
		}
	}
	else
	{
		QueryPerformanceCounter((LARGE_INTEGER*)&result);
		_asm mov eax,dword ptr result[0]
		_asm mov edx,dword ptr result[4]
		_asm ret
	}	
}

