mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpilib] Refactor Alert (#7279)
This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance. Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting. This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized. Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n)) Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n)) This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert. This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
This commit is contained in:
@@ -4,69 +4,183 @@
|
||||
|
||||
#include "frc/Alert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <networktables/NTSendable.h>
|
||||
#include <networktables/NTSendableBuilder.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/sendable/SendableHelper.h>
|
||||
#include <wpi/sendable/SendableRegistry.h>
|
||||
|
||||
#include "frc/Timer.h"
|
||||
#include "frc/Errors.h"
|
||||
#include "frc/RobotController.h"
|
||||
#include "frc/smartdashboard/SmartDashboard.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
class Alert::PublishedAlert {
|
||||
public:
|
||||
PublishedAlert(uint64_t timestamp, std::string_view text)
|
||||
: timestamp{timestamp}, text{text} {}
|
||||
uint64_t timestamp;
|
||||
std::string text;
|
||||
auto operator<=>(const PublishedAlert& other) const {
|
||||
if (timestamp != other.timestamp) {
|
||||
return other.timestamp <=> timestamp;
|
||||
} else {
|
||||
return text <=> other.text;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Alert::SendableAlerts : public nt::NTSendable,
|
||||
public wpi::SendableHelper<SendableAlerts> {
|
||||
public:
|
||||
SendableAlerts() { m_alerts.fill({}); }
|
||||
|
||||
void InitSendable(nt::NTSendableBuilder& builder) override {
|
||||
builder.SetSmartDashboardType("Alerts");
|
||||
builder.AddStringArrayProperty(
|
||||
"errors", [this]() { return GetStrings(AlertType::kError); }, nullptr);
|
||||
builder.AddStringArrayProperty(
|
||||
"warnings", [this]() { return GetStrings(AlertType::kWarning); },
|
||||
nullptr);
|
||||
builder.AddStringArrayProperty(
|
||||
"infos", [this]() { return GetStrings(AlertType::kInfo); }, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the set of active alerts for the given type.
|
||||
* @param type the type
|
||||
* @return reference to the set of active alerts for the type
|
||||
*/
|
||||
std::set<PublishedAlert>& GetActiveAlertsStorage(AlertType type) {
|
||||
return const_cast<std::set<Alert::PublishedAlert>&>(
|
||||
std::as_const(*this).GetActiveAlertsStorage(type));
|
||||
}
|
||||
|
||||
const std::set<PublishedAlert>& GetActiveAlertsStorage(AlertType type) const {
|
||||
switch (type) {
|
||||
case AlertType::kInfo:
|
||||
case AlertType::kWarning:
|
||||
case AlertType::kError:
|
||||
return m_alerts[static_cast<int32_t>(type)];
|
||||
default:
|
||||
throw FRC_MakeError(frc::err::InvalidParameter,
|
||||
"Invalid Alert Type: {}", type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SendableAlerts for a given group, initializing and publishing
|
||||
* if it does not already exist.
|
||||
* @param group the group name
|
||||
* @return the SendableAlerts for the group
|
||||
*/
|
||||
static SendableAlerts& ForGroup(std::string_view group) {
|
||||
// Force initialization of SendableRegistry before our magic static to
|
||||
// prevent incorrect destruction order.
|
||||
wpi::SendableRegistry::EnsureInitialized();
|
||||
static wpi::StringMap<Alert::SendableAlerts> groups;
|
||||
|
||||
auto [iter, exists] = groups.try_emplace(group);
|
||||
SendableAlerts& sendable = iter->second;
|
||||
if (!exists) {
|
||||
frc::SmartDashboard::PutData(group, &iter->second);
|
||||
}
|
||||
return sendable;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> GetStrings(AlertType type) const {
|
||||
auto& set = GetActiveAlertsStorage(type);
|
||||
std::vector<std::string> output;
|
||||
output.reserve(set.size());
|
||||
for (auto& alert : set) {
|
||||
output.emplace_back(alert.text);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::array<std::set<PublishedAlert>, 3> m_alerts;
|
||||
};
|
||||
|
||||
Alert::Alert(std::string_view text, AlertType type)
|
||||
: Alert("Alerts", text, type) {}
|
||||
|
||||
wpi::StringMap<Alert::SendableAlerts> Alert::groups;
|
||||
|
||||
Alert::Alert(std::string_view group, std::string_view text, AlertType type)
|
||||
: m_type(type), m_text(text) {
|
||||
if (!groups.contains(group)) {
|
||||
groups[group] = SendableAlerts();
|
||||
frc::SmartDashboard::PutData(group, &groups[group]);
|
||||
: m_type(type),
|
||||
m_text(text),
|
||||
m_activeAlerts{
|
||||
&SendableAlerts::ForGroup(group).GetActiveAlertsStorage(m_type)} {}
|
||||
|
||||
Alert::Alert(Alert&& other)
|
||||
: m_type{other.m_type},
|
||||
m_text{std::move(other.m_text)},
|
||||
m_activeAlerts{std::exchange(other.m_activeAlerts, nullptr)},
|
||||
m_active{std::exchange(other.m_active, false)},
|
||||
m_activeStartTime{other.m_activeStartTime} {}
|
||||
|
||||
Alert& Alert::operator=(Alert&& other) {
|
||||
if (&other != this) {
|
||||
// We want to destroy current state after the move is done
|
||||
Alert tmp{std::move(*this)};
|
||||
// Now, swap moved-from state with other state
|
||||
std::swap(m_type, other.m_type);
|
||||
std::swap(m_text, other.m_text);
|
||||
std::swap(m_activeAlerts, other.m_activeAlerts);
|
||||
std::swap(m_active, other.m_active);
|
||||
std::swap(m_activeStartTime, other.m_activeStartTime);
|
||||
}
|
||||
groups[group].m_alerts.push_back(std::shared_ptr<Alert>(this));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Alert::~Alert() {
|
||||
Set(false);
|
||||
}
|
||||
|
||||
void Alert::Set(bool active) {
|
||||
if (active && !m_active) {
|
||||
m_activeStartTime = frc::Timer::GetFPGATimestamp();
|
||||
if (active == m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
m_activeStartTime = frc::RobotController::GetFPGATime();
|
||||
m_activeAlerts->emplace(m_activeStartTime, m_text);
|
||||
} else {
|
||||
m_activeAlerts->erase({m_activeStartTime, m_text});
|
||||
}
|
||||
m_active = active;
|
||||
}
|
||||
|
||||
void Alert::SetText(std::string_view text) {
|
||||
if (text == m_text) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string oldText = std::move(m_text);
|
||||
m_text = text;
|
||||
|
||||
if (m_active) {
|
||||
auto iter = m_activeAlerts->find({m_activeStartTime, oldText});
|
||||
auto hint = m_activeAlerts->erase(iter);
|
||||
m_activeAlerts->emplace_hint(hint, m_activeStartTime, m_text);
|
||||
}
|
||||
}
|
||||
|
||||
void Alert::SendableAlerts::InitSendable(nt::NTSendableBuilder& builder) {
|
||||
builder.SetSmartDashboardType("Alerts");
|
||||
builder.AddStringArrayProperty(
|
||||
"errors", [this]() { return GetStrings(AlertType::kError); }, nullptr);
|
||||
builder.AddStringArrayProperty(
|
||||
"warnings", [this]() { return GetStrings(AlertType::kWarning); },
|
||||
nullptr);
|
||||
builder.AddStringArrayProperty(
|
||||
"infos", [this]() { return GetStrings(AlertType::kInfo); }, nullptr);
|
||||
}
|
||||
|
||||
std::vector<std::string> Alert::SendableAlerts::GetStrings(
|
||||
AlertType type) const {
|
||||
wpi::SmallVector<std::shared_ptr<Alert>> alerts;
|
||||
alerts.reserve(m_alerts.size());
|
||||
for (auto alert : m_alerts) {
|
||||
if (alert->m_active && alert->m_type == type) {
|
||||
alerts.push_back(alert);
|
||||
}
|
||||
std::string frc::format_as(Alert::AlertType type) {
|
||||
switch (type) {
|
||||
case Alert::AlertType::kInfo:
|
||||
return "kInfo";
|
||||
case Alert::AlertType::kWarning:
|
||||
return "kWarning";
|
||||
case Alert::AlertType::kError:
|
||||
return "kError";
|
||||
default:
|
||||
return std::to_string(static_cast<int>(type));
|
||||
}
|
||||
std::sort(alerts.begin(), alerts.end(), [](const auto a, const auto b) {
|
||||
return a->m_activeStartTime > b->m_activeStartTime;
|
||||
});
|
||||
std::vector<std::string> output{alerts.size()};
|
||||
for (unsigned int i = 0; i < alerts.size(); ++i) {
|
||||
std::string text{alerts[i]->m_text};
|
||||
output[i] = text;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user