Update codebase to remove clang warnings (and a couple of legit errors
[typhoon.git] / src / input.c
1 /**
2
3 Copyright (c) Scott Gasch
4
5 Module Name:
6
7     input.c
8
9 Abstract:
10
11     This module implements an input thread dedicated to watching for
12     user input on stdin and maintaining a queue of input events for
13     consumption by the main engine thread.  Currently only the main
14     engine thread (and not helper threads) may consume user input
15     because: 1. I see no performance reason to make all threads able
16     to consume input and 2. it makes the locking assumptions easier.
17
18 Author:
19
20     Scott Gasch ([email protected]) 14 May 2004
21
22 Revision History:
23
24     $Id: input.c 345 2007-12-02 22:56:42Z scott $
25
26 **/
27
28 #include "chess.h"
29
30 DLIST_ENTRY g_InputEventList;
31 volatile static ULONG g_uInputLock;
32 extern CHAR g_szInitialCommand[SMALL_STRING_LEN_CHAR];
33
34 #define INPUT_IS_LOCKED (g_uInputLock != 0)
35 #define LOCK_INPUT \
36     AcquireSpinLock(&g_uInputLock); \
37     ASSERT(INPUT_IS_LOCKED);
38 #define UNLOCK_INPUT \
39     ASSERT(INPUT_IS_LOCKED); \
40     ReleaseSpinLock(&g_uInputLock); 
41 volatile FLAG g_fExitProgram = FALSE;
42 volatile ULONG g_uNumInputEvents;
43
44 typedef struct _INPUT_EVENT
45 {
46     DLIST_ENTRY links;
47     CHAR *szInput;
48 }
49 INPUT_EVENT;
50
51 ULONG g_uBlockingInputLock = 0;
52
53 static void 
54 _WaitUntilTheresInputToRead(void) 
55 {
56     if (g_uBlockingInputLock != (ULONG)-1) 
57     {
58         SystemObtainSemaphoreResource(g_uBlockingInputLock);
59         SystemReleaseSemaphoreResource(g_uBlockingInputLock);
60     }
61     else 
62     {
63         SystemDeferExecution(100);
64     }
65 }
66
67
68 static void 
69 _UnblockBlockedReaders(void) 
70 {
71     if (g_uBlockingInputLock != (ULONG)-1)
72     {
73         SystemReleaseSemaphoreResource(g_uBlockingInputLock);
74     }
75 }
76
77
78 static void 
79 _BlockBlockingReaders(void) 
80 {
81     if (g_uBlockingInputLock != (ULONG)-1)
82     {
83         SystemObtainSemaphoreResource(g_uBlockingInputLock);
84     }
85 }
86
87
88 void 
89 PushNewInput(CHAR *buf)
90 /**
91
92 Routine description:
93
94     Allocate a new input event for some user input and push it onto
95     the queue.
96
97 Parameters:
98
99     CHAR *buf
100
101 Return value:
102
103     void
104
105 **/
106 {
107     INPUT_EVENT *pEvent;
108     FLAG fDone = FALSE;
109     FLAG fInQuote, fSemi;
110     CHAR *p;
111     CHAR *q;
112     
113     do
114     {
115         pEvent = (INPUT_EVENT *)SystemAllocateMemory(sizeof(INPUT_EVENT));
116         
117         // 
118         // Note: you can specify more than one command per line using
119         // a semi-colon to separate them.  If the semi-colon is quoted,
120         // though, ignore it.
121         //
122         fInQuote = FALSE;
123         fSemi = FALSE;
124         p = buf;
125         do
126         {
127             switch(*p)
128             {
129                 case ';':
130                     if (FALSE == fInQuote)
131                     {
132                         *p = '\0';
133                         fSemi = TRUE;
134                     }
135                     else 
136                     {
137                         p++;
138                     }
139                     break;
140                 case '"':
141                     fInQuote = FLIP(fInQuote);
142                     p++;
143                     break;
144                 case '\0':
145                     fDone = TRUE;
146                     break;
147                 default:
148                     p++;
149                     break;
150             }
151         }
152         while((FALSE == fDone) && (FALSE == fSemi));
153         pEvent->szInput = STRDUP(buf);
154         
155         //
156         // Push it onto the queue
157         //
158         LOCK_INPUT;
159         InsertTailList(&g_InputEventList, &(pEvent->links));
160         g_uNumInputEvents++;
161         if (g_uNumInputEvents == 1) 
162         {
163             _UnblockBlockedReaders();
164         }
165         q = pEvent->szInput;
166         ASSERT(q);
167         while(*q && isspace(*q)) q++;
168 #ifdef DEBUG
169         Trace("INPUT THREAD SAW (event %u): %s", 
170               g_uNumInputEvents, 
171               pEvent->szInput);
172 #endif
173         if (!STRNCMPI("quit", q, 4)) 
174         {
175             g_fExitProgram = TRUE;
176             UNLOCK_INPUT;
177             return;
178         }
179         UNLOCK_INPUT;
180
181         buf = (p + 1);
182     }
183     while(FALSE == fDone);
184 }
185
186
187 static void 
188 _InitInputSystemCommonCode(void)
189 /**
190
191 Routine description:
192
193     Common code for initializing the input system (called whether we
194     are spawing an input thread or running in batch mode with no input
195     thread).
196
197 Parameters:
198
199     void
200
201 Return value:
202
203     void
204
205 **/
206 {
207     setbuf(stdout, NULL);
208     setbuf(stdin, NULL);
209     setbuf(stderr, NULL);
210     
211     g_uBlockingInputLock = SystemCreateSemaphore(1);
212     if (g_uBlockingInputLock == (ULONG)-1) 
213     {
214         Bug("_InitInputSystemCommonCode: Failed to create input semaphore.\n");
215     }
216     g_uNumInputEvents = 0;
217     _BlockBlockingReaders();
218     InitializeListHead(&g_InputEventList);
219     g_uInputLock = 0;
220     if (g_szInitialCommand[0] != '\0')
221     {
222         Trace("INPUT SYSTEM INIT: Pushing \"%s\"\n", g_szInitialCommand);
223         PushNewInput(g_szInitialCommand);
224         g_szInitialCommand[0] = '\0';
225     }
226 }
227
228
229
230 void 
231 InitInputSystemInBatchMode(void)
232 /**
233
234 Routine description:
235
236     User specified --batch on the cmdline... so don't spawn an input
237     thread.  Just process the initial command.
238
239 Parameters:
240
241     void
242
243 Return value:
244
245     void
246
247 **/
248 {
249     if (g_szInitialCommand[0] == '\0')
250     {
251         UtilPanic(INCONSISTENT_STATE, 
252                   NULL, "Batch mode specified with no initial command",
253                   NULL, NULL, 
254                   __FILE__, __LINE__);
255     }
256     _InitInputSystemCommonCode();
257 }
258     
259
260 #ifdef USE_READLINE
261 char *readline(const char *prompt);
262 void add_history(const char *line);
263 #endif
264
265 ULONG 
266 InputThreadEntry(ULONG uUnused)
267 /**
268
269 Routine description:
270
271     The entry point of the input thread.
272
273 Parameters:
274
275     ULONG uUnused
276
277 Return value:
278
279     ULONG
280
281 **/
282 {
283     static CHAR buf[SMALL_STRING_LEN_CHAR];
284     INPUT_EVENT *pEvent;
285     DLIST_ENTRY *p;
286     FLAG fFailure;
287 #ifdef USE_READLINE
288     CHAR *pReadline = NULL;
289 #endif
290
291     while(TRUE != g_fExitProgram)
292     {
293         //
294         // Get another input
295         //
296         fFailure = FALSE;
297 #ifdef USE_READLINE
298         memset(buf, 0, sizeof(buf));
299         pReadline = readline(NULL);
300         if (pReadline != NULL) 
301         {
302             strncpy(buf, pReadline, sizeof(buf) - 1);
303             add_history(pReadline);
304             free(pReadline);
305         }
306         else
307         {
308             fFailure = TRUE;
309         }
310 #else
311         if (NULL == fgets(buf, sizeof(buf), stdin))
312         {
313             fFailure = TRUE;
314         }
315 #endif
316         if (fFailure == TRUE) 
317         {
318             Trace("INPUT THREAD: got end-of-file on stdin, exiting.\n");
319             break;
320         }
321
322         //
323         // And push it onto the queue
324         //
325         PushNewInput(buf);
326     }
327     Trace("INPUT THREAD: thread terminating.\n");
328     
329     //
330     // Free the queue
331     //
332     while(!IsListEmpty(&g_InputEventList))
333     {
334         p = RemoveHeadList(&g_InputEventList);
335         pEvent = CONTAINING_STRUCT(p, INPUT_EVENT, links);
336         SystemFreeMemory(pEvent->szInput);
337         SystemFreeMemory(pEvent);
338     }
339     g_uNumInputEvents = (ULONG)-1;
340     return(0);
341 }
342
343
344 ULONG 
345 InitInputSystemWithDedicatedThread(void)
346 /**
347
348 Routine description:
349
350     Initialize the input system with a dedicated input thread.
351
352 Parameters:
353
354     void
355
356 Return value:
357
358     ULONG
359
360 **/
361 {
362     ULONG uHandle = -1;
363
364     _InitInputSystemCommonCode();
365     if (FALSE == SystemCreateThread(InputThreadEntry, 
366                                     0, 
367                                     &uHandle))
368     {
369         Bug("Failed to start input thread!\n");
370         uHandle = -1;
371     }
372     return(uHandle);
373 }
374
375
376 CHAR *
377 PeekNextInput(void)
378 /**
379
380 Routine description:
381
382     Peek at what the next input event will be without consuming it.
383     Because only one thread can be consuming events from the queue at
384     a time (presumably the same thread that called this Peek function)
385     it should be safe to do this without a lock.  But the overhead is
386     low so I lock here anyway.
387
388 Parameters:
389
390     void
391
392 Return value:
393
394     CHAR *
395
396 **/
397 {
398     INPUT_EVENT *p;
399     char *q = NULL;
400
401     LOCK_INPUT;
402     if (!IsListEmpty(&g_InputEventList))
403     {
404         p = CONTAINING_STRUCT(g_InputEventList.pFlink, INPUT_EVENT, links);
405         q = p->szInput;
406     }
407     UNLOCK_INPUT;
408     return(q);
409 }
410
411
412 CHAR *
413 ReadNextInput(void)
414 /**
415
416 Routine description:
417
418     Read the next input (and dequeue it).
419
420 Parameters:
421
422     void
423
424 Return value:
425
426     CHAR *
427
428 **/
429 {
430     CHAR *pRet = NULL;
431     INPUT_EVENT *p;
432     DLIST_ENTRY *q;
433
434     LOCK_INPUT;
435     if (!IsListEmpty(&g_InputEventList))
436     {
437         q = RemoveHeadList(&g_InputEventList);
438         p = CONTAINING_STRUCT(q, INPUT_EVENT, links);
439         g_uNumInputEvents--;
440         if (g_uNumInputEvents == 0)
441         {
442             _BlockBlockingReaders();
443         }
444         pRet = p->szInput;
445         SystemFreeMemory(p);
446     }
447     UNLOCK_INPUT;
448     return(pRet);
449 }
450
451
452 CHAR *
453 BlockingReadInput(void)
454 /**
455
456 Routine description:
457
458     Block waiting on the next input read.
459
460 Parameters:
461
462     void
463
464 Return value:
465
466     CHAR
467
468 **/
469 {
470     CHAR *pCh = NULL;
471
472     do
473     {
474         _WaitUntilTheresInputToRead();
475         if (TRUE == g_fExitProgram) 
476         {
477             if (-1 != g_uBlockingInputLock) 
478             {
479                 SystemDeleteSemaphore(g_uBlockingInputLock);
480             }
481             return(NULL);
482         }
483         pCh = ReadNextInput();
484         if (NULL != pCh)
485         {
486             if (strlen(pCh) > 0) break;
487             SystemFreeMemory(pCh);
488         }
489     }
490     while(1);
491     return(pCh);
492 }
493
494 ULONG 
495 NumberOfPendingInputEvents(void)
496 /**
497
498 Routine description:
499
500     How many input events are pending on the queue.  Note: the value
501     returned is obviously potentially out of date the instant it gets
502     sent back to the caller...
503
504 Parameters:
505
506     void
507
508 Return value:
509
510     ULONG
511
512 **/
513 {
514     return(g_uNumInputEvents);
515 }