ui/app: fix race condition between Window.Redraw and Draw

A Window.Redraw called after the client has completed building a
frame and before Window.Draw resets the redraw flag is effectively
ignored. Move the flag reset earlier to just before the client is
asked to build the frame, to ensure that no state updates are lost.

Tighten and simplify the locking while we're here.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-04 16:31:48 +02:00
parent f1dffa155c
commit 30a558d10d
+27 -26
View File
@@ -96,9 +96,6 @@ func (w *Window) Err() error {
} }
func (w *Window) Draw(root *ui.Ops) { func (w *Window) Draw(root *ui.Ops) {
if !w.IsAlive() {
return
}
w.mu.Lock() w.mu.Lock()
var drawDur time.Duration var drawDur time.Duration
if !w.drawStart.IsZero() { if !w.drawStart.IsZero() {
@@ -107,11 +104,10 @@ func (w *Window) Draw(root *ui.Ops) {
} }
stage := w.stage stage := w.stage
sync := w.syncGPU sync := w.syncGPU
size := w.size
w.hasNextFrame = false
w.syncGPU = false w.syncGPU = false
alive := w.isAlive()
w.mu.Unlock() w.mu.Unlock()
if stage < StageRunning { if !alive || stage < StageRunning {
return return
} }
if w.gpu != nil { if w.gpu != nil {
@@ -135,7 +131,11 @@ func (w *Window) Draw(root *ui.Ops) {
return return
} }
} }
w.reader.Reset(root)
redrawTime, redraw := collectRedraws(&w.reader)
now := time.Now() now := time.Now()
w.mu.Lock()
size := w.size
frameDur := now.Sub(w.lastFrame) frameDur := now.Sub(w.lastFrame)
frameDur = frameDur.Truncate(100 * time.Microsecond) frameDur = frameDur.Truncate(100 * time.Microsecond)
w.lastFrame = now w.lastFrame = now
@@ -144,11 +144,11 @@ func (w *Window) Draw(root *ui.Ops) {
w.timings = fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings()) w.timings = fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings())
w.setNextFrame(time.Time{}) w.setNextFrame(time.Time{})
} }
w.reader.Reset(root) if redraw {
if t, ok := collectRedraws(&w.reader); ok { w.setNextFrame(redrawTime)
w.setNextFrame(t)
} }
w.updateAnimation() w.updateAnimation()
w.mu.Unlock()
w.gpu.Draw(w.Profiling, size, root) w.gpu.Draw(w.Profiling, size, root)
} }
@@ -174,7 +174,9 @@ func collectRedraws(r *ui.OpsReader) (time.Time, bool) {
} }
func (w *Window) Redraw() { func (w *Window) Redraw() {
if !w.IsAlive() { w.mu.Lock()
defer w.mu.Unlock()
if !w.isAlive() {
return return
} }
w.setNextFrame(time.Time{}) w.setNextFrame(time.Time{})
@@ -182,9 +184,11 @@ func (w *Window) Redraw() {
} }
func (w *Window) updateAnimation() { func (w *Window) updateAnimation() {
w.mu.Lock()
defer w.mu.Unlock()
animate := false animate := false
if w.delayedDraw != nil {
w.delayedDraw.Stop()
w.delayedDraw = nil
}
if w.stage >= StageRunning && w.hasNextFrame { if w.stage >= StageRunning && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 { if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true animate = true
@@ -199,13 +203,7 @@ func (w *Window) updateAnimation() {
} }
func (w *Window) setNextFrame(at time.Time) { func (w *Window) setNextFrame(at time.Time) {
w.mu.Lock()
defer w.mu.Unlock()
if !w.hasNextFrame || at.Before(w.nextFrame) { if !w.hasNextFrame || at.Before(w.nextFrame) {
if w.delayedDraw != nil {
w.delayedDraw.Stop()
w.delayedDraw = nil
}
w.hasNextFrame = true w.hasNextFrame = true
w.nextFrame = at w.nextFrame = at
} }
@@ -224,7 +222,13 @@ func (w *Window) Stage() Stage {
} }
func (w *Window) IsAlive() bool { func (w *Window) IsAlive() bool {
return w.Stage() != StageDead && w.err == nil w.mu.Lock()
defer w.mu.Unlock()
return w.isAlive()
}
func (w *Window) isAlive() bool {
return w.stage > StageDead && w.err == nil
} }
func (w *Window) contextDriver() interface{} { func (w *Window) contextDriver() interface{} {
@@ -236,13 +240,12 @@ func (w *Window) event(e Event) {
defer w.eventLock.Unlock() defer w.eventLock.Unlock()
w.mu.Lock() w.mu.Lock()
needAck := false needAck := false
needRedraw := false
switch e := e.(type) { switch e := e.(type) {
case input.Event: case input.Event:
needRedraw = true w.setNextFrame(time.Time{})
case *CommandEvent: case *CommandEvent:
needAck = true needAck = true
needRedraw = true w.setNextFrame(time.Time{})
case StageEvent: case StageEvent:
w.stage = e.Stage w.stage = e.Stage
if w.stage > StageDead { if w.stage > StageDead {
@@ -259,15 +262,13 @@ func (w *Window) event(e Event) {
} }
w.drawStart = time.Now() w.drawStart = time.Now()
needAck = true needAck = true
w.hasNextFrame = false
w.syncGPU = e.sync w.syncGPU = e.sync
w.size = e.Size w.size = e.Size
} }
stage := w.stage stage := w.stage
w.mu.Unlock()
if needRedraw {
w.setNextFrame(time.Time{})
}
w.updateAnimation() w.updateAnimation()
w.mu.Unlock()
w.events <- e w.events <- e
if needAck { if needAck {
// Send a dummy event; when it gets through we // Send a dummy event; when it gets through we