app: let drivers control Window directly

Before this change, Window driver callbacks would all go through
channels to be processed by Window.run. However, Window.run may call
into the driver, which again may invoke a Window callback. These
re-entrant calls have been a source of deadlocks and subtle errors,
resulting in increasingly complex channel logic. This change eliminates
the goroutine split between Window and the driver, allowing callbacks
between Window and the driver without restrictions.

The goroutine split between Window and the driver is historical and
was meant to tame the complicated callback logic of drivers into a nice
for-select loop. However, the complexity isn't worth the gain, and there is
no concurrency concerns because there is always a 1:1 correspondance between
a driver goroutine and its Window object.

Fixes: https://todo.sr.ht/~eliasnaur/gio/329
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2022-01-04 14:48:38 +01:00
parent 72b2f2c1bf
commit 4f5baa9a51
2 changed files with 199 additions and 328 deletions
+3 -6
View File
@@ -165,6 +165,7 @@ type window struct {
hoverID router.SemanticID hoverID router.SemanticID
rootID router.SemanticID rootID router.SemanticID
focusID router.SemanticID focusID router.SemanticID
diffs []router.SemanticID
} }
} }
@@ -856,12 +857,8 @@ func (w *window) draw(env *C.JNIEnv, sync bool) {
w.semantic.rootID = newR w.semantic.rootID = newR
callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR))) callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR)))
} }
diffs := w.callbacks.RequestSemanticDiffs() w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0])
for { for _, id := range w.semantic.diffs {
id := <-diffs
if id == 0 {
break
}
callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id))) callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id)))
} }
} }
+196 -322
View File
@@ -34,19 +34,21 @@ type Window struct {
// driverFuncs is a channel of functions to run when // driverFuncs is a channel of functions to run when
// the Window has a valid driver. // the Window has a valid driver.
driverFuncs chan func(d driver) driverFuncs chan func(d driver)
// driverDefers is like driverFuncs for functions that may
// block and shouldn't be waited for.
driverDefers chan func(d driver)
// wakeups wakes up the native event loop to send a // wakeups wakes up the native event loop to send a
// WakeupEvent that flushes driverFuncs. // WakeupEvent that flushes driverFuncs.
wakeups chan struct{} wakeups chan struct{}
// wakeupFuncs is sent wakeup functions when the driver changes.
wakeupFuncs chan func()
// redraws is notified when a redraw is requested by the client.
redraws chan struct{}
// immediateRedraws is like redraw but doesn't need a wakeup.
immediateRedraws chan struct{}
// scheduledRedraws is sent the most recent delayed redraw time.
scheduledRedraws chan time.Time
out chan event.Event out chan event.Event
in chan event.Event frames chan *op.Ops
ack chan struct{} frameAck chan struct{}
invalidates chan struct{}
frames chan *op.Ops
frameAck chan struct{}
// dead is closed when the window is destroyed. // dead is closed when the window is destroyed.
dead chan struct{} dead chan struct{}
@@ -66,25 +68,6 @@ type Window struct {
// semantic data, lazily evaluated if requested by a backend to speed up // semantic data, lazily evaluated if requested by a backend to speed up
// the cases where semantic data is not needed. // the cases where semantic data is not needed.
semantic struct { semantic struct {
// requestDiffs is notified when a backend requests the list of changed
// semantic ids.
requestDiffs chan struct{}
// diffs is sent every changed semantic id when semRequestDiffs is requested,
// ending with the zero id.
diffs chan router.SemanticID
// lookups is sent semantic IDs for lookup.
lookups chan router.SemanticID
// results is sent the responses for semLookups queries.
results chan semanticResult
// requestRoots is sent request for the root ID.
requestRoots chan struct{}
// roots is sent root IDs when requested throught queryRoots.
roots chan router.SemanticID
// positions is sent positional requests.
positions chan f32.Point
// positionIDs is sent results for positions requests.
positionIDs chan semanticID
// uptodate tracks whether the fields below are up to date. // uptodate tracks whether the fields below are up to date.
uptodate bool uptodate bool
root router.SemanticID root router.SemanticID
@@ -94,11 +77,6 @@ type Window struct {
} }
} }
type semanticID struct {
found bool
id router.SemanticID
}
type semanticResult struct { type semanticResult struct {
found bool found bool
node router.SemanticNode node router.SemanticNode
@@ -115,11 +93,6 @@ type queue struct {
q router.Router q router.Router
} }
// driverEvent is sent when the underlying driver changes.
type driverEvent struct {
wakeup func()
}
// Pre-allocate the ack event to avoid garbage. // Pre-allocate the ack event to avoid garbage.
var ackEvent event.Event var ackEvent event.Event
@@ -143,28 +116,19 @@ func NewWindow(options ...Option) *Window {
cnf.apply(unit.Metric{}, options) cnf.apply(unit.Metric{}, options)
w := &Window{ w := &Window{
in: make(chan event.Event), out: make(chan event.Event),
out: make(chan event.Event), immediateRedraws: make(chan struct{}, 0),
ack: make(chan struct{}), redraws: make(chan struct{}, 1),
invalidates: make(chan struct{}, 1), scheduledRedraws: make(chan time.Time, 1),
frames: make(chan *op.Ops), frames: make(chan *op.Ops),
frameAck: make(chan struct{}), frameAck: make(chan struct{}),
driverFuncs: make(chan func(d driver), 1), driverFuncs: make(chan func(d driver), 1),
driverDefers: make(chan func(d driver), 1), wakeups: make(chan struct{}, 1),
wakeups: make(chan struct{}, 1), wakeupFuncs: make(chan func()),
dead: make(chan struct{}), dead: make(chan struct{}),
nocontext: cnf.CustomRenderer, nocontext: cnf.CustomRenderer,
} }
w.semantic.ids = make(map[router.SemanticID]router.SemanticNode) w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
w.semantic.lookups = make(chan router.SemanticID)
w.semantic.results = make(chan semanticResult)
w.semantic.requestDiffs = make(chan struct{})
w.semantic.requestRoots = make(chan struct{})
w.semantic.roots = make(chan router.SemanticID)
w.semantic.positions = make(chan f32.Point)
w.semantic.positionIDs = make(chan semanticID)
// Add buffer to limit context switching when the diff is large.
w.semantic.diffs = make(chan router.SemanticID, 50)
w.callbacks.w = w w.callbacks.w = w
go w.run(options) go w.run(options)
return w return w
@@ -183,14 +147,12 @@ func (w *Window) update(frame *op.Ops) {
<-w.frameAck <-w.frameAck
} }
func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error { func (w *Window) validateAndProcess(d driver, frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
for { for {
if w.gpu == nil && !w.nocontext { if w.gpu == nil && !w.nocontext {
var err error var err error
if w.ctx == nil { if w.ctx == nil {
w.driverRun(func(d driver) { w.ctx, err = d.NewContext()
w.ctx, err = d.NewContext()
})
if err != nil { if err != nil {
return err return err
} }
@@ -198,11 +160,7 @@ func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync
} }
} }
if sync && w.ctx != nil { if sync && w.ctx != nil {
var err error if err := w.ctx.Refresh(); err != nil {
w.driverRun(func(d driver) {
err = w.ctx.Refresh()
})
if err != nil {
if errors.Is(err, errOutOfDate) { if errors.Is(err, errOutOfDate) {
// Surface couldn't be created for transient reasons. Skip // Surface couldn't be created for transient reasons. Skip
// this frame and wait for the next. // this frame and wait for the next.
@@ -242,7 +200,7 @@ func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync
return err return err
} }
} }
w.processFrame(frameStart, frame) w.processFrame(d, frameStart, frame)
return nil return nil
} }
} }
@@ -269,7 +227,7 @@ func (w *Window) render(frame *op.Ops, viewport image.Point) error {
return w.ctx.Present() return w.ctx.Present()
} }
func (w *Window) processFrame(frameStart time.Time, frame *op.Ops) { func (w *Window) processFrame(d driver, frameStart time.Time, frame *op.Ops) {
w.queue.q.Frame(frame) w.queue.q.Frame(frame)
for k := range w.semantic.ids { for k := range w.semantic.ids {
delete(w.semantic.ids, k) delete(w.semantic.ids, k)
@@ -277,12 +235,12 @@ func (w *Window) processFrame(frameStart time.Time, frame *op.Ops) {
w.semantic.uptodate = false w.semantic.uptodate = false
switch w.queue.q.TextInputState() { switch w.queue.q.TextInputState() {
case router.TextInputOpen: case router.TextInputOpen:
w.driverDefer(func(d driver) { d.ShowTextInput(true) }) d.ShowTextInput(true)
case router.TextInputClose: case router.TextInputClose:
w.driverDefer(func(d driver) { d.ShowTextInput(false) }) d.ShowTextInput(false)
} }
if hint, ok := w.queue.q.TextInputHint(); ok { if hint, ok := w.queue.q.TextInputHint(); ok {
w.driverDefer(func(d driver) { d.SetInputHint(hint) }) d.SetInputHint(hint)
} }
if txt, ok := w.queue.q.WriteClipboard(); ok { if txt, ok := w.queue.q.WriteClipboard(); ok {
w.WriteClipboard(txt) w.WriteClipboard(txt)
@@ -300,14 +258,7 @@ func (w *Window) processFrame(frameStart time.Time, frame *op.Ops) {
if t, ok := w.queue.q.WakeupTime(); ok { if t, ok := w.queue.q.WakeupTime(); ok {
w.setNextFrame(t) w.setNextFrame(t)
} }
// Opportunistically check whether Invalidate has been called, to avoid w.updateAnimation(d)
// stopping and starting animation mode.
select {
case <-w.invalidates:
w.setNextFrame(time.Time{})
default:
}
w.updateAnimation()
} }
// Invalidate the window such that a FrameEvent will be generated immediately. // Invalidate the window such that a FrameEvent will be generated immediately.
@@ -320,7 +271,13 @@ func (w *Window) processFrame(frameStart time.Time, frame *op.Ops) {
// Invalidate is safe for concurrent use. // Invalidate is safe for concurrent use.
func (w *Window) Invalidate() { func (w *Window) Invalidate() {
select { select {
case w.invalidates <- struct{}{}: case w.immediateRedraws <- struct{}{}:
return
default:
}
select {
case w.redraws <- struct{}{}:
w.wakeup()
default: default:
} }
} }
@@ -391,71 +348,47 @@ func (w *Window) Center() {
// Note that most programs should not call Run; configuring a Window with // Note that most programs should not call Run; configuring a Window with
// CustomRenderer is a notable exception. // CustomRenderer is a notable exception.
func (w *Window) Run(f func()) { func (w *Window) Run(f func()) {
w.driverRun(func(_ driver) { done := make(chan struct{})
w.driverDefer(func(d driver) {
defer close(done)
f() f()
}) })
}
// driverRun runs f on the driver event goroutine and returns when f has
// completed. It can only be called during the processing of an event from
// w.in.
func (w *Window) driverRun(f func(d driver)) {
done := make(chan struct{})
wrapper := func(d driver) {
defer close(done)
f(d)
}
select { select {
case w.driverFuncs <- wrapper: case <-done:
select {
case <-done:
case <-w.dead:
}
case <-w.dead: case <-w.dead:
} }
} }
// driverDefer is like driverRun but can be run from any context. It doesn't wait // driverDefer is like Run but can be run from any context. It doesn't wait
// for f to return. // for f to return.
func (w *Window) driverDefer(f func(d driver)) { func (w *Window) driverDefer(f func(d driver)) {
select { select {
case w.driverDefers <- f: case w.driverFuncs <- f:
w.wakeup() w.wakeup()
case <-w.dead: case <-w.dead:
} }
} }
func (w *Window) updateAnimation() { func (w *Window) updateAnimation(d driver) {
animate := false animate := false
if w.delayedDraw != nil {
w.delayedDraw.Stop()
w.delayedDraw = nil
}
if w.stage >= system.StageRunning && w.hasNextFrame { if w.stage >= system.StageRunning && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 { if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true animate = true
} else { } else {
w.delayedDraw = time.NewTimer(dt) // Schedule redraw.
select {
case <-w.scheduledRedraws:
default:
}
w.scheduledRedraws <- w.nextFrame
} }
} }
if animate != w.animating { if animate != w.animating {
w.animating = animate w.animating = animate
if animate { d.SetAnimating(animate)
w.driverDefer(enableAnim)
} else {
w.driverDefer(disableAnim)
}
} }
} }
func enableAnim(d driver) {
d.SetAnimating(true)
}
func disableAnim(d driver) {
d.SetAnimating(false)
}
func (w *Window) wakeup() { func (w *Window) wakeup() {
select { select {
case w.wakeups <- struct{}{}: case w.wakeups <- struct{}{}:
@@ -476,100 +409,54 @@ func (c *callbacks) SetDriver(d driver) {
if d != nil { if d != nil {
wakeup = d.Wakeup wakeup = d.Wakeup
} }
c.Event(driverEvent{wakeup}) c.w.wakeupFuncs <- wakeup
} }
func (c *callbacks) Event(e event.Event) { func (c *callbacks) Event(e event.Event) {
deferChan := c.w.driverDefers
if c.d == nil { if c.d == nil {
deferChan = nil panic("event while no driver active")
}
for {
select {
case f := <-deferChan:
f(c.d)
case c.w.in <- e:
c.w.runFuncs(c.d)
return
case <-c.w.dead:
return
}
} }
c.w.processEvent(c.d, e)
c.w.updateState(c.d)
} }
// SemanticRoot returns the ID of the semantic root. // SemanticRoot returns the ID of the semantic root.
func (c *callbacks) SemanticRoot() router.SemanticID { func (c *callbacks) SemanticRoot() router.SemanticID {
c.w.semantic.requestRoots <- struct{}{} c.w.updateSemantics()
return <-c.w.semantic.roots return c.w.semantic.root
} }
// LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root. // LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
func (c *callbacks) LookupSemantic(semID router.SemanticID) (router.SemanticNode, bool) { func (c *callbacks) LookupSemantic(semID router.SemanticID) (router.SemanticNode, bool) {
c.w.semantic.lookups <- semID c.w.updateSemantics()
res := <-c.w.semantic.results n, found := c.w.semantic.ids[semID]
return res.node, res.found return n, found
} }
func (c *callbacks) RequestSemanticDiffs() <-chan router.SemanticID { func (c *callbacks) AppendSemanticDiffs(diffs []router.SemanticID) []router.SemanticID {
c.w.semantic.requestDiffs <- struct{}{} c.w.updateSemantics()
return c.w.semantic.diffs if tree := c.w.semantic.prevTree; len(tree) > 0 {
c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
}
return diffs
} }
func (c *callbacks) SemanticAt(pos f32.Point) (router.SemanticID, bool) { func (c *callbacks) SemanticAt(pos f32.Point) (router.SemanticID, bool) {
c.w.semantic.positions <- pos c.w.updateSemantics()
res := <-c.w.semantic.positionIDs return c.w.queue.q.SemanticAt(pos)
return res.id, res.found
} }
func (w *Window) runFuncs(d driver) { func (w *Window) waitAck(d driver) {
// Don't run driver functions if there's no driver.
if d == nil {
<-w.ack
return
}
var defers []func(d driver)
// Don't miss deferred functions when ack arrives immediately. There is one
// wakeup event per function, so one select is enough.
select {
case f := <-w.driverDefers:
defers = append(defers, f)
default:
}
// Wait for ack while running incoming runnables.
for { for {
select { select {
case f := <-w.driverFuncs: case f := <-w.driverFuncs:
f(d) f(d)
case f := <-w.driverDefers: case w.out <- ackEvent:
defers = append(defers, f) // A dummy event went through, so we know the application has processed the previous event.
case <-w.ack:
for _, f := range defers {
f(d)
}
return
}
}
}
func (w *Window) waitAck() {
// Send a dummy event; when it gets through we
// know the application has processed the previous event.
w.out <- ackEvent
}
// Prematurely destroy the window and wait for the native window
// destroy event.
func (w *Window) destroy(err error) {
w.destroyGPU()
// Ack the current event.
w.ack <- struct{}{}
w.out <- system.DestroyEvent{Err: err}
close(w.dead)
close(w.out)
for e := range w.in {
w.ack <- struct{}{}
if _, ok := e.(system.DestroyEvent); ok {
return return
case <-w.immediateRedraws:
// Invalidate was called during frame processing.
w.setNextFrame(time.Time{})
} }
} }
} }
@@ -590,24 +477,25 @@ func (w *Window) destroyGPU() {
// waitFrame waits for the client to either call FrameEvent.Frame // waitFrame waits for the client to either call FrameEvent.Frame
// or to continue event handling. It returns whether the client // or to continue event handling. It returns whether the client
// called Frame or not. // called Frame or not.
func (w *Window) waitFrame() (*op.Ops, bool) { func (w *Window) waitFrame(d driver) (*op.Ops, bool) {
select { for {
case frame := <-w.frames: select {
// The client called FrameEvent.Frame. case f := <-w.driverFuncs:
return frame, true f(d)
case w.out <- ackEvent: case frame := <-w.frames:
// The client ignored FrameEvent and continued processing // The client called FrameEvent.Frame.
// events. return frame, true
return nil, false case w.out <- ackEvent:
// The client ignored FrameEvent and continued processing
// events.
return nil, false
case <-w.immediateRedraws:
// Invalidate was called during frame processing.
w.setNextFrame(time.Time{})
}
} }
} }
func (w *Window) lookupSemantic(id router.SemanticID) (router.SemanticNode, bool) {
w.updateSemantics()
n, ok := w.semantic.ids[id]
return n, ok
}
// updateSemantics refreshes the semantics tree, the id to node map and the ids of // updateSemantics refreshes the semantics tree, the id to node map and the ids of
// updated nodes. // updated nodes.
func (w *Window) updateSemantics() { func (w *Window) updateSemantics() {
@@ -623,21 +511,8 @@ func (w *Window) updateSemantics() {
} }
} }
// sendSemanticDiffs traverses the previous semantic tree and sends changed ids to
// w.semDiffs.
func (w *Window) sendSemanticDiffs() {
w.updateSemantics()
defer func() {
// Mark end of list.
w.semantic.diffs <- 0
}()
if tree := w.semantic.prevTree; len(tree) > 0 {
w.collectSemanticDiffs(w.semantic.prevTree[0])
}
}
// collectSemanticDiffs traverses the previous semantic tree, noting changed nodes. // collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
func (w *Window) collectSemanticDiffs(n router.SemanticNode) { func (w *Window) collectSemanticDiffs(diffs *[]router.SemanticID, n router.SemanticNode) {
newNode, exists := w.semantic.ids[n.ID] newNode, exists := w.semantic.ids[n.ID]
// Ignore deleted nodes, as their disappearance will be reported through an // Ignore deleted nodes, as their disappearance will be reported through an
// ancestor node. // ancestor node.
@@ -650,132 +525,133 @@ func (w *Window) collectSemanticDiffs(n router.SemanticNode) {
newCh := newNode.Children[i] newCh := newNode.Children[i]
diff = ch.ID != newCh.ID diff = ch.ID != newCh.ID
} }
w.collectSemanticDiffs(ch) w.collectSemanticDiffs(diffs, ch)
} }
if diff { if diff {
w.semantic.diffs <- n.ID *diffs = append(*diffs, n.ID)
}
}
func (w *Window) updateState(d driver) {
for {
select {
case f := <-w.driverFuncs:
f(d)
case <-w.redraws:
w.setNextFrame(time.Time{})
w.updateAnimation(d)
default:
return
}
}
}
func (w *Window) processEvent(d driver, e event.Event) {
select {
case <-w.dead:
return
default:
}
switch e2 := e.(type) {
case system.StageEvent:
if e2.Stage < system.StageRunning {
if w.gpu != nil {
w.ctx.Lock()
w.gpu.Release()
w.gpu = nil
w.ctx.Unlock()
}
}
w.stage = e2.Stage
w.updateAnimation(d)
w.out <- e
w.waitAck(d)
case frameEvent:
if e2.Size == (image.Point{}) {
panic(errors.New("internal error: zero-sized Draw"))
}
if w.stage < system.StageRunning {
// No drawing if not visible.
break
}
frameStart := time.Now()
w.hasNextFrame = false
e2.Frame = w.update
e2.Queue = &w.queue
w.out <- e2.FrameEvent
frame, gotFrame := w.waitFrame(d)
err := w.validateAndProcess(d, frameStart, e2.Size, e2.Sync, frame)
if gotFrame {
// We're done with frame, let the client continue.
w.frameAck <- struct{}{}
}
if err != nil {
w.destroyGPU()
w.out <- system.DestroyEvent{Err: err}
close(w.dead)
close(w.out)
break
}
w.updateCursor()
case *system.CommandEvent:
w.out <- e
w.waitAck(d)
case system.DestroyEvent:
w.destroyGPU()
w.out <- e2
close(w.dead)
close(w.out)
case ViewEvent:
w.out <- e2
w.waitAck(d)
case wakeupEvent:
case event.Event:
if w.queue.q.Queue(e2) {
w.setNextFrame(time.Time{})
w.updateAnimation(d)
}
w.updateCursor()
w.out <- e
} }
} }
func (w *Window) run(options []Option) { func (w *Window) run(options []Option) {
// Some OpenGL drivers don't like being made current on many different
// OS threads. Force the Go runtime to map the event loop goroutine to
// only one thread.
runtime.LockOSThread()
defer close(w.out)
defer close(w.dead)
if err := newWindow(&w.callbacks, options); err != nil { if err := newWindow(&w.callbacks, options); err != nil {
w.out <- system.DestroyEvent{Err: err} w.out <- system.DestroyEvent{Err: err}
close(w.dead)
close(w.out)
return return
} }
var wakeup func() var wakeup func()
var timer *time.Timer
for { for {
var ( var (
wakeups <-chan struct{} wakeups <-chan struct{}
timer <-chan time.Time timeC <-chan time.Time
) )
if wakeup != nil { if wakeup != nil {
wakeups = w.wakeups wakeups = w.wakeups
// Make sure any pending deferred driver functions are processed before calling if timer != nil {
// into driverFunc again; only one driver function can be queued at a time. timeC = timer.C
}
}
select {
case t := <-w.scheduledRedraws:
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(time.Until(t))
case <-w.dead:
return
case <-timeC:
select { select {
case <-wakeups: case w.redraws <- struct{}{}:
wakeup() wakeup()
default: default:
} }
}
if w.delayedDraw != nil {
timer = w.delayedDraw.C
}
select {
case <-timer:
w.setNextFrame(time.Time{})
w.updateAnimation()
case <-w.invalidates:
w.setNextFrame(time.Time{})
w.updateAnimation()
case <-wakeups: case <-wakeups:
wakeup() wakeup()
case semID := <-w.semantic.lookups: case wakeup = <-w.wakeupFuncs:
node, found := w.lookupSemantic(semID)
w.semantic.results <- semanticResult{
found: found,
node: node,
}
case <-w.semantic.requestDiffs:
w.sendSemanticDiffs()
case <-w.semantic.requestRoots:
w.semantic.roots <- w.semantic.root
case pos := <-w.semantic.positions:
sid, exists := w.queue.q.SemanticAt(pos)
w.semantic.positionIDs <- semanticID{
found: exists,
id: sid,
}
case e := <-w.in:
switch e2 := e.(type) {
case system.StageEvent:
if e2.Stage < system.StageRunning {
if w.gpu != nil {
w.ctx.Lock()
w.gpu.Release()
w.gpu = nil
w.ctx.Unlock()
}
}
w.stage = e2.Stage
w.updateAnimation()
w.out <- e
w.waitAck()
case frameEvent:
if e2.Size == (image.Point{}) {
panic(errors.New("internal error: zero-sized Draw"))
}
if w.stage < system.StageRunning {
// No drawing if not visible.
break
}
frameStart := time.Now()
w.hasNextFrame = false
e2.Frame = w.update
e2.Queue = &w.queue
w.out <- e2.FrameEvent
frame, gotFrame := w.waitFrame()
err := w.validateAndProcess(frameStart, e2.Size, e2.Sync, frame)
if gotFrame {
// We're done with frame, let the client continue.
w.frameAck <- struct{}{}
}
if err != nil {
w.destroyGPU()
w.destroy(err)
return
}
w.updateCursor()
case *system.CommandEvent:
w.out <- e
w.waitAck()
case driverEvent:
wakeup = e2.wakeup
case system.DestroyEvent:
w.destroyGPU()
w.out <- e2
w.ack <- struct{}{}
return
case ViewEvent:
w.out <- e2
w.waitAck()
case wakeupEvent:
case event.Event:
if w.queue.q.Queue(e2) {
w.setNextFrame(time.Time{})
w.updateAnimation()
}
w.updateCursor()
w.out <- e
}
w.ack <- struct{}{}
} }
} }
} }
@@ -878,5 +754,3 @@ func CustomRenderer(custom bool) Option {
cnf.CustomRenderer = custom cnf.CustomRenderer = custom
} }
} }
func (driverEvent) ImplementsEvent() {}