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"
|
|
|
|
|
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <algorithm>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <cinttypes>
|
2023-06-08 19:59:54 -07:00
|
|
|
#include <concepts>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <cstring>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <functional>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <initializer_list>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <map>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <memory>
|
2022-10-15 16:33:14 -07:00
|
|
|
#include <span>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <string>
|
2021-06-06 16:13:58 -07:00
|
|
|
#include <string_view>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <utility>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <vector>
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
#include <fmt/format.h>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <imgui.h>
|
2023-10-10 00:28:54 -07:00
|
|
|
#include <imgui_stdlib.h>
|
2022-10-08 10:01:31 -07:00
|
|
|
#include <networktables/NetworkTableInstance.h>
|
|
|
|
|
#include <networktables/NetworkTableValue.h>
|
|
|
|
|
#include <ntcore_c.h>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <ntcore_cpp.h>
|
2022-10-08 10:01:31 -07:00
|
|
|
#include <ntcore_cpp_types.h>
|
|
|
|
|
#include <wpi/MessagePack.h>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <wpi/SmallString.h>
|
2021-06-06 19:51:14 -07:00
|
|
|
#include <wpi/SpanExtras.h>
|
2021-06-06 16:13:58 -07:00
|
|
|
#include <wpi/StringExtras.h>
|
2022-10-08 10:01:31 -07:00
|
|
|
#include <wpi/mpack.h>
|
2024-05-12 06:25:42 -07:00
|
|
|
#include <wpi/print.h>
|
2020-09-12 10:55:46 -07:00
|
|
|
#include <wpi/raw_ostream.h>
|
|
|
|
|
|
2024-09-12 23:44:19 -07:00
|
|
|
#ifndef NO_PROTOBUF
|
|
|
|
|
#include <google/protobuf/descriptor.h>
|
|
|
|
|
#include <google/protobuf/message.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-09-12 10:55:46 -07:00
|
|
|
#include "glass/Context.h"
|
|
|
|
|
#include "glass/DataSource.h"
|
[glass] Use JSON files for storage instead of imgui ini
Storage is now nested.
Separate "roots" can be configured which save to separate files.
In particular, this is used to save wpigui and ImGui window position
to a -window.json file.
ImGui's ini (for window position) is mapped to JSON.
You can optionally specify a directory to load from on the command line.
If one isn't provided, it uses the global system directory.
Any changes made are automatically saved here.
Workspace | Open: select directory, the current layout is replaced with that
workspace, and future auto-saves also switch to that location. The main
window size/location is not changed, only the contents.
Workspace | Save As: select directory, the current layout is saved there,
and future auto-saves also switch to that location.
Workspace | Reset: window locations are preserved, but all other settings
are reset to default (including e.g. removing plot windows). This will also
end up clearing the current save file. as with load, the main window
size/location is not changed.
Workspace | Save As Global: "save as" to the global system location
Notably, the main window size/location is only loaded at startup, but is
auto-saved as part of the current workspace.
2021-11-25 00:51:00 -08:00
|
|
|
#include "glass/Storage.h"
|
2024-04-28 14:03:49 -05:00
|
|
|
#include "glass/support/ExtraGuiWidgets.h"
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
static std::string StringArrayToString(std::span<const std::string> in) {
|
2020-09-12 10:55:46 -07:00
|
|
|
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()
|
2022-10-08 10:01:31 -07:00
|
|
|
: NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
|
|
|
|
|
|
|
|
|
|
NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
|
2022-10-31 21:52:14 -07:00
|
|
|
: m_inst{inst}, m_poller{inst} {
|
|
|
|
|
m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic |
|
|
|
|
|
nt::EventFlags::kValueAll |
|
|
|
|
|
nt::EventFlags::kImmediate);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
NetworkTablesModel::Entry::~Entry() {
|
|
|
|
|
if (publisher != 0) {
|
|
|
|
|
nt::Unpublish(publisher);
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-19 21:41:47 -07:00
|
|
|
static void UpdateMsgpackValueSource(NetworkTablesModel& model,
|
|
|
|
|
NetworkTablesModel::ValueSource* out,
|
2022-10-08 10:01:31 -07:00
|
|
|
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:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeBoolean(mpack_tag_bool_value(&tag), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case mpack::mpack_type_int:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeInteger(mpack_tag_int_value(&tag), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case mpack::mpack_type_uint:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeInteger(mpack_tag_uint_value(&tag), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case mpack::mpack_type_float:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeFloat(mpack_tag_float_value(&tag), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case mpack::mpack_type_double:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeDouble(mpack_tag_double_value(&tag), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case mpack::mpack_type_str: {
|
|
|
|
|
std::string str;
|
|
|
|
|
mpack_read_str(&r, &tag, &str);
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeString(std::move(str), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
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;
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateMsgpackValueSource(model, &child, r, child.path,
|
|
|
|
|
time); // recurse
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
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];
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateMsgpackValueSource(model, &child, r, child.path, time);
|
2022-10-08 10:01:31 -07:00
|
|
|
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);
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateMsgpackValueSource(model, &child, r, child.path, time);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-19 21:41:47 -07:00
|
|
|
static void UpdateStructValueSource(NetworkTablesModel& model,
|
|
|
|
|
NetworkTablesModel::ValueSource* out,
|
|
|
|
|
const wpi::DynamicStruct& s,
|
|
|
|
|
std::string_view name, int64_t time) {
|
|
|
|
|
auto desc = s.GetDescriptor();
|
|
|
|
|
out->typeStr = "struct:" + desc->GetName();
|
|
|
|
|
auto& fields = desc->GetFields();
|
|
|
|
|
if (!out->valueChildrenMap || fields.size() != out->valueChildren.size()) {
|
|
|
|
|
out->valueChildren.clear();
|
|
|
|
|
out->valueChildrenMap = true;
|
|
|
|
|
out->valueChildren.reserve(fields.size());
|
|
|
|
|
for (auto&& field : fields) {
|
|
|
|
|
out->valueChildren.emplace_back();
|
|
|
|
|
auto& child = out->valueChildren.back();
|
|
|
|
|
child.name = field.GetName();
|
|
|
|
|
child.path = fmt::format("{}/{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
auto outIt = out->valueChildren.begin();
|
|
|
|
|
for (auto&& field : fields) {
|
|
|
|
|
auto& child = *outIt++;
|
|
|
|
|
switch (field.GetType()) {
|
|
|
|
|
case wpi::StructFieldType::kBool:
|
|
|
|
|
if (field.IsArray()) {
|
|
|
|
|
std::vector<int> v;
|
|
|
|
|
v.reserve(field.GetArraySize());
|
|
|
|
|
for (size_t i = 0; i < field.GetArraySize(); ++i) {
|
|
|
|
|
v.emplace_back(s.GetBoolField(&field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeBooleanArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeBoolean(s.GetBoolField(&field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case wpi::StructFieldType::kChar:
|
|
|
|
|
child.value = nt::Value::MakeString(s.GetStringField(&field), time);
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case wpi::StructFieldType::kInt8:
|
|
|
|
|
case wpi::StructFieldType::kInt16:
|
|
|
|
|
case wpi::StructFieldType::kInt32:
|
|
|
|
|
case wpi::StructFieldType::kInt64:
|
|
|
|
|
case wpi::StructFieldType::kUint8:
|
|
|
|
|
case wpi::StructFieldType::kUint16:
|
|
|
|
|
case wpi::StructFieldType::kUint32:
|
|
|
|
|
case wpi::StructFieldType::kUint64: {
|
|
|
|
|
bool isUint = field.IsUint();
|
|
|
|
|
if (field.IsArray()) {
|
|
|
|
|
std::vector<int64_t> v;
|
|
|
|
|
v.reserve(field.GetArraySize());
|
|
|
|
|
for (size_t i = 0; i < field.GetArraySize(); ++i) {
|
|
|
|
|
if (isUint) {
|
|
|
|
|
v.emplace_back(s.GetUintField(&field, i));
|
|
|
|
|
} else {
|
|
|
|
|
v.emplace_back(s.GetIntField(&field, i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeIntegerArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
if (isUint) {
|
|
|
|
|
child.value = nt::Value::MakeInteger(s.GetUintField(&field), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeInteger(s.GetIntField(&field), time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case wpi::StructFieldType::kFloat:
|
|
|
|
|
if (field.IsArray()) {
|
|
|
|
|
std::vector<float> v;
|
|
|
|
|
v.reserve(field.GetArraySize());
|
|
|
|
|
for (size_t i = 0; i < field.GetArraySize(); ++i) {
|
|
|
|
|
v.emplace_back(s.GetFloatField(&field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeFloatArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeFloat(s.GetFloatField(&field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case wpi::StructFieldType::kDouble:
|
|
|
|
|
if (field.IsArray()) {
|
|
|
|
|
std::vector<double> v;
|
|
|
|
|
v.reserve(field.GetArraySize());
|
|
|
|
|
for (size_t i = 0; i < field.GetArraySize(); ++i) {
|
|
|
|
|
v.emplace_back(s.GetDoubleField(&field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeDoubleArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeDouble(s.GetDoubleField(&field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case wpi::StructFieldType::kStruct:
|
|
|
|
|
if (field.IsArray()) {
|
|
|
|
|
if (child.valueChildrenMap) {
|
|
|
|
|
child.valueChildren.clear();
|
|
|
|
|
child.valueChildrenMap = false;
|
|
|
|
|
}
|
|
|
|
|
child.valueChildren.resize(field.GetArraySize());
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& child2 : child.valueChildren) {
|
|
|
|
|
if (child2.name.empty()) {
|
|
|
|
|
child2.name = fmt::format("[{}]", i);
|
|
|
|
|
child2.path = fmt::format("{}{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
UpdateStructValueSource(model, &child2, s.GetStructField(&field, i),
|
|
|
|
|
child2.path, time); // recurse
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
UpdateStructValueSource(model, &child, s.GetStructField(&field),
|
|
|
|
|
child.path, time); // recurse
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 23:44:19 -07:00
|
|
|
#ifndef NO_PROTOBUF
|
2023-10-19 21:41:47 -07:00
|
|
|
static void UpdateProtobufValueSource(NetworkTablesModel& model,
|
|
|
|
|
NetworkTablesModel::ValueSource* out,
|
|
|
|
|
const google::protobuf::Message& msg,
|
|
|
|
|
std::string_view name, int64_t time) {
|
|
|
|
|
auto desc = msg.GetDescriptor();
|
|
|
|
|
out->typeStr = "proto:" + desc->full_name();
|
|
|
|
|
if (!out->valueChildrenMap ||
|
|
|
|
|
desc->field_count() != static_cast<int>(out->valueChildren.size())) {
|
|
|
|
|
out->valueChildren.clear();
|
|
|
|
|
out->valueChildrenMap = true;
|
|
|
|
|
out->valueChildren.reserve(desc->field_count());
|
|
|
|
|
for (int i = 0, end = desc->field_count(); i < end; ++i) {
|
|
|
|
|
out->valueChildren.emplace_back();
|
|
|
|
|
auto& child = out->valueChildren.back();
|
|
|
|
|
child.name = desc->field(i)->name();
|
|
|
|
|
child.path = fmt::format("{}/{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
auto refl = msg.GetReflection();
|
|
|
|
|
auto outIt = out->valueChildren.begin();
|
|
|
|
|
for (int fieldNum = 0, end = desc->field_count(); fieldNum < end;
|
|
|
|
|
++fieldNum) {
|
|
|
|
|
auto field = desc->field(fieldNum);
|
|
|
|
|
auto& child = *outIt++;
|
|
|
|
|
switch (field->cpp_type()) {
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<int> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedBool(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeBooleanArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeBoolean(refl->GetBool(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<std::string> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedString(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeStringArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeString(refl->GetString(msg, field), time);
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<int64_t> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedInt32(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeIntegerArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeInteger(refl->GetInt32(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<int64_t> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedInt64(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeIntegerArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeInteger(refl->GetInt64(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<int64_t> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedUInt32(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeIntegerArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeInteger(refl->GetUInt32(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<int64_t> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedUInt64(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeIntegerArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeInteger(refl->GetUInt64(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<float> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedFloat(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeFloatArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value = nt::Value::MakeFloat(refl->GetFloat(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<double> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedDouble(msg, field, i));
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeDoubleArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeDouble(refl->GetDouble(msg, field), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
std::vector<std::string> v;
|
|
|
|
|
v.reserve(size);
|
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
|
v.emplace_back(refl->GetRepeatedEnum(msg, field, i)->name());
|
|
|
|
|
}
|
|
|
|
|
child.value = nt::Value::MakeStringArray(std::move(v), time);
|
|
|
|
|
} else {
|
|
|
|
|
child.value =
|
|
|
|
|
nt::Value::MakeString(refl->GetEnum(msg, field)->name(), time);
|
|
|
|
|
}
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
|
|
|
|
break;
|
|
|
|
|
case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
|
|
|
|
|
if (field->is_repeated()) {
|
|
|
|
|
if (child.valueChildrenMap) {
|
|
|
|
|
child.valueChildren.clear();
|
|
|
|
|
child.valueChildrenMap = false;
|
|
|
|
|
}
|
|
|
|
|
size_t size = refl->FieldSize(msg, field);
|
|
|
|
|
child.valueChildren.resize(size);
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& child2 : child.valueChildren) {
|
|
|
|
|
if (child2.name.empty()) {
|
|
|
|
|
child2.name = fmt::format("[{}]", i);
|
|
|
|
|
child2.path = fmt::format("{}{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
UpdateProtobufValueSource(model, &child2,
|
|
|
|
|
refl->GetRepeatedMessage(msg, field, i),
|
|
|
|
|
child2.path, time); // recurse
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
UpdateProtobufValueSource(
|
|
|
|
|
model, &child,
|
|
|
|
|
refl->GetMessage(msg, field,
|
|
|
|
|
model.GetProtobufDatabase().GetMessageFactory()),
|
|
|
|
|
child.path, time); // recurse
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-12 23:44:19 -07:00
|
|
|
#endif
|
2023-10-19 21:41:47 -07:00
|
|
|
|
|
|
|
|
static void UpdateJsonValueSource(NetworkTablesModel& model,
|
|
|
|
|
NetworkTablesModel::ValueSource* out,
|
2022-10-08 10:01:31 -07:00
|
|
|
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];
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateJsonValueSource(model, &child, kv.value(), child.path, time);
|
2022-10-08 10:01:31 -07:00
|
|
|
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);
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateJsonValueSource(model, &child, kv.value(), child.path, time);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
// recurse
|
|
|
|
|
UpdateJsonValueSource(model, &child, j[i++], child.path, time);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case wpi::json::value_t::string:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeString(j.get_ref<const std::string&>(), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case wpi::json::value_t::boolean:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeBoolean(j.get<bool>(), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case wpi::json::value_t::number_integer:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeInteger(j.get<int64_t>(), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case wpi::json::value_t::number_unsigned:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeInteger(j.get<uint64_t>(), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case wpi::json::value_t::number_float:
|
2023-10-19 21:41:47 -07:00
|
|
|
out->value = nt::Value::MakeDouble(j.get<double>(), time);
|
|
|
|
|
out->UpdateFromValue(model, name, "");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
out->value = {};
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-10 00:28:54 -07:00
|
|
|
void NetworkTablesModel::ValueSource::UpdateDiscreteSource(
|
|
|
|
|
std::string_view name, double value, int64_t time, bool digital) {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
if (!source) {
|
|
|
|
|
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
|
|
|
|
|
}
|
|
|
|
|
source->SetValue(value, time);
|
|
|
|
|
source->SetDigital(digital);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T, typename MakeValue>
|
|
|
|
|
void NetworkTablesModel::ValueSource::UpdateDiscreteArray(
|
|
|
|
|
std::string_view name, std::span<const T> arr, int64_t time,
|
|
|
|
|
MakeValue makeValue, bool digital) {
|
|
|
|
|
if (valueChildrenMap) {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
valueChildrenMap = false;
|
|
|
|
|
}
|
|
|
|
|
valueChildren.resize(arr.size());
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& child : valueChildren) {
|
|
|
|
|
if (child.name.empty()) {
|
|
|
|
|
child.name = fmt::format("[{}]", i);
|
|
|
|
|
child.path = fmt::format("{}{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
child.value = makeValue(arr[i], time);
|
|
|
|
|
child.UpdateDiscreteSource(child.path, arr[i], time, digital);
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void NetworkTablesModel::ValueSource::UpdateFromValue(
|
2023-10-19 21:41:47 -07:00
|
|
|
NetworkTablesModel& model, std::string_view name,
|
|
|
|
|
std::string_view typeStr) {
|
2022-10-08 10:01:31 -07:00
|
|
|
switch (value.type()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_BOOLEAN:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteSource(name, value.GetBoolean() ? 1 : 0, value.time(),
|
|
|
|
|
true);
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2022-10-08 10:01:31 -07:00
|
|
|
case NT_INTEGER:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteSource(name, value.GetInteger(), value.time());
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case NT_FLOAT:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteSource(name, value.GetFloat(), value.time());
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_DOUBLE:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteSource(name, value.GetDouble(), value.time());
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteArray(name, value.GetBooleanArray(), value.time(),
|
|
|
|
|
nt::Value::MakeBoolean, true);
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case NT_INTEGER_ARRAY:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteArray(name, value.GetIntegerArray(), value.time(),
|
|
|
|
|
nt::Value::MakeInteger);
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case NT_FLOAT_ARRAY:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteArray(name, value.GetFloatArray(), value.time(),
|
|
|
|
|
nt::Value::MakeFloat);
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
2023-10-10 00:28:54 -07:00
|
|
|
UpdateDiscreteArray(name, value.GetDoubleArray(), value.time(),
|
|
|
|
|
nt::Value::MakeDouble);
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_STRING_ARRAY: {
|
|
|
|
|
auto arr = value.GetStringArray();
|
|
|
|
|
if (valueChildrenMap) {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
valueChildrenMap = false;
|
|
|
|
|
}
|
|
|
|
|
valueChildren.resize(arr.size());
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& child : valueChildren) {
|
|
|
|
|
if (child.name.empty()) {
|
|
|
|
|
child.name = fmt::format("[{}]", i);
|
|
|
|
|
child.path = fmt::format("{}{}", name, child.name);
|
|
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
child.value = nt::Value::MakeString(arr[i++], value.time());
|
|
|
|
|
child.UpdateFromValue(model, child.path, "");
|
2023-10-10 00:28:54 -07:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
case NT_STRING:
|
|
|
|
|
if (typeStr == "json") {
|
|
|
|
|
try {
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateJsonValueSource(model, this,
|
|
|
|
|
wpi::json::parse(value.GetString()), name,
|
2022-10-08 10:01:31 -07:00
|
|
|
value.last_change());
|
|
|
|
|
} catch (wpi::json::exception&) {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
valueChildren.clear();
|
2023-02-17 18:01:54 -08:00
|
|
|
valueStr.clear();
|
|
|
|
|
wpi::raw_string_ostream os{valueStr};
|
|
|
|
|
os << '"';
|
|
|
|
|
os.write_escaped(value.GetString());
|
|
|
|
|
os << '"';
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case NT_RAW:
|
|
|
|
|
if (typeStr == "msgpack") {
|
|
|
|
|
mpack_reader_t r;
|
|
|
|
|
mpack_reader_init_data(&r, value.GetRaw());
|
2023-10-19 21:41:47 -07:00
|
|
|
UpdateMsgpackValueSource(model, this, r, name, value.last_change());
|
2022-10-08 10:01:31 -07:00
|
|
|
mpack_reader_destroy(&r);
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto structNameOpt = wpi::remove_prefix(typeStr, "struct:")) {
|
|
|
|
|
auto structName = *structNameOpt;
|
|
|
|
|
auto withoutArray = wpi::remove_suffix(structName, "[]");
|
|
|
|
|
bool isArray = withoutArray.has_value();
|
2023-10-19 21:41:47 -07:00
|
|
|
if (isArray) {
|
2024-06-04 21:01:52 -07:00
|
|
|
structName = *withoutArray;
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
auto desc = model.m_structDb.Find(structName);
|
2024-10-11 16:09:32 -07:00
|
|
|
if (desc && desc->IsValid() && desc->GetSize() != 0) {
|
2023-10-19 21:41:47 -07:00
|
|
|
if (isArray) {
|
|
|
|
|
// array of struct at top level
|
|
|
|
|
if (valueChildrenMap) {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
valueChildrenMap = false;
|
|
|
|
|
}
|
|
|
|
|
auto raw = value.GetRaw();
|
|
|
|
|
valueChildren.resize(raw.size() / desc->GetSize());
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
for (auto&& child : valueChildren) {
|
|
|
|
|
if (child.name.empty()) {
|
|
|
|
|
child.name = fmt::format("[{}]", i);
|
|
|
|
|
child.path = fmt::format("{}{}", name, child.name);
|
|
|
|
|
}
|
|
|
|
|
wpi::DynamicStruct s{desc, raw};
|
|
|
|
|
UpdateStructValueSource(model, &child, s, child.path,
|
|
|
|
|
value.last_change());
|
|
|
|
|
++i;
|
|
|
|
|
raw = wpi::drop_front(raw, desc->GetSize());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
wpi::DynamicStruct s{desc, value.GetRaw()};
|
|
|
|
|
UpdateStructValueSource(model, this, s, name, value.last_change());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto filename = wpi::remove_prefix(typeStr, "proto:")) {
|
2024-09-12 23:44:19 -07:00
|
|
|
#ifndef NO_PROTOBUF
|
2024-06-04 21:01:52 -07:00
|
|
|
auto msg = model.m_protoDb.Find(*filename);
|
2023-10-19 21:41:47 -07:00
|
|
|
if (msg) {
|
|
|
|
|
msg->Clear();
|
|
|
|
|
auto raw = value.GetRaw();
|
|
|
|
|
if (msg->ParseFromArray(raw.data(), raw.size())) {
|
|
|
|
|
UpdateProtobufValueSource(model, this, *msg, name,
|
|
|
|
|
value.last_change());
|
|
|
|
|
} else {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
}
|
2024-09-12 23:44:19 -07:00
|
|
|
#else
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
#endif
|
2022-10-08 10:01:31 -07:00
|
|
|
} else {
|
|
|
|
|
valueChildren.clear();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
default:
|
2022-10-08 10:01:31 -07:00
|
|
|
valueChildren.clear();
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesModel::Update() {
|
|
|
|
|
bool updateTree = false;
|
2022-10-31 21:52:14 -07:00
|
|
|
for (auto&& event : m_poller.ReadQueue()) {
|
|
|
|
|
if (auto info = event.GetTopicInfo()) {
|
|
|
|
|
auto& entry = m_entries[info->topic];
|
|
|
|
|
if (event.flags & nt::EventFlags::kPublish) {
|
|
|
|
|
if (!entry) {
|
|
|
|
|
entry = std::make_unique<Entry>();
|
|
|
|
|
m_sortedEntries.emplace_back(entry.get());
|
|
|
|
|
updateTree = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (event.flags & nt::EventFlags::kUnpublish) {
|
2022-11-29 21:57:48 -08:00
|
|
|
// meta topic handling
|
|
|
|
|
if (wpi::starts_with(info->name, '$')) {
|
|
|
|
|
// meta topic handling
|
|
|
|
|
if (info->name == "$clients") {
|
|
|
|
|
m_clients.clear();
|
|
|
|
|
} else if (info->name == "$serverpub") {
|
|
|
|
|
m_server.publishers.clear();
|
|
|
|
|
} else if (info->name == "$serversub") {
|
|
|
|
|
m_server.subscribers.clear();
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto client =
|
|
|
|
|
wpi::remove_prefix(info->name, "$clientpub$")) {
|
|
|
|
|
auto it = m_clients.find(*client);
|
2022-11-29 21:57:48 -08:00
|
|
|
if (it != m_clients.end()) {
|
|
|
|
|
it->second.publishers.clear();
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto client =
|
|
|
|
|
wpi::remove_prefix(info->name, "$clientsub$")) {
|
|
|
|
|
auto it = m_clients.find(*client);
|
2022-11-29 21:57:48 -08:00
|
|
|
if (it != m_clients.end()) {
|
|
|
|
|
it->second.subscribers.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-31 21:52:14 -07:00
|
|
|
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
|
|
|
|
|
entry.get());
|
|
|
|
|
// will be removed completely below
|
|
|
|
|
if (it != m_sortedEntries.end()) {
|
|
|
|
|
*it = nullptr;
|
|
|
|
|
}
|
|
|
|
|
m_entries.erase(info->topic);
|
2020-09-12 10:55:46 -07:00
|
|
|
updateTree = true;
|
2022-10-31 21:52:14 -07:00
|
|
|
continue;
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
2022-10-31 21:52:14 -07:00
|
|
|
if (event.flags & nt::EventFlags::kProperties) {
|
|
|
|
|
updateTree = true;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2022-10-31 21:52:14 -07:00
|
|
|
if (entry) {
|
|
|
|
|
entry->UpdateTopic(std::move(event));
|
|
|
|
|
}
|
|
|
|
|
} else if (auto valueData = event.GetValueEventData()) {
|
|
|
|
|
auto& entry = m_entries[valueData->topic];
|
|
|
|
|
if (entry) {
|
2023-10-19 21:41:47 -07:00
|
|
|
entry->value = std::move(valueData->value);
|
|
|
|
|
entry->UpdateFromValue(*this);
|
2022-10-31 21:52:14 -07:00
|
|
|
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
|
|
|
|
|
entry->info.type_str == "msgpack") {
|
|
|
|
|
// meta topic handling
|
|
|
|
|
if (entry->info.name == "$clients") {
|
2022-12-12 23:38:36 -08:00
|
|
|
// need to remove deleted entries as UpdateClients() uses GetEntry()
|
|
|
|
|
if (updateTree) {
|
|
|
|
|
std::erase(m_sortedEntries, nullptr);
|
|
|
|
|
}
|
2022-10-31 21:52:14 -07:00
|
|
|
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());
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto client =
|
|
|
|
|
wpi::remove_prefix(entry->info.name, "$clientpub$")) {
|
|
|
|
|
auto it = m_clients.find(*client);
|
2022-10-31 21:52:14 -07:00
|
|
|
if (it != m_clients.end()) {
|
|
|
|
|
it->second.UpdatePublishers(entry->value.GetRaw());
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto client =
|
|
|
|
|
wpi::remove_prefix(entry->info.name, "$clientsub$")) {
|
|
|
|
|
auto it = m_clients.find(*client);
|
2022-10-31 21:52:14 -07:00
|
|
|
if (it != m_clients.end()) {
|
|
|
|
|
it->second.UpdateSubscribers(entry->value.GetRaw());
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto typeStr =
|
|
|
|
|
wpi::remove_prefix(entry->info.name, "/.schema/struct:");
|
|
|
|
|
entry->value.IsRaw() && typeStr &&
|
2023-10-19 21:41:47 -07:00
|
|
|
entry->info.type_str == "structschema") {
|
|
|
|
|
// struct schema handling
|
|
|
|
|
std::string_view schema{
|
|
|
|
|
reinterpret_cast<const char*>(entry->value.GetRaw().data()),
|
|
|
|
|
entry->value.GetRaw().size()};
|
|
|
|
|
std::string err;
|
2024-06-04 21:01:52 -07:00
|
|
|
auto desc = m_structDb.Add(*typeStr, schema, &err);
|
2023-10-19 21:41:47 -07:00
|
|
|
if (!desc) {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("could not decode struct '{}' schema '{}': {}\n",
|
2023-10-19 21:41:47 -07:00
|
|
|
entry->info.name, schema, err);
|
|
|
|
|
} else if (desc->IsValid()) {
|
|
|
|
|
// loop over all entries with this type and update
|
|
|
|
|
for (auto&& entryPair : m_entries) {
|
2023-12-18 11:31:08 -08:00
|
|
|
if (!entryPair.second) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
if (auto ts = wpi::remove_prefix(entryPair.second->info.type_str,
|
|
|
|
|
"struct:")) {
|
|
|
|
|
if (*ts == *typeStr ||
|
|
|
|
|
wpi::remove_suffix(*ts, "[]").value_or(*ts) == *typeStr) {
|
|
|
|
|
entryPair.second->UpdateFromValue(*this);
|
|
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto filename =
|
|
|
|
|
wpi::remove_prefix(entry->info.name, "/.schema/proto:");
|
|
|
|
|
entry->value.IsRaw() && filename &&
|
2023-10-19 21:41:47 -07:00
|
|
|
entry->info.type_str == "proto:FileDescriptorProto") {
|
2024-09-12 23:44:19 -07:00
|
|
|
#ifndef NO_PROTOBUF
|
2023-10-19 21:41:47 -07:00
|
|
|
// protobuf descriptor handling
|
2024-06-04 21:01:52 -07:00
|
|
|
if (!m_protoDb.Add(*filename, entry->value.GetRaw())) {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print("could not decode protobuf '{}' filename '{}'\n",
|
2024-06-04 21:01:52 -07:00
|
|
|
entry->info.name, *filename);
|
2023-10-23 23:36:23 -07:00
|
|
|
} else {
|
|
|
|
|
// loop over all protobuf entries and update (conservatively)
|
|
|
|
|
for (auto&& entryPair : m_entries) {
|
2023-12-18 11:31:08 -08:00
|
|
|
if (!entryPair.second) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::string_view ts = entryPair.second->info.type_str;
|
2023-10-23 23:36:23 -07:00
|
|
|
if (wpi::starts_with(ts, "proto:")) {
|
|
|
|
|
entryPair.second->UpdateFromValue(*this);
|
|
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-12 23:44:19 -07:00
|
|
|
#endif
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2022-12-12 23:38:36 -08:00
|
|
|
std::erase(m_sortedEntries, nullptr);
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
RebuildTree();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesModel::RebuildTree() {
|
2020-09-12 10:55:46 -07:00
|
|
|
// 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);
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
|
|
|
|
|
int category) {
|
|
|
|
|
tree->clear();
|
2021-06-06 16:13:58 -07:00
|
|
|
wpi::SmallVector<std::string_view, 16> parts;
|
2020-09-12 10:55:46 -07:00
|
|
|
for (auto& entry : m_sortedEntries) {
|
2022-10-08 10:01:31 -07:00
|
|
|
if (!IsVisible(static_cast<ShowCategory>(category), entry->persistent,
|
|
|
|
|
entry->retained)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
parts.clear();
|
2022-10-08 10:01:31 -07:00
|
|
|
wpi::split(entry->info.name, parts, '/', -1, false);
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2020-12-28 14:36:30 -08:00
|
|
|
// ignore a raw "/" key
|
|
|
|
|
if (parts.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-12 10:55:46 -07:00
|
|
|
// get to leaf
|
2022-10-08 10:01:31 -07:00
|
|
|
auto nodes = tree;
|
2022-10-15 16:33:14 -07:00
|
|
|
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
|
2020-09-12 10:55:46 -07:00
|
|
|
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
|
2021-06-06 16:13:58 -07:00
|
|
|
nodes->back().path.assign(
|
2022-10-08 10:01:31 -07:00
|
|
|
entry->info.name.data(),
|
|
|
|
|
part.data() + part.size() - entry->info.name.data());
|
2020-09-12 10:55:46 -07:00
|
|
|
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() {
|
2022-11-20 17:27:28 -08:00
|
|
|
return true;
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-31 12:01:51 -08:00
|
|
|
NetworkTablesModel::Client::Subscriber::Subscriber(
|
|
|
|
|
nt::meta::ClientSubscriber&& oth)
|
|
|
|
|
: ClientSubscriber{std::move(oth)},
|
|
|
|
|
topicsStr{StringArrayToString(topics)} {}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void NetworkTablesModel::Client::UpdatePublishers(
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const uint8_t> data) {
|
2022-12-31 12:01:51 -08:00
|
|
|
if (auto pubs = nt::meta::DecodeClientPublishers(data)) {
|
|
|
|
|
publishers = std::move(*pubs);
|
2022-10-08 10:01:31 -07:00
|
|
|
} else {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print(stderr, "Failed to update publishers\n");
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void NetworkTablesModel::Client::UpdateSubscribers(
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const uint8_t> data) {
|
2022-12-31 12:01:51 -08:00
|
|
|
if (auto subs = nt::meta::DecodeClientSubscribers(data)) {
|
|
|
|
|
subscribers.clear();
|
|
|
|
|
subscribers.reserve(subs->size());
|
|
|
|
|
for (auto&& sub : *subs) {
|
|
|
|
|
subscribers.emplace_back(std::move(sub));
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-05-12 06:25:42 -07:00
|
|
|
wpi::print(stderr, "Failed to update subscribers\n");
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void NetworkTablesModel::UpdateClients(std::span<const uint8_t> data) {
|
2022-12-31 12:01:51 -08:00
|
|
|
auto clientsArr = nt::meta::DecodeClients(data);
|
|
|
|
|
if (!clientsArr) {
|
2022-10-08 10:01:31 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we need to create a new map so deletions are reflected
|
|
|
|
|
std::map<std::string, Client, std::less<>> newClients;
|
2022-12-31 12:01:51 -08:00
|
|
|
for (auto&& client : *clientsArr) {
|
2022-10-08 10:01:31 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 20:29:29 -07:00
|
|
|
static bool GetHeadingTypeString(std::string_view* ts) {
|
2024-06-04 21:01:52 -07:00
|
|
|
if (auto withoutProto = wpi::remove_prefix(*ts, "proto:")) {
|
|
|
|
|
*ts = *withoutProto;
|
2023-10-19 21:41:47 -07:00
|
|
|
auto lastdot = ts->rfind('.');
|
|
|
|
|
if (lastdot != std::string_view::npos) {
|
|
|
|
|
*ts = wpi::substr(*ts, lastdot + 1);
|
|
|
|
|
}
|
2024-06-04 21:01:52 -07:00
|
|
|
if (auto withoutProtobuf = wpi::remove_prefix(*ts, "Protobuf")) {
|
|
|
|
|
*ts = *withoutProtobuf;
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
return true;
|
2024-06-04 21:01:52 -07:00
|
|
|
} else if (auto withoutStruct = wpi::remove_prefix(*ts, "struct:")) {
|
|
|
|
|
*ts = *withoutStruct;
|
2023-10-19 21:41:47 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 20:29:29 -07:00
|
|
|
static const char* GetShortTypeString(std::string_view ts) {
|
|
|
|
|
if (wpi::starts_with(ts, "proto:")) {
|
|
|
|
|
return "protobuf";
|
|
|
|
|
} else if (wpi::starts_with(ts, "struct:")) {
|
|
|
|
|
return "struct";
|
|
|
|
|
} else {
|
|
|
|
|
return ts.data();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char* GetTypeString(NT_Type type, const char* overrideTypeStr) {
|
|
|
|
|
if (overrideTypeStr) {
|
|
|
|
|
return GetShortTypeString(overrideTypeStr);
|
|
|
|
|
}
|
2023-10-27 18:41:42 -06:00
|
|
|
switch (type) {
|
|
|
|
|
case NT_BOOLEAN:
|
|
|
|
|
return "boolean";
|
|
|
|
|
case NT_INTEGER:
|
|
|
|
|
return "int";
|
|
|
|
|
case NT_FLOAT:
|
|
|
|
|
return "float";
|
|
|
|
|
case NT_DOUBLE:
|
|
|
|
|
return "double";
|
|
|
|
|
case NT_STRING:
|
|
|
|
|
return "string";
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
|
|
|
|
return "boolean[]";
|
|
|
|
|
case NT_INTEGER_ARRAY:
|
|
|
|
|
return "int[]";
|
|
|
|
|
case NT_FLOAT_ARRAY:
|
|
|
|
|
return "float[]";
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
return "double[]";
|
|
|
|
|
case NT_STRING_ARRAY:
|
|
|
|
|
return "string[]";
|
|
|
|
|
case NT_RAW:
|
|
|
|
|
return "raw";
|
|
|
|
|
case NT_RPC:
|
|
|
|
|
return "rpc";
|
|
|
|
|
default:
|
|
|
|
|
return "other";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
|
2023-10-27 18:41:42 -06:00
|
|
|
const char* overrideTypeStr,
|
2022-06-15 22:21:52 -06:00
|
|
|
NetworkTablesFlags flags) {
|
2020-09-12 10:55:46 -07:00
|
|
|
auto& val = entry.value;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!val) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2023-10-30 20:29:29 -07:00
|
|
|
const char* typeStr = GetTypeString(val.type(), overrideTypeStr);
|
|
|
|
|
ImGui::SetNextItemWidth(
|
|
|
|
|
-1 * (ImGui::CalcTextSize(typeStr).x + ImGui::GetStyle().FramePadding.x));
|
2023-10-27 18:41:42 -06:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
switch (val.type()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_BOOLEAN:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "%s", val.GetBoolean() ? "true" : "false");
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case NT_INTEGER:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "%" PRId64, val.GetInteger());
|
2022-10-08 10:01:31 -07:00
|
|
|
break;
|
|
|
|
|
case NT_FLOAT:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "%.6f", val.GetFloat());
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2022-06-15 22:21:52 -06:00
|
|
|
case NT_DOUBLE: {
|
|
|
|
|
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
|
|
|
|
|
kNetworkTablesFlags_PrecisionBitShift;
|
|
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
|
#endif
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, fmt::format("%.{}f", precision).c_str(),
|
2022-10-08 10:01:31 -07:00
|
|
|
val.GetDouble());
|
2022-06-15 22:21:52 -06:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
#endif
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2022-06-15 22:21:52 -06:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_STRING: {
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "%s", entry.valueStr.c_str());
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
2022-10-08 10:01:31 -07:00
|
|
|
case NT_INTEGER_ARRAY:
|
|
|
|
|
case NT_FLOAT_ARRAY:
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
case NT_STRING_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2023-10-19 21:41:47 -07:00
|
|
|
case NT_RAW: {
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, val.GetRaw().empty() ? "[]" : "[...]");
|
2023-10-19 21:41:47 -07:00
|
|
|
if (ImGui::IsItemHovered()) {
|
|
|
|
|
ImGui::BeginTooltip();
|
2023-10-30 20:29:29 -07:00
|
|
|
if (overrideTypeStr) {
|
|
|
|
|
ImGui::TextUnformatted(overrideTypeStr);
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
ImGui::Text("%u bytes", static_cast<unsigned int>(val.GetRaw().size()));
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
default:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "?");
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static constexpr size_t kTextBufferSize = 4096;
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
static char* GetTextBuffer(std::string_view in) {
|
2020-09-12 10:55:46 -07:00
|
|
|
static char textBuffer[kTextBufferSize];
|
|
|
|
|
size_t len = (std::min)(in.size(), kTextBufferSize - 1);
|
|
|
|
|
std::memcpy(textBuffer, in.data(), len);
|
|
|
|
|
textBuffer[len] = '\0';
|
|
|
|
|
return textBuffer;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-10 00:28:54 -07:00
|
|
|
namespace {
|
|
|
|
|
class ArrayEditor {
|
|
|
|
|
public:
|
|
|
|
|
virtual ~ArrayEditor() = default;
|
|
|
|
|
virtual bool Emit() = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <int NTType, typename T>
|
|
|
|
|
class ArrayEditorImpl final : public ArrayEditor {
|
|
|
|
|
public:
|
|
|
|
|
ArrayEditorImpl(NetworkTablesModel& model, std::string name,
|
|
|
|
|
NetworkTablesFlags flags, std::span<const T> value)
|
|
|
|
|
: m_model{model},
|
|
|
|
|
m_name{std::move(name)},
|
|
|
|
|
m_flags{flags},
|
|
|
|
|
m_arr{value.begin(), value.end()} {}
|
|
|
|
|
|
|
|
|
|
bool Emit() final;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
NetworkTablesModel& m_model;
|
|
|
|
|
std::string m_name;
|
|
|
|
|
NetworkTablesFlags m_flags;
|
|
|
|
|
std::vector<T> m_arr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <int NTType, typename T>
|
|
|
|
|
bool ArrayEditorImpl<NTType, T>::Emit() {
|
|
|
|
|
if (ImGui::BeginTable(
|
|
|
|
|
"arrayvalues", 1,
|
|
|
|
|
ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit |
|
|
|
|
|
ImGuiTableFlags_RowBg,
|
|
|
|
|
ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 16))) {
|
|
|
|
|
ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
|
|
|
|
|
int toAdd = -1;
|
|
|
|
|
int toRemove = -1;
|
|
|
|
|
ImGuiListClipper clipper;
|
|
|
|
|
clipper.Begin(m_arr.size());
|
|
|
|
|
while (clipper.Step()) {
|
|
|
|
|
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
|
|
|
|
|
ImGui::TableNextRow();
|
|
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
ImGui::PushID(row);
|
|
|
|
|
char label[16];
|
|
|
|
|
wpi::format_to_n_c_str(label, sizeof(label), "[{}]", row);
|
|
|
|
|
if constexpr (NTType == NT_BOOLEAN_ARRAY) {
|
|
|
|
|
static const char* boolOptions[] = {"false", "true"};
|
|
|
|
|
ImGui::Combo(label, &m_arr[row], boolOptions, 2);
|
|
|
|
|
} else if constexpr (NTType == NT_FLOAT_ARRAY) {
|
|
|
|
|
ImGui::InputFloat(label, &m_arr[row], 0, 0, "%.6f");
|
|
|
|
|
} else if constexpr (NTType == NT_DOUBLE_ARRAY) {
|
|
|
|
|
unsigned char precision = (m_flags & NetworkTablesFlags_Precision) >>
|
|
|
|
|
kNetworkTablesFlags_PrecisionBitShift;
|
|
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
|
#endif
|
|
|
|
|
ImGui::InputDouble(label, &m_arr[row], 0, 0,
|
|
|
|
|
fmt::format("%.{}f", precision).c_str());
|
|
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
#endif
|
|
|
|
|
} else if constexpr (NTType == NT_INTEGER_ARRAY) {
|
|
|
|
|
ImGui::InputScalar(label, ImGuiDataType_S64, &m_arr[row]);
|
|
|
|
|
} else if constexpr (NTType == NT_STRING_ARRAY) {
|
|
|
|
|
ImGui::InputText(label, &m_arr[row]);
|
|
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
if (ImGui::SmallButton("+")) {
|
|
|
|
|
toAdd = row;
|
|
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
if (ImGui::SmallButton("-")) {
|
|
|
|
|
toRemove = row;
|
|
|
|
|
}
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (toAdd != -1) {
|
|
|
|
|
m_arr.emplace(m_arr.begin() + toAdd);
|
|
|
|
|
} else if (toRemove != -1) {
|
|
|
|
|
m_arr.erase(m_arr.begin() + toRemove);
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
|
}
|
|
|
|
|
if (ImGui::Button("Add to end")) {
|
|
|
|
|
m_arr.emplace_back();
|
|
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
if (ImGui::Button("Cancel")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
if (ImGui::Button("Apply")) {
|
|
|
|
|
auto* entry = m_model.GetEntry(m_name);
|
|
|
|
|
if (!entry) {
|
|
|
|
|
entry = m_model.AddEntry(
|
|
|
|
|
nt::GetTopic(m_model.GetInstance().GetHandle(), m_name));
|
|
|
|
|
}
|
|
|
|
|
if constexpr (NTType == NT_BOOLEAN_ARRAY) {
|
|
|
|
|
if (entry->publisher == 0) {
|
|
|
|
|
entry->publisher =
|
|
|
|
|
nt::Publish(entry->info.topic, NT_BOOLEAN_ARRAY, "boolean[]");
|
|
|
|
|
}
|
|
|
|
|
nt::SetBooleanArray(entry->publisher, m_arr);
|
|
|
|
|
} else if constexpr (NTType == NT_FLOAT_ARRAY) {
|
|
|
|
|
if (entry->publisher == 0) {
|
|
|
|
|
entry->publisher =
|
|
|
|
|
nt::Publish(entry->info.topic, NT_FLOAT_ARRAY, "float[]");
|
|
|
|
|
}
|
|
|
|
|
nt::SetFloatArray(entry->publisher, m_arr);
|
|
|
|
|
} else if constexpr (NTType == NT_DOUBLE_ARRAY) {
|
|
|
|
|
if (entry->publisher == 0) {
|
|
|
|
|
entry->publisher =
|
|
|
|
|
nt::Publish(entry->info.topic, NT_DOUBLE_ARRAY, "double[]");
|
|
|
|
|
}
|
|
|
|
|
nt::SetDoubleArray(entry->publisher, m_arr);
|
|
|
|
|
} else if constexpr (NTType == NT_INTEGER_ARRAY) {
|
|
|
|
|
if (entry->publisher == 0) {
|
|
|
|
|
entry->publisher =
|
|
|
|
|
nt::Publish(entry->info.topic, NT_INTEGER_ARRAY, "int[]");
|
|
|
|
|
}
|
|
|
|
|
nt::SetIntegerArray(entry->publisher, m_arr);
|
|
|
|
|
} else if constexpr (NTType == NT_STRING_ARRAY) {
|
|
|
|
|
if (entry->publisher == 0) {
|
|
|
|
|
entry->publisher =
|
|
|
|
|
nt::Publish(entry->info.topic, NT_STRING_ARRAY, "string[]");
|
|
|
|
|
}
|
|
|
|
|
nt::SetStringArray(entry->publisher, m_arr);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
static ImGuiID gArrayEditorID;
|
|
|
|
|
static std::unique_ptr<ArrayEditor> gArrayEditor;
|
|
|
|
|
|
|
|
|
|
static void EmitEntryValueEditable(NetworkTablesModel* model,
|
|
|
|
|
NetworkTablesModel::Entry& entry,
|
2022-06-15 22:21:52 -06:00
|
|
|
NetworkTablesFlags flags) {
|
2020-09-12 10:55:46 -07:00
|
|
|
auto& val = entry.value;
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!val) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2023-10-30 20:29:29 -07:00
|
|
|
const char* typeStr = GetTypeString(
|
|
|
|
|
val.type(),
|
|
|
|
|
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str());
|
|
|
|
|
ImGui::SetNextItemWidth(
|
|
|
|
|
-1 * (ImGui::CalcTextSize(typeStr).x + ImGui::GetStyle().FramePadding.x));
|
2023-10-27 18:41:42 -06:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::PushID(entry.info.name.c_str());
|
|
|
|
|
switch (val.type()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_BOOLEAN: {
|
|
|
|
|
static const char* boolOptions[] = {"false", "true"};
|
2022-10-08 10:01:31 -07:00
|
|
|
int v = val.GetBoolean() ? 1 : 0;
|
2023-10-30 20:29:29 -07:00
|
|
|
if (ImGui::Combo(typeStr, &v, boolOptions, 2)) {
|
2022-10-08 10:01:31 -07:00
|
|
|
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();
|
2024-04-28 14:03:49 -05:00
|
|
|
if (InputExpr<int64_t>(typeStr, &v, "%d",
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2022-10-08 10:01:31 -07:00
|
|
|
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();
|
2024-04-28 14:03:49 -05:00
|
|
|
if (InputExpr<float>(typeStr, &v, "%.6f",
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
2022-10-08 10:01:31 -07:00
|
|
|
if (entry.publisher == 0) {
|
|
|
|
|
entry.publisher = nt::Publish(entry.info.topic, NT_FLOAT, "float");
|
|
|
|
|
}
|
|
|
|
|
nt::SetFloat(entry.publisher, v);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_DOUBLE: {
|
2022-10-08 10:01:31 -07:00
|
|
|
double v = val.GetDouble();
|
2022-06-15 22:21:52 -06:00
|
|
|
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
|
|
|
|
|
kNetworkTablesFlags_PrecisionBitShift;
|
|
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
|
#endif
|
2024-04-28 14:03:49 -05:00
|
|
|
if (InputExpr<double>(typeStr, &v,
|
|
|
|
|
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);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2022-06-15 22:21:52 -06:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
#endif
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case NT_STRING: {
|
2023-02-17 18:01:54 -08:00
|
|
|
char* v = GetTextBuffer(entry.valueStr);
|
2024-05-25 11:19:52 -05:00
|
|
|
ImGui::InputText(typeStr, v, kTextBufferSize,
|
|
|
|
|
ImGuiInputTextFlags_EnterReturnsTrue);
|
|
|
|
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
2023-02-17 18:01:54 -08:00
|
|
|
if (v[0] == '"') {
|
|
|
|
|
if (entry.publisher == 0) {
|
|
|
|
|
entry.publisher =
|
|
|
|
|
nt::Publish(entry.info.topic, NT_STRING, "string");
|
|
|
|
|
}
|
|
|
|
|
wpi::SmallString<128> buf;
|
|
|
|
|
nt::SetString(entry.publisher,
|
|
|
|
|
wpi::UnescapeCString(v + 1, buf).first);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_BOOLEAN_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2023-10-10 00:28:54 -07:00
|
|
|
if (ImGui::BeginPopupContextItem("boolean[]")) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_BOOLEAN_ARRAY, int>>(
|
|
|
|
|
*model, entry.info.name, flags,
|
|
|
|
|
entry.value.GetBooleanArray());
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
ImGui::EndPopup();
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_INTEGER_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2023-10-10 00:28:54 -07:00
|
|
|
if (ImGui::BeginPopupContextItem("int[]")) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_INTEGER_ARRAY, int64_t>>(
|
|
|
|
|
*model, entry.info.name, flags,
|
|
|
|
|
entry.value.GetIntegerArray());
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
ImGui::EndPopup();
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_FLOAT_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2023-10-10 00:28:54 -07:00
|
|
|
if (ImGui::BeginPopupContextItem("float[]")) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_FLOAT_ARRAY, float>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetFloatArray());
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
ImGui::EndPopup();
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_DOUBLE_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2023-10-10 00:28:54 -07:00
|
|
|
if (ImGui::BeginPopupContextItem("double[]")) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_DOUBLE_ARRAY, double>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetDoubleArray());
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
ImGui::EndPopup();
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-10 00:28:54 -07:00
|
|
|
case NT_STRING_ARRAY:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[]");
|
2023-10-10 00:28:54 -07:00
|
|
|
if (ImGui::BeginPopupContextItem("string[]")) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_STRING_ARRAY, std::string>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetStringArray());
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2023-10-10 00:28:54 -07:00
|
|
|
ImGui::EndPopup();
|
|
|
|
|
break;
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2023-10-19 21:41:47 -07:00
|
|
|
case NT_RAW: {
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, val.GetRaw().empty() ? "[]" : "[...]");
|
2023-10-19 21:41:47 -07:00
|
|
|
if (ImGui::IsItemHovered()) {
|
|
|
|
|
ImGui::BeginTooltip();
|
2023-10-30 20:29:29 -07:00
|
|
|
if (!entry.info.type_str.empty()) {
|
|
|
|
|
ImGui::TextUnformatted(entry.info.type_str.c_str());
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
|
|
|
|
ImGui::Text("%u bytes", static_cast<unsigned int>(val.GetRaw().size()));
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
2023-10-19 21:41:47 -07:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
case NT_RPC:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "[...]");
|
2020-09-12 10:55:46 -07:00
|
|
|
break;
|
|
|
|
|
default:
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::LabelText(typeStr, "?");
|
2020-09-12 10:55:46 -07:00
|
|
|
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);
|
2023-02-17 16:45:38 -08:00
|
|
|
// publish a default value so it's editable
|
|
|
|
|
switch (type) {
|
|
|
|
|
case NT_BOOLEAN:
|
|
|
|
|
nt::SetDefaultBoolean(entry->publisher, false);
|
|
|
|
|
break;
|
|
|
|
|
case NT_INTEGER:
|
|
|
|
|
nt::SetDefaultInteger(entry->publisher, 0);
|
|
|
|
|
break;
|
|
|
|
|
case NT_FLOAT:
|
|
|
|
|
nt::SetDefaultFloat(entry->publisher, 0.0);
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE:
|
|
|
|
|
nt::SetDefaultDouble(entry->publisher, 0.0);
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING:
|
|
|
|
|
nt::SetDefaultString(entry->publisher, "");
|
|
|
|
|
break;
|
|
|
|
|
case NT_BOOLEAN_ARRAY:
|
|
|
|
|
nt::SetDefaultBooleanArray(entry->publisher, {});
|
|
|
|
|
break;
|
|
|
|
|
case NT_INTEGER_ARRAY:
|
|
|
|
|
nt::SetDefaultIntegerArray(entry->publisher, {});
|
|
|
|
|
break;
|
|
|
|
|
case NT_FLOAT_ARRAY:
|
|
|
|
|
nt::SetDefaultFloatArray(entry->publisher, {});
|
|
|
|
|
break;
|
|
|
|
|
case NT_DOUBLE_ARRAY:
|
|
|
|
|
nt::SetDefaultDoubleArray(entry->publisher, {});
|
|
|
|
|
break;
|
|
|
|
|
case NT_STRING_ARRAY:
|
|
|
|
|
nt::SetDefaultStringArray(entry->publisher, {});
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 16:46:02 -08:00
|
|
|
void glass::DisplayNetworkTablesAddMenu(NetworkTablesModel* model,
|
|
|
|
|
std::string_view path,
|
|
|
|
|
NetworkTablesFlags flags) {
|
|
|
|
|
static char nameBuffer[kTextBufferSize];
|
|
|
|
|
|
|
|
|
|
if (ImGui::BeginMenu("Add new...")) {
|
|
|
|
|
if (ImGui::IsWindowAppearing()) {
|
|
|
|
|
nameBuffer[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
|
|
|
|
|
kTextBufferSize);
|
|
|
|
|
std::string fullNewPath;
|
|
|
|
|
if (path == "/") {
|
|
|
|
|
path = "";
|
|
|
|
|
}
|
|
|
|
|
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
|
|
|
|
|
|
|
|
|
|
ImGui::Text("Adding: %s", fullNewPath.c_str());
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
auto entry = model->GetEntry(fullNewPath);
|
|
|
|
|
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
|
|
|
|
|
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
|
|
|
|
|
nameBuffer[0] != '\0') &&
|
|
|
|
|
!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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
static void EmitParentContextMenu(NetworkTablesModel* model,
|
|
|
|
|
const std::string& path,
|
2020-12-29 20:49:29 -08:00
|
|
|
NetworkTablesFlags flags) {
|
2020-12-30 22:43:35 -08:00
|
|
|
if (ImGui::BeginPopupContextItem(path.c_str())) {
|
2020-12-29 20:49:29 -08:00
|
|
|
ImGui::Text("%s", path.c_str());
|
|
|
|
|
ImGui::Separator();
|
|
|
|
|
|
2023-02-17 16:46:02 -08:00
|
|
|
DisplayNetworkTablesAddMenu(model, path, flags);
|
2020-12-29 20:49:29 -08:00
|
|
|
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-12-29 20:49:29 -08: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);
|
2020-12-29 20:49:29 -08:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
if (ImGui::BeginPopupContextItem(path)) {
|
|
|
|
|
ImGui::TextUnformatted(path);
|
2020-12-29 20:49:29 -08:00
|
|
|
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());
|
2023-09-17 20:00:16 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
if (!child.valueChildren.empty()) {
|
2023-10-19 21:41:47 -07:00
|
|
|
auto pos = ImGui::GetCursorPos();
|
|
|
|
|
char label[128];
|
|
|
|
|
std::string_view ts = child.typeStr;
|
2023-10-30 20:29:29 -07:00
|
|
|
bool havePopup = GetHeadingTypeString(&ts);
|
2023-10-19 21:41:47 -07:00
|
|
|
wpi::format_to_n_c_str(label, sizeof(label), "{}##v_{}", ts.data(),
|
|
|
|
|
child.name.c_str());
|
|
|
|
|
bool valueChildrenOpen =
|
|
|
|
|
TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth);
|
|
|
|
|
if (havePopup) {
|
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
|
|
|
ImGui::BeginTooltip();
|
|
|
|
|
ImGui::TextUnformatted(child.typeStr.c_str());
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
2023-09-17 20:00:16 -07:00
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
// make it look like a normal label w/type
|
|
|
|
|
ImGui::SetCursorPos(pos);
|
|
|
|
|
ImGui::LabelText(child.valueChildrenMap ? "{...}" : "[...]", "%s", "");
|
|
|
|
|
if (valueChildrenOpen) {
|
2022-10-08 10:01:31 -07:00
|
|
|
EmitValueTree(child.valueChildren, flags);
|
|
|
|
|
TreePop();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
EmitEntryValueReadonly(child, nullptr, flags);
|
2020-12-29 20:49:29 -08:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-09-12 10:55:46 -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();
|
2023-10-19 21:41:47 -07:00
|
|
|
char label[128];
|
|
|
|
|
std::string_view ts = entry.info.type_str;
|
2023-10-30 20:29:29 -07:00
|
|
|
bool havePopup = GetHeadingTypeString(&ts);
|
2023-10-19 21:41:47 -07:00
|
|
|
wpi::format_to_n_c_str(label, sizeof(label), "{}##v_{}", ts.data(),
|
|
|
|
|
entry.info.name.c_str());
|
2022-10-08 10:01:31 -07:00
|
|
|
valueChildrenOpen =
|
|
|
|
|
TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth |
|
|
|
|
|
ImGuiTreeNodeFlags_AllowItemOverlap);
|
2023-10-19 21:41:47 -07:00
|
|
|
if (havePopup) {
|
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
|
|
|
ImGui::BeginTooltip();
|
|
|
|
|
ImGui::TextUnformatted(entry.info.type_str.c_str());
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
// make it look like a normal label w/type
|
2023-10-30 20:29:29 -07:00
|
|
|
const char* typeStr = GetTypeString(
|
|
|
|
|
NT_RAW,
|
|
|
|
|
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str());
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::SetCursorPos(pos);
|
2023-10-30 20:29:29 -07:00
|
|
|
ImGui::SetNextItemWidth(-1 * (ImGui::CalcTextSize(typeStr).x +
|
|
|
|
|
ImGui::GetStyle().FramePadding.x));
|
|
|
|
|
ImGui::LabelText(typeStr, "%s", "");
|
2023-10-10 00:28:54 -07:00
|
|
|
if ((entry.value.IsBooleanArray() || entry.value.IsFloatArray() ||
|
|
|
|
|
entry.value.IsDoubleArray() || entry.value.IsIntegerArray() ||
|
|
|
|
|
entry.value.IsStringArray()) &&
|
|
|
|
|
ImGui::BeginPopupContextItem(label)) {
|
|
|
|
|
if (ImGui::Selectable("Edit Array")) {
|
|
|
|
|
if (entry.value.IsBooleanArray()) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_BOOLEAN_ARRAY, int>>(
|
|
|
|
|
*model, entry.info.name, flags,
|
|
|
|
|
entry.value.GetBooleanArray());
|
|
|
|
|
} else if (entry.value.IsFloatArray()) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_FLOAT_ARRAY, float>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetFloatArray());
|
|
|
|
|
} else if (entry.value.IsDoubleArray()) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_DOUBLE_ARRAY, double>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetDoubleArray());
|
|
|
|
|
} else if (entry.value.IsIntegerArray()) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_INTEGER_ARRAY, int64_t>>(
|
|
|
|
|
*model, entry.info.name, flags,
|
|
|
|
|
entry.value.GetIntegerArray());
|
|
|
|
|
} else if (entry.value.IsStringArray()) {
|
|
|
|
|
gArrayEditor =
|
|
|
|
|
std::make_unique<ArrayEditorImpl<NT_STRING_ARRAY, std::string>>(
|
|
|
|
|
*model, entry.info.name, flags, entry.value.GetStringArray());
|
|
|
|
|
}
|
|
|
|
|
ImGui::OpenPopup(gArrayEditorID);
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
} else if (flags & NetworkTablesFlags_ReadOnly) {
|
|
|
|
|
EmitEntryValueReadonly(
|
|
|
|
|
entry,
|
|
|
|
|
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str(),
|
|
|
|
|
flags);
|
2020-12-28 12:58:06 -08:00
|
|
|
} else {
|
2023-10-10 00:28:54 -07:00
|
|
|
EmitEntryValueEditable(model, entry, flags);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
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();
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & NetworkTablesFlags_ShowTimestamp) {
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::TableNextColumn();
|
2020-12-28 12:58:06 -08:00
|
|
|
if (entry.value) {
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::Text("%f", (entry.value.last_change() * 1.0e-6) -
|
2020-12-17 07:29:00 -08:00
|
|
|
(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
|
|
|
}
|
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();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
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) {
|
2020-09-12 10:55:46 -07:00
|
|
|
for (auto&& node : tree) {
|
2022-10-08 10:01:31 -07:00
|
|
|
if (root && (flags & NetworkTablesFlags_ShowSpecial) == 0 &&
|
|
|
|
|
wpi::starts_with(node.name, '$')) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
if (node.entry) {
|
2022-10-08 10:01:31 -07:00
|
|
|
EmitEntry(model, *node.entry, node.name.c_str(), flags, category);
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!node.children.empty()) {
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::TableNextRow();
|
|
|
|
|
ImGui::TableNextColumn();
|
2020-09-12 10:55:46 -07:00
|
|
|
bool open =
|
|
|
|
|
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
|
2022-10-08 10:01:31 -07:00
|
|
|
EmitParentContextMenu(model, node.path, flags);
|
2020-09-12 10:55:46 -07:00
|
|
|
if (open) {
|
2022-10-08 10:01:31 -07:00
|
|
|
EmitTree(model, node.children, flags, category, false);
|
2020-09-12 10:55:46 -07:00
|
|
|
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;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
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);
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
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());
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::EndTable();
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
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);
|
2022-11-26 17:01:40 -08:00
|
|
|
ImGui::TableSetupColumn("Topics Only", ImGuiTableColumnFlags_WidthStretch,
|
2022-10-08 10:01:31 -07:00
|
|
|
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();
|
2022-11-26 17:01:40 -08:00
|
|
|
ImGui::Text(sub.options.topicsOnly ? "Yes" : "No");
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
ImGui::Text(sub.options.sendAll ? "Yes" : "No");
|
|
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
ImGui::Text(sub.options.prefixMatch ? "Yes" : "No");
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void glass::DisplayNetworkTablesInfo(NetworkTablesModel* model) {
|
|
|
|
|
auto inst = model->GetInstance();
|
2020-09-12 10:55:46 -07:00
|
|
|
|
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);
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
ImGui::EndTable();
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
|
|
|
|
|
auto netMode = inst.GetNetworkMode();
|
2025-01-02 23:05:13 -08:00
|
|
|
if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT) {
|
2022-10-08 10:01:31 -07:00
|
|
|
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();
|
2022-12-31 12:01:51 -08:00
|
|
|
ImGui::Text("%s (version %u.%u)", client.second.conn.c_str(),
|
|
|
|
|
client.second.version >> 8, client.second.version & 0xff);
|
2022-10-08 10:01:31 -07:00
|
|
|
DisplayClient(client.second);
|
|
|
|
|
ImGui::Unindent();
|
|
|
|
|
PopID();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ImGui::Unindent();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
2022-10-08 10:01:31 -07:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
void glass::DisplayNetworkTables(NetworkTablesModel* model,
|
|
|
|
|
NetworkTablesFlags flags) {
|
2023-10-10 00:28:54 -07:00
|
|
|
gArrayEditorID = ImGui::GetID("Array Editor");
|
|
|
|
|
if (ImGui::BeginPopupModal("Array Editor", nullptr,
|
|
|
|
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
|
|
|
if (!gArrayEditor || gArrayEditor->Emit()) {
|
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
|
gArrayEditor.release();
|
|
|
|
|
}
|
|
|
|
|
ImGui::EndPopup();
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-08 10:01:31 -07:00
|
|
|
if (flags & NetworkTablesFlags_CombinedView) {
|
|
|
|
|
DisplayTable(model, model->GetTreeRoot(), flags, ShowAll);
|
2020-09-12 10:55:46 -07:00
|
|
|
} 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);
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-16 22:05:41 -07:00
|
|
|
void NetworkTablesFlagsSettings::Update() {
|
|
|
|
|
if (!m_pTreeView) {
|
|
|
|
|
auto& storage = GetStorage();
|
[glass] Use JSON files for storage instead of imgui ini
Storage is now nested.
Separate "roots" can be configured which save to separate files.
In particular, this is used to save wpigui and ImGui window position
to a -window.json file.
ImGui's ini (for window position) is mapped to JSON.
You can optionally specify a directory to load from on the command line.
If one isn't provided, it uses the global system directory.
Any changes made are automatically saved here.
Workspace | Open: select directory, the current layout is replaced with that
workspace, and future auto-saves also switch to that location. The main
window size/location is not changed, only the contents.
Workspace | Save As: select directory, the current layout is saved there,
and future auto-saves also switch to that location.
Workspace | Reset: window locations are preserved, but all other settings
are reset to default (including e.g. removing plot windows). This will also
end up clearing the current save file. as with load, the main window
size/location is not changed.
Workspace | Save As Global: "save as" to the global system location
Notably, the main window size/location is only loaded at startup, but is
auto-saved as part of the current workspace.
2021-11-25 00:51:00 -08:00
|
|
|
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);
|
[glass] Use JSON files for storage instead of imgui ini
Storage is now nested.
Separate "roots" can be configured which save to separate files.
In particular, this is used to save wpigui and ImGui window position
to a -window.json file.
ImGui's ini (for window position) is mapped to JSON.
You can optionally specify a directory to load from on the command line.
If one isn't provided, it uses the global system directory.
Any changes made are automatically saved here.
Workspace | Open: select directory, the current layout is replaced with that
workspace, and future auto-saves also switch to that location. The main
window size/location is not changed, only the contents.
Workspace | Save As: select directory, the current layout is saved there,
and future auto-saves also switch to that location.
Workspace | Reset: window locations are preserved, but all other settings
are reset to default (including e.g. removing plot windows). This will also
end up clearing the current save file. as with load, the main window
size/location is not changed.
Workspace | Save As Global: "save as" to the global system location
Notably, the main window size/location is only loaded at startup, but is
auto-saved as part of the current workspace.
2021-11-25 00:51:00 -08:00
|
|
|
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);
|
[glass] Use JSON files for storage instead of imgui ini
Storage is now nested.
Separate "roots" can be configured which save to separate files.
In particular, this is used to save wpigui and ImGui window position
to a -window.json file.
ImGui's ini (for window position) is mapped to JSON.
You can optionally specify a directory to load from on the command line.
If one isn't provided, it uses the global system directory.
Any changes made are automatically saved here.
Workspace | Open: select directory, the current layout is replaced with that
workspace, and future auto-saves also switch to that location. The main
window size/location is not changed, only the contents.
Workspace | Save As: select directory, the current layout is saved there,
and future auto-saves also switch to that location.
Workspace | Reset: window locations are preserved, but all other settings
are reset to default (including e.g. removing plot windows). This will also
end up clearing the current save file. as with load, the main window
size/location is not changed.
Workspace | Save As Global: "save as" to the global system location
Notably, the main window size/location is only loaded at startup, but is
auto-saved as part of the current workspace.
2021-11-25 00:51:00 -08:00
|
|
|
m_pCreateNoncanonicalKeys = &storage.GetBool(
|
2021-03-16 22:05:41 -07:00
|
|
|
"createNonCanonical",
|
|
|
|
|
m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
|
2022-06-15 22:21:52 -06:00
|
|
|
m_pPrecision = &storage.GetInt(
|
|
|
|
|
"precision", (m_defaultFlags & NetworkTablesFlags_Precision) >>
|
|
|
|
|
kNetworkTablesFlags_PrecisionBitShift);
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
|
2022-06-15 22:21:52 -06:00
|
|
|
m_flags &= ~(
|
2022-10-08 10:01:31 -07:00
|
|
|
NetworkTablesFlags_TreeView | NetworkTablesFlags_CombinedView |
|
|
|
|
|
NetworkTablesFlags_ShowSpecial | NetworkTablesFlags_ShowProperties |
|
|
|
|
|
NetworkTablesFlags_ShowTimestamp |
|
|
|
|
|
NetworkTablesFlags_ShowServerTimestamp |
|
2022-06-15 22:21:52 -06:00
|
|
|
NetworkTablesFlags_CreateNoncanonicalKeys | NetworkTablesFlags_Precision);
|
2020-12-29 20:49:29 -08:00
|
|
|
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
|
2022-06-15 22:21:52 -06:00
|
|
|
: 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);
|
2022-06-15 22:21:52 -06:00
|
|
|
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();
|
|
|
|
|
DisplayNetworkTables(m_model, m_flags.GetFlags());
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
2023-01-01 21:05:09 -07:00
|
|
|
|
|
|
|
|
void NetworkTablesView::Settings() {
|
|
|
|
|
m_flags.DisplayMenu();
|
2023-02-17 16:46:02 -08:00
|
|
|
DisplayNetworkTablesAddMenu(m_model, {}, m_flags.GetFlags());
|
2023-01-01 21:05:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NetworkTablesView::HasSettings() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|