ebiten: add RunGameWithOptions to specify graphics library

This also adds mobile.SetGameWithOptions.

Updates #2378
This commit is contained in:
Hajime Hoshi 2022-12-09 14:07:04 +09:00
parent 032f55d19a
commit bb68ebfcad
18 changed files with 198 additions and 65 deletions

View File

@ -26,6 +26,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -47,6 +48,7 @@ var (
flagInitFocused = flag.Bool("initfocused", true, "whether the window is focused on start") flagInitFocused = flag.Bool("initfocused", true, "whether the window is focused on start")
flagMinWindowSize = flag.String("minwindowsize", "", "minimum window size (e.g., 100x200)") flagMinWindowSize = flag.String("minwindowsize", "", "minimum window size (e.g., 100x200)")
flagMaxWindowSize = flag.String("maxwindowsize", "", "maximium window size (e.g., 1920x1080)") flagMaxWindowSize = flag.String("maxwindowsize", "", "maximium window size (e.g., 1920x1080)")
flagGraphicsLibrary = flag.String("graphicslibrary", "", "graphics library (e.g. opengl)")
) )
func init() { func init() {
@ -93,6 +95,8 @@ type game struct {
width float64 width float64
height float64 height float64
transparent bool transparent bool
logOnce sync.Once
} }
func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) { func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
@ -111,6 +115,12 @@ func (g *game) LayoutF(outsideWidth, outsideHeight float64) (float64, float64) {
} }
func (g *game) Update() error { func (g *game) Update() error {
g.logOnce.Do(func() {
var debug ebiten.DebugInfo
ebiten.ReadDebugInfo(&debug)
fmt.Printf("Graphics library: %s\n", debug.GraphicsLibrary)
})
var ( var (
screenWidth float64 screenWidth float64
screenHeight float64 screenHeight float64
@ -441,12 +451,26 @@ func main() {
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
} }
op := &ebiten.RunGameOptions{}
switch *flagGraphicsLibrary {
case "":
op.GraphicsLibrary = ebiten.GraphicsLibraryAuto
case "opengl":
op.GraphicsLibrary = ebiten.GraphicsLibraryOpenGL
case "directx":
op.GraphicsLibrary = ebiten.GraphicsLibraryDirectX
case "metal":
op.GraphicsLibrary = ebiten.GraphicsLibraryMetal
default:
log.Fatalf("unexpected graphics library: %s", *flagGraphicsLibrary)
}
const title = "Window Size (Ebitengine Demo)" const title = "Window Size (Ebitengine Demo)"
ww := int(float64(g.width) * initScreenScale) ww := int(float64(g.width) * initScreenScale)
wh := int(float64(g.height) * initScreenScale) wh := int(float64(g.height) * initScreenScale)
ebiten.SetWindowSize(ww, wh) ebiten.SetWindowSize(ww, wh)
ebiten.SetWindowTitle(title) ebiten.SetWindowTitle(title)
if err := ebiten.RunGame(g); err != nil { if err := ebiten.RunGameWithOptions(g, op); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -127,23 +127,33 @@ func (c CompositeMode) blend() Blend {
} }
// GraphicsLibrary represets graphics libraries supported by the engine. // GraphicsLibrary represets graphics libraries supported by the engine.
type GraphicsLibrary = ui.GraphicsLibrary type GraphicsLibrary int
const ( const (
GraphicsLibraryAuto GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryAuto)
// GraphicsLibraryUnknown represents the state at which graphics library cannot be determined, // GraphicsLibraryUnknown represents the state at which graphics library cannot be determined,
// e.g. hasn't loaded yet or failed to initialize. // e.g. hasn't loaded yet or failed to initialize.
GraphicsLibraryUnknown GraphicsLibrary = ui.GraphicsLibraryUnknown GraphicsLibraryUnknown GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryUnknown)
// GraphicsLibraryOpenGL represents the graphics library OpenGL. // GraphicsLibraryOpenGL represents the graphics library OpenGL.
GraphicsLibraryOpenGL GraphicsLibrary = ui.GraphicsLibraryOpenGL GraphicsLibraryOpenGL GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryOpenGL)
// GraphicsLibraryDirectX represents the graphics library Microsoft DirectX. // GraphicsLibraryDirectX represents the graphics library Microsoft DirectX.
GraphicsLibraryDirectX GraphicsLibrary = ui.GraphicsLibraryDirectX GraphicsLibraryDirectX GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryDirectX)
// GraphicsLibraryMetal represents the graphics library Apple's Metal. // GraphicsLibraryMetal represents the graphics library Apple's Metal.
GraphicsLibraryMetal GraphicsLibrary = ui.GraphicsLibraryMetal GraphicsLibraryMetal GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryMetal)
) )
// String returns a string representing the graphics library.
func (g GraphicsLibrary) String() string {
return ui.GraphicsLibrary(g).String()
}
// Ensures GraphicsLibraryAuto is zero (the default value for RunOptions).
var _ [GraphicsLibraryAuto]int = [0]int{}
// DebugInfo is a struct to store debug info about the graphics. // DebugInfo is a struct to store debug info about the graphics.
type DebugInfo struct { type DebugInfo struct {
// GraphicsLibrary represents the graphics library currently in use. // GraphicsLibrary represents the graphics library currently in use.
@ -152,5 +162,5 @@ type DebugInfo struct {
// ReadDebugInfo writes debug info (e.g. current graphics library) into a provided struct. // ReadDebugInfo writes debug info (e.g. current graphics library) into a provided struct.
func ReadDebugInfo(d *DebugInfo) { func ReadDebugInfo(d *DebugInfo) {
d.GraphicsLibrary = ui.GetGraphicsLibrary() d.GraphicsLibrary = GraphicsLibrary(ui.GetGraphicsLibrary())
} }

View File

@ -543,7 +543,7 @@ type DrawTrianglesShaderOptions struct {
} }
// Check the number of images. // Check the number of images.
var _ [len(DrawTrianglesShaderOptions{}.Images)]struct{} = [graphics.ShaderImageCount]struct{}{} var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]struct{} = [0]struct{}{}
// DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader. // DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
// //

View File

@ -21,6 +21,7 @@ import (
var theGlobalState = globalState{ var theGlobalState = globalState{
isScreenClearedEveryFrame_: 1, isScreenClearedEveryFrame_: 1,
graphicsLibrary_: int32(GraphicsLibraryUnknown),
} }
// globalState represents a global state in this package. // globalState represents a global state in this package.

View File

@ -28,7 +28,7 @@ type graphicsDriverCreator interface {
newMetal() (graphicsdriver.Graphics, error) newMetal() (graphicsdriver.Graphics, error)
} }
func newGraphicsDriver(creator graphicsDriverCreator) (graphicsdriver.Graphics, error) { func newGraphicsDriver(creator graphicsDriverCreator, graphicsLibrary GraphicsLibrary) (graphicsdriver.Graphics, error) {
envName := "EBITENGINE_GRAPHICS_LIBRARY" envName := "EBITENGINE_GRAPHICS_LIBRARY"
env := os.Getenv(envName) env := os.Getenv(envName)
if env == "" { if env == "" {
@ -39,6 +39,20 @@ func newGraphicsDriver(creator graphicsDriverCreator) (graphicsdriver.Graphics,
switch env { switch env {
case "", "auto": case "", "auto":
// Use the specified graphics library.
// Otherwise, prefer the environment variable.
case "opengl":
graphicsLibrary = GraphicsLibraryOpenGL
case "directx":
graphicsLibrary = GraphicsLibraryDirectX
case "metal":
graphicsLibrary = GraphicsLibraryMetal
default:
return nil, fmt.Errorf("ui: an unsupported graphics library is specified by the environment variable: %s", env)
}
switch graphicsLibrary {
case GraphicsLibraryAuto:
g, lib, err := creator.newAuto() g, lib, err := creator.newAuto()
if err != nil { if err != nil {
return nil, err return nil, err
@ -48,41 +62,68 @@ func newGraphicsDriver(creator graphicsDriverCreator) (graphicsdriver.Graphics,
} }
theGlobalState.setGraphicsLibrary(lib) theGlobalState.setGraphicsLibrary(lib)
return g, nil return g, nil
case "opengl": case GraphicsLibraryOpenGL:
g, err := creator.newOpenGL() g, err := creator.newOpenGL()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if g == nil { if g == nil {
return nil, fmt.Errorf("ui: %s=%s is specified but OpenGL is not available", envName, env) return nil, fmt.Errorf("ui: %s is specified but OpenGL is not available", graphicsLibrary)
} }
theGlobalState.setGraphicsLibrary(GraphicsLibraryOpenGL) theGlobalState.setGraphicsLibrary(GraphicsLibraryOpenGL)
return g, nil return g, nil
case "directx": case GraphicsLibraryDirectX:
g, err := creator.newDirectX() g, err := creator.newDirectX()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if g == nil { if g == nil {
return nil, fmt.Errorf("ui: %s=%s is specified but DirectX is not available.", envName, env) return nil, fmt.Errorf("ui: %s is specified but DirectX is not available.", graphicsLibrary)
} }
theGlobalState.setGraphicsLibrary(GraphicsLibraryDirectX) theGlobalState.setGraphicsLibrary(GraphicsLibraryDirectX)
return g, nil return g, nil
case "metal": case GraphicsLibraryMetal:
g, err := creator.newMetal() g, err := creator.newMetal()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if g == nil { if g == nil {
return nil, fmt.Errorf("ui: %s=%s is specified but Metal is not available", envName, env) return nil, fmt.Errorf("ui: %s is specified but Metal is not available", graphicsLibrary)
} }
theGlobalState.setGraphicsLibrary(GraphicsLibraryMetal) theGlobalState.setGraphicsLibrary(GraphicsLibraryMetal)
return g, nil return g, nil
default: default:
return nil, fmt.Errorf("ui: an unsupported graphics library is specified: %s", env) return nil, fmt.Errorf("ui: an unsupported graphics library is specified: %d", graphicsLibrary)
} }
} }
func GraphicsDriverForTesting() graphicsdriver.Graphics { func GraphicsDriverForTesting() graphicsdriver.Graphics {
return theUI.graphicsDriver return theUI.graphicsDriver
} }
type GraphicsLibrary int
const (
GraphicsLibraryAuto GraphicsLibrary = iota
GraphicsLibraryOpenGL
GraphicsLibraryDirectX
GraphicsLibraryMetal
GraphicsLibraryUnknown
)
func (g GraphicsLibrary) String() string {
switch g {
case GraphicsLibraryAuto:
return "Auto"
case GraphicsLibraryOpenGL:
return "OpenGL"
case GraphicsLibraryDirectX:
return "DirectX"
case GraphicsLibraryMetal:
return "Metal"
case GraphicsLibraryUnknown:
return "Unknown"
default:
return fmt.Sprintf("GraphicsLibrary(%d)", g)
}
}

View File

@ -21,7 +21,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/thread" "github.com/hajimehoshi/ebiten/v2/internal/thread"
) )
func (u *userInterfaceImpl) Run(game Game) error { func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
u.context = newContext(game) u.context = newContext(game)
// 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).
@ -36,7 +36,7 @@ func (u *userInterfaceImpl) Run(game Game) error {
var err error var err error
if u.t.Call(func() { if u.t.Call(func() {
err = u.init() err = u.init(options)
}); err != nil { }); err != nil {
ch <- err ch <- err
return return

View File

@ -21,7 +21,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/thread" "github.com/hajimehoshi/ebiten/v2/internal/thread"
) )
func (u *userInterfaceImpl) Run(game Game) error { func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
u.context = newContext(game) u.context = newContext(game)
// 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).
@ -30,7 +30,7 @@ func (u *userInterfaceImpl) Run(game Game) error {
u.setRunning(true) u.setRunning(true)
if err := u.init(); err != nil { if err := u.init(options); err != nil {
return err return err
} }

View File

@ -73,15 +73,6 @@ const (
WindowResizingModeEnabled WindowResizingModeEnabled
) )
type GraphicsLibrary int
const (
GraphicsLibraryUnknown GraphicsLibrary = iota
GraphicsLibraryOpenGL
GraphicsLibraryDirectX
GraphicsLibraryMetal
)
type UserInterface struct { type UserInterface struct {
userInterfaceImpl userInterfaceImpl
} }
@ -104,3 +95,7 @@ func (u *UserInterface) dumpScreenshot(mipmap *mipmap.Mipmap, name string, black
func (u *UserInterface) dumpImages(dir string) (string, error) { func (u *UserInterface) dumpImages(dir string) (string, error) {
return atlas.DumpImages(u.graphicsDriver, dir) return atlas.DumpImages(u.graphicsDriver, dir)
} }
type RunOptions struct {
GraphicsLibrary GraphicsLibrary
}

View File

@ -888,7 +888,7 @@ event:
u.framebufferSizeCallbackCh = nil u.framebufferSizeCallbackCh = nil
} }
func (u *userInterfaceImpl) init() error { func (u *userInterfaceImpl) init(options *RunOptions) error {
glfw.WindowHint(glfw.AutoIconify, glfw.False) glfw.WindowHint(glfw.AutoIconify, glfw.False)
decorated := glfw.False decorated := glfw.False
@ -906,7 +906,7 @@ func (u *userInterfaceImpl) init() error {
g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{ g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
transparent: transparent, transparent: transparent,
}) }, options.GraphicsLibrary)
if err != nil { if err != nil {
return err return err
} }

View File

@ -637,7 +637,7 @@ func (u *userInterfaceImpl) forceUpdateOnMinimumFPSMode() {
}() }()
} }
func (u *userInterfaceImpl) Run(game Game) error { func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
if u.initFocused && window.Truthy() { if u.initFocused && window.Truthy() {
// Do not focus the canvas when the current document is in an iframe. // Do not focus the canvas when the current document is in an iframe.
// Otherwise, the parent page tries to focus the iframe on every loading, which is annoying (#1373). // Otherwise, the parent page tries to focus the iframe on every loading, which is annoying (#1373).
@ -649,7 +649,7 @@ func (u *userInterfaceImpl) Run(game Game) error {
u.running = true u.running = true
g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{ g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
canvas: canvas, canvas: canvas,
}) }, options.GraphicsLibrary)
if err != nil { if err != nil {
return err return err
} }

View File

@ -235,10 +235,10 @@ func (u *userInterfaceImpl) SetForeground(foreground bool) error {
} }
} }
func (u *userInterfaceImpl) Run(game Game) error { func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
u.setGBuildSizeCh = make(chan struct{}) u.setGBuildSizeCh = make(chan struct{})
go func() { go func() {
if err := u.run(game, true); err != nil { if err := u.run(game, true, options); err != nil {
// As mobile apps never ends, Loop can't return. Just panic here. // As mobile apps never ends, Loop can't return. Just panic here.
panic(err) panic(err)
} }
@ -247,19 +247,19 @@ func (u *userInterfaceImpl) Run(game Game) error {
return nil return nil
} }
func RunWithoutMainLoop(game Game) { func RunWithoutMainLoop(game Game, options *RunOptions) {
theUI.runWithoutMainLoop(game) theUI.runWithoutMainLoop(game, options)
} }
func (u *userInterfaceImpl) runWithoutMainLoop(game Game) { func (u *userInterfaceImpl) runWithoutMainLoop(game Game, options *RunOptions) {
go func() { go func() {
if err := u.run(game, false); err != nil { if err := u.run(game, false, options); err != nil {
u.errCh <- err u.errCh <- err
} }
}() }()
} }
func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) { func (u *userInterfaceImpl) run(game Game, mainloop bool, options *RunOptions) (err error) {
// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for // Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer // Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
// unfortunately. // unfortunately.
@ -284,7 +284,7 @@ func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) {
g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{ g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
gomobileContext: mgl, gomobileContext: mgl,
}) }, options.GraphicsLibrary)
if err != nil { if err != nil {
return err return err
} }

View File

@ -56,9 +56,9 @@ type userInterfaceImpl struct {
input Input input Input
} }
func (u *userInterfaceImpl) Run(game Game) error { func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error {
u.context = newContext(game) u.context = newContext(game)
g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}) g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}, options.GraphicsLibrary)
if err != nil { if err != nil {
return err return err
} }

View File

@ -49,11 +49,11 @@ func (s *state) run() {
atomic.StoreInt32(&s.running, 1) atomic.StoreInt32(&s.running, 1)
} }
func SetGame(game ebiten.Game) { func SetGame(game ebiten.Game, options *ebiten.RunGameOptions) {
if theState.isRunning() { if theState.isRunning() {
panic("ebitenmobileview: SetGame cannot be called twice or more") panic("ebitenmobileview: SetGame cannot be called twice or more")
} }
ebiten.RunGameWithoutMainLoop(game) ebiten.RunGameWithoutMainLoop(game, options)
theState.run() theState.run()
} }

View File

@ -21,6 +21,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/mobile/ebitenmobileview" "github.com/hajimehoshi/ebiten/v2/mobile/ebitenmobileview"
) )
func setGame(game ebiten.Game) { func setGame(game ebiten.Game, options *ebiten.RunGameOptions) {
ebitenmobileview.SetGame(game) ebitenmobileview.SetGame(game, options)
} }

