diff --git a/app/os.go b/app/os.go index e1ded757..28ed35f8 100644 --- a/app/os.go +++ b/app/os.go @@ -173,19 +173,13 @@ type context interface { Unlock() } -// basicDriver is the subset of [driver] that may be called even after -// a window is destroyed. -type basicDriver interface { +// driver is the interface for the platform implementation +// of a window. +type driver interface { // Event blocks until an event is available and returns it. Event() event.Event // Invalidate requests a FrameEvent. Invalidate() -} - -// driver is the interface for the platform implementation -// of a window. -type driver interface { - basicDriver // SetAnimating sets the animation flag. When the window is animating, // FrameEvents are delivered as fast as the display can handle them. SetAnimating(anim bool) diff --git a/app/os_unix.go b/app/os_unix.go index b91cf89a..933b8ff5 100644 --- a/app/os_unix.go +++ b/app/os_unix.go @@ -9,7 +9,6 @@ import ( "errors" "unsafe" - "gioui.org/io/event" "gioui.org/io/pointer" ) @@ -57,35 +56,12 @@ func newWindow(window *callbacks, options []Option) { errFirst = err } } - window.SetDriver(&dummyDriver{ - win: window, - wakeups: make(chan event.Event, 1), - }) if errFirst == nil { errFirst = errors.New("app: no window driver available") } window.ProcessEvent(DestroyEvent{Err: errFirst}) } -type dummyDriver struct { - win *callbacks - wakeups chan event.Event -} - -func (d *dummyDriver) Event() event.Event { - if e, ok := d.win.nextEvent(); ok { - return e - } - return <-d.wakeups -} - -func (d *dummyDriver) Invalidate() { - select { - case d.wakeups <- wakeupEvent{}: - default: - } -} - // xCursor contains mapping from pointer.Cursor to XCursor. var xCursor = [...]string{ pointer.CursorDefault: "left_ptr", diff --git a/app/os_wayland.go b/app/os_wayland.go index 0689655d..393f4c7f 100644 --- a/app/os_wayland.go +++ b/app/os_wayland.go @@ -217,10 +217,6 @@ type window struct { wakeups chan struct{} closing bool - - // invMu avoids the race between the destruction of disp and - // Invalidate waking it up. - invMu sync.Mutex } type poller struct { @@ -1369,10 +1365,8 @@ func (w *window) close(err error) { w.ProcessEvent(WaylandViewEvent{}) w.ProcessEvent(DestroyEvent{Err: err}) w.destroy() - w.invMu.Lock() w.disp.destroy() w.disp = nil - w.invMu.Unlock() } func (w *window) dispatch() { @@ -1416,11 +1410,7 @@ func (w *window) Invalidate() { default: return } - w.invMu.Lock() - defer w.invMu.Unlock() - if w.disp != nil { - w.disp.wakeup() - } + w.disp.wakeup() } func (w *window) Run(f func()) { diff --git a/app/os_windows.go b/app/os_windows.go index 72489270..888391b7 100644 --- a/app/os_windows.go +++ b/app/os_windows.go @@ -54,9 +54,6 @@ type window struct { borderSize image.Point config Config loop *eventLoop - - // invMu avoids the race between destroying the window and Invalidate. - invMu sync.Mutex } const _WM_WAKEUP = windows.WM_USER + iota @@ -304,10 +301,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr windows.ReleaseDC(w.hdc) w.hdc = 0 } - w.invMu.Lock() // The system destroys the HWND for us. w.hwnd = 0 - w.invMu.Unlock() windows.PostQuitMessage(0) case windows.WM_NCCALCSIZE: if w.config.Decorated { @@ -620,13 +615,6 @@ func (w *window) Frame(frame *op.Ops) { } func (w *window) wakeup() { - w.invMu.Lock() - defer w.invMu.Unlock() - if w.hwnd == 0 { - w.loop.Wakeup() - w.loop.FlushEvents() - return - } if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil { panic(err) } diff --git a/app/os_x11.go b/app/os_x11.go index 3865f25d..83e6b351 100644 --- a/app/os_x11.go +++ b/app/os_x11.go @@ -113,9 +113,6 @@ type x11Window struct { wakeups chan struct{} handler x11EventHandler buf [100]byte - - // invMy avoids the race between destroy and Invalidate. - invMu sync.Mutex } var ( @@ -416,11 +413,6 @@ func (w *x11Window) Invalidate() { case w.wakeups <- struct{}{}: default: } - w.invMu.Lock() - defer w.invMu.Unlock() - if w.x == nil { - return - } if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN { panic(fmt.Errorf("failed to write to pipe: %v", err)) } @@ -509,8 +501,6 @@ func (w *x11Window) dispatch() { } func (w *x11Window) destroy() { - w.invMu.Lock() - defer w.invMu.Unlock() if w.notify.write != 0 { syscall.Close(w.notify.write) w.notify.write = 0 diff --git a/app/window.go b/app/window.go index 766e9321..fc7cf790 100644 --- a/app/window.go +++ b/app/window.go @@ -9,6 +9,7 @@ import ( "image/color" "reflect" "runtime" + "sync" "time" "unicode/utf8" @@ -89,8 +90,11 @@ type Window struct { } imeState editorState driver driver - // basic is the driver interface that is needed even after the window is gone. - basic basicDriver + + // invMu protects mayInvalidate. + invMu sync.Mutex + mayInvalidate bool + // coalesced tracks the most recent events waiting to be delivered // to the client. coalesced eventSummary @@ -272,8 +276,11 @@ func (w *Window) updateState() { // // Invalidate is safe for concurrent use. func (w *Window) Invalidate() { - if w.basic != nil { - w.basic.Invalidate() + w.invMu.Lock() + defer w.invMu.Unlock() + if w.mayInvalidate { + w.mayInvalidate = false + w.driver.Invalidate() } } @@ -283,7 +290,7 @@ func (w *Window) Option(opts ...Option) { if len(opts) == 0 { return } - if w.basic == nil { + if w.driver == nil { w.initialOpts = append(w.initialOpts, opts...) return } @@ -378,11 +385,8 @@ func (w *Window) setNextFrame(at time.Time) { } } -func (c *callbacks) SetDriver(d basicDriver) { - c.w.basic = d - if d, ok := d.(driver); ok { - c.w.driver = d - } +func (c *callbacks) SetDriver(d driver) { + c.w.driver = d } func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) { @@ -549,9 +553,19 @@ func (c *callbacks) Invalidate() { } func (c *callbacks) nextEvent() (event.Event, bool) { - s := &c.w.coalesced - // Every event counts as a wakeup. - defer func() { s.wakeup = false }() + return c.w.nextEvent() +} + +func (w *Window) nextEvent() (event.Event, bool) { + s := &w.coalesced + mayInvalidate := true + defer func() { + // Every event counts as a wakeup. + s.wakeup = false + w.invMu.Lock() + defer w.invMu.Unlock() + w.mayInvalidate = mayInvalidate + }() switch { case s.view != nil: e := *s.view @@ -561,6 +575,7 @@ func (c *callbacks) nextEvent() (event.Event, bool) { e := *s.destroy // Clear pending events after DestroyEvent is delivered. *s = eventSummary{} + mayInvalidate = false return e, true case s.cfg != nil: e := *s.cfg @@ -573,6 +588,7 @@ func (c *callbacks) nextEvent() (event.Event, bool) { case s.wakeup: return wakeupEvent{}, true } + mayInvalidate = false return nil, false } @@ -617,7 +633,6 @@ func (w *Window) processEvent(e event.Event) bool { case DestroyEvent: w.destroyGPU() w.driver = nil - w.basic = nil if q := w.timer.quit; q != nil { q <- struct{}{} <-q @@ -688,10 +703,17 @@ func (w *Window) processEvent(e event.Event) bool { // [FrameEvent], or until [Invalidate] is called. The window is created // and shown the first time Event is called. func (w *Window) Event() event.Event { - if w.basic == nil { + if w.driver == nil { w.init() } - return w.basic.Event() + if w.driver == nil { + e, ok := w.nextEvent() + if !ok { + panic("window initializion failed without a DestroyEvent") + } + return e + } + return w.driver.Event() } func (w *Window) init() { @@ -832,7 +854,7 @@ func (w *Window) Perform(actions system.Action) { if acts == 0 { return } - if w.basic == nil { + if w.driver == nil { w.initialActions = append(w.initialActions, acts) return }