/*	XGSemaphore.cpp
 *
 *		Semaphore support. This encapsulates semaphore functionality on
 *	the destination architecture
 *
 *		Note: If this is a non-threaded build of the YAAF libraries,
 *	this still maintains an enter/leave critical count in case I'm
 *	recursively calling something I should't be. That'll at least simplify
 *	porting a non-threaded YAAF application into a threaded YAAF build.
 */

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

  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/

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

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <XError.h>
#include <XThread.h>

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGSemaphore::XGSemaphore
 *
 *		Create this semaphore. The count provided is the maximum number
 *	of threads that are permitted to pass the "EnterCritical" section.
 *	By default that is set to 1.
 */

XGSemaphore::XGSemaphore(unsigned long max)
{
#if !OPT_WINOS
	if (!XGThread::HasThreadManager()) {
		fAccess = max;
		return;
	}
#endif

#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
#if OPT_MACOS == 1
	fAccess = max;
#endif

#if OPT_WINOS == 1
	fHandle = CreateSemaphore(NULL,max,max,NULL);
#endif

#if OPT_XWIN == 1
	pthread_mutex_init(&fMutex,NULL);
	pthread_cond_init(&fCond,NULL);
	fAccess = max;
#endif
#else

	/*
	 *	Threads are not supported
	 */
	
	fAccess = max;
#endif
}

/*$	XGSemaphore::~XGSemaphore
 *
 *		Delete this thing. It is undefined what happens when you
 *	delete this thing while something is in the critical section.
 *	Therefore, it's preferable to create these as global objects
 *	which have lifespan guarenteed to be longer than the threads
 *	that make up this application.
 */

XGSemaphore::~XGSemaphore()
{
#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
#if OPT_WINOS == 1
	if (fHandle) CloseHandle(fHandle);
#endif // OPT_WINOS
#endif // OPT_THREADED
}

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGSemaphore::EnterCritical
 *
 *		Enter critical section that is protected by this semaphore.
 *	This checks to see if the access count is zero. If it is, this
 *	will then wait until the access count becomes non-zero again.
 *	Then, this will decrement the access count and return.
 */
 
void XGSemaphore::EnterCritical(void)
{
#if !OPT_WINOS
	if (!XGThread::HasThreadManager()) {
		Assert(fAccess != 0);
		fAccess--;
		return;
	}
#endif

#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
#if OPT_MACOS == 1
	ThreadBeginCritical();				// Stop tasks swap temporarly
	
	while (fAccess == 0) {
		ThreadEndCritical();
		YieldToAnyThread();				// Unable to enter yet.
		ThreadBeginCritical();
	}
	fAccess--;							// I'm in. Allow access again.
	
	ThreadEndCritical();
#endif

#if OPT_WINOS == 1
	WaitForSingleObject(fHandle,INFINITE);
#endif

#if OPT_XWIN == 1
	// lock
	pthread_mutex_lock(&fMutex);
	
	// now see if I have access
	while (fAccess == 0) {
		pthread_cond_wait(&fCond,&fMutex);
	}
	
	// I'm in. Decrement the count to indicate that I'm in
	fAccess--;
	
	// unlock
	pthread_mutex_unlock(&fMutex);
#endif
#else // OPT_THREADED == 0
	/*
	 *	Threads are not supported
	 */
	
	Assert(fAccess != 0);
	fAccess--;
#endif // OPT_THREADED
}

/*$	XGSemaphore::LeaveCritical
 *
 *		Leave critical section. This increements the access count,
 *	permitting other threads to return from EnterCritical() which
 *	may be waiting for access to this resource.
 */

void XGSemaphore::LeaveCritical(void)
{
#if !OPT_WINOS
	if (!XGThread::HasThreadManager()) {
		fAccess++;
		return;
	}
#endif

#if OPT_THREADED == 1
	/*
	 *	Threads are supported
	 */
	
#if OPT_MACOS == 1
	ThreadBeginCritical();				// Stop tasks swap temporarly
	fAccess++;							// Atomic increment allowed access
	ThreadEndCritical();				// Restart task swapping
#endif

#if OPT_WINOS == 1
	ReleaseSemaphore(fHandle,1,NULL);
#endif

#if OPT_XWIN == 1
	// lock
	pthread_mutex_lock(&fMutex);
	
	// Increment access count
	fAccess++;
	
	// Send the message if I was locked before. (Locking only happens
	// when fAccess is zero, so send the conditional unlock when this
	// is one.
	if (fAccess == 1) pthread_cond_signal(&fCond);
	
	// unlock
	pthread_mutex_unlock(&fMutex);
#endif
#else // OPT_THREADED == 0
	/*
	 *	Threads are not supported
	 */
	
	fAccess++;
#endif
}
