summaryrefslogtreecommitdiff
path: root/src/input.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
committerScott Gasch <[email protected]>2016-06-01 18:58:58 -0700
commit3fd43cd5fcb22bb65bf2a92a25d95d801b11c9e0 (patch)
tree9b6443235d16ba17f094a1a1c7ae53d2bcb9267b /src/input.c
Initial checkin for typhoon chess engine.
Diffstat (limited to 'src/input.c')
-rwxr-xr-xsrc/input.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/src/input.c b/src/input.c
new file mode 100755
index 0000000..9f829f7
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,515 @@
+/**
+
+Copyright (c) Scott Gasch
+
+Module Name:
+
+ input.c
+
+Abstract:
+
+ This module implements an input thread dedicated to watching for
+ user input on stdin and maintaining a queue of input events for
+ consumption by the main engine thread. Currently only the main
+ engine thread (and not helper threads) may consume user input
+ because: 1. I see no performance reason to make all threads able
+ to consume input and 2. it makes the locking assumptions easier.
+
+Author:
+
+ Scott Gasch ([email protected]) 14 May 2004
+
+Revision History:
+
+ $Id: input.c 345 2007-12-02 22:56:42Z scott $
+
+**/
+
+#include "chess.h"
+
+DLIST_ENTRY g_InputEventList;
+volatile static ULONG g_uInputLock;
+extern CHAR g_szInitialCommand[SMALL_STRING_LEN_CHAR];
+
+#define INPUT_IS_LOCKED (g_uInputLock != 0)
+#define LOCK_INPUT \
+ AcquireSpinLock(&g_uInputLock); \
+ ASSERT(INPUT_IS_LOCKED);
+#define UNLOCK_INPUT \
+ ASSERT(INPUT_IS_LOCKED); \
+ ReleaseSpinLock(&g_uInputLock);
+volatile FLAG g_fExitProgram = FALSE;
+volatile ULONG g_uNumInputEvents;
+
+typedef struct _INPUT_EVENT
+{
+ DLIST_ENTRY links;
+ CHAR *szInput;
+}
+INPUT_EVENT;
+
+ULONG g_uBlockingInputLock = 0;
+
+static void
+_WaitUntilTheresInputToRead(void)
+{
+ if (g_uBlockingInputLock != (ULONG)-1)
+ {
+ SystemObtainSemaphoreResource(g_uBlockingInputLock);
+ SystemReleaseSemaphoreResource(g_uBlockingInputLock);
+ }
+ else
+ {
+ SystemDeferExecution(100);
+ }
+}
+
+
+static void
+_UnblockBlockedReaders(void)
+{
+ if (g_uBlockingInputLock != (ULONG)-1)
+ {
+ SystemReleaseSemaphoreResource(g_uBlockingInputLock);
+ }
+}
+
+
+static void
+_BlockBlockingReaders(void)
+{
+ if (g_uBlockingInputLock != (ULONG)-1)
+ {
+ SystemObtainSemaphoreResource(g_uBlockingInputLock);
+ }
+}
+
+
+void
+PushNewInput(CHAR *buf)
+/**
+
+Routine description:
+
+ Allocate a new input event for some user input and push it onto
+ the queue.
+
+Parameters:
+
+ CHAR *buf
+
+Return value:
+
+ void
+
+**/
+{
+ INPUT_EVENT *pEvent;
+ FLAG fDone = FALSE;
+ FLAG fInQuote, fSemi;
+ CHAR *p;
+ CHAR *q;
+
+ do
+ {
+ pEvent = (INPUT_EVENT *)SystemAllocateMemory(sizeof(INPUT_EVENT));
+
+ //
+ // Note: you can specify more than one command per line using
+ // a semi-colon to separate them. If the semi-colon is quoted,
+ // though, ignore it.
+ //
+ fInQuote = FALSE;
+ fSemi = FALSE;
+ p = buf;
+ do
+ {
+ switch(*p)
+ {
+ case ';':
+ if (FALSE == fInQuote)
+ {
+ *p = '\0';
+ fSemi = TRUE;
+ }
+ else
+ {
+ p++;
+ }
+ break;
+ case '"':
+ fInQuote = FLIP(fInQuote);
+ p++;
+ break;
+ case '\0':
+ fDone = TRUE;
+ break;
+ default:
+ p++;
+ break;
+ }
+ }
+ while((FALSE == fDone) && (FALSE == fSemi));
+ pEvent->szInput = STRDUP(buf);
+
+ //
+ // Push it onto the queue
+ //
+ LOCK_INPUT;
+ InsertTailList(&g_InputEventList, &(pEvent->links));
+ g_uNumInputEvents++;
+ if (g_uNumInputEvents == 1)
+ {
+ _UnblockBlockedReaders();
+ }
+ q = pEvent->szInput;
+ ASSERT(q);
+ while(*q && isspace(*q)) q++;
+#ifdef DEBUG
+ Trace("INPUT THREAD SAW (event %u): %s",
+ g_uNumInputEvents,
+ pEvent->szInput);
+#endif
+ if (!STRNCMPI("quit", q, 4))
+ {
+ g_fExitProgram = TRUE;
+ UNLOCK_INPUT;
+ return;
+ }
+ UNLOCK_INPUT;
+
+ buf = (p + 1);
+ }
+ while(FALSE == fDone);
+}
+
+
+static void
+_InitInputSystemCommonCode(void)
+/**
+
+Routine description:
+
+ Common code for initializing the input system (called whether we
+ are spawing an input thread or running in batch mode with no input
+ thread).
+
+Parameters:
+
+ void
+
+Return value:
+
+ void
+
+**/
+{
+ setbuf(stdout, NULL);
+ setbuf(stdin, NULL);
+ setbuf(stderr, NULL);
+
+ g_uBlockingInputLock = SystemCreateSemaphore(1);
+ if (g_uBlockingInputLock == (ULONG)-1)
+ {
+ Bug("_InitInputSystemCommonCode: Failed to create input semaphore.\n");
+ }
+ g_uNumInputEvents = 0;
+ _BlockBlockingReaders();
+ InitializeListHead(&g_InputEventList);
+ g_uInputLock = 0;
+ if (g_szInitialCommand[0] != '\0')
+ {
+ Trace("INPUT SYSTEM INIT: Pushing \"%s\"\n", g_szInitialCommand);
+ PushNewInput(g_szInitialCommand);
+ g_szInitialCommand[0] = '\0';
+ }
+}
+
+
+
+void
+InitInputSystemInBatchMode(void)
+/**
+
+Routine description:
+
+ User specified --batch on the cmdline... so don't spawn an input
+ thread. Just process the initial command.
+
+Parameters:
+
+ void
+
+Return value:
+
+ void
+
+**/
+{
+ if (g_szInitialCommand[0] == '\0')
+ {
+ UtilPanic(INCONSISTENT_STATE,
+ NULL, "Batch mode specified with no initial command",
+ NULL, NULL,
+ __FILE__, __LINE__);
+ }
+ _InitInputSystemCommonCode();
+}
+
+
+#ifdef USE_READLINE
+char *readline(const char *prompt);
+void add_history(const char *line);
+#endif
+
+ULONG
+InputThreadEntry(ULONG uUnused)
+/**
+
+Routine description:
+
+ The entry point of the input thread.
+
+Parameters:
+
+ ULONG uUnused
+
+Return value:
+
+ ULONG
+
+**/
+{
+ static CHAR buf[SMALL_STRING_LEN_CHAR];
+ INPUT_EVENT *pEvent;
+ DLIST_ENTRY *p;
+ FLAG fFailure;
+#ifdef USE_READLINE
+ CHAR *pReadline = NULL;
+#endif
+
+ while(TRUE != g_fExitProgram)
+ {
+ //
+ // Get another input
+ //
+ fFailure = FALSE;
+#ifdef USE_READLINE
+ memset(buf, 0, sizeof(buf));
+ pReadline = readline(NULL);
+ if (pReadline != NULL)
+ {
+ strncpy(buf, pReadline, sizeof(buf) - 1);
+ add_history(pReadline);
+ free(pReadline);
+ }
+ else
+ {
+ fFailure = TRUE;
+ }
+#else
+ if (NULL == fgets(buf, sizeof(buf), stdin))
+ {
+ fFailure = TRUE;
+ }
+#endif
+ if (fFailure == TRUE)
+ {
+ Trace("INPUT THREAD: got end-of-file on stdin, exiting.\n");
+ break;
+ }
+
+ //
+ // And push it onto the queue
+ //
+ PushNewInput(buf);
+ }
+ Trace("INPUT THREAD: thread terminating.\n");
+
+ //
+ // Free the queue
+ //
+ while(!IsListEmpty(&g_InputEventList))
+ {
+ p = RemoveHeadList(&g_InputEventList);
+ pEvent = CONTAINING_STRUCT(p, INPUT_EVENT, links);
+ SystemFreeMemory(pEvent->szInput);
+ SystemFreeMemory(pEvent);
+ }
+ g_uNumInputEvents = (ULONG)-1;
+ return(0);
+}
+
+
+ULONG
+InitInputSystemWithDedicatedThread(void)
+/**
+
+Routine description:
+
+ Initialize the input system with a dedicated input thread.
+
+Parameters:
+
+ void
+
+Return value:
+
+ ULONG
+
+**/
+{
+ ULONG uHandle = -1;
+
+ _InitInputSystemCommonCode();
+ if (FALSE == SystemCreateThread(InputThreadEntry,
+ 0,
+ &uHandle))
+ {
+ Bug("Failed to start input thread!\n");
+ uHandle = -1;
+ }
+ return(uHandle);
+}
+
+
+CHAR *
+PeekNextInput(void)
+/**
+
+Routine description:
+
+ Peek at what the next input event will be without consuming it.
+ Because only one thread can be consuming events from the queue at
+ a time (presumably the same thread that called this Peek function)
+ it should be safe to do this without a lock. But the overhead is
+ low so I lock here anyway.
+
+Parameters:
+
+ void
+
+Return value:
+
+ CHAR *
+
+**/
+{
+ INPUT_EVENT *p;
+ char *q = NULL;
+
+ LOCK_INPUT;
+ if (!IsListEmpty(&g_InputEventList))
+ {
+ p = CONTAINING_STRUCT(g_InputEventList.pFlink, INPUT_EVENT, links);
+ q = p->szInput;
+ }
+ UNLOCK_INPUT;
+ return(q);
+}
+
+
+CHAR *
+ReadNextInput(void)
+/**
+
+Routine description:
+
+ Read the next input (and dequeue it).
+
+Parameters:
+
+ void
+
+Return value:
+
+ CHAR *
+
+**/
+{
+ CHAR *pRet = NULL;
+ INPUT_EVENT *p;
+ DLIST_ENTRY *q;
+
+ LOCK_INPUT;
+ if (!IsListEmpty(&g_InputEventList))
+ {
+ q = RemoveHeadList(&g_InputEventList);
+ p = CONTAINING_STRUCT(q, INPUT_EVENT, links);
+ g_uNumInputEvents--;
+ if (g_uNumInputEvents == 0)
+ {
+ _BlockBlockingReaders();
+ }
+ pRet = p->szInput;
+ SystemFreeMemory(p);
+ }
+ UNLOCK_INPUT;
+ return(pRet);
+}
+
+
+CHAR *
+BlockingReadInput(void)
+/**
+
+Routine description:
+
+ Block waiting on the next input read.
+
+Parameters:
+
+ void
+
+Return value:
+
+ CHAR
+
+**/
+{
+ CHAR *pCh = NULL;
+
+ do
+ {
+ _WaitUntilTheresInputToRead();
+ if (TRUE == g_fExitProgram)
+ {
+ if (-1 != g_uBlockingInputLock)
+ {
+ SystemDeleteSemaphore(g_uBlockingInputLock);
+ }
+ return(NULL);
+ }
+ pCh = ReadNextInput();
+ if (NULL != pCh)
+ {
+ if (strlen(pCh) > 0) break;
+ SystemFreeMemory(pCh);
+ }
+ }
+ while(1);
+ return(pCh);
+}
+
+ULONG
+NumberOfPendingInputEvents(void)
+/**
+
+Routine description:
+
+ How many input events are pending on the queue. Note: the value
+ returned is obviously potentially out of date the instant it gets
+ sent back to the caller...
+
+Parameters:
+
+ void
+
+Return value:
+
+ ULONG
+
+**/
+{
+ return(g_uNumInputEvents);
+}