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.
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
60
window.go
60
window.go
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue