Restructured uitask to accept a limited range of actions. Implemented this on the Windows backend.
This commit is contained in:
parent
ad2c8d9033
commit
607e710459
27
uitask.go
27
uitask.go
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue