mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
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:
+92
-35
@@ -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
|
||||||
}
|
}
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
for _, k := range str {
|
w := views[view]
|
||||||
if n, ok := convertKey(k); ok {
|
for _, k := range str {
|
||||||
w.w.event(key.ChordEvent{Name: n, Modifiers: kmods})
|
if n, ok := convertKey(k); ok {
|
||||||
|
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)
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.w.event(key.EditEvent{Text: str})
|
w := views[view]
|
||||||
|
w.w.event(key.EditEvent{Text: str})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onMouse
|
//export gio_onMouse
|
||||||
@@ -107,26 +150,32 @@ 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)
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.w.event(pointer.Event{
|
w := views[view]
|
||||||
Type: typ,
|
w.w.event(pointer.Event{
|
||||||
Source: pointer.Mouse,
|
Type: typ,
|
||||||
Time: t,
|
Source: pointer.Mouse,
|
||||||
Position: f32.Point{X: float32(x), Y: float32(y)},
|
Time: t,
|
||||||
Scroll: f32.Point{X: float32(dx), Y: float32(dy)},
|
Position: f32.Point{X: float32(x), Y: float32(y)},
|
||||||
|
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) {
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.draw(true)
|
w := views[view]
|
||||||
|
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) {
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.w.event(key.FocusEvent{Focus: focus == C.YES})
|
w := views[view]
|
||||||
|
w.w.event(key.FocusEvent{Focus: focus == C.YES})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) draw(sync bool) {
|
func (w *window) draw(sync bool) {
|
||||||
@@ -161,32 +210,40 @@ func getConfig() Config {
|
|||||||
|
|
||||||
//export gio_onTerminate
|
//export gio_onTerminate
|
||||||
func gio_onTerminate(view C.CFTypeRef) {
|
func gio_onTerminate(view C.CFTypeRef) {
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
delete(views, view)
|
w := views[view]
|
||||||
w.w.event(DestroyEvent{})
|
delete(views, view)
|
||||||
|
w.w.event(DestroyEvent{})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onHide
|
//export gio_onHide
|
||||||
func gio_onHide(view C.CFTypeRef) {
|
func gio_onHide(view C.CFTypeRef) {
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.setStage(StagePaused)
|
w := views[view]
|
||||||
|
w.setStage(StagePaused)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onShow
|
//export gio_onShow
|
||||||
func gio_onShow(view C.CFTypeRef) {
|
func gio_onShow(view C.CFTypeRef) {
|
||||||
w := views[view]
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
w.setStage(StageRunning)
|
w := views[view]
|
||||||
|
w.setStage(StageRunning)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//export gio_onCreate
|
//export gio_onCreate
|
||||||
func gio_onCreate(view C.CFTypeRef) {
|
func gio_onCreate(view C.CFTypeRef) {
|
||||||
w := &window{
|
viewDo(view, func(views viewMap, view C.CFTypeRef) {
|
||||||
view: view,
|
w := &window{
|
||||||
}
|
view: view,
|
||||||
wopts := <-mainWindow.out
|
}
|
||||||
w.w = wopts.window
|
wopts := <-mainWindow.out
|
||||||
w.w.setDriver(w)
|
w.w = wopts.window
|
||||||
views[view] = w
|
w.w.setDriver(w)
|
||||||
|
views[view] = w
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWindow(win *Window, opts *WindowOptions) error {
|
func createWindow(win *Window, opts *WindowOptions) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user