Restructured uitask to accept a limited range of actions. Implemented this on the Windows backend.

This commit is contained in:
Pietro Gagliardi 2014-07-01 11:55:11 -04:00
parent ad2c8d9033
commit 607e710459
3 changed files with 83 additions and 101 deletions

View File

@ -36,19 +36,14 @@ var Ready = make(chan struct{})
// Do not pulse Stop more than once. // Do not pulse Stop more than once.
var Stop = make(chan struct{}) var Stop = make(chan struct{})
// This function is a simple helper functionn that basically pushes the effect of a function call for later. This allows the selected safe Window methods to be safe. // uitask is an object of a type implemented by each uitask_***.go that does everything that needs to be communicated to the main thread.
// TODO make sure this acts sanely if called from uitask itself type _uitask struct{}
func touitask(f func()) { var uitask = _uitask{}
done := make(chan struct{})
defer close(done) // and the required methods are:
go func() { // to avoid locking uitask itself var xuitask interface {
done2 := make(chan struct{}) // make the chain uitask <- f <- uitask to avoid deadlocks // creates a window
defer close(done2) // TODO whether this waits for the window creation to finish is implementation defined?
uitask <- func() { createWindow(*Window, Control, bool)
f() } = uitask
done2 <- struct{}{} // compilation will fail if uitask doesn't have all these methods
}
done <- <-done2
}()
<-done
}

View File

@ -9,6 +9,8 @@ import (
) )
/* /*
TODO rewrite this comment block
problem: messages have to be dispatched on the same thread as system calls, and we can't mux GetMessage() with select, and PeekMessage() every iteration is wasteful (and leads to lag for me (only) with the concurrent garbage collector sweep) problem: messages have to be dispatched on the same thread as system calls, and we can't mux GetMessage() with select, and PeekMessage() every iteration is wasteful (and leads to lag for me (only) with the concurrent garbage collector sweep)
possible: solution: use PostThreadMessage() to send uimsgs out to the message loop, which runs on its own goroutine possible: solution: use PostThreadMessage() to send uimsgs out to the message loop, which runs on its own goroutine
(I had come up with this first but wanted to try other things before doing it (and wasn't really sure if user-defined messages were safe, not quite understanding the system); nsf came up with it independently and explained that this was really the only right way to do it, so thanks to him) (I had come up with this first but wanted to try other things before doing it (and wasn't really sure if user-defined messages were safe, not quite understanding the system); nsf came up with it independently and explained that this was really the only right way to do it, so thanks to him)
@ -21,31 +23,37 @@ the only recourse, and the one both Microsoft (http://support.microsoft.com/kb/1
yay. yay.
*/ */
var uitask chan interface{} var msghwnd _HWND
type uimsg struct {
call *syscall.LazyProc
p []uintptr
ret chan uiret
}
type uiret struct {
ret uintptr
err error
}
const ( const (
msgRequested = _WM_APP + iota + 1 // + 1 just to be safe msgQuit = _WM_APP + iota + 1 // + 1 just to be safe
msgQuit
msgSetAreaSize msgSetAreaSize
msgRepaintAll msgRepaintAll
msgCreateWindow
) )
var ( type uitaskParams struct {
_postMessage = user32.NewProc("PostMessageW") window *Window // createWindow
) control Control // createWindow
show bool // createWindow
}
var msghwnd _HWND // SendMessage() won't return unti lthe deed is done, even if the deed is on another thread
// SendMessage() does a thread switch if necessary
// this also means we don't have to worry about the uitaskParams object being garbage collected
func (_uitask) createWindow(w *Window, c Control, s bool) {
uc := &uitaskParams{
window: w,
control: c,
show: s,
}
_sendMessage.Call(
uintptr(msghwnd),
msgCreateWindow,
uintptr(0),
uintptr(unsafe.Pointer(uc)))
}
func uiinit() error { func uiinit() error {
err := doWindowsInit() err := doWindowsInit()
@ -58,25 +66,17 @@ func uiinit() error {
return fmt.Errorf("error making invisible window for handling events: %v", err) return fmt.Errorf("error making invisible window for handling events: %v", err)
} }
// do this only on success just to be safe
uitask = make(chan interface{})
return nil return nil
} }
var (
_postMessage = user32.NewProc("PostMessageW")
)
func ui() { func ui() {
go func() { go func() {
for { <-Stop
select { // PostMessage() so it gets handled after any events currently being processed complete
case m := <-uitask:
r1, _, err := _postMessage.Call(
uintptr(msghwnd),
msgRequested,
uintptr(0),
uintptr(unsafe.Pointer(&m)))
if r1 == 0 { // failure
panic("error sending message to message loop to call function: " + err.Error())
}
case <-Stop:
r1, _, err := _postMessage.Call( r1, _, err := _postMessage.Call(
uintptr(msghwnd), uintptr(msghwnd),
msgQuit, msgQuit,
@ -85,8 +85,6 @@ func ui() {
if r1 == 0 { // failure if r1 == 0 { // failure
panic("error sending quit message to message loop: " + err.Error()) panic("error sending quit message to message loop: " + err.Error())
} }
}
}
}() }()
msgloop() msgloop()
@ -180,23 +178,14 @@ func makeMessageHandler() (hwnd _HWND, err error) {
func messageHandlerWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT { func messageHandlerWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
switch uMsg { switch uMsg {
case msgRequested:
mt := (*interface{})(unsafe.Pointer(lParam))
switch m := (*mt).(type) {
case *uimsg:
r1, _, err := m.call.Call(m.p...)
m.ret <- uiret{
ret: r1,
err: err,
}
case func():
m()
}
return 0
case msgQuit: case msgQuit:
// does not return a value according to MSDN // does not return a value according to MSDN
_postQuitMessage.Call(0) _postQuitMessage.Call(0)
return 0 return 0
case msgCreateWindow:
uc := (*uitaskParams)(unsafe.Pointer(lParam))
uc.window.create(uc.control, uc.show)
return 0
} }
return defWindowProc(hwnd, uMsg, wParam, lParam) return defWindowProc(hwnd, uMsg, wParam, lParam)
} }

View File

@ -71,16 +71,15 @@ func (w *Window) SetSpaced(spaced bool) {
// Open creates the Window with Create and then shows the Window with Show. As with Create, you cannot call Open more than once per window. // Open creates the Window with Create and then shows the Window with Show. As with Create, you cannot call Open more than once per window.
func (w *Window) Open(control Control) { func (w *Window) Open(control Control) {
w.create(control, true) uitask.createWindow(w, control, true)
} }
// Create creates the Window, setting its control to the given control. It does not show the window. This can only be called once per window, and finalizes all initialization of the control. // Create creates the Window, setting its control to the given control. It does not show the window. This can only be called once per window, and finalizes all initialization of the control.
func (w *Window) Create(control Control) { func (w *Window) Create(control Control) {
w.create(control, false) uitask.createWindow(w, control, false)
} }
func (w *Window) create(control Control, show bool) { func (w *Window) create(control Control, show bool) {
touitask(func() {
if w.created { if w.created {
panic("window already open") panic("window already open")
} }
@ -111,7 +110,6 @@ func (w *Window) create(control Control, show bool) {
if show { if show {
w.Show() w.Show()
} }
})
} }
// Show shows the window. // Show shows the window.