Files
allwpilib/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp

250 lines
8.0 KiB
C++
Raw Normal View History

2020-12-26 14:31:24 -08:00
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
2025-11-07 19:56:21 -05:00
#include "wpi/glass/networktables/NetworkTablesProvider.hpp"
#include <algorithm>
2024-09-20 17:43:39 -07:00
#include <memory>
#include <utility>
#include <fmt/format.h>
2025-11-07 19:57:55 -05:00
#include "wpi/glass/Storage.hpp"
#include "wpi/gui/wpigui.hpp"
2025-11-07 19:56:21 -05:00
#include "wpi/nt/ntcore_cpp.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/StringExtras.hpp"
2025-11-07 20:00:05 -05:00
using namespace wpi::glass;
NetworkTablesProvider::NetworkTablesProvider(Storage& storage)
2025-11-07 20:01:58 -05:00
: NetworkTablesProvider{storage,
wpi::nt::NetworkTableInstance::GetDefault()} {}
2022-10-08 10:01:31 -07:00
NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
2025-11-07 20:00:05 -05:00
wpi::nt::NetworkTableInstance inst)
: Provider{storage.GetChild("windows")},
2022-10-08 10:01:31 -07:00
m_inst{inst},
m_poller{inst},
m_typeCache{storage.GetChild("types")} {
storage.SetCustomApply([this] {
m_listener = m_poller.AddListener(
{{""}}, wpi::nt::EventFlags::IMMEDIATE | wpi::nt::EventFlags::TOPIC);
for (auto&& childIt : m_storage.GetChildren()) {
auto id = childIt.key();
auto typePtr = m_typeCache.FindValue(id);
if (!typePtr || typePtr->type != Storage::Value::kString) {
continue;
}
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(typePtr->stringVal);
if (builderIt == m_typeMap.end()) {
continue;
}
auto entry = GetOrCreateView(
2022-10-08 10:01:31 -07:00
builderIt->second, m_inst.GetTopic(fmt::format("{}/.type", id)), id);
if (entry) {
Show(entry, nullptr);
}
}
});
storage.SetCustomClear([this, &storage] {
m_poller.RemoveListener(m_listener);
m_listener = 0;
for (auto&& modelEntry : m_modelEntries) {
modelEntry->model.reset();
}
m_viewEntries.clear();
m_windows.clear();
m_typeCache.EraseAll();
storage.ClearValues();
});
}
void NetworkTablesProvider::DisplayMenu() {
2025-11-07 20:00:05 -05:00
wpi::util::SmallVector<std::string_view, 6> path;
wpi::util::SmallString<64> name;
for (auto&& entry : m_viewEntries) {
path.clear();
2025-11-07 20:00:05 -05:00
wpi::util::split(entry->name, '/', -1, false,
2025-11-07 20:01:58 -05:00
[&](auto name) { path.emplace_back(name); });
bool fullDepth = true;
int depth = 0;
for (; depth < (static_cast<int>(path.size()) - 1); ++depth) {
name = path[depth];
if (!ImGui::BeginMenu(name.c_str())) {
fullDepth = false;
break;
}
}
if (fullDepth) {
bool visible = entry->window && entry->window->IsVisible();
bool wasVisible = visible;
// FIXME: enabled?
// data is the last item, so is guaranteed to be null-terminated
ImGui::MenuItem(path.back().data(), nullptr, &visible, true);
// Add type label to smartdashboard sendables
2025-11-07 20:00:05 -05:00
if (wpi::util::starts_with(entry->name, "/SmartDashboard/")) {
auto typeEntry = m_typeCache.FindValue(entry->name);
if (typeEntry) {
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::Text("%s", typeEntry->stringVal.c_str());
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::Dummy(ImVec2(10.0f, 0.0f));
}
}
if (!wasVisible && visible) {
Show(entry.get(), entry->window);
} else if (wasVisible && !visible && entry->window) {
entry->window->SetVisible(false);
}
}
for (; depth > 0; --depth) {
ImGui::EndMenu();
}
}
}
void NetworkTablesProvider::Update() {
Provider::Update();
for (auto&& event : m_poller.ReadQueue()) {
if (auto info = event.GetTopicInfo()) {
// add/remove entries from NT changes
// look for .type fields
2025-11-07 20:01:58 -05:00
if (!wpi::util::ends_with(info->name, "/.type") ||
info->type != NT_STRING || info->type_str != "string") {
continue;
}
if (event.flags & wpi::nt::EventFlags::UNPUBLISH) {
auto it = m_topicMap.find(info->topic);
if (it != m_topicMap.end()) {
m_poller.RemoveListener(it->second.listener);
m_topicMap.erase(it);
}
auto it2 = std::find_if(
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
return static_cast<Entry*>(elem->modelEntry)
->typeTopic.GetHandle() == info->topic;
});
if (it2 != m_viewEntries.end()) {
m_viewEntries.erase(it2);
}
} else if (event.flags & wpi::nt::EventFlags::PUBLISH) {
// subscribe to it; use a subscriber so we only get string values
SubListener sublistener;
2025-11-07 20:01:58 -05:00
sublistener.subscriber =
wpi::nt::StringTopic{info->topic}.Subscribe("");
sublistener.listener = m_poller.AddListener(
sublistener.subscriber,
wpi::nt::EventFlags::VALUE_ALL | wpi::nt::EventFlags::IMMEDIATE);
m_topicMap.try_emplace(info->topic, std::move(sublistener));
}
} else if (auto valueData = event.GetValueEventData()) {
// handle actual .type strings
if (!valueData->value.IsString()) {
continue;
2022-10-08 10:01:31 -07:00
}
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(valueData->value.GetString());
if (builderIt == m_typeMap.end()) {
continue;
}
2022-10-08 10:01:31 -07:00
2025-11-07 20:00:05 -05:00
auto topicName = wpi::nt::GetTopicName(valueData->topic);
auto tableName =
2025-11-07 20:00:05 -05:00
wpi::util::remove_suffix(topicName, "/.type").value_or(topicName);
2022-10-08 10:01:31 -07:00
2025-11-07 20:00:05 -05:00
GetOrCreateView(builderIt->second, wpi::nt::Topic{valueData->topic},
tableName);
// cache the type
m_typeCache.SetString(tableName, valueData->value.GetString());
}
}
}
void NetworkTablesProvider::Register(std::string_view typeName,
CreateModelFunc createModel,
CreateViewFunc createView) {
m_typeMap[typeName] = Builder{std::move(createModel), std::move(createView)};
}
void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
// if there's already a window, just show it
if (entry->window) {
entry->window->SetVisible(true);
return;
}
// get or create model
if (!entry->modelEntry->model) {
entry->modelEntry->model =
2022-10-08 10:01:31 -07:00
entry->modelEntry->createModel(m_inst, entry->name.c_str());
}
if (!entry->modelEntry->model) {
return;
}
// the window might exist and we're just not associated to it yet
if (!window) {
window = GetOrAddWindow(entry->name, true, Window::kHide);
}
if (!window) {
return;
}
2025-11-07 20:00:05 -05:00
if (auto name = wpi::util::remove_prefix(entry->name, "/SmartDashboard/")) {
window->SetDefaultName(fmt::format("{} (SmartDashboard)", *name));
}
entry->window = window;
// create view
auto view = entry->createView(window, entry->modelEntry->model.get(),
entry->name.c_str());
if (!view) {
return;
}
window->SetView(std::move(view));
entry->window->SetVisible(true);
}
NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
2025-11-07 20:00:05 -05:00
const Builder& builder, wpi::nt::Topic typeTopic, std::string_view name) {
// get view entry if it already exists
auto viewIt = FindViewEntry(name);
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
// make sure typeEntry is set in model
2022-10-08 10:01:31 -07:00
static_cast<Entry*>((*viewIt)->modelEntry)->typeTopic = typeTopic;
return viewIt->get();
}
// get or create model entry
auto modelIt = FindModelEntry(name);
if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
2022-10-08 10:01:31 -07:00
static_cast<Entry*>(modelIt->get())->typeTopic = typeTopic;
} else {
modelIt = m_modelEntries.emplace(
2022-10-08 10:01:31 -07:00
modelIt, std::make_unique<Entry>(typeTopic, name, builder));
}
// create new view entry
viewIt = m_viewEntries.emplace(
viewIt,
std::make_unique<ViewEntry>(
name, modelIt->get(), [](Model*, const char*) { return true; },
builder.createView));
return viewIt->get();
}