X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=libchimara%2Fevent.c;h=366c18929905840e06345a8957d422c6f9702462;hb=b1a03904003733d21bf579e37316cc2906eb7e49;hp=d7cb6596cbdd61611536bfe981c5116351b750a5;hpb=0b85f1dd5993e2ed111ec2ba13bbbb4ebda06ada;p=rodin%2Fchimara.git diff --git a/libchimara/event.c b/libchimara/event.c index d7cb659..366c189 100644 --- a/libchimara/event.c +++ b/libchimara/event.c @@ -1,11 +1,14 @@ #include "event.h" #include "magic.h" #include "glk.h" +#include "window.h" +#include "input.h" #include +#include "chimara-glk.h" #include "chimara-glk-private.h" -extern ChimaraGlkPrivate *glk_data; +extern GPrivate *glk_data_key; #define EVENT_TIMEOUT_MICROSECONDS (3000000) @@ -13,23 +16,25 @@ extern ChimaraGlkPrivate *glk_data; full, wait for max three seconds and then drop the event. If the event queue is NULL, i.e. freed, then fail silently. */ void -event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2) +event_throw(ChimaraGlk *glk, glui32 type, winid_t win, glui32 val1, glui32 val2) { - if(!glk_data->event_queue) + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + + if(!priv->event_queue) return; GTimeVal timeout; g_get_current_time(&timeout); g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS); - g_mutex_lock(glk_data->event_lock); + g_mutex_lock(priv->event_lock); /* Wait for room in the event queue */ - while( g_queue_get_length(glk_data->event_queue) >= EVENT_QUEUE_MAX_LENGTH ) - if( !g_cond_timed_wait(glk_data->event_queue_not_full, glk_data->event_lock, &timeout) ) + while( g_queue_get_length(priv->event_queue) >= EVENT_QUEUE_MAX_LENGTH ) + if( !g_cond_timed_wait(priv->event_queue_not_full, priv->event_lock, &timeout) ) { /* Drop the event after 3 seconds */ - g_mutex_unlock(glk_data->event_lock); + g_mutex_unlock(priv->event_lock); return; } @@ -38,12 +43,90 @@ event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2) event->win = win; event->val1 = val1; event->val2 = val2; - g_queue_push_head(glk_data->event_queue, event); + g_queue_push_head(priv->event_queue, event); /* Signal that there is an event */ - g_cond_signal(glk_data->event_queue_not_empty); + g_cond_signal(priv->event_queue_not_empty); + + g_mutex_unlock(priv->event_lock); +} + +/* Helper function: Wait for an event in the event queue. If it is a forced + * input event, but no windows have an input request of that type, then wait + * for the next event and put the forced input event back on top of the queue. + */ +static void +get_appropriate_event(event_t *event) +{ + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + + g_mutex_lock(glk_data->event_lock); + event_t *retrieved_event = NULL; + + /* Wait for an event */ + if( g_queue_is_empty(glk_data->event_queue) ) + g_cond_wait(glk_data->event_queue_not_empty, glk_data->event_lock); + + retrieved_event = g_queue_pop_tail(glk_data->event_queue); + + /* Signal that the event queue is no longer full */ + g_cond_signal(glk_data->event_queue_not_full); + g_mutex_unlock(glk_data->event_lock); + + if(retrieved_event->type == evtype_ForcedCharInput) + { + /* Check for forced character input in the queue */ + winid_t win; + for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL)) + if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE) + break; + if(win) + { + force_char_input_from_queue(win, event); + g_free(retrieved_event); + } + else + { + get_appropriate_event(event); + g_mutex_lock(glk_data->event_lock); + g_queue_push_tail(glk_data->event_queue, retrieved_event); + g_cond_signal(glk_data->event_queue_not_empty); + g_mutex_unlock(glk_data->event_lock); + } + } + else if(retrieved_event->type == evtype_ForcedLineInput) + { + /* Check for forced line input in the queue */ + winid_t win; + for(win = glk_window_iterate(NULL, NULL); win; win = glk_window_iterate(win, NULL)) + if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) + break; + if(win) + { + force_line_input_from_queue(win, event); + g_free(retrieved_event); + } + else + { + get_appropriate_event(event); + g_mutex_lock(glk_data->event_lock); + g_queue_push_tail(glk_data->event_queue, retrieved_event); + g_cond_signal(glk_data->event_queue_not_empty); + g_mutex_unlock(glk_data->event_lock); + } + } + else + { + if(retrieved_event == NULL) + { + WARNING("Retrieved NULL event from non-empty event queue"); + return; + } + memcpy(event, retrieved_event, sizeof(event_t)); + g_free(retrieved_event); + } } /** @@ -64,31 +147,124 @@ glk_select(event_t *event) { g_return_if_fail(event != NULL); - g_mutex_lock(glk_data->event_lock); + /* Flush all window buffers */ + winid_t win; + for(win = glk_window_iterate(NULL, NULL); win != NULL; win = glk_window_iterate(win, NULL)) { + if(win->type == wintype_TextBuffer) + flush_window_buffer(win); + } - /* Wait for an event */ - while( g_queue_is_empty(glk_data->event_queue) ) - g_cond_wait(glk_data->event_queue_not_empty, glk_data->event_lock); + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + + get_appropriate_event(event); + + /* Check for interrupt */ + glk_tick(); - event_t *retrieved_event = g_queue_pop_tail(glk_data->event_queue); - if(retrieved_event == NULL) + /* If the event was a line input event, the library must release the buffer */ + if(event->type == evtype_LineInput && glk_data->unregister_arr) { - g_mutex_unlock(glk_data->event_lock); - WARNING("Retrieved NULL event from non-empty event queue"); - return; - } - memcpy(event, retrieved_event, sizeof(event_t)); - g_free(retrieved_event); + if(event->win->input_request_type == INPUT_REQUEST_LINE_UNICODE) + (*glk_data->unregister_arr)(event->win->line_input_buffer_unicode, event->win->line_input_buffer_max_len, "&+#!Iu", event->win->buffer_rock); + else + (*glk_data->unregister_arr)(event->win->line_input_buffer, event->win->line_input_buffer_max_len, "&+#!Cn", event->win->buffer_rock); + } + + /* If an abort event was generated, the thread should have exited by now */ + g_assert(event->type != evtype_Abort); +} - /* Signal that the event queue is no longer full */ - g_cond_signal(glk_data->event_queue_not_full); +/** + * glk_select_poll: + * @event: Return location for an event. + * + * You can also inquire if an event is available, without stopping to wait for + * one to occur. + * + * This checks if an internally-spawned event is available. If so, it stores it + * in the structure pointed to by @event. If not, it sets + * @event->type to %evtype_None. Either way, it returns almost + * immediately. + * + * The first question you now ask is, what is an internally-spawned event? + * glk_select_poll() does not check for or return %evtype_CharInput, + * %evtype_LineInput, %evtype_MouseInput, or %evtype_Hyperlink events. It is + * intended for you to test conditions which may have occurred while you are + * computing, and not interfacing with the player. For example, time may pass + * during slow computations; you can use glk_select_poll() to see if a + * %evtype_Timer event has occurred. (See Timer Events.) + * + * At the moment, glk_select_poll() checks for %evtype_Timer, %evtype_Arrange, + * %evtype_Redraw and %evtype_SoundNotify events. But see Other Events. + * + * The second question is, what does it mean that glk_select_poll() returns + * almost immediately? In some Glk libraries, text that you send + * to a window is buffered; it does not actually appear until you request player + * input with glk_select(). glk_select_poll() attends to this buffer-flushing + * task in the same way. (Although it does not do the Hit + * any key to scroll down waiting which may be done in + * glk_select(); that's a player-input task.) + * + * Similarly, on multitasking platforms, glk_select() may yield time to other + * processes; and glk_select_poll() does this as well. + * + * The upshot of this is that you should not call glk_select_poll() very often. + * If you are not doing much work between player inputs, you should not need to + * call it at all. + * + * + * For example, in a virtual machine interpreter, you should not call + * glk_select_poll() after every opcode. + * + * + * However, if you are doing intense computation, you may wish to call + * glk_select_poll() every so often to yield time to other processes. And if you + * are printing intermediate results during this computation, you should + * glk_select_poll() every so often, so that you can be certain your output will + * be displayed before the next glk_select(). + * + * + * However, you should call glk_tick() often — once per opcode in a VM + * interpreter. See The Tick + * Thing. + * + */ +void +glk_select_poll(event_t *event) +{ + g_return_if_fail(event != NULL); + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + + event->type = evtype_None; + + g_mutex_lock(glk_data->event_lock); + + if( !g_queue_is_empty(glk_data->event_queue) ) + { + GList *link; + int count; + for(count = 0; (link = g_queue_peek_nth_link(glk_data->event_queue, count)) != NULL; count++) + { + glui32 type = ((event_t *)link->data)->type; + if(type != evtype_CharInput && type != evtype_LineInput && type != evtype_MouseInput && type != evtype_Hyperlink) + { + memcpy(event, link->data, sizeof(event_t)); + g_free(link->data); + g_queue_delete_link(glk_data->event_queue, link); + g_cond_signal(glk_data->event_queue_not_full); + break; + } + } + } + g_mutex_unlock(glk_data->event_lock); /* Check for interrupt */ glk_tick(); - + /* If an abort event was generated, the thread should have exited by now */ g_assert(event->type != evtype_Abort); } -