ui/app: (macOS) serialize access to the views map

The CVDisplayLink used for synchronizing rendering to the display
refresh rate runs on a separate thread from the main thread.
View callbacks must therefore be serialized. Implement that by
introducing a singleton goroutine that owns the view map and runs
incoming commands.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-19 01:18:48 +02:00
parent a36372a243
commit a140527b08
+60 -3
View File
@@ -15,6 +15,7 @@ import (
"errors" "errors"
"image" "image"
"runtime" "runtime"
"sync"
"time" "time"
"unsafe" "unsafe"
@@ -34,11 +35,43 @@ type window struct {
stage Stage stage Stage
} }
type viewCmd struct {
view C.CFTypeRef
f viewFunc
}
type viewFunc func(views viewMap, view C.CFTypeRef)
type viewMap map[C.CFTypeRef]*window
var (
viewOnce sync.Once
viewCmds = make(chan viewCmd)
viewAcks = make(chan struct{})
)
var mainWindow = newWindowRendezvous() var mainWindow = newWindowRendezvous()
var viewFactory func() C.CFTypeRef var viewFactory func() C.CFTypeRef
var views = make(map[C.CFTypeRef]*window) func viewDo(view C.CFTypeRef, f viewFunc) {
viewOnce.Do(func() {
go runViewCmdLoop()
})
viewCmds <- viewCmd{view, f}
<-viewAcks
}
func runViewCmdLoop() {
views := make(viewMap)
for {
select {
case cmd := <-viewCmds:
cmd.f(views, cmd.view)
viewAcks <- struct{}{}
}
}
}
func (w *window) contextView() C.CFTypeRef { func (w *window) contextView() C.CFTypeRef {
return w.view return w.view
@@ -62,12 +95,18 @@ func (w *window) setStage(stage Stage) {
w.w.event(StageEvent{stage}) w.w.event(StageEvent{stage})
} }
//export gio_onFrameCallback // Use a top level func for onFrameCallback to avoid
func gio_onFrameCallback(view C.CFTypeRef) { // garbage from viewDo.
func onFrameCmd(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.draw(false) w.draw(false)
} }
//export gio_onFrameCallback
func gio_onFrameCallback(view C.CFTypeRef) {
viewDo(view, onFrameCmd)
}
//export gio_onKeys //export gio_onKeys
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) { func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger) {
str := C.GoString(cstr) str := C.GoString(cstr)
@@ -78,19 +117,23 @@ func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger)
if mods&C.NSEventModifierFlagShift != 0 { if mods&C.NSEventModifierFlagShift != 0 {
kmods |= key.ModShift kmods |= key.ModShift
} }
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
for _, k := range str { for _, k := range str {
if n, ok := convertKey(k); ok { if n, ok := convertKey(k); ok {
w.w.event(key.ChordEvent{Name: n, Modifiers: kmods}) w.w.event(key.ChordEvent{Name: n, Modifiers: kmods})
} }
} }
})
} }
//export gio_onText //export gio_onText
func gio_onText(view C.CFTypeRef, cstr *C.char) { func gio_onText(view C.CFTypeRef, cstr *C.char) {
str := C.GoString(cstr) str := C.GoString(cstr)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.w.event(key.EditEvent{Text: str}) w.w.event(key.EditEvent{Text: str})
})
} }
//export gio_onMouse //export gio_onMouse
@@ -107,6 +150,7 @@ func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.doub
panic("invalid direction") panic("invalid direction")
} }
t := time.Duration(float64(ti)*float64(time.Second) + .5) t := time.Duration(float64(ti)*float64(time.Second) + .5)
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.w.event(pointer.Event{ w.w.event(pointer.Event{
Type: typ, Type: typ,
@@ -115,18 +159,23 @@ func gio_onMouse(view C.CFTypeRef, cdir C.int, x, y, dx, dy C.CGFloat, ti C.doub
Position: f32.Point{X: float32(x), Y: float32(y)}, Position: f32.Point{X: float32(x), Y: float32(y)},
Scroll: f32.Point{X: float32(dx), Y: float32(dy)}, Scroll: f32.Point{X: float32(dx), Y: float32(dy)},
}) })
})
} }
//export gio_onDraw //export gio_onDraw
func gio_onDraw(view C.CFTypeRef) { func gio_onDraw(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.draw(true) w.draw(true)
})
} }
//export gio_onFocus //export gio_onFocus
func gio_onFocus(view C.CFTypeRef, focus C.BOOL) { func gio_onFocus(view C.CFTypeRef, focus C.BOOL) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.w.event(key.FocusEvent{Focus: focus == C.YES}) w.w.event(key.FocusEvent{Focus: focus == C.YES})
})
} }
func (w *window) draw(sync bool) { func (w *window) draw(sync bool) {
@@ -161,25 +210,32 @@ func getConfig() Config {
//export gio_onTerminate //export gio_onTerminate
func gio_onTerminate(view C.CFTypeRef) { func gio_onTerminate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
delete(views, view) delete(views, view)
w.w.event(DestroyEvent{}) w.w.event(DestroyEvent{})
})
} }
//export gio_onHide //export gio_onHide
func gio_onHide(view C.CFTypeRef) { func gio_onHide(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.setStage(StagePaused) w.setStage(StagePaused)
})
} }
//export gio_onShow //export gio_onShow
func gio_onShow(view C.CFTypeRef) { func gio_onShow(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := views[view] w := views[view]
w.setStage(StageRunning) w.setStage(StageRunning)
})
} }
//export gio_onCreate //export gio_onCreate
func gio_onCreate(view C.CFTypeRef) { func gio_onCreate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) {
w := &window{ w := &window{
view: view, view: view,
} }
@@ -187,6 +243,7 @@ func gio_onCreate(view C.CFTypeRef) {
w.w = wopts.window w.w = wopts.window
w.w.setDriver(w) w.w.setDriver(w)
views[view] = w views[view] = w
})
} }
func createWindow(win *Window, opts *WindowOptions) error { func createWindow(win *Window, opts *WindowOptions) error {