internal/ui: refactoring: add ui.SetError

This is a preparation to move uiContext to the package internal/ui.
This commit is contained in:
Hajime Hoshi 2022-02-13 16:30:33 +09:00
parent 2c68124f0e
commit b282b1805b
13 changed files with 116 additions and 58 deletions

View File

@ -23,6 +23,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/mipmap" "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. // 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 { if panicOnErrorAtImageAt {
panic(err) panic(err)
} }
theUIContext.setError(err) ui.SetError(err)
return 0, 0, 0, 0 return 0, 0, 0, 0
} }
return pix[0], pix[1], pix[2], pix[3] 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() r, g, b, a := clr.RGBA()
pix := []byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)} 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 { 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/mipmap, pixels are copied when necessary.
// * In internal/shareable, pixels are copied to make its paddings. // * 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 { if err := i.mipmap.ReplacePixels(pixels, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
theUIContext.setError(err) ui.SetError(err)
} }
} }

70
internal/ui/context.go Normal file
View File

@ -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)
}

View File

@ -31,7 +31,7 @@ type Input struct {
m sync.Mutex m sync.Mutex
} }
func (i *Input) update(context Context) { func (i *Input) update(context *contextImpl) {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
@ -41,7 +41,7 @@ func (i *Input) update(context Context) {
i.touches = cbackend.AppendTouches(i.touches) i.touches = cbackend.AppendTouches(i.touches)
for idx, t := range 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].X = int(x)
i.touches[idx].Y = int(y) i.touches[idx].Y = int(y)
} }

View File

@ -155,7 +155,7 @@ var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{
} }
// update must be called from the main thread. // 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() i.ui.m.Lock()
defer i.ui.m.Unlock() defer i.ui.m.Unlock()
@ -196,7 +196,7 @@ func (i *Input) update(window *glfw.Window, context Context) error {
s := i.ui.deviceScaleFactor(m) s := i.ui.deviceScaleFactor(m)
cx = i.ui.dipFromGLFWPixel(cx, m) cx = i.ui.dipFromGLFWPixel(cx, m)
cy = i.ui.dipFromGLFWPixel(cy, 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. // AdjustPosition can return NaN at the initialization.
if !math.IsNaN(cx) && !math.IsNaN(cy) { if !math.IsNaN(cx) && !math.IsNaN(cy) {

View File

@ -75,7 +75,7 @@ func (i *Input) CursorPosition() (x, y int) {
if i.ui.context == nil { if i.ui.context == nil {
return 0, 0 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) return int(xf), int(yf)
} }
@ -90,7 +90,7 @@ func (i *Input) TouchPosition(id TouchID) (x, y int) {
d := i.ui.DeviceScaleFactor() d := i.ui.DeviceScaleFactor()
for tid, pos := range i.touches { for tid, pos := range i.touches {
if id == tid { 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) return int(x), int(y)
} }
} }

View File

@ -23,7 +23,9 @@ import (
) )
func (u *UserInterface) Run(uicontext Context) error { 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). // Initialize the main thread first so the thread is available at u.run (#809).
u.t = thread.NewOSThread() u.t = thread.NewOSThread()

View File

@ -23,7 +23,9 @@ import (
) )
func (u *UserInterface) Run(uicontext Context) error { 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). // Initialize the main thread first so the thread is available at u.run (#809).
u.t = thread.NewNoopThread() u.t = thread.NewNoopThread()

View File

@ -20,15 +20,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "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 type MouseButton int
const ( const (

View File

@ -30,7 +30,8 @@ func init() {
} }
type UserInterface struct { type UserInterface struct {
input Input context *contextImpl
input Input
} }
var theUserInterface UserInterface var theUserInterface UserInterface
@ -40,15 +41,18 @@ func Get() *UserInterface {
} }
func (u *UserInterface) Run(context Context) error { func (u *UserInterface) Run(context Context) error {
u.context = &contextImpl{
context: context,
}
cbackend.InitializeGame() cbackend.InitializeGame()
for { for {
w, h := cbackend.ScreenSize() w, h := cbackend.ScreenSize()
context.Layout(float64(w), float64(h)) u.context.layout(float64(w), float64(h))
cbackend.BeginFrame() 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 return err
} }

View File

@ -46,7 +46,7 @@ func driverCursorModeToGLFWCursorMode(mode CursorMode) int {
} }
type UserInterface struct { type UserInterface struct {
context Context context *contextImpl
title string title string
window *glfw.Window window *glfw.Window
@ -731,8 +731,8 @@ func (u *UserInterface) registerWindowSetSizeCallback() {
outsideWidth, outsideHeight = u.updateSize() outsideWidth, outsideHeight = u.updateSize()
}) })
u.context.Layout(outsideWidth, outsideHeight) u.context.layout(outsideWidth, outsideHeight)
if err := u.context.ForceUpdateFrame(); err != nil { if err := u.context.forceUpdateFrame(); err != nil {
return err return err
} }
if graphics().IsGL() { if graphics().IsGL() {
@ -1042,9 +1042,9 @@ func (u *UserInterface) loop() error {
}); err != nil { }); err != nil {
return err 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 return err
} }
@ -1365,7 +1365,7 @@ func (u *UserInterface) ResetForFrame() {
u.t.Call(func() { u.t.Call(func() {
w, h = u.updateSize() w, h = u.updateSize()
}) })
u.context.Layout(w, h) u.context.layout(w, h)
u.input.resetForFrame() u.input.resetForFrame()
u.m.Lock() u.m.Lock()

View File

@ -61,7 +61,7 @@ type UserInterface struct {
lastDeviceScaleFactor float64 lastDeviceScaleFactor float64
context Context context *contextImpl
input Input input Input
} }
@ -240,14 +240,14 @@ func (u *UserInterface) updateSize() {
body := document.Get("body") body := document.Get("body")
bw := body.Get("clientWidth").Float() bw := body.Get("clientWidth").Float()
bh := body.Get("clientHeight").Float() bh := body.Get("clientHeight").Float()
u.context.Layout(bw, bh) u.context.layout(bw, bh)
case go2cpp.Truthy(): case go2cpp.Truthy():
w := go2cpp.Get("screenWidth").Float() w := go2cpp.Get("screenWidth").Float()
h := go2cpp.Get("screenHeight").Float() h := go2cpp.Get("screenHeight").Float()
u.context.Layout(w, h) u.context.layout(w, h)
default: default:
// Node.js // 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.input.updateForGo2Cpp()
u.updateSize() u.updateSize()
if force { if force {
if err := u.context.ForceUpdateFrame(); err != nil { if err := u.context.forceUpdateFrame(); err != nil {
return err return err
} }
} else { } else {
if err := u.context.UpdateFrame(); err != nil { if err := u.context.updateFrame(); err != nil {
return err return err
} }
} }
@ -319,7 +319,9 @@ func (u *UserInterface) needsUpdate() bool {
} }
func (u *UserInterface) loop(context Context) <-chan error { func (u *UserInterface) loop(context Context) <-chan error {
u.context = context u.context = &contextImpl{
context: context,
}
errCh := make(chan error, 1) errCh := make(chan error, 1)
reqStopAudioCh := make(chan struct{}) reqStopAudioCh := make(chan struct{})

View File

@ -109,7 +109,7 @@ type UserInterface struct {
setGBuildSizeCh chan struct{} setGBuildSizeCh chan struct{}
once sync.Once once sync.Once
context Context context *contextImpl
input Input input Input
@ -273,7 +273,9 @@ func (u *UserInterface) run(context Context, mainloop bool) (err error) {
u.sizeChanged = true u.sizeChanged = true
u.m.Unlock() u.m.Unlock()
u.context = context u.context = &contextImpl{
context: context,
}
if mainloop { if mainloop {
// When mainloop is true, gomobile-build is used. In this case, GL functions must be called via // 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() u.m.RUnlock()
if sizeChanged { if sizeChanged {
u.context.Layout(outsideWidth, outsideHeight) u.context.layout(outsideWidth, outsideHeight)
} }
} }
@ -330,7 +332,7 @@ func (u *UserInterface) update() error {
renderEndCh <- struct{}{} renderEndCh <- struct{}{}
}() }()
if err := u.context.UpdateFrame(); err != nil { if err := u.context.updateFrame(); err != nil {
return err return err
} }
return nil return nil
@ -368,7 +370,7 @@ func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
} }
func (u *UserInterface) adjustPosition(x, y int) (int, 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) return int(xf), int(yf)
} }

View File

@ -18,7 +18,6 @@ import (
"fmt" "fmt"
"math" "math"
"sync" "sync"
"sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/buffered"
"github.com/hajimehoshi/ebiten/v2/internal/clock" "github.com/hajimehoshi/ebiten/v2/internal/clock"
@ -39,8 +38,6 @@ type uiContext struct {
outsideWidth float64 outsideWidth float64
outsideHeight float64 outsideHeight float64
err atomic.Value
m sync.Mutex m sync.Mutex
} }
@ -52,16 +49,7 @@ func (c *uiContext) set(game Game) {
c.game = game c.game = game
} }
func (c *uiContext) setError(err error) {
c.err.Store(err)
}
func (c *uiContext) Layout(outsideWidth, outsideHeight float64) { 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.outsideWidth = outsideWidth
c.outsideHeight = outsideHeight c.outsideHeight = outsideHeight
} }
@ -149,10 +137,6 @@ func (c *uiContext) ForceUpdateFrame() error {
} }
func (c *uiContext) updateFrame(updateCount int) error { func (c *uiContext) updateFrame(updateCount int) error {
if err, ok := c.err.Load().(error); ok && err != nil {
return err
}
debug.Logf("----\n") debug.Logf("----\n")
if err := buffered.BeginFrame(); err != nil { if err := buffered.BeginFrame(); err != nil {