Verbeterd interrupt mechanisme en afbreken van het Glk programma
authorfliep <fliep@ddfedd41-794f-dd11-ae45-00112f111e67>
Sun, 24 Aug 2008 15:19:05 +0000 (15:19 +0000)
committerfliep <fliep@ddfedd41-794f-dd11-ae45-00112f111e67>
Sun, 24 Aug 2008 15:19:05 +0000 (15:19 +0000)
glk_set_interrupt_handler()
glk_tick()

src/Makefile
src/abort.c [new file with mode: 0644]
src/abort.h [new file with mode: 0644]
src/event.c
src/event.h
src/first.c
src/glk.c
src/main.c
src/model.c
src/strio.c
src/window.c

index 5c4103d8298834c59e45183a7fe4532a3099ca37..d3fbf06e3f0bd456bdbcf6415c3c23df64a66230 100644 (file)
@@ -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 (file)
index 0000000..63ef3bd
--- /dev/null
@@ -0,0 +1,98 @@
+#include "event.h"
+#include <glib.h>
+#include <gtk/gtk.h>
+
+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 (file)
index 0000000..ada26ac
--- /dev/null
@@ -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
+
index 76f82409ad5bcddf4201fa2e17b60b23c20c7796..276206361491abf64ec88e09ea34b9f88945256f 100644 (file)
@@ -1,6 +1,9 @@
 #include "event.h"
+#include "glk.h"
 #include <string.h>
 
+#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);
 }
index b17335c0fd1a4ab2a8a31b550637764f78948140..0296d7cd05933dcbb74a2d6d157ab1ded9c598f9 100644 (file)
@@ -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();
index 3f004a89c14f5428b1c49d1b614cfdf0c345a855..a80314941e8f8370e24ae9d9ee5ab95cc97a4cac 100644 (file)
@@ -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) {
index dbec7596f581f478af528bc8473699e1b1820e5f..88ca873e10ea3c58e1eae636d980ac8dbcb8d0fc 100644 (file)
--- a/src/glk.c
+++ b/src/glk.c
@@ -2,6 +2,7 @@
 #include <gtk/gtk.h>
 
 #include "glk.h"
+#include "abort.h"
 
 /**
  * glk_exit:
 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();
+}
index 1a1867f50357c398f3c6c323bd0fff74698a2937..8d561f4a8f2e841ba6297b6b54515466c9f02fba 100644 (file)
@@ -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;
 }
index f0af53f839e0cc5aea9f15c6743079b13056402c..b89dbc958bcf8f40f93da00bdf26712f64d6e76e 100644 (file)
@@ -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();
index 6b70dfa3bd3ce904530df6a577c7fbe2c02ba02d..27807246ec54297b7aa3f64888a1ebea1159f7e0 100644 (file)
@@ -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);
index db0af1a23b6a171c5c453d808f53e73d0e3956e4..cd2ff7aa04e8886c6e3417020452a71222286934 100644 (file)
@@ -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;
 }