ebiten: Add ebitensinglethread build tag (#1396)

Updates #1367
This commit is contained in:
Jake Coffman 2020-10-20 12:55:17 -05:00 committed by GitHub
parent a76de7c251
commit 290f05060a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 47 deletions

3
doc.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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