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