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 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) { func Run(g GraphicsContext, width, height int, scale float64, title string, fps int) (err error) {
if currentRunContext != nil { if currentRunContext != nil {
return errors.New("loop: The game is already running") 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() n := now()
currentRunContext.lastUpdated = n currentRunContext.lastUpdated = n
currentRunContext.lastFPSUpdated = 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 { for {
e, err := ui.CurrentUI().Update() e, err := ui.CurrentUI().Update()
if err != nil { if err != nil {
return err return err
} }
switch e := e.(type) { switch e := e.(type) {
case ui.NopEvent:
return nil
case ui.ScreenSizeEvent: case ui.ScreenSizeEvent:
if err := g.SetSize(e.Width, e.Height, e.ActualScale); err != nil { if err := g.SetSize(e.Width, e.Height, e.ActualScale); err != nil {
return err return err
} }
e.Done <- struct{}{} e.Done <- struct{}{}
continue
case ui.CloseEvent: case ui.CloseEvent:
return nil return regularTermination{}
case ui.RenderEvent: case ui.RenderEvent:
if err := currentRunContext.render(g); err != nil { if err := currentRunContext.render(g); err != nil {
return err return err
} }
e.Done <- struct{}{} e.Done <- struct{}{}
return nil
default: default:
panic("not reach") panic("not reach")
} }
@ -163,9 +186,9 @@ func (c *runContext) render(g GraphicsContext) error {
if err := g.UpdateAndDraw(ui.GLContext(), tt); err != nil { if err := g.UpdateAndDraw(ui.GLContext(), tt); err != nil {
return err return err
} }
if err := ui.CurrentUI().SwapBuffers(); err != nil { /*if err := ui.CurrentUI().SwapBuffers(); err != nil {
return err return err
} }*/
c.lastUpdated += int64(tt) * int64(time.Second) / int64(fps) c.lastUpdated += int64(tt) * int64(time.Second) / int64(fps)
c.frames++ c.frames++
return nil return nil

View File

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

View File

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

View File

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

View File

@ -29,12 +29,14 @@ type userInterface struct {
scale float64 scale float64
deviceScale float64 deviceScale float64
sizeChanged bool sizeChanged bool
contextRestored chan struct{} contextRestored bool
windowFocus chan struct{} windowFocus bool
} }
var currentUI = &userInterface{ var currentUI = &userInterface{
sizeChanged: true, sizeChanged: true,
contextRestored: true,
windowFocus: true,
} }
func CurrentUI() UserInterface { func CurrentUI() UserInterface {
@ -46,16 +48,6 @@ func shown() bool {
return !js.Global.Get("document").Get("hidden").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) { func (u *userInterface) SetScreenSize(width, height int) (bool, error) {
return u.setScreenSize(width, height, u.scale), nil return u.setScreenSize(width, height, u.scale), nil
} }
@ -74,11 +66,11 @@ func (u *userInterface) ActualScreenScale() float64 {
} }
func (u *userInterface) Update() (interface{}, error) { func (u *userInterface) Update() (interface{}, error) {
if u.windowFocus != nil { if !u.windowFocus {
<-u.windowFocus return NopEvent{}, nil
} }
if u.contextRestored != nil { if !u.contextRestored {
<-u.contextRestored return NopEvent{}, nil
} }
currentInput.updateGamepads() currentInput.updateGamepads()
if u.sizeChanged { if u.sizeChanged {
@ -102,12 +94,19 @@ func (u *userInterface) Terminate() error {
return nil return nil
} }
func (u *userInterface) SwapBuffers() error { func (u *userInterface) AnimationFrameLoop(f func() error) error {
vsync() ch := make(chan error)
for !shown() { var ff func()
vsync() 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 { func (u *userInterface) FinishRendering() error {
@ -151,14 +150,10 @@ func initialize() error {
<-ch <-ch
} }
window.Call("addEventListener", "focus", func() { window.Call("addEventListener", "focus", func() {
if currentUI.windowFocus == nil { currentUI.windowFocus = true
return
}
close(currentUI.windowFocus)
currentUI.windowFocus = nil
}) })
window.Call("addEventListener", "blur", func() { window.Call("addEventListener", "blur", func() {
currentUI.windowFocus = make(chan struct{}) currentUI.windowFocus = false
}) })
canvas = doc.Call("createElement", "canvas") canvas = doc.Call("createElement", "canvas")
@ -244,11 +239,11 @@ func initialize() error {
canvas.Call("addEventListener", "webglcontextlost", func(e *js.Object) { canvas.Call("addEventListener", "webglcontextlost", func(e *js.Object) {
e.Call("preventDefault") e.Call("preventDefault")
currentUI.contextRestored = make(chan struct{}) currentUI.contextRestored = false
}) })
canvas.Call("addEventListener", "webglcontextrestored", func(e *js.Object) { canvas.Call("addEventListener", "webglcontextrestored", func(e *js.Object) {
close(currentUI.contextRestored) // TODO: Call preventDefault?
currentUI.contextRestored = nil currentUI.contextRestored = true
}) })
return nil return nil
} }

View File

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