Use init and clear for GMutex and GCond
[projects/chimara/chimara.git] / libchimara / event.c
1 #include "event.h"
2 #include "magic.h"
3 #include "glk.h"
4 #include "window.h"
5 #include "input.h"
6 #include <string.h>
7
8 #include "chimara-glk.h"
9 #include "chimara-glk-private.h"
10
11 extern GPrivate *glk_data_key;
12
13 #define EVENT_TIMEOUT_MICROSECONDS (3000000)
14
15 /* Internal function: push an event onto the event queue. If the event queue is
16 full, wait for max three seconds and then drop the event. If the event queue is
17 NULL, i.e. freed, then fail silently. */
18 void
19 event_throw(ChimaraGlk *glk, glui32 type, winid_t win, glui32 val1, glui32 val2)
20 {
21         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
22         
23         if(!priv->event_queue)
24                 return;
25
26         GTimeVal timeout;
27         g_get_current_time(&timeout);
28         g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS);
29
30         g_mutex_lock(&priv->event_lock);
31
32         /* Wait for room in the event queue */
33         while( g_queue_get_length(priv->event_queue) >= EVENT_QUEUE_MAX_LENGTH )
34                 if( !g_cond_timed_wait(&priv->event_queue_not_full, &priv->event_lock, &timeout) ) 
35                 {
36                         /* Drop the event after 3 seconds */
37                         g_mutex_unlock(&priv->event_lock);
38                         return;
39                 }
40
41         event_t *event = g_new0(event_t, 1);
42         event->type = type;
43         event->win = win;
44         event->val1 = val1;
45         event->val2 = val2;
46         g_queue_push_head(priv->event_queue, event);
47
48         /* Signal that there is an event */
49         g_cond_signal(&priv->event_queue_not_empty);
50
51         g_mutex_unlock(&priv->event_lock);
52 }
53
54 /* Helper function: Wait for an event in the event queue. If it is a forced
55  * input event, but no windows have an input request of that type, then wait
56  * for the next event and put the forced input event back on top of the queue.
57  */
58 static void
59 get_appropriate_event(event_t *event)
60 {
61         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
62
63         g_mutex_lock(&glk_data->event_lock);
64
65         event_t *retrieved_event = NULL;
66
67         /* Wait for an event */
68         if( g_queue_is_empty(glk_data->event_queue) )
69                 g_cond_wait(&glk_data->event_queue_not_empty, &glk_data->event_lock);
70
71         retrieved_event = g_queue_pop_tail(glk_data->event_queue);
72
73         /* Signal that the event queue is no longer full */
74         g_cond_signal(&glk_data->event_queue_not_full);
75
76         g_mutex_unlock(&glk_data->event_lock);
77
78         if(retrieved_event->type == evtype_ForcedCharInput)
79         {
80                 /* Check for forced character input in the queue */
81                 winid_t win;
82                 for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL))
83                         if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
84                                 break;
85                 if(win)
86                 {
87                         force_char_input_from_queue(win, event);
88                         g_free(retrieved_event);
89                 }
90                 else
91                 {
92                         get_appropriate_event(event);
93                         g_mutex_lock(&glk_data->event_lock);
94                         g_queue_push_tail(glk_data->event_queue, retrieved_event);
95                         g_cond_signal(&glk_data->event_queue_not_empty);
96                         g_mutex_unlock(&glk_data->event_lock);
97                 }
98         }
99         else if(retrieved_event->type == evtype_ForcedLineInput)
100         {
101                 /* Check for forced line input in the queue */
102                 winid_t win;
103                 for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL))
104                         if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
105                                 break;
106                 if(win)
107                 {
108                         force_line_input_from_queue(win, event);
109                         g_free(retrieved_event);
110                 }
111                 else
112                 {
113                         get_appropriate_event(event);
114                         g_mutex_lock(&glk_data->event_lock);
115                         g_queue_push_tail(glk_data->event_queue, retrieved_event);
116                         g_cond_signal(&glk_data->event_queue_not_empty);
117                         g_mutex_unlock(&glk_data->event_lock);
118                 }
119         }
120         else
121         {
122                 if(retrieved_event == NULL)
123                 {
124                         WARNING("Retrieved NULL event from non-empty event queue");
125                         return;
126                 }
127                 memcpy(event, retrieved_event, sizeof(event_t));
128                 g_free(retrieved_event);
129         }
130 }
131
132 /**
133  * glk_select:
134  * @event: Pointer to an #event_t.
135  *
136  * Causes the program to wait for an event, and then store it in the structure
137  * pointed to by @event. Unlike most Glk functions that take pointers, the
138  * argument of glk_select() may not be %NULL.
139  *
140  * Most of the time, you only get the events that you request. However, there
141  * are some events which can arrive at any time. This is why you must always
142  * call glk_select() in a loop, and continue the loop until you get the event
143  * you really want.
144  */
145 void
146 glk_select(event_t *event)
147 {
148         g_return_if_fail(event != NULL);
149
150         /* Flush all window buffers */
151         winid_t win;
152         for(win = glk_window_iterate(NULL, NULL); win != NULL; win = glk_window_iterate(win, NULL)) {
153                 if(win->type == wintype_TextBuffer || win->type == wintype_TextGrid)
154                         flush_window_buffer(win);
155         }
156
157         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
158
159         get_appropriate_event(event);
160
161         /* Check for interrupt */
162         glk_tick();
163
164         /* If the event was a line input event, the library must release the buffer */
165         if(event->type == evtype_LineInput && glk_data->unregister_arr) 
166         {
167         if(event->win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
168                         (*glk_data->unregister_arr)(event->win->line_input_buffer_unicode, event->win->line_input_buffer_max_len, "&+#!Iu", event->win->buffer_rock);
169                 else
170             (*glk_data->unregister_arr)(event->win->line_input_buffer, event->win->line_input_buffer_max_len, "&+#!Cn", event->win->buffer_rock);
171     }
172         
173         /* If an abort event was generated, the thread should have exited by now */
174         g_assert(event->type != evtype_Abort);
175 }
176
177 /**
178  * glk_select_poll:
179  * @event: Return location for an event.
180  *
181  * You can also inquire if an event is available, without stopping to wait for 
182  * one to occur.
183  * 
184  * This checks if an internally-spawned event is available. If so, it stores it 
185  * in the structure pointed to by @event. If not, it sets
186  * <code>@event->type</code> to %evtype_None. Either way, it returns almost
187  * immediately.
188  * 
189  * The first question you now ask is, what is an internally-spawned event?
190  * glk_select_poll() does not check for or return %evtype_CharInput,
191  * %evtype_LineInput, %evtype_MouseInput, or %evtype_Hyperlink events. It is
192  * intended for you to test conditions which may have occurred while you are
193  * computing, and not interfacing with the player. For example, time may pass
194  * during slow computations; you can use glk_select_poll() to see if a 
195  * %evtype_Timer event has occurred. (See <link 
196  * linkend="chimara-Timer-Events">Timer Events</link>.)
197  * 
198  * At the moment, glk_select_poll() checks for %evtype_Timer, %evtype_Arrange,
199  * %evtype_Redraw and %evtype_SoundNotify events. But see <link 
200  * linkend="chimara-Other-Events">Other Events</link>.
201  * 
202  * The second question is, what does it mean that glk_select_poll() returns 
203  * <quote>almost immediately</quote>? In some Glk libraries, text that you send 
204  * to a window is buffered; it does not actually appear until you request player
205  * input with glk_select(). glk_select_poll() attends to this buffer-flushing 
206  * task in the same way. (Although it does not do the <quote><computeroutput>Hit
207  * any key to scroll down</computeroutput></quote> waiting which may be done in
208  * glk_select(); that's a player-input task.)
209  * 
210  * Similarly, on multitasking platforms, glk_select() may yield time to other
211  * processes; and glk_select_poll() does this as well.
212  * 
213  * The upshot of this is that you should not call glk_select_poll() very often. 
214  * If you are not doing much work between player inputs, you should not need to
215  * call it at all.
216  *
217  * <note><para>
218  *  For example, in a virtual machine interpreter, you should not call
219  *  glk_select_poll() after every opcode.
220  * </para></note>
221  * 
222  * However, if you are doing intense computation, you may wish to call
223  * glk_select_poll() every so often to yield time to other processes. And if you
224  * are printing intermediate results during this computation, you should
225  * glk_select_poll() every so often, so that you can be certain your output will 
226  * be displayed before the next glk_select().
227  * 
228  * <note><para>
229  *  However, you should call glk_tick() often &mdash; once per opcode in a VM
230  *  interpreter. See <link linkend="chimara-The-Tick-Thing">The Tick 
231  *  Thing</link>.
232  * </para></note>
233  */
234 void
235 glk_select_poll(event_t *event)
236 {
237         g_return_if_fail(event != NULL);
238
239         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
240         
241         event->type = evtype_None;
242         event->win = NULL;
243         event->val1 = 0;
244         event->val2 = 0;
245
246         g_mutex_lock(&glk_data->event_lock);
247
248         if( !g_queue_is_empty(glk_data->event_queue) )
249         {
250                 GList *link;
251                 int count;
252                 for(count = 0; (link = g_queue_peek_nth_link(glk_data->event_queue, count)) != NULL; count++)
253                 {
254                         glui32 type = ((event_t *)link->data)->type;
255                         if(type != evtype_CharInput && type != evtype_LineInput && type != evtype_MouseInput && type != evtype_Hyperlink)
256                         {
257                                 memcpy(event, link->data, sizeof(event_t));
258                                 g_free(link->data);
259                                 g_queue_delete_link(glk_data->event_queue, link);
260                                 g_cond_signal(&glk_data->event_queue_not_full);
261                                 break;
262                         }
263                 }
264         }
265
266         g_mutex_unlock(&glk_data->event_lock);
267
268         /* Check for interrupt */
269         glk_tick();
270
271         /* If an abort event was generated, the thread should have exited by now */
272         g_assert(event->type != evtype_Abort);
273 }