event.c event.h \
input.c input.h \
style.c \
+ abort.c abort.h \
first.c
OBJECTS = main.o \
callbacks.o \
event.o \
input.o \
style.o \
+ abort.o \
first.o
CFLAGS = -g -Wall -O0 -export-dynamic `pkg-config --cflags ${PKG_CONFIG}`
--- /dev/null
+#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);
+}
+
+
--- /dev/null
+#ifndef ABORT_H
+#define ABORT_H
+
+void interrupt_init();
+void interrupt_free();
+void cleanup();
+void check_for_abort();
+void signal_abort();
+
+#endif
+
#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;
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 */
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);
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);
}
#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();
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) {
#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();
+}
#include "callbacks.h"
#include "error.h"
#include "event.h"
+#include "abort.h"
#include "glk.h"
/*
# define N_(String) (String)
#endif
-#include "callbacks.h"
-
/* The global builder object to be used to request handles to widgets */
GtkBuilder *builder = NULL;
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 ) {
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;
}
static winid_t mainwin = NULL;
+void sayit(void)
+{
+ g_printerr("I'm the interrupt handler!\n");
+}
+
void glk_main(void)
{
/* Open the main window. */
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:
printf("Var2: %d\n", ev.val2);
}
}
-
/* Bye bye */
glk_exit();
{
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);
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;
}