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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 (

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {