I have an inputHandler that responds only to buttonDown events.
In the buttonDown function, I check buttonIsPressed
to see if a chord with another button is happening, and take an alternate action if so.
Here's what SHOULD happen when you press both (A) and (B) together:
First, either (A) or (B) buttonDown triggers (whichever happened to be pressed first in the chord). Its "solo" action executes.
Then, the other buttonDown triggers, but since it detects the other "buttonIsPressed
", it performs the chord action instead of its "solo" action.
The bug happens when the chord action entails a change of inputHandler (like switching to a different game mode).
It also seems to happen only when the two buttons were pressed in the same frame. (Which is very easy even at 30fps, if you hit them near-simultaneously.)
In that situation, the first button's solo buttonDown is HELD OVER and executed later, on the wrong inputHandler.
The first solo action fails to trigger. (So be it: that frame has passed. It lost its chance.) Instead, that buttonDown is sent to the NEW InputHandler, causing unwanted behavior there.
This feels wrong: the inputHandler change can only be triggered by a chord event, so the button HAD to be down before the inputHandler change. No button is pressed down AFTER that inputHandler change, so no buttonDown should be received.
Proposed better outcome: any stray buttonDown events from before the handler change should be discarded. Otherwise, chords can be unpredictable.
I already have my own workaround for discarding "held over" buttonDown events. (I wait for a button UP before allowing any further button actions.) But in a faster-paced game than mine, that could have its own side effects.
Simple example project:
InputHandler Bug Test.zip (12.5 KB)
Here's the main.lua code from that:
local pd <const> = playdate
pd.display.setRefreshRate(10)
print("Modes 'Primary' and 'Secondary' have their own inputHandlers.")
print("(A) and (B) have unique buttonDown actions for each mode.")
print("Press BOTH buttons together to swap between the two modes.")
print("(No action is ever assigned to buttonUp.)\n")
print("TEST #1 - ALWAYS SUCCEEDS: \n")
print("Press either one, WAIT briefly holding it, then add the other.")
print(" DESIRED RESULT:\nThe first-pressed button's action will trigger, then\nthe mode will swap as the second button is added.\n")
print("TEST #2 - OFTEN FAILS (ESPECIALLY WITH LOW FPS): \n")
print("Press both buttons NEAR-SIMULTANEOUSLY.")
print(" SAME DESIRED RESULT: \nThe first-pressed button's action should trigger, then\nthe mode should immediately swap as the second button\nis added.")
print(" BUT OFTEN, INSTEAD: \nThe first button press, which occured in the intial mode\nand is required to trigger the swap, is IGNORED and instead\nis processed AFTER the mode swap. It is then treated as the\nsecond-pressed, which in turn triggers a second double-button\nmode swap. Thus, not only does the initial buttonDown action\nfail to happen, but the mode gets swapped twice, resulting in\nno mode change for the user.")
local modePrimaryInputHandlers = {
BButtonDown = function()
if pd.buttonIsPressed(pd.kButtonA) then --Both buttons together
print("(A)+(B): swapping to Secondary mode...")
startModeSecondary()
else
print("(B) pressed --> execute Primary mode B action")
end
end,
AButtonDown = function()
if pd.buttonIsPressed(pd.kButtonB) then --Both buttons together
print("(B)+(A): swapping to Secondary mode...")
startModeSecondary()
else
print("(A) pressed --> execute Primary mode A action")
end
end
}
local modeSecondaryInputHandlers = {
BButtonDown = function()
if pd.buttonIsPressed(pd.kButtonA) then --Both buttons together
print("(A)+(B): swapping to Primary mode...")
startModePrimary()
else
print("(B) pressed --> execute Secondary mode B action")
end
end,
AButtonDown = function()
if pd.buttonIsPressed(pd.kButtonB) then --Both buttons together
print("(B)+(A): swapping to Primary mode...")
startModePrimary()
else
print("(A) pressed --> execute Secondary mode A action")
end
end
}
function startModePrimary()
print("\n Now in mode PRIMARY")
pd.inputHandlers.push(modePrimaryInputHandlers, true)
end
function startModeSecondary()
print("\n Now in mode SECONDARY")
pd.inputHandlers.push(modeSecondaryInputHandlers, true)
end
function pd.update()
end
startModePrimary() --Begin in Primary mode