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

1640 lines
52 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.
#include "glass/networktables/NetworkTables.h"
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <span>
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include <imgui.h>
2022-10-08 10:01:31 -07:00
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableValue.h>
#include <ntcore_c.h>
#include <ntcore_cpp.h>
2022-10-08 10:01:31 -07:00
#include <ntcore_cpp_types.h>
#include <wpi/MessagePack.h>
#include <wpi/SmallString.h>
#include <wpi/SpanExtras.h>
#include <wpi/StringExtras.h>
2022-10-08 10:01:31 -07:00
#include <wpi/mpack.h>
#include <wpi/raw_ostream.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
2022-10-08 10:01:31 -07:00
using namespace mpack;
namespace {
enum ShowCategory {
ShowPersistent,
ShowRetained,
ShowTransitory,
ShowAll,
};
} // namespace
static bool IsVisible(ShowCategory category, bool persistent, bool retained) {
switch (category) {
case ShowPersistent:
return persistent;
case ShowRetained:
return retained && !persistent;
case ShowTransitory:
return !retained && !persistent;
case ShowAll:
return true;
default:
return false;
}
}
static std::string BooleanArrayToString(std::span<const int> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto v : in) {
if (!first) {
os << ',';
}
first = false;
if (v) {
os << "true";
} else {
os << "false";
}
}
os << ']';
return rv;
}
static std::string IntegerArrayToString(std::span<const int64_t> in) {
2022-10-08 10:01:31 -07:00
return fmt::format("[{:d}]", fmt::join(in, ","));
}
template <typename T>
static std::string FloatArrayToString(std::span<const T> in) {
2022-10-08 10:01:31 -07:00
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
return fmt::format("[{:.6f}]", fmt::join(in, ","));
}
static std::string StringArrayToString(std::span<const std::string> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto&& v : in) {
if (!first) {
os << ',';
}
first = false;
os << '"';
os.write_escaped(v);
os << '"';
}
os << ']';
return rv;
}
NetworkTablesModel::NetworkTablesModel()
2022-10-08 10:01:31 -07:00
: NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
: m_inst{inst},
m_subscriber{nt::SubscribeMultiple(inst.GetHandle(), {{"", "$"}})},
m_topicPoller{inst},
m_valuePoller{inst} {
m_topicPoller.Add({{""}},
NT_TOPIC_NOTIFY_IMMEDIATE | NT_TOPIC_NOTIFY_PROPERTIES |
NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_UNPUBLISH);
m_valuePoller.Add(m_subscriber,
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
}
2022-10-08 10:01:31 -07:00
NetworkTablesModel::Entry::~Entry() {
if (publisher != 0) {
nt::Unpublish(publisher);
}
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::Entry::UpdateInfo(nt::TopicInfo&& info_) {
info = std::move(info_);
properties = info.GetProperties();
persistent = false;
auto it = properties.find("persistent");
if (it != properties.end()) {
if (auto v = it->get_ptr<const bool*>()) {
persistent = *v;
}
}
retained = false;
it = properties.find("retained");
if (it != properties.end()) {
if (auto v = it->get_ptr<const bool*>()) {
retained = *v;
}
}
}
static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
mpack_reader_t& r, std::string_view name,
int64_t time) {
mpack_tag_t tag = mpack_read_tag(&r);
switch (mpack_tag_type(&tag)) {
case mpack::mpack_type_bool:
out->UpdateFromValue(
nt::Value::MakeBoolean(mpack_tag_bool_value(&tag), time), name, "");
break;
case mpack::mpack_type_int:
out->UpdateFromValue(
nt::Value::MakeInteger(mpack_tag_int_value(&tag), time), name, "");
break;
case mpack::mpack_type_uint:
out->UpdateFromValue(
nt::Value::MakeInteger(mpack_tag_uint_value(&tag), time), name, "");
break;
case mpack::mpack_type_float:
out->UpdateFromValue(
nt::Value::MakeFloat(mpack_tag_float_value(&tag), time), name, "");
break;
case mpack::mpack_type_double:
out->UpdateFromValue(
nt::Value::MakeDouble(mpack_tag_double_value(&tag), time), name, "");
break;
case mpack::mpack_type_str: {
std::string str;
mpack_read_str(&r, &tag, &str);
mpack_done_str(&r);
out->UpdateFromValue(nt::Value::MakeString(std::move(str), time), name,
"");
break;
}
case mpack::mpack_type_bin:
// just skip it
mpack_skip_bytes(&r, mpack_tag_bin_length(&tag));
mpack_done_bin(&r);
break;
case mpack::mpack_type_array: {
if (out->valueChildrenMap) {
out->valueChildren.clear();
out->valueChildrenMap = false;
}
out->valueChildren.resize(mpack_tag_array_count(&tag));
unsigned int i = 0;
for (auto&& child : out->valueChildren) {
if (child.name.empty()) {
child.name = fmt::format("[{}]", i);
child.path = fmt::format("{}{}", name, child.name);
}
++i;
UpdateMsgpackValueSource(&child, r, child.path, time); // recurse
}
mpack_done_array(&r);
break;
}
case mpack::mpack_type_map: {
if (!out->valueChildrenMap) {
out->valueChildren.clear();
out->valueChildrenMap = true;
}
wpi::StringMap<size_t> elems;
for (size_t i = 0, size = out->valueChildren.size(); i < size; ++i) {
elems[out->valueChildren[i].name] = i;
}
bool added = false;
uint32_t count = mpack_tag_map_count(&tag);
for (uint32_t i = 0; i < count; ++i) {
std::string key;
if (mpack_expect_str(&r, &key) == mpack_ok) {
auto it = elems.find(key);
if (it != elems.end()) {
auto& child = out->valueChildren[it->second];
UpdateMsgpackValueSource(&child, r, child.path, time);
elems.erase(it);
} else {
added = true;
out->valueChildren.emplace_back();
auto& child = out->valueChildren.back();
child.name = std::move(key);
child.path = fmt::format("{}/{}", name, child.name);
UpdateMsgpackValueSource(&child, r, child.path, time);
}
}
}
// erase unmatched keys
out->valueChildren.erase(
std::remove_if(
out->valueChildren.begin(), out->valueChildren.end(),
[&](const auto& child) { return elems.count(child.name) > 0; }),
out->valueChildren.end());
if (added) {
// sort by name
std::sort(out->valueChildren.begin(), out->valueChildren.end(),
[](const auto& a, const auto& b) { return a.name < b.name; });
}
mpack_done_map(&r);
break;
}
default:
out->value = {};
mpack_done_type(&r, mpack_tag_type(&tag));
break;
}
}
2022-10-08 10:01:31 -07:00
static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
const wpi::json& j, std::string_view name,
int64_t time) {
switch (j.type()) {
case wpi::json::value_t::object: {
if (!out->valueChildrenMap) {
out->valueChildren.clear();
out->valueChildrenMap = true;
}
wpi::StringMap<size_t> elems;
for (size_t i = 0, size = out->valueChildren.size(); i < size; ++i) {
elems[out->valueChildren[i].name] = i;
}
bool added = false;
for (auto&& kv : j.items()) {
auto it = elems.find(kv.key());
if (it != elems.end()) {
auto& child = out->valueChildren[it->second];
UpdateJsonValueSource(&child, kv.value(), child.path, time);
elems.erase(it);
} else {
added = true;
out->valueChildren.emplace_back();
auto& child = out->valueChildren.back();
child.name = kv.key();
child.path = fmt::format("{}/{}", name, child.name);
UpdateJsonValueSource(&child, kv.value(), child.path, time);
}
}
// erase unmatched keys
out->valueChildren.erase(
std::remove_if(
out->valueChildren.begin(), out->valueChildren.end(),
[&](const auto& child) { return elems.count(child.name) > 0; }),
out->valueChildren.end());
if (added) {
// sort by name
std::sort(out->valueChildren.begin(), out->valueChildren.end(),
[](const auto& a, const auto& b) { return a.name < b.name; });
}
break;
}
case wpi::json::value_t::array: {
if (out->valueChildrenMap) {
out->valueChildren.clear();
out->valueChildrenMap = false;
}
out->valueChildren.resize(j.size());
unsigned int i = 0;
for (auto&& child : out->valueChildren) {
if (child.name.empty()) {
child.name = fmt::format("[{}]", i);
child.path = fmt::format("{}{}", name, child.name);
}
++i;
UpdateJsonValueSource(&child, j[i], child.path, time); // recurse
}
break;
}
case wpi::json::value_t::string:
out->UpdateFromValue(
nt::Value::MakeString(j.get_ref<const std::string&>(), time), name,
"");
break;
case wpi::json::value_t::boolean:
out->UpdateFromValue(nt::Value::MakeBoolean(j.get<bool>(), time), name,
"");
break;
case wpi::json::value_t::number_integer:
out->UpdateFromValue(nt::Value::MakeInteger(j.get<int64_t>(), time), name,
"");
break;
case wpi::json::value_t::number_unsigned:
out->UpdateFromValue(nt::Value::MakeInteger(j.get<uint64_t>(), time),
name, "");
break;
case wpi::json::value_t::number_float:
out->UpdateFromValue(nt::Value::MakeDouble(j.get<double>(), time), name,
"");
break;
default:
out->value = {};
break;
}
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::ValueSource::UpdateFromValue(
nt::Value&& v, std::string_view name, std::string_view typeStr) {
value = v;
switch (value.type()) {
case NT_BOOLEAN:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
2022-10-08 10:01:31 -07:00
source->SetValue(value.GetBoolean() ? 1 : 0, value.last_change());
source->SetDigital(true);
break;
2022-10-08 10:01:31 -07:00
case NT_INTEGER:
valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
source->SetValue(value.GetInteger(), value.last_change());
source->SetDigital(false);
break;
case NT_FLOAT:
valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
source->SetValue(value.GetFloat(), value.last_change());
source->SetDigital(false);
break;
case NT_DOUBLE:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
2022-10-08 10:01:31 -07:00
source->SetValue(value.GetDouble(), value.last_change());
source->SetDigital(false);
break;
case NT_BOOLEAN_ARRAY:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
valueStr = BooleanArrayToString(value.GetBooleanArray());
break;
case NT_INTEGER_ARRAY:
valueChildren.clear();
valueStr = IntegerArrayToString(value.GetIntegerArray());
break;
case NT_FLOAT_ARRAY:
valueChildren.clear();
valueStr = FloatArrayToString(value.GetFloatArray());
break;
case NT_DOUBLE_ARRAY:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
valueStr = FloatArrayToString(value.GetDoubleArray());
break;
case NT_STRING_ARRAY:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
valueStr = StringArrayToString(value.GetStringArray());
break;
case NT_STRING:
if (typeStr == "json") {
try {
UpdateJsonValueSource(this, wpi::json::parse(value.GetString()), name,
value.last_change());
} catch (wpi::json::exception&) {
// ignore
}
} else {
valueChildren.clear();
}
break;
case NT_RAW:
if (typeStr == "msgpack") {
mpack_reader_t r;
mpack_reader_init_data(&r, value.GetRaw());
UpdateMsgpackValueSource(this, r, name, value.last_change());
mpack_reader_destroy(&r);
} else {
valueChildren.clear();
}
break;
default:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
break;
}
}
void NetworkTablesModel::Update() {
bool updateTree = false;
2022-10-08 10:01:31 -07:00
for (auto&& event : m_topicPoller.ReadQueue()) {
auto& entry = m_entries[event.info.topic];
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
if (!entry) {
2022-10-08 10:01:31 -07:00
entry = std::make_unique<Entry>();
m_sortedEntries.emplace_back(entry.get());
updateTree = true;
}
}
2022-10-08 10:01:31 -07:00
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
entry.get());
// will be removed completely below
if (it != m_sortedEntries.end()) {
*it = nullptr;
}
2022-10-08 10:01:31 -07:00
m_entries.erase(event.info.topic);
updateTree = true;
continue;
}
2022-10-08 10:01:31 -07:00
if (event.flags & NT_TOPIC_NOTIFY_PROPERTIES) {
updateTree = true;
}
if (entry) {
entry->UpdateTopic(std::move(event));
}
2022-10-08 10:01:31 -07:00
}
for (auto&& event : m_valuePoller.ReadQueue()) {
auto& entry = m_entries[event.topic];
if (entry) {
entry->UpdateFromValue(std::move(event.value), entry->info.name,
entry->info.type_str);
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
entry->info.type_str == "msgpack") {
fmt::print(stderr, "Updating meta-topic {}\n", entry->info.name);
// meta topic handling
if (entry->info.name == "$clients") {
UpdateClients(entry->value.GetRaw());
} else if (entry->info.name == "$serverpub") {
m_server.UpdatePublishers(entry->value.GetRaw());
} else if (entry->info.name == "$serversub") {
m_server.UpdateSubscribers(entry->value.GetRaw());
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdatePublishers(entry->value.GetRaw());
}
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdateSubscribers(entry->value.GetRaw());
}
}
}
}
}
// shortcut common case (updates)
if (!updateTree) {
return;
}
// remove deleted entries
m_sortedEntries.erase(
std::remove(m_sortedEntries.begin(), m_sortedEntries.end(), nullptr),
m_sortedEntries.end());
2022-10-08 10:01:31 -07:00
RebuildTree();
}
void NetworkTablesModel::RebuildTree() {
// sort by name
2022-10-08 10:01:31 -07:00
std::sort(
m_sortedEntries.begin(), m_sortedEntries.end(),
[](const auto& a, const auto& b) { return a->info.name < b->info.name; });
RebuildTreeImpl(&m_root, ShowAll);
RebuildTreeImpl(&m_persistentRoot, ShowPersistent);
RebuildTreeImpl(&m_retainedRoot, ShowRetained);
RebuildTreeImpl(&m_transitoryRoot, ShowTransitory);
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
int category) {
tree->clear();
wpi::SmallVector<std::string_view, 16> parts;
for (auto& entry : m_sortedEntries) {
2022-10-08 10:01:31 -07:00
if (!IsVisible(static_cast<ShowCategory>(category), entry->persistent,
entry->retained)) {
continue;
}
parts.clear();
2022-10-08 10:01:31 -07:00
wpi::split(entry->info.name, parts, '/', -1, false);
// ignore a raw "/" key
if (parts.empty()) {
continue;
}
// get to leaf
2022-10-08 10:01:31 -07:00
auto nodes = tree;
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
auto it =
std::find_if(nodes->begin(), nodes->end(),
[&](const auto& node) { return node.name == part; });
if (it == nodes->end()) {
nodes->emplace_back(part);
// path is from the beginning of the string to the end of the current
// part; this works because part is a reference to the internals of
2022-10-08 10:01:31 -07:00
// entry->info.name
nodes->back().path.assign(
2022-10-08 10:01:31 -07:00
entry->info.name.data(),
part.data() + part.size() - entry->info.name.data());
it = nodes->end() - 1;
}
nodes = &it->children;
}
auto it = std::find_if(nodes->begin(), nodes->end(), [&](const auto& node) {
return node.name == parts.back();
});
if (it == nodes->end()) {
nodes->emplace_back(parts.back());
// no need to set path, as it's identical to entry->name
it = nodes->end() - 1;
}
it->entry = entry;
}
}
bool NetworkTablesModel::Exists() {
2022-10-08 10:01:31 -07:00
return m_inst.IsConnected();
}
NetworkTablesModel::Entry* NetworkTablesModel::GetEntry(std::string_view name) {
auto entryIt = std::lower_bound(
m_sortedEntries.begin(), m_sortedEntries.end(), name,
[](auto&& entry, auto&& name) { return entry->info.name < name; });
if (entryIt == m_sortedEntries.end() || (*entryIt)->info.name != name) {
return nullptr;
}
return *entryIt;
}
NetworkTablesModel::Entry* NetworkTablesModel::AddEntry(NT_Topic topic) {
auto& entry = m_entries[topic];
if (!entry) {
entry = std::make_unique<Entry>();
entry->info = nt::GetTopicInfo(topic);
entry->properties = entry->info.GetProperties();
m_sortedEntries.emplace_back(entry.get());
}
RebuildTree();
return entry.get();
}
void NetworkTablesModel::Client::UpdatePublishers(
std::span<const uint8_t> data) {
2022-10-08 10:01:31 -07:00
mpack_reader_t r;
mpack_reader_init_data(&r, data);
uint32_t numPub = mpack_expect_array_max(&r, 1000);
std::vector<ClientPublisher> newPublishers;
newPublishers.reserve(numPub);
for (uint32_t i = 0; i < numPub; ++i) {
ClientPublisher pub;
uint32_t numMapElem = mpack_expect_map(&r);
for (uint32_t j = 0; j < numMapElem; ++j) {
std::string key;
mpack_expect_str(&r, &key);
if (key == "uid") {
pub.uid = mpack_expect_i64(&r);
} else if (key == "topic") {
mpack_expect_str(&r, &pub.topic);
} else {
mpack_discard(&r);
}
}
mpack_done_map(&r);
newPublishers.emplace_back(std::move(pub));
}
mpack_done_array(&r);
if (mpack_reader_destroy(&r) == mpack_ok) {
publishers = std::move(newPublishers);
} else {
fmt::print(stderr, "Failed to update publishers\n");
}
}
2022-10-08 10:01:31 -07:00
static void DecodeSubscriberOptions(
mpack_reader_t& r, NetworkTablesModel::SubscriberOptions* options) {
*options = NetworkTablesModel::SubscriberOptions{};
uint32_t numMapElem = mpack_expect_map(&r);
for (uint32_t j = 0; j < numMapElem; ++j) {
std::string key;
mpack_expect_str(&r, &key);
if (key == "immediate") {
options->immediate = mpack_expect_bool(&r);
} else if (key == "sendAll") {
options->sendAll = mpack_expect_bool(&r);
} else if (key == "periodic") {
options->periodic = mpack_expect_float(&r);
} else if (key == "prefix") {
options->prefixMatch = mpack_expect_bool(&r);
} else {
// TODO: Save other options
mpack_discard(&r);
}
}
mpack_done_map(&r);
}
void NetworkTablesModel::Client::UpdateSubscribers(
std::span<const uint8_t> data) {
2022-10-08 10:01:31 -07:00
mpack_reader_t r;
mpack_reader_init_data(&r, data);
uint32_t numSub = mpack_expect_array_max(&r, 1000);
std::vector<ClientSubscriber> newSubscribers;
newSubscribers.reserve(numSub);
for (uint32_t i = 0; i < numSub; ++i) {
ClientSubscriber sub;
uint32_t numMapElem = mpack_expect_map(&r);
for (uint32_t j = 0; j < numMapElem; ++j) {
std::string key;
mpack_expect_str(&r, &key);
if (key == "uid") {
sub.uid = mpack_expect_i64(&r);
} else if (key == "topics") {
uint32_t numPrefix = mpack_expect_array_max(&r, 100);
sub.topics.reserve(numPrefix);
for (uint32_t k = 0; k < numPrefix; ++k) {
std::string val;
if (mpack_expect_str(&r, &val) == mpack_ok) {
sub.topics.emplace_back(std::move(val));
}
}
sub.topicsStr = StringArrayToString(sub.topics);
mpack_done_array(&r);
} else if (key == "options") {
DecodeSubscriberOptions(r, &sub.options);
} else {
mpack_discard(&r);
}
}
mpack_done_map(&r);
newSubscribers.emplace_back(std::move(sub));
}
mpack_done_array(&r);
if (mpack_reader_destroy(&r) == mpack_ok) {
subscribers = std::move(newSubscribers);
} else {
fmt::print(stderr, "Failed to update subscribers\n");
}
}
void NetworkTablesModel::UpdateClients(std::span<const uint8_t> data) {
2022-10-08 10:01:31 -07:00
mpack_reader_t r;
mpack_reader_init_data(&r, data);
uint32_t numClients = mpack_expect_array_max(&r, 100);
std::vector<Client> clientsArr;
clientsArr.reserve(numClients);
for (uint32_t i = 0; i < numClients; ++i) {
Client client;
uint32_t numMapElem = mpack_expect_map(&r);
for (uint32_t j = 0; j < numMapElem; ++j) {
std::string key;
mpack_expect_str(&r, &key);
if (key == "id") {
mpack_expect_str(&r, &client.id);
} else if (key == "conn") {
mpack_expect_str(&r, &client.conn);
} else if (key == "ver") {
uint16_t val = mpack_expect_u16(&r);
client.version = fmt::format("{}.{}", val >> 8, val & 0xff);
} else {
mpack_discard(&r);
}
}
mpack_done_map(&r);
clientsArr.emplace_back(std::move(client));
}
mpack_done_array(&r);
if (mpack_reader_destroy(&r) != mpack_ok) {
return;
}
// we need to create a new map so deletions are reflected
std::map<std::string, Client, std::less<>> newClients;
for (auto&& client : clientsArr) {
auto& newClient = newClients[client.id];
newClient = std::move(client);
auto it = m_clients.find(newClient.id);
if (it != m_clients.end()) {
// transfer from existing
newClient.publishers = std::move(it->second.publishers);
newClient.subscribers = std::move(it->second.subscribers);
} else {
// initially populate
if (Entry* entry = GetEntry(fmt::format("$clientpub${}", newClient.id))) {
if (entry->value.IsRaw() && entry->info.type_str == "msgpack") {
newClient.UpdatePublishers(entry->value.GetRaw());
}
}
if (Entry* entry = GetEntry(fmt::format("$clientsub${}", newClient.id))) {
if (entry->value.IsRaw() && entry->info.type_str == "msgpack") {
newClient.UpdateSubscribers(entry->value.GetRaw());
}
}
}
}
// replace map
m_clients = std::move(newClients);
}
static bool StringToBooleanArray(std::string_view in, std::vector<int>* out) {
in = wpi::trim(in);
if (in.empty()) {
2022-10-08 10:01:31 -07:00
return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
}
if (in.back() == ']') {
in.remove_suffix(1);
}
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
val = wpi::trim(val);
if (wpi::equals_lower(val, "true")) {
2022-10-08 10:01:31 -07:00
out->emplace_back(1);
} else if (wpi::equals_lower(val, "false")) {
2022-10-08 10:01:31 -07:00
out->emplace_back(0);
} else {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
2022-10-08 10:01:31 -07:00
return false;
}
}
2022-10-08 10:01:31 -07:00
return true;
}
2022-10-08 10:01:31 -07:00
static bool StringToIntegerArray(std::string_view in,
std::vector<int64_t>* out) {
in = wpi::trim(in);
if (in.empty()) {
2022-10-08 10:01:31 -07:00
return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
}
if (in.back() == ']') {
in.remove_suffix(1);
}
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
2022-10-08 10:01:31 -07:00
if (auto num = wpi::parse_integer<int64_t>(wpi::trim(val), 0)) {
out->emplace_back(num.value());
} else {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
2022-10-08 10:01:31 -07:00
return false;
}
}
2022-10-08 10:01:31 -07:00
return true;
}
template <typename T>
static bool StringToFloatArray(std::string_view in, std::vector<T>* out) {
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
in = wpi::trim(in);
if (in.empty()) {
return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
}
if (in.back() == ']') {
in.remove_suffix(1);
}
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
if (auto num = wpi::parse_float<T>(wpi::trim(val))) {
out->emplace_back(num.value());
} else {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
return false;
}
}
return true;
}
static int fromxdigit(char ch) {
if (ch >= 'a' && ch <= 'f') {
return (ch - 'a' + 10);
} else if (ch >= 'A' && ch <= 'F') {
return (ch - 'A' + 10);
} else {
return ch - '0';
}
}
static std::string_view UnescapeString(std::string_view source,
wpi::SmallVectorImpl<char>& buf) {
assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
buf.clear();
buf.reserve(source.size() - 2);
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
if (*s != '\\') {
buf.push_back(*s);
continue;
}
switch (*++s) {
case 't':
buf.push_back('\t');
break;
case 'n':
buf.push_back('\n');
break;
case 'x': {
if (!isxdigit(*(s + 1))) {
buf.push_back('x'); // treat it like a unknown escape
break;
}
int ch = fromxdigit(*++s);
if (std::isxdigit(*(s + 1))) {
ch <<= 4;
ch |= fromxdigit(*++s);
}
buf.push_back(static_cast<char>(ch));
break;
}
default:
buf.push_back(*s);
break;
}
}
return {buf.data(), buf.size()};
}
2022-10-08 10:01:31 -07:00
static bool StringToStringArray(std::string_view in,
std::vector<std::string>* out) {
in = wpi::trim(in);
if (in.empty()) {
2022-10-08 10:01:31 -07:00
return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
}
if (in.back() == ']') {
in.remove_suffix(1);
}
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
wpi::SmallString<32> buf;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
val = wpi::trim(val);
if (val.empty()) {
continue;
}
if (val.front() != '"' || val.back() != '"') {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
2022-10-08 10:01:31 -07:00
return false;
}
2022-10-08 10:01:31 -07:00
out->emplace_back(UnescapeString(val, buf));
}
2022-10-08 10:01:31 -07:00
return true;
}
2022-10-08 10:01:31 -07:00
static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
const char* typeStr,
NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
2022-10-08 10:01:31 -07:00
switch (val.type()) {
case NT_BOOLEAN:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "boolean", "%s",
val.GetBoolean() ? "true" : "false");
break;
case NT_INTEGER:
ImGui::LabelText(typeStr ? typeStr : "int", "%" PRId64, val.GetInteger());
break;
case NT_FLOAT:
ImGui::LabelText(typeStr ? typeStr : "double", "%.6f", val.GetFloat());
break;
case NT_DOUBLE: {
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "double",
fmt::format("%.{}f", precision).c_str(),
val.GetDouble());
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
break;
}
case NT_STRING: {
// GetString() comes from a std::string, so it's null terminated
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "string", "%s",
val.GetString().data());
break;
}
case NT_BOOLEAN_ARRAY:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "boolean[]", "%s",
entry.valueStr.c_str());
break;
case NT_INTEGER_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "int[]", "%s",
entry.valueStr.c_str());
break;
case NT_FLOAT_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "float[]", "%s",
entry.valueStr.c_str());
break;
case NT_DOUBLE_ARRAY:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "double[]", "%s",
entry.valueStr.c_str());
break;
case NT_STRING_ARRAY:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "string[]", "%s",
entry.valueStr.c_str());
break;
case NT_RAW:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "raw", "[...]");
break;
default:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
}
static constexpr size_t kTextBufferSize = 4096;
static char* GetTextBuffer(std::string_view in) {
static char textBuffer[kTextBufferSize];
size_t len = (std::min)(in.size(), kTextBufferSize - 1);
std::memcpy(textBuffer, in.data(), len);
textBuffer[len] = '\0';
return textBuffer;
}
static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry,
NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
2022-10-08 10:01:31 -07:00
const char* typeStr =
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str();
ImGui::PushID(entry.info.name.c_str());
switch (val.type()) {
case NT_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
2022-10-08 10:01:31 -07:00
int v = val.GetBoolean() ? 1 : 0;
if (ImGui::Combo(typeStr ? typeStr : "boolean", &v, boolOptions, 2)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_BOOLEAN, "boolean");
}
nt::SetBoolean(entry.publisher, v);
}
break;
}
case NT_INTEGER: {
int64_t v = val.GetInteger();
if (ImGui::InputScalar(typeStr ? typeStr : "int", ImGuiDataType_S64, &v,
nullptr, nullptr, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_INTEGER, "int");
}
nt::SetInteger(entry.publisher, v);
}
break;
}
case NT_FLOAT: {
float v = val.GetFloat();
if (ImGui::InputFloat(typeStr ? typeStr : "float", &v, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_FLOAT, "float");
}
nt::SetFloat(entry.publisher, v);
}
break;
}
case NT_DOUBLE: {
2022-10-08 10:01:31 -07:00
double v = val.GetDouble();
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
2022-10-08 10:01:31 -07:00
if (ImGui::InputDouble(typeStr ? typeStr : "double", &v, 0, 0,
fmt::format("%.{}f", precision).c_str(),
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_DOUBLE, "double");
}
nt::SetDouble(entry.publisher, v);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
break;
}
case NT_STRING: {
2022-10-08 10:01:31 -07:00
char* v = GetTextBuffer(val.GetString());
if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_STRING, "string");
}
nt::SetString(entry.publisher, v);
}
break;
}
case NT_BOOLEAN_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
2022-10-08 10:01:31 -07:00
if (ImGui::InputText(typeStr ? typeStr : "boolean[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
std::vector<int> outv;
if (StringToBooleanArray(v, &outv)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_BOOLEAN_ARRAY, "boolean[]");
}
nt::SetBooleanArray(entry.publisher, outv);
}
}
break;
}
case NT_INTEGER_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText(typeStr ? typeStr : "int[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
std::vector<int64_t> outv;
if (StringToIntegerArray(v, &outv)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_INTEGER_ARRAY, "int[]");
}
nt::SetIntegerArray(entry.publisher, outv);
}
}
break;
}
case NT_FLOAT_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText(typeStr ? typeStr : "float[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
std::vector<float> outv;
if (StringToFloatArray(v, &outv)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_DOUBLE_ARRAY, "float[]");
}
nt::SetFloatArray(entry.publisher, outv);
}
}
break;
}
case NT_DOUBLE_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
2022-10-08 10:01:31 -07:00
if (ImGui::InputText(typeStr ? typeStr : "double[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
std::vector<double> outv;
if (StringToFloatArray(v, &outv)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_DOUBLE_ARRAY, "double[]");
}
nt::SetDoubleArray(entry.publisher, outv);
}
}
break;
}
case NT_STRING_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
2022-10-08 10:01:31 -07:00
if (ImGui::InputText(typeStr ? typeStr : "string[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
std::vector<std::string> outv;
if (StringToStringArray(v, &outv)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_STRING_ARRAY, "string[]");
}
nt::SetStringArray(entry.publisher, outv);
}
}
break;
}
case NT_RAW:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "raw",
val.GetRaw().empty() ? "[]" : "[...]");
break;
case NT_RPC:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "rpc", "[...]");
break;
default:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
ImGui::PopID();
}
2022-10-08 10:01:31 -07:00
static void CreateTopicMenuItem(NetworkTablesModel* model,
std::string_view path, NT_Type type,
const char* typeStr, bool enabled) {
if (ImGui::MenuItem(typeStr, nullptr, false, enabled)) {
auto entry =
model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
if (entry->publisher == 0) {
entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
}
}
}
static void EmitParentContextMenu(NetworkTablesModel* model,
const std::string& path,
NetworkTablesFlags flags) {
static char nameBuffer[kTextBufferSize];
if (ImGui::BeginPopupContextItem(path.c_str())) {
ImGui::Text("%s", path.c_str());
ImGui::Separator();
if (ImGui::BeginMenu("Add new...")) {
if (ImGui::IsWindowAppearing()) {
nameBuffer[0] = '\0';
}
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
kTextBufferSize);
std::string fullNewPath;
if (path == "/") {
fullNewPath = path + nameBuffer;
} else {
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
}
ImGui::Text("Adding: %s", fullNewPath.c_str());
ImGui::Separator();
2022-10-08 10:01:31 -07:00
auto entry = model->GetEntry(fullNewPath);
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
nameBuffer[0] != '\0') &&
2022-10-08 10:01:31 -07:00
!exists;
CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
enabled);
ImGui::EndMenu();
}
ImGui::EndPopup();
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
static void EmitValueName(DataSource* source, const char* name,
const char* path) {
if (source) {
ImGui::Selectable(name);
source->EmitDrag();
} else {
ImGui::TextUnformatted(name);
}
2022-10-08 10:01:31 -07:00
if (ImGui::BeginPopupContextItem(path)) {
ImGui::TextUnformatted(path);
ImGui::EndPopup();
}
}
2022-10-08 10:01:31 -07:00
static void EmitValueTree(
const std::vector<NetworkTablesModel::EntryValueTreeNode>& children,
NetworkTablesFlags flags) {
for (auto&& child : children) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
EmitValueName(child.source.get(), child.name.c_str(), child.path.c_str());
ImGui::TableNextColumn();
if (!child.valueChildren.empty()) {
char label[64];
std::snprintf(label, sizeof(label),
child.valueChildrenMap ? "{...}##v_%s" : "[...]##v_%s",
child.name.c_str());
if (TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth)) {
EmitValueTree(child.valueChildren, flags);
TreePop();
}
} else {
EmitEntryValueReadonly(child, nullptr, flags);
}
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
static void EmitEntry(NetworkTablesModel* model,
NetworkTablesModel::Entry& entry, const char* name,
NetworkTablesFlags flags, ShowCategory category) {
if (!IsVisible(category, entry.persistent, entry.retained)) {
return;
}
bool valueChildrenOpen = false;
ImGui::TableNextRow();
ImGui::TableNextColumn();
EmitValueName(entry.source.get(), name, entry.info.name.c_str());
ImGui::TableNextColumn();
if (!entry.valueChildren.empty()) {
auto pos = ImGui::GetCursorPos();
char label[64];
std::snprintf(label, sizeof(label),
entry.valueChildrenMap ? "{...}##v_%s" : "[...]##v_%s",
entry.info.name.c_str());
valueChildrenOpen =
TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth |
ImGuiTreeNodeFlags_AllowItemOverlap);
// make it look like a normal label w/type
ImGui::SetCursorPos(pos);
ImGui::LabelText(entry.info.type_str.c_str(), "%s", "");
} else if (flags & NetworkTablesFlags_ReadOnly) {
EmitEntryValueReadonly(
entry,
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str(),
flags);
} else {
EmitEntryValueEditable(entry, flags);
}
2022-10-08 10:01:31 -07:00
if (flags & NetworkTablesFlags_ShowProperties) {
ImGui::TableNextColumn();
ImGui::Text("%s", entry.info.properties.c_str());
if (ImGui::BeginPopupContextItem(entry.info.name.c_str())) {
if (ImGui::Checkbox("persistent", &entry.persistent)) {
nt::SetTopicPersistent(entry.info.topic, entry.persistent);
}
if (ImGui::Checkbox("retained", &entry.retained)) {
if (entry.retained) {
nt::SetTopicProperty(entry.info.topic, "retained", true);
} else {
nt::DeleteTopicProperty(entry.info.topic, "retained");
}
}
ImGui::EndPopup();
}
}
if (flags & NetworkTablesFlags_ShowTimestamp) {
2022-10-08 10:01:31 -07:00
ImGui::TableNextColumn();
if (entry.value) {
2022-10-08 10:01:31 -07:00
ImGui::Text("%f", (entry.value.last_change() * 1.0e-6) -
(GetZeroTime() * 1.0e-6));
} else {
ImGui::TextUnformatted("");
}
}
2022-10-08 10:01:31 -07:00
if (flags & NetworkTablesFlags_ShowServerTimestamp) {
ImGui::TableNextColumn();
if (entry.value && entry.value.server_time() != 0) {
if (entry.value.server_time() == 1) {
ImGui::TextUnformatted("---");
} else {
ImGui::Text("%f", entry.value.server_time() * 1.0e-6);
}
} else {
ImGui::TextUnformatted("");
}
}
if (valueChildrenOpen) {
EmitValueTree(entry.valueChildren, flags);
TreePop();
}
}
2022-10-08 10:01:31 -07:00
static void EmitTree(NetworkTablesModel* model,
const std::vector<NetworkTablesModel::TreeNode>& tree,
NetworkTablesFlags flags, ShowCategory category,
bool root) {
for (auto&& node : tree) {
2022-10-08 10:01:31 -07:00
if (root && (flags & NetworkTablesFlags_ShowSpecial) == 0 &&
wpi::starts_with(node.name, '$')) {
continue;
}
if (node.entry) {
2022-10-08 10:01:31 -07:00
EmitEntry(model, *node.entry, node.name.c_str(), flags, category);
}
if (!node.children.empty()) {
2022-10-08 10:01:31 -07:00
ImGui::TableNextRow();
ImGui::TableNextColumn();
bool open =
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
2022-10-08 10:01:31 -07:00
EmitParentContextMenu(model, node.path, flags);
if (open) {
2022-10-08 10:01:31 -07:00
EmitTree(model, node.children, flags, category, false);
TreePop();
}
}
}
}
2022-10-08 10:01:31 -07:00
static void DisplayTable(NetworkTablesModel* model,
const std::vector<NetworkTablesModel::TreeNode>& tree,
NetworkTablesFlags flags, ShowCategory category) {
if (tree.empty()) {
return;
}
2022-10-08 10:01:31 -07:00
const bool showProperties = (flags & NetworkTablesFlags_ShowProperties);
const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
const bool showServerTimestamp =
(flags & NetworkTablesFlags_ShowServerTimestamp);
ImGui::BeginTable("values",
2 + (showProperties ? 1 : 0) + (showTimestamp ? 1 : 0) +
(showServerTimestamp ? 1 : 0),
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_BordersInner);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed,
0.35f * ImGui::GetWindowWidth());
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed,
12 * ImGui::GetFontSize());
if (showProperties) {
ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
12 * ImGui::GetFontSize());
}
if (showTimestamp) {
ImGui::TableSetupColumn("Time");
}
if (showServerTimestamp) {
ImGui::TableSetupColumn("Server Time");
}
ImGui::TableHeadersRow();
// EmitParentContextMenu(model, "/", flags);
if (flags & NetworkTablesFlags_TreeView) {
switch (category) {
case ShowPersistent:
PushID("persistent");
break;
case ShowRetained:
PushID("retained");
break;
case ShowTransitory:
PushID("transitory");
break;
default:
break;
}
EmitTree(model, tree, flags, category, true);
if (category != ShowAll) {
PopID();
}
} else {
for (auto entry : model->GetEntries()) {
if ((flags & NetworkTablesFlags_ShowSpecial) != 0 ||
!wpi::starts_with(entry->info.name, '$')) {
EmitEntry(model, *entry, entry->info.name.c_str(), flags, category);
}
}
2022-10-08 10:01:31 -07:00
}
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
static void DisplayClient(const NetworkTablesModel::Client& client) {
if (CollapsingHeader("Publishers")) {
ImGui::BeginTable("publishers", 2, ImGuiTableFlags_Resizable);
ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
10 * ImGui::GetFontSize());
ImGui::TableSetupColumn("Topic");
ImGui::TableHeadersRow();
for (auto&& pub : client.publishers) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%" PRId64, pub.uid);
ImGui::TableNextColumn();
ImGui::Text("%s", pub.topic.c_str());
}
2022-10-08 10:01:31 -07:00
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Subscribers")) {
ImGui::BeginTable(
"subscribers", 6,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp);
ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
10 * ImGui::GetFontSize());
ImGui::TableSetupColumn("Topics", ImGuiTableColumnFlags_WidthStretch, 6.0f);
ImGui::TableSetupColumn("Periodic", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableSetupColumn("Immediate", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableSetupColumn("Send All", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableSetupColumn("Prefix Match", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableHeadersRow();
for (auto&& sub : client.subscribers) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%" PRId64, sub.uid);
ImGui::TableNextColumn();
ImGui::Text("%s", sub.topicsStr.c_str());
ImGui::TableNextColumn();
ImGui::Text("%0.3f", sub.options.periodic);
ImGui::TableNextColumn();
ImGui::Text(sub.options.immediate ? "Yes" : "No");
ImGui::TableNextColumn();
ImGui::Text(sub.options.sendAll ? "Yes" : "No");
ImGui::TableNextColumn();
ImGui::Text(sub.options.prefixMatch ? "Yes" : "No");
}
ImGui::EndTable();
}
}
2022-10-08 10:01:31 -07:00
void glass::DisplayNetworkTablesInfo(NetworkTablesModel* model) {
auto inst = model->GetInstance();
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Connections")) {
ImGui::BeginTable("connections", 4, ImGuiTableFlags_Resizable);
ImGui::TableSetupColumn("Id");
ImGui::TableSetupColumn("Address");
ImGui::TableSetupColumn("Updated");
ImGui::TableSetupColumn("Proto");
ImGui::TableSetupScrollFreeze(1, 0);
ImGui::TableHeadersRow();
for (auto&& i : inst.GetConnections()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s", i.remote_id.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", i.remote_ip.c_str());
ImGui::TableNextColumn();
ImGui::Text("%llu",
static_cast<unsigned long long>( // NOLINT(runtime/int)
i.last_update));
ImGui::TableNextColumn();
ImGui::Text("%d.%d", i.protocol_version >> 8, i.protocol_version & 0xff);
}
2022-10-08 10:01:31 -07:00
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
auto netMode = inst.GetNetworkMode();
if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT4) {
if (CollapsingHeader("Server")) {
PushID("Server");
ImGui::Indent();
DisplayClient(model->GetServer());
ImGui::Unindent();
PopID();
}
if (CollapsingHeader("Clients")) {
ImGui::Indent();
for (auto&& client : model->GetClients()) {
if (CollapsingHeader(client.second.id.c_str())) {
PushID(client.second.id.c_str());
ImGui::Indent();
ImGui::Text("%s (version %s)", client.second.conn.c_str(),
client.second.version.c_str());
DisplayClient(client.second);
ImGui::Unindent();
PopID();
}
}
ImGui::Unindent();
}
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
void glass::DisplayNetworkTables(NetworkTablesModel* model,
NetworkTablesFlags flags) {
if (flags & NetworkTablesFlags_CombinedView) {
DisplayTable(model, model->GetTreeRoot(), flags, ShowAll);
} else {
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Persistent Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetPersistentTreeRoot(), flags,
ShowPersistent);
}
if (CollapsingHeader("Retained Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetRetainedTreeRoot(), flags, ShowRetained);
}
if (CollapsingHeader("Transitory Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetTransitoryTreeRoot(), flags,
ShowTransitory);
}
}
}
2021-03-16 22:05:41 -07:00
void NetworkTablesFlagsSettings::Update() {
if (!m_pTreeView) {
auto& storage = GetStorage();
m_pTreeView =
&storage.GetBool("tree", m_defaultFlags & NetworkTablesFlags_TreeView);
2022-10-08 10:01:31 -07:00
m_pCombinedView = &storage.GetBool(
"combined", m_defaultFlags & NetworkTablesFlags_CombinedView);
m_pShowSpecial = &storage.GetBool(
"special", m_defaultFlags & NetworkTablesFlags_ShowSpecial);
m_pShowProperties = &storage.GetBool(
"properties", m_defaultFlags & NetworkTablesFlags_ShowProperties);
m_pShowTimestamp = &storage.GetBool(
2021-03-16 22:05:41 -07:00
"timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
2022-10-08 10:01:31 -07:00
m_pShowServerTimestamp = &storage.GetBool(
"serverTimestamp",
m_defaultFlags & NetworkTablesFlags_ShowServerTimestamp);
m_pCreateNoncanonicalKeys = &storage.GetBool(
2021-03-16 22:05:41 -07:00
"createNonCanonical",
m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
m_pPrecision = &storage.GetInt(
"precision", (m_defaultFlags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift);
}
m_flags &= ~(
2022-10-08 10:01:31 -07:00
NetworkTablesFlags_TreeView | NetworkTablesFlags_CombinedView |
NetworkTablesFlags_ShowSpecial | NetworkTablesFlags_ShowProperties |
NetworkTablesFlags_ShowTimestamp |
NetworkTablesFlags_ShowServerTimestamp |
NetworkTablesFlags_CreateNoncanonicalKeys | NetworkTablesFlags_Precision);
m_flags |=
2021-03-16 22:05:41 -07:00
(*m_pTreeView ? NetworkTablesFlags_TreeView : 0) |
2022-10-08 10:01:31 -07:00
(*m_pCombinedView ? NetworkTablesFlags_CombinedView : 0) |
(*m_pShowSpecial ? NetworkTablesFlags_ShowSpecial : 0) |
(*m_pShowProperties ? NetworkTablesFlags_ShowProperties : 0) |
2021-03-16 22:05:41 -07:00
(*m_pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
2022-10-08 10:01:31 -07:00
(*m_pShowServerTimestamp ? NetworkTablesFlags_ShowServerTimestamp : 0) |
2021-03-16 22:05:41 -07:00
(*m_pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
: 0) |
(*m_pPrecision << kNetworkTablesFlags_PrecisionBitShift);
2021-03-16 22:05:41 -07:00
}
void NetworkTablesFlagsSettings::DisplayMenu() {
if (!m_pTreeView) {
return;
}
ImGui::MenuItem("Tree View", "", m_pTreeView);
2022-10-08 10:01:31 -07:00
ImGui::MenuItem("Combined View", "", m_pCombinedView);
ImGui::MenuItem("Show Special", "", m_pShowSpecial);
ImGui::MenuItem("Show Properties", "", m_pShowProperties);
2021-03-16 22:05:41 -07:00
ImGui::MenuItem("Show Timestamp", "", m_pShowTimestamp);
2022-10-08 10:01:31 -07:00
ImGui::MenuItem("Show Server Timestamp", "", m_pShowServerTimestamp);
if (ImGui::BeginMenu("Decimal Precision")) {
static const char* precisionOptions[] = {"1", "2", "3", "4", "5",
"6", "7", "8", "9", "10"};
for (int i = 1; i <= 10; i++) {
if (ImGui::MenuItem(precisionOptions[i - 1], nullptr,
i == *m_pPrecision)) {
*m_pPrecision = i;
}
}
ImGui::EndMenu();
}
2021-03-16 22:05:41 -07:00
ImGui::Separator();
ImGui::MenuItem("Allow creation of non-canonical keys", "",
m_pCreateNoncanonicalKeys);
}
void NetworkTablesView::Display() {
m_flags.Update();
if (ImGui::BeginPopupContextItem()) {
m_flags.DisplayMenu();
ImGui::EndPopup();
}
DisplayNetworkTables(m_model, m_flags.GetFlags());
}