Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ebiten: add RunOnMainThread(func()) #1927

Merged
merged 5 commits into from
Jan 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions internal/graphicscommand/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,11 @@ func (q *commandQueue) Enqueue(command command) {
}

// Flush flushes the command queue.
func (q *commandQueue) Flush() error {
return runOnMainThread(func() error {
return q.flush()
func (q *commandQueue) Flush() (err error) {
RunOnMainThread(func() {
err = q.flush()
})
return
}

// flush must be called the main thread.
Expand Down Expand Up @@ -699,18 +700,19 @@ func (c *newShaderCommand) Exec(indexOffset int) error {
}

// InitializeGraphicsDriverState initialize the current graphics driver state.
func InitializeGraphicsDriverState() error {
return runOnMainThread(func() error {
return theGraphicsDriver.Initialize()
func InitializeGraphicsDriverState() (err error) {
RunOnMainThread(func() {
err = theGraphicsDriver.Initialize()
})
return
}

// ResetGraphicsDriverState resets the current graphics driver state.
// If the graphics driver doesn't have an API to reset, ResetGraphicsDriverState does nothing.
func ResetGraphicsDriverState() error {
func ResetGraphicsDriverState() (err error) {
if r, ok := theGraphicsDriver.(interface{ Reset() error }); ok {
return runOnMainThread(func() error {
return r.Reset()
RunOnMainThread(func() {
err = r.Reset()
})
}
return nil
Expand All @@ -719,9 +721,8 @@ func ResetGraphicsDriverState() error {
// MaxImageSize returns the maximum size of an image.
func MaxImageSize() int {
var size int
_ = runOnMainThread(func() error {
RunOnMainThread(func() {
size = theGraphicsDriver.MaxImageSize()
return nil
})
return size
}
10 changes: 6 additions & 4 deletions internal/graphicscommand/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ package graphicscommand
var theThread Thread

type Thread interface {
Call(f func() error) error
Call(f func())
}

// SetMainThread must be called from the main thread (i.e, the goroutine where the thread is created).
func SetMainThread(thread Thread) {
theThread = thread
}

func runOnMainThread(f func() error) error {
// RunOnMainThread calls f on the main thread, and returns an error if any.
func RunOnMainThread(f func()) {
// The thread is nil when 1) GOOS=js or 2) using golang.org/x/mobile/gl.
// When golang.org/x/mobile/gl is used, all the GL functions are called via Context, which already runs on an
// appropriate thread.
if theThread == nil {
return f()
f()
return
}
return theThread.Call(f)
theThread.Call(f)
}
56 changes: 31 additions & 25 deletions internal/thread/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,64 @@

package thread

import (
"errors"
)

// Thread defines threading behavior in Ebiten.
type Thread interface {
Call(func() error) error
Call(func())
Loop()
Stop()
}

// OSThread represents an OS thread.
type OSThread struct {
funcs chan func() error
results chan error
funcs chan func()
done chan struct{}
terminate chan struct{}
}

// NewOSThread creates a new 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),
funcs: make(chan func()),
done: make(chan struct{}),
terminate: make(chan struct{}),
}
}

// BreakLoop represents an termination of the loop.
var BreakLoop = errors.New("break loop")

// Loop starts the thread loop until a posted function returns BreakLoop.
// Loop starts the thread loop until Stop is called.
//
// Loop must be called on the thread.
func (t *OSThread) Loop() {
for f := range t.funcs {
err := f()
if err == BreakLoop {
t.results <- nil
for {
select {
case fn := <-t.funcs:
func() {
defer func() {
t.done <- struct{}{}
}()

fn()
}()
case <-t.terminate:
return
}
t.results <- err
}
}

// Stop stops the thread loop.
func (t *OSThread) Stop() {
close(t.terminate)
}

// Call calls f on the thread.
//
// Do not call this from the same thread. This would block forever.
//
// If f returns BreakLoop, Loop returns.
//
// Call blocks if Loop is not called.
func (t *OSThread) Call(f func() error) error {
func (t *OSThread) Call(f func()) {
t.funcs <- f
return <-t.results
<-t.done
}

// NoopThread is used to disable threading.
Expand All @@ -81,6 +86,7 @@ func NewNoopThread() *NoopThread {
func (t *NoopThread) Loop() {}

// Call executes the func immediately
func (t *NoopThread) Call(f func() error) error {
return f()
}
func (t *NoopThread) Call(f func()) { f() }

// Stop does nothing
func (t *NoopThread) Stop() {}
17 changes: 5 additions & 12 deletions internal/uidriver/glfw/run_notsinglethread.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@ func (u *UserInterface) Run(uicontext driver.UIContext) error {

ch := make(chan error, 1)
go func() {
defer func() {
_ = u.t.Call(func() error {
return thread.BreakLoop
})
}()
defer u.t.Stop()

defer close(ch)

if err := u.t.Call(func() error {
return u.init()
var err error
if u.t.Call(func() {
err = u.init()
}); err != nil {
ch <- err
return
Expand Down Expand Up @@ -77,11 +74,7 @@ func (u *UserInterface) runOnAnotherThreadFromMainThread(f func() error) error {

var err error
go func() {
defer func() {
_ = u.t.Call(func() error {
return thread.BreakLoop
})
}()
defer u.t.Stop()
err = f()
}()
u.t.Loop()
Expand Down
Loading