//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Multithread.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "Core/Asura_Multithread.h"
#include "Debugging/Asura_CPUProfile.h"

// Misc
#include <process.h>
#include <xtl.h>

/******************************************************************************/
/* Classes                                                                    */
/******************************************************************************/

class Asura_Xbox360_ThreadParams
{
public:

	// Constructor
	Asura_Xbox360_ThreadParams( const char* szThreadName, Asura_SyncEvent* pxSyncEvent, Asura_ThreadFunc pfnThreadFunc, const void* pUserData )
		: m_szThreadName( szThreadName )
		, m_pxSyncEvent( pxSyncEvent )
		, m_pfnThreadFunc( pfnThreadFunc )
		, m_pUserData( pUserData )
	{
	}

	// Data
	const char* m_szThreadName;
	Asura_SyncEvent* m_pxSyncEvent;		// To keep a local variable in scope while the thread is starting up
	Asura_ThreadFunc m_pfnThreadFunc;
	const void* m_pUserData;
};

// Bizarrely, this stuff isn't defined in the Platform SDK headers
struct Asura_Xbox360_ThreadInfo
{
	DWORD  m_uType; // Must be 0x1000
	LPCSTR m_szName;
	DWORD  m_uThreadID; // -1 for caller thread
	DWORD  m_uFlags;
};

/******************************************************************************/
/* Globals                                                                  */
/******************************************************************************/
static u_int g_uProcessorID = 1;	// processor - start first thread at 1.

u_int Asura_Thread_System::s_auStackSizes[]  =	{ 32 * 1024		//ASURA_MULTITHREAD_STACK_SIZE_SMALL
												, 128 * 1024	//ASURA_MULTITHREAD_STACK_SIZE_MEDIUM
												, 0 };			//ASURA_MULTITHREAD_STACK_SIZE_LARGE

/******************************************************************************/
/* Functions                                                                  */
/******************************************************************************/

// Set the thread name in the debugger. See XDK index entry 'SetThreadName'
void Asura_Xbox360_SetThreadName( const char* const szThreadName,
	const DWORD uThreadID /*= static_cast< DWORD >(-1)*/ )
{
	Asura_Xbox360_ThreadInfo xInfo;
	xInfo.m_uType = 0x1000;
	xInfo.m_szName = szThreadName;
	xInfo.m_uThreadID = uThreadID;
	xInfo.m_uFlags = 0;

	__try
	{
		const DWORD uNumArgs = sizeof( xInfo ) / sizeof( DWORD );
		const ULONG_PTR* const puPtr = reinterpret_cast< const ULONG_PTR*>( &xInfo );

		RaiseException( 0x406D1388, 0, uNumArgs, puPtr );
	}
	__except( EXCEPTION_CONTINUE_EXECUTION )
	{
	}
}

static u_int WINAPI Asura_Xbox360_ThreadStartup( void* pParam )
{
	const Asura_Xbox360_ThreadParams* pxParams = reinterpret_cast< const Asura_Xbox360_ThreadParams* >( pParam );
	Asura_ThreadFunc pfnThreadFunc = pxParams->m_pfnThreadFunc;
	const void* pUserData = pxParams->m_pUserData;

	XSetThreadProcessor(GetCurrentThread(), g_uProcessorID);
	// By default all threads are created on the same processor as the creating thread.
	// We want to use all processors so allocate them in round-robin fashion.
	// But to avoid other threads blocking the main thread, don't use thread 0.
	g_uProcessorID = (g_uProcessorID + 1) % 6;
	if (g_uProcessorID == 0)
	{
		g_uProcessorID = 1;
	}

	ASURA_CPU_PROFILE__START_THREAD( pxParams->m_szThreadName);

	Asura_Xbox360_SetThreadName( pxParams->m_szThreadName );

	// Now we've got copies of the thread startup data, signal the event
	pxParams->m_pxSyncEvent->SetSignal();

	pfnThreadFunc( pUserData );

	ASURA_CPU_PROFILE__STOP_THREAD();

	return 0;
}

/******************************************************************************/
/* Asura_Thread_System Static Class Functions                                 */
/******************************************************************************/

u_int Asura_Thread_System::Platform_GetNumHardwareThreads()
{
    return 6;
}

