//
// Asura_ProducerConsumerQueue.h
//
// Created by KJFL - 18 April 2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
#pragma once
#endif

#ifndef __ASURA_PRODUCERCONSUMERQUEUE_H__
#define __ASURA_PRODUCERCONSUMERQUEUE_H__

// Asura_ProducerConsumerQueue creates a standalone thread group to act as workers for any tasks added.
// The worker threads are created on construction and destroyed as the class goes out of scope. When there
// are no tasks, the worker threads wait in an efficient manner using Asura_Semaphore. This class is intended 
// for use with systems that need to do tasks from time to time in the background (e.g. sound), without the
// hassle/overheads of creating a dedicated thread each time.
// 
// Tasks are defined by three things: a void* data pointer, a data size u_int (intended for assertions etc),
// and a function pointer of type void (*pfn)( void *, u_int ). Essentially the function pointer will be 
// called with the two data parameters.
//
// Tasks are stored in a cyclic queue: on construction you can choose the maximum size of the queue, and the
// number of consumer threads (both should be >=1). There are two methods for adding to the queue - one waits
// for spare space, and the other will return false if there wasn't any spare space (i.e. not adding the task).

#include "Core/Asura_Multithread.h" // for Asura_Semaphore

typedef void (*Asura_ProduceConsumerFunction)( void*, u_int );

class Asura_ProducerConsumerQueue
{
	public:	

		Asura_ProducerConsumerQueue( u_int uMaxQueueSize, u_int uNumberOfThreads = 1, const ASURA_MULTITHREAD_STACK_SIZE xStackSize = ASURA_MULTITHREAD_STACK_SIZE_LARGE );
		~Asura_ProducerConsumerQueue();

		// Try to add something to the queue. 
		// If the queue is full, it will immediately return false.
		bool TryToAddToQueue( Asura_ProduceConsumerFunction pfTaskFunction, void* pData, u_int uSizeOfData );

		// Add something to the queue, waiting if the queue is full.
		void AddToQueue( Asura_ProduceConsumerFunction pfTaskFunction, void* pData, u_int uSizeOfData );

		// Returns the max size of the queue, as passed into the constructor.
		u_int GetMaxQueueSize( void ) const;

		// Currently not implemented.
		// Returns the current size of the queue - by design this does no locks etc,
		// so it's only useful for external routines trying to gauge fullness/progress.
		u_int GetCurrentQueueSize( void ) const { return 0; } 

	private:

		struct TaskDataPacket
		{
			Asura_ProduceConsumerFunction m_pfTaskFunction;

			void*	m_pData;
			u_int	m_uSizeOfData; // in bytes
		};

		// internal method called by both AddToQueue() and TryToAddToQueue()
		void AddToQueue_Internal( Asura_ProduceConsumerFunction pfTaskFunction, void* pData, u_int uSizeOfData );

		bool GetFromQueue( Asura_ProducerConsumerQueue::TaskDataPacket& xPacket );

		static void ConsumerThreadMain( const void* pQueue );

		bool ShouldTerminate( void ) const { return m_bShouldTerminate; }

		Asura_Semaphore	m_xWorkAvailableSemaphore;
		Asura_Semaphore m_xFreeSpaceSemaphore;
		Asura_Semaphore	m_xThreadsTerminated;


		TaskDataPacket*	m_pxQueueHead;

		u_int			m_uNumberOfThreads;
		u_int			m_uMaxQueueSize;
		volatile u_int	m_uNextFree;
		volatile u_int	m_uNextTask;

		Asura_Mutex		m_xAddMutex;
		Asura_Mutex		m_xGetMutex;

		bool			m_bShouldTerminate;
};


#endif//__ASURA_PRODUCERCONSUMERQUEUE_H__

