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.
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
#include "glass/networktables/NetworkTables.h"
|
|
|
|
|
|
2020-12-26 14:31:24 -08:00
|
|
|
#include <networktables/NetworkTableValue.h>
|
|
|
|
|
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <cinttypes>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <initializer_list>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <ntcore_cpp.h>
|
|
|
|
|
#include <wpi/ArrayRef.h>
|
|
|
|
|
#include <wpi/Format.h>
|
|
|
|
|
#include <wpi/SmallString.h>
|
|
|
|
|
#include <wpi/StringRef.h>
|
|
|
|
|
#include <wpi/raw_ostream.h>
|
|
|
|
|
|
|
|
|
|
#include "glass/Context.h"
|
|
|
|
|
#include "glass/DataSource.h"
|
|
|
|
|
|
|
|
|
|
using namespace glass;
|
|
|
|
|
|
|
|
|
|
static std::string BooleanArrayToString(wpi::ArrayRef<int> in) {
|
|
|
|
|
std::string rv;
|
|
|
|
|
wpi::raw_string_ostream os{rv};
|
|
|
|
|
os << '[';
|
|
|
|
|
bool first = true;
|
|
|
|
|
for (auto v : in) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!first) {
|
|
|
|
|
os << ',';
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
first = false;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (v) {
|
2020-09-12 10:55:46 -07:00
|
|
|
os << "true";
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2020-09-12 10:55:46 -07:00
|
|
|
os << "false";
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
os << ']';
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string DoubleArrayToString(wpi::ArrayRef<double> in) {
|
|
|
|
|
std::string rv;
|
|
|
|
|
wpi::raw_string_ostream os{rv};
|
|
|
|
|
os << '[';
|
|
|
|
|
bool first = true;
|
|
|
|
|
for (auto v : in) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!first) {
|
|
|
|
|
os << ',';
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
first = false;
|
|
|
|
|
os << wpi::format("%.6f", v);
|
|
|
|
|
}
|
|
|
|
|
os << ']';
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string StringArrayToString(wpi::ArrayRef<std::string> in) {
|
|
|
|
|
std::string rv;
|
|
|
|
|
wpi::raw_string_ostream os{rv};
|
|
|
|
|
os << '[';
|
|
|
|
|
bool first = true;
|
|
|
|
|
for (auto&& v : in) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!first) {
|
|
|
|
|
os << ',';
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
first = false;
|
|
|
|
|
os << '"';
|
|
|
|
|
os.write_escaped(v);
|
|
|
|
|
os << '"';
|
|
|
|
|
}
|
|
|
|
|
os << ']';
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkTablesModel::NetworkTablesModel()
|
|
|
|
|
: NetworkTablesModel{nt::GetDefaultInstance()} {}
|
|
|
|
|
|
|
|
|
|
NetworkTablesModel::NetworkTablesModel(NT_Inst inst)
|
|
|
|
|
: m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {
|
|
|
|
|
nt::AddPolledEntryListener(m_poller, "",
|
|
|
|
|
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
|
|
|
|
|
NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
|
|
|
|
|
NT_NOTIFY_FLAGS | NT_NOTIFY_IMMEDIATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkTablesModel::~NetworkTablesModel() {
|
|
|
|
|
nt::DestroyEntryListenerPoller(m_poller);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkTablesModel::Entry::Entry(nt::EntryNotification&& event)
|
|
|
|
|
: entry{event.entry},
|
|
|
|
|
name{std::move(event.name)},
|
|
|
|
|
value{std::move(event.value)},
|
|
|
|
|
flags{nt::GetEntryFlags(event.entry)} {
|
|
|
|
|
UpdateValue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesModel::Entry::UpdateValue() {
|
|
|
|
|
switch (value->type()) {
|
|
|
|
|
case NT_BOOLEAN:
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!source) {
|
2020-09-12 10:55:46 -07:00
|
|
|
source = std::make_unique<DataSource>(wpi::Twine{"NT:"} + name);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
source->SetValue(value->GetBoolean() ? 1 : 0);
|
|
|
|
|
source->SetDigital(true);
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE:
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!source) {
|
2020-09-12 10:55:46 -07:00
|
|
|
source = std::make_unique<DataSource>(wpi::Twine{"NT:"} + name);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
source->SetValue(value->GetDouble());
|
|
|
|
|
source->SetDigital(false);
|
|
|
|
|
break;
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
|
|
|
|
valueStr = BooleanArrayToString(value->GetBooleanArray());
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
valueStr = DoubleArrayToString(value->GetDoubleArray());
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING_ARRAY:
|
|
|
|
|
valueStr = StringArrayToString(value->GetStringArray());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesModel::Update() {
|
|
|
|
|
bool timedOut = false;
|
|
|
|
|
bool updateTree = false;
|
|
|
|
|
for (auto&& event : nt::PollEntryListener(m_poller, 0, &timedOut)) {
|
|
|
|
|
auto& entry = m_entries[event.entry];
|
|
|
|
|
if (event.flags & NT_NOTIFY_NEW) {
|
|
|
|
|
if (!entry) {
|
|
|
|
|
entry = std::make_unique<Entry>(std::move(event));
|
|
|
|
|
m_sortedEntries.emplace_back(entry.get());
|
|
|
|
|
updateTree = true;
|
2020-12-28 13:02:24 -08:00
|
|
|
continue;
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!entry) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
if (event.flags & NT_NOTIFY_DELETE) {
|
|
|
|
|
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
|
|
|
|
|
entry.get());
|
|
|
|
|
// will be removed completely below
|
2020-12-28 12:58:06 -08:00
|
|
|
if (it != m_sortedEntries.end()) {
|
|
|
|
|
*it = nullptr;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
m_entries.erase(event.entry);
|
|
|
|
|
updateTree = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (event.flags & NT_NOTIFY_UPDATE) {
|
|
|
|
|
entry->value = std::move(event.value);
|
|
|
|
|
entry->UpdateValue();
|
|
|
|
|
}
|
|
|
|
|
if (event.flags & NT_NOTIFY_FLAGS) {
|
|
|
|
|
entry->flags = nt::GetEntryFlags(event.entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// shortcut common case (updates)
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!updateTree) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
// remove deleted entries
|
|
|
|
|
m_sortedEntries.erase(
|
|
|
|
|
std::remove(m_sortedEntries.begin(), m_sortedEntries.end(), nullptr),
|
|
|
|
|
m_sortedEntries.end());
|
|
|
|
|
|
|
|
|
|
// sort by name
|
|
|
|
|
std::sort(m_sortedEntries.begin(), m_sortedEntries.end(),
|
|
|
|
|
[](const auto& a, const auto& b) { return a->name < b->name; });
|
|
|
|
|
|
|
|
|
|
// rebuild tree
|
|
|
|
|
m_root.clear();
|
|
|
|
|
wpi::SmallVector<wpi::StringRef, 16> parts;
|
|
|
|
|
for (auto& entry : m_sortedEntries) {
|
|
|
|
|
parts.clear();
|
|
|
|
|
wpi::StringRef{entry->name}.split(parts, '/', -1, false);
|
|
|
|
|
|
|
|
|
|
// get to leaf
|
|
|
|
|
auto nodes = &m_root;
|
|
|
|
|
for (auto part : wpi::ArrayRef(parts.begin(), parts.end()).drop_back()) {
|
|
|
|
|
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
|
|
|
|
|
// entry->name
|
|
|
|
|
nodes->back().path.assign(entry->name.data(),
|
|
|
|
|
part.end() - entry->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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
bool NetworkTablesModel::Exists() {
|
|
|
|
|
return nt::IsConnected(m_inst);
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
static std::shared_ptr<nt::Value> StringToBooleanArray(wpi::StringRef in) {
|
|
|
|
|
in = in.trim();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (in.empty()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
return nt::NetworkTableValue::MakeBooleanArray(
|
|
|
|
|
std::initializer_list<bool>{});
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
|
|
|
|
if (in.front() == '[') {
|
|
|
|
|
in = in.drop_front();
|
|
|
|
|
}
|
|
|
|
|
if (in.back() == ']') {
|
|
|
|
|
in = in.drop_back();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
in = in.trim();
|
|
|
|
|
|
|
|
|
|
wpi::SmallVector<wpi::StringRef, 16> inSplit;
|
|
|
|
|
wpi::SmallVector<int, 16> out;
|
|
|
|
|
|
|
|
|
|
in.split(inSplit, ',', -1, false);
|
|
|
|
|
for (auto val : inSplit) {
|
|
|
|
|
val = val.trim();
|
|
|
|
|
if (val.equals_lower("true")) {
|
|
|
|
|
out.emplace_back(1);
|
|
|
|
|
} else if (val.equals_lower("false")) {
|
|
|
|
|
out.emplace_back(0);
|
|
|
|
|
} else {
|
|
|
|
|
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
|
|
|
|
|
<< "'\n";
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nt::NetworkTableValue::MakeBooleanArray(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::shared_ptr<nt::Value> StringToDoubleArray(wpi::StringRef in) {
|
|
|
|
|
in = in.trim();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (in.empty()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
return nt::NetworkTableValue::MakeBooleanArray(
|
|
|
|
|
std::initializer_list<bool>{});
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
|
|
|
|
if (in.front() == '[') {
|
|
|
|
|
in = in.drop_front();
|
|
|
|
|
}
|
|
|
|
|
if (in.back() == ']') {
|
|
|
|
|
in = in.drop_back();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
in = in.trim();
|
|
|
|
|
|
|
|
|
|
wpi::SmallVector<wpi::StringRef, 16> inSplit;
|
|
|
|
|
wpi::SmallVector<double, 16> out;
|
|
|
|
|
|
|
|
|
|
in.split(inSplit, ',', -1, false);
|
|
|
|
|
for (auto val : inSplit) {
|
|
|
|
|
val = val.trim();
|
|
|
|
|
wpi::SmallString<32> valStr = val;
|
|
|
|
|
double d;
|
|
|
|
|
if (std::sscanf(valStr.c_str(), "%lf", &d) == 1) {
|
|
|
|
|
out.emplace_back(d);
|
|
|
|
|
} else {
|
|
|
|
|
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
|
|
|
|
|
<< "'\n";
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nt::NetworkTableValue::MakeDoubleArray(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int fromxdigit(char ch) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ch >= 'a' && ch <= 'f') {
|
2020-09-12 10:55:46 -07:00
|
|
|
return (ch - 'a' + 10);
|
2020-12-28 12:58:06 -08:00
|
|
|
} else if (ch >= 'A' && ch <= 'F') {
|
2020-09-12 10:55:46 -07:00
|
|
|
return (ch - 'A' + 10);
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2020-09-12 10:55:46 -07:00
|
|
|
return ch - '0';
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static wpi::StringRef UnescapeString(wpi::StringRef 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 wpi::StringRef{buf.data(), buf.size()};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::shared_ptr<nt::Value> StringToStringArray(wpi::StringRef in) {
|
|
|
|
|
in = in.trim();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (in.empty()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
return nt::NetworkTableValue::MakeStringArray(
|
|
|
|
|
std::initializer_list<std::string>{});
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
|
|
|
|
if (in.front() == '[') {
|
|
|
|
|
in = in.drop_front();
|
|
|
|
|
}
|
|
|
|
|
if (in.back() == ']') {
|
|
|
|
|
in = in.drop_back();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
in = in.trim();
|
|
|
|
|
|
|
|
|
|
wpi::SmallVector<wpi::StringRef, 16> inSplit;
|
|
|
|
|
std::vector<std::string> out;
|
|
|
|
|
wpi::SmallString<32> buf;
|
|
|
|
|
|
|
|
|
|
in.split(inSplit, ',', -1, false);
|
|
|
|
|
for (auto val : inSplit) {
|
|
|
|
|
val = val.trim();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (val.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
if (val.front() != '"' || val.back() != '"') {
|
|
|
|
|
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
|
|
|
|
|
<< "'\n";
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
out.emplace_back(UnescapeString(val, buf));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nt::NetworkTableValue::MakeStringArray(std::move(out));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void EmitEntryValueReadonly(NetworkTablesModel::Entry& entry) {
|
|
|
|
|
auto& val = entry.value;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!val) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
switch (val->type()) {
|
|
|
|
|
case NT_BOOLEAN:
|
|
|
|
|
ImGui::LabelText("boolean", "%s", val->GetBoolean() ? "true" : "false");
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE:
|
|
|
|
|
ImGui::LabelText("double", "%.6f", val->GetDouble());
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING: {
|
|
|
|
|
// GetString() comes from a std::string, so it's null terminated
|
|
|
|
|
ImGui::LabelText("string", "%s", val->GetString().data());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
|
|
|
|
ImGui::LabelText("boolean[]", "%s", entry.valueStr.c_str());
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
ImGui::LabelText("double[]", "%s", entry.valueStr.c_str());
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING_ARRAY:
|
|
|
|
|
ImGui::LabelText("string[]", "%s", entry.valueStr.c_str());
|
|
|
|
|
break;
|
|
|
|
|
case NT_RAW:
|
|
|
|
|
ImGui::LabelText("raw", "[...]");
|
|
|
|
|
break;
|
|
|
|
|
case NT_RPC:
|
|
|
|
|
ImGui::LabelText("rpc", "[...]");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ImGui::LabelText("other", "?");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static constexpr size_t kTextBufferSize = 4096;
|
|
|
|
|
|
|
|
|
|
static char* GetTextBuffer(wpi::StringRef 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) {
|
|
|
|
|
auto& val = entry.value;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!val) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
ImGui::PushID(entry.name.c_str());
|
|
|
|
|
switch (val->type()) {
|
|
|
|
|
case NT_BOOLEAN: {
|
|
|
|
|
static const char* boolOptions[] = {"false", "true"};
|
|
|
|
|
int v = val->GetBoolean() ? 1 : 0;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (ImGui::Combo("boolean", &v, boolOptions, 2)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeBoolean(v));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_DOUBLE: {
|
|
|
|
|
double v = val->GetDouble();
|
|
|
|
|
if (ImGui::InputDouble("double", &v, 0, 0, "%.6f",
|
2020-12-28 12:58:06 -08:00
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeDouble(v));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_STRING: {
|
|
|
|
|
char* v = GetTextBuffer(val->GetString());
|
|
|
|
|
if (ImGui::InputText("string", v, kTextBufferSize,
|
2020-12-28 12:58:06 -08:00
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeString(v));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_BOOLEAN_ARRAY: {
|
|
|
|
|
char* v = GetTextBuffer(entry.valueStr);
|
|
|
|
|
if (ImGui::InputText("boolean[]", v, kTextBufferSize,
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (auto outv = StringToBooleanArray(v)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, std::move(outv));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_DOUBLE_ARRAY: {
|
|
|
|
|
char* v = GetTextBuffer(entry.valueStr);
|
|
|
|
|
if (ImGui::InputText("double[]", v, kTextBufferSize,
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (auto outv = StringToDoubleArray(v)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, std::move(outv));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_STRING_ARRAY: {
|
|
|
|
|
char* v = GetTextBuffer(entry.valueStr);
|
|
|
|
|
if (ImGui::InputText("string[]", v, kTextBufferSize,
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (auto outv = StringToStringArray(v)) {
|
2020-09-12 10:55:46 -07:00
|
|
|
nt::SetEntryValue(entry.entry, std::move(outv));
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_RAW:
|
|
|
|
|
ImGui::LabelText("raw", "[...]");
|
|
|
|
|
break;
|
|
|
|
|
case NT_RPC:
|
|
|
|
|
ImGui::LabelText("rpc", "[...]");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ImGui::LabelText("other", "?");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void EmitEntry(NetworkTablesModel::Entry& entry, const char* name,
|
|
|
|
|
NetworkTablesFlags flags) {
|
|
|
|
|
if (entry.source) {
|
|
|
|
|
ImGui::Selectable(name);
|
|
|
|
|
entry.source->EmitDrag();
|
|
|
|
|
} else {
|
|
|
|
|
ImGui::Text("%s", name);
|
|
|
|
|
}
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
if (flags & NetworkTablesFlags_ReadOnly) {
|
2020-09-12 10:55:46 -07:00
|
|
|
EmitEntryValueReadonly(entry);
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2020-09-12 10:55:46 -07:00
|
|
|
EmitEntryValueEditable(entry);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::NextColumn();
|
|
|
|
|
|
|
|
|
|
if (flags & NetworkTablesFlags_ShowFlags) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if ((entry.flags & NT_PERSISTENT) != 0) {
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::Text("Persistent");
|
2020-12-28 12:58:06 -08:00
|
|
|
} else if (entry.flags != 0) {
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::Text("%02x", entry.flags);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & NetworkTablesFlags_ShowTimestamp) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (entry.value) {
|
2020-12-17 07:29:00 -08:00
|
|
|
ImGui::Text("%f", (entry.value->last_change() * 1.0e-6) -
|
|
|
|
|
(GetZeroTime() * 1.0e-6));
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::TextUnformatted("");
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void EmitTree(const std::vector<NetworkTablesModel::TreeNode>& tree,
|
|
|
|
|
NetworkTablesFlags flags) {
|
|
|
|
|
for (auto&& node : tree) {
|
|
|
|
|
if (node.entry) {
|
|
|
|
|
EmitEntry(*node.entry, node.name.c_str(), flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!node.children.empty()) {
|
|
|
|
|
bool open =
|
|
|
|
|
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::NextColumn();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (flags & NetworkTablesFlags_ShowFlags) {
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
if (flags & NetworkTablesFlags_ShowTimestamp) {
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::Separator();
|
|
|
|
|
if (open) {
|
|
|
|
|
EmitTree(node.children, flags);
|
|
|
|
|
TreePop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glass::DisplayNetworkTables(NetworkTablesModel* model,
|
|
|
|
|
NetworkTablesFlags flags) {
|
|
|
|
|
auto inst = model->GetInstance();
|
|
|
|
|
|
|
|
|
|
if (flags & NetworkTablesFlags_ShowConnections) {
|
|
|
|
|
if (CollapsingHeader("Connections")) {
|
|
|
|
|
ImGui::Columns(4, "connections");
|
|
|
|
|
ImGui::Text("Id");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("Address");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("Updated");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("Proto");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
for (auto&& i : nt::GetConnections(inst)) {
|
|
|
|
|
ImGui::Text("%s", i.remote_id.c_str());
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("%s", i.remote_ip.c_str());
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("%llu",
|
|
|
|
|
static_cast<unsigned long long>( // NOLINT(runtime/int)
|
|
|
|
|
i.last_update));
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("%d.%d", i.protocol_version >> 8,
|
|
|
|
|
i.protocol_version & 0xff);
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
ImGui::Columns();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!CollapsingHeader("Values", ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool showFlags = (flags & NetworkTablesFlags_ShowFlags);
|
|
|
|
|
const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
|
|
|
|
|
|
|
|
|
|
static bool first = true;
|
|
|
|
|
ImGui::Columns(2 + (showFlags ? 1 : 0) + (showTimestamp ? 1 : 0), "values");
|
2020-12-28 12:58:06 -08:00
|
|
|
if (first) {
|
|
|
|
|
ImGui::SetColumnWidth(-1, 0.5f * ImGui::GetWindowWidth());
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::Text("Name");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
ImGui::Text("Value");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
if (showFlags) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (first) {
|
|
|
|
|
ImGui::SetColumnWidth(-1, 12 * ImGui::GetFontSize());
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
ImGui::Text("Flags");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
if (showTimestamp) {
|
|
|
|
|
ImGui::Text("Changed");
|
|
|
|
|
ImGui::NextColumn();
|
|
|
|
|
}
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
first = false;
|
|
|
|
|
|
|
|
|
|
if (flags & NetworkTablesFlags_TreeView) {
|
|
|
|
|
EmitTree(model->GetTreeRoot(), flags);
|
|
|
|
|
} else {
|
|
|
|
|
for (auto entry : model->GetEntries()) {
|
|
|
|
|
EmitEntry(*entry, entry->name.c_str(), flags);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ImGui::Columns();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesView::Display() {
|
|
|
|
|
if (ImGui::BeginPopupContextItem()) {
|
|
|
|
|
auto& storage = GetStorage();
|
|
|
|
|
auto pTreeView = storage.GetBoolRef(
|
|
|
|
|
"tree", m_defaultFlags & NetworkTablesFlags_TreeView);
|
|
|
|
|
auto pShowConnections = storage.GetBoolRef(
|
|
|
|
|
"connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
|
|
|
|
|
auto pShowFlags = storage.GetBoolRef(
|
|
|
|
|
"flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
|
|
|
|
|
auto pShowTimestamp = storage.GetBoolRef(
|
|
|
|
|
"timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
|
|
|
|
|
|
|
|
|
|
ImGui::MenuItem("Tree View", "", pTreeView);
|
|
|
|
|
ImGui::MenuItem("Show Connections", "", pShowConnections);
|
|
|
|
|
ImGui::MenuItem("Show Flags", "", pShowFlags);
|
|
|
|
|
ImGui::MenuItem("Show Timestamp", "", pShowTimestamp);
|
|
|
|
|
|
|
|
|
|
m_flags &=
|
|
|
|
|
~(NetworkTablesFlags_TreeView | NetworkTablesFlags_ShowConnections |
|
|
|
|
|
NetworkTablesFlags_ShowFlags | NetworkTablesFlags_ShowTimestamp);
|
|
|
|
|
m_flags |= (*pTreeView ? NetworkTablesFlags_TreeView : 0) |
|
|
|
|
|
(*pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
|
|
|
|
|
(*pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
|
|
|
|
|
(*pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0);
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DisplayNetworkTables(m_model, m_flags);
|
|
|
|
|
}
|