bool Asura_Thread_System::Platform_CreateThread( const char* szThreadName, Asura_ThreadFunc pfnThreadFunc, const void* pUserData, const ASURA_MULTITHREAD_STACK_SIZE eStackSize )
{
	Asura_Assert(eStackSize != ASURA_MULTITHREAD_STACK_NUM_SIZES, "Invalid stack size enumeration passed to Platform_CreateThread");

	Asura_SyncEvent xSyncEvent( "Thread Creation", false );

	Asura_Xbox360_ThreadParams xParams( szThreadName, &xSyncEvent, pfnThreadFunc, pUserData );

	u_int uThreadID;
	uintptr_t pThreadHandle = _beginthreadex
							(
								NULL,							// void *security,
								s_auStackSizes[eStackSize],		// unsigned stack_size,
								Asura_Xbox360_ThreadStartup,	// unsigned ( __stdcall *start_address )( void * ),
								&xParams,						// void *arglist,
								0,								// unsigned initflag,
								&uThreadID						// unsigned *thrdaddr 
							);

	if (pThreadHandle != NULL)
	{
		xSyncEvent.WaitForSignal();
	}

	Asura_Assert( pThreadHandle != NULL, "CreateThread() failed." );
	const bool bReturn = (pThreadHandle != NULL);
	
	// MGH 21/4/2008: We aren't going to use the handle further, so we can close it immediately.
	// We need to do this in order to avoid a 4096 byte memory leak.
	if (pThreadHandle != NULL)
	{
		CloseHandle(reinterpret_cast<HANDLE>(pThreadHandle));
	}

	return bReturn;
}

void Asura_Thread_System::Platform_YieldCurrentThread()
{
	Sleep(0);
}

void Asura_Thread_System::Platform_DelayCurrentThread(float fTimeInSeconds, bool bAllowCallbacks)
{
	Sleep( static_cast< DWORD >( fTimeInSeconds * 1000.0f ) );
}

Asura_ThreadID Asura_Thread_System::Platform_GetCurrentThreadID()
{
	return GetCurrentThreadId();
}

Asura_ThreadID Asura_Thread_System::Platform_GetInvalidThreadID()
{
	return 0xFFFFFFFF;
}

/******************************************************************************/
/* Asura_Mutex Class Functions                                                */
/******************************************************************************/

void Asura_Mutex::Platform_Lock()
{
	if(!m_bInitialised)
	{
		InitializeCriticalSection(&m_xCriticalSection);
		m_bInitialised	= true;
	}

	EnterCriticalSection( &m_xCriticalSection );
}

void Asura_Mutex::Platform_Unlock()
{
	LeaveCriticalSection( &m_xCriticalSection );
}

/******************************************************************************/
/* Asura_SyncEvent Class Functions                                            */
/******************************************************************************/

void Asura_SyncEvent::Platform_WaitForSignal()
{
	#if defined ASURA_ASSERT
		const DWORD uRet = 
	#endif
		WaitForSingleObject( m_xEventHandle, INFINITE );

	Asura_Assert( uRet == WAIT_OBJECT_0, "Unexpected return value from WaitforSingleObject()." );
}

void Asura_SyncEvent::Platform_SetSignal()
{
	#if defined ASURA_ASSERT
		const int iRet = 
	#endif
		SetEvent( m_xEventHandle );

	Asura_Assert( iRet != 0, "Error in SetEvent()." );
}

void Asura_SyncEvent::Platform_ClearSignal()
{
	#ifdef ASURA_ASSERT
		const int iRet = 
	#endif
		ResetEvent( m_xEventHandle );

	Asura_Assert( iRet != 0, "Error in ResetEvent()." );
}


//////////////////////////////////////////////////////////////////////////
// Semaphore
//////////////////////////////////////////////////////////////////////////
void Asura_Semaphore::Platform_Wait()
{
#if defined ASURA_ASSERT
	const DWORD uRet = 
#endif
		WaitForSingleObject( m_xSemaphoreHandle, INFINITE );

	Asura_Assert( uRet == WAIT_OBJECT_0, "Unexpected return value from WaitforSingleObject()." );
}

bool Asura_Semaphore::Platform_TryWait()
{
	const DWORD uRet = WaitForSingleObject( m_xSemaphoreHandle, 0 );

	if (uRet == WAIT_OBJECT_0)
	{
		return true;
	}

	Asura_Assert( uRet == WAIT_TIMEOUT, "Unexpected return value from WaitforSingleObject()." );
	return false;
}

bool Asura_Semaphore::Platform_Signal()
{
	const DWORD uRet = ReleaseSemaphore( m_xSemaphoreHandle, 1, 0 );

	Asura_Assert( uRet != 0, "Unexpected return value from ReleaseSemaphore()." );
	return (uRet != 0);
}

// EOF //////////////////////////////////////////////////////////////////////////
