diff --git a/image.go b/image.go index 717bc924e..f0ee6c6e1 100644 --- a/image.go +++ b/image.go @@ -23,6 +23,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/mipmap" + "github.com/hajimehoshi/ebiten/v2/internal/ui" ) // panicOnErrorAtImageAt indicates whether (*Image).At panics on an error or not. @@ -718,7 +719,7 @@ func (i *Image) at(x, y int) (r, g, b, a uint8) { if panicOnErrorAtImageAt { panic(err) } - theUIContext.setError(err) + ui.SetError(err) return 0, 0, 0, 0 } return pix[0], pix[1], pix[2], pix[3] @@ -746,7 +747,7 @@ func (i *Image) Set(x, y int, clr color.Color) { r, g, b, a := clr.RGBA() pix := []byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)} if err := i.mipmap.ReplacePixels(pix, x, y, 1, 1); err != nil { - theUIContext.setError(err) + ui.SetError(err) } } @@ -794,7 +795,7 @@ func (i *Image) ReplacePixels(pixels []byte) { // * In internal/mipmap, pixels are copied when necessary. // * In internal/shareable, pixels are copied to make its paddings. if err := i.mipmap.ReplacePixels(pixels, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil { - theUIContext.setError(err) + ui.SetError(err) } } diff --git a/internal/ui/context.go b/internal/ui/context.go new file mode 100644 index 000000000..ed04cad7f --- /dev/null +++ b/internal/ui/context.go @@ -0,0 +1,70 @@ +// Copyright 2022 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ui + +import ( + "sync/atomic" +) + +type Context interface { + UpdateFrame() error + ForceUpdateFrame() error + Layout(outsideWidth, outsideHeight float64) + + // AdjustPosition can be called from a different goroutine from Update's or Layout's. + AdjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) +} + +type contextImpl struct { + context Context + + err atomic.Value +} + +func (c *contextImpl) updateFrame() error { + if err, ok := c.err.Load().(error); ok && err != nil { + return err + } + return c.context.UpdateFrame() +} + +func (c *contextImpl) forceUpdateFrame() error { + if err, ok := c.err.Load().(error); ok && err != nil { + return err + } + return c.context.ForceUpdateFrame() +} + +func (c *contextImpl) layout(outsideWidth, outsideHeight float64) { + // The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589) + // Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic. + if outsideWidth == 0 || outsideHeight == 0 { + return + } + + c.context.Layout(outsideWidth, outsideHeight) +} + +func (c *contextImpl) adjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) { + return c.context.AdjustPosition(x, y, deviceScaleFactor) +} + +func (c *contextImpl) setError(err error) { + c.err.Store(err) +} + +func SetError(err error) { + Get().context.setError(err) +} diff --git a/internal/ui/input_cbackend.go b/internal/ui/input_cbackend.go index 7a90d0777..56a10793d 100644 --- a/internal/ui/input_cbackend.go +++ b/internal/ui/input_cbackend.go @@ -31,7 +31,7 @@ type Input struct { m sync.Mutex } -func (i *Input) update(context Context) { +func (i *Input) update(context *contextImpl) { i.m.Lock() defer i.m.Unlock() @@ -41,7 +41,7 @@ func (i *Input) update(context Context) { i.touches = cbackend.AppendTouches(i.touches) for idx, t := range i.touches { - x, y := context.AdjustPosition(float64(t.X), float64(t.Y), deviceScaleFactor) + x, y := context.adjustPosition(float64(t.X), float64(t.Y), deviceScaleFactor) i.touches[idx].X = int(x) i.touches[idx].Y = int(y) } diff --git a/internal/ui/input_glfw.go b/internal/ui/input_glfw.go index 12822d6bc..8d8c053f3 100644 --- a/internal/ui/input_glfw.go +++ b/internal/ui/input_glfw.go @@ -155,7 +155,7 @@ var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{ } // update must be called from the main thread. -func (i *Input) update(window *glfw.Window, context Context) error { +func (i *Input) update(window *glfw.Window, context *contextImpl) error { i.ui.m.Lock() defer i.ui.m.Unlock() @@ -196,7 +196,7 @@ func (i *Input) update(window *glfw.Window, context Context) error { s := i.ui.deviceScaleFactor(m) cx = i.ui.dipFromGLFWPixel(cx, m) cy = i.ui.dipFromGLFWPixel(cy, m) - cx, cy = context.AdjustPosition(cx, cy, s) + cx, cy = context.adjustPosition(cx, cy, s) // AdjustPosition can return NaN at the initialization. if !math.IsNaN(cx) && !math.IsNaN(cy) { diff --git a/internal/ui/input_js.go b/internal/ui/input_js.go index 61efe72b9..bda10d445 100644 --- a/internal/ui/input_js.go +++ b/internal/ui/input_js.go @@ -75,7 +75,7 @@ func (i *Input) CursorPosition() (x, y int) { if i.ui.context == nil { return 0, 0 } - xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor()) + xf, yf := i.ui.context.adjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor()) return int(xf), int(yf) } @@ -90,7 +90,7 @@ func (i *Input) TouchPosition(id TouchID) (x, y int) { d := i.ui.DeviceScaleFactor() for tid, pos := range i.touches { if id == tid { - x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d) + x, y := i.ui.context.adjustPosition(float64(pos.X), float64(pos.Y), d) return int(x), int(y) } } diff --git a/internal/ui/run_notsinglethread.go b/internal/ui/run_notsinglethread.go index 3dd0aa2d2..3f83ba2fe 100644 --- a/internal/ui/run_notsinglethread.go +++ b/internal/ui/run_notsinglethread.go @@ -23,7 +23,9 @@ import ( ) func (u *UserInterface) Run(uicontext Context) error { - u.context = uicontext + u.context = &contextImpl{ + context: uicontext, + } // Initialize the main thread first so the thread is available at u.run (#809). u.t = thread.NewOSThread() diff --git a/internal/ui/run_singlethread.go b/internal/ui/run_singlethread.go index 649048e8f..8b77ea5a6 100644 --- a/internal/ui/run_singlethread.go +++ b/internal/ui/run_singlethread.go @@ -23,7 +23,9 @@ import ( ) func (u *UserInterface) Run(uicontext Context) error { - u.context = uicontext + u.context = &contextImpl{ + context: uicontext, + } // Initialize the main thread first so the thread is available at u.run (#809). u.t = thread.NewNoopThread() diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 335a21297..8053315a8 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -20,15 +20,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" ) -type Context interface { - UpdateFrame() error - ForceUpdateFrame() error - Layout(outsideWidth, outsideHeight float64) - - // AdjustPosition can be called from a different goroutine from Update's or Layout's. - AdjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) -} - type MouseButton int const ( diff --git a/internal/ui/ui_cbackend.go b/internal/ui/ui_cbackend.go index f4cf2e29d..fe7264216 100644 --- a/internal/ui/ui_cbackend.go +++ b/internal/ui/ui_cbackend.go @@ -30,7 +30,8 @@ func init() { } type UserInterface struct { - input Input + context *contextImpl + input Input } var theUserInterface UserInterface @@ -40,15 +41,18 @@ func Get() *UserInterface { } func (u *UserInterface) Run(context Context) error { + u.context = &contextImpl{ + context: context, + } cbackend.InitializeGame() for { w, h := cbackend.ScreenSize() - context.Layout(float64(w), float64(h)) + u.context.layout(float64(w), float64(h)) cbackend.BeginFrame() - u.input.update(context) + u.input.update(u.context) - if err := context.UpdateFrame(); err != nil { + if err := u.context.updateFrame(); err != nil { return err } diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index dbcc66eb1..96c1858d4 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -46,7 +46,7 @@ func driverCursorModeToGLFWCursorMode(mode CursorMode) int { } type UserInterface struct { - context Context + context *contextImpl title string window *glfw.Window @@ -731,8 +731,8 @@ func (u *UserInterface) registerWindowSetSizeCallback() { outsideWidth, outsideHeight = u.updateSize() }) - u.context.Layout(outsideWidth, outsideHeight) - if err := u.context.ForceUpdateFrame(); err != nil { + u.context.layout(outsideWidth, outsideHeight) + if err := u.context.forceUpdateFrame(); err != nil { return err } if graphics().IsGL() { @@ -1042,9 +1042,9 @@ func (u *UserInterface) loop() error { }); err != nil { return err } - u.context.Layout(outsideWidth, outsideHeight) + u.context.layout(outsideWidth, outsideHeight) - if err := u.context.UpdateFrame(); err != nil { + if err := u.context.updateFrame(); err != nil { return err } @@ -1365,7 +1365,7 @@ func (u *UserInterface) ResetForFrame() { u.t.Call(func() { w, h = u.updateSize() }) - u.context.Layout(w, h) + u.context.layout(w, h) u.input.resetForFrame() u.m.Lock() diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 7704d2804..958ffaa59 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -61,7 +61,7 @@ type UserInterface struct { lastDeviceScaleFactor float64 - context Context + context *contextImpl input Input } @@ -240,14 +240,14 @@ func (u *UserInterface) updateSize() { body := document.Get("body") bw := body.Get("clientWidth").Float() bh := body.Get("clientHeight").Float() - u.context.Layout(bw, bh) + u.context.layout(bw, bh) case go2cpp.Truthy(): w := go2cpp.Get("screenWidth").Float() h := go2cpp.Get("screenHeight").Float() - u.context.Layout(w, h) + u.context.layout(w, h) default: // Node.js - u.context.Layout(640, 480) + u.context.layout(640, 480) } } } @@ -293,11 +293,11 @@ func (u *UserInterface) updateImpl(force bool) error { u.input.updateForGo2Cpp() u.updateSize() if force { - if err := u.context.ForceUpdateFrame(); err != nil { + if err := u.context.forceUpdateFrame(); err != nil { return err } } else { - if err := u.context.UpdateFrame(); err != nil { + if err := u.context.updateFrame(); err != nil { return err } } @@ -319,7 +319,9 @@ func (u *UserInterface) needsUpdate() bool { } func (u *UserInterface) loop(context Context) <-chan error { - u.context = context + u.context = &contextImpl{ + context: context, + } errCh := make(chan error, 1) reqStopAudioCh := make(chan struct{}) diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index d0cda231e..21a566490 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -109,7 +109,7 @@ type UserInterface struct { setGBuildSizeCh chan struct{} once sync.Once - context Context + context *contextImpl input Input @@ -273,7 +273,9 @@ func (u *UserInterface) run(context Context, mainloop bool) (err error) { u.sizeChanged = true u.m.Unlock() - u.context = context + u.context = &contextImpl{ + context: context, + } if mainloop { // When mainloop is true, gomobile-build is used. In this case, GL functions must be called via @@ -320,7 +322,7 @@ func (u *UserInterface) layoutIfNeeded() { u.m.RUnlock() if sizeChanged { - u.context.Layout(outsideWidth, outsideHeight) + u.context.layout(outsideWidth, outsideHeight) } } @@ -330,7 +332,7 @@ func (u *UserInterface) update() error { renderEndCh <- struct{}{} }() - if err := u.context.UpdateFrame(); err != nil { + if err := u.context.updateFrame(); err != nil { return err } return nil @@ -368,7 +370,7 @@ func (u *UserInterface) setGBuildSize(widthPx, heightPx int) { } func (u *UserInterface) adjustPosition(x, y int) (int, int) { - xf, yf := u.context.AdjustPosition(float64(x), float64(y), deviceScale()) + xf, yf := u.context.adjustPosition(float64(x), float64(y), deviceScale()) return int(xf), int(yf) } diff --git a/uicontext.go b/uicontext.go index 86afb4861..167bbf8bc 100644 --- a/uicontext.go +++ b/uicontext.go @@ -18,7 +18,6 @@ import ( "fmt" "math" "sync" - "sync/atomic" "github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/clock" @@ -39,8 +38,6 @@ type uiContext struct { outsideWidth float64 outsideHeight float64 - err atomic.Value - m sync.Mutex } @@ -52,16 +49,7 @@ func (c *uiContext) set(game Game) { c.game = game } -func (c *uiContext) setError(err error) { - c.err.Store(err) -} - func (c *uiContext) Layout(outsideWidth, outsideHeight float64) { - // The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589) - // Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic. - if outsideWidth == 0 || outsideHeight == 0 { - return - } c.outsideWidth = outsideWidth c.outsideHeight = outsideHeight } @@ -149,10 +137,6 @@ func (c *uiContext) ForceUpdateFrame() error { } func (c *uiContext) updateFrame(updateCount int) error { - if err, ok := c.err.Load().(error); ok && err != nil { - return err - } - debug.Logf("----\n") if err := buffered.BeginFrame(); err != nil {