Add repeat delay to prevent double inputs and add more debug info

This commit is contained in:
Mikko Ahlroth 2021-11-05 19:56:50 +02:00
parent d733a0b069
commit bcdcb607a6
5 changed files with 51 additions and 13 deletions

View file

@ -44,6 +44,11 @@ config :ex_speed_game,
# spurious inputs. In milliseconds.
debounce_delay: 20,
# Delay between repeated presses of the same button. Any repeated presses during this duration
# after the initial press will be discarded, to prevent double hits from unreliable buttons.
# In milliseconds.
repeat_delay: 100,
# Delay at start of game between ticks, in milliseconds.
delay_start: 570,

View file

@ -16,7 +16,7 @@ defmodule ExSpeedGame.Application do
def start(_type, _args) do
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, max_restarts: 1_000_000, name: ExSpeedGame.Supervisor]
opts = [strategy: :one_for_all, max_restarts: 1_000_000, name: ExSpeedGame.Supervisor]
children =
[
@ -32,6 +32,7 @@ defmodule ExSpeedGame.Application do
def children(_target) do
pins = Application.get_env(:ex_speed_game, :button_pins)
debounce_delay = Application.get_env(:ex_speed_game, :debounce_delay)
repeat_delay = Application.get_env(:ex_speed_game, :repeat_delay)
led_channel = Application.get_env(:ex_speed_game, :led_channel)
led_brightness = Application.get_env(:ex_speed_game, :led_brightness)
@ -40,7 +41,12 @@ defmodule ExSpeedGame.Application do
{Registry,
[name: ExSpeedGame.PubSub, keys: :duplicate, partitions: System.schedulers_online()]},
{ButtonInput,
%ButtonInput.Options{pins: pins, debounce_delay: debounce_delay, name: ButtonInput}},
%ButtonInput.Options{
pins: pins,
debounce_delay: debounce_delay,
repeat_delay: repeat_delay,
name: ButtonInput
}},
{Lights, %Lights.Options{channel: led_channel, brightness: led_brightness, name: Lights}},
{Menu, %Menu.Options{name: Menu}},
{DynamicSupervisor, strategy: :one_for_one, name: GameSupervisor},

View file

@ -1,13 +1,15 @@
defmodule ExSpeedGame.Game.ButtonInput do
use GenServer
import ExSpeedGame.Utils.TypedStruct
require Logger
alias ExSpeedGame.Game.Types
defmodule Options do
deftypedstruct(%{
pins: Types.pins(),
debounce_delay: integer(),
debounce_delay: pos_integer(),
repeat_delay: pos_integer(),
name: GenServer.name()
})
end
@ -15,11 +17,13 @@ defmodule ExSpeedGame.Game.ButtonInput do
defmodule State do
deftypedstruct(%{
pins: Types.pins(),
debounce_delay: integer(),
debounce_delay: pos_integer(),
repeat_delay: pos_integer(),
listener: {pid() | nil, nil},
pin_refs: {[reference()], []},
pin_timers: {%{optional(Types.pin()) => reference()}, %{}},
active: {boolean(), false}
active: {boolean(), false},
last_input: {%{optional(Types.pin()) => integer()}, %{}}
})
end
@ -33,16 +37,23 @@ defmodule ExSpeedGame.Game.ButtonInput do
def start_link(%Options{} = opts) do
GenServer.start_link(
__MODULE__,
%{pins: opts.pins, debounce_delay: opts.debounce_delay},
%{pins: opts.pins, debounce_delay: opts.debounce_delay, repeat_delay: opts.repeat_delay},
name: opts.name
)
end
@impl true
@spec init(map()) :: {:ok, State.t()}
def init(%{pins: pins, debounce_delay: debounce_delay}) do
def init(%{pins: pins, debounce_delay: debounce_delay, repeat_delay: repeat_delay}) do
pin_refs = for pin <- Tuple.to_list(pins), do: init_pin(pin)
{:ok, %State{pins: pins, debounce_delay: debounce_delay, pin_refs: pin_refs}}
{:ok,
%State{
pins: pins,
debounce_delay: debounce_delay,
repeat_delay: repeat_delay,
pin_refs: pin_refs
}}
end
@impl true
@ -93,10 +104,23 @@ defmodule ExSpeedGame.Game.ButtonInput do
end
def handle_info({:debounce, pin}, %State{active: true} = state) do
send(state.listener, {:input, pin})
last_input = Map.get(state.last_input, pin)
now = System.monotonic_time(:millisecond)
Logger.debug("#{inspect({now, last_input})} input pin #{inspect(pin)}")
updated_last_input =
if is_nil(last_input) or last_input + state.repeat_delay < now do
Logger.debug("Input accepted")
send(state.listener, {:input, pin})
Map.put(state.last_input, pin, now)
else
Logger.debug("Input suppressed")
state.last_input
end
pin_timers = Map.delete(state.pin_timers, pin)
{:noreply, %State{state | pin_timers: pin_timers}}
{:noreply, %State{state | pin_timers: pin_timers, last_input: updated_last_input}}
end
# Discard all other messages

View file

@ -53,12 +53,10 @@ defmodule ExSpeedGame.Game.Lights do
|> Enum.each(fn
{true, i} ->
from = index2coordinates(i)
Logger.debug("Setting #{i} to #{inspect(index2color(i))} #{inspect(from)}")
Blinkchain.fill(from, @leds_per_button, 1, index2color(i))
{false, i} ->
from = index2coordinates(i)
Logger.debug("Setting #{i} to black #{inspect(from)}")
Blinkchain.fill(from, @leds_per_button, 1, @black)
end)

View file

@ -61,6 +61,7 @@ defmodule ExSpeedGame.Game.Modes.Speed do
choice = Randomiser.get(state.previous)
Lights.set_index(Lights, choice)
new_queue = :queue.in(choice, state.queue)
Logger.debug("Added new choice #{inspect(choice)} to end of queue")
next_delay = schedule_tick(state.delay)
@ -105,7 +106,11 @@ defmodule ExSpeedGame.Game.Modes.Speed do
PubSub.publish(new_state)
{:noreply, new_state}
_ ->
val ->
Logger.debug(
"Game ended, got input #{inspect(choice)} but expected #{inspect(val)}. Queue: #{inspect(:queue.to_list(state.queue))}"
)
end_game(state)
end
end