internal/ui: refactoring

This commit is contained in:
Hajime Hoshi 2023-10-24 14:35:29 +09:00
parent 2eca476054
commit f2acc3d9f7
2 changed files with 36 additions and 36 deletions

View File

@ -59,13 +59,13 @@ type context struct {
setContextOnce sync.Once setContextOnce sync.Once
deferred []func() funcsInFrameCh chan func()
deferredM sync.Mutex
} }
func newContext(game Game) *context { func newContext(game Game) *context {
return &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. // Flush deferred functions, like reading pixels from GPU.
if err := c.flushDeferredFuncs(ui); err != nil { if err := c.processFuncsInFrame(ui); err != nil {
return err return nil
} }
// ForceUpdate can be invoked even if the context is not initialized yet (#1591). // 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()) return u.context.logicalPositionToClientPosition(x, y, u.DeviceScaleFactor())
} }
// appendDeferredFunc appends a function. func (c *context) runInFrame(f func()) {
// An appended function is called at the next frame. ch := make(chan struct{})
func (c *context) appendDeferredFunc(f func()) { c.funcsInFrameCh <- func() {
c.deferredM.Lock() defer close(ch)
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 {
f() f()
} }
<-ch
return
}
if len(fs) > 0 { func (c *context) processFuncsInFrame(ui *UserInterface) error {
// Catch the error that happened at (*Image).At. var processed bool
if err := ui.error(); err != nil { for {
return err 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
} }

View File

@ -128,12 +128,16 @@ func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region
// ReadPixels failed since this was called in between two frames. // ReadPixels failed since this was called in between two frames.
// Try this again at the next frame. // Try this again at the next frame.
if !ok { if !ok {
ch := make(chan error) // If this function is called from the same sequence as a game's Update and Draw,
u.context.appendDeferredFunc(func() { // this causes a dead lock.
defer close(ch) // 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) ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region)
if err != nil { if err != nil {
ch <- err err1 = err
return return
} }
if !ok { if !ok {
@ -141,12 +145,7 @@ func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region
panic("ui: ReadPixels unexpectedly failed") panic("ui: ReadPixels unexpectedly failed")
} }
}) })
return err1
// 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 nil return nil