Changed the way uitask is dispatched on GTK+ to make event handling not a CPU hog.
This commit is contained in:
parent
1729764db8
commit
55f7a9638e
|
@ -10,7 +10,7 @@ import (
|
|||
/*
|
||||
cgo doesn't support calling Go functions by default; we have to mark them for export. Not a problem, except arguments to GTK+ callbacks depend on the callback itself. Since we're generating callback functions as simple closures of one type, this file will wrap the generated callbacks in the appropriate callback type. We pass the actual generated pointer to the extra data parameter of the callback.
|
||||
|
||||
while we're at it the callback for our idle function will be handled here too for cleanliness purposes
|
||||
while we're at it the callback for our idle function will be handled here too
|
||||
*/
|
||||
|
||||
// #cgo pkg-config: gtk+-3.0
|
||||
|
@ -18,6 +18,7 @@ while we're at it the callback for our idle function will be handled here too fo
|
|||
// extern gboolean our_callback(gpointer);
|
||||
// extern gboolean our_window_callback(GtkWidget *, GdkEvent *, gpointer);
|
||||
// extern void our_clicked_callback(GtkButton *, gpointer);
|
||||
// extern gboolean our_idle_callback(gpointer);
|
||||
import "C"
|
||||
|
||||
//export our_callback
|
||||
|
@ -36,8 +37,20 @@ func our_clicked_callback(button *C.GtkButton, what C.gpointer) {
|
|||
our_callback(what)
|
||||
}
|
||||
|
||||
//export our_idle_callback
|
||||
func our_idle_callback(what C.gpointer) C.gboolean {
|
||||
// there are two issues we solve here:
|
||||
// 1) we need to make sure the uitask request gets garbage collected when we're done so as to not waste memory, but only when we're done so as to not have craziness happen
|
||||
// 2) we need to make sure one idle function runs and finishes running before we start the next; otherwise we could wind up with weird things like the ret channel being closed early
|
||||
// so this function calls the uitask function and sends a message back to the dispatcher that it finished running; the dispatcher is still holding onto the uitask function so it won't be collected
|
||||
idleop := (*gtkIdleOp)(unsafe.Pointer(what))
|
||||
idleop.what()
|
||||
idleop.done <- struct{}{}
|
||||
return C.FALSE // remove this idle function; we're finished
|
||||
}
|
||||
|
||||
var callbacks = map[string]C.GCallback{
|
||||
"idle": C.GCallback(C.our_callback),
|
||||
"idle": C.GCallback(C.our_idle_callback),
|
||||
"delete-event": C.GCallback(C.our_window_callback),
|
||||
"configure-event": C.GCallback(C.our_window_callback),
|
||||
"clicked": C.GCallback(C.our_clicked_callback),
|
||||
|
|
|
@ -24,12 +24,14 @@ func gtk_init() bool {
|
|||
return fromgbool(C.gtk_init_check((*C.int)(nil), (***C.char)(nil)))
|
||||
}
|
||||
|
||||
// the garbage collector has been found to eat my callback functions; this will stop it
|
||||
var callbackstore = make([]*func() bool, 0, 50)
|
||||
// see our_idle_callback in callbacks_unix.go for details
|
||||
type gtkIdleOp struct {
|
||||
what func()
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func gdk_threads_add_idle(what func() bool) {
|
||||
callbackstore = append(callbackstore, &what)
|
||||
C.gdk_threads_add_idle(callbacks["idle"], C.gpointer(unsafe.Pointer(&what)))
|
||||
func gdk_threads_add_idle(idleop *gtkIdleOp) {
|
||||
C.gdk_threads_add_idle(callbacks["idle"], C.gpointer(unsafe.Pointer(idleop)))
|
||||
}
|
||||
|
||||
func gtk_main() {
|
||||
|
@ -45,6 +47,9 @@ func gtk_window_new() *gtkWidget {
|
|||
return fromgtkwidget(C.gtk_window_new(0))
|
||||
}
|
||||
|
||||
// the garbage collector has been found to eat my callback functions; this will stop it
|
||||
var callbackstore = make([]*func() bool, 0, 50)
|
||||
|
||||
func g_signal_connect(obj *gtkWidget, sig string, callback func() bool) {
|
||||
callbackstore = append(callbackstore, &callback)
|
||||
ccallback := callbacks[sig]
|
||||
|
|
|
@ -45,7 +45,7 @@ GTK+ is strange: there are constructor functions that return `GtkWidget *`, but
|
|||
|
||||
`uitask` is a channel that takes `func()` literals. These are closures generated by the `sysData` functions that contain the GTK+ calls and a channel send for return values (like with Windows above). If no return value is needed, the channel send just sends a `struct{}`. The UI goroutine merely calls these functions.
|
||||
|
||||
As the GTK+ main loop system does not quite run in a sane way (it allows recursion, and the `gtk_main_loop_iteration_do` function only onperates on the innermost call), we cannot use the `for`/`select` template for `ui()`. Fortunately, we can hook into the GDK main loop (that the GTK+ main loop uses) to run our `uitask` dispatches whenever the GDK main loop is idle. The only catch is that the `uitask` receives have to be non-blocking for this to work properly, so we wrap them in a `select` with a null `default`.
|
||||
As the GTK+ main loop system does not quite run in a sane way (it allows recursion, and the `gtk_main_loop_iteration_do` function only onperates on the innermost call), we cannot use the `for`/`select` template for `ui()`. Fortunately, GDK provides gdk_threads_add_idle(), which allows us to run, and optionally (and importantly) run only once, a function on the `gtk_main()` thread when not processing events. We use this, combined with a goroutine for dispatching, to handle `uitask` requests. See `our_idle_callback` in callbacks_unix.go for details.
|
||||
|
||||
GTK+ layout managers are not used since the UI library's layout managers are coded in a portable way. (`GtkFixed` is used instead.) This isn't ideal, but it works for now.
|
||||
|
||||
|
|
1
todo.md
1
todo.md
|
@ -23,7 +23,6 @@ so I don't forget:
|
|||
- change sysData.make() so it does not take the initial window text as an argument and instead have the respective Control/Window.make() call sysData.setText() expressly; this would allow me to remove the "no such concept of text" checks from the GTK+ and Mac OS X backends
|
||||
|
||||
important things:
|
||||
- GTK+ ProgressBar indeterminate animation is running like mad; pretty sure it's our idle task being evil
|
||||
- because the main event loop is not called if initialization fails, it is presently impossible for MsgBoxError() to work if UI initialization fails; this basically means we cannot allow initializiation to fail on Mac OS X if we want to be able to report UI init failures to the user with one (which would be desirable, maybe (would violate Windows HIG?))
|
||||
- figure out where to auto-place windows in Cocoa (also window coordinates are still not flipped properly so (0,0) on screen is the bottom-left)
|
||||
- also provide a method to center windows; Cocoa provides one for us but
|
||||
|
|
|
@ -18,15 +18,19 @@ func ui(main func()) error {
|
|||
return fmt.Errorf("gtk_init failed (reason unknown; TODO)")
|
||||
}
|
||||
|
||||
// thanks to tristan in irc.gimp.net/#gtk
|
||||
gdk_threads_add_idle(func() bool {
|
||||
select {
|
||||
case f := <-uitask:
|
||||
f()
|
||||
default: // do not block
|
||||
// thanks to tristan and Daniel_S in irc.gimp.net/#gtk
|
||||
// see our_idle_callback in callbacks_unix.go for details
|
||||
go func() {
|
||||
for f := range uitask {
|
||||
done := make(chan struct{})
|
||||
gdk_threads_add_idle(>kIdleOp{
|
||||
what: f,
|
||||
done: done,
|
||||
})
|
||||
<-done
|
||||
close(done)
|
||||
}
|
||||
return true // don't destroy the callback
|
||||
})
|
||||
}()
|
||||
|
||||
go func() {
|
||||
main()
|
||||
|
|
Loading…
Reference in New Issue