From 59295cc85fd6c6aca554480c5e060dd89d9d6f68 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 20 Dec 2022 09:52:13 +0900 Subject: [PATCH] internal/ui: bug fix: input state should be reset for each tick, not frame Before this change, input states were reset for each frame. When FPS is bigger than TPS, the input state was reset more often than expected and then some inputs were missing. This change fixes the issue by resetting input states not for each frame but for each tick. This change also updates some comments of the input API. Updates #2496 Closes #2501 --- input.go | 8 ++++---- internal/ui/context.go | 10 +++++++--- internal/ui/input.go | 2 +- internal/ui/ui_glfw.go | 8 ++------ internal/ui/ui_js.go | 9 ++++++--- internal/ui/ui_mobile.go | 16 ++++++++++------ internal/ui/ui_nintendosdk.go | 11 +++++++---- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/input.go b/input.go index 0d1b9125c..d35b6033c 100644 --- a/input.go +++ b/input.go @@ -49,7 +49,7 @@ func InputChars() []rune { // IsKeyPressed returns a boolean indicating whether key is pressed. // -// If you want to know whether the key started being pressed in the current frame, +// If you want to know whether the key started being pressed in the current tick, // use inpututil.IsKeyJustPressed // // Note that a Key represents a pysical key of US keyboard layout. @@ -91,7 +91,7 @@ func Wheel() (xoff, yoff float64) { // IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed. // -// If you want to know whether the mouseButton started being pressed in the current frame, +// If you want to know whether the mouseButton started being pressed in the current tick, // use inpututil.IsMouseButtonJustPressed // // IsMouseButtonPressed is concurrent-safe. @@ -206,7 +206,7 @@ func GamepadButtonNum(id GamepadID) int { // IsGamepadButtonPressed reports whether the given button of the gamepad (id) is pressed or not. // -// If you want to know whether the given button of gamepad (id) started being pressed in the current frame, +// If you want to know whether the given button of gamepad (id) started being pressed in the current tick, // use inpututil.IsGamepadButtonJustPressed // // IsGamepadButtonPressed is concurrent-safe. @@ -349,7 +349,7 @@ type TouchID = ui.TouchID // AppendTouchIDs appends the current touch states to touches, and returns the extended buffer. // Giving a slice that already has enough capacity works efficiently. // -// If you want to know whether a touch started being pressed in the current frame, +// If you want to know whether a touch started being pressed in the current tick, // use inpututil.JustPressedTouchIDs // // AppendTouchIDs doesn't append anything when there are no touches. diff --git a/internal/ui/context.go b/internal/ui/context.go index 73cac4c3b..2c5d32e3e 100644 --- a/internal/ui/context.go +++ b/internal/ui/context.go @@ -89,9 +89,7 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update return err } - // Read the input state and use it for one frame to give a consistent result for one frame (#2496). - var inputState InputState - ui.beginFrame(&inputState) + ui.beginFrame() defer ui.endFrame() // The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589) @@ -128,13 +126,19 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update if err := hooks.RunBeforeUpdateHooks(); err != nil { return err } + + // Read the input state and use it for one tick to give a consistent result for one tick (#2496, #2501). + var inputState InputState + ui.readInputState(&inputState) if err := c.game.Update(inputState); err != nil { return err } + // Catch the error that happened at (*Image).At. if err := theGlobalState.error(); err != nil { return err } + ui.resetForTick() } diff --git a/internal/ui/input.go b/internal/ui/input.go index ebe5fd9fd..340c1d2f6 100644 --- a/internal/ui/input.go +++ b/internal/ui/input.go @@ -50,7 +50,7 @@ type InputState struct { RunesCount int } -func (i *InputState) resetForFrame() { +func (i *InputState) resetForTick() { i.WheelX = 0 i.WheelY = 0 i.RunesCount = 0 diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 60287297c..f141a028c 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -696,13 +696,8 @@ func (u *userInterfaceImpl) createWindow(width, height int) error { return nil } -func (u *userInterfaceImpl) beginFrame(inputState *InputState) { +func (u *userInterfaceImpl) beginFrame() { atomic.StoreUint32(&u.inFrame, 1) - - u.m.Lock() - defer u.m.Unlock() - *inputState = u.inputState - u.inputState.resetForFrame() } func (u *userInterfaceImpl) endFrame() { @@ -1416,6 +1411,7 @@ func (u *userInterfaceImpl) resetForTick() { u.m.Lock() defer u.m.Unlock() u.windowBeingClosed = false + u.inputState.resetForTick() } func (u *userInterfaceImpl) readInputState(inputState *InputState) { diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 038e1d929..26e8ae763 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -680,16 +680,19 @@ func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) { } +func (u *userInterfaceImpl) readInputState(inputState *InputState) { + *inputState = u.inputState +} + func (u *userInterfaceImpl) resetForTick() { + u.inputState.resetForTick() } func (u *userInterfaceImpl) Window() Window { return &nullWindow{} } -func (u *userInterfaceImpl) beginFrame(inputState *InputState) { - *inputState = u.inputState - u.inputState.resetForFrame() +func (u *userInterfaceImpl) beginFrame() { } func (u *userInterfaceImpl) endFrame() { diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index bc5b3fc26..a1da69d17 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -418,7 +418,16 @@ func (u *userInterfaceImpl) DeviceScaleFactor() float64 { return deviceScale() } +func (u *userInterfaceImpl) readInputState(inputState *InputState) { + u.m.Lock() + defer u.m.Unlock() + *inputState = u.inputState +} + func (u *userInterfaceImpl) resetForTick() { + u.m.Lock() + defer u.m.Unlock() + u.inputState.resetForTick() } func (u *userInterfaceImpl) Window() Window { @@ -448,12 +457,7 @@ func (u *userInterfaceImpl) ScheduleFrame() { } } -func (u *userInterfaceImpl) beginFrame(inputState *InputState) { - u.m.Lock() - defer u.m.Unlock() - - *inputState = u.inputState - u.inputState.resetForFrame() +func (u *userInterfaceImpl) beginFrame() { } func (u *userInterfaceImpl) endFrame() { diff --git a/internal/ui/ui_nintendosdk.go b/internal/ui/ui_nintendosdk.go index dda1a2df3..d52462f4a 100644 --- a/internal/ui/ui_nintendosdk.go +++ b/internal/ui/ui_nintendosdk.go @@ -92,7 +92,12 @@ func (*userInterfaceImpl) ScreenSizeInFullscreen() (int, int) { return 0, 0 } -func (*userInterfaceImpl) resetForTick() { +func (u *userInterfaceImpl) readInputState(inputState *InputState) { + *inputState = u.inputState +} + +func (u *userInterfaceImpl) resetForTick() { + u.inputState.resetForTick() } func (*userInterfaceImpl) CursorMode() CursorMode { @@ -133,9 +138,7 @@ func (*userInterfaceImpl) Window() Window { return &nullWindow{} } -func (u *userInterfaceImpl) beginFrame(inputState *InputState) { - *inputState = u.inputState - u.inputState.resetForFrame() +func (u *userInterfaceImpl) beginFrame() { } func (u *userInterfaceImpl) endFrame() {