diff --git a/doc.go b/doc.go index e5138c54d..db51d67c3 100644 --- a/doc.go +++ b/doc.go @@ -70,4 +70,7 @@ // number of graphics commands affects the performance of your game. // // `ebitengl` forces to use OpenGL in any environments. +// +// `ebitensinglethread` disables Ebiten's thread safety to unlock maximum performance. If you use this you will have +// to manage threads yourself. Functions like IsKeyPressed will no longer be concurrent-safe with this build tag. package ebiten diff --git a/internal/thread/thread.go b/internal/thread/thread.go index aad4753a3..08c8edf51 100644 --- a/internal/thread/thread.go +++ b/internal/thread/thread.go @@ -18,17 +18,23 @@ import ( "errors" ) -// Thread represents an OS thread. -type Thread struct { +// Thread defines threading behavior in ebiten. +type Thread interface { + Call(func() error) error + Loop() +} + +// OSThread represents an OS thread. +type OSThread struct { funcs chan func() error results chan error } -// New creates a new thread. +// NewOSThread creates a new thread. // -// It is assumed that the OS thread is fixed by runtime.LockOSThread when New is called. -func New() *Thread { - return &Thread{ +// It is assumed that the OS thread is fixed by runtime.LockOSThread when NewOSThread is called. +func NewOSThread() *OSThread { + return &OSThread{ funcs: make(chan func() error), results: make(chan error), } @@ -40,7 +46,7 @@ var BreakLoop = errors.New("break loop") // Loop starts the thread loop until a posted function returns BreakLoop. // // Loop must be called on the thread. -func (t *Thread) Loop() { +func (t *OSThread) Loop() { for f := range t.funcs { err := f() if err == BreakLoop { @@ -58,7 +64,23 @@ func (t *Thread) Loop() { // If f returns BreakLoop, Loop returns. // // Call blocks if Loop is not called. -func (t *Thread) Call(f func() error) error { +func (t *OSThread) Call(f func() error) error { t.funcs <- f return <-t.results } + +// NoopThread is used to disable threading. +type NoopThread struct{} + +// NewNoopThread creates a new thread that does no threading. +func NewNoopThread() Thread { + return &NoopThread{} +} + +// Loop does nothing +func (t *NoopThread) Loop() {} + +// Call executes the func immediately +func (t *NoopThread) Call(f func() error) error { + return f() +} diff --git a/internal/uidriver/glfw/run_notsinglethread.go b/internal/uidriver/glfw/run_notsinglethread.go new file mode 100644 index 000000000..b7905122a --- /dev/null +++ b/internal/uidriver/glfw/run_notsinglethread.go @@ -0,0 +1,61 @@ +// Copyright 2020 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. + +// +build !ebitensinglethread +// +build darwin freebsd linux windows +// +build !android +// +build !ios + +package glfw + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/driver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" + "github.com/hajimehoshi/ebiten/v2/internal/thread" +) + +func (u *UserInterface) Run(uicontext driver.UIContext) error { + u.context = uicontext + + // Initialize the main thread first so the thread is available at u.run (#809). + u.t = thread.NewOSThread() + graphicscommand.SetMainThread(u.t) + + ch := make(chan error, 1) + go func() { + defer func() { + _ = u.t.Call(func() error { + return thread.BreakLoop + }) + }() + + defer close(ch) + + _ = u.t.Call(func() error { + if err := u.init(); err != nil { + ch <- err + } + return nil + }) + + if err := u.loop(); err != nil { + ch <- err + } + }() + + u.setRunning(true) + u.t.Loop() + u.setRunning(false) + return <-ch +} diff --git a/internal/uidriver/glfw/run_singlethread.go b/internal/uidriver/glfw/run_singlethread.go new file mode 100644 index 000000000..42b0c48f0 --- /dev/null +++ b/internal/uidriver/glfw/run_singlethread.go @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +// +build ebitensinglethread +// +build darwin freebsd linux windows +// +build !android +// +build !ios + +package glfw + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/driver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" + "github.com/hajimehoshi/ebiten/v2/internal/thread" +) + +func (u *UserInterface) Run(uicontext driver.UIContext) error { + u.context = uicontext + + // Initialize the main thread first so the thread is available at u.run (#809). + u.t = thread.NewNoopThread() + graphicscommand.SetMainThread(u.t) + + u.setRunning(true) + + if err := u.init(); err != nil { + return err + } + + if err := u.loop(); err != nil { + return err + } + + u.setRunning(false) + return nil +} diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 857bcb482..f933f944b 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -29,7 +29,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/devicescale" "github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/glfw" - "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/hooks" "github.com/hajimehoshi/ebiten/v2/internal/thread" ) @@ -80,7 +79,7 @@ type UserInterface struct { input Input iwindow window - t *thread.Thread + t thread.Thread m sync.RWMutex } @@ -583,41 +582,6 @@ func init() { runtime.LockOSThread() } -func (u *UserInterface) Run(uicontext driver.UIContext) error { - u.context = uicontext - - // Initialize the main thread first so the thread is available at u.run (#809). - u.t = thread.New() - graphicscommand.SetMainThread(u.t) - - ch := make(chan error, 1) - go func() { - defer func() { - _ = u.t.Call(func() error { - return thread.BreakLoop - }) - }() - - defer close(ch) - - _ = u.t.Call(func() error { - if err := u.init(); err != nil { - ch <- err - } - return nil - }) - - if err := u.loop(); err != nil { - ch <- err - } - }() - - u.setRunning(true) - u.t.Loop() - u.setRunning(false) - return <-ch -} - func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) { panic("glfw: RunWithoutMainLoop is not implemented") } diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index ef7b2698b..ec2a9b619 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -111,7 +111,7 @@ type UserInterface struct { input Input - t *thread.Thread + t *thread.OSThread m sync.RWMutex } @@ -271,7 +271,7 @@ func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) ctx := <-glContextCh u.Graphics().(*opengl.Graphics).SetGomobileGLContext(ctx) } else { - u.t = thread.New() + u.t = thread.NewOSThread() graphicscommand.SetMainThread(u.t) }