From 766f9ed028c757561b99e4ed5aa487d381fe80a3 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 26 Aug 2018 10:19:10 -0400 Subject: Migrated util.go and main.go to the new pkgui convention and C file. Also replaced C.CBytes() with C.malloc() (this bumps our minimum version requirement to 1.8, but it's better than keeping a massive slice around at all times). --- BBB_GOFILES/main.go | 132 ---------------------------------------------------- BBB_GOFILES/util.go | 45 ------------------ main.go | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ pkgui.c | 23 +++++++++ pkgui.h | 9 ++++ util.go | 31 ++++++++++++ util.h | 3 -- 7 files changed, 190 insertions(+), 180 deletions(-) delete mode 100644 BBB_GOFILES/main.go delete mode 100644 BBB_GOFILES/util.go create mode 100644 main.go create mode 100644 pkgui.c create mode 100644 pkgui.h create mode 100644 util.go delete mode 100644 util.h diff --git a/BBB_GOFILES/main.go b/BBB_GOFILES/main.go deleted file mode 100644 index 6500345..0000000 --- a/BBB_GOFILES/main.go +++ /dev/null @@ -1,132 +0,0 @@ -// 11 december 2015 - -package ui - -import ( - "runtime" - "errors" - "sync" - "unsafe" -) - -// #include "ui.h" -// extern void doQueueMain(void *); -// extern int doOnShouldQuit(void *); -// // see golang/go#19835 -// typedef void (*queueMainCallback)(void *); -// typedef int (*onShouldQuitCallback)(void *); -import "C" - -// make sure main() runs on the first thread created by the OS -// if main() calls Main(), things will just work on macOS, where the first thread created by the OS is the only thread allowed to be the main GUI thread -// we might as well lock the OS thread for the other platforms here too (though on those it doesn't matter *which* thread we lock to) -// TODO describe the source of this trick -func init() { - runtime.LockOSThread() -} - -// Main initializes package ui, runs f to set up the program, -// and executes the GUI main loop. f should set up the program's -// initial state: open the main window, create controls, and set up -// events. It should then return, at which point Main will -// process events until Quit is called, at which point Main will return -// nil. If package ui fails to initialize, Main returns an appropriate -// error. -func Main(f func()) error { - // TODO HEAP SAFETY - opts := C.uiInitOptions{} - estr := C.uiInit(&opts) - if estr != nil { - err := errors.New(C.GoString(estr)) - C.uiFreeInitError(estr) - return err - } - C.uiOnShouldQuit(C.onShouldQuitCallback(C.doOnShouldQuit), nil) - QueueMain(f) - C.uiMain() - return nil -} - -// Quit queues a return from Main. It does not exit the program. -// It also does not immediately cause Main to return; Main will -// return when it next can. Quit must be called from the GUI thread. -func Quit() { - C.uiQuit() -} - -// These prevent the passing of Go functions into C land. -// TODO make an actual sparse list instead of this monotonic map thingy -var ( - qmmap = make(map[uintptr]func()) - qmcurrent = uintptr(0) - qmlock sync.Mutex -) - -// QueueMain queues f to be executed on the GUI thread when -// next possible. It returns immediately; that is, it does not wait -// for the function to actually be executed. QueueMain is the only -// function that can be called from other goroutines, and its -// primary purpose is to allow communication between other -// goroutines and the GUI thread. Calling QueueMain after Quit -// has been called results in undefined behavior. -// -// If you start a goroutine in f, it also cannot call package ui -// functions. So for instance, the following will result in -// undefined behavior: -// -// ui.QueueMain(func() { -// go ui.MsgBox(...) -// }) -func QueueMain(f func()) { - qmlock.Lock() - defer qmlock.Unlock() - - n := uintptr(0) - for { - n = qmcurrent - qmcurrent++ - if qmmap[n] == nil { - break - } - } - qmmap[n] = f - C.uiQueueMain(C.queueMainCallback(C.doQueueMain), unsafe.Pointer(n)) -} - -//export doQueueMain -func doQueueMain(nn unsafe.Pointer) { - qmlock.Lock() - - n := uintptr(nn) - f := qmmap[n] - delete(qmmap, n) - - // allow uiQueueMain() to be called by a queued function - // TODO explicitly allow this in libui too - qmlock.Unlock() - - f() -} - -// no need to lock this; this API is only safe on the main thread -var shouldQuitFunc func() bool - -// OnShouldQuit schedules f to be exeucted when the OS wants -// the program to quit or when a Quit menu item has been clicked. -// Only one function may be registered at a time. If the function -// returns true, Quit will be called. If the function returns false, or -// if OnShouldQuit is never called. Quit will not be called and the -// OS will be told that the program needs to continue running. -func OnShouldQuit(f func() bool) { - shouldQuitFunc = f -} - -//export doOnShouldQuit -func doOnShouldQuit(unused unsafe.Pointer) C.int { - if shouldQuitFunc == nil { - return 0 - } - return frombool(shouldQuitFunc()) -} - -// TODO Timer? diff --git a/BBB_GOFILES/util.go b/BBB_GOFILES/util.go deleted file mode 100644 index bcd4d33..0000000 --- a/BBB_GOFILES/util.go +++ /dev/null @@ -1,45 +0,0 @@ -// 12 december 2015 - -package ui - -import ( - "unsafe" -) - -// #include -// #include "util.h" -import "C" - -// We want Go itself to complain when we're out of memory. -// The allocators in cgo *should* do this, but there isn't a -// C.CMalloc(). There *is* a C.CBytes(), however, for transferring -// binary blobs from Go to C. If we pass this an arbitrary slice -// of the desired length, we get our C.CMalloc(). Using a slice -// that's always initialized to zero gives us the memset(0) -// (or ZeroMemory()) for free. -var allocBytes = make([]byte, 1024) // 1024 bytes first - -//export pkguiAlloc -func pkguiAlloc(n C.size_t) unsafe.Pointer { - if n > C.size_t(len(allocBytes)) { - // TODO round n up to a multiple of a power of 2? - // for instance 0x1234 bytes -> 0x1800 bytes - allocBytes = make([]byte, n) - } - return C.CBytes(allocBytes[:n]) -} - -func freestr(str *C.char) { - C.free(unsafe.Pointer(str)) -} - -func tobool(b C.int) bool { - return b != 0 -} - -func frombool(b bool) C.int { - if b { - return 1 - } - return 0 -} diff --git a/main.go b/main.go new file mode 100644 index 0000000..356e0e8 --- /dev/null +++ b/main.go @@ -0,0 +1,127 @@ +// 11 december 2015 + +package ui + +import ( + "runtime" + "errors" + "sync" + "unsafe" +) + +// #include "pkgui.h" +import "C" + +// make sure main() runs on the first thread created by the OS +// if main() calls Main(), things will just work on macOS, where the first thread created by the OS is the only thread allowed to be the main GUI thread +// we might as well lock the OS thread for the other platforms here too (though on those it doesn't matter *which* thread we lock to) +// TODO describe the source of this trick +func init() { + runtime.LockOSThread() +} + +// Main initializes package ui, runs f to set up the program, +// and executes the GUI main loop. f should set up the program's +// initial state: open the main window, create controls, and set up +// events. It should then return, at which point Main will +// process events until Quit is called, at which point Main will return +// nil. If package ui fails to initialize, Main returns an appropriate +// error. +func Main(f func()) error { + opts := C.pkguiAllocInitOptions() + estr := C.uiInit(opts) + C.pkguiFreeInitOptions(opts) + if estr != nil { + err := errors.New(C.GoString(estr)) + C.uiFreeInitError(estr) + return err + } + C.pkguiOnShouldQuit() + QueueMain(f) + C.uiMain() + return nil +} + +// Quit queues a return from Main. It does not exit the program. +// It also does not immediately cause Main to return; Main will +// return when it next can. Quit must be called from the GUI thread. +func Quit() { + C.uiQuit() +} + +// These prevent the passing of Go functions into C land. +// TODO make an actual sparse list instead of this monotonic map thingy +var ( + qmmap = make(map[uintptr]func()) + qmcurrent = uintptr(0) + qmlock sync.Mutex +) + +// QueueMain queues f to be executed on the GUI thread when +// next possible. It returns immediately; that is, it does not wait +// for the function to actually be executed. QueueMain is the only +// function that can be called from other goroutines, and its +// primary purpose is to allow communication between other +// goroutines and the GUI thread. Calling QueueMain after Quit +// has been called results in undefined behavior. +// +// If you start a goroutine in f, it also cannot call package ui +// functions. So for instance, the following will result in +// undefined behavior: +// +// ui.QueueMain(func() { +// go ui.MsgBox(...) +// }) +func QueueMain(f func()) { + qmlock.Lock() + defer qmlock.Unlock() + + n := uintptr(0) + for { + n = qmcurrent + qmcurrent++ + if qmmap[n] == nil { + break + } + } + qmmap[n] = f + C.pkguiQueueMain(C.uintptr_t(n)) +} + +//export pkguiDoQueueMain +func pkguiDoQueueMain(nn unsafe.Pointer) { + qmlock.Lock() + + n := uintptr(nn) + f := qmmap[n] + delete(qmmap, n) + + // allow uiQueueMain() to be called by a queued function + // TODO explicitly allow this in libui too + qmlock.Unlock() + + f() +} + +// no need to lock this; this API is only safe on the main thread +var shouldQuitFunc func() bool + +// OnShouldQuit schedules f to be exeucted when the OS wants +// the program to quit or when a Quit menu item has been clicked. +// Only one function may be registered at a time. If the function +// returns true, Quit will be called. If the function returns false, or +// if OnShouldQuit is never called. Quit will not be called and the +// OS will be told that the program needs to continue running. +func OnShouldQuit(f func() bool) { + shouldQuitFunc = f +} + +//export pkguiDoOnShouldQuit +func pkguiDoOnShouldQuit(unused unsafe.Pointer) C.int { + if shouldQuitFunc == nil { + return 0 + } + return frombool(shouldQuitFunc()) +} + +// TODO Timer? diff --git a/pkgui.c b/pkgui.c new file mode 100644 index 0000000..7caab2c --- /dev/null +++ b/pkgui.c @@ -0,0 +1,23 @@ +// 26 august 2018 +#include "pkgui.h" +#include "xxxxx" + +uiInitOptions *pkguiAllocInitOptions(void) +{ + return (uiInitOptions *) pkguiAlloc(sizeof (uiInitOptions)); +} + +void pkguiFreeInitOptions(uiInitOptions *o) +{ + free(o); +} + +void pkguiQueueMain(uintptr_t n) +{ + uiQueueMain(pkguiDoQueueMain, (void *) n); +} + +void pkguiOnShouldQuit(void) +{ + uiOnShouldQuit(pkguiDoOnShouldQuit, NULL); +} diff --git a/pkgui.h b/pkgui.h new file mode 100644 index 0000000..2af5f2b --- /dev/null +++ b/pkgui.h @@ -0,0 +1,9 @@ +// 12 august 2018 +#include +#include "ui.h" + +// main.go +extern uiInitOptions *pkguiAllocInitOptions(void); +extern void pkguiFreeInitOptions(uiInitOptions *o); +extern void pkguiQueueMain(uintptr_t n); +extern void pkguiOnShouldQuit(void); diff --git a/util.go b/util.go new file mode 100644 index 0000000..667f679 --- /dev/null +++ b/util.go @@ -0,0 +1,31 @@ +// 12 december 2015 + +package ui + +import ( + "unsafe" +) + +// #include "pkgui.h" +import "C" + +//export pkguiAlloc +func pkguiAlloc(n C.size_t) unsafe.Pointer { + // cgo turns C.malloc() into a panic-on-OOM version; use it + return C.malloc(n) +} + +func freestr(str *C.char) { + C.free(unsafe.Pointer(str)) +} + +func tobool(b C.int) bool { + return b != 0 +} + +func frombool(b bool) C.int { + if b { + return 1 + } + return 0 +} diff --git a/util.h b/util.h deleted file mode 100644 index 3354234..0000000 --- a/util.h +++ /dev/null @@ -1,3 +0,0 @@ -// 12 august 2018 - -extern void *pkguiAlloc(size_t); -- cgit v1.2.3