/*static char *SCCSID = "@(#)semaph.c   6.12 92/02/19";*/
/*==============================================================*\
 *  SEMAPH.C - routines for demonstrating semaphore API.	*
 *	  (C) Copyright IBM Corporation 1992.			*
 *--------------------------------------------------------------*
 *  This module contains code to demonstrate the use of		*
 *  semaphores to control access to a resource shared by	*
 *  multiple threads. Event semaphores are used to signal a	*
 *  thread is to give up a resource. A Mutex semaphore is used	*
 *  to provide exclusive access to the resource. A mux		*
 *  semaphore provides a method to check multiple event		*
 *  semaphores.							*
 *								*
 *								*
 *--------------------------------------------------------------*
 *								*
 *  This source file contains the following functions:		*
 *								*
 *	  VOID	 SemError(PSZ,ULONG);				*
 *	  ULONG	 CreateAllSems(VOID);				*
 *	  VOID	 StartSemExample(VOID);				*
 *	  VOID	 ThreadConsumer(ULONG);				*
 *	  VOID	 SignalUserEvent(PULONG pfAutoMode);		*
 *	  ULONG	 SetAutoMode(VOID);				*
 *	  VOID	 RunAuto(VOID);					*
 *	  VOID	 StopSemaphore(VOID);				*
 *								*
 *								*
\*==============================================================*/

/*--------------------------------------------------------------*\
 *  Include files, macros, defined constants, and externs	*
\*--------------------------------------------------------------*/
#include "semaph.h"

/*--------------------------------------------------------------*\
 *  Global variables						*
\*--------------------------------------------------------------*/
extern LONG colors[MAXUSERS];
extern LONG clrText[MAXUSERS];
static VOID MyMove (ULONG usMyID, ULONG ulUser);

/****************************************************************\
 * Procedure to print error messages to screen in a Message Box.*
 *--------------------------------------------------------------*
 *								*
 *  Name:    SemError(pszAPIName,usErrorCode)			*
 *								*
 *  Purpose: Used to print name of API and error number when	*
 *	     a return code other than 0 is returned from an	*
 *	     API call.						*
 *								*
 *  Usage:   Called by all procedures in this file whenever	*
 *	     an API call fails.					*
 *								*
 *  Method:  The error number is converted to a string.	 String *
 *	     functions are used to build the error message.	*
 *	     The message is printed in a Message Box.		*
 *								*
 *  NOTE:    This function is called by multiple threads,	*
 *	     therefore, only re-entrant functions can be used.	*
 *	     Note that string is build according to American	*
 *	     English conventions using hard-coded strings, and	*
 *	     so portability to other languages would require a	*
 *	     more flexible approach.				*
 *								*
 *  Returns: none.						*
 *								*
\****************************************************************/
VOID SemError(PSZ pszAPIName,ULONG usErrorCode)
{
   char acMessage[100],acErrorNumber[10];

   itoa(usErrorCode,acErrorNumber,BASETEN);
   strcpy(acMessage,pszAPIName);
   strcat(acMessage,": error # ");
   strcat(acMessage,acErrorNumber);

   WinMessageBox(HWND_DESKTOP,
		 hwndMain,
		 acMessage,
		 szAppName,
		 1,
		 MB_OK);
   return;
}

/****************************************************************\
 * Routine to create  semaphores used in this file.		*
 *--------------------------------------------------------------*
 *								*
 *  Name:    CreateAllSems(VOID)				*
 *								*
 *  Purpose: Create semaphores needed by the consumer threads.	*
 *	     Checks return codes from semaphore creation.	*
 *								*
 *  Usage:   Called by StartSemExample.				*
 *								*
 *  Method:  Semaphores are all anonymous private semaphores	*
 *	     since the semaphores are used by threads in the	*
 *	     same process.					*
 *								*
 *  Returns: 0 if all semaphores are created successfully.	*
 *	     Otherwise returns error code for first create	*
 *	     semaphore API to fail.				*
 *								*
\****************************************************************/
ULONG CreateAllSems(VOID)
{
   ULONG      rc;
   SEMRECORD   asr[MAXRESOURCES];
   INT	       i;

   rc = DosCreateMutexSem((PSZ)NULL,&hmtxOwnResource, 0L,FALSE);
   if (rc)
   {
       SemError("DosCreateMutexSem",rc);
       return(rc);
   }

   for (i = 0; i < MAXRESOURCES; i++)
   {
      rc = DosCreateEventSem( (PSZ)NULL, &aSquares[i].hev,
				      0L, FALSE);
      if (rc)
      {
	  SemError("DosCreateEventSem",rc);
	  return(rc);
      }
      else
      {
	  asr[i].ulUser = i;
	  asr[i].hsemCur = (PVOID) aSquares[i].hev;
      }
   }
			  /* this muxwait semaphore contains all of the
			  event semaphores created in the loop above. */
   rc = DosCreateMuxWaitSem((PSZ)NULL,&hmuxResource,
	   MAXRESOURCES,asr, DCMW_WAIT_ANY);
   if (rc)
   {
      SemError("DosCreateMuxWaitSem",rc);
      return(rc);
   }
   rc = DosCreateEventSem((PSZ)NULL,&hevStop,0L,FALSE);
   if (rc)
   {
       SemError("DosCreateEventSem",rc);
       return(rc);
   }
   return(rc);
}

/****************************************************************\
 * Routine to start semaphore example				*
 *--------------------------------------------------------------*
 *								*
 *  Name:     StartSemExample(VOID)				*
 *								*
 *  Purpose:  Calls routines to create semaphores and draw	*
 *	      resources.  Creates consumer threads.		*
 *								*
 *  Usage:    Called in file usercmd.c when the user selects	*
 *	      start from the semaphore menu.			*
 *								*
 *  Method:   Uses routines in paint.c to draw consumers and	*
 *	      resources.  This is done by creating a paint	*
 *	      message, not calling the draw routines directly.	*
 *								*
 *  Returns:  TRUE if start succeeds, FALSE if start fails	*
 *								*
\****************************************************************/
INT   StartSemExample(VOID)
{
    TID		tid=0;
    ULONG	rc;
    INT		i;
    FONTMETRICS fntmet;
    HPS		hps;
    SWP		swp;

    InitSemaphExample();

    rc = CreateAllSems();

    if (rc)
	return FALSE;
    /****************************************************************\
     * Create consumer threads. Note that values can be passed to   *
     * threads in OS/2 2.0. We pass the ordinal number of the child *
     * to each child.						    *
    \****************************************************************/

    for (usConsumerThreadsCreated = 0;
     usConsumerThreadsCreated < cNumUsers; usConsumerThreadsCreated++)
    {
	rc = DosCreateThread((PTID) &tid, (PFNTHREAD)ThreadConsumer,
	     (ULONG) usConsumerThreadsCreated, (ULONG)1,(ULONG) STACKSIZE);
	if (rc)
	{
	    SemError("DosCreateThread",rc);
	    return FALSE;
	}
	else
	{
	    thrConsumers[usConsumerThreadsCreated].tid = tid;
	    thrConsumers[usConsumerThreadsCreated].lHits = 0L;
	}
    }
    for (i = 0; i < (INT) cNumUsers; i++)
    {
	DosResumeThread (thrConsumers[i].tid);
    }
    hps = WinGetPS (hwndMain);
    if (hps)
    {
	GpiQueryFontMetrics (hps, (LONG) sizeof fntmet, &fntmet);
	WinQueryWindowPos (hwndMain, &swp);
	SetRectPositions((SHORT)swp.cx, (SHORT)swp.cy,
			 (SHORT) fntmet.lMaxBaselineExt,
			 (SHORT) fntmet.lMaxDescender);
	DrawRects (hps);
	WinReleasePS (hps);
    }
    return TRUE;
}

/****************************************************************\
 * Routine to signal consumer to release resource.		*
 *--------------------------------------------------------------*
 *								*
 *  Name:     SignalUserEvent(pfAutoMode)			*
 *								*
 *  Purpose:  Posts user event semaphore to signal thread to	*
 *	      release resource.	 Also posts event to stop	*
 *	      Auto mode if *pfAutoMode is true.			*
 *								*
 *  Usage:    Called in file usercmd.c when the user selects	*
 *	      Event from the semaphore menu.			*
 *								*
 *  Method:   Turns off Auto mode, if present by posting auto	*
 *	      semaphore.  User event is then posted.		*
 *								*
 *  Returns:							*
 *								*
\****************************************************************/
VOID SignalUserEvent(PULONG pfAutoMode)
{
  ULONG rc;

			  /* If sample is in auto mode turn auto mode off. */
  if (*pfAutoMode)
  {
      rc = DosPostEventSem(hevStopAuto);
      if (rc)
      {
	 SemError("DosPostEventSem Stop Auto",rc);
      }
		       /* Wait for auto mode thread to die, so we don't
			  end up with multiple copies of it later.    */

      rc = DosWaitThread(&tidAutoThread,0L);
      if (rc)
      {
	 SemError("DosWaitThread",rc);
      }
      *pfAutoMode = FALSE;
      DosCloseEventSem (hevStopAuto);
  }

  /* If Auto mode haas already posted the event this is OK
     so we will not check error codes here. */

  DosPostEventSem(aSquares[rand() % MAXRESOURCES].hev);
  return;
}

/****************************************************************\
 * Routine to start Auto mode.					*
 *--------------------------------------------------------------*
 *								*
 *  Name:     SetAutoMode(VOID)					*
 *								*
 *  Purpose:  Creates thread and semaphore needed to run auto	*
 *	      mode.						*
 *								*
 *  Usage:    Called in file usercmd.c when the user selects	*
 *	      Auto from the semaphore menu.			*
 *								*
 *  Returns:  NO_ERROR on success, else error from api call.	*
 *								*
\****************************************************************/
ULONG SetAutoMode()
{
    ULONG rc;

    rc = DosCreateEventSem((PSZ)NULL,&hevStopAuto,0L,FALSE);
    if (rc)
    {
	SemError("DosCreateEventSem",rc);
	return(rc);
    }

    rc = DosCreateThread((PTID)&tidAutoThread,(PFNTHREAD)RunAuto,0L,0L,STACKSIZE);
    if (rc)
    {
	DosCloseEventSem (hevStopAuto); /* close semaphore created above */
	SemError("DosCreateThread", rc);
	return(rc);
    }

    return(NO_ERROR);
}

/****************************************************************\
 * Routine to run Auto mode.					*
 *--------------------------------------------------------------*
 *								*
 *  Name:     RunAuto(ULONG ulArg)				*
 *								*
 *  Purpose:  Posts user event at fixed time interval to signal *
 *	      consumers to release resource.			*
 *								*
 *  Usage:    Thread created by SetAutoMode.			*
 *								*
 *  Method:   Kills itself when StopAutoMode semaphore is	*
 *	      posted.						*
 *								*
 *  Returns:							*
 *								*
\****************************************************************/
VOID __syscall RunAuto( ULONG ulArg )
{
   ULONG rcWait;
   HAB habLocal;
   HMQ hmqLocal;
   INT i;

   /* Need a message queue for any thread that wants to print messages. */

   habLocal = WinInitialize(0);
   hmqLocal = WinCreateMsgQueue(habLocal,0);

   /* while stop auto semaphore not posted, post user event semaphore. */

      /***************************************************************
       * Don't check return code from DosPostEventSem(hevUserEvent)  *
       * since we just want the resource to change hands.  We don't  *
       * care if it changes hands exactly every time it goes through *
       * the loop.						     *
       *							     *
       * The event may already be posted if the system is busy and   *
       * the resource threads don't finish with it fast enough.      *
       * This is not a problem in this case.			     *
       ***************************************************************/

   do {			     /* if ulTimeout is zero, still waitevent
				 for 1 msec to force yielding of CPU. */
       rcWait = DosWaitEventSem(hevStopAuto, max (ulTimeout, 1));
       if (rcWait == ERROR_TIMEOUT)
       {
	   i = rand () % MAXRESOURCES;	    /* generate it */
	   DosPostEventSem (aSquares[i].hev);
       }
   } while (rcWait == ERROR_TIMEOUT);

   /* If there was an error, print a message */

   if (rcWait)
   {
      SemError("DosWaitEventSem",rcWait);
   }

   WinDestroyMsgQueue (hmqLocal);
   WinTerminate (habLocal);
   DosExit(EXIT_THREAD,0);

		/* Since ulArg is not referenced anywhere we must use this
		   use this dummy reference to stop a compiler warning.	   */
   ++ulArg;
   return;
}

/****************************************************************\
 * Routine to stop semaphore example.				*
 *--------------------------------------------------------------*
 *								*
 *  Name:     BeginStop(pfAutoMode)				*
 *								*
 *  Purpose:  Posts stop event semaphore to signal threads to	*
 *	      die.  Also posts event to stop Auto mode if	*
 *	      necessary. Then waits for threads to complete,	*
 *	      Creates thread StopSemaphore which posts stop	*
 *	      event and waits for child threads.		*
 *								*
 *  Usage:    Called in file usercmd.c when the user selects	*
 *	      Stop from the semaphore menu.			*
 *								*
 *  Method:   Execs thread to do waits so that message thread	*
 *	      doesn't hang.                                     *
 *  Returns:							*
 *								*
\****************************************************************/
VOID BeginStop (PULONG pfAutoMode)
{
   ULONG rc;
   TID tidLocal;

   rc = DosPostEventSem(hevStop);
   if (rc)
   {
       SemError("DosPostEventSem",rc);
       return;
   }
   if (*pfAutoMode)
   {
       rc = DosPostEventSem(hevStopAuto);
       if (rc)
       {
	  SemError("DosPostEventSem",rc);
       }
   }
   rc = DosCreateThread((PTID)&tidLocal, (PFNTHREAD)StopSemaphore,
			  (ULONG)pfAutoMode,(ULONG)0,(ULONG) STACKSIZE);
   if (rc)
   {
       SemError("DosCreateThread",rc);
   }
   return;
}

/****************************************************************\
 * Routine to really stop semaphore example.			*
 *--------------------------------------------------------------*
 *								*
 *  Name:     StopSemaphore( ULONG ulArg )			*
 *								*
 *  Purpose:  Waits for threads to complete,			*
 *	      Sends message to message thread to indicate this	*
 *	      has occurred, and exits.				*
 *								*
 *  Usage:    Exec'd from BeginStop when user selects Stop from *
 *	      Semaphore menu.					*
 *								*
 *  Method:   Turns off Auto mode, if present by posting auto	*
 *	      semaphore.  Then stop event is posted.  Waits	*
 *	      for threads to die.				*
 *  Returns:							*
 *								*
\****************************************************************/
VOID __syscall StopSemaphore( ULONG ulArg )
{
   ULONG rc,usCount, i;
   PULONG pfAutoMode = (PULONG)ulArg;

   if (*pfAutoMode)
   {
      rc = DosWaitThread(&tidAutoThread,0L);
      if (rc && (rc != ERROR_INVALID_THREADID))
      {
	 SemError("DosWaitThread",rc);
      }
      *pfAutoMode = FALSE;
   }
   /* Wait for usConsumer threads to die.  Order of death not important. */

   for (usCount = 0; usCount < usConsumerThreadsCreated; usCount++)
   {
      rc = DosWaitThread(&thrConsumers[usCount].tid,0L);

      /* rc is ERROR_INVALID_THREADID the thread is already dead.*\
      \* This is OK and not a error.				 */

      if (rc && (rc != ERROR_INVALID_THREADID))
      {
	 SemError("DosWaitThread",rc);
      }
   }
		/* Threads dead so we don't need semaphores any more.  */
   DosCloseEventSem(hevStopAuto);
   DosCloseEventSem(hevStop);
   DosCloseMutexSem(hmtxOwnResource);
   for (i = 0; i < MAXRESOURCES; i++)
   {
      DosCloseEventSem(aSquares[i].hev);
   }
   DosCloseMuxWaitSem (hmuxResource);
   WinPostMsg (hwndMain, WM_COMMAND, (MPARAM)IDM_STOPFINISHED, (MPARAM)NULL);
   DosExit (EXIT_THREAD, 0);
   return;
}

/****************************************************************\
 * Routine for consumer threads.				*
 *--------------------------------------------------------------*
 *								*
 *  Name:     ThreadConsumer(ULONG)				*
 *								*
 *  Purpose:  There are NUMUSERS copies of this thread to act	*
 *	      as consumers of the resource. The thread waits for*
 *	      exclusive access to the resource and colors it.	*
 *								*
 *  Usage:    Threads created by StartSemExample.		*
 *								*
 *  Method:   Waits for mutex to gain ownership of resource.	*
 *	      Releases resource when user event. Dies when	*
 *	      Stop event.					*
 *  Returns:							*
 *								*
\****************************************************************/
VOID __syscall ThreadConsumer( ULONG ulMyID )
{
   ULONG  ulPostCnt;
   ULONG  ulUser=0L;
   ULONG  rc;
   HAB	  habb;
   HMQ	  hmqq;

   /* Need a message queue for any thread that wants to print messages. */
   habb = WinInitialize(0);
   hmqq = WinCreateMsgQueue(habb,0);

   /* while the user has not selected stop ... */
   while ((DosWaitEventSem(hevStop,SEM_IMMEDIATE_RETURN)) == ERROR_TIMEOUT)
   {
      /* Wait for exclusive ownership of resource */

      DosRequestMutexSem(hmtxOwnResource,SEM_INDEFINITE_WAIT);

      /* the following check is necessary because the stop semaphore
       * may have been posted  while we were waiting on the mutex
       */
      if (DosWaitEventSem(hevStop, SEM_IMMEDIATE_RETURN) == ERROR_TIMEOUT)
      {
      /*********************************************************************\
       * an item is ready, which one?  don't wait forever because there is *
       * a possibility that the stop semaphore was posted and that no more *
       * resource is forthcoming. we wait twice as long as we think is	   *
       * necessary.							   *
      \*********************************************************************/
	 if (!DosWaitMuxWaitSem (hmuxResource, max (ulTimeout << 1,
	    TIMEOUTPERIOD) , &ulUser))
	 {
	    MyMove (ulMyID, ulUser);
	    DosResetEventSem(aSquares[ulUser].hev, &ulPostCnt);
	 }
      }
			       /* Let some other thread have resource. */
      rc = DosReleaseMutexSem(hmtxOwnResource);
      if (rc)
      {
	 SemError("DosReleaseMutexSem",rc);
      }
   }
   /* hevStop was posted, kill this thread */
   WinDestroyMsgQueue (hmqq);
   WinTerminate (habb);
   DosExit(EXIT_THREAD, 0);
   return;
}

/****************************************************************\
 *  called from ThreadConsumer to make and register move	*
 *--------------------------------------------------------------*
 *								*
 *  Name: MyMove (usMyID, ulUser)				*
 *	  ULONG	    usMyID;	id of caller			*
 *	  ULONG	    ulUser;	number of the square to "hit"	*
 *								*
 *  Purpose: color a square on the game board 'my' color.	*
 *	     update display of number of hits for 'my' thread.	*
 *								*
 *  Usage: called only from ThreadConsumer			*
 *								*
 *  Method:							*
 *								*
 *  Returns: none						*
 *								*
\****************************************************************/
static VOID MyMove (ULONG usMyID, ULONG ulUser)
{
   if (aSquares [ulUser].usOwner != usMyID)
   {
      aSquares[ulUser].usOwner = usMyID;
      thrConsumers[usMyID].lHits++;
      DrawResource (&aSquares[ulUser].rcl, colors [usMyID]);
      DrawStats (usMyID);
   }
   return;
}

/****************************************************************\
 * Routine to init game board					*
 *--------------------------------------------------------------*
 *								*
 *  Name:     InitSemaphExample(VOID)				*
 *								*
 *  Purpose:  Initializes game board unowned, sets semaphores	*
 *	      to 0.						*
 *  Usage:    Called from StartSemExample in semaph.c when	*
 *	      user selects start from the semaphore menu.	*
 *  Method:							*
 *								*
 *  Returns:  none						*
 *								*
\****************************************************************/
VOID InitSemaphExample(VOID)
{
   int i;

   for (i = 0; i < MAXRESOURCES; i++)
   {
      aSquares[i].hev = 0;
      aSquares[i].usOwner = UNOWNED;
   }
   hevStopAuto = 0L;
   hevStop = 0L;
   hmtxOwnResource = 0L;
   hmuxResource	 = 0L;
   return;
}
