/* 
 * 
 * Confidential Information of Telekinesys Research Limited (t/a Havok).  Not for disclosure or distribution without Havok's
 * prior written consent.This software contains code, techniques and know-how which is confidential and proprietary to Havok.
 * Level 2 and Level 3 source code contains trade secrets of Havok. Havok Software (C) Copyright 1999-2008 Telekinesys Research Limited t/a Havok. All Rights Reserved. Use of this software is subject to the terms of an end user license agreement.
 * 
 */


#include <Demos/demos.h>

#include <Physics/Collide/Query/Multithreaded/hkpCollisionJobQueueUtils.h>
#include <Physics/Collide/Query/Multithreaded/hkpCollisionJobsRegisterUtil.h>

#include <Physics/Utilities/Thread/JobDispatcher/hkpJobDispatcherUtil.h>
#include <Physics/Utilities/Thread/Multithreading/hkpMultithreadingUtil.h>

#include <Demos/DemoCommon/Utilities/CollisionQuery/CollisionQueryMultithreadedUtility.h>


CollisionQueryMultithreadedUtility::Input::Input()
{
	m_world								= HK_NULL;
	m_registerDefaultPpuJobFunctions	= true;
}


CollisionQueryMultithreadedUtility::InputPs3::InputPs3() : Input()
{
	m_numSpus							= 6;
	m_spursThreadGroupPriority			= 250;
	m_spursHandlerThreadPriority		= 1;
	m_spuUtil							= HK_NULL;
	m_monitorCacheArray					= HK_NULL;
}


CollisionQueryMultithreadedUtility::CollisionQueryMultithreadedUtility() :
	m_cpuWaitSemaphore(0,1000), m_taskCompletionSemaphore(0, 1000), m_spuWaitSemaphore(0,1000),
	m_jobQueue(&m_cpuWaitSemaphore, &m_taskCompletionSemaphore, &m_spuWaitSemaphore),
	m_jobDispatcherUtil(HK_NULL)
{
}


CollisionQueryMultithreadedUtility::~CollisionQueryMultithreadedUtility()
{
	if ( m_jobDispatcherUtil )
	{
		delete m_jobDispatcherUtil;
	}
}


void CollisionQueryMultithreadedUtility::initPpu(Input& input)
{
	// Init the job queue.
	initJobQueue();

	// Init ppu job dispatcher utility.
	initPpuJobDispatcherUtility(input);
}


void CollisionQueryMultithreadedUtility::initSpu(InputPs3& input)
{
	// Init the job queue.
	initJobQueue();

	// Init SPURS.
	initPs3Spurs(input, true);
}


void CollisionQueryMultithreadedUtility::initPpuAndSpu(InputPs3& input)
{
	// Init the job queue.
	initJobQueue();

	// Init SPURS.
	initPs3Spurs(input, false);

	// Init ppu job dispatcher utility.
	initPpuJobDispatcherUtility(input);
}


void CollisionQueryMultithreadedUtility::initJobQueue()
{
	m_jobQueue.m_jobPopFunc    = hkpCollisionJobQueueUtils::popCollisionJob;
	m_jobQueue.m_finishJobFunc = hkpCollisionJobQueueUtils::finishCollisionJob;
}


void CollisionQueryMultithreadedUtility::initPpuJobDispatcherUtility(Input& input)
{
	// Create a ppu job dispatcher utility.
	m_jobDispatcherUtil = new hkpJobDispatcherUtil();

	// Register all default collision query job functions; you are free to register only selected functions here or even custom implementations.
	if ( input.m_registerDefaultPpuJobFunctions )
	{
		hkpCollisionQueryJobsRegisterUtil::registerDefaultCollisionQueryFunctions(m_jobDispatcherUtil);
	}
}


void CollisionQueryMultithreadedUtility::initPs3Spurs(InputPs3& input, bool spuOnly)
{
#if defined(HK_PLATFORM_HAS_SPU)

	//
	// Some more job queue setup specifically for SPURS.
	// For maximum performance we don't want the PPU to take any SPU jobs.
	//
	if ( spuOnly )
	{
		m_jobQueue.setJobQueryRules( hkJobQueue::SPU_AVAILABLE_AND_PPU_CANNOT_TAKE_SPU_TASKS );
	}

	//
	// Init SPURS.
	//
	{	
		// Init the spu utility
		m_spuUtil = new hkSpuUtil();
		*(input.m_spuUtil) = m_spuUtil;

		// As long as we use the INTEGRATE .elf as the default .elf, we need to pass it the world's job queue (as there are hardcoded accesses to the world's job queue in hkpMultithreadingUtil)!
		hkArray<hkSpuTaskParams> defaultElfParams;
		{
			defaultElfParams.setSize(input.m_numSpus);
			{
				for ( int i = 0; i < input.m_numSpus; i++ )
				{
					defaultElfParams[i].m_param0 = input.m_world->m_multithreadedSimulationJobData;
					defaultElfParams[i].m_param1 = input.m_world->getJobQueue();
					defaultElfParams[i].m_param2 = input.m_monitorCacheArray[i];	// This parameter is only needed if you wish to collect timers from the SPU
#if defined(HK_PLATFORM_PS3)
					defaultElfParams[i].m_param3 = m_spuUtil->getMemoryBins();
#endif	
				}
			}
		}

		//
		// initialize the collision query specific general job data
		//
		m_jobData.m_collisionInput = input.m_world->m_collisionInput;

		// For our COLLISION QUERY .elf we use our own job queue as this is where our jobs will be added to.
		// Note: we could also use the world's job queue for this. On PS3 however the world's job queue is configured to not allow PPU to take SPU jobs, thus causing a deadlock in this case.
		//       To keep the code simple we therefore use a custom queue for all platforms.
		m_elfParams.setSize(input.m_numSpus);
		{
			for ( int i = 0; i < input.m_numSpus; i++ )
			{
				m_elfParams[i].m_param0 = &m_jobData;
				m_elfParams[i].m_param1 = &m_jobQueue;
				m_elfParams[i].m_param2 = input.m_monitorCacheArray[i];	// This parameter is only needed if you wish to collect timers from the SPU
#if defined(HK_PLATFORM_PS3)
				m_elfParams[i].m_param3 = m_spuUtil->getMemoryBins();
#endif
			}
		}

		hkSpuTaskSetParams taskSetParams;

#if defined(HK_PLATFORM_PS3)
		// If we are running on the PS3 use the task set defaultElfParams for the two spurs task addresses
		extern char _binary_hkpCollide_elf_start[];
		extern char _binary_hkpIntegrate_elf_start[];
		extern char _binary_hkpCollisionQuery_elf_start[];

		taskSetParams.m_param0 = _binary_hkpIntegrate_elf_start;
		taskSetParams.m_param1 = _binary_hkpCollide_elf_start;

		void* defaultElf = _binary_hkpIntegrate_elf_start;
		m_elf			 = _binary_hkpCollisionQuery_elf_start;

		CellSpurs* spurs = (CellSpurs*)hkAlignedAllocate<char> ( CELL_SPURS_ALIGN, CELL_SPURS_SIZE , HK_MEMORY_CLASS_DEMO ); 
		int ret = cellSpursInitialize (spurs, input.m_numSpus, input.m_spursThreadGroupPriority, input.m_spursHandlerThreadPriority, false);
		if (ret)
		{
			HK_ERROR(0x73e432a2, "cellSpursInitialize failed :" << ret);
		}
#else
		HK_CELL_SPURS* spurs = HK_NULL;
		void* defaultElf = (void*)(hkUlong)HK_TASK_PHYSICS_SIMULATE;	// simulate = integrate + collide
		m_elf			 = (void*)(hkUlong)HK_TASK_COLLISION_QUERY;
#endif

		m_spuUtil->initSpursTaskset            (input.m_numSpus, taskSetParams, spurs);
		m_spuUtil->setDefaultSpursTaskAndParams(defaultElfParams, defaultElf);
	}

#else

	HK_ASSERT2(0xaf35461f, false, "You can only call CollisionQueryMultithreadedUtility::initSpu() or CollisionQueryMultithreadedUtility::initPpuAndSpu() on PS3.");

#endif
}


void CollisionQueryMultithreadedUtility::putJobOntoQueue(hkpCollisionQueryJob* job, hkJobQueue::JobType jobType)
{
#if defined(HK_DEBUG)
	if ( jobType == hkJobQueue::JOB_TYPE_CPU )
	{
		HK_ASSERT2(0xaf541fe3, m_jobDispatcherUtil, "You have to call initPpuAndSpu() or initPpu() if you want to put CPU jobs onto the queue.");
	}
#endif


	m_jobQueue.addJob( *reinterpret_cast<hkJobQueue::JobQueueEntry*>(job), hkJobQueue::JOB_HIGH_PRIORITY, jobType );
}


void CollisionQueryMultithreadedUtility::startPpu(hkpMultithreadingUtil* multiThreadingUtil)
{
	// Kick off multithreading's worker threads.
	multiThreadingUtil->processAsynchronousJobs( m_jobQueue, m_jobDispatcherUtil );
}


void CollisionQueryMultithreadedUtility::startSpu()
{
#if defined(HK_PLATFORM_HAS_SPU)

	// Kick off the collision query spurs task (if we utilize the spu).
	m_spuUtil->startSpursTasks(&m_elfParams, m_elf);

#else

	HK_ASSERT2(0xaf35461e, false, "You can only call CollisionQueryMultithreadedUtility::startSpu() or CollisionQueryMultithreadedUtility::startPpuAndSpu() on PS3.");

#endif
}


void CollisionQueryMultithreadedUtility::startPpuAndSpu(hkpMultithreadingUtil* multiThreadingUtil)
{
	startSpu();
	startPpu(multiThreadingUtil);
}


void CollisionQueryMultithreadedUtility::waitForSpuFinished()
{
#if defined(HK_PLATFORM_HAS_SPU)

	//
	// Wait for the spurs tasks to finish. This makes sure all timer information will have finished dma-ing to main memory.
	//
	{
		for (int i = 0; i < m_spuUtil->getNumActiveSpursTasks(); ++i)
		{
			m_jobQueue.m_taskCompletionSemaphore->acquire();
		}
	}

#else

	HK_ASSERT2(0xaf35461d, false, "You can only call CollisionQueryMultithreadedUtility::waitForSpuFinished() on PS3.");

#endif
}


/*
* Havok SDK - NO SOURCE PC DOWNLOAD, BUILD(#20080529)
*
* Confidential Information of Havok.  (C) Copyright 1999-2008 
* Telekinesys Research Limited t/a Havok. All Rights Reserved. The Havok
* Logo, and the Havok buzzsaw logo are trademarks of Havok.  Title, ownership
* rights, and intellectual property rights in the Havok software remain in
* Havok and/or its suppliers.
*
* Use of this software for evaluation purposes is subject to and indicates 
* acceptance of the End User licence Agreement for this product. A copy of 
* the license is included with this software and is also available at
* www.havok.com/tryhavok
*
*/
