diff --git a/wpilibc/src/main/native/cpp/smartdashboard/ListenerExecutor.cpp b/wpilibc/src/main/native/cpp/smartdashboard/ListenerExecutor.cpp new file mode 100644 index 0000000000..75b373a335 --- /dev/null +++ b/wpilibc/src/main/native/cpp/smartdashboard/ListenerExecutor.cpp @@ -0,0 +1,28 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/smartdashboard/ListenerExecutor.h" + +using namespace frc::detail; + +void ListenerExecutor::Execute(std::function task) { + std::scoped_lock lock(m_lock); + m_tasks.emplace_back(task); +} + +void ListenerExecutor::RunListenerTasks() { + // Locally copy tasks from internal list; minimizes blocking time + { + std::scoped_lock lock(m_lock); + std::swap(m_tasks, m_runningTasks); + } + + for (auto&& task : m_runningTasks) { + task(); + } + m_runningTasks.clear(); +} diff --git a/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp b/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp index bce03b5de9..ac4a107fc3 100644 --- a/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp +++ b/wpilibc/src/main/native/cpp/smartdashboard/SendableBuilderImpl.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2017-2019 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -10,6 +10,8 @@ #include #include +#include "frc/smartdashboard/SmartDashboard.h" + using namespace frc; void SendableBuilderImpl::SetTable(std::shared_ptr table) { @@ -88,7 +90,8 @@ void SendableBuilderImpl::AddBooleanProperty(const wpi::Twine& key, return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsBoolean()) return; - setter(event.value->GetBoolean()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetBoolean()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -111,7 +114,8 @@ void SendableBuilderImpl::AddDoubleProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsDouble()) return; - setter(event.value->GetDouble()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetDouble()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -134,7 +138,8 @@ void SendableBuilderImpl::AddStringProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsString()) return; - setter(event.value->GetString()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetString()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -157,7 +162,8 @@ void SendableBuilderImpl::AddBooleanArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsBooleanArray()) return; - setter(event.value->GetBooleanArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetBooleanArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -180,7 +186,8 @@ void SendableBuilderImpl::AddDoubleArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsDoubleArray()) return; - setter(event.value->GetDoubleArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetDoubleArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -203,7 +210,8 @@ void SendableBuilderImpl::AddStringArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsStringArray()) return; - setter(event.value->GetStringArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetStringArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -226,7 +234,8 @@ void SendableBuilderImpl::AddRawProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsRaw()) return; - setter(event.value->GetRaw()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetRaw()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -247,7 +256,9 @@ void SendableBuilderImpl::AddValueProperty( m_properties.back().createListener = [=](nt::NetworkTableEntry entry) -> NT_EntryListener { return entry.AddListener( - [=](const nt::EntryNotification& event) { setter(event.value); }, + [=](const nt::EntryNotification& event) { + SmartDashboard::PostListenerTask([=] { setter(event.value); }); + }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; } @@ -271,7 +282,8 @@ void SendableBuilderImpl::AddSmallStringProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsString()) return; - setter(event.value->GetString()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetString()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -296,7 +308,8 @@ void SendableBuilderImpl::AddSmallBooleanArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsBooleanArray()) return; - setter(event.value->GetBooleanArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetBooleanArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -322,7 +335,8 @@ void SendableBuilderImpl::AddSmallDoubleArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsDoubleArray()) return; - setter(event.value->GetDoubleArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetDoubleArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -349,7 +363,8 @@ void SendableBuilderImpl::AddSmallStringArrayProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsStringArray()) return; - setter(event.value->GetStringArray()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetStringArray()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; @@ -374,7 +389,8 @@ void SendableBuilderImpl::AddSmallRawProperty( return entry.AddListener( [=](const nt::EntryNotification& event) { if (!event.value->IsRaw()) return; - setter(event.value->GetRaw()); + SmartDashboard::PostListenerTask( + [=] { setter(event.value->GetRaw()); }); }, NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE); }; diff --git a/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp b/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp index ac667a3fcd..9c89e49af3 100644 --- a/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp +++ b/wpilibc/src/main/native/cpp/smartdashboard/SmartDashboard.cpp @@ -254,10 +254,17 @@ std::shared_ptr SmartDashboard::GetValue(wpi::StringRef keyName) { return Singleton::GetInstance().table->GetEntry(keyName).GetValue(); } +detail::ListenerExecutor SmartDashboard::listenerExecutor; + +void SmartDashboard::PostListenerTask(std::function task) { + listenerExecutor.Execute(task); +} + void SmartDashboard::UpdateValues() { auto& inst = Singleton::GetInstance(); std::scoped_lock lock(inst.tablesToDataMutex); for (auto& i : inst.tablesToData) { i.getValue().builder.UpdateTable(); } + listenerExecutor.RunListenerTasks(); } diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/ListenerExecutor.h b/wpilibc/src/main/native/include/frc/smartdashboard/ListenerExecutor.h new file mode 100644 index 0000000000..95002785af --- /dev/null +++ b/wpilibc/src/main/native/include/frc/smartdashboard/ListenerExecutor.h @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include +#include + +#include + +namespace frc::detail { +/** + * An executor for running listener tasks posted by Sendable listeners + * synchronously from the main application thread. + * + * @see Sendable + */ +class ListenerExecutor { + public: + /** + * Posts a task to the executor to be run synchronously from the main thread. + * + * @param task The task to run synchronously from the main thread. + */ + void Execute(std::function task); + + /** + * Runs all posted tasks. Called periodically from main thread. + */ + void RunListenerTasks(); + + private: + std::vector> m_tasks; + std::vector> m_runningTasks; + wpi::mutex m_lock; +}; +} // namespace frc::detail diff --git a/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h b/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h index 903fb8b8b6..a7161da297 100644 --- a/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h +++ b/wpilibc/src/main/native/include/frc/smartdashboard/SmartDashboard.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2011-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2011-2019 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -14,6 +14,7 @@ #include #include "frc/ErrorBase.h" +#include "frc/smartdashboard/ListenerExecutor.h" #include "frc/smartdashboard/SendableBase.h" namespace frc { @@ -401,6 +402,15 @@ class SmartDashboard : public ErrorBase, public SendableBase { */ static std::shared_ptr GetValue(wpi::StringRef keyName); + /** + * Posts a task from a listener to the ListenerExecutor, so that it can be run + * synchronously from the main loop on the next call to {@link + * SmartDashboard#updateValues()}. + * + * @param task The task to run synchronously from the main thread. + */ + static void PostListenerTask(std::function task); + /** * Puts all sendable data to the dashboard. */ @@ -408,6 +418,8 @@ class SmartDashboard : public ErrorBase, public SendableBase { private: virtual ~SmartDashboard() = default; + + static detail::ListenerExecutor listenerExecutor; }; } // namespace frc diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/ListenerExecutor.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/ListenerExecutor.java new file mode 100644 index 0000000000..274c7a81d5 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/ListenerExecutor.java @@ -0,0 +1,50 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.wpilibj.smartdashboard; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + +/** + * An executor for running listener tasks posted by {@link edu.wpi.first.wpilibj.Sendable} listeners + * synchronously from the main application thread. + */ +class ListenerExecutor implements Executor { + private final Collection m_tasks = new ArrayList<>(); + private final Object m_lock = new Object(); + + /** + * Posts a task to the executor to be run synchronously from the main thread. + * + * @param task The task to run synchronously from the main thread. + */ + @Override + public void execute(Runnable task) { + synchronized (m_lock) { + m_tasks.add(task); + } + } + + /** + * Runs all posted tasks. Called periodically from main thread. + */ + public void runListenerTasks() { + // Locally copy tasks from internal list; minimizes blocking time + Collection tasks = new ArrayList<>(); + synchronized (m_lock) { + tasks.addAll(m_tasks); + m_tasks.clear(); + } + + // Run all tasks + for (Runnable task : tasks) { + task.run(); + } + } +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SendableBuilderImpl.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SendableBuilderImpl.java index c9531bf182..01ddf27a6e 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SendableBuilderImpl.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SendableBuilderImpl.java @@ -222,7 +222,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isBoolean()) { - setter.accept(event.value.getBoolean()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getBoolean())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -245,7 +245,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isDouble()) { - setter.accept(event.value.getDouble()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getDouble())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -268,7 +268,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isString()) { - setter.accept(event.value.getString()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getString())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -292,7 +292,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isBooleanArray()) { - setter.accept(event.value.getBooleanArray()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getBooleanArray())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -316,7 +316,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isDoubleArray()) { - setter.accept(event.value.getDoubleArray()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getDoubleArray())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -340,7 +340,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isStringArray()) { - setter.accept(event.value.getStringArray()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getStringArray())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -363,7 +363,7 @@ public class SendableBuilderImpl implements SendableBuilder { if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { if (event.value.isRaw()) { - setter.accept(event.value.getRaw()); + SmartDashboard.postListenerTask(() -> setter.accept(event.value.getRaw())); } }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } @@ -386,7 +386,7 @@ public class SendableBuilderImpl implements SendableBuilder { } if (setter != null) { property.m_createListener = entry -> entry.addListener(event -> { - setter.accept(event.value); + SmartDashboard.postListenerTask(() -> setter.accept(event.value)); }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); } m_properties.add(property); diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SmartDashboard.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SmartDashboard.java index 32de56471a..08c32b2eed 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SmartDashboard.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/smartdashboard/SmartDashboard.java @@ -50,6 +50,11 @@ public class SmartDashboard { @SuppressWarnings("PMD.UseConcurrentHashMap") private static final Map tablesToData = new HashMap<>(); + /** + * The executor for listener tasks; calls listener tasks synchronously from main thread. + */ + private static final ListenerExecutor listenerExecutor = new ListenerExecutor(); + static { HAL.report(tResourceType.kResourceType_SmartDashboard, 0); } @@ -521,6 +526,16 @@ public class SmartDashboard { return getEntry(key).getRaw(defaultValue); } + /** + * Posts a task from a listener to the ListenerExecutor, so that it can be run synchronously + * from the main loop on the next call to {@link SmartDashboard#updateValues()}. + * + * @param task The task to run synchronously from the main thread. + */ + public static void postListenerTask(Runnable task) { + listenerExecutor.execute(task); + } + /** * Puts all sendable data to the dashboard. */ @@ -528,5 +543,7 @@ public class SmartDashboard { for (Data data : tablesToData.values()) { data.m_builder.updateTable(); } + // Execute posted listener tasks + listenerExecutor.runListenerTasks(); } }