From 1e0bdf844d8d5aa8e171dca76d8a089793b93b13 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 1 Sep 2016 02:38:47 +0900 Subject: [PATCH] loop: Bug fix: vsync should not use channels on browsers (#259) --- internal/loop/run.go | 29 +++++++++++++++++--- internal/ui/event.go | 3 +++ internal/ui/ui.go | 2 +- internal/ui/ui_glfw.go | 24 ++++++++++------- internal/ui/ui_js.go | 57 ++++++++++++++++++---------------------- internal/ui/ui_mobile.go | 8 ++++-- 6 files changed, 76 insertions(+), 47 deletions(-) diff --git a/internal/loop/run.go b/internal/loop/run.go index 4156d23cc..dafa3e8d5 100644 --- a/internal/loop/run.go +++ b/internal/loop/run.go @@ -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 diff --git a/internal/ui/event.go b/internal/ui/event.go index bfe23f7b6..fecaf97d8 100644 --- a/internal/ui/event.go +++ b/internal/ui/event.go @@ -14,6 +14,9 @@ package ui +type NopEvent struct { +} + type CloseEvent struct { } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index f5349a10b..6de6a5150 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -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) diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index f37b9d1e5..82d31fb89 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -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 { diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index a1d0dd903..3d4f1eb22 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -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 } diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index ba37e3d80..8f869f052 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -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) {