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
deferred []func()
deferredM sync.Mutex
funcsInFrameCh chan func()
}
func newContext(game Game) *context {
return &context{
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 {
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
}
}
}

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.
// 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