On 02/16/2010 01:44 PM, Gwenael Casaccio wrote:
Hi,
you'll find as an attached file the first release of the new gtk event loop.
And you'll find attached the second release of the new GTK event loop. :-)
The handling of dialogs is needed with this one too, but it should not
be hard (especially with the #runOnAnswer: temporary trick).
I only tested it with example_tictactoe.st but it seems to work!
Paolo
diff --git a/packages/gtk/GtkImpl.st b/packages/gtk/GtkImpl.st
index 6a2d2fc..27316ec 100644
--- a/packages/gtk/GtkImpl.st
+++ b/packages/gtk/GtkImpl.st
@@ -488,23 +488,31 @@ GtkTextBuffer extend [
Gtk class extend [
+ | loop |
+
+ mainQuit [
+ loop quit
+ ]
main [
- "Main loop implementation. Same as gtk_main, but it lets Smalltalk
- processes run. Like gtk_main, it is ended with Gtk>>#mainQuit."
+ "Main loop implementation. Same as gtk_main, but it splits
+ g_main_context_run in two threads so that Smalltalk processes
+ run. Like gtk_main, it is ended with Gtk>>#mainQuit."
<category: 'event loop'>
- | sem |
+ | sem newLoop context |
sem := Semaphore new.
- "This call is asynchronous, so it leaves us waiting on the #wait call
just
- below. Our loop starts as soon as the next bytecode is executed,
- because sem is asynchronously signalled by my_gtk_main."
- GTK.Gtk main: sem.
- sem wait.
-
- [GTK.Gtk mainIterationDo: false.
- GTK.Gtk shouldQuit]
- whileFalse: [Processor yield]
+ newLoop := GTK.Gtk main: sem.
+ newLoop isNil ifTrue: [ ^self ].
+ loop := newLoop.
+ context := loop getContext.
+
+ [
+ [sem wait.
+ [context acquireWait; dispatch]
+ ensure: [context releaseSignal].
+ loop isRunning] whileTrue]
+ ensure: [loop unref]
]
]
diff --git a/packages/gtk/Makefile.am b/packages/gtk/Makefile.am
index 300881a..e1d1667 100644
--- a/packages/gtk/Makefile.am
+++ b/packages/gtk/Makefile.am
@@ -12,7 +12,7 @@ LC_UNSET = LANG=C; export LANG; \
EXTRA_DIST = cpp.awk structs.awk funcs.awk mk_enums.awk mk_sizeof.awk
mkorder.awk
gst_module_ldflags = -rpath $(moduleexecdir) -release $(VERSION) -module \
- -no-undefined # -export-symbols-regex gst_initModule
+ -export-symbols-regex gst_initModule
ALL_LIBS = $(GLIB_LIBS) $(GTK_LIBS) $(PANGO_LIBS) $(ATK_LIBS) $(GTHREAD_LIBS) \
$(CAIRO_LIBS)
@@ -40,7 +40,7 @@ GTK_FILES = \
# We don't want to include all of GLib for obvious reasons...
GLIB_FILES = \
- glib/goption.h glib/gdate.h
+ glib/goption.h glib/gdate.h glib/gmain.h
LOCAL_FILES = $(srcdir)/placer.h
diff --git a/packages/gtk/MoreFuncs.st b/packages/gtk/MoreFuncs.st
index a07ae29..6ef5e78 100644
--- a/packages/gtk/MoreFuncs.st
+++ b/packages/gtk/MoreFuncs.st
@@ -1,26 +1,29 @@
-Gtk class extend [
-
- mainIteration [
+GMainContext extend [
+ dispatch [
<category: 'C call-outs'>
- <asyncCCall: 'gstGtkMainIteration' args: #()>
+ <asyncCCall: 'g_main_context_dispatch' args: #(#self )>
]
- mainIterationDo: blocking [
+ releaseSignal [
<category: 'C call-outs'>
- <asyncCCall: 'gstGtkMainIterationDo' args: #(#boolean )>
+ <cCall: 'gstGtkMainContextReleaseSignal' returning: #void args: #(#self
)>
]
- main: aSemaphore [
+ acquireWait [
<category: 'C call-outs'>
- <asyncCCall: 'gstGtkMain' args: #(#smalltalk )>
+ <cCall: 'gstGtkMainContextAcquireWait' returning: #void args: #(#self )>
]
+]
- shouldQuit [
+
+Gtk class extend [
+
+ main: aSemaphore [
<category: 'C call-outs'>
- <cCall: 'gstGtkShouldQuit' returning: #boolean args: #()>
+ <cCall: 'gstGtkMain' returning: #{GMainLoop} args: #(#smalltalk )>
]
diff --git a/packages/gtk/gst-gtk.c b/packages/gtk/gst-gtk.c
index 45f6edf..cc5456c 100644
--- a/packages/gtk/gst-gtk.c
+++ b/packages/gtk/gst-gtk.c
@@ -80,10 +80,9 @@ typedef struct SmalltalkClosure
int n_params;
} SmalltalkClosure;
-VMProxy *_gst_vm_proxy;
+static VMProxy *_gst_vm_proxy;
static GQuark q_gst_object = 0;
-static int pending_quit_count = 0;
static GTypeInfo gtype_oop_info = {
0, /* class_size */
@@ -133,18 +132,7 @@ static const GTypeValueTable gtype_oop_value_table = {
static GType G_TYPE_OOP;
/* Start the main event loop and then signal OOP. */
-static void my_gtk_main (OOP semaphore);
-
-/* Wait in the main event loop until there are no pending events. */
-static void my_gtk_main_iteration ();
-
-/* Wait in the main event loop until there are no pending events. Never
- block unless BLOCKING is true. */
-static void my_gtk_main_iteration_do (gboolean blocking);
-
-/* Answer whether we should leave the current event loop (if gtk_main_quit
- has been called). */
-static gboolean should_quit ();
+static GMainLoop *create_main_loop_thread (OOP semaphore);
/* Unref OBJ and detach it from the Smalltalk object that has represented
it so far. */
@@ -710,53 +698,163 @@ connect_accel_group_no_user_data (OOP accel_group,
accel_flags, receiver, selector, NULL);
}
-/* Event loop. The GTK+ event loop in GNU Smalltalk takes place
- using my_gtk_main_iteration in a separate process. GtkImpl.st
- redefines Gtk class>>#main calling this function: the event
- loop process is sitting on SEMAPHORE, so that after the first event
- is delivered, SEMAPHORE is unblocked and the real event loop
- takes place in the semaphore process. */
+/* Event loop. The GTK+ event loop in GNU Smalltalk is split between two
+ operating system threads using the low-level g_main_context functions
+
(http://library.gnome.org/devel/glib/unstable/glib-The-Main-Event-Loop.html).
-void
-my_gtk_main (OOP semaphore)
+ Everything except dispatching occurs in a separate thread than the one
+ executing Smalltalk code. After check(), however, the thread releases
+ the context and waits on a condition variable for the Smalltalk code to
+ finish the dispatch phase.
+
+ This ensures that all GTK+ code executes in a single OS thread (avoiding
+ complicated usage of gdk_threads_{enter,leave}) and at the same time
+ allows Smalltalk processes to run in the background while GTK+ events
+ are polled. */
+
+static GMainLoop *loop;
+static GThread *thread;
+static GMutex *mutex;
+static GCond *cond;
+static GCond *cond_dispatch;
+static volatile gboolean queued;
+
+static void
+main_context_acquire_wait (GMainContext *context)
{
- _gst_vm_proxy->registerOOP (semaphore);
- _gst_vm_proxy->asyncSignalAndUnregister (semaphore);
- gtk_main ();
+ g_mutex_lock (mutex);
+ g_main_context_wait (context, cond, mutex);
+
+ /* No need to keep the mutex except during g_main_context_acquire_wait
+ and g_main_context_release_signal, i.e. except while we operate on
+ cond. */
+ g_mutex_unlock (mutex);
}
-void
-my_gtk_main_iteration ()
+static void
+main_context_release_signal (GMainContext *context)
{
- int pending_quit = FALSE;
- while (gtk_events_pending ())
- if (gtk_main_iteration_do (TRUE))
- {
- pending_quit_count += !pending_quit;
- pending_quit = TRUE;
- }
+ g_mutex_lock (mutex);
+ g_main_context_release (context);
+
+ /* Restart the polling thread. Note that #dispatch is asynchronous, so
+ this might execute before the Smalltalk code finishes running! At
+ least in theory :-) this allows debugging GTK+ signal handlers. */
+ queued = false;
+ g_cond_broadcast (cond_dispatch);
+ g_mutex_unlock (mutex);
}
-void
-my_gtk_main_iteration_do (gboolean blocking)
+static gpointer
+main_loop_thread (gpointer semaphore)
{
- int pending_quit = FALSE;
- while (gtk_events_pending ())
- if (gtk_main_iteration_do (blocking))
- {
- pending_quit_count += !pending_quit;
- pending_quit = TRUE;
- }
+ OOP semaphoreOOP = semaphore;
+ static GPollFD *fds;
+ static int allocated_nfds;
+ GMainContext *context = g_main_loop_get_context (loop);
+
+ if (!fds)
+ {
+ fds = g_new (GPollFD, 20);
+ allocated_nfds = 20;
+ }
+
+ /* Mostly based on g_main_context_iterate (a static function in gmain.c)
+ except that we have to use our own mutex and that g_main_context_dispatch
+ is replaced by signaling semaphoreOOP. */
+
+ g_mutex_lock (mutex);
+ while (g_main_loop_is_running (loop))
+ {
+ int nfds, maxprio, timeout;
+
+ g_main_context_wait (context, cond, mutex);
+ g_main_context_prepare (context, &maxprio);
+ while ((nfds = g_main_context_query (context, maxprio,
+ &timeout, fds, allocated_nfds))
+ > allocated_nfds)
+ {
+ g_free (fds);
+ fds = g_new (GPollFD, nfds);
+ allocated_nfds = nfds;
+ }
+
+ /* Release the context so that the other thread can dispatch while
+ this one polls. */
+ g_main_context_release (context);
+ g_mutex_unlock (mutex);
+
+ g_poll (fds, nfds, timeout);
+
+ g_mutex_lock (mutex);
+ g_main_context_wait (context, cond, mutex);
+ g_main_context_check (context, maxprio, fds, nfds);
+ g_main_context_release (context);
+
+ /* Dispatch on the other thread and wait for it to rendez-vous. */
+ queued = true;
+ _gst_vm_proxy->asyncSignal (semaphoreOOP);
+
+ /* TODO: shouldn't be necessary. */
+ _gst_vm_proxy->wakeUp ();
+ while (queued)
+ g_cond_wait (cond_dispatch, mutex);
+ }
+
+ g_main_loop_unref (loop);
+ loop = NULL;
+ thread = NULL;
+ g_mutex_unlock (mutex);
+
+ /* TODO: Not thread-safe! */
+ _gst_vm_proxy->unregisterOOP (semaphoreOOP);
+ return NULL;
}
-gboolean
-should_quit ()
+GMainLoop *
+create_main_loop_thread (OOP semaphore)
{
- if (!pending_quit_count)
- return FALSE;
+ if (!mutex)
+ {
+ /* One-time initialization. */
+ mutex = g_mutex_new ();
+ cond = g_cond_new ();
+ cond_dispatch = g_cond_new ();
+ }
+
+ g_mutex_lock (mutex);
+ if (loop)
+ {
+ /* A loop exists. If it is exiting, wait for it, otherwise
+ leave immediately. */
+ GThread *loop_thread = thread;
+ gboolean exiting = g_main_loop_is_running (loop);
+ g_mutex_unlock (mutex);
+ if (!exiting)
+ return NULL;
+ if (loop_thread)
+ g_thread_join (loop_thread);
+ }
+ else
+ g_mutex_unlock (mutex);
+
+ _gst_vm_proxy->registerOOP (semaphore);
+ loop = g_main_loop_new (NULL, TRUE);
- pending_quit_count--;
- return TRUE;
+ /* Add a second reference to be released when the thread exits. The first
+ one is passed to Smalltalk ("return loop" below). */
+ g_main_loop_ref (loop);
+ thread = g_thread_create (main_loop_thread, semaphore, TRUE, NULL);
+ if (!thread)
+ {
+ /* Destroy both references, since the thread won't have any occasion
+ to release his. */
+ g_main_loop_unref (loop);
+ g_main_loop_unref (loop);
+ return NULL;
+ }
+
+ return loop;
}
/* Wrappers for GValue users. */
@@ -1006,7 +1104,10 @@ gst_initModule (proxy)
initialized = gtk_init_check (&argc, &argv);
if (initialized && !g_thread_supported ())
- g_thread_init (NULL);
+ {
+ g_thread_init (NULL);
+ gdk_threads_init ();
+ }
q_gst_object = g_quark_from_string ("gst_object");
g_type_init ();
@@ -1039,10 +1140,9 @@ gst_initModule (proxy)
_gst_vm_proxy->defineCFunc ("gstGtkConnectAccelGroupNoUserData",
connect_accel_group_no_user_data);
_gst_vm_proxy->defineCFunc ("gstGtkConnectSignal", connect_signal);
_gst_vm_proxy->defineCFunc ("gstGtkConnectSignalNoUserData",
connect_signal_no_user_data);
- _gst_vm_proxy->defineCFunc ("gstGtkMain", my_gtk_main);
- _gst_vm_proxy->defineCFunc ("gstGtkMainIteration", my_gtk_main_iteration);
- _gst_vm_proxy->defineCFunc ("gstGtkMainIterationDo",
my_gtk_main_iteration_do);
- _gst_vm_proxy->defineCFunc ("gstGtkShouldQuit", should_quit);
+ _gst_vm_proxy->defineCFunc ("gstGtkMain", create_main_loop_thread);
+ _gst_vm_proxy->defineCFunc ("gstGtkMainContextAcquireWait",
main_context_acquire_wait);
+ _gst_vm_proxy->defineCFunc ("gstGtkMainContextReleaseSignal",
main_context_release_signal);
_gst_vm_proxy->defineCFunc ("gstGtkGetProperty", object_get_property);
_gst_vm_proxy->defineCFunc ("gstGtkSetProperty", object_set_property);
_gst_vm_proxy->defineCFunc ("gstGtkGetChildProperty",
container_get_child_property);
_______________________________________________
help-smalltalk mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/help-smalltalk