From 59e92e8233a48584c8e2c6833900678418b3ab82 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 13 Jul 2019 11:41:12 +0200 Subject: [PATCH] ui/app: introduce DestroyEvent for ending the event loop Replace the StageDead stage with DestroyEvent dedicated to ending the event loop and, for premature window closes, the error. Drop the error return from NewWindow; any errors in window creation will appear as an immediate DestroyEvent with its Err field set. Signed-off-by: Elias Naur --- ui/app/app.go | 24 ++++++++++++++++-------- ui/app/os_ios.go | 2 +- ui/app/os_macos.go | 2 +- ui/app/os_wayland.go | 6 ++++-- ui/app/os_windows.go | 6 ++++-- ui/app/window.go | 41 +++++++++++++++++++++++++---------------- 6 files changed, 51 insertions(+), 30 deletions(-) diff --git a/ui/app/app.go b/ui/app/app.go index 456dfeac..499e0d64 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -17,6 +17,8 @@ type Event interface { ImplementsEvent() } +// DrawEvent is sent when a Window's Draw +// method must be called. type DrawEvent struct { Config Config Size image.Point @@ -27,6 +29,14 @@ type DrawEvent struct { sync bool } +// DestroyEvent is the last event sent through +// a window event channel. +type DestroyEvent struct { + // Err is nil for normal window closures. If a + // window is prematurely closed, Err is the cause. + Err error +} + // Insets is the space taken up by // system decoration such as translucent // system bars and software keyboards. @@ -60,8 +70,7 @@ type windowAndOptions struct { } const ( - StageDead Stage = iota - StagePaused + StagePaused Stage = iota StageRunning ) @@ -93,8 +102,6 @@ var extraArgs string func (l Stage) String() string { switch l { - case StageDead: - return "StageDead" case StagePaused: return "StagePaused" case StageRunning: @@ -104,10 +111,6 @@ func (l Stage) String() string { } } -func (_ DrawEvent) ImplementsEvent() {} -func (_ StageEvent) ImplementsEvent() {} -func (_ *CommandEvent) ImplementsEvent() {} - func init() { args := strings.Split(extraArgs, "|") os.Args = append(os.Args, args...) @@ -178,3 +181,8 @@ func newWindowRendezvous() *windowRendezvous { }() return wr } + +func (_ DrawEvent) ImplementsEvent() {} +func (_ StageEvent) ImplementsEvent() {} +func (_ *CommandEvent) ImplementsEvent() {} +func (_ DestroyEvent) ImplementsEvent() {} diff --git a/ui/app/os_ios.go b/ui/app/os_ios.go index 2b86eccc..9ef63132 100644 --- a/ui/app/os_ios.go +++ b/ui/app/os_ios.go @@ -111,7 +111,7 @@ func onStop(view C.CFTypeRef) { func onDestroy(view C.CFTypeRef) { w := views[view] delete(views, view) - w.w.event(StageEvent{StageDead}) + w.w.event(DestroyEvent{}) C.gio_removeLayer(w.layer) C.CFRelease(w.layer) w.layer = 0 diff --git a/ui/app/os_macos.go b/ui/app/os_macos.go index f310197f..c9345c64 100644 --- a/ui/app/os_macos.go +++ b/ui/app/os_macos.go @@ -163,7 +163,7 @@ func getConfig() Config { func gio_onTerminate(view C.CFTypeRef) { w := views[view] delete(views, view) - w.setStage(StageDead) + w.w.event(DestroyEvent{}) } //export gio_onHide diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go index 2a2f3696..fd9043e3 100644 --- a/ui/app/os_wayland.go +++ b/ui/app/os_wayland.go @@ -113,6 +113,7 @@ type window struct { lastTouch f32.Point stage Stage + dead bool lastFrameCallback *C.struct_wl_callback mu sync.Mutex @@ -308,7 +309,8 @@ func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface //export gio_onToplevelClose func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) { w := winMap[topLvl] - w.setStage(StageDead) + w.dead = true + w.w.event(DestroyEvent{}) } //export gio_onToplevelConfigure @@ -769,7 +771,7 @@ loop: if ret := C.wl_display_flush(conn.disp); ret < 0 { break } - if w.stage == StageDead { + if w.dead { break } // Clear poll events. diff --git a/ui/app/os_windows.go b/ui/app/os_windows.go index a673b6bf..3527ff6e 100644 --- a/ui/app/os_windows.go +++ b/ui/app/os_windows.go @@ -61,6 +61,7 @@ type window struct { width int height int stage Stage + dead bool mu sync.Mutex animating bool @@ -319,7 +320,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr w.scrollEvent(wParam, lParam) case _WM_DESTROY: delete(winMap, hwnd) - w.setStage(StageDead) + w.dead = true + w.w.event(DestroyEvent{}) case _WM_REDRAW: w.mu.Lock() anim := w.animating @@ -368,7 +370,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr) { // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ func (w *window) loop() error { loop: - for w.stage > StageDead { + for !w.dead { var msg msg // Since posted messages are always returned before system messages, // but we want our WM_REDRAW to always come last, just like WM_PAINT. diff --git a/ui/app/window.go b/ui/app/window.go index 902af0d5..06fee21e 100644 --- a/ui/app/window.go +++ b/ui/app/window.go @@ -29,7 +29,6 @@ type Window struct { drawStart time.Time gpu *gpu.GPU inputState key.TextInputState - err error events chan Event @@ -70,7 +69,7 @@ var ackEvent Event // ignore or adjust them. // If the current program is running on iOS and Android, // NewWindow returns the window previously by the platform. -func NewWindow(opts *WindowOptions) (*Window, error) { +func NewWindow(opts *WindowOptions) *Window { if opts == nil { opts = &WindowOptions{ Width: ui.Dp(800), @@ -87,9 +86,11 @@ func NewWindow(opts *WindowOptions) (*Window, error) { stage: StagePaused, } if err := createWindow(w, opts); err != nil { - return nil, err + // For simplicity, NewWindow always succeeds. Send + // an immediate DestroyEvent instead of returning the error. + w.destroy(err) } - return w, nil + return w } func (w *Window) Events() <-chan Event { @@ -107,10 +108,6 @@ func (w *Window) setTextInput(s key.TextInputState) { w.inputState = s } -func (w *Window) Err() error { - return w.err -} - func (w *Window) Queue() input.Queue { return &w.router } @@ -144,12 +141,12 @@ func (w *Window) Draw(root *ui.Ops) { if w.gpu == nil { ctx, err := newContext(driver) if err != nil { - w.err = err + w.destroy(err) return } w.gpu, err = gpu.NewGPU(ctx) if err != nil { - w.err = err + w.destroy(err) return } } @@ -190,6 +187,9 @@ func (w *Window) updateAnimation() { w.delayedDraw.Stop() w.delayedDraw = nil } + if !w.isAlive() { + return + } if w.stage >= StageRunning && w.hasNextFrame { if dt := time.Until(w.nextFrame); dt <= 0 { animate = true @@ -223,7 +223,7 @@ func (w *Window) Stage() Stage { } func (w *Window) isAlive() bool { - return w.stage > StageDead && w.err == nil + return w.driver != nil } func (w *Window) contextDriver() interface{} { @@ -236,10 +236,18 @@ func (w *Window) setDriver(d *window) { w.driver = d } +func (w *Window) destroy(err error) { + w.setDriver(nil) + go func() { + w.event(DestroyEvent{err}) + }() +} + func (w *Window) event(e Event) { w.eventLock.Lock() defer w.eventLock.Unlock() w.mu.Lock() + died := false needAck := false switch e := e.(type) { case input.Event: @@ -248,12 +256,13 @@ func (w *Window) event(e Event) { } case *CommandEvent: needAck = true + case DestroyEvent: + w.driver = nil + died = true case StageEvent: w.stage = e.Stage - if w.stage > StageDead { - needAck = true - w.syncGPU = true - } + needAck = true + w.syncGPU = true case DrawEvent: if e.Size == (image.Point{}) { panic(errors.New("internal error: zero-sized Draw")) @@ -290,7 +299,7 @@ func (w *Window) event(e Event) { w.gpu.Refresh() } } - if stage == StageDead { + if died { close(w.events) } }