From f2acc3d9f7ed3841bc2cae5d3a6ae2dbe6a8a726 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 24 Oct 2023 14:35:29 +0900 Subject: [PATCH] internal/ui: refactoring --- internal/ui/context.go | 53 +++++++++++++++++++++--------------------- internal/ui/ui.go | 19 +++++++-------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/internal/ui/context.go b/internal/ui/context.go index 6bb93ca8f..cfb0d9b37 100644 --- a/internal/ui/context.go +++ b/internal/ui/context.go @@ -59,13 +59,13 @@ type context struct { setContextOnce sync.Once - deferred []func() - deferredM sync.Mutex + funcsInFrameCh chan func() } func newContext(game Game) *context { return &context{ - game: game, + game: game, + funcsInFrameCh: make(chan func()), } } @@ -118,8 +118,8 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update }() // Flush deferred functions, like reading pixels from GPU. - if err := c.flushDeferredFuncs(ui); err != nil { - return err + if err := c.processFuncsInFrame(ui); err != nil { + return nil } // ForceUpdate can be invoked even if the context is not initialized yet (#1591). @@ -297,30 +297,31 @@ func (u *UserInterface) LogicalPositionToClientPosition(x, y float64) (float64, return u.context.logicalPositionToClientPosition(x, y, u.DeviceScaleFactor()) } -// appendDeferredFunc appends a function. -// An appended function is called at the next frame. -func (c *context) appendDeferredFunc(f func()) { - c.deferredM.Lock() - defer c.deferredM.Unlock() - c.deferred = append(c.deferred, f) -} - -func (c *context) flushDeferredFuncs(ui *UserInterface) error { - c.deferredM.Lock() - fs := c.deferred - c.deferred = nil - c.deferredM.Unlock() - - for _, f := range fs { +func (c *context) runInFrame(f func()) { + ch := make(chan struct{}) + c.funcsInFrameCh <- func() { + defer close(ch) f() } + <-ch + return +} - if len(fs) > 0 { - // Catch the error that happened at (*Image).At. - if err := ui.error(); err != nil { - return err +func (c *context) processFuncsInFrame(ui *UserInterface) error { + var processed bool + for { + select { + case f := <-c.funcsInFrameCh: + f() + processed = true + default: + if processed { + // Catch the error that happened at (*Image).At. + if err := ui.error(); err != nil { + return err + } + } + return nil } } - - return nil } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index b5ec342b4..53715b920 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -128,12 +128,16 @@ func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region // ReadPixels failed since this was called in between two frames. // Try this again at the next frame. if !ok { - ch := make(chan error) - u.context.appendDeferredFunc(func() { - defer close(ch) + // If this function is called from the same sequence as a game's Update and Draw, + // this causes a dead lock. + // This never happens so far, but if handling inputs after EndFrame is implemented, + // this might be possible (#1704). + + var err1 error + u.context.runInFrame(func() { ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region) if err != nil { - ch <- err + err1 = err return } if !ok { @@ -141,12 +145,7 @@ func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region panic("ui: ReadPixels unexpectedly failed") } }) - - // If this function is called from the game (Update/Draw) goroutine in between two frames, - // this channel never returns and causes a dead lock. - // This never happens so far, but if handling inputs after EndFrame is implemented, - // this might be possible (#1704). - return <-ch + return err1 } return nil