[wpilib] SendableChooser: Add onChange listener (#5458)

This commit is contained in:
Gold856
2023-07-18 19:33:45 -04:00
committed by GitHub
parent 6f7cdd460e
commit 0b91ca6d5a
6 changed files with 98 additions and 6 deletions

View File

@@ -31,7 +31,7 @@ template <class T>
requires std::copy_constructible<T> && std::default_initializable<T>
class SendableChooser : public SendableChooserBase {
wpi::StringMap<T> m_choices;
std::function<void(T)> m_listener;
template <class U>
static U _unwrap_smart_ptr(const U& value);
@@ -82,6 +82,14 @@ class SendableChooser : public SendableChooserBase {
*/
auto GetSelected() -> decltype(_unwrap_smart_ptr(m_choices[""]));
/**
* Bind a listener that's called when the selected value changes.
* Only one listener can be bound. Calling this function will replace the
* previous listener.
* @param listener The function to call that accepts the new value
*/
void OnChange(std::function<void(T)>);
void InitSendable(nt::NTSendableBuilder& builder) override;
};

View File

@@ -48,6 +48,13 @@ auto SendableChooser<T>::GetSelected()
}
}
template <class T>
requires std::copy_constructible<T> && std::default_initializable<T>
void SendableChooser<T>::OnChange(std::function<void(T)> listener) {
std::scoped_lock lock(m_mutex);
m_listener = listener;
}
template <class T>
requires std::copy_constructible<T> && std::default_initializable<T>
void SendableChooser<T>::InitSendable(nt::NTSendableBuilder& builder) {
@@ -95,11 +102,23 @@ void SendableChooser<T>::InitSendable(nt::NTSendableBuilder& builder) {
nullptr);
builder.AddStringProperty(kSelected, nullptr,
[=, this](std::string_view val) {
std::scoped_lock lock(m_mutex);
m_haveSelected = true;
m_selected = val;
for (auto& pub : m_activePubs) {
pub.Set(val);
T choice{};
std::function<void(T)> listener;
{
std::scoped_lock lock(m_mutex);
m_haveSelected = true;
m_selected = val;
for (auto& pub : m_activePubs) {
pub.Set(val);
}
if (m_previousVal != val && m_listener) {
choice = m_choices[val];
listener = m_listener;
}
m_previousVal = val;
}
if (listener) {
listener(choice);
}
});
}

View File

@@ -45,6 +45,7 @@ class SendableChooserBase : public nt::NTSendable,
wpi::SmallVector<nt::StringPublisher, 2> m_activePubs;
wpi::mutex m_mutex;
int m_instance;
std::string m_previousVal;
static std::atomic_int s_instances;
};

View File

@@ -56,5 +56,22 @@ TEST(SendableChooserTest,
EXPECT_EQ(0, chooser.GetSelected());
}
TEST(SendableChooserTest, ChangeListener) {
frc::SendableChooser<int> chooser;
for (int i = 1; i <= 3; i++) {
chooser.AddOption(std::to_string(i), i);
}
int currentVal = 0;
chooser.OnChange([&](int val) { currentVal = val; });
frc::SmartDashboard::PutData("chooser", &chooser);
frc::SmartDashboard::UpdateValues();
frc::SmartDashboard::PutString("chooser/selected", "3");
frc::SmartDashboard::UpdateValues();
EXPECT_EQ(3, currentVal);
}
INSTANTIATE_TEST_SUITE_P(SendableChooserTests, SendableChooserTest,
::testing::Values(0, 1, 2, 3));

View File

@@ -19,6 +19,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
/**
* The {@link SendableChooser} class is a useful tool for presenting a selection of options to the
@@ -48,6 +49,8 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
private String m_defaultChoice = "";
private final int m_instance;
private String m_previousVal;
private Consumer<V> m_listener;
private static final AtomicInteger s_instances = new AtomicInteger();
/** Instantiates a {@link SendableChooser}. */
@@ -114,6 +117,19 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
}
}
/**
* Bind a listener that's called when the selected value changes. Only one listener can be bound.
* Calling this function will replace the previous listener.
*
* @param listener The function to call that accepts the new value
*/
public void onChange(Consumer<V> listener) {
requireNonNullParam(listener, "listener", "onChange");
m_mutex.lock();
m_listener = listener;
m_mutex.unlock();
}
private String m_selected;
private final List<StringPublisher> m_activePubs = new ArrayList<>();
private final ReentrantLock m_mutex = new ReentrantLock();
@@ -151,15 +167,28 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
SELECTED,
null,
val -> {
V choice;
Consumer<V> listener;
m_mutex.lock();
try {
m_selected = val;
if (!m_selected.equals(m_previousVal) && m_listener != null) {
choice = m_map.get(val);
listener = m_listener;
} else {
choice = null;
listener = null;
}
m_previousVal = val;
for (StringPublisher pub : m_activePubs) {
pub.set(val);
}
} finally {
m_mutex.unlock();
}
if (listener != null) {
listener.accept(choice);
}
});
}
}

View File

@@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -64,6 +65,23 @@ class SendableChooserTest {
}
}
@Test
void testChangeListener() {
try (var chooser = new SendableChooser<Integer>()) {
for (int i = 1; i <= 3; i++) {
chooser.addOption(String.valueOf(i), i);
}
AtomicInteger currentVal = new AtomicInteger();
chooser.onChange(val -> currentVal.set(val));
SmartDashboard.putData("chooser", chooser);
SmartDashboard.updateValues();
SmartDashboard.putString("chooser/selected", "3");
SmartDashboard.updateValues();
assertEquals(3, currentVal.get());
}
}
@AfterEach
void tearDown() {
m_inst.close();