View File

@ -20,6 +20,6 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
func setGame(game ebiten.Game) { func setGame(game ebiten.Game, options *ebiten.RunGameOptions) {
panic("mobile: setGame is not implemented in this environment") panic("mobile: setGame is not implemented in this environment")
} }

View File

@ -29,5 +29,14 @@ import (
// //
// SetGame can be called anytime. Until SetGame is called, the game does not start. // SetGame can be called anytime. Until SetGame is called, the game does not start.
func SetGame(game ebiten.Game) { func SetGame(game ebiten.Game) {
setGame(game) SetGameWithOptions(game, nil)
}
// SetGameWithOptions sets a mobile game with the specified options.
//
// SetGameWithOptions is expected to be called only once.
//
// SetGameWithOptions can be called anytime. Until SetGameWithOptions is called, the game does not start.
func SetGameWithOptions(game ebiten.Game, options *ebiten.RunGameOptions) {
setGame(game, options)
} }

57
run.go
View File

@ -224,13 +224,57 @@ var Termination = ui.RegularTermination
// //
// The size unit is device-independent pixel. // The size unit is device-independent pixel.
// //
// Don't call RunGame twice or more in one process. // Don't call RunGame or RunGameWithOptions twice or more in one process.
func RunGame(game Game) error { func RunGame(game Game) error {
return RunGameWithOptions(game, nil)
}
// RungameOptions represents options for RunGameWithOptions.
type RunGameOptions struct {
// GraphicsLibrary is a graphics library Ebitengine will use.
// The default (zero) value is GraphicsLibraryAuto, which lets Ebitengine choose the graphics library.
GraphicsLibrary GraphicsLibrary
}
// RunGameWithOptions starts the main loop and runs the game with the specified options.
// 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 Layout function is called when necessary, and you can specify the logical screen size by the function.
//
// options can be nil. In this case, the default options are used.
//
// If game implements FinalScreenDrawer, its DrawFinalScreen is called after Draw.
// The argument screen represents the final screen. The argument offscreen is an offscreen modified at Draw.
// If game does not implement FinalScreenDrawer, the dafault rendering for the final screen is used.
//
// game's functions are called on the same goroutine.
//
// On browsers, it is strongly recommended to use iframe if you embed an Ebitengine application in your website.
//
// RunGameWithOptions must be called on the main thread.
// Note that Ebitengine bounds the main goroutine to the main OS thread by runtime.LockOSThread.
//
// Ebitengine tries to call game's Update function 60 times a second by default. In other words,
// TPS (ticks per second) is 60 by default.
// This is not related to framerate (display's refresh rate).
//
// RunGameWithOptions returns error when 1) an error happens in the underlying graphics driver, 2) an audio error happens
// or 3) Update returns an error. In the case of 3), RunGameWithOptions 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
// with == or != directly.
//
// If you want to terminate a game on desktops, it is recommended to return Termination at Update, which will halt
// execution without returning an error value from RunGameWithOptions.
//
// The size unit is device-independent pixel.
//
// Don't call RunGame or RunGameWithOptions twice or more in one process.
func RunGameWithOptions(game Game, options *RunGameOptions) error {
defer atomic.StoreInt32(&isRunGameEnded_, 1) defer atomic.StoreInt32(&isRunGameEnded_, 1)
initializeWindowPositionIfNeeded(WindowSize()) initializeWindowPositionIfNeeded(WindowSize())
g := newGameForUI(game) g := newGameForUI(game)
if err := ui.Get().Run(g); err != nil { if err := ui.Get().Run(g, toUIRunOptions(options)); err != nil {
if errors.Is(err, Termination) { if errors.Is(err, Termination) {
return nil return nil
} }
@ -557,3 +601,12 @@ func SetScreenTransparent(transparent bool) {
func SetInitFocused(focused bool) { func SetInitFocused(focused bool) {
ui.Get().SetInitFocused(focused) ui.Get().SetInitFocused(focused)
} }
func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
if options == nil {
return &ui.RunOptions{}
}
return &ui.RunOptions{
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
}
}

View File

@ -27,6 +27,6 @@ import (
// Instead, functions in github.com/hajimehoshi/ebiten/v2/mobile package calls this. // Instead, functions in github.com/hajimehoshi/ebiten/v2/mobile package calls this.
// //
// TODO: Remove this. In order to remove this, the gameForUI should be in another package. // TODO: Remove this. In order to remove this, the gameForUI should be in another package.
func RunGameWithoutMainLoop(game Game) { func RunGameWithoutMainLoop(game Game, options *RunGameOptions) {
ui.RunWithoutMainLoop(newGameForUI(game)) ui.RunWithoutMainLoop(newGameForUI(game), toUIRunOptions(options))
} }