ebiten: add Termination for a regular termination (#2272)

Closes #2266
This commit is contained in:
Terra Brown 2022-08-27 08:33:40 -04:00 committed by GitHub
parent 0587a45c61
commit de35a5a6f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 48 additions and 88 deletions

View File

@ -19,7 +19,6 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
@ -41,7 +40,6 @@ import (
var ( var (
gophersImage *ebiten.Image gophersImage *ebiten.Image
mplusFont font.Face mplusFont font.Face
regularTermination = errors.New("regular termination")
) )
func init() { func init() {
@ -86,7 +84,7 @@ func (g *Game) Update() error {
} }
} }
if runtime.GOOS != "js" && ebiten.IsKeyPressed(ebiten.KeyQ) { if runtime.GOOS != "js" && ebiten.IsKeyPressed(ebiten.KeyQ) {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -130,7 +128,7 @@ func main() {
ebiten.SetFullscreen(true) ebiten.SetFullscreen(true)
ebiten.SetWindowTitle("Fullscreen (Ebiten Demo)") ebiten.SetWindowTitle("Fullscreen (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -317,8 +317,6 @@ func outputKeyRectsGo(k map[ebiten.Key]image.Rectangle) error {
}) })
} }
var regularTermination = errors.New("regular termination")
type game struct { type game struct {
rects map[ebiten.Key]image.Rectangle rects map[ebiten.Key]image.Rectangle
} }
@ -329,7 +327,7 @@ func (g *game) Update() error {
if err != nil { if err != nil {
return err return err
} }
return regularTermination return ebiten.Termination
} }
func (g *game) Draw(_ *ebiten.Image) { func (g *game) Draw(_ *ebiten.Image) {
@ -341,7 +339,7 @@ func (g *game) Layout(outw, outh int) (int, int) {
func main() { func main() {
g := &game{} g := &game{}
if err := ebiten.RunGame(g); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(g); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := outputKeyRectsGo(g.rects); err != nil { if err := outputKeyRectsGo(g.rects); err != nil {

View File

@ -19,7 +19,6 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"image" "image"
_ "image/png" _ "image/png"
@ -139,15 +138,13 @@ func (g *Game) init() {
} }
} }
var regularTermination = errors.New("regular termination")
func (g *Game) Update() error { func (g *Game) Update() error {
if !g.inited { if !g.inited {
g.init() g.init()
} }
if inpututil.IsKeyJustPressed(ebiten.KeyQ) { if inpututil.IsKeyJustPressed(ebiten.KeyQ) {
return regularTermination return ebiten.Termination
} }
// Decrease the number of the sprites. // Decrease the number of the sprites.
@ -202,7 +199,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() { func main() {
ebiten.SetFullscreen(true) ebiten.SetFullscreen(true)
ebiten.SetWindowTitle("Sprites HD (Ebiten Demo)") ebiten.SetWindowTitle("Sprites HD (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -18,7 +18,6 @@
package main package main
import ( import (
"errors"
"log" "log"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -26,8 +25,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
) )
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
windowClosingHandled bool windowClosingHandled bool
} }
@ -38,7 +35,7 @@ func (g *Game) Update() error {
} }
if g.windowClosingHandled { if g.windowClosingHandled {
if inpututil.IsKeyJustPressed(ebiten.KeyY) { if inpututil.IsKeyJustPressed(ebiten.KeyY) {
return regularTermination return ebiten.Termination
} }
if inpututil.IsKeyJustPressed(ebiten.KeyN) { if inpututil.IsKeyJustPressed(ebiten.KeyN) {
g.windowClosingHandled = false g.windowClosingHandled = false
@ -62,7 +59,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() { func main() {
ebiten.SetWindowClosingHandled(true) ebiten.SetWindowClosingHandled(true)
ebiten.SetWindowTitle("Window Closing (Ebiten Demo)") ebiten.SetWindowTitle("Window Closing (Ebiten Demo)")
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -15,7 +15,6 @@
package buffered_test package buffered_test
import ( import (
"errors"
"image/color" "image/color"
"os" "os"
"testing" "testing"
@ -34,8 +33,6 @@ func runOnMainThread(f func()) {
<-ch <-ch
} }
var regularTermination = errors.New("regular termination")
type game struct { type game struct {
m *testing.M m *testing.M
endCh chan struct{} endCh chan struct{}
@ -47,7 +44,7 @@ func (g *game) Update() error {
case f := <-mainCh: case f := <-mainCh:
f() f()
case <-g.endCh: case <-g.endCh:
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -73,7 +70,7 @@ func TestMain(m *testing.M) {
m: m, m: m,
endCh: endCh, endCh: endCh,
} }
if err := ebiten.RunGame(g); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(g); err != nil {
panic(err) panic(err)
} }

View File

@ -18,15 +18,12 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"image/color" "image/color"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
dst *ebiten.Image dst *ebiten.Image
phase int phase int
@ -94,7 +91,7 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return fmt.Errorf("phase: %d, got: %v, want: %v", g.phase, got, want) return fmt.Errorf("phase: %d, got: %v, want: %v", g.phase, got, want)
} }
return regularTermination return ebiten.Termination
} }
return nil return nil
@ -108,7 +105,7 @@ func (g *Game) Layout(width, height int) (int, int) {
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -18,15 +18,12 @@
package main package main
import ( import (
"errors"
"image/color" "image/color"
"time" "time"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
count int count int
} }
@ -34,7 +31,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.count++ g.count++
if g.count >= 2 { if g.count >= 2 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -62,7 +59,7 @@ func (g *Game) Layout(width, height int) (int, int) {
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -18,15 +18,12 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"image/color" "image/color"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
count int count int
} }
@ -34,7 +31,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.count++ g.count++
if g.count == 16 { if g.count == 16 {
return regularTermination return ebiten.Termination
} }
w, h := 256+g.count, 256+g.count w, h := 256+g.count, 256+g.count
@ -72,7 +69,7 @@ func (g *Game) Layout(width, height int) (int, int) {
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -17,13 +17,7 @@
package main package main
import ( import "github.com/hajimehoshi/ebiten/v2"
"errors"
"github.com/hajimehoshi/ebiten/v2"
)
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
count int count int
@ -39,7 +33,7 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
` `
g.count++ g.count++
if g.count == 16 { if g.count == 16 {
return regularTermination return ebiten.Termination
} }
if g.count < 8 { if g.count < 8 {
@ -64,7 +58,7 @@ func (g *Game) Layout(width, height int) (int, int) {
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -18,7 +18,6 @@
package main package main
import ( import (
"errors"
"image" "image"
"image/color" "image/color"
"math" "math"
@ -31,8 +30,6 @@ import (
"golang.org/x/image/font/opentype" "golang.org/x/image/font/opentype"
) )
var regularTermination = errors.New("regular termination")
var ( var (
emptyImage = ebiten.NewImage(3, 3) emptyImage = ebiten.NewImage(3, 3)
debugCircleImage *ebiten.Image debugCircleImage *ebiten.Image
@ -62,7 +59,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.counter++ g.counter++
if g.counter > 16 { if g.counter > 16 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -87,7 +84,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeigh
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }

View File

@ -18,15 +18,12 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"image/color" "image/color"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var regularTermination = errors.New("regular termination")
var srcInit *ebiten.Image var srcInit *ebiten.Image
func init() { func init() {
@ -59,7 +56,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.count++ g.count++
if g.count == 16 { if g.count == 16 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -115,7 +112,7 @@ func (g *Game) Layout(width, height int) (int, int) {
} }
func main() { func main() {
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -18,7 +18,6 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"image/color" "image/color"
@ -26,8 +25,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
var regularTermination = errors.New("regular termination")
var ( var (
baseImage *ebiten.Image baseImage *ebiten.Image
derivedImage *ebiten.Image derivedImage *ebiten.Image
@ -58,7 +55,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.count++ g.count++
if g.count == 16 { if g.count == 16 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -86,7 +83,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() { func main() {
ebiten.SetWindowTitle("Test") ebiten.SetWindowTitle("Test")
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -17,11 +17,7 @@
package main package main
import ( import "github.com/hajimehoshi/ebiten/v2"
"errors"
"github.com/hajimehoshi/ebiten/v2"
)
func init() { func init() {
s, err := ebiten.NewShader([]byte(` s, err := ebiten.NewShader([]byte(`
@ -36,8 +32,6 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
s.Dispose() s.Dispose()
} }
var regularTermination = errors.New("regular termination")
type Game struct { type Game struct {
counter int counter int
} }
@ -45,7 +39,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.counter++ g.counter++
if g.counter > 1 { if g.counter > 1 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -59,7 +53,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() { func main() {
// Run a game loop at least for one frame to ensure the shader disposed. // Run a game loop at least for one frame to ensure the shader disposed.
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -18,7 +18,6 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
@ -27,8 +26,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
var regularTermination = errors.New("regular termination")
var ( var (
baseImage *ebiten.Image baseImage *ebiten.Image
derivedImage *ebiten.Image derivedImage *ebiten.Image
@ -59,7 +56,7 @@ type Game struct {
func (g *Game) Update() error { func (g *Game) Update() error {
g.count++ g.count++
if g.count == 16 { if g.count == 16 {
return regularTermination return ebiten.Termination
} }
return nil return nil
} }
@ -87,7 +84,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
func main() { func main() {
ebiten.SetWindowTitle("Test") ebiten.SetWindowTitle("Test")
if err := ebiten.RunGame(&Game{}); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(&Game{}); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -15,15 +15,12 @@
package testing package testing
import ( import (
"errors"
"os" "os"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
var regularTermination = errors.New("regular termination")
type game struct { type game struct {
m *testing.M m *testing.M
code int code int
@ -31,7 +28,7 @@ type game struct {
func (g *game) Update() error { func (g *game) Update() error {
g.code = g.m.Run() g.code = g.m.Run()
return regularTermination return ebiten.Termination
} }
func (*game) Draw(*ebiten.Image) { func (*game) Draw(*ebiten.Image) {
@ -46,7 +43,7 @@ func MainWithRunLoop(m *testing.M) {
g := &game{ g := &game{
m: m, m: m,
} }
if err := ebiten.RunGame(g); err != nil && !errors.Is(err, regularTermination) { if err := ebiten.RunGame(g); err != nil {
panic(err) panic(err)
} }
os.Exit(g.code) os.Exit(g.code)

23
run.go
View File

@ -15,6 +15,7 @@
package ebiten package ebiten
import ( import (
"errors"
"sync/atomic" "sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/clock" "github.com/hajimehoshi/ebiten/v2/internal/clock"
@ -45,6 +46,10 @@ type Game interface {
// //
// After the first frame, Update might not be called or might be called once // After the first frame, Update might not be called or might be called once
// or more for one frame. The frequency is determined by the current TPS (tick-per-second). // or more for one frame. The frequency is determined by the current TPS (tick-per-second).
//
// If the error returned is nil, game execution proceeds normally.
// If the error returned is Termination, game execution halts, but does not return an error from RunGame.
// If the error returned is any other non-nil value, game execution halts and the error is returned from RunGame.
Update() error Update() error
// Draw draws the game screen by one frame. // Draw draws the game screen by one frame.
@ -167,6 +172,9 @@ func (i *imageDumperGame) Layout(outsideWidth, outsideHeight int) (screenWidth,
return i.game.Layout(outsideWidth, outsideHeight) return i.game.Layout(outsideWidth, outsideHeight)
} }
// Termination is a special error which indicates Game termination without error.
var Termination = ui.RegularTermination
// RunGame starts the main loop and runs the game. // RunGame starts the main loop and runs the game.
// game's Update function is called every tick to update the game logic. // game's Update function is called every tick to update the game logic.
// game's Draw function is called every frame to draw the screen. // game's Draw function is called every frame to draw the screen.
@ -184,12 +192,12 @@ func (i *imageDumperGame) Layout(outsideWidth, outsideHeight int) (screenWidth,
// This is not related to framerate (display's refresh rate). // This is not related to framerate (display's refresh rate).
// //
// RunGame returns error when 1) an error happens in the underlying graphics driver, 2) an audio error happens // RunGame returns error when 1) an error happens in the underlying graphics driver, 2) an audio error happens
// or 3) f returns an error. In the case of 3), RunGame returns the same error so far, but it is recommended to // or 3) Update returns an error. In the case of 3), RunGame returns the same error so far, but it is recommended to
// use errors.Is when you check the returned error is the error you want, rather than comparing the values // use errors.Is when you check the returned error is the error you want, rather than comparing the values
// with == or != directly. // with == or != directly.
// //
// If you want to terminate a game on desktops, it is totally fine to define your own error value, return it at // If you want to terminate a game on desktops, it is recommended to return Termination at Update, which will halt
// Update, and check whether the returned error value from RunGame is the same as the value you defined. // execution without returning an error value from RunGame.
// //
// The size unit is device-independent pixel. // The size unit is device-independent pixel.
// //
@ -202,9 +210,10 @@ func RunGame(game Game) error {
game: game, game: game,
}) })
if err := ui.Get().Run(g); err != nil { if err := ui.Get().Run(g); err != nil {
if err == ui.RegularTermination { if errors.Is(err, Termination) {
return nil return nil
} }
return err return err
} }
return nil return nil
@ -216,7 +225,7 @@ func isRunGameEnded() bool {
// ScreenSizeInFullscreen returns the size in device-independent pixels when the game is fullscreen. // ScreenSizeInFullscreen returns the size in device-independent pixels when the game is fullscreen.
// The adopted monitor is the 'current' monitor which the window belongs to. // The adopted monitor is the 'current' monitor which the window belongs to.
// The returned value can be given to Run or SetSize function if the perfectly fit fullscreen is needed. // The returned value can be given to SetSize function if the perfectly fit fullscreen is needed.
// //
// On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size. // On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size.
// ScreenSizeInFullscreen's returning value is different from the actual screen size and this is a known issue (#2145). // ScreenSizeInFullscreen's returning value is different from the actual screen size and this is a known issue (#2145).
@ -228,8 +237,8 @@ func isRunGameEnded() bool {
// the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's // the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's
// behavior depends on the monitor size, ScreenSizeInFullscreen is useful. // behavior depends on the monitor size, ScreenSizeInFullscreen is useful.
// //
// ScreenSizeInFullscreen must be called on the main thread before ebiten.Run, and is concurrent-safe after // ScreenSizeInFullscreen must be called on the main thread before ebiten.RunGame, and is concurrent-safe after
// ebiten.Run. // ebiten.RunGame.
func ScreenSizeInFullscreen() (int, int) { func ScreenSizeInFullscreen() (int, int) {
return ui.Get().ScreenSizeInFullscreen() return ui.Get().ScreenSizeInFullscreen()
} }