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
This commit is contained in:
Hajime Hoshi 2022-12-20 09:52:13 +09:00
parent d80f1112ed
commit 59295cc85f
7 changed files with 37 additions and 27 deletions

View File

@ -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.

View File

@ -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()
}

View File

@ -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

View File

@ -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) {

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {