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.
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.
// TODO make sure this acts sanely if called from uitask itself
func touitask(f func()) {
done := make(chan struct{})
defer close(done)
go func() { // to avoid locking uitask itself
done2 := make(chan struct{}) // make the chain uitask <- f <- uitask to avoid deadlocks
defer close(done2)
uitask <- func() {
f()
done2 <- struct{}{}
}
done <- <-done2
}()
<-done
}
// uitask is an object of a type implemented by each uitask_***.go that does everything that needs to be communicated to the main thread.
type _uitask struct{}
var uitask = _uitask{}
// and the required methods are:
var xuitask interface {
// creates a window
// TODO whether this waits for the window creation to finish is implementation defined?
createWindow(*Window, Control, bool)
} = uitask
// compilation will fail if uitask doesn't have all these methods

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)
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)
@ -21,31 +23,37 @@ the only recourse, and the one both Microsoft (http://support.microsoft.com/kb/1
yay.
*/
var uitask chan interface{}
type uimsg struct {
call *syscall.LazyProc
p []uintptr
ret chan uiret
}
type uiret struct {
ret uintptr
err error
}
var msghwnd _HWND
const (
msgRequested = _WM_APP + iota + 1 // + 1 just to be safe
msgQuit
msgQuit = _WM_APP + iota + 1 // + 1 just to be safe
msgSetAreaSize
msgRepaintAll
msgCreateWindow
)
var (
_postMessage = user32.NewProc("PostMessageW")
)
type uitaskParams struct {
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 {
err := doWindowsInit()
@ -58,34 +66,24 @@ func uiinit() error {
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
}
var (
_postMessage = user32.NewProc("PostMessageW")
)
func ui() {
go func() {
for {
select {
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(
uintptr(msghwnd),
msgQuit,
uintptr(0),
uintptr(0))
if r1 == 0 { // failure
panic("error sending quit message to message loop: " + err.Error())
}
}
<-Stop
// PostMessage() so it gets handled after any events currently being processed complete
r1, _, err := _postMessage.Call(
uintptr(msghwnd),
msgQuit,
uintptr(0),
uintptr(0))
if r1 == 0 { // failure
panic("error sending quit message to message loop: " + err.Error())
}
}()
@ -180,23 +178,14 @@ func makeMessageHandler() (hwnd _HWND, err error) {
func messageHandlerWndProc(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT {
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:
// does not return a value according to MSDN
_postQuitMessage.Call(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)
}

View File

@ -71,47 +71,45 @@ 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.
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.
func (w *Window) Create(control Control) {
w.create(control, false)
uitask.createWindow(w, control, false)
}
func (w *Window) create(control Control, show bool) {
touitask(func() {
if w.created {
panic("window already open")
if w.created {
panic("window already open")
}
w.sysData.spaced = w.spaced
w.sysData.close = w.Closing
if w.sysData.close == nil {
w.sysData.close = func() bool {
return false
}
w.sysData.spaced = w.spaced
w.sysData.close = w.Closing
if w.sysData.close == nil {
w.sysData.close = func() bool {
return false
}
}
err := w.sysData.make(nil)
}
err := w.sysData.make(nil)
if err != nil {
panic(fmt.Errorf("error opening window: %v", err))
}
if control != nil {
w.sysData.allocate = control.allocate
err = control.make(w.sysData)
if err != nil {
panic(fmt.Errorf("error opening window: %v", err))
panic(fmt.Errorf("error adding window's control: %v", err))
}
if control != nil {
w.sysData.allocate = control.allocate
err = control.make(w.sysData)
if err != nil {
panic(fmt.Errorf("error adding window's control: %v", err))
}
}
err = w.sysData.setWindowSize(w.initWidth, w.initHeight)
if err != nil {
panic(fmt.Errorf("error setting window size (in Window.Open()): %v", err))
}
w.sysData.setText(w.initTitle)
w.created = true
if show {
w.Show()
}
})
}
err = w.sysData.setWindowSize(w.initWidth, w.initHeight)
if err != nil {
panic(fmt.Errorf("error setting window size (in Window.Open()): %v", err))
}
w.sysData.setText(w.initTitle)
w.created = true
if show {
w.Show()
}
}
// Show shows the window.