From cbcf1d5e5101aedeed860e07e8ffffee42342d24 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sat, 4 Oct 2025 22:18:37 +0200 Subject: [PATCH] fix: Duration() returns nonzero value after controller was disconnected pipad.Duration() should return 0 for all buttons when controller was disconnected. --- pipad/pipad.go | 18 ++++++---- pipad/pipad_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 pipad/pipad_test.go diff --git a/pipad/pipad.go b/pipad/pipad.go index de4bec2..9c44395 100644 --- a/pipad/pipad.go +++ b/pipad/pipad.go @@ -57,9 +57,10 @@ package pipad import ( - "github.com/elgopher/pi/internal/input" "log" + "github.com/elgopher/pi/internal/input" + "github.com/elgopher/pi" "github.com/elgopher/pi/pievent" ) @@ -82,9 +83,17 @@ const ( Y Button = "Y" ) -// Duration returns button press duration for any controller +// Duration returns button press duration for any controller. +// If multiple controllers are pressed simultaneously, it returns +// the longest duration among them. func Duration(b Button) int { - return buttonAnyState.Duration(b) + duration := 0 + + for _, state := range buttonState { + duration = max(duration, state.Duration(b)) + } + + return duration } // PlayerCount returns the number of connected controllers @@ -102,7 +111,6 @@ func PlayerDuration(b Button, player int) int { } var buttonState = map[int]*input.State[Button]{} -var buttonAnyState input.State[Button] func init() { ButtonTarget().SubscribeAll(onButton) @@ -117,10 +125,8 @@ func onButton(event EventButton, _ pievent.Handler) { switch event.Type { case EventDown: buttonState[event.Player].SetDownFrame(event.Button, pi.Frame) - buttonAnyState.SetDownFrame(event.Button, pi.Frame) case EventUp: buttonState[event.Player].SetUpFrame(event.Button, pi.Frame) - buttonAnyState.SetUpFrame(event.Button, pi.Frame) } } diff --git a/pipad/pipad_test.go b/pipad/pipad_test.go new file mode 100644 index 0000000..135164f --- /dev/null +++ b/pipad/pipad_test.go @@ -0,0 +1,81 @@ +// Copyright 2025 Jacek Olszak +// This code is licensed under MIT license (see LICENSE for details) + +package pipad_test + +import ( + "testing" + + "github.com/elgopher/pi" + "github.com/elgopher/pi/pipad" + "github.com/stretchr/testify/assert" +) + +var ( + player0connected = pipad.EventConnection{Type: pipad.EventConnect, Player: 0} + player1connected = pipad.EventConnection{Type: pipad.EventConnect, Player: 1} + player0disconnected = pipad.EventConnection{Type: pipad.EventDisconnect, Player: 0} + player1disconnected = pipad.EventConnection{Type: pipad.EventDisconnect, Player: 1} +) + +func TestPlayerCount(t *testing.T) { + assert.Equal(t, 0, pipad.PlayerCount()) + pipad.ConnectionTarget().Publish(player0connected) + pipad.ConnectionTarget().Publish(player1connected) + assert.Equal(t, 2, pipad.PlayerCount()) + pipad.ConnectionTarget().Publish(player0disconnected) + assert.Equal(t, 1, pipad.PlayerCount()) + pipad.ConnectionTarget().Publish(player1disconnected) +} + +func TestDuration(t *testing.T) { + { + pipad.ConnectionTarget().Publish(player0connected) + pipad.ButtonTarget().Publish( + pipad.EventButton{Type: pipad.EventDown, Button: pipad.A, Player: 0}, + ) + + t.Run("should return duration when button was pressed", func(t *testing.T) { + duration := pipad.Duration(pipad.A) + assert.Equal(t, 1, duration) + + playerDuration := pipad.PlayerDuration(pipad.A, 0) + assert.Equal(t, 1, playerDuration) + }) + + pi.Frame++ + + t.Run("should take into account how many frames passed", func(t *testing.T) { + assert.Equal(t, 2, pipad.Duration(pipad.A)) + assert.Equal(t, 2, pipad.PlayerDuration(pipad.A, 0)) + }) + + pipad.ConnectionTarget().Publish(player0disconnected) + } + + t.Run("should return 0 after controller was disconnected", func(t *testing.T) { + assert.Equal(t, 0, pipad.Duration(pipad.A)) + assert.Equal(t, 0, pipad.PlayerDuration(pipad.A, 0)) + }) + + t.Run("should return the longest duration when two controllers are pressed simultaneously", func(t *testing.T) { + pipad.ConnectionTarget().Publish(player0connected) + defer pipad.ConnectionTarget().Publish(player0disconnected) + pipad.ConnectionTarget().Publish(player1connected) + defer pipad.ConnectionTarget().Publish(player1disconnected) + + pipad.ButtonTarget().Publish( + pipad.EventButton{Type: pipad.EventDown, Button: pipad.A, Player: 0}, + ) + pi.Frame++ + pipad.ButtonTarget().Publish( + pipad.EventButton{Type: pipad.EventUp, Button: pipad.A, Player: 1}, + ) + assert.Equal(t, 2, pipad.Duration(pipad.A)) + assert.Equal(t, 2, pipad.PlayerDuration(pipad.A, 0)) + }) + + t.Run("should return 0 when player was never connected", func(t *testing.T) { + assert.Equal(t, 0, pipad.PlayerDuration(pipad.A, 2)) + }) +}