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