//
// Asura_Task_Array.cpp
//
// Created by KJFL - 23 January 2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_USE_TASK_SYSTEM

// This file's header.
#include "Core/Threads/Asura_Task_Array.h"

#include "Core/Threads/Asura_Task_System.h"

#include "Core/Asura_Multithread.h"


Asura_Task_Array::Asura_Task_Array( u_int uTotalArraySize, u_int uSuggestedNumberPerTask, Asura_TaskFunction pfnTask )
: m_uArraySize( uTotalArraySize )
, m_uArrayIndex( 0 )
, m_uElementsPerTask( uSuggestedNumberPerTask )
, m_bTerminateTasks( false )
, m_pxDataPackets( 0 )
, m_uNumberOfDataPackets( Asura_Task_System::GetNumberOfWorkerThreads() )
, Asura_Task( pfnTask )
{
	Asura_Assert( m_uNumberOfDataPackets >=1, "Code bug: No worker threads?" );

	m_pxDataPackets = new TaskDataPacket[m_uNumberOfDataPackets];

	// Tell the system about this task
	Asura_Task_System::AddTask( this );

	// And let's go
	SignalWorkIsAvailable();
}

Asura_Task_Array::~Asura_Task_Array()
{
	m_bTerminateTasks = true;

	// No locks required here since nothing internally signals work is available 
	// apart from the constructor.
	SignalWorkIsNotAvailable();
	
	// Ask to be removed from the system - this will not return until any in-progress
	// tasks have completed.
	Asura_Task_System::RemoveTask( this );

	delete [] m_pxDataPackets;
}

void* Asura_Task_Array::GetTaskData( void )
{
	m_xAccessMutex.Lock();

    // Try to get something from the Array. Return 0 if nothing is available.
	if ( m_uArrayIndex == m_uArraySize || m_bTerminateTasks )
	{
		SignalWorkIsNotAvailable();
		m_xAccessMutex.Unlock();
		m_bTerminateTasks = true; 
		return 0;
	}

	TaskDataPacket* pxPacket = 0;

	// KJFL - 24 January 2008 - I'm not entirely sold on the mechanism I've got here
	// for reusing data packets, but it's got to be better than newing and deleting
	// packets on the fly, which is what I did originally in the proof of concept.
	for ( u_int u=0; u<m_uNumberOfDataPackets; ++u )
	{
		if ( m_pxDataPackets[u].m_bFinishedWithData )
		{
			pxPacket = &m_pxDataPackets[u];
		}
	}

	if (!pxPacket)
	{
		Asura_Assert( false, "Code bug: unable to find empty data packet" );
		SignalWorkIsNotAvailable();
		m_xAccessMutex.Unlock();
		m_bTerminateTasks = true; 
		return 0;
	}
	
	// Ideally we'd do "m_uElementsPerTask", but we may be at the end of the array
	// in which case we'll process however many are left if it's less.
	u_int uRemainingElements = m_uArraySize - m_uArrayIndex;
	u_int uElementsForThisTask = Asura_Maths::Min( uRemainingElements, m_uElementsPerTask );

	pxPacket->m_uArrayStartIndex = m_uArrayIndex;
	pxPacket->m_uNumberToProcess = uElementsForThisTask;
	pxPacket->m_bFinishedWithData = false;
	
	m_uArrayIndex += uElementsForThisTask;

	m_xAccessMutex.Unlock();

	// Signal another subtask is available for someone to get.
	// Doing this after the unlock will reduce missed locks
	// when the threads start up.
	SignalWorkIsAvailable();

	return pxPacket;
}

bool Asura_Task_Array::HasFinished( void ) const
{
	return m_bTerminateTasks;
}


#endif // #ifdef ASURA_USE_TASK_SYSTEM

