Skip to content

Commit

Permalink
ebiten: add RunOnMainThread(func()) (hajimehoshi#1927)
Browse files Browse the repository at this point in the history
  • Loading branch information
changkun authored Jan 2, 2022
1 parent 0e74b34 commit 626c91e
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 160 deletions.
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

0 comments on commit 626c91e

Please sign in to comment.