From 5292406311d31682a850baf059cab01a6f0262b2 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Sun, 24 Aug 2008 15:19:05 +0000 Subject: [PATCH] Verbeterd interrupt mechanisme en afbreken van het Glk programma glk_set_interrupt_handler() glk_tick() git-svn-id: http://lassie.dyndns-server.com/svn/gargoyle-gtk@12 ddfedd41-794f-dd11-ae45-00112f111e67 --- src/Makefile | 2 ++ src/abort.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/abort.h | 11 ++++++ src/event.c | 32 +++++++++++------ src/event.h | 4 +-- src/first.c | 7 ++++ src/glk.c | 37 ++++++++++++++++++++ src/main.c | 7 ++-- src/model.c | 12 +++++-- src/strio.c | 3 +- src/window.c | 6 ++-- 11 files changed, 193 insertions(+), 26 deletions(-) create mode 100644 src/abort.c create mode 100644 src/abort.h diff --git a/src/Makefile b/src/Makefile index 5c4103d..d3fbf06 100644 --- a/src/Makefile +++ b/src/Makefile @@ -12,6 +12,7 @@ SOURCES = main.c \ event.c event.h \ input.c input.h \ style.c \ + abort.c abort.h \ first.c OBJECTS = main.o \ callbacks.o \ @@ -26,6 +27,7 @@ OBJECTS = main.o \ event.o \ input.o \ style.o \ + abort.o \ first.o CFLAGS = -g -Wall -O0 -export-dynamic `pkg-config --cflags ${PKG_CONFIG}` diff --git a/src/abort.c b/src/abort.c new file mode 100644 index 0000000..63ef3bd --- /dev/null +++ b/src/abort.c @@ -0,0 +1,98 @@ +#include "event.h" +#include +#include + +static GMutex *abort_lock = NULL; +static gboolean abort_signalled = FALSE; +static void (*interrupt_handler)(void) = NULL; + +/* Internal function: initialize the interrupt handling system. */ +void +interrupt_init() +{ + abort_lock = g_mutex_new(); +} + +/* Internal function: free the resources allocated in interrupt_init(). */ +void +interrupt_free() +{ + g_mutex_lock(abort_lock); + /* Make sure no other thread is busy with this */ + g_mutex_unlock(abort_lock); + g_mutex_free(abort_lock); + abort_lock = NULL; +} + +/** + * glk_set_interrupt_handler: + * @func: A pointer to a function which takes no argument and returns no result. + * + * Specifies an interrupt handler function for cleaning up critical resources. + * If Glk receives an interrupt, and you have set an interrupt handler, your + * handler will be called, before the process is shut down. + * + * Initially there is no interrupt handler. You can reset to not having any by + * calling glk_set_interrupt_handler(%NULL). + * + * If you call glk_set_interrupt_handler() with a new handler function while an + * older one is set, the new one replaces the old one. Glk does not try to queue + * interrupt handlers. + * + * You should not try to interact with the player in your interrupt handler. Do + * not call glk_select() or glk_select_poll(). Anything you print to a window + * may not be visible to the player. + */ +void +glk_set_interrupt_handler(void (*func)(void)) +{ + interrupt_handler = func; +} + +/* Internal function: Free all Glk resources. */ +void +cleanup() +{ + events_free(); + interrupt_free(); +} + +/* Internal function: abort this Glk program, freeing resources and calling the +user's interrupt handler. */ +void +abort_glk() +{ + if(interrupt_handler) + (*interrupt_handler)(); + cleanup(); + g_thread_exit(NULL); +} + +/* Internal function: Signal this Glk thread to abort. Does nothing if the abort +mutex has already been freed. (That means the thread already ended.) */ +void +signal_abort() +{ + if(abort_lock) { + g_mutex_lock(abort_lock); + abort_signalled = TRUE; + g_mutex_unlock(abort_lock); + /* Stop blocking on the event queue condition */ + event_throw(evtype_Abort, NULL, 0, 0); + } +} + +/* Internal function: check if the Glk program has been interrupted. */ +void +check_for_abort() +{ + g_mutex_lock(abort_lock); + if(abort_signalled) + { + g_mutex_unlock(abort_lock); + abort_glk(); + } + g_mutex_unlock(abort_lock); +} + + diff --git a/src/abort.h b/src/abort.h new file mode 100644 index 0000000..ada26ac --- /dev/null +++ b/src/abort.h @@ -0,0 +1,11 @@ +#ifndef ABORT_H +#define ABORT_H + +void interrupt_init(); +void interrupt_free(); +void cleanup(); +void check_for_abort(); +void signal_abort(); + +#endif + diff --git a/src/event.c b/src/event.c index 76f8240..2762063 100644 --- a/src/event.c +++ b/src/event.c @@ -1,6 +1,9 @@ #include "event.h" +#include "glk.h" #include +#define EVENT_TIMEOUT_MICROSECONDS (3000000) + static GQueue *event_queue = NULL; static GMutex *event_lock = NULL; static GCond *event_queue_not_empty = NULL; @@ -22,26 +25,33 @@ events_init(). */ void events_free() { + g_mutex_lock(event_lock); g_queue_foreach(event_queue, (GFunc)g_free, NULL); g_queue_free(event_queue); - g_mutex_free(event_lock); g_cond_free(event_queue_not_empty); g_cond_free(event_queue_not_full); + event_queue = NULL; + g_mutex_unlock(event_lock); + g_mutex_free(event_lock); } /* Internal function: push an event onto the event queue. If the event queue is -full, wait for max three seconds and then drop the event. */ +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) { + if(!event_queue) + return; + GTimeVal timeout; g_get_current_time(&timeout); - g_time_val_add(&timeout, 3000000); /* 3 Seconds */ + g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS); g_mutex_lock(event_lock); /* Wait for room in the event queue */ - if( g_queue_get_length(event_queue) >= EVENT_QUEUE_MAX_LENGTH ) + while( g_queue_get_length(event_queue) >= EVENT_QUEUE_MAX_LENGTH ) if( !g_cond_timed_wait(event_queue_not_full, event_lock, &timeout) ) { /* Drop the event after 3 seconds */ @@ -83,7 +93,7 @@ glk_select(event_t *event) g_mutex_lock(event_lock); /* Wait for an event */ - if( g_queue_is_empty(event_queue) ) + while( g_queue_is_empty(event_queue) ) g_cond_wait(event_queue_not_empty, event_lock); event_t *retrieved_event = g_queue_pop_tail(event_queue); @@ -100,10 +110,10 @@ glk_select(event_t *event) g_cond_signal(event_queue_not_full); g_mutex_unlock(event_lock); - - /* Implementation-defined events */ - switch(event->type) { - case EVENT_TYPE_QUIT: - g_thread_exit(NULL); - } + + /* Check for interrupt */ + glk_tick(); + + /* If an abort event was generated, the thread should have exited by now */ + g_assert(event->type != evtype_Abort); } diff --git a/src/event.h b/src/event.h index b17335c..0296d7c 100644 --- a/src/event.h +++ b/src/event.h @@ -5,9 +5,7 @@ #include "glk.h" #define EVENT_QUEUE_MAX_LENGTH (100) - -/* Implementation-defined events */ -#define EVENT_TYPE_QUIT (-1) +#define evtype_Abort (-1) void events_init(); void events_free(); diff --git a/src/first.c b/src/first.c index 3f004a8..a803149 100644 --- a/src/first.c +++ b/src/first.c @@ -62,10 +62,17 @@ static void verb_unscript(void); static void verb_save(void); static void verb_restore(void); +void handler(void) +{ + fprintf(stderr, "I'm the interrupt handler!\n"); +} + /* The glk_main() function is called by the Glk system; it's the main entry point for your program. */ void glk_main(void) { + glk_set_interrupt_handler(&handler); + /* Open the main window. */ mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); if (!mainwin) { diff --git a/src/glk.c b/src/glk.c index dbec759..88ca873 100644 --- a/src/glk.c +++ b/src/glk.c @@ -2,6 +2,7 @@ #include #include "glk.h" +#include "abort.h" /** * glk_exit: @@ -23,6 +24,42 @@ void glk_exit(void) { + cleanup(); g_thread_exit(NULL); } +/** + * glk_tick: + * + * Many platforms have some annoying thing that has to be done every so often, + * or the gnurrs come from the voodvork out and eat your computer. + * + * Well, not really. But you should call glk_tick() every so often, just in + * case. It may be necessary to yield time to other applications in a + * cooperative-multitasking OS, or to check for player interrupts in an infinite + * loop. + * + * This call is fast; in fact, on average, it does nothing at all. So you can + * call it often. (In a virtual machine interpreter, once per opcode is + * appropriate. In a program with lots of computation, pick a comparable rate.) + * + * glk_tick() does not try to update the screen, or check for player input, or + * any other interface task. For that, you should call glk_select() or + * glk_select_poll(). + * + * Basically, you must ensure there's some fixed upper bound on the amount of + * computation that can occur before a glk_tick() (or glk_select()) occurs. In a + * VM interpreter, where the VM code might contain an infinite loop, this is + * critical. In a C program, you can often eyeball it. + */ +void +glk_tick() +{ + check_for_abort(); + + /* Do one iteration of the main loop if there are any events */ + gdk_threads_enter(); + if(gtk_events_pending()) + gtk_main_iteration(); + gdk_threads_leave(); +} diff --git a/src/main.c b/src/main.c index 1a1867f..8d561f4 100644 --- a/src/main.c +++ b/src/main.c @@ -42,6 +42,7 @@ #include "callbacks.h" #include "error.h" #include "event.h" +#include "abort.h" #include "glk.h" /* @@ -66,8 +67,6 @@ # define N_(String) (String) #endif -#include "callbacks.h" - /* The global builder object to be used to request handles to widgets */ GtkBuilder *builder = NULL; @@ -130,6 +129,7 @@ main(int argc, char *argv[]) gtk_widget_show(window); events_init(); + interrupt_init(); /* In een aparte thread of proces */ if( (glk_thread = g_thread_create(glk_enter, NULL, TRUE, &error)) == NULL ) { @@ -142,11 +142,10 @@ main(int argc, char *argv[]) gtk_main(); gdk_threads_leave(); - event_throw(EVENT_TYPE_QUIT, NULL, 0, 0); + signal_abort(); g_thread_join(glk_thread); g_object_unref( G_OBJECT(builder) ); - events_free(); return 0; } diff --git a/src/model.c b/src/model.c index f0af53f..b89dbc9 100644 --- a/src/model.c +++ b/src/model.c @@ -2,6 +2,11 @@ static winid_t mainwin = NULL; +void sayit(void) +{ + g_printerr("I'm the interrupt handler!\n"); +} + void glk_main(void) { /* Open the main window. */ @@ -52,11 +57,13 @@ void glk_main(void) glk_set_window(mainwin); - gchar buffer[256] = "blaat"; + glk_set_interrupt_handler(&sayit); + + gchar buffer[256]; event_t ev; while(1) { glk_put_string("prompt> "); - glk_request_line_event(mainwin, buffer, 256, 5); + glk_request_line_event(mainwin, buffer, 256, 0); glk_select(&ev); switch(ev.type) { default: @@ -67,7 +74,6 @@ void glk_main(void) printf("Var2: %d\n", ev.val2); } } - /* Bye bye */ glk_exit(); diff --git a/src/strio.c b/src/strio.c index 6b70dfa..2780724 100644 --- a/src/strio.c +++ b/src/strio.c @@ -59,8 +59,7 @@ write_utf8_to_window(winid_t win, gchar *s) { gdk_threads_enter(); - GtkTextBuffer *buffer = - gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); GtkTextIter iter; gtk_text_buffer_get_end_iter(buffer, &iter); diff --git a/src/window.c b/src/window.c index db0af1a..cd2ff7a 100644 --- a/src/window.c +++ b/src/window.c @@ -248,16 +248,16 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, break; default: + gdk_threads_leave(); g_warning("%s: unsupported window type", __func__); g_free(win); - gdk_threads_leave(); return NULL; } - win->window_node = root_window; - gdk_threads_leave(); + win->window_node = root_window; + return win; } -- 2.30.2