//
// Asura_Platform_Physics_Solver.cpp
//
// Created by KJFL - 11 July 2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Physics/Asura_Physics_Solver.h"

#ifndef ASURA_PHYSICS_USE_OLD_SOLVER

// Stdlib platform-specific.
#include <cell/sync/barrier.h>
#include <cell/sync/lfqueue.h>

// Engine platform-specific.
#include "PPU_SPU_Utils/Asura_PS3_SPUGroupController.h"

#ifdef	SPURS_PHYSICS	//	See Asura_Engine_Configuration for appropriate project
	#include "Core/Memory/Asura_Mem.h"
	#include "PS3/PPU_SPU_UTILS/Asura_PS3_SPU_HelperStructs.h"
	#include "PS3/PPU_SPU_UTILS/Asura_PS3_SPURS.h"
	#include "PS3/PPU_SPU_UTILS/Asura_PS3_SPURS_TaskController.h"

	extern const char _binary_task_Physics_Taskset_elf_start[];	

	static CellSpursBarrier g_xSPUBarrier;
	static CellSyncBarrier	g_xPPUBarrier;

	static Physics_TaskParameters g_xBarriers;

	static Asura_PS3_SPURS_TaskController*	g_pxSPURSTaskController = NULL;
	static const u_int uNUMBER_OF_SPU_THREADS = 4;
   
	static Asura_Physics_Solver_Data	g_xTerminatorTask __attribute__(( aligned(16) ));
	static const u_int uMAX_NUMBER_OF_QUEUE_ENTRIES = 2;

	static u_char*	g_pucSolverMem = 0;

	static const u_int uSOLVER_BUFFER_SIZE = 128 * 1024;
	static const u_int uNUMBER_OF_BUFFERS = 8;
	static const u_int uMIN_CONTACTS_TO_SEND_TO_SPU = 2;
#else

	extern u_int _binary_Physics_Solver_SPU_elf_start;

	static Asura_PS3_SPUGroupController*	g_pxSPUThreadGroupController = NULL;
	    
	static Asura_Physics_Solver_Data	g_xTerminatorTask __attribute__(( aligned(16) ));

	static const u_int uNUMBER_OF_SPU_THREADS = 4;
	static const u_int uMAX_NUMBER_OF_QUEUE_ENTRIES = 2;

	static u_char*	g_pucSolverMem = 0;

	static const u_int uSOLVER_BUFFER_SIZE = 128 * 1024;
	static const u_int uNUMBER_OF_BUFFERS = 8;
	static const u_int uMIN_CONTACTS_TO_SEND_TO_SPU = 2;

	static CellSyncBarrier g_xSPUBarrier;
	static CellSyncBarrier g_xPPUBarrier;
#endif

//const char* g_aszThreadNames[uNUMBER_OF_SPU_THREADS] =
//{
//  "Physics_Solver_SPU_0",
//  "Physics_Solver_SPU_1",
//  "Physics_Solver_SPU_2",
//  "Physics_Solver_SPU_3",
//};


static bool g_bBegunSPUUpdatesThisFrame;


static void BeginSPUUpdates( void )
{
#ifdef SPURS_PHYSICS
	cellSpursBarrierInitialize( g_pxSPURSTaskController->GetTaskset()->GetTaskset(), &g_xSPUBarrier, uNUMBER_OF_SPU_THREADS );
    cellSyncBarrierInitialize( &g_xPPUBarrier, uNUMBER_OF_SPU_THREADS );

    uint64_t uSPUBarrierAddr = reinterpret_cast<uint64_t>( &g_xSPUBarrier );
    uint64_t uPPUBarrierAddr = reinterpret_cast<uint64_t>( &g_xPPUBarrier );
	g_xBarriers.uSPUBarrierAddr = uSPUBarrierAddr;
	g_xBarriers.uPPUBarrierAddr	= uPPUBarrierAddr;  
	
	g_pxSPURSTaskController->Begin( (void*)&g_xBarriers );
#else
    // Each SPU thread will need to wait until all the others have hit their
    // terminators.
    cellSyncBarrierInitialize( &g_xSPUBarrier, uNUMBER_OF_SPU_THREADS );

    // And then the PPU will need to wait until they've all resumed checking
    // the queue before suspending the thread group.
    cellSyncBarrierInitialize( &g_xPPUBarrier, uNUMBER_OF_SPU_THREADS );

    uint64_t uSPUBarrierAddr = reinterpret_cast<uint64_t>( &g_xSPUBarrier );
    uint64_t uPPUBarrierAddr = reinterpret_cast<uint64_t>( &g_xPPUBarrier );
	g_pxSPUThreadGroupController->Begin( NULL, NULL, &uSPUBarrierAddr, &uPPUBarrierAddr );
#endif
}


static void EndSPUUpdates( void )
{
#ifdef	SPURS_PHYSICS
	u_int uRetCode	= 0;
	g_pxSPURSTaskController->PushTerminators();

	do
	{
	}	while ( (uRetCode = cellSyncBarrierTryWait( &g_xPPUBarrier )) != 0 );

	g_pxSPURSTaskController->End();
#else
    g_pxSPUThreadGroupController->PushTerminators();
    cellSyncBarrierWait( &g_xPPUBarrier );
	g_pxSPUThreadGroupController->End();
#endif
}


void Asura_Physics_Solver::Platform_Initialise( void )
{
	g_pucSolverMem = (u_char*)Asura_AllocateAlign( uSOLVER_BUFFER_SIZE * uNUMBER_OF_BUFFERS, ASURA_ALIGN_128_BYTE );

	g_xTerminatorTask.m_uNumberOfSolverBodies = 0;

#ifdef SPURS_PHYSICS
	g_pxSPURSTaskController = new Asura_PS3_SPURS_TaskController( uNUMBER_OF_SPU_THREADS, "Physics_Solver_Taskset", _binary_task_Physics_Taskset_elf_start, sizeof(Asura_Physics_Solver_Data), uMAX_NUMBER_OF_QUEUE_ENTRIES );
	g_pxSPURSTaskController->SetTerminator( &g_xTerminatorTask );
#else
	g_pxSPUThreadGroupController = new Asura_PS3_SPUGroupController( "Physics_Solver_SPU",
																     &_binary_Physics_Solver_SPU_elf_start,
																     uNUMBER_OF_SPU_THREADS,
																     Asura_PS3_SPUGroupController::PRIORITY_HIGH,
																     sizeof(Asura_Physics_Solver_Data), uMAX_NUMBER_OF_QUEUE_ENTRIES,
                                                                     NULL, //g_aszThreadNames,
                                                                     true );

	g_pxSPUThreadGroupController->SetTerminatorJob( &g_xTerminatorTask );

    // Since we may not actually send any work to the SPUs for a while and
    // since there's a lengthy pause the first time we do so (while the
    // thread group is initialised, etc., it would seem) we'll start and
    // suspend it immediately.
    BeginSPUUpdates();
    EndSPUUpdates();
#endif
}
	
void Asura_Physics_Solver::Platform_Shutdown( void )
{
#ifdef SPURS_PHYSICS
	g_pxSPURSTaskController->End();
	delete g_pxSPURSTaskController;
#else
	g_pxSPUThreadGroupController->End(); // just in case
	
	delete g_pxSPUThreadGroupController;
#endif

	if ( g_pucSolverMem )
	{
		Asura_DeallocateAlign( g_pucSolverMem );
		g_pucSolverMem = 0;
	}
}


void Asura_Physics_Solver::Platform_Begin( void )
{
    g_bBegunSPUUpdatesThisFrame = false;
}


void Asura_Physics_Solver::Platform_End( void )
{
    if( g_bBegunSPUUpdatesThisFrame )
    {
        EndSPUUpdates();
    }
}


u_char* Asura_Physics_Solver::Platform_GetSolverBuffer( void )
{
	static u_int uBuffer = 0;
	
	u_int uOffset = uSOLVER_BUFFER_SIZE*uBuffer;

	// Cycle round the buffers so we don't run into an existing DMA 
	// TODO: KJFL - 10 July 2008 - this is hacky.
	uBuffer = (uBuffer+1)&7;

	return g_pucSolverMem + uOffset;
}

u_int Asura_Physics_Solver::Platform_GetSolverBufferSize( void )
{
	return uSOLVER_BUFFER_SIZE;
}

bool Asura_Physics_Solver::Platform_SolveAllContacts( Asura_Physics_Solver_Data* pxTask )
{
	if( pxTask->m_uNumberOfSingleBodyContacts + pxTask->m_uNumberOfPairingContacts + pxTask->m_uNumberOfConeTwistConstraints < uMIN_CONTACTS_TO_SEND_TO_SPU )
    {
        return false;
    }

    if( !g_bBegunSPUUpdatesThisFrame )
    {
        BeginSPUUpdates();
        g_bBegunSPUUpdatesThisFrame = true;
    }

#ifdef SPURS_PHYSICS
	g_pxSPURSTaskController->PushTask_Blocking( pxTask );
#else
    CellSyncLFQueue* pxQueue = g_pxSPUThreadGroupController->GetQueue();
    cellSyncLFQueuePush( pxQueue, pxTask );
#endif

    return true;
}

#endif
