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_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..5f5b9e8f 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,13 @@ type Window struct { } imeState editorState driver driver - // basic is the driver interface that is needed even after the window is gone. - basic basicDriver + + // once initializes pendingInvalidates. + once sync.Once + // pendingInvalidates ensures only one Invalidate may + // be pending at any time. + pendingInvalidates chan struct{} + // coalesced tracks the most recent events waiting to be delivered // to the client. coalesced eventSummary @@ -272,8 +278,10 @@ func (w *Window) updateState() { // // Invalidate is safe for concurrent use. func (w *Window) Invalidate() { - if w.basic != nil { - w.basic.Invalidate() + select { + case <-w.invalidateChan(): + w.driver.Invalidate() + default: } } @@ -283,7 +291,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 +386,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{}) { @@ -550,8 +555,9 @@ 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 }() + wakeup := s.wakeup + s.wakeup = false + c.w.invalidateOk() switch { case s.view != nil: e := *s.view @@ -561,6 +567,11 @@ func (c *callbacks) nextEvent() (event.Event, bool) { e := *s.destroy // Clear pending events after DestroyEvent is delivered. *s = eventSummary{} + // Don't allow invalidates from now on. + select { + case <-c.w.invalidateChan(): + default: + } return e, true case s.cfg != nil: e := *s.cfg @@ -570,12 +581,26 @@ func (c *callbacks) nextEvent() (event.Event, bool) { e := *s.frame s.frame = nil return e.FrameEvent, true - case s.wakeup: + case wakeup: return wakeupEvent{}, true } return nil, false } +func (w *Window) invalidateChan() chan struct{} { + w.once.Do(func() { + w.pendingInvalidates = make(chan struct{}, 1) + }) + return w.pendingInvalidates +} + +func (w *Window) invalidateOk() { + select { + case w.invalidateChan() <- struct{}{}: + default: + } +} + func (w *Window) processEvent(e event.Event) bool { switch e2 := e.(type) { case wakeupEvent: @@ -617,7 +642,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 +712,10 @@ 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() + return w.driver.Event() } func (w *Window) init() { @@ -832,7 +856,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 }