loop: Bug fix: vsync should not use channels on browsers (#259)

This commit is contained in:
Hajime Hoshi 2016-09-01 02:38:47 +09:00
parent 57a32464dc
commit 1e0bdf844d
6 changed files with 76 additions and 47 deletions

View File

@ -86,6 +86,12 @@ type GraphicsContext interface {
UpdateAndDraw(context *opengl.Context, updateCount int) error
}
type regularTermination struct{}
func (e regularTermination) Error() string {
return "regular termination"
}
func Run(g GraphicsContext, width, height int, scale float64, title string, fps int) (err error) {
if currentRunContext != nil {
return errors.New("loop: The game is already running")
@ -107,24 +113,41 @@ func Run(g GraphicsContext, width, height int, scale float64, title string, fps
n := now()
currentRunContext.lastUpdated = n
currentRunContext.lastFPSUpdated = n
if err := ui.CurrentUI().AnimationFrameLoop(func() error {
return currentRunContext.update(g)
}); err != nil {
if err == (regularTermination{}) {
return nil
}
return err
}
return nil
}
func (c *runContext) update(g GraphicsContext) error {
for {
e, err := ui.CurrentUI().Update()
if err != nil {
return err
}
switch e := e.(type) {
case ui.NopEvent:
return nil
case ui.ScreenSizeEvent:
if err := g.SetSize(e.Width, e.Height, e.ActualScale); err != nil {
return err
}
e.Done <- struct{}{}
continue
case ui.CloseEvent:
return nil
return regularTermination{}
case ui.RenderEvent:
if err := currentRunContext.render(g); err != nil {
return err
}
e.Done <- struct{}{}
return nil
default:
panic("not reach")
}
@ -163,9 +186,9 @@ func (c *runContext) render(g GraphicsContext) error {
if err := g.UpdateAndDraw(ui.GLContext(), tt); err != nil {
return err
}
if err := ui.CurrentUI().SwapBuffers(); err != nil {
/*if err := ui.CurrentUI().SwapBuffers(); err != nil {
return err
}
}*/
c.lastUpdated += int64(tt) * int64(time.Second) / int64(fps)
c.frames++
return nil

View File

@ -14,6 +14,9 @@
package ui
type NopEvent struct {
}
type CloseEvent struct {
}

View File

@ -17,8 +17,8 @@ package ui
type UserInterface interface {
Start(width, height int, scale float64, title string) error
Update() (interface{}, error)
SwapBuffers() error
Terminate() error
AnimationFrameLoop(f func() error) error
ScreenScale() float64
SetScreenSize(width, height int) (bool, error)
SetScreenScale(scale float64) (bool, error)

View File

@ -251,17 +251,21 @@ func (u *userInterface) Terminate() error {
return nil
}
func (u *userInterface) SwapBuffers() error {
// The bound framebuffer must be the default one (0) before swapping buffers.
if err := glContext.BindScreenFramebuffer(); err != nil {
return err
func (u *userInterface) AnimationFrameLoop(f func() error) error {
for {
if err := f(); err != nil {
return err
}
// The bound framebuffer must be the default one (0) before swapping buffers.
if err := glContext.BindScreenFramebuffer(); err != nil {
return err
}
if err := u.runOnMainThread(func() error {
return u.swapBuffers()
}); err != nil {
return err
}
}
if err := u.runOnMainThread(func() error {
return u.swapBuffers()
}); err != nil {
return err
}
return nil
}
func (u *userInterface) swapBuffers() error {

View File

@ -29,12 +29,14 @@ type userInterface struct {
scale float64
deviceScale float64
sizeChanged bool
contextRestored chan struct{}
windowFocus chan struct{}
contextRestored bool
windowFocus bool
}
var currentUI = &userInterface{
sizeChanged: true,
sizeChanged: true,
contextRestored: true,
windowFocus: true,
}
func CurrentUI() UserInterface {
@ -46,16 +48,6 @@ func shown() bool {
return !js.Global.Get("document").Get("hidden").Bool()
}
func vsync() {
ch := make(chan struct{})
js.Global.Get("window").Call("requestAnimationFrame", func() {
// TODO: In iOS8, this is called at every 1/30[sec] frame.
// Can we use DOMHighResTimeStamp?
close(ch)
})
<-ch
}
func (u *userInterface) SetScreenSize(width, height int) (bool, error) {
return u.setScreenSize(width, height, u.scale), nil
}
@ -74,11 +66,11 @@ func (u *userInterface) ActualScreenScale() float64 {
}
func (u *userInterface) Update() (interface{}, error) {
if u.windowFocus != nil {
<-u.windowFocus
if !u.windowFocus {
return NopEvent{}, nil
}
if u.contextRestored != nil {
<-u.contextRestored
if !u.contextRestored {
return NopEvent{}, nil
}
currentInput.updateGamepads()
if u.sizeChanged {
@ -102,12 +94,19 @@ func (u *userInterface) Terminate() error {
return nil
}
func (u *userInterface) SwapBuffers() error {
vsync()
for !shown() {
vsync()
func (u *userInterface) AnimationFrameLoop(f func() error) error {
ch := make(chan error)
var ff func()
ff = func() {
if err := f(); err != nil {
ch <- err
close(ch)
return
}
js.Global.Get("window").Call("requestAnimationFrame", ff)
}
return nil
ff()
return <-ch
}
func (u *userInterface) FinishRendering() error {
@ -151,14 +150,10 @@ func initialize() error {
<-ch
}
window.Call("addEventListener", "focus", func() {
if currentUI.windowFocus == nil {
return
}
close(currentUI.windowFocus)
currentUI.windowFocus = nil
currentUI.windowFocus = true
})
window.Call("addEventListener", "blur", func() {
currentUI.windowFocus = make(chan struct{})
currentUI.windowFocus = false
})
canvas = doc.Call("createElement", "canvas")
@ -244,11 +239,11 @@ func initialize() error {
canvas.Call("addEventListener", "webglcontextlost", func(e *js.Object) {
e.Call("preventDefault")
currentUI.contextRestored = make(chan struct{})
currentUI.contextRestored = false
})
canvas.Call("addEventListener", "webglcontextrestored", func(e *js.Object) {
close(currentUI.contextRestored)
currentUI.contextRestored = nil
// TODO: Call preventDefault?
currentUI.contextRestored = true
})
return nil
}

View File

@ -98,8 +98,12 @@ func (u *userInterface) Update() (interface{}, error) {
return RenderEvent{chRenderEnd}, nil
}
func (u *userInterface) SwapBuffers() error {
return nil
func (u *userInterface) AnimationFrameLoop(f func() error) error {
for {
if err := f(); err != nil {
return err
}
}
}
func (u *userInterface) SetScreenSize(width, height int) (bool, error) {