276206361491abf64ec88e09ea34b9f88945256f
[rodin/chimara.git] / src / event.c
1 #include "event.h"
2 #include "glk.h"
3 #include <string.h>
4
5 #define EVENT_TIMEOUT_MICROSECONDS (3000000)
6
7 static GQueue *event_queue = NULL;
8 static GMutex *event_lock = NULL;
9 static GCond *event_queue_not_empty = NULL;
10 static GCond *event_queue_not_full = NULL;
11
12 /* Internal function: initialize the event_queue, locking and signalling 
13 objects. */
14 void
15 events_init()
16 {
17         event_queue = g_queue_new();
18         event_lock = g_mutex_new();
19         event_queue_not_empty = g_cond_new();
20         event_queue_not_full = g_cond_new();
21 }
22
23 /* Internal function: free the event queue and all the objects allocated in
24 events_init(). */
25 void
26 events_free()
27 {
28         g_mutex_lock(event_lock);
29         g_queue_foreach(event_queue, (GFunc)g_free, NULL);
30         g_queue_free(event_queue);
31         g_cond_free(event_queue_not_empty);
32         g_cond_free(event_queue_not_full);
33         event_queue = NULL;
34         g_mutex_unlock(event_lock);
35         g_mutex_free(event_lock);
36 }
37
38 /* Internal function: push an event onto the event queue. If the event queue is
39 full, wait for max three seconds and then drop the event. If the event queue is
40 NULL, i.e. freed, then fail silently. */
41 void
42 event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2)
43 {
44         if(!event_queue)
45                 return;
46
47         GTimeVal timeout;
48         g_get_current_time(&timeout);
49         g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS);
50
51         g_mutex_lock(event_lock);
52
53         /* Wait for room in the event queue */
54         while( g_queue_get_length(event_queue) >= EVENT_QUEUE_MAX_LENGTH )
55                 if( !g_cond_timed_wait(event_queue_not_full, event_lock, &timeout) ) 
56                 {
57                         /* Drop the event after 3 seconds */
58                         g_mutex_unlock(event_lock);
59                         return;
60                 }
61
62         event_t *event = g_new0(event_t, 1);
63         event->type = type;
64         event->win = win;
65         event->val1 = val1;
66         event->val2 = val2;
67         g_queue_push_head(event_queue, event);
68
69         /* Signal that there is an event */
70         g_cond_signal(event_queue_not_empty);
71
72         g_mutex_unlock(event_lock);
73 }
74
75 /**
76  * glk_select:
77  * @event: Pointer to an #event_t.
78  *
79  * Causes the program to wait for an event, and then store it in the structure
80  * pointed to by @event. Unlike most Glk functions that take pointers, the
81  * argument of glk_select() may not be %NULL.
82  *
83  * Most of the time, you only get the events that you request. However, there
84  * are some events which can arrive at any time. This is why you must always
85  * call glk_select() in a loop, and continue the loop until you get the event
86  * you really want.
87  */
88 void
89 glk_select(event_t *event)
90 {
91         g_return_if_fail(event != NULL);
92
93         g_mutex_lock(event_lock);
94
95         /* Wait for an event */
96         while( g_queue_is_empty(event_queue) )
97                 g_cond_wait(event_queue_not_empty, event_lock);
98
99         event_t *retrieved_event = g_queue_pop_tail(event_queue);
100         if(retrieved_event == NULL)
101         {
102                 g_mutex_unlock(event_lock);
103                 g_warning("%s: Retrieved NULL event from non-empty event queue", __func__);
104                 return;
105         }
106         memcpy(event, retrieved_event, sizeof(event_t));
107         g_free(retrieved_event);
108
109         /* Signal that the event queue is no longer full */
110         g_cond_signal(event_queue_not_full);
111
112         g_mutex_unlock(event_lock);
113         
114         /* Check for interrupt */
115         glk_tick();
116         
117         /* If an abort event was generated, the thread should have exited by now */
118         g_assert(event->type != evtype_Abort);
119 }