Add repeat delay to prevent double inputs and add more debug info
This commit is contained in:
parent
d733a0b069
commit
bcdcb607a6
5 changed files with 51 additions and 13 deletions
|
@ -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,
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue