diff --git a/internal/ui/input_js.go b/internal/ui/input_js.go index 888d9a08b..2a5ba66c3 100644 --- a/internal/ui/input_js.go +++ b/internal/ui/input_js.go @@ -237,7 +237,8 @@ func (i *Input) updateFromEvent(e js.Value) error { i.updateTouchesFromEvent(e) } - return i.ui.forceUpdateOnMinimumFPSMode() + i.ui.forceUpdateOnMinimumFPSMode() + return nil } func (i *Input) setMouseCursorFromEvent(e js.Value) { diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 4f293092c..143c58549 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -15,6 +15,7 @@ package ui import ( + "sync" "syscall/js" "time" @@ -86,6 +87,8 @@ type userInterfaceImpl struct { context *context input Input + + m sync.Mutex } func init() { @@ -275,6 +278,10 @@ func (u *userInterfaceImpl) update() error { } func (u *userInterfaceImpl) updateImpl(force bool) error { + // Guard updateImpl as this function cannot be invoked until this finishes (#2339). + u.m.Lock() + defer u.m.Unlock() + // context can be nil when an event is fired but the loop doesn't start yet (#1928). if u.context == nil { return nil @@ -488,10 +495,15 @@ func init() { func setWindowEventHandlers(v js.Value) { v.Call("addEventListener", "resize", js.FuncOf(func(this js.Value, args []js.Value) interface{} { theUI.updateScreenSize() - if err := theUI.updateImpl(true); err != nil && theUI.err != nil { - theUI.err = err - return nil - } + + // updateImpl can block. Use goroutine. + // See https://pkg.go.dev/syscall/js#FuncOf. + go func() { + if err := theUI.updateImpl(true); err != nil && theUI.err != nil { + theUI.err = err + return + } + }() return nil })) } @@ -609,11 +621,18 @@ func setCanvasEventHandlers(v js.Value) { })) } -func (u *userInterfaceImpl) forceUpdateOnMinimumFPSMode() error { +func (u *userInterfaceImpl) forceUpdateOnMinimumFPSMode() { if u.fpsMode != FPSModeVsyncOffMinimum { - return nil + return } - return u.updateImpl(true) + + // updateImpl can block. Use goroutine. + // See https://pkg.go.dev/syscall/js#FuncOf. + go func() { + if err := u.updateImpl(true); err != nil && u.err != nil { + u.err = err + } + }() } func (u *userInterfaceImpl) Run(game Game) error {