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

2065 lines
71 KiB
C++
Raw Normal View History

2020-12-26 14:31:24 -08:00
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/networktables/NetworkTables.h"
#include <cinttypes>
#include <concepts>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <span>
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include <imgui.h>
#include <imgui_stdlib.h>
2022-10-08 10:01:31 -07:00
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableValue.h>
#include <ntcore_c.h>
#include <ntcore_cpp.h>
2022-10-08 10:01:31 -07:00
#include <ntcore_cpp_types.h>
#include <wpi/MessagePack.h>
#include <wpi/SmallString.h>
#include <wpi/SpanExtras.h>
#include <wpi/StringExtras.h>
2022-10-08 10:01:31 -07:00
#include <wpi/mpack.h>
#include <wpi/raw_ostream.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
2022-10-08 10:01:31 -07:00
using namespace mpack;
namespace {
enum ShowCategory {
ShowPersistent,
ShowRetained,
ShowTransitory,
ShowAll,
};
} // namespace
static bool IsVisible(ShowCategory category, bool persistent, bool retained) {
switch (category) {
case ShowPersistent:
return persistent;
case ShowRetained:
return retained && !persistent;
case ShowTransitory:
return !retained && !persistent;
case ShowAll:
return true;
default:
return false;
}
}
static std::string StringArrayToString(std::span<const std::string> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto&& v : in) {
if (!first) {
os << ',';
}
first = false;
os << '"';
os.write_escaped(v);
os << '"';
}
os << ']';
return rv;
}
NetworkTablesModel::NetworkTablesModel()
2022-10-08 10:01:31 -07:00
: NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
: m_inst{inst}, m_poller{inst} {
m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
NetworkTablesModel::Entry::~Entry() {
if (publisher != 0) {
nt::Unpublish(publisher);
}
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::Entry::UpdateInfo(nt::TopicInfo&& info_) {
info = std::move(info_);
properties = info.GetProperties();
persistent = false;
auto it = properties.find("persistent");
if (it != properties.end()) {
if (auto v = it->get_ptr<const bool*>()) {
persistent = *v;
}
}
retained = false;
it = properties.find("retained");
if (it != properties.end()) {
if (auto v = it->get_ptr<const bool*>()) {
retained = *v;
}
}
}
static void UpdateMsgpackValueSource(NetworkTablesModel& 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:
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:
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:
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:
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:
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);
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;
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];
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);
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;
}
}
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;
}
}
}
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;
}
}
}
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];
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);
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);
}
// recurse
UpdateJsonValueSource(model, &child, j[i++], child.path, time);
2022-10-08 10:01:31 -07:00
}
break;
}
case wpi::json::value_t::string:
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:
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:
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:
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:
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;
}
}
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(
NetworkTablesModel& model, std::string_view name,
std::string_view typeStr) {
2022-10-08 10:01:31 -07:00
switch (value.type()) {
case NT_BOOLEAN:
UpdateDiscreteSource(name, value.GetBoolean() ? 1 : 0, value.time(),
true);
break;
2022-10-08 10:01:31 -07:00
case NT_INTEGER:
UpdateDiscreteSource(name, value.GetInteger(), value.time());
2022-10-08 10:01:31 -07:00
break;
case NT_FLOAT:
UpdateDiscreteSource(name, value.GetFloat(), value.time());
2022-10-08 10:01:31 -07:00
break;
case NT_DOUBLE:
UpdateDiscreteSource(name, value.GetDouble(), value.time());
break;
case NT_BOOLEAN_ARRAY:
UpdateDiscreteArray(name, value.GetBooleanArray(), value.time(),
nt::Value::MakeBoolean, true);
2022-10-08 10:01:31 -07:00
break;
case NT_INTEGER_ARRAY:
UpdateDiscreteArray(name, value.GetIntegerArray(), value.time(),
nt::Value::MakeInteger);
2022-10-08 10:01:31 -07:00
break;
case NT_FLOAT_ARRAY:
UpdateDiscreteArray(name, value.GetFloatArray(), value.time(),
nt::Value::MakeFloat);
break;
case NT_DOUBLE_ARRAY:
UpdateDiscreteArray(name, value.GetDoubleArray(), value.time(),
nt::Value::MakeDouble);
break;
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);
}
child.value = nt::Value::MakeString(arr[i++], value.time());
child.UpdateFromValue(model, child.path, "");
}
2022-10-08 10:01:31 -07:00
break;
}
2022-10-08 10:01:31 -07:00
case NT_STRING:
if (typeStr == "json") {
try {
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();
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());
UpdateMsgpackValueSource(model, this, r, name, value.last_change());
2022-10-08 10:01:31 -07:00
mpack_reader_destroy(&r);
} else if (wpi::starts_with(typeStr, "struct:")) {
auto structName = wpi::drop_front(typeStr, 7);
bool isArray = structName.ends_with("[]");
if (isArray) {
structName = wpi::drop_back(structName, 2);
}
auto desc = model.m_structDb.Find(structName);
if (desc && desc->IsValid()) {
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();
}
} else if (wpi::starts_with(typeStr, "proto:")) {
auto msg = model.m_protoDb.Find(wpi::drop_front(typeStr, 6));
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();
}
2022-10-08 10:01:31 -07:00
} else {
valueChildren.clear();
}
break;
default:
2022-10-08 10:01:31 -07:00
valueChildren.clear();
break;
}
}
void NetworkTablesModel::Update() {
bool updateTree = false;
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) {
// 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();
} else if (wpi::starts_with(info->name, "$clientpub$")) {
auto it = m_clients.find(wpi::drop_front(info->name, 11));
if (it != m_clients.end()) {
it->second.publishers.clear();
}
} else if (wpi::starts_with(info->name, "$clientsub$")) {
auto it = m_clients.find(wpi::drop_front(info->name, 11));
if (it != m_clients.end()) {
it->second.subscribers.clear();
}
}
}
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);
updateTree = true;
continue;
}
if (event.flags & nt::EventFlags::kProperties) {
updateTree = true;
}
if (entry) {
entry->UpdateTopic(std::move(event));
}
} else if (auto valueData = event.GetValueEventData()) {
auto& entry = m_entries[valueData->topic];
if (entry) {
entry->value = std::move(valueData->value);
entry->UpdateFromValue(*this);
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
entry->info.type_str == "msgpack") {
// meta topic handling
if (entry->info.name == "$clients") {
// need to remove deleted entries as UpdateClients() uses GetEntry()
if (updateTree) {
std::erase(m_sortedEntries, nullptr);
}
UpdateClients(entry->value.GetRaw());
} else if (entry->info.name == "$serverpub") {
m_server.UpdatePublishers(entry->value.GetRaw());
} else if (entry->info.name == "$serversub") {
m_server.UpdateSubscribers(entry->value.GetRaw());
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdatePublishers(entry->value.GetRaw());
}
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdateSubscribers(entry->value.GetRaw());
}
2022-10-08 10:01:31 -07:00
}
} else if (entry->value.IsRaw() &&
wpi::starts_with(entry->info.name, "/.schema/struct:") &&
entry->info.type_str == "structschema") {
// struct schema handling
auto typeStr = wpi::drop_front(entry->info.name, 16);
std::string_view schema{
reinterpret_cast<const char*>(entry->value.GetRaw().data()),
entry->value.GetRaw().size()};
std::string err;
auto desc = m_structDb.Add(typeStr, schema, &err);
if (!desc) {
fmt::print("could not decode struct '{}' schema '{}': {}\n",
entry->info.name, schema, err);
} else if (desc->IsValid()) {
// loop over all entries with this type and update
for (auto&& entryPair : m_entries) {
auto ts = entryPair.second->info.type_str;
if (!wpi::starts_with(ts, "struct:")) {
continue;
}
ts = wpi::drop_front(ts, 7);
if (ts == typeStr || (wpi::ends_with(ts, "[]") &&
wpi::drop_back(ts, 2) == typeStr)) {
entryPair.second->UpdateFromValue(*this);
}
}
}
} else if (entry->value.IsRaw() &&
wpi::starts_with(entry->info.name, "/.schema/proto:") &&
entry->info.type_str == "proto:FileDescriptorProto") {
// protobuf descriptor handling
auto filename = wpi::drop_front(entry->info.name, 15);
m_protoDb.Add(filename, entry->value.GetRaw());
// loop over all protobuf entries and update (conservatively)
for (auto&& entryPair : m_entries) {
auto ts = entryPair.second->info.type_str;
if (wpi::starts_with(ts, "proto:")) {
entryPair.second->UpdateFromValue(*this);
}
}
2022-10-08 10:01:31 -07:00
}
}
}
}
// shortcut common case (updates)
if (!updateTree) {
return;
}
// remove deleted entries
std::erase(m_sortedEntries, nullptr);
2022-10-08 10:01:31 -07:00
RebuildTree();
}
void NetworkTablesModel::RebuildTree() {
// sort by name
2022-10-08 10:01:31 -07:00
std::sort(
m_sortedEntries.begin(), m_sortedEntries.end(),
[](const auto& a, const auto& b) { return a->info.name < b->info.name; });
RebuildTreeImpl(&m_root, ShowAll);
RebuildTreeImpl(&m_persistentRoot, ShowPersistent);
RebuildTreeImpl(&m_retainedRoot, ShowRetained);
RebuildTreeImpl(&m_transitoryRoot, ShowTransitory);
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
int category) {
tree->clear();
wpi::SmallVector<std::string_view, 16> parts;
for (auto& entry : m_sortedEntries) {
2022-10-08 10:01:31 -07:00
if (!IsVisible(static_cast<ShowCategory>(category), entry->persistent,
entry->retained)) {
continue;
}
parts.clear();
2022-10-08 10:01:31 -07:00
wpi::split(entry->info.name, parts, '/', -1, false);
// ignore a raw "/" key
if (parts.empty()) {
continue;
}
// get to leaf
2022-10-08 10:01:31 -07:00
auto nodes = tree;
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
auto it =
std::find_if(nodes->begin(), nodes->end(),
[&](const auto& node) { return node.name == part; });
if (it == nodes->end()) {
nodes->emplace_back(part);
// path is from the beginning of the string to the end of the current
// part; this works because part is a reference to the internals of
2022-10-08 10:01:31 -07:00
// entry->info.name
nodes->back().path.assign(
2022-10-08 10:01:31 -07:00
entry->info.name.data(),
part.data() + part.size() - entry->info.name.data());
it = nodes->end() - 1;
}
nodes = &it->children;
}
auto it = std::find_if(nodes->begin(), nodes->end(), [&](const auto& node) {
return node.name == parts.back();
});
if (it == nodes->end()) {
nodes->emplace_back(parts.back());
// no need to set path, as it's identical to entry->name
it = nodes->end() - 1;
}
it->entry = entry;
}
}
bool NetworkTablesModel::Exists() {
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();
}
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(
std::span<const uint8_t> data) {
if (auto pubs = nt::meta::DecodeClientPublishers(data)) {
publishers = std::move(*pubs);
2022-10-08 10:01:31 -07:00
} else {
fmt::print(stderr, "Failed to update publishers\n");
}
}
2022-10-08 10:01:31 -07:00
void NetworkTablesModel::Client::UpdateSubscribers(
std::span<const uint8_t> data) {
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 {
fmt::print(stderr, "Failed to update subscribers\n");
}
}
void NetworkTablesModel::UpdateClients(std::span<const uint8_t> data) {
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;
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);
}
static bool SimplifyTypeString(std::string_view* ts) {
if (wpi::starts_with(*ts, "proto:")) {
*ts = wpi::drop_front(*ts, 6);
auto lastdot = ts->rfind('.');
if (lastdot != std::string_view::npos) {
*ts = wpi::substr(*ts, lastdot + 1);
}
if (wpi::starts_with(*ts, "Protobuf")) {
*ts = wpi::drop_front(*ts, 8);
}
return true;
} else if (wpi::starts_with(*ts, "struct:")) {
*ts = wpi::drop_front(*ts, 7);
return true;
}
return false;
}
2022-10-08 10:01:31 -07:00
static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
const char* typeStr,
NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
2022-10-08 10:01:31 -07:00
switch (val.type()) {
case NT_BOOLEAN:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "boolean", "%s",
val.GetBoolean() ? "true" : "false");
break;
case NT_INTEGER:
ImGui::LabelText(typeStr ? typeStr : "int", "%" PRId64, val.GetInteger());
break;
case NT_FLOAT:
ImGui::LabelText(typeStr ? typeStr : "double", "%.6f", val.GetFloat());
break;
case NT_DOUBLE: {
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "double",
fmt::format("%.{}f", precision).c_str(),
val.GetDouble());
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
break;
}
case NT_STRING: {
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "string", "%s",
entry.valueStr.c_str());
break;
}
case NT_BOOLEAN_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "boolean[]", "[]");
2022-10-08 10:01:31 -07:00
break;
case NT_INTEGER_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "int[]", "[]");
2022-10-08 10:01:31 -07:00
break;
case NT_FLOAT_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "float[]", "[]");
break;
case NT_DOUBLE_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "double[]", "[]");
break;
case NT_STRING_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "string[]", "[]");
break;
case NT_RAW: {
std::string_view ts = typeStr ? typeStr : "raw";
bool partial = SimplifyTypeString(&ts);
ImGui::LabelText(val.GetRaw().empty() ? "[]" : "[...]", "%s", ts.data());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (partial) {
ImGui::TextUnformatted(typeStr);
}
ImGui::Text("%u bytes", static_cast<unsigned int>(val.GetRaw().size()));
ImGui::EndTooltip();
}
break;
}
default:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
}
static constexpr size_t kTextBufferSize = 4096;
static char* GetTextBuffer(std::string_view in) {
static char textBuffer[kTextBufferSize];
size_t len = (std::min)(in.size(), kTextBufferSize - 1);
std::memcpy(textBuffer, in.data(), len);
textBuffer[len] = '\0';
return textBuffer;
}
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,
NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
2022-10-08 10:01:31 -07:00
const char* typeStr =
entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str();
ImGui::PushID(entry.info.name.c_str());
switch (val.type()) {
case NT_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
2022-10-08 10:01:31 -07:00
int v = val.GetBoolean() ? 1 : 0;
if (ImGui::Combo(typeStr ? typeStr : "boolean", &v, boolOptions, 2)) {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_BOOLEAN, "boolean");
}
nt::SetBoolean(entry.publisher, v);
}
break;
}
case NT_INTEGER: {
int64_t v = val.GetInteger();
if (ImGui::InputScalar(typeStr ? typeStr : "int", ImGuiDataType_S64, &v,
nullptr, nullptr, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_INTEGER, "int");
}
nt::SetInteger(entry.publisher, v);
}
break;
}
case NT_FLOAT: {
float v = val.GetFloat();
if (ImGui::InputFloat(typeStr ? typeStr : "float", &v, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_FLOAT, "float");
}
nt::SetFloat(entry.publisher, v);
}
break;
}
case NT_DOUBLE: {
2022-10-08 10:01:31 -07:00
double v = val.GetDouble();
unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift;
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
2022-10-08 10:01:31 -07:00
if (ImGui::InputDouble(typeStr ? typeStr : "double", &v, 0, 0,
fmt::format("%.{}f", precision).c_str(),
ImGuiInputTextFlags_EnterReturnsTrue)) {
2022-10-08 10:01:31 -07:00
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_DOUBLE, "double");
}
nt::SetDouble(entry.publisher, v);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
break;
}
case NT_STRING: {
char* v = GetTextBuffer(entry.valueStr);
2022-10-08 10:01:31 -07:00
if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
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
}
}
break;
}
case NT_BOOLEAN_ARRAY:
ImGui::LabelText("boolean[]", "[]");
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
}
ImGui::EndPopup();
2022-10-08 10:01:31 -07:00
}
break;
case NT_INTEGER_ARRAY:
ImGui::LabelText("int[]", "[]");
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
}
ImGui::EndPopup();
2022-10-08 10:01:31 -07:00
}
break;
case NT_FLOAT_ARRAY:
ImGui::LabelText("float[]", "[]");
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);
}
ImGui::EndPopup();
}
break;
case NT_DOUBLE_ARRAY:
ImGui::LabelText("double[]", "[]");
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);
}
ImGui::EndPopup();
}
break;
case NT_STRING_ARRAY:
ImGui::LabelText("string[]", "[]");
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);
}
ImGui::EndPopup();
break;
}
break;
case NT_RAW: {
std::string_view ts = typeStr ? typeStr : "raw";
bool partial = SimplifyTypeString(&ts);
ImGui::LabelText(val.GetRaw().empty() ? "[]" : "[...]", "%s", ts.data());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (partial) {
ImGui::TextUnformatted(typeStr);
}
ImGui::Text("%u bytes", static_cast<unsigned int>(val.GetRaw().size()));
ImGui::EndTooltip();
}
break;
}
case NT_RPC:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "rpc", "[...]");
break;
default:
2022-10-08 10:01:31 -07:00
ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
ImGui::PopID();
}
2022-10-08 10:01:31 -07:00
static void CreateTopicMenuItem(NetworkTablesModel* model,
std::string_view path, NT_Type type,
const char* typeStr, bool enabled) {
if (ImGui::MenuItem(typeStr, nullptr, false, enabled)) {
auto entry =
model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
if (entry->publisher == 0) {
entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
// 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
}
}
}
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,
NetworkTablesFlags flags) {
if (ImGui::BeginPopupContextItem(path.c_str())) {
ImGui::Text("%s", path.c_str());
ImGui::Separator();
DisplayNetworkTablesAddMenu(model, path, flags);
ImGui::EndPopup();
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
static void EmitValueName(DataSource* source, const char* name,
const char* path) {
if (source) {
ImGui::Selectable(name);
source->EmitDrag();
} else {
ImGui::TextUnformatted(name);
}
2022-10-08 10:01:31 -07:00
if (ImGui::BeginPopupContextItem(path)) {
ImGui::TextUnformatted(path);
ImGui::EndPopup();
}
}
2022-10-08 10:01:31 -07:00
static void EmitValueTree(
const std::vector<NetworkTablesModel::EntryValueTreeNode>& children,
NetworkTablesFlags flags) {
for (auto&& child : children) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
EmitValueName(child.source.get(), child.name.c_str(), child.path.c_str());
2022-10-08 10:01:31 -07:00
ImGui::TableNextColumn();
if (!child.valueChildren.empty()) {
auto pos = ImGui::GetCursorPos();
char label[128];
std::string_view ts = child.typeStr;
bool havePopup = SimplifyTypeString(&ts);
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();
}
}
// 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);
}
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
static void EmitEntry(NetworkTablesModel* model,
NetworkTablesModel::Entry& entry, const char* name,
NetworkTablesFlags flags, ShowCategory category) {
if (!IsVisible(category, entry.persistent, entry.retained)) {
return;
}
bool valueChildrenOpen = false;
ImGui::TableNextRow();
ImGui::TableNextColumn();
EmitValueName(entry.source.get(), name, entry.info.name.c_str());
ImGui::TableNextColumn();
if (!entry.valueChildren.empty()) {
auto pos = ImGui::GetCursorPos();
char label[128];
std::string_view ts = entry.info.type_str;
bool havePopup = SimplifyTypeString(&ts);
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);
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
ImGui::SetCursorPos(pos);
ImGui::LabelText(entry.info.type_str.c_str(), "%s", "");
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);
} else {
EmitEntryValueEditable(model, entry, flags);
}
2022-10-08 10:01:31 -07:00
if (flags & NetworkTablesFlags_ShowProperties) {
ImGui::TableNextColumn();
ImGui::Text("%s", entry.info.properties.c_str());
if (ImGui::BeginPopupContextItem(entry.info.name.c_str())) {
if (ImGui::Checkbox("persistent", &entry.persistent)) {
nt::SetTopicPersistent(entry.info.topic, entry.persistent);
}
if (ImGui::Checkbox("retained", &entry.retained)) {
if (entry.retained) {
nt::SetTopicProperty(entry.info.topic, "retained", true);
} else {
nt::DeleteTopicProperty(entry.info.topic, "retained");
}
}
ImGui::EndPopup();
}
}
if (flags & NetworkTablesFlags_ShowTimestamp) {
2022-10-08 10:01:31 -07:00
ImGui::TableNextColumn();
if (entry.value) {
2022-10-08 10:01:31 -07:00
ImGui::Text("%f", (entry.value.last_change() * 1.0e-6) -
(GetZeroTime() * 1.0e-6));
} else {
ImGui::TextUnformatted("");
}
}
2022-10-08 10:01:31 -07:00
if (flags & NetworkTablesFlags_ShowServerTimestamp) {
ImGui::TableNextColumn();
if (entry.value && entry.value.server_time() != 0) {
if (entry.value.server_time() == 1) {
ImGui::TextUnformatted("---");
} else {
ImGui::Text("%f", entry.value.server_time() * 1.0e-6);
}
} else {
ImGui::TextUnformatted("");
}
}
if (valueChildrenOpen) {
EmitValueTree(entry.valueChildren, flags);
TreePop();
}
}
2022-10-08 10:01:31 -07:00
static void EmitTree(NetworkTablesModel* model,
const std::vector<NetworkTablesModel::TreeNode>& tree,
NetworkTablesFlags flags, ShowCategory category,
bool root) {
for (auto&& node : tree) {
2022-10-08 10:01:31 -07:00
if (root && (flags & NetworkTablesFlags_ShowSpecial) == 0 &&
wpi::starts_with(node.name, '$')) {
continue;
}
if (node.entry) {
2022-10-08 10:01:31 -07:00
EmitEntry(model, *node.entry, node.name.c_str(), flags, category);
}
if (!node.children.empty()) {
2022-10-08 10:01:31 -07:00
ImGui::TableNextRow();
ImGui::TableNextColumn();
bool open =
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
2022-10-08 10:01:31 -07:00
EmitParentContextMenu(model, node.path, flags);
if (open) {
2022-10-08 10:01:31 -07:00
EmitTree(model, node.children, flags, category, false);
TreePop();
}
}
}
}
2022-10-08 10:01:31 -07:00
static void DisplayTable(NetworkTablesModel* model,
const std::vector<NetworkTablesModel::TreeNode>& tree,
NetworkTablesFlags flags, ShowCategory category) {
if (tree.empty()) {
return;
}
2022-10-08 10:01:31 -07:00
const bool showProperties = (flags & NetworkTablesFlags_ShowProperties);
const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
const bool showServerTimestamp =
(flags & NetworkTablesFlags_ShowServerTimestamp);
ImGui::BeginTable("values",
2 + (showProperties ? 1 : 0) + (showTimestamp ? 1 : 0) +
(showServerTimestamp ? 1 : 0),
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_BordersInner);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed,
0.35f * ImGui::GetWindowWidth());
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed,
12 * ImGui::GetFontSize());
if (showProperties) {
ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
12 * ImGui::GetFontSize());
}
if (showTimestamp) {
ImGui::TableSetupColumn("Time");
}
if (showServerTimestamp) {
ImGui::TableSetupColumn("Server Time");
}
ImGui::TableHeadersRow();
if (flags & NetworkTablesFlags_TreeView) {
switch (category) {
case ShowPersistent:
PushID("persistent");
break;
case ShowRetained:
PushID("retained");
break;
case ShowTransitory:
PushID("transitory");
break;
default:
break;
}
EmitTree(model, tree, flags, category, true);
if (category != ShowAll) {
PopID();
}
} else {
for (auto entry : model->GetEntries()) {
if ((flags & NetworkTablesFlags_ShowSpecial) != 0 ||
!wpi::starts_with(entry->info.name, '$')) {
EmitEntry(model, *entry, entry->info.name.c_str(), flags, category);
}
}
2022-10-08 10:01:31 -07:00
}
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
static void DisplayClient(const NetworkTablesModel::Client& client) {
if (CollapsingHeader("Publishers")) {
ImGui::BeginTable("publishers", 2, ImGuiTableFlags_Resizable);
ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
10 * ImGui::GetFontSize());
ImGui::TableSetupColumn("Topic");
ImGui::TableHeadersRow();
for (auto&& pub : client.publishers) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%" PRId64, pub.uid);
ImGui::TableNextColumn();
ImGui::Text("%s", pub.topic.c_str());
}
2022-10-08 10:01:31 -07:00
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Subscribers")) {
ImGui::BeginTable(
"subscribers", 6,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp);
ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
10 * ImGui::GetFontSize());
ImGui::TableSetupColumn("Topics", ImGuiTableColumnFlags_WidthStretch, 6.0f);
ImGui::TableSetupColumn("Periodic", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableSetupColumn("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();
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();
}
}
2022-10-08 10:01:31 -07:00
void glass::DisplayNetworkTablesInfo(NetworkTablesModel* model) {
auto inst = model->GetInstance();
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Connections")) {
ImGui::BeginTable("connections", 4, ImGuiTableFlags_Resizable);
ImGui::TableSetupColumn("Id");
ImGui::TableSetupColumn("Address");
ImGui::TableSetupColumn("Updated");
ImGui::TableSetupColumn("Proto");
ImGui::TableSetupScrollFreeze(1, 0);
ImGui::TableHeadersRow();
for (auto&& i : inst.GetConnections()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s", i.remote_id.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", i.remote_ip.c_str());
ImGui::TableNextColumn();
ImGui::Text("%llu",
static_cast<unsigned long long>( // NOLINT(runtime/int)
i.last_update));
ImGui::TableNextColumn();
ImGui::Text("%d.%d", i.protocol_version >> 8, i.protocol_version & 0xff);
}
2022-10-08 10:01:31 -07:00
ImGui::EndTable();
}
2022-10-08 10:01:31 -07:00
auto netMode = inst.GetNetworkMode();
if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT4) {
if (CollapsingHeader("Server")) {
PushID("Server");
ImGui::Indent();
DisplayClient(model->GetServer());
ImGui::Unindent();
PopID();
}
if (CollapsingHeader("Clients")) {
ImGui::Indent();
for (auto&& client : model->GetClients()) {
if (CollapsingHeader(client.second.id.c_str())) {
PushID(client.second.id.c_str());
ImGui::Indent();
ImGui::Text("%s (version %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();
}
}
2022-10-08 10:01:31 -07:00
}
2022-10-08 10:01:31 -07:00
void glass::DisplayNetworkTables(NetworkTablesModel* model,
NetworkTablesFlags flags) {
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);
} else {
2022-10-08 10:01:31 -07:00
if (CollapsingHeader("Persistent Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetPersistentTreeRoot(), flags,
ShowPersistent);
}
if (CollapsingHeader("Retained Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetRetainedTreeRoot(), flags, ShowRetained);
}
if (CollapsingHeader("Transitory Values", ImGuiTreeNodeFlags_DefaultOpen)) {
DisplayTable(model, model->GetTransitoryTreeRoot(), flags,
ShowTransitory);
}
}
}
2021-03-16 22:05:41 -07:00
void NetworkTablesFlagsSettings::Update() {
if (!m_pTreeView) {
auto& storage = GetStorage();
m_pTreeView =
&storage.GetBool("tree", m_defaultFlags & NetworkTablesFlags_TreeView);
2022-10-08 10:01:31 -07:00
m_pCombinedView = &storage.GetBool(
"combined", m_defaultFlags & NetworkTablesFlags_CombinedView);
m_pShowSpecial = &storage.GetBool(
"special", m_defaultFlags & NetworkTablesFlags_ShowSpecial);
m_pShowProperties = &storage.GetBool(
"properties", m_defaultFlags & NetworkTablesFlags_ShowProperties);
m_pShowTimestamp = &storage.GetBool(
2021-03-16 22:05:41 -07:00
"timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
2022-10-08 10:01:31 -07:00
m_pShowServerTimestamp = &storage.GetBool(
"serverTimestamp",
m_defaultFlags & NetworkTablesFlags_ShowServerTimestamp);
m_pCreateNoncanonicalKeys = &storage.GetBool(
2021-03-16 22:05:41 -07:00
"createNonCanonical",
m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
m_pPrecision = &storage.GetInt(
"precision", (m_defaultFlags & NetworkTablesFlags_Precision) >>
kNetworkTablesFlags_PrecisionBitShift);
}
m_flags &= ~(
2022-10-08 10:01:31 -07:00
NetworkTablesFlags_TreeView | NetworkTablesFlags_CombinedView |
NetworkTablesFlags_ShowSpecial | NetworkTablesFlags_ShowProperties |
NetworkTablesFlags_ShowTimestamp |
NetworkTablesFlags_ShowServerTimestamp |
NetworkTablesFlags_CreateNoncanonicalKeys | NetworkTablesFlags_Precision);
m_flags |=
2021-03-16 22:05:41 -07:00
(*m_pTreeView ? NetworkTablesFlags_TreeView : 0) |
2022-10-08 10:01:31 -07:00
(*m_pCombinedView ? NetworkTablesFlags_CombinedView : 0) |
(*m_pShowSpecial ? NetworkTablesFlags_ShowSpecial : 0) |
(*m_pShowProperties ? NetworkTablesFlags_ShowProperties : 0) |
2021-03-16 22:05:41 -07:00
(*m_pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
2022-10-08 10:01:31 -07:00
(*m_pShowServerTimestamp ? NetworkTablesFlags_ShowServerTimestamp : 0) |
2021-03-16 22:05:41 -07:00
(*m_pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
: 0) |
(*m_pPrecision << kNetworkTablesFlags_PrecisionBitShift);
2021-03-16 22:05:41 -07:00
}
void NetworkTablesFlagsSettings::DisplayMenu() {
if (!m_pTreeView) {
return;
}
ImGui::MenuItem("Tree View", "", m_pTreeView);
2022-10-08 10:01:31 -07:00
ImGui::MenuItem("Combined View", "", m_pCombinedView);
ImGui::MenuItem("Show Special", "", m_pShowSpecial);
ImGui::MenuItem("Show Properties", "", m_pShowProperties);
2021-03-16 22:05:41 -07:00
ImGui::MenuItem("Show Timestamp", "", m_pShowTimestamp);
2022-10-08 10:01:31 -07:00
ImGui::MenuItem("Show Server Timestamp", "", m_pShowServerTimestamp);
if (ImGui::BeginMenu("Decimal Precision")) {
static const char* precisionOptions[] = {"1", "2", "3", "4", "5",
"6", "7", "8", "9", "10"};
for (int i = 1; i <= 10; i++) {
if (ImGui::MenuItem(precisionOptions[i - 1], nullptr,
i == *m_pPrecision)) {
*m_pPrecision = i;
}
}
ImGui::EndMenu();
}
2021-03-16 22:05:41 -07:00
ImGui::Separator();
ImGui::MenuItem("Allow creation of non-canonical keys", "",
m_pCreateNoncanonicalKeys);
}
void NetworkTablesView::Display() {
m_flags.Update();
DisplayNetworkTables(m_model, m_flags.GetFlags());
}
void NetworkTablesView::Settings() {
m_flags.DisplayMenu();
DisplayNetworkTablesAddMenu(m_model, {}, m_flags.GetFlags());
}
bool NetworkTablesView::HasSettings() {
return true;
}