/*	XThread.h
 *
 *		This encapsulates thread support
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#ifndef PandaWave_XTHREAD
#define PandaWave_XTHREAD

/**@name Support--Thread Management
 *
 *		Thread management is a tricky issue, especially for cross-
 *	platform application frameworks such as YAAF. In part this is
 *	because of the wide variety of threading capabilities provided
 *	by different platforms.
 *
 *		For example, on the Macintosh, the Thread Manager enables
 *	thread support. However, threads are "cooperative", meaning that
 *	a thread must explicitly yield time slices to other threads.
 *
 *		On Windows, the built-in threading capabilities provide
 *	pre-emptive threading; however, windows created by a thread are
 *	owned by that thread. This is incompatable with YAAF, which
 *	presumes a central event processing loop for all the winodws
 *	in the application.
 *
 *		And Unix has a wide variety of threading capabilities,
 *	from pthread support to no threading support at all. (We assume
 *	with YAAF that pthreads are present, but this capability hasn't
 *	been as thoroughly tested as I would like.)
 *
 *		In order to allow your multi-threaded application to work
 *	successfully with YAAF, you must follow the following rules:
 *
 *		1) A non-main thread must *not* create dialogs, alerts,
 *	or other windows. YAAF is not designed to properly process such
 *	alerts. (In fact, the error handling mechanism currently used
 *	by YAAF is broken in this way, and will be fixed in a future
 *	release.)
 *
 *		This means that subthreads are restricted to processing
 *	threads or networking operations which can be performed without
 *	a user interface. (If you need to pass status messages, you must
 *	somehow pass those messages back to the main thread for posting.)
 *
 *		2) You *must* write your threaded application to use explicit
 *	yielding, even on a pre-emptive environment.
 *
 *		3) Normal caviats about being careful with your threading
 *	model apply. One platform's thread-safe operation is another
 *	platform's memory "turkey shoot", and a system failure waiting
 *	to happen.
 *
 *		Also note that while parts of YAAF have been written to be
 *	re-entrant safe by using semaphores, not all of YAAF is safe in
 *	this manner. So beware!
 *
 *		Look. I'm not trying to scare you away from threads. But they
 *	are a big messy topic, especially for something like YAAF. Best
 *	to play it safe.
 */
 
//@{

#include <XConfig.h>

#if OPT_MACOS == 1
#include <Threads.h>
#endif

#if OPT_XWIN && OPT_THREADED
#include <pthread.h>
#endif

/************************************************************************/
/*																		*/
/*	Forwards															*/
/*																		*/
/************************************************************************/

class XGThread;

/************************************************************************/
/*																		*/
/*	Thread Support														*/
/*																		*/
/************************************************************************/

typedef void (* XGThreadProcPtr)(XGThread *, long arg);

/*$	XGThread
 *
 *		This is the thread object; this knows how to handle, identify,
 *	fold, and mutulate a thread object.
 *
 *		Attach/Detach semantics provides me a way to manipulate a
 *	thread, even after the thread dies. Normally, the XGThread class
 *	pointer is owned by the thread--when the thread dies, the XGThread
 *	class object is deleted. This sucks if you want to keep a pointer
 *	to the thread object pass it's expiration.
 *
 *		Thus, the Attach/Detach semantics allows other objects to 
 *	keep a pointer to the XGThread, and claim ownership past the
 *	thread's termination.
 *
 *		Note that when the thread is first created, it's owned by
 *	the thread itself. That means if you want to create this thread
 *	and keep a pointer to that thread, you need to explicitly attach
 *	to the thread after it's creation: thus,
 *
\begin{verbatim}

	XGThread *ptr = new XGThread(myProc,0,0,true);	// create suspended
	ptr->Attach();									// attach to it
	ptr->Resume();									// and resume execution

\end{verbatim}
 *
 *		Otherwise, the thread could theoretically execute to completion
 *	and terminate, releasing the thread handle, before your assignment
 *	operation completes: leaving you with a stale pointer instead of
 *	a valid pointer to a thread object.
 *
 *	(Meaning that the operation:
 *
\begin{verbatim}

	XGThread *ptr = new XGThread(myProc);			// create running
	ptr->Attach();									// <<--ERROR!!!

\end{verbatim}
 *
 *		is not safe because before you get a chance to call ptr->Attach(),
 *	your thread may have already bailed.)
 */

class XGThread {
	public:
								XGThread(XGThreadProcPtr,
										 long arg = 0,
										 long stack = 0, 
										 bool suspend = false);
								XGThread();
		virtual					~XGThread();
		
		/*
		 *	Attach/detach semantics--for other threads which want
		 *	to know information about this thread.
		 */
		
		
		/*$	XGThread::Attach
		 *
		 *		Increments the attach count. When created, this
		 *	thread has an attach count of 1, corrisponding to
		 *	the thread procedure which owns this thread.
		 */
		
		void					Attach(void)
									{
										fAttach++;
									}
									
		/*$	XGThread::Detach
		 *
		 *		Decrements the attach count, and if the count
		 *	reaches zero, deletes this object. Normally you would
		 *	call this instead of the destructor for this thread.
		 */
		
		void					Detach(void)
									{
										if (--fAttach <= 0) delete this;
									}
		
		/*$	XGThread::GetAttach
		 *
		 *		Returns the attach count for this object.
		 */
		
		long					GetAttach(void) const
									{
										return fAttach;
									}
		
		/*
		 *	Thread management routines
		 */
		 		
		static XGThread			*CurrentThread();
		static void				YieldThread(bool f = false);
		static void				Sleep(long = 0);
		
		/*$	XGThread::GetThreadCount
		 *
		 *		Returns the number of threads that currently exist
		 *	in my application. This returns the total number of threads
		 *	including the main thread.
		 */
		
		static long				GetThreadCount()
									{
										return gCount;
									}
#if OPT_MACOS
		static void				SetThreadPool(short number, long stack);
#endif
		
		/*
		 *	Scheduling routines
		 */
		
		void					Suspend(void);
		void					Resume(void);
		bool					IsSuspend(void);	// Thread suspended?
		
		void					Kill(void);
		bool					IsAlive(void);		// Valid thread?
		
		/*
		 *	Can I even use thread routines?
		 */
		
		static bool				HasThreadManager();

	private:
		static long				gCount;
		long					fAttach;

#if OPT_THREADED == 1
		XGThreadProcPtr			fProc;
		long					fArg;
		
		XGThread				*fNext;
		static XGThread			*gList;

#if OPT_MACOS == 1
		ThreadID				fThreadID;
		static bool				gInitTM;
		static bool				gHasTM;
		static long				gStackSize;
		static bool				gThreadPool;
		static pascal void		*ThreadEntry(void *);
#endif

#if OPT_WINOS == 1
		bool					fThreadSuspend;
		HANDLE					fThreadHandle;
		unsigned int			fThreadID;
		
		static unsigned int WINAPI ThreadEntry(LPVOID);
#endif

#if OPT_XWIN == 1
		bool					fThreadValid;
		bool					fThreadSuspend;
		pthread_t				fThreadID;
		static void				*ThreadEntry(void *);
#endif

#endif // OPT_THREADED
};

/*$	XGSemaphore
 *
 *		This is a semaphore object. This implements a simple interface
 *	to a mutual exclusion object, an object which can control access to
 *	a chunk of code. If you are looking to control access to a particular
 *	subroutine, you may consider using the XGCritical() class, which is
 *	an exception-safe mechanism for locking and unlocking a semaphore.
 */

class XGSemaphore {
	public:
								XGSemaphore(unsigned long maxaccess = 1);
		virtual					~XGSemaphore();
		
		/*
		 *	Access routines
		 */
		
		void					EnterCritical(void);
		void					LeaveCritical(void);

	private:

#if OPT_THREADED == 1
	#if OPT_MACOS == 1
		unsigned long			fAccess;
	#endif

	#if OPT_WINOS == 1
		HANDLE					fHandle;
	#endif
	
	#if OPT_XWIN == 1
		pthread_mutex_t			fMutex;
		pthread_cond_t			fCond;
		unsigned long			fAccess;
	#endif
#else // OPT_THREADED == 0
	unsigned long				fAccess;
#endif // OPT_THREADED
};

/*$	XGCritical
 *
 *		Exception safe mechanism for locking and unlocking a semaphore.
 *	This provides the enter/exit semantics in a stack object for semaphore 
 *	access; this provides a return-safe and throw-safe way of providing 
 *	enter/exit semaphore access
 */

class XGCritical {
	public:
		
		/*$	XGCritical::XGCritical
		 *
		 *		Constructs this object. Stores a pointer to the
		 *	semaphore, and calls the semaphore's "EnterCritical"
		 *	method.
		 */
		
								XGCritical(XGSemaphore *x)
									{
										fSemaphore = x;
										x->EnterCritical();
									}
									
		/*$	XGCritical::~XGCritical
		 *
		 *		Destructs this object. This calls the "LeaveCritical"
		 *	method of the stored semaphore.
		 */
		
								~XGCritical()
									{
										fSemaphore->LeaveCritical();
									}
	
	private:
		XGSemaphore				*fSemaphore;
};

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

#if OPT_MACOS
extern bool	GYieldThread;		// true--everyone wants to give up time
#endif

//@}

#endif /* PandaWave_XTHREAD */
