[wpiutil, ntcore] Add structured data support (#5391)

This adds support for two serialization formats for complex data types:

- Protobuf for complex objects with variable length internals that need forward and backward wire compatibility (lower speed, more flexible)
- Raw struct (ByteBuffer-style) for fixed-length objects (higher speed, less flexible)

Deserialization can be done either by creating a new object (for immutable objects) or overwriting the contents of an existing object (for mutable objects).

Implementing classes should provide inner classes that implement the Protobuf or Struct interface (in Java) or specialize the wpi::Protobuf or wpi::Struct struct (in C++). It is possible for classes to implement both. If the class itself does not implement serialization, it's possible for third parties/users to provide an implementation instead.

Uses the Google protobuf implementation for C++ and the QuickBuffers alternative protobuf implementation for Java.
This commit is contained in:
Peter Johnson
2023-10-19 21:41:47 -07:00
committed by GitHub
parent ecb7cfa9ef
commit cf54d9ccb7
133 changed files with 13506 additions and 90 deletions

View File

@@ -19,7 +19,8 @@ jobs:
- os: macOS-12
name: macOS
container: ""
flags: "-DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON"
env: "PATH=\"/usr/local/opt/protobuf@3/bin:$PATH\""
flags: "-DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON -DCMAKE_LIBRARY_PATH=/usr/local/opt/protobuf@3/lib -DProtobuf_INCLUDE_DIR=/usr/local/opt/protobuf@3/include -DProtobuf_PROTOC_EXECUTABLE=/usr/local/opt/protobuf@3/bin/protoc"
name: "Build - ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
@@ -27,10 +28,14 @@ jobs:
steps:
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 ninja-build
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 libprotobuf-dev protobuf-compiler ninja-build
- name: Install QuickBuffers (Linux)
if: runner.os == 'Linux'
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.2/protoc-gen-quickbuf_1.3.2_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.2_amd64.deb
- name: Install opencv (macOS)
run: brew install opencv ninja
run: brew install opencv protobuf@3 ninja
if: runner.os == 'macOS'
- name: Set up Python 3.8 (macOS)

View File

@@ -29,7 +29,11 @@ jobs:
container: wpilib/roborio-cross-ubuntu:2024-22.04
steps:
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14 libprotobuf-dev protobuf-compiler ninja-build
- name: Install QuickBuffers
if: runner.os == 'Linux'
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.2/protoc-gen-quickbuf_1.3.2_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.2_amd64.deb
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
@@ -40,7 +44,7 @@ jobs:
- uses: actions/checkout@v3
- name: configure
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
run: mkdir build && cd build && cmake -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
env:
SCCACHE_GHA_ENABLED: "true"

View File

@@ -31,6 +31,7 @@ includeOtherLibs {
^cscore
^fmt/
^gtest/
^google/
^hal/
^imgui
^implot

View File

@@ -176,6 +176,12 @@ endif()
find_package(LIBSSH 0.7.1)
find_package(Protobuf REQUIRED)
find_program(Quickbuf_EXECUTABLE
NAMES protoc-gen-quickbuf
DOC "The Quickbuf protoc plugin"
)
if (WITH_FLAT_INSTALL)
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
set(WPINET_DEP_REPLACE "include($\{SELF_DIR\}/wpinet-config.cmake)")

View File

@@ -20,6 +20,8 @@ By default, all libraries except for the HAL and WPILib get built with a default
The jinja2 pip package is needed to generate classes for NT4's pubsub.
The protobuf library and compiler are needed for protobuf generation. The QuickBuffers protoc-gen package is also required when Java is being built.
OpenCV needs to be findable by CMake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build OpenCV from source and install it.
If you want JNI and Java, you will need a JDK of at least version 11 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.

View File

@@ -24,6 +24,7 @@ plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'com.diffplug.spotless' version '6.20.0' apply false
id 'com.github.spotbugs' version '5.1.3' apply false
id 'com.google.protobuf' version '0.9.3' apply false
}
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')

View File

@@ -142,6 +142,8 @@ doxygen {
//TODO: building memory docs causes search to break
exclude 'wpi/memory/**'
exclude '*.pb.h'
aliases 'effects=\\par <i>Effects:</i>^^',
'notes=\\par <i>Notes:</i>^^',
'requires=\\par <i>Requires:</i>^^',

View File

@@ -24,6 +24,7 @@ includeOtherLibs {
^fmt/
^fields/
^frc/
^google/
^imgui
^networktables/
^ntcore

View File

@@ -14,6 +14,8 @@
#include <vector>
#include <fmt/format.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <networktables/NetworkTableInstance.h>
@@ -114,36 +116,37 @@ void NetworkTablesModel::Entry::UpdateInfo(nt::TopicInfo&& info_) {
}
}
static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
static void UpdateMsgpackValueSource(NetworkTablesModel& model,
NetworkTablesModel::ValueSource* out,
mpack_reader_t& r, std::string_view name,
int64_t time) {
mpack_tag_t tag = mpack_read_tag(&r);
switch (mpack_tag_type(&tag)) {
case mpack::mpack_type_bool:
out->UpdateFromValue(
nt::Value::MakeBoolean(mpack_tag_bool_value(&tag), time), name, "");
out->value = nt::Value::MakeBoolean(mpack_tag_bool_value(&tag), time);
out->UpdateFromValue(model, name, "");
break;
case mpack::mpack_type_int:
out->UpdateFromValue(
nt::Value::MakeInteger(mpack_tag_int_value(&tag), time), name, "");
out->value = nt::Value::MakeInteger(mpack_tag_int_value(&tag), time);
out->UpdateFromValue(model, name, "");
break;
case mpack::mpack_type_uint:
out->UpdateFromValue(
nt::Value::MakeInteger(mpack_tag_uint_value(&tag), time), name, "");
out->value = nt::Value::MakeInteger(mpack_tag_uint_value(&tag), time);
out->UpdateFromValue(model, name, "");
break;
case mpack::mpack_type_float:
out->UpdateFromValue(
nt::Value::MakeFloat(mpack_tag_float_value(&tag), time), name, "");
out->value = nt::Value::MakeFloat(mpack_tag_float_value(&tag), time);
out->UpdateFromValue(model, name, "");
break;
case mpack::mpack_type_double:
out->UpdateFromValue(
nt::Value::MakeDouble(mpack_tag_double_value(&tag), time), name, "");
out->value = nt::Value::MakeDouble(mpack_tag_double_value(&tag), time);
out->UpdateFromValue(model, name, "");
break;
case mpack::mpack_type_str: {
std::string str;
mpack_read_str(&r, &tag, &str);
out->UpdateFromValue(nt::Value::MakeString(std::move(str), time), name,
"");
out->value = nt::Value::MakeString(std::move(str), time);
out->UpdateFromValue(model, name, "");
break;
}
case mpack::mpack_type_bin:
@@ -164,7 +167,8 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
child.path = fmt::format("{}{}", name, child.name);
}
++i;
UpdateMsgpackValueSource(&child, r, child.path, time); // recurse
UpdateMsgpackValueSource(model, &child, r, child.path,
time); // recurse
}
mpack_done_array(&r);
break;
@@ -186,7 +190,7 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
auto it = elems.find(key);
if (it != elems.end()) {
auto& child = out->valueChildren[it->second];
UpdateMsgpackValueSource(&child, r, child.path, time);
UpdateMsgpackValueSource(model, &child, r, child.path, time);
elems.erase(it);
} else {
added = true;
@@ -194,7 +198,7 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
auto& child = out->valueChildren.back();
child.name = std::move(key);
child.path = fmt::format("{}/{}", name, child.name);
UpdateMsgpackValueSource(&child, r, child.path, time);
UpdateMsgpackValueSource(model, &child, r, child.path, time);
}
}
}
@@ -219,7 +223,318 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
}
}
static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
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,
const wpi::json& j, std::string_view name,
int64_t time) {
switch (j.type()) {
@@ -237,7 +552,7 @@ static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
auto it = elems.find(kv.key());
if (it != elems.end()) {
auto& child = out->valueChildren[it->second];
UpdateJsonValueSource(&child, kv.value(), child.path, time);
UpdateJsonValueSource(model, &child, kv.value(), child.path, time);
elems.erase(it);
} else {
added = true;
@@ -245,7 +560,7 @@ static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
auto& child = out->valueChildren.back();
child.name = kv.key();
child.path = fmt::format("{}/{}", name, child.name);
UpdateJsonValueSource(&child, kv.value(), child.path, time);
UpdateJsonValueSource(model, &child, kv.value(), child.path, time);
}
}
// erase unmatched keys
@@ -273,30 +588,30 @@ static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
child.name = fmt::format("[{}]", i);
child.path = fmt::format("{}{}", name, child.name);
}
UpdateJsonValueSource(&child, j[i++], child.path, time); // recurse
// recurse
UpdateJsonValueSource(model, &child, j[i++], child.path, time);
}
break;
}
case wpi::json::value_t::string:
out->UpdateFromValue(
nt::Value::MakeString(j.get_ref<const std::string&>(), time), name,
"");
out->value = nt::Value::MakeString(j.get_ref<const std::string&>(), time);
out->UpdateFromValue(model, name, "");
break;
case wpi::json::value_t::boolean:
out->UpdateFromValue(nt::Value::MakeBoolean(j.get<bool>(), time), name,
"");
out->value = nt::Value::MakeBoolean(j.get<bool>(), time);
out->UpdateFromValue(model, name, "");
break;
case wpi::json::value_t::number_integer:
out->UpdateFromValue(nt::Value::MakeInteger(j.get<int64_t>(), time), name,
"");
out->value = nt::Value::MakeInteger(j.get<int64_t>(), time);
out->UpdateFromValue(model, name, "");
break;
case wpi::json::value_t::number_unsigned:
out->UpdateFromValue(nt::Value::MakeInteger(j.get<uint64_t>(), time),
name, "");
out->value = nt::Value::MakeInteger(j.get<uint64_t>(), time);
out->UpdateFromValue(model, name, "");
break;
case wpi::json::value_t::number_float:
out->UpdateFromValue(nt::Value::MakeDouble(j.get<double>(), time), name,
"");
out->value = nt::Value::MakeDouble(j.get<double>(), time);
out->UpdateFromValue(model, name, "");
break;
default:
out->value = {};
@@ -336,8 +651,8 @@ void NetworkTablesModel::ValueSource::UpdateDiscreteArray(
}
void NetworkTablesModel::ValueSource::UpdateFromValue(
nt::Value&& v, std::string_view name, std::string_view typeStr) {
value = v;
NetworkTablesModel& model, std::string_view name,
std::string_view typeStr) {
switch (value.type()) {
case NT_BOOLEAN:
UpdateDiscreteSource(name, value.GetBoolean() ? 1 : 0, value.time(),
@@ -381,15 +696,16 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
child.name = fmt::format("[{}]", i);
child.path = fmt::format("{}{}", name, child.name);
}
child.UpdateFromValue(nt::Value::MakeString(arr[i++], value.time()),
child.path, "");
child.value = nt::Value::MakeString(arr[i++], value.time());
child.UpdateFromValue(model, child.path, "");
}
break;
}
case NT_STRING:
if (typeStr == "json") {
try {
UpdateJsonValueSource(this, wpi::json::parse(value.GetString()), name,
UpdateJsonValueSource(model, this,
wpi::json::parse(value.GetString()), name,
value.last_change());
} catch (wpi::json::exception&) {
// ignore
@@ -407,9 +723,57 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
if (typeStr == "msgpack") {
mpack_reader_t r;
mpack_reader_init_data(&r, value.GetRaw());
UpdateMsgpackValueSource(this, r, name, value.last_change());
UpdateMsgpackValueSource(model, this, r, name, value.last_change());
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();
}
} else {
valueChildren.clear();
}
@@ -473,8 +837,8 @@ void NetworkTablesModel::Update() {
} else if (auto valueData = event.GetValueEventData()) {
auto& entry = m_entries[valueData->topic];
if (entry) {
entry->UpdateFromValue(std::move(valueData->value), entry->info.name,
entry->info.type_str);
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
@@ -499,6 +863,46 @@ void NetworkTablesModel::Update() {
it->second.UpdateSubscribers(entry->value.GetRaw());
}
}
} 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);
}
}
}
}
}
@@ -663,6 +1067,24 @@ void NetworkTablesModel::UpdateClients(std::span<const uint8_t> data) {
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;
}
static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
const char* typeStr,
NetworkTablesFlags flags) {
@@ -717,9 +1139,20 @@ static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
case NT_STRING_ARRAY:
ImGui::LabelText(typeStr ? typeStr : "string[]", "[]");
break;
case NT_RAW:
ImGui::LabelText(typeStr ? typeStr : "raw", "[...]");
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:
ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
@@ -1022,10 +1455,20 @@ static void EmitEntryValueEditable(NetworkTablesModel* model,
break;
}
break;
case NT_RAW:
ImGui::LabelText(typeStr ? typeStr : "raw",
val.GetRaw().empty() ? "[]" : "[...]");
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:
ImGui::LabelText(typeStr ? typeStr : "rpc", "[...]");
break;
@@ -1164,15 +1607,25 @@ static void EmitValueTree(
ImGui::TableNextColumn();
if (!child.valueChildren.empty()) {
char label[64];
if (child.valueChildrenMap) {
wpi::format_to_n_c_str(label, sizeof(label), "{{...}}##v_{}",
child.name);
} else {
wpi::format_to_n_c_str(label, sizeof(label), "[...]##v_{}", child.name);
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();
}
}
if (TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth)) {
// make it look like a normal label w/type
ImGui::SetCursorPos(pos);
ImGui::LabelText(child.valueChildrenMap ? "{...}" : "[...]", "%s", "");
if (valueChildrenOpen) {
EmitValueTree(child.valueChildren, flags);
TreePop();
}
@@ -1197,19 +1650,21 @@ static void EmitEntry(NetworkTablesModel* model,
ImGui::TableNextColumn();
if (!entry.valueChildren.empty()) {
auto pos = ImGui::GetCursorPos();
char label[64];
if (entry.valueChildrenMap) {
wpi::format_to_n_c_str(label, sizeof(label), "{{...}}##v_{}",
entry.info.name.c_str());
} else {
wpi::format_to_n_c_str(label, sizeof(label), "[...]##v_{}",
entry.info.name.c_str());
}
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());
valueChildrenOpen =
TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth |
ImGuiTreeNodeFlags_AllowItemOverlap);
if (havePopup) {
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(entry.info.type_str.c_str());
ImGui::EndTooltip();
}
}
// make it look like a normal label w/type
ImGui::SetCursorPos(pos);
ImGui::LabelText(entry.info.type_str.c_str(), "%s", "");

View File

@@ -18,6 +18,8 @@
#include <ntcore_cpp.h>
#include <wpi/DenseMap.h>
#include <wpi/json.h>
#include <wpi/protobuf/ProtobufMessageDatabase.h>
#include <wpi/struct/DynamicStruct.h>
#include "glass/Model.h"
#include "glass/View.h"
@@ -31,7 +33,7 @@ class NetworkTablesModel : public Model {
struct EntryValueTreeNode;
struct ValueSource {
void UpdateFromValue(nt::Value&& v, std::string_view name,
void UpdateFromValue(NetworkTablesModel& model, std::string_view name,
std::string_view typeStr);
/** The latest value. */
@@ -40,6 +42,9 @@ class NetworkTablesModel : public Model {
/** String representation of the value (for arrays / complex values). */
std::string valueStr;
/** Data type */
std::string typeStr;
/** Data source (for numeric values). */
std::unique_ptr<DataSource> source;
@@ -73,6 +78,10 @@ class NetworkTablesModel : public Model {
Entry& operator=(const Entry&) = delete;
~Entry();
void UpdateFromValue(NetworkTablesModel& model) {
ValueSource::UpdateFromValue(model, info.name, info.type_str);
}
void UpdateTopic(nt::Event&& event) {
if (std::holds_alternative<nt::TopicInfo>(event.data)) {
UpdateInfo(std::get<nt::TopicInfo>(std::move(event.data)));
@@ -158,6 +167,9 @@ class NetworkTablesModel : public Model {
Entry* GetEntry(std::string_view name);
Entry* AddEntry(NT_Topic topic);
wpi::StructDescriptorDatabase& GetStructDatabase() { return m_structDb; }
wpi::ProtobufMessageDatabase& GetProtobufDatabase() { return m_protoDb; }
private:
void RebuildTree();
void RebuildTreeImpl(std::vector<TreeNode>* tree, int category);
@@ -177,6 +189,9 @@ class NetworkTablesModel : public Model {
std::map<std::string, Client, std::less<>> m_clients;
Client m_server;
wpi::StructDescriptorDatabase m_structDb;
wpi::ProtobufMessageDatabase m_protoDb;
};
using NetworkTablesFlags = int;

View File

@@ -54,6 +54,11 @@ if (WITH_JAVA)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
file(GLOB QUICKBUF_JAR
${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar)
set(CMAKE_JAVA_INCLUDE_PATH wpimath.jar ${QUICKBUF_JAR})
file(GLOB ntcore_jni_src
src/main/native/cpp/jni/*.cpp
${WPILIB_BINARY_DIR}/ntcore/generated/main/native/cpp/jni/*.cpp)

View File

@@ -7,16 +7,22 @@ package edu.wpi.first.networktables;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.concurrent.Event;
import edu.wpi.first.util.datalog.DataLog;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import us.hebi.quickbuf.ProtoMessage;
/**
* NetworkTables Instance.
@@ -86,6 +92,7 @@ public final class NetworkTableInstance implements AutoCloseable {
public synchronized void close() {
if (m_owned && m_handle != 0) {
m_listeners.close();
m_schemas.forEach((k, v) -> v.close());
NetworkTablesJNI.destroyInstance(m_handle);
m_handle = 0;
}
@@ -176,15 +183,119 @@ public final class NetworkTableInstance implements AutoCloseable {
handle = topic.getHandle();
}
topic = new {{ t.TypeName }}Topic(this, handle);
m_topics.put(name, topic);
{{ t.TypeName }}Topic wrapTopic = new {{ t.TypeName }}Topic(this, handle);
m_topics.put(name, wrapTopic);
// also cache by handle
m_topicsByHandle.put(handle, topic);
m_topicsByHandle.put(handle, wrapTopic);
return ({{ t.TypeName }}Topic) topic;
return wrapTopic;
}
{% endfor %}
/**
* Get protobuf-encoded value topic.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param name topic name
* @param proto protobuf serialization implementation
* @return ProtobufTopic
*/
public <T, MessageType extends ProtoMessage<?>>
ProtobufTopic<T> getProtobufTopic(String name, Protobuf<T, MessageType> proto) {
Topic topic = m_topics.get(name);
if (topic instanceof ProtobufTopic<?>
&& ((ProtobufTopic<?>) topic).getProto().equals(proto)) {
@SuppressWarnings("unchecked")
ProtobufTopic<T> wrapTopic = (ProtobufTopic<T>) topic;
return wrapTopic;
}
int handle;
if (topic == null) {
handle = NetworkTablesJNI.getTopic(m_handle, name);
} else {
handle = topic.getHandle();
}
ProtobufTopic<T> wrapTopic = ProtobufTopic.wrap(this, handle, proto);
m_topics.put(name, wrapTopic);
// also cache by handle
m_topicsByHandle.put(handle, wrapTopic);
return wrapTopic;
}
/**
* Get struct-encoded value topic.
*
* @param <T> value class (inferred from struct)
* @param name topic name
* @param struct struct serialization implementation
* @return StructTopic
*/
public <T>
StructTopic<T> getStructTopic(String name, Struct<T> struct) {
Topic topic = m_topics.get(name);
if (topic instanceof StructTopic<?>
&& ((StructTopic<?>) topic).getStruct().equals(struct)) {
@SuppressWarnings("unchecked")
StructTopic<T> wrapTopic = (StructTopic<T>) topic;
return wrapTopic;
}
int handle;
if (topic == null) {
handle = NetworkTablesJNI.getTopic(m_handle, name);
} else {
handle = topic.getHandle();
}
StructTopic<T> wrapTopic = StructTopic.wrap(this, handle, struct);
m_topics.put(name, wrapTopic);
// also cache by handle
m_topicsByHandle.put(handle, wrapTopic);
return wrapTopic;
}
/**
* Get struct-encoded value array topic.
*
* @param <T> value class (inferred from struct)
* @param name topic name
* @param struct struct serialization implementation
* @return StructArrayTopic
*/
public <T>
StructArrayTopic<T> getStructArrayTopic(String name, Struct<T> struct) {
Topic topic = m_topics.get(name);
if (topic instanceof StructArrayTopic<?>
&& ((StructArrayTopic<?>) topic).getStruct().equals(struct)) {
@SuppressWarnings("unchecked")
StructArrayTopic<T> wrapTopic = (StructArrayTopic<T>) topic;
return wrapTopic;
}
int handle;
if (topic == null) {
handle = NetworkTablesJNI.getTopic(m_handle, name);
} else {
handle = topic.getHandle();
}
StructArrayTopic<T> wrapTopic = StructArrayTopic.wrap(this, handle, struct);
m_topics.put(name, wrapTopic);
// also cache by handle
m_topicsByHandle.put(handle, wrapTopic);
return wrapTopic;
}
private Topic[] topicHandlesToTopics(int[] handles) {
Topic[] topics = new Topic[handles.length];
for (int i = 0; i < handles.length; i++) {
@@ -1050,6 +1161,78 @@ public final class NetworkTableInstance implements AutoCloseable {
return m_listeners.addLogger(minLevel, maxLevel, func);
}
/**
* Returns whether there is a data schema already registered with the given name that this
* instance has published. This does NOT perform a check as to whether the schema has already
* been published by another node on the network.
*
* @param name Name (the string passed as the data type for topics using this schema)
* @return True if schema already registered
*/
public boolean hasSchema(String name) {
return m_schemas.containsKey("/.schema/" + name);
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas
* are published just like normal topics, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for topics using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
public void addSchema(String name, String type, byte[] schema) {
m_schemas.computeIfAbsent("/.schema/" + name, k -> {
RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}");
pub.setDefault(schema);
return pub;
});
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas
* are published just like normal topics, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for topics using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
public void addSchema(String name, String type, String schema) {
m_schemas.computeIfAbsent("/.schema/" + name, k -> {
RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}");
pub.setDefault(StandardCharsets.UTF_8.encode(schema));
return pub;
});
}
/**
* Registers a protobuf schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param proto protobuf serialization object
*/
public void addSchema(Protobuf<?, ?> proto) {
proto.forEachDescriptor(
this::hasSchema,
(typeString, schema) -> addSchema(typeString, "proto:FileDescriptorProto", schema));
}
/**
* Registers a struct schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param struct struct serialization object
*/
public void addSchema(Struct<?> struct) {
addSchemaImpl(struct, new HashSet<>());
}
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -1067,6 +1250,22 @@ public final class NetworkTableInstance implements AutoCloseable {
return m_handle;
}
private void addSchemaImpl(Struct<?> struct, Set<String> seen) {
String typeString = struct.getTypeString();
if (hasSchema(typeString)) {
return;
}
if (!seen.add(typeString)) {
throw new UnsupportedOperationException(typeString + ": circular reference with " + seen);
}
addSchema(typeString, "structschema", struct.getSchema());
for (Struct<?> inner : struct.getNested()) {
addSchemaImpl(inner, seen);
}
seen.remove(typeString);
}
private boolean m_owned;
private int m_handle;
private final ConcurrentMap<String, RawPublisher> m_schemas = new ConcurrentHashMap<>();
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.networktables;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
@@ -13,8 +15,10 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import us.hebi.quickbuf.ProtoMessage;
/** A network table that knows its subtable path. */
@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class NetworkTable {
/** The path separator for sub-tables and keys. */
public static final char PATH_SEPARATOR = '/';
@@ -250,6 +254,44 @@ public final class NetworkTable {
return m_inst.getStringArrayTopic(m_pathWithSep + name);
}
/**
* Get protobuf-encoded value topic.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param name topic name
* @param proto protobuf serialization implementation
* @return ProtobufTopic
*/
public <T, MessageType extends ProtoMessage<?>> ProtobufTopic<T> getProtobufTopic(
String name, Protobuf<T, MessageType> proto) {
return m_inst.getProtobufTopic(m_pathWithSep + name, proto);
}
/**
* Get struct-encoded value topic.
*
* @param <T> value class (inferred from struct)
* @param name topic name
* @param struct struct serialization implementation
* @return StructTopic
*/
public <T> StructTopic<T> getStructTopic(String name, Struct<T> struct) {
return m_inst.getStructTopic(m_pathWithSep + name, struct);
}
/**
* Get struct-encoded value array topic.
*
* @param <T> value class (inferred from struct)
* @param name topic name
* @param struct struct serialization implementation
* @return StructTopic
*/
public <T> StructArrayTopic<T> getStructArrayTopic(String name, Struct<T> struct) {
return m_inst.getStructArrayTopic(m_pathWithSep + name, struct);
}
private final ConcurrentMap<String, NetworkTableEntry> m_entries = new ConcurrentHashMap<>();
/**

View File

@@ -0,0 +1,17 @@
// 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.
package edu.wpi.first.networktables;
/**
* NetworkTables protobuf-encoded value entry.
*
* <p>Unlike NetworkTableEntry, the entry goes away when close() is called.
*
* @param <T> value class
*/
public interface ProtobufEntry<T> extends ProtobufSubscriber<T>, ProtobufPublisher<T> {
/** Stops publishing the entry if it's published. */
void unpublish();
}

View File

@@ -0,0 +1,209 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.protobuf.ProtobufBuffer;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
/**
* NetworkTables protobuf-encoded value implementation.
*
* @param <T> value class
*/
final class ProtobufEntryImpl<T> extends EntryBase implements ProtobufEntry<T> {
/**
* Constructor.
*
* @param topic Topic
* @param handle Native handle
* @param defaultValue Default value for get()
*/
ProtobufEntryImpl(
ProtobufTopic<T> topic,
ProtobufBuffer<T, ?> buf,
int handle,
T defaultValue,
boolean schemaPublished) {
super(handle);
m_topic = topic;
m_defaultValue = defaultValue;
m_buf = buf;
m_schemaPublished = schemaPublished;
}
@Override
public ProtobufTopic<T> getTopic() {
return m_topic;
}
@Override
public T get() {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public T get(T defaultValue) {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public boolean getInto(T out) {
byte[] raw = NetworkTablesJNI.getRaw(m_handle, m_emptyRaw);
if (raw.length == 0) {
return false;
}
try {
synchronized (m_buf) {
m_buf.readInto(out, raw);
return true;
}
} catch (IOException e) {
// ignored
}
return false;
}
@Override
public TimestampedObject<T> getAtomic() {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public TimestampedObject<T> getAtomic(T defaultValue) {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public TimestampedObject<T>[] readQueue() {
TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
@SuppressWarnings("unchecked")
TimestampedObject<T>[] arr = (TimestampedObject<T>[]) new TimestampedObject<?>[raw.length];
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i].value == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
TimestampedObject<T>[] newArr =
(TimestampedObject<T>[]) new TimestampedObject<?>[raw.length - err];
int i = 0;
for (TimestampedObject<T> e : arr) {
if (e.value != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@Override
public T[] readQueueValues() {
byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(m_topic.getProto().getTypeClass(), raw.length);
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i] == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
T[] newArr = (T[]) Array.newInstance(m_topic.getProto().getTypeClass(), raw.length - err);
int i = 0;
for (T e : arr) {
if (e != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@Override
public void set(T value, long time) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getProto());
}
ByteBuffer bb = m_buf.write(value);
NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
}
} catch (IOException e) {
// ignore
}
}
@Override
public void setDefault(T value) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getProto());
}
ByteBuffer bb = m_buf.write(value);
NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
}
} catch (IOException e) {
// ignore
}
}
@Override
public void unpublish() {
NetworkTablesJNI.unpublish(m_handle);
}
private T fromRaw(byte[] raw, T defaultValue) {
if (raw.length == 0) {
return defaultValue;
}
try {
synchronized (m_buf) {
return m_buf.read(raw);
}
} catch (IOException e) {
return defaultValue;
}
}
private TimestampedObject<T> fromRaw(TimestampedRaw raw, T defaultValue) {
if (raw.value.length == 0) {
return new TimestampedObject<T>(0, 0, defaultValue);
}
try {
synchronized (m_buf) {
return new TimestampedObject<T>(raw.timestamp, raw.serverTime, m_buf.read(raw.value));
}
} catch (IOException e) {
return new TimestampedObject<T>(0, 0, defaultValue);
}
}
private final ProtobufTopic<T> m_topic;
private final T m_defaultValue;
private final ProtobufBuffer<T, ?> m_buf;
private boolean m_schemaPublished;
private static final byte[] m_emptyRaw = new byte[] {};
}

View File

@@ -0,0 +1,52 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Consumer;
/**
* NetworkTables protobuf-encoded value publisher.
*
* @param <T> value class
*/
public interface ProtobufPublisher<T> extends Publisher, Consumer<T> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
ProtobufTopic<T> getTopic();
/**
* Publish a new value using current NT time.
*
* @param value value to publish
*/
default void set(T value) {
set(value, 0);
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void set(T value, long time);
/**
* Publish a default value. On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void setDefault(T value);
@Override
default void accept(T value) {
set(value);
}
}

View File

@@ -0,0 +1,94 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Supplier;
/**
* NetworkTables protobuf-encoded value subscriber.
*
* @param <T> value class
*/
@SuppressWarnings("PMD.MissingOverride")
public interface ProtobufSubscriber<T> extends Subscriber, Supplier<T> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
ProtobufTopic<T> getTopic();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the stored default value.
*
* @return value
*/
T get();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
T get(T defaultValue);
/**
* Get the last published value, replacing the contents in place of an existing object. If no
* value has been published or the value cannot be unpacked, does not replace the contents and
* returns false. This function will not work (will throw UnsupportedOperationException) unless T
* is mutable (and the implementation of Struct implements unpackInto).
*
* <p>Note: due to Java language limitations, it's not possible to validate at compile time that
* the out parameter is mutable.
*
* @param out object to replace contents of; must be mutable
* @return true if successful
* @throws UnsupportedOperationException if T is immutable
*/
boolean getInto(T out);
/**
* Get the last published value along with its timestamp. If no value has been published or the
* value cannot be unpacked, returns the stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedObject<T> getAtomic();
/**
* Get the last published value along with its timestamp. If no value has been published or the
* value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedObject<T> getAtomic(T defaultValue);
/**
* Get an array of all valid value changes since the last call to readQueue. Also provides a
* timestamp for each value. Values that cannot be unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of timestamped values; empty array if no valid new changes have been published
* since the previous call.
*/
TimestampedObject<T>[] readQueue();
/**
* Get an array of all valid value changes since the last call to readQueue. Values that cannot be
* unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of values; empty array if no valid new changes have been published since the
* previous call.
*/
T[] readQueueValues();
}

View File

@@ -0,0 +1,178 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.protobuf.ProtobufBuffer;
/**
* NetworkTables protobuf-encoded value topic.
*
* @param <T> value class
*/
public final class ProtobufTopic<T> extends Topic {
private ProtobufTopic(Topic topic, Protobuf<T, ?> proto) {
super(topic.m_inst, topic.m_handle);
m_proto = proto;
}
private ProtobufTopic(NetworkTableInstance inst, int handle, Protobuf<T, ?> proto) {
super(inst, handle);
m_proto = proto;
}
/**
* Create a ProtobufTopic from a generic topic.
*
* @param <T> value class (inferred from proto)
* @param topic generic topic
* @param proto protobuf serialization implementation
* @return ProtobufTopic for value class
*/
public static <T> ProtobufTopic<T> wrap(Topic topic, Protobuf<T, ?> proto) {
return new ProtobufTopic<T>(topic, proto);
}
/**
* Create a ProtobufTopic from a native handle; generally NetworkTableInstance.getProtobufTopic()
* should be used instead.
*
* @param <T> value class (inferred from proto)
* @param inst Instance
* @param handle Native handle
* @param proto protobuf serialization implementation
* @return ProtobufTopic for value class
*/
public static <T> ProtobufTopic<T> wrap(
NetworkTableInstance inst, int handle, Protobuf<T, ?> proto) {
return new ProtobufTopic<T>(inst, handle, proto);
}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object is not closed.
*
* <p>Subscribers that do not match the published data type do not return any values. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options subscribe options
* @return subscriber
*/
public ProtobufSubscriber<T> subscribe(T defaultValue, PubSubOption... options) {
return new ProtobufEntryImpl<T>(
this,
ProtobufBuffer.create(m_proto),
NetworkTablesJNI.subscribe(
m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
defaultValue,
false);
}
/**
* Create a new publisher to the topic.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
public ProtobufPublisher<T> publish(PubSubOption... options) {
m_inst.addSchema(m_proto);
return new ProtobufEntryImpl<T>(
this,
ProtobufBuffer.create(m_proto),
NetworkTablesJNI.publish(
m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
null,
true);
}
/**
* Create a new publisher to the topic, with type string and initial properties.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
* @throws IllegalArgumentException if properties is not a JSON object
*/
public ProtobufPublisher<T> publishEx(String properties, PubSubOption... options) {
m_inst.addSchema(m_proto);
return new ProtobufEntryImpl<T>(
this,
ProtobufBuffer.create(m_proto),
NetworkTablesJNI.publishEx(
m_handle,
NetworkTableType.kRaw.getValue(),
m_proto.getTypeString(),
properties,
options),
null,
true);
}
/**
* Create a new entry for the topic.
*
* <p>Entries act as a combination of a subscriber and a weak publisher. The subscriber is active
* as long as the entry is not closed. The publisher is created when the entry is first written
* to, and remains active until either unpublish() is called or the entry is closed.
*
* <p>It is not possible to use two different data types with the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine if the data type matches,
* use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options publish and/or subscribe options
* @return entry
*/
public ProtobufEntry<T> getEntry(T defaultValue, PubSubOption... options) {
return new ProtobufEntryImpl<T>(
this,
ProtobufBuffer.create(m_proto),
NetworkTablesJNI.getEntry(
m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
defaultValue,
false);
}
public Protobuf<T, ?> getProto() {
return m_proto;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof ProtobufTopic)) {
return false;
}
return super.equals(other) && m_proto == ((ProtobufTopic<?>) other).m_proto;
}
@Override
public int hashCode() {
return super.hashCode() ^ m_proto.hashCode();
}
private final Protobuf<T, ?> m_proto;
}

View File

@@ -0,0 +1,17 @@
// 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.
package edu.wpi.first.networktables;
/**
* NetworkTables struct-encoded array value entry.
*
* <p>Unlike NetworkTableEntry, the entry goes away when close() is called.
*
* @param <T> value class
*/
public interface StructArrayEntry<T> extends StructArraySubscriber<T>, StructArrayPublisher<T> {
/** Stops publishing the entry if it's published. */
void unpublish();
}

View File

@@ -0,0 +1,197 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.struct.StructBuffer;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
/**
* NetworkTables struct-encoded value implementation.
*
* @param <T> value class
*/
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
final class StructArrayEntryImpl<T> extends EntryBase implements StructArrayEntry<T> {
/**
* Constructor.
*
* @param topic Topic
* @param handle Native handle
* @param defaultValue Default value for get()
*/
StructArrayEntryImpl(
StructArrayTopic<T> topic,
StructBuffer<T> buf,
int handle,
T[] defaultValue,
boolean schemaPublished) {
super(handle);
m_topic = topic;
m_defaultValue = defaultValue;
m_buf = buf;
m_schemaPublished = schemaPublished;
}
@Override
public StructArrayTopic<T> getTopic() {
return m_topic;
}
@Override
public T[] get() {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public T[] get(T[] defaultValue) {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public TimestampedObject<T[]> getAtomic() {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public TimestampedObject<T[]> getAtomic(T[] defaultValue) {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public TimestampedObject<T[]>[] readQueue() {
TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
@SuppressWarnings("unchecked")
TimestampedObject<T[]>[] arr = (TimestampedObject<T[]>[]) new TimestampedObject<?>[raw.length];
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i].value == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
TimestampedObject<T[]>[] newArr =
(TimestampedObject<T[]>[]) new TimestampedObject<?>[raw.length - err];
int i = 0;
for (TimestampedObject<T[]> e : arr) {
if (e.value != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@Override
public T[][] readQueueValues() {
byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
@SuppressWarnings("unchecked")
T[][] arr = (T[][]) Array.newInstance(Array.class, raw.length);
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i] == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
T[][] newArr = (T[][]) Array.newInstance(Array.class, raw.length - err);
int i = 0;
for (T[] e : arr) {
if (e != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
@Override
public void set(T[] value, long time) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getStruct());
}
ByteBuffer bb = m_buf.writeArray(value);
NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
}
} catch (RuntimeException e) {
// ignore
}
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
@Override
public void setDefault(T[] value) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getStruct());
}
ByteBuffer bb = m_buf.writeArray(value);
NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
}
} catch (RuntimeException e) {
// ignore
}
}
@Override
public void unpublish() {
NetworkTablesJNI.unpublish(m_handle);
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private T[] fromRaw(byte[] raw, T[] defaultValue) {
if (raw.length == 0) {
return defaultValue;
}
try {
synchronized (m_buf) {
return m_buf.readArray(raw);
}
} catch (RuntimeException e) {
return defaultValue;
}
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private TimestampedObject<T[]> fromRaw(TimestampedRaw raw, T[] defaultValue) {
if (raw.value.length == 0) {
return new TimestampedObject<T[]>(0, 0, defaultValue);
}
try {
synchronized (m_buf) {
return new TimestampedObject<T[]>(
raw.timestamp, raw.serverTime, m_buf.readArray(raw.value));
}
} catch (RuntimeException e) {
return new TimestampedObject<T[]>(0, 0, defaultValue);
}
}
private final StructArrayTopic<T> m_topic;
private final T[] m_defaultValue;
private final StructBuffer<T> m_buf;
private boolean m_schemaPublished;
private static final byte[] m_emptyRaw = new byte[] {};
}

View File

@@ -0,0 +1,52 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Consumer;
/**
* NetworkTables struct-encoded array value publisher.
*
* @param <T> value class
*/
public interface StructArrayPublisher<T> extends Publisher, Consumer<T[]> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
StructArrayTopic<T> getTopic();
/**
* Publish a new value using current NT time.
*
* @param value value to publish
*/
default void set(T[] value) {
set(value, 0);
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void set(T[] value, long time);
/**
* Publish a default value. On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void setDefault(T[] value);
@Override
default void accept(T[] value) {
set(value);
}
}

View File

@@ -0,0 +1,79 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Supplier;
/**
* NetworkTables struct-encoded array value subscriber.
*
* @param <T> value class
*/
@SuppressWarnings("PMD.MissingOverride")
public interface StructArraySubscriber<T> extends Subscriber, Supplier<T[]> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
StructArrayTopic<T> getTopic();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the stored default value.
*
* @return value
*/
T[] get();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
T[] get(T[] defaultValue);
/**
* Get the last published value along with its timestamp. If no value has been published or the
* value cannot be unpacked, returns the stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedObject<T[]> getAtomic();
/**
* Get the last published value along with its timestamp. If no value has been published or the
* value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedObject<T[]> getAtomic(T[] defaultValue);
/**
* Get an array of all valid value changes since the last call to readQueue. Also provides a
* timestamp for each value. Values that cannot be unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of timestamped values; empty array if no valid new changes have been published
* since the previous call.
*/
TimestampedObject<T[]>[] readQueue();
/**
* Get an array of all valid value changes since the last call to readQueue. Values that cannot be
* unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of values; empty array if no valid new changes have been published since the
* previous call.
*/
T[][] readQueueValues();
}

View File

@@ -0,0 +1,178 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.struct.Struct;
import edu.wpi.first.util.struct.StructBuffer;
/**
* NetworkTables struct-encoded array value topic.
*
* @param <T> value class
*/
public final class StructArrayTopic<T> extends Topic {
private StructArrayTopic(Topic topic, Struct<T> struct) {
super(topic.m_inst, topic.m_handle);
m_struct = struct;
}
private StructArrayTopic(NetworkTableInstance inst, int handle, Struct<T> struct) {
super(inst, handle);
m_struct = struct;
}
/**
* Create a StructArrayTopic from a generic topic.
*
* @param <T> value class (inferred from struct)
* @param topic generic topic
* @param struct struct serialization implementation
* @return StructArrayTopic for value class
*/
public static <T> StructArrayTopic<T> wrap(Topic topic, Struct<T> struct) {
return new StructArrayTopic<T>(topic, struct);
}
/**
* Create a StructArrayTopic from a native handle; generally
* NetworkTableInstance.getStructArrayTopic() should be used instead.
*
* @param <T> value class (inferred from struct)
* @param inst Instance
* @param handle Native handle
* @param struct struct serialization implementation
* @return StructArrayTopic for value class
*/
public static <T> StructArrayTopic<T> wrap(
NetworkTableInstance inst, int handle, Struct<T> struct) {
return new StructArrayTopic<T>(inst, handle, struct);
}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object is not closed.
*
* <p>Subscribers that do not match the published data type do not return any values. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options subscribe options
* @return subscriber
*/
public StructArraySubscriber<T> subscribe(T[] defaultValue, PubSubOption... options) {
return new StructArrayEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.subscribe(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
defaultValue,
false);
}
/**
* Create a new publisher to the topic.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
public StructArrayPublisher<T> publish(PubSubOption... options) {
m_inst.addSchema(m_struct);
return new StructArrayEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.publish(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
null,
true);
}
/**
* Create a new publisher to the topic, with type string and initial properties.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
* @throws IllegalArgumentException if properties is not a JSON object
*/
public StructArrayPublisher<T> publishEx(String properties, PubSubOption... options) {
m_inst.addSchema(m_struct);
return new StructArrayEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.publishEx(
m_handle,
NetworkTableType.kRaw.getValue(),
m_struct.getTypeString() + "[]",
properties,
options),
null,
true);
}
/**
* Create a new entry for the topic.
*
* <p>Entries act as a combination of a subscriber and a weak publisher. The subscriber is active
* as long as the entry is not closed. The publisher is created when the entry is first written
* to, and remains active until either unpublish() is called or the entry is closed.
*
* <p>It is not possible to use two different data types with the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine if the data type matches,
* use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options publish and/or subscribe options
* @return entry
*/
public StructArrayEntry<T> getEntry(T[] defaultValue, PubSubOption... options) {
return new StructArrayEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.getEntry(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
defaultValue,
false);
}
public Struct<T> getStruct() {
return m_struct;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof StructArrayTopic)) {
return false;
}
return super.equals(other) && m_struct == ((StructArrayTopic<?>) other).m_struct;
}
@Override
public int hashCode() {
return super.hashCode() ^ m_struct.hashCode();
}
private final Struct<T> m_struct;
}

View File

@@ -0,0 +1,17 @@
// 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.
package edu.wpi.first.networktables;
/**
* NetworkTables struct-encoded value entry.
*
* <p>Unlike NetworkTableEntry, the entry goes away when close() is called.
*
* @param <T> value class
*/
public interface StructEntry<T> extends StructSubscriber<T>, StructPublisher<T> {
/** Stops publishing the entry if it's published. */
void unpublish();
}

View File

@@ -0,0 +1,207 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.struct.StructBuffer;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
/**
* NetworkTables struct-encoded value implementation.
*
* @param <T> value class
*/
final class StructEntryImpl<T> extends EntryBase implements StructEntry<T> {
/**
* Constructor.
*
* @param topic Topic
* @param handle Native handle
* @param defaultValue Default value for get()
*/
StructEntryImpl(
StructTopic<T> topic,
StructBuffer<T> buf,
int handle,
T defaultValue,
boolean schemaPublished) {
super(handle);
m_topic = topic;
m_defaultValue = defaultValue;
m_buf = buf;
m_schemaPublished = schemaPublished;
}
@Override
public StructTopic<T> getTopic() {
return m_topic;
}
@Override
public T get() {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public T get(T defaultValue) {
return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public boolean getInto(T out) {
byte[] raw = NetworkTablesJNI.getRaw(m_handle, m_emptyRaw);
if (raw.length == 0) {
return false;
}
synchronized (m_buf) {
m_buf.readInto(out, raw);
return true;
}
}
@Override
public TimestampedObject<T> getAtomic() {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
}
@Override
public TimestampedObject<T> getAtomic(T defaultValue) {
return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
}
@Override
public TimestampedObject<T>[] readQueue() {
TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
@SuppressWarnings("unchecked")
TimestampedObject<T>[] arr = (TimestampedObject<T>[]) new TimestampedObject<?>[raw.length];
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i].value == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
TimestampedObject<T>[] newArr =
(TimestampedObject<T>[]) new TimestampedObject<?>[raw.length - err];
int i = 0;
for (TimestampedObject<T> e : arr) {
if (e.value != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@Override
public T[] readQueueValues() {
byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(m_topic.getStruct().getTypeClass(), raw.length);
int err = 0;
for (int i = 0; i < raw.length; i++) {
arr[i] = fromRaw(raw[i], null);
if (arr[i] == null) {
err++;
}
}
// discard bad values
if (err > 0) {
@SuppressWarnings("unchecked")
T[] newArr = (T[]) Array.newInstance(m_topic.getStruct().getTypeClass(), raw.length - err);
int i = 0;
for (T e : arr) {
if (e != null) {
arr[i] = e;
i++;
}
}
arr = newArr;
}
return arr;
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
@Override
public void set(T value, long time) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getStruct());
}
ByteBuffer bb = m_buf.write(value);
NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
}
} catch (RuntimeException e) {
// ignore
}
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
@Override
public void setDefault(T value) {
try {
synchronized (m_buf) {
if (!m_schemaPublished) {
m_schemaPublished = true;
m_topic.getInstance().addSchema(m_buf.getStruct());
}
ByteBuffer bb = m_buf.write(value);
NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
}
} catch (RuntimeException e) {
// ignore
}
}
@Override
public void unpublish() {
NetworkTablesJNI.unpublish(m_handle);
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private T fromRaw(byte[] raw, T defaultValue) {
if (raw.length == 0) {
return defaultValue;
}
try {
synchronized (m_buf) {
return m_buf.read(raw);
}
} catch (RuntimeException e) {
return defaultValue;
}
}
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private TimestampedObject<T> fromRaw(TimestampedRaw raw, T defaultValue) {
if (raw.value.length == 0) {
return new TimestampedObject<T>(0, 0, defaultValue);
}
try {
synchronized (m_buf) {
return new TimestampedObject<T>(raw.timestamp, raw.serverTime, m_buf.read(raw.value));
}
} catch (RuntimeException e) {
return new TimestampedObject<T>(0, 0, defaultValue);
}
}
private final StructTopic<T> m_topic;
private final T m_defaultValue;
private final StructBuffer<T> m_buf;
private boolean m_schemaPublished;
private static final byte[] m_emptyRaw = new byte[] {};
}

View File

@@ -0,0 +1,52 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Consumer;
/**
* NetworkTables struct-encoded value publisher.
*
* @param <T> value class
*/
public interface StructPublisher<T> extends Publisher, Consumer<T> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
StructTopic<T> getTopic();
/**
* Publish a new value using current NT time.
*
* @param value value to publish
*/
default void set(T value) {
set(value, 0);
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void set(T value, long time);
/**
* Publish a default value. On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void setDefault(T value);
@Override
default void accept(T value) {
set(value);
}
}

View File

@@ -0,0 +1,94 @@
// 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.
package edu.wpi.first.networktables;
import java.util.function.Supplier;
/**
* NetworkTables struct-encoded value subscriber.
*
* @param <T> value class
*/
@SuppressWarnings("PMD.MissingOverride")
public interface StructSubscriber<T> extends Subscriber, Supplier<T> {
/**
* Get the corresponding topic.
*
* @return Topic
*/
@Override
StructTopic<T> getTopic();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the stored default value.
*
* @return value
*/
T get();
/**
* Get the last published value. If no value has been published or the value cannot be unpacked,
* returns the passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
T get(T defaultValue);
/**
* Get the last published value, replacing the contents in place of an existing object. If no
* value has been published or the value cannot be unpacked, does not replace the contents and
* returns false. This function will not work (will throw UnsupportedOperationException) unless T
* is mutable (and the implementation of Struct implements unpackInto).
*
* <p>Note: due to Java language limitations, it's not possible to validate at compile time that
* the out parameter is mutable.
*
* @param out object to replace contents of; must be mutable
* @return true if successful, false if no value has been published
* @throws UnsupportedOperationException if T is immutable
*/
boolean getInto(T out);
/**
* Get the last published value along with its timestamp. If no value has been published or the
* value cannot be unpacked, returns the stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedObject<T> getAtomic();
/**
* Get the last published value along with its timestamp If no value has been published or the
* value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedObject<T> getAtomic(T defaultValue);
/**
* Get an array of all valid value changes since the last call to readQueue. Also provides a
* timestamp for each value. Values that cannot be unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of timestamped values; empty array if no valid new changes have been published
* since the previous call.
*/
TimestampedObject<T>[] readQueue();
/**
* Get an array of all value changes since the last call to readQueue. Values that cannot be
* unpacked are dropped.
*
* <p>The "poll storage" subscribe option can be used to set the queue depth.
*
* @return Array of values; empty array if no valid new changes have been published since the
* previous call.
*/
T[] readQueueValues();
}

View File

@@ -0,0 +1,177 @@
// 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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.struct.Struct;
import edu.wpi.first.util.struct.StructBuffer;
/**
* NetworkTables struct-encoded value topic.
*
* @param <T> value class
*/
public final class StructTopic<T> extends Topic {
private StructTopic(Topic topic, Struct<T> struct) {
super(topic.m_inst, topic.m_handle);
m_struct = struct;
}
private StructTopic(NetworkTableInstance inst, int handle, Struct<T> struct) {
super(inst, handle);
m_struct = struct;
}
/**
* Create a StructTopic from a generic topic.
*
* @param <T> value class (inferred from struct)
* @param topic generic topic
* @param struct struct serialization implementation
* @return StructTopic for value class
*/
public static <T> StructTopic<T> wrap(Topic topic, Struct<T> struct) {
return new StructTopic<T>(topic, struct);
}
/**
* Create a StructTopic from a native handle; generally NetworkTableInstance.getStructTopic()
* should be used instead.
*
* @param <T> value class (inferred from struct)
* @param inst Instance
* @param handle Native handle
* @param struct struct serialization implementation
* @return StructTopic for value class
*/
public static <T> StructTopic<T> wrap(NetworkTableInstance inst, int handle, Struct<T> struct) {
return new StructTopic<T>(inst, handle, struct);
}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object is not closed.
*
* <p>Subscribers that do not match the published data type do not return any values. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options subscribe options
* @return subscriber
*/
public StructSubscriber<T> subscribe(T defaultValue, PubSubOption... options) {
return new StructEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.subscribe(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString(), options),
defaultValue,
false);
}
/**
* Create a new publisher to the topic.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
public StructPublisher<T> publish(PubSubOption... options) {
m_inst.addSchema(m_struct);
return new StructEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.publish(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString(), options),
null,
true);
}
/**
* Create a new publisher to the topic, with type string and initial properties.
*
* <p>The publisher is only active as long as the returned object is not closed.
*
* <p>It is not possible to publish two different data types to the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored). To determine if
* the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
* @throws IllegalArgumentException if properties is not a JSON object
*/
public StructPublisher<T> publishEx(String properties, PubSubOption... options) {
m_inst.addSchema(m_struct);
return new StructEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.publishEx(
m_handle,
NetworkTableType.kRaw.getValue(),
m_struct.getTypeString(),
properties,
options),
null,
true);
}
/**
* Create a new entry for the topic.
*
* <p>Entries act as a combination of a subscriber and a weak publisher. The subscriber is active
* as long as the entry is not closed. The publisher is created when the entry is first written
* to, and remains active until either unpublish() is called or the entry is closed.
*
* <p>It is not possible to use two different data types with the same topic. Conflicts between
* publishers are typically resolved by the server on a first-come, first-served basis. Any
* published values that do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine if the data type matches,
* use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a getter function
* @param options publish and/or subscribe options
* @return entry
*/
public StructEntry<T> getEntry(T defaultValue, PubSubOption... options) {
return new StructEntryImpl<T>(
this,
StructBuffer.create(m_struct),
NetworkTablesJNI.getEntry(
m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString(), options),
defaultValue,
false);
}
public Struct<T> getStruct() {
return m_struct;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof StructTopic)) {
return false;
}
return super.equals(other) && m_struct == ((StructTopic<?>) other).m_struct;
}
@Override
public int hashCode() {
return super.hashCode() ^ m_struct.hashCode();
}
private final Struct<T> m_struct;
}

View File

@@ -0,0 +1,33 @@
// 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.
package edu.wpi.first.networktables;
/** NetworkTables timestamped object. */
public final class TimestampedObject<T> {
/**
* Create a timestamped value.
*
* @param timestamp timestamp in local time base
* @param serverTime timestamp in server time base
* @param value value
*/
public TimestampedObject(long timestamp, long serverTime, T value) {
this.timestamp = timestamp;
this.serverTime = serverTime;
this.value = value;
}
/** Timestamp in local time base. */
@SuppressWarnings("MemberName")
public final long timestamp;
/** Timestamp in server time base. May be 0 or 1 for locally set values. */
@SuppressWarnings("MemberName")
public final long serverTime;
/** Value. */
@SuppressWarnings("MemberName")
public final T value;
}

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include <wpi/DataLog.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/json.h>
@@ -1481,6 +1482,41 @@ void LocalStorage::StopDataLog(NT_DataLogger logger) {
}
}
bool LocalStorage::HasSchema(std::string_view name) {
std::scoped_lock lock{m_mutex};
wpi::SmallString<128> fullName{"/.schema/"};
fullName += name;
auto it = m_impl.m_schemas.find(fullName);
return it != m_impl.m_schemas.end();
}
void LocalStorage::AddSchema(std::string_view name, std::string_view type,
std::span<const uint8_t> schema) {
std::scoped_lock lock{m_mutex};
wpi::SmallString<128> fullName{"/.schema/"};
fullName += name;
auto& pubHandle = m_impl.m_schemas[fullName];
if (pubHandle != 0) {
return;
}
auto topic = m_impl.GetOrCreateTopic(fullName);
if (topic->localPublishers.size() >= kMaxPublishers) {
WPI_ERROR(m_impl.m_logger,
"reached maximum number of publishers to '{}', not publishing",
topic->name);
return;
}
pubHandle = m_impl
.AddLocalPublisher(topic, {{"retained", true}},
PubSubConfig{NT_RAW, type, {}})
->handle;
m_impl.SetDefaultEntryValue(pubHandle, Value::MakeRaw(schema));
}
void LocalStorage::Reset() {
std::scoped_lock lock{m_mutex};
m_impl.m_network = nullptr;

View File

@@ -321,6 +321,13 @@ class LocalStorage final : public net::ILocalStorage {
std::string_view logPrefix);
void StopDataLog(NT_DataLogger logger);
//
// Schema functions
//
bool HasSchema(std::string_view name);
void AddSchema(std::string_view name, std::string_view type,
std::span<const uint8_t> schema);
void Reset();
private:
@@ -549,6 +556,9 @@ class LocalStorage final : public net::ILocalStorage {
// string-based listeners
VectorSet<ListenerData*> m_topicPrefixListeners;
// schema publishers
wpi::StringMap<NT_Publisher> m_schemas;
// topic functions
void NotifyTopic(TopicData* topic, unsigned int eventFlags);

View File

@@ -7,9 +7,14 @@
#include <wpi/json.h>
#include "networktables/GenericEntry.h"
#include "networktables/NetworkTableInstance.h"
using namespace nt;
NetworkTableInstance Topic::GetInstance() const {
return NetworkTableInstance{GetInstanceFromHandle(m_handle)};
}
wpi::json Topic::GetProperty(std::string_view name) const {
return ::nt::GetTopicProperty(m_handle, name);
}

View File

@@ -625,6 +625,15 @@ NT_Listener NT_AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level,
return nt::AddPolledLogger(poller, min_level, max_level);
}
NT_Bool NT_HasSchema(NT_Inst inst, const char* name) {
return nt::HasSchema(inst, name);
}
void NT_AddSchema(NT_Inst inst, const char* name, const char* type,
const uint8_t* schema, size_t schemaSize) {
nt::AddSchema(inst, name, type, {schema, schemaSize});
}
void NT_DisposeValue(NT_Value* value) {
switch (value->type) {
case NT_UNASSIGNED:

View File

@@ -782,4 +782,19 @@ NT_Listener AddPolledLogger(NT_ListenerPoller poller, unsigned int minLevel,
}
}
bool HasSchema(NT_Inst inst, std::string_view name) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
return ii->localStorage.HasSchema(name);
} else {
return false;
}
}
void AddSchema(NT_Inst inst, std::string_view name, std::string_view type,
std::span<const uint8_t> schema) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->localStorage.AddSchema(name, type, schema);
}
}
} // namespace nt

View File

@@ -14,8 +14,11 @@
#include <wpi/StringMap.h>
#include <wpi/mutex.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "networktables/NetworkTableEntry.h"
#include "networktables/Topic.h"
#include "ntcore_c.h"
namespace nt {
@@ -29,9 +32,15 @@ class FloatTopic;
class IntegerArrayTopic;
class IntegerTopic;
class NetworkTableInstance;
template <wpi::ProtobufSerializable T>
class ProtobufTopic;
class RawTopic;
class StringArrayTopic;
class StringTopic;
template <wpi::StructSerializable T>
class StructArrayTopic;
template <wpi::StructSerializable T>
class StructTopic;
class Topic;
/**
@@ -220,6 +229,39 @@ class NetworkTable final {
*/
StringArrayTopic GetStringArrayTopic(std::string_view name) const;
/**
* Gets a protobuf serialized value topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::ProtobufSerializable T>
ProtobufTopic<T> GetProtobufTopic(std::string_view name) const {
return ProtobufTopic<T>{GetTopic(name)};
}
/**
* Gets a raw struct serialized value topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::StructSerializable T>
StructTopic<T> GetStructTopic(std::string_view name) const {
return StructTopic<T>{GetTopic(name)};
}
/**
* Gets a raw struct serialized array topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::StructSerializable T>
StructArrayTopic<T> GetStructArrayTopic(std::string_view name) const {
return StructArrayTopic<T>{GetTopic(name)};
}
/**
* Returns the table at the specified key. If there is no table at the
* specified key, it will create a new table

View File

@@ -13,6 +13,9 @@
#include <utility>
#include <vector>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "networktables/NetworkTable.h"
#include "networktables/NetworkTableEntry.h"
#include "ntcore_c.h"
@@ -29,9 +32,15 @@ class FloatTopic;
class IntegerArrayTopic;
class IntegerTopic;
class MultiSubscriber;
template <wpi::ProtobufSerializable T>
class ProtobufTopic;
class RawTopic;
class StringArrayTopic;
class StringTopic;
template <wpi::StructSerializable T>
class StructArrayTopic;
template <wpi::StructSerializable T>
class StructTopic;
class Subscriber;
class Topic;
@@ -238,6 +247,33 @@ class NetworkTableInstance final {
*/
StringArrayTopic GetStringArrayTopic(std::string_view name) const;
/**
* Gets a protobuf serialized value topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::ProtobufSerializable T>
ProtobufTopic<T> GetProtobufTopic(std::string_view name) const;
/**
* Gets a raw struct serialized value topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::StructSerializable T>
StructTopic<T> GetStructTopic(std::string_view name) const;
/**
* Gets a raw struct serialized array topic.
*
* @param name topic name
* @return Topic
*/
template <wpi::StructSerializable T>
StructArrayTopic<T> GetStructArrayTopic(std::string_view name) const;
/**
* Get Published Topics.
*
@@ -718,6 +754,75 @@ class NetworkTableInstance final {
/** @} */
/**
* @{
* @name Schema Functions
*/
/**
* Returns whether there is a data schema already registered with the given
* name. This does NOT perform a check as to whether the schema has already
* been published by another node on the network.
*
* @param name Name (the string passed as the data type for topics using this
* schema)
* @return True if schema already registered
*/
bool HasSchema(std::string_view name) const;
/**
* Registers a data schema. Data schemas provide information for how a
* certain data type string can be decoded. The type string of a data schema
* indicates the type of the schema itself (e.g. "protobuf" for protobuf
* schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are
* published just like normal topics, with the name being generated from the
* provided name: "/.schema/<name>". Duplicate calls to this function with
* the same name are silently ignored.
*
* @param name Name (the string passed as the data type for topics using this
* schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
void AddSchema(std::string_view name, std::string_view type,
std::span<const uint8_t> schema);
/**
* Registers a data schema. Data schemas provide information for how a
* certain data type string can be decoded. The type string of a data schema
* indicates the type of the schema itself (e.g. "protobuf" for protobuf
* schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are
* published just like normal topics, with the name being generated from the
* provided name: "/.schema/<name>". Duplicate calls to this function with
* the same name are silently ignored.
*
* @param name Name (the string passed as the data type for topics using this
* schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
void AddSchema(std::string_view name, std::string_view type,
std::string_view schema);
/**
* Registers a protobuf schema. Duplicate calls to this function with the same
* name are silently ignored.
*
* @tparam T protobuf serializable type
* @param msg protobuf message
*/
template <wpi::ProtobufSerializable T>
void AddProtobufSchema(wpi::ProtobufMessage<T>& msg);
/**
* Registers a struct schema. Duplicate calls to this function with the same
* name are silently ignored.
*
* @param T struct serializable type
*/
template <wpi::StructSerializable T>
void AddStructSchema();
/**
* Equality operator. Returns true if both instances refer to the same
* native handle.

View File

@@ -38,6 +38,24 @@ inline NT_Inst NetworkTableInstance::GetHandle() const {
return m_handle;
}
template <wpi::ProtobufSerializable T>
inline ProtobufTopic<T> NetworkTableInstance::GetProtobufTopic(
std::string_view name) const {
return ProtobufTopic<T>{GetTopic(name)};
}
template <wpi::StructSerializable T>
inline StructTopic<T> NetworkTableInstance::GetStructTopic(
std::string_view name) const {
return StructTopic<T>{GetTopic(name)};
}
template <wpi::StructSerializable T>
inline StructArrayTopic<T> NetworkTableInstance::GetStructArrayTopic(
std::string_view name) const {
return StructArrayTopic<T>{GetTopic(name)};
}
inline std::vector<Topic> NetworkTableInstance::GetTopics() {
auto handles = ::nt::GetTopics(m_handle, "", 0);
return {handles.begin(), handles.end()};
@@ -223,4 +241,36 @@ inline NT_Listener NetworkTableInstance::AddLogger(unsigned int min_level,
return ::nt::AddLogger(m_handle, min_level, max_level, std::move(func));
}
inline bool NetworkTableInstance::HasSchema(std::string_view name) const {
return ::nt::HasSchema(m_handle, name);
}
inline void NetworkTableInstance::AddSchema(std::string_view name,
std::string_view type,
std::span<const uint8_t> schema) {
::nt::AddSchema(m_handle, name, type, schema);
}
inline void NetworkTableInstance::AddSchema(std::string_view name,
std::string_view type,
std::string_view schema) {
::nt::AddSchema(m_handle, name, type, schema);
}
template <wpi::ProtobufSerializable T>
void NetworkTableInstance::AddProtobufSchema(wpi::ProtobufMessage<T>& msg) {
msg.ForEachProtobufDescriptor(
[this](auto typeString) { return HasSchema(typeString); },
[this](auto typeString, auto schema) {
AddSchema(typeString, "proto:FileDescriptorProto", schema);
});
}
template <wpi::StructSerializable T>
void NetworkTableInstance::AddStructSchema() {
wpi::ForEachStructSchema<T>([this](auto typeString, auto schema) {
AddSchema(typeString, "structschema", schema);
});
}
} // namespace nt

View File

@@ -0,0 +1,474 @@
// 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.
#pragma once
#include <stdint.h>
#include <atomic>
#include <concepts>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/mutex.h>
#include <wpi/protobuf/Protobuf.h>
#include "networktables/NetworkTableInstance.h"
#include "networktables/Topic.h"
#include "ntcore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace nt {
template <wpi::ProtobufSerializable T>
class ProtobufTopic;
/**
* NetworkTables protobuf-encoded value subscriber.
*/
template <wpi::ProtobufSerializable T>
class ProtobufSubscriber : public Subscriber {
public:
using TopicType = ProtobufTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
ProtobufSubscriber() = default;
/**
* Construct from a subscriber handle; recommended to use
* ProtobufTopic::Subscribe() instead.
*
* @param handle Native handle
* @param msg Protobuf message
* @param defaultValue Default value
*/
ProtobufSubscriber(NT_Subscriber handle, wpi::ProtobufMessage<T> msg,
T defaultValue)
: Subscriber{handle},
m_msg{std::move(msg)},
m_defaultValue{std::move(defaultValue)} {}
ProtobufSubscriber(const ProtobufSubscriber&) = delete;
ProtobufSubscriber& operator=(const ProtobufSubscriber&) = delete;
ProtobufSubscriber(ProtobufSubscriber&& rhs)
: Subscriber{std::move(rhs)},
m_msg{std::move(rhs.m_msg)},
m_defaultValue{std::move(rhs.defaultValue)} {}
ProtobufSubscriber& operator=(ProtobufSubscriber&& rhs) {
Subscriber::operator=(std::move(rhs));
m_msg = std::move(rhs.m_msg);
m_defaultValue = std::move(rhs.defaultValue);
return *this;
}
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* stored default value.
*
* @return value
*/
ValueType Get() const { return Get(m_defaultValue); }
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
ValueType Get(const T& defaultValue) const {
return GetAtomic(defaultValue).value;
}
/**
* Get the last published value, replacing the contents in place of an
* existing object. If no value has been published or the value cannot be
* unpacked, does not replace the contents and returns false.
*
* @param[out] out object to replace contents of
* @return true if successful
*/
bool GetInto(T* out) {
wpi::SmallVector<uint8_t, 128> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.empty()) {
return false;
} else {
std::scoped_lock lock{m_mutex};
return m_msg.UnpackInto(out, view.value);
}
}
/**
* Get the last published value along with its timestamp
* If no value has been published or the value cannot be unpacked, returns the
* stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); }
/**
* Get the last published value along with its timestamp.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedValueType GetAtomic(const T& defaultValue) const {
wpi::SmallVector<uint8_t, 128> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (!view.value.empty()) {
std::scoped_lock lock{m_mutex};
if (auto optval = m_msg.Unpack(view.value)) {
return {view.time, view.serverTime, *optval};
}
}
return {0, 0, defaultValue};
}
/**
* Get an array of all valid value changes since the last call to ReadQueue.
* Also provides a timestamp for each value. Values that cannot be unpacked
* are dropped.
*
* @note The "poll storage" subscribe option can be used to set the queue
* depth.
*
* @return Array of timestamped values; empty array if no valid new changes
* have been published since the previous call.
*/
std::vector<TimestampedValueType> ReadQueue() {
auto raw = ::nt::ReadQueueRaw(m_subHandle);
std::vector<TimestampedValueType> rv;
rv.reserve(raw.size());
std::scoped_lock lock{m_mutex};
for (auto&& r : raw) {
if (auto optval = m_msg.Unpack(r.value)) {
rv.emplace_back(r.time, r.serverTime, *optval);
}
}
return rv;
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return ProtobufTopic<T>{::nt::GetTopicFromHandle(m_subHandle)};
}
private:
wpi::mutex m_mutex;
wpi::ProtobufMessage<T> m_msg;
ValueType m_defaultValue;
};
/**
* NetworkTables protobuf-encoded value publisher.
*/
template <wpi::ProtobufSerializable T>
class ProtobufPublisher : public Publisher {
public:
using TopicType = ProtobufTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
ProtobufPublisher() = default;
/**
* Construct from a publisher handle; recommended to use
* ProtobufTopic::Publish() instead.
*
* @param handle Native handle
* @param msg Protobuf message
*/
explicit ProtobufPublisher(NT_Publisher handle, wpi::ProtobufMessage<T> msg)
: Publisher{handle}, m_msg{std::move(msg)} {}
ProtobufPublisher(const ProtobufPublisher&) = delete;
ProtobufPublisher& operator=(const ProtobufPublisher&) = delete;
ProtobufPublisher(ProtobufPublisher&& rhs)
: Publisher{std::move(rhs)},
m_msg{std::move(rhs.m_msg)},
m_schemaPublished{rhs.m_schemaPublished} {}
ProtobufPublisher& operator=(ProtobufPublisher&& rhs) {
Publisher::operator=(std::move(rhs));
m_msg = std::move(rhs.m_msg);
m_schemaPublished.clear();
if (rhs.m_schemaPublished.test()) {
m_schemaPublished.test_and_set();
}
return *this;
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void Set(const T& value, int64_t time = 0) {
wpi::SmallVector<uint8_t, 128> buf;
{
std::scoped_lock lock{m_mutex};
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddProtobufSchema<T>(m_msg);
}
m_msg.Pack(buf, value);
}
::nt::SetRaw(m_pubHandle, buf, time);
}
/**
* Publish a default value.
* On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void SetDefault(const T& value) {
wpi::SmallVector<uint8_t, 128> buf;
{
std::scoped_lock lock{m_mutex};
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddProtobufSchema<T>(m_msg);
}
m_msg.Pack(buf, value);
}
::nt::SetDefaultRaw(m_pubHandle, buf);
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return ProtobufTopic<T>{::nt::GetTopicFromHandle(m_pubHandle)};
}
private:
wpi::mutex m_mutex;
wpi::ProtobufMessage<T> m_msg;
std::atomic_flag m_schemaPublished = ATOMIC_FLAG_INIT;
};
/**
* NetworkTables protobuf-encoded value entry.
*
* @note Unlike NetworkTableEntry, the entry goes away when this is destroyed.
*/
template <wpi::ProtobufSerializable T>
class ProtobufEntry final : public ProtobufSubscriber<T>,
public ProtobufPublisher<T> {
public:
using SubscriberType = ProtobufSubscriber<T>;
using PublisherType = ProtobufPublisher<T>;
using TopicType = ProtobufTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
ProtobufEntry() = default;
/**
* Construct from an entry handle; recommended to use
* ProtobufTopic::GetEntry() instead.
*
* @param handle Native handle
* @param msg Protobuf message
* @param defaultValue Default value
*/
ProtobufEntry(NT_Entry handle, wpi::ProtobufMessage<T> msg, T defaultValue)
: ProtobufSubscriber<T>{handle, std::move(msg), std::move(defaultValue)},
ProtobufPublisher<T>{handle, {}} {}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
explicit operator bool() const { return this->m_subHandle != 0; }
/**
* Gets the native handle for the entry.
*
* @return Native handle
*/
NT_Entry GetHandle() const { return this->m_subHandle; }
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return ProtobufTopic<T>{::nt::GetTopicFromHandle(this->m_subHandle)};
}
/**
* Stops publishing the entry if it's published.
*/
void Unpublish() { ::nt::Unpublish(this->m_pubHandle); }
};
/**
* NetworkTables protobuf-encoded value topic.
*/
template <wpi::ProtobufSerializable T>
class ProtobufTopic final : public Topic {
public:
using SubscriberType = ProtobufSubscriber<T>;
using PublisherType = ProtobufPublisher<T>;
using EntryType = ProtobufEntry<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
ProtobufTopic() = default;
/**
* Construct from a topic handle; recommended to use
* NetworkTableInstance::GetProtobufTopic() instead.
*
* @param handle Native handle
*/
explicit ProtobufTopic(NT_Topic handle) : Topic{handle} {}
/**
* Construct from a generic topic.
*
* @param topic Topic
*/
explicit ProtobufTopic(Topic topic) : Topic{topic} {}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object
* is not destroyed.
*
* @note Subscribers that do not match the published data type do not return
* any values. To determine if the data type matches, use the appropriate
* Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options subscribe options
* @return subscriber
*/
[[nodiscard]]
SubscriberType Subscribe(
T defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) {
wpi::ProtobufMessage<T> msg;
auto typeString = msg.GetTypeString();
return ProtobufSubscriber<T>{
::nt::Subscribe(m_handle, NT_RAW, typeString, options), std::move(msg),
std::move(defaultValue)};
}
/**
* Create a new publisher to the topic.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
wpi::ProtobufMessage<T> msg;
auto typeString = msg.GetTypeString();
return ProtobufPublisher<T>{
::nt::Publish(m_handle, NT_RAW, typeString, options), std::move(msg)};
}
/**
* Create a new publisher to the topic, with type string and initial
* properties.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType PublishEx(
const wpi::json& properties,
const PubSubOptions& options = kDefaultPubSubOptions) {
wpi::ProtobufMessage<T> msg;
auto typeString = msg.GetTypeString();
return ProtobufPublisher<T>{
::nt::PublishEx(m_handle, NT_RAW, typeString, properties, options),
std::move(msg)};
}
/**
* Create a new entry for the topic.
*
* Entries act as a combination of a subscriber and a weak publisher. The
* subscriber is active as long as the entry is not destroyed. The publisher
* is created when the entry is first written to, and remains active until
* either Unpublish() is called or the entry is destroyed.
*
* @note It is not possible to use two different data types with the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options publish and/or subscribe options
* @return entry
*/
[[nodiscard]]
EntryType GetEntry(T defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
wpi::ProtobufMessage<T> msg;
auto typeString = msg.GetTypeString();
return ProtobufEntry<T>{
::nt::GetEntry(m_handle, NT_RAW, typeString, options), std::move(msg),
std::move(defaultValue)};
}
};
} // namespace nt

View File

@@ -0,0 +1,593 @@
// 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.
#pragma once
#include <stdint.h>
#include <atomic>
#include <ranges>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/mutex.h>
#include <wpi/struct/Struct.h>
#include "networktables/NetworkTableInstance.h"
#include "networktables/Topic.h"
#include "ntcore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace nt {
template <wpi::StructSerializable T>
class StructArrayTopic;
/**
* NetworkTables struct-encoded value array subscriber.
*/
template <wpi::StructSerializable T>
class StructArraySubscriber : public Subscriber {
using S = wpi::Struct<T>;
public:
using TopicType = StructArrayTopic<T>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArraySubscriber() = default;
/**
* Construct from a subscriber handle; recommended to use
* StructTopic::Subscribe() instead.
*
* @param handle Native handle
* @param defaultValue Default value
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
StructArraySubscriber(NT_Subscriber handle, U&& defaultValue)
: Subscriber{handle},
m_defaultValue{defaultValue.begin(), defaultValue.end()} {
}
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* stored default value.
*
* @return value
*/
ValueType Get() const { return Get(m_defaultValue); }
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
ValueType Get(U&& defaultValue) const {
return GetAtomic(std::forward<U>(defaultValue)).value;
}
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
ValueType Get(std::span<const T> defaultValue) const {
return GetAtomic(defaultValue).value;
}
/**
* Get the last published value along with its timestamp
* If no value has been published or the value cannot be unpacked, returns the
* stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); }
/**
* Get the last published value along with its timestamp.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
TimestampedValueType GetAtomic(U&& defaultValue) const {
wpi::SmallVector<uint8_t, 128> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() == 0 || (view.value.size() % S::kSize) != 0) {
return {0, 0, std::forward<U>(defaultValue)};
}
TimestampedValueType rv{view.time, view.serverTime, {}};
rv.value.reserve(view.value.size() / S::kSize);
for (auto in = view.value.begin(), end = view.value.end(); in != end;
in += S::kSize) {
rv.value.emplace_back(
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
}
return rv;
}
/**
* Get the last published value along with its timestamp.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedValueType GetAtomic(std::span<const T> defaultValue) const {
wpi::SmallVector<uint8_t, 128> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() == 0 || (view.value.size() % S::kSize) != 0) {
return {0, 0, {defaultValue.begin(), defaultValue.end()}};
}
TimestampedValueType rv{view.time, view.serverTime, {}};
rv.value.reserve(view.value.size() / S::kSize);
for (auto in = view.value.begin(), end = view.value.end(); in != end;
in += S::kSize) {
rv.value.emplace_back(
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
}
return rv;
}
/**
* Get an array of all valid value changes since the last call to ReadQueue.
* Also provides a timestamp for each value. Values that cannot be unpacked
* are dropped.
*
* @note The "poll storage" subscribe option can be used to set the queue
* depth.
*
* @return Array of timestamped values; empty array if no valid new changes
* have been published since the previous call.
*/
std::vector<TimestampedValueType> ReadQueue() {
auto raw = ::nt::ReadQueueRaw(m_subHandle);
std::vector<TimestampedValueType> rv;
rv.reserve(raw.size());
for (auto&& r : raw) {
if (r.value.size() == 0 || (r.value.size() % S::kSize) != 0) {
continue;
}
std::vector<T> values;
values.reserve(r.value.size() / S::kSize);
for (auto in = r.value.begin(), end = r.value.end(); in != end;
in += S::kSize) {
values.emplace_back(
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
}
rv.emplace_back(r.time, r.serverTime, std::move(values));
}
return rv;
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructArrayTopic<T>{::nt::GetTopicFromHandle(m_subHandle)};
}
private:
ValueType m_defaultValue;
};
/**
* NetworkTables struct-encoded value array publisher.
*/
template <wpi::StructSerializable T>
class StructArrayPublisher : public Publisher {
using S = wpi::Struct<T>;
public:
using TopicType = StructArrayTopic<T>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayPublisher() = default;
/**
* Construct from a publisher handle; recommended to use
* StructTopic::Publish() instead.
*
* @param handle Native handle
*/
explicit StructArrayPublisher(NT_Publisher handle) : Publisher{handle} {}
StructArrayPublisher(const StructArrayPublisher&) = delete;
StructArrayPublisher& operator=(const StructArrayPublisher&) = delete;
StructArrayPublisher(StructArrayPublisher&& rhs)
: Publisher{std::move(rhs)},
m_buf{std::move(rhs.m_buf)},
m_schemaPublished{rhs.m_schemaPublished} {}
StructArrayPublisher& operator=(StructArrayPublisher&& rhs) {
Publisher::operator=(std::move(rhs));
m_buf = std::move(rhs.m_buf);
m_schemaPublished.clear();
if (rhs.m_schemaPublished.test()) {
m_schemaPublished.test_and_set();
}
return *this;
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
void Set(U&& value, int64_t time = 0) {
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddStructSchema<T>();
}
m_buf.Write(std::forward<U>(value),
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); });
}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void Set(std::span<const T> value, int64_t time = 0) {
m_buf.Write(value,
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); });
}
/**
* Publish a default value.
* On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
void SetDefault(U&& value) {
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddStructSchema<T>();
}
m_buf.Write(std::forward<U>(value),
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); });
}
/**
* Publish a default value.
* On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void SetDefault(std::span<const T> value) {
m_buf.Write(value,
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); });
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructArrayTopic<T>{::nt::GetTopicFromHandle(m_pubHandle)};
}
private:
wpi::StructArrayBuffer<T> m_buf;
std::atomic_flag m_schemaPublished = ATOMIC_FLAG_INIT;
};
/**
* NetworkTables struct-encoded value array entry.
*
* @note Unlike NetworkTableEntry, the entry goes away when this is destroyed.
*/
template <wpi::StructSerializable T>
class StructArrayEntry final : public StructArraySubscriber<T>,
public StructArrayPublisher<T> {
public:
using SubscriberType = StructArraySubscriber<T>;
using PublisherType = StructArrayPublisher<T>;
using TopicType = StructArrayTopic<T>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayEntry() = default;
/**
* Construct from an entry handle; recommended to use
* StructTopic::GetEntry() instead.
*
* @param handle Native handle
* @param defaultValue Default value
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
StructArrayEntry(NT_Entry handle, U&& defaultValue)
: StructArraySubscriber<T>{handle, defaultValue},
StructArrayPublisher<T>{handle} {
}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
explicit operator bool() const { return this->m_subHandle != 0; }
/**
* Gets the native handle for the entry.
*
* @return Native handle
*/
NT_Entry GetHandle() const { return this->m_subHandle; }
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructArrayTopic<T>{::nt::GetTopicFromHandle(this->m_subHandle)};
}
/**
* Stops publishing the entry if it's published.
*/
void Unpublish() { ::nt::Unpublish(this->m_pubHandle); }
};
/**
* NetworkTables struct-encoded value array topic.
*/
template <wpi::StructSerializable T>
class StructArrayTopic final : public Topic {
public:
using SubscriberType = StructArraySubscriber<T>;
using PublisherType = StructArrayPublisher<T>;
using EntryType = StructArrayEntry<T>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayTopic() = default;
/**
* Construct from a topic handle; recommended to use
* NetworkTableInstance::GetStructTopic() instead.
*
* @param handle Native handle
*/
explicit StructArrayTopic(NT_Topic handle) : Topic{handle} {}
/**
* Construct from a generic topic.
*
* @param topic Topic
*/
explicit StructArrayTopic(Topic topic) : Topic{topic} {}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object
* is not destroyed.
*
* @note Subscribers that do not match the published data type do not return
* any values. To determine if the data type matches, use the appropriate
* Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options subscribe options
* @return subscriber
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
[[nodiscard]]
SubscriberType Subscribe(
U&& defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArraySubscriber<T>{
::nt::Subscribe(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(), options),
defaultValue};
}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object
* is not destroyed.
*
* @note Subscribers that do not match the published data type do not return
* any values. To determine if the data type matches, use the appropriate
* Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options subscribe options
* @return subscriber
*/
[[nodiscard]]
SubscriberType Subscribe(
std::span<const T> defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArraySubscriber<T>{
::nt::Subscribe(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(), options),
defaultValue};
}
/**
* Create a new publisher to the topic.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArrayPublisher<T>{::nt::Publish(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(), options)};
}
/**
* Create a new publisher to the topic, with type string and initial
* properties.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType PublishEx(
const wpi::json& properties,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArrayPublisher<T>{::nt::PublishEx(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(), properties,
options)};
}
/**
* Create a new entry for the topic.
*
* Entries act as a combination of a subscriber and a weak publisher. The
* subscriber is active as long as the entry is not destroyed. The publisher
* is created when the entry is first written to, and remains active until
* either Unpublish() is called or the entry is destroyed.
*
* @note It is not possible to use two different data types with the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options publish and/or subscribe options
* @return entry
*/
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
[[nodiscard]]
EntryType GetEntry(U&& defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArrayEntry<T>{
::nt::GetEntry(m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(),
options),
defaultValue};
}
/**
* Create a new entry for the topic.
*
* Entries act as a combination of a subscriber and a weak publisher. The
* subscriber is active as long as the entry is not destroyed. The publisher
* is created when the entry is first written to, and remains active until
* either Unpublish() is called or the entry is destroyed.
*
* @note It is not possible to use two different data types with the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options publish and/or subscribe options
* @return entry
*/
[[nodiscard]]
EntryType GetEntry(std::span<const T> defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructArrayEntry<T>{
::nt::GetEntry(m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(),
options),
defaultValue};
}
};
} // namespace nt

View File

@@ -0,0 +1,438 @@
// 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.
#pragma once
#include <stdint.h>
#include <atomic>
#include <concepts>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/struct/Struct.h>
#include "networktables/NetworkTableInstance.h"
#include "networktables/Topic.h"
#include "ntcore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace nt {
template <wpi::StructSerializable T>
class StructTopic;
/**
* NetworkTables struct-encoded value subscriber.
*/
template <wpi::StructSerializable T>
class StructSubscriber : public Subscriber {
using S = wpi::Struct<T>;
public:
using TopicType = StructTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
StructSubscriber() = default;
/**
* Construct from a subscriber handle; recommended to use
* StructTopic::Subscribe() instead.
*
* @param handle Native handle
* @param defaultValue Default value
*/
StructSubscriber(NT_Subscriber handle, T defaultValue)
: Subscriber{handle}, m_defaultValue{std::move(defaultValue)} {}
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* stored default value.
*
* @return value
*/
ValueType Get() const { return Get(m_defaultValue); }
/**
* Get the last published value.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue.
*
* @param defaultValue default value to return if no value has been published
* @return value
*/
ValueType Get(const T& defaultValue) const {
return GetAtomic(defaultValue).value;
}
/**
* Get the last published value, replacing the contents in place of an
* existing object. If no value has been published or the value cannot be
* unpacked, does not replace the contents and returns false.
*
* @param[out] out object to replace contents of
* @return true if successful
*/
bool GetInto(T* out) {
wpi::SmallVector<uint8_t, S::kSize> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() < S::kSize) {
return false;
} else {
wpi::UnpackStructInto(out, view.value.subspan<0, S::kSize>());
return true;
}
}
/**
* Get the last published value along with its timestamp
* If no value has been published or the value cannot be unpacked, returns the
* stored default value and a timestamp of 0.
*
* @return timestamped value
*/
TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); }
/**
* Get the last published value along with its timestamp.
* If no value has been published or the value cannot be unpacked, returns the
* passed defaultValue and a timestamp of 0.
*
* @param defaultValue default value to return if no value has been published
* @return timestamped value
*/
TimestampedValueType GetAtomic(const T& defaultValue) const {
wpi::SmallVector<uint8_t, S::kSize> buf;
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() < S::kSize) {
return {0, 0, defaultValue};
} else {
return {view.time, view.serverTime,
S::Unpack(view.value.subspan<0, S::kSize>())};
}
}
/**
* Get an array of all valid value changes since the last call to ReadQueue.
* Also provides a timestamp for each value. Values that cannot be unpacked
* are dropped.
*
* @note The "poll storage" subscribe option can be used to set the queue
* depth.
*
* @return Array of timestamped values; empty array if no valid new changes
* have been published since the previous call.
*/
std::vector<TimestampedValueType> ReadQueue() {
auto raw = ::nt::ReadQueueRaw(m_subHandle);
std::vector<TimestampedValueType> rv;
rv.reserve(raw.size());
for (auto&& r : raw) {
if (r.value.size() < S::kSize) {
continue;
} else {
rv.emplace_back(
r.time, r.serverTime,
S::Unpack(
std::span<const uint8_t>(r.value).subspan<0, S::kSize>()));
}
}
return rv;
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructTopic<T>{::nt::GetTopicFromHandle(m_subHandle)};
}
private:
ValueType m_defaultValue;
};
/**
* NetworkTables struct-encoded value publisher.
*/
template <wpi::StructSerializable T>
class StructPublisher : public Publisher {
using S = wpi::Struct<T>;
public:
using TopicType = StructTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
StructPublisher() = default;
StructPublisher(const StructPublisher&) = delete;
StructPublisher& operator=(const StructPublisher&) = delete;
StructPublisher(StructPublisher&& rhs)
: Publisher{std::move(rhs)}, m_schemaPublished{rhs.m_schemaPublished} {}
StructPublisher& operator=(StructPublisher&& rhs) {
Publisher::operator=(std::move(rhs));
m_schemaPublished.clear();
if (rhs.m_schemaPublished.test()) {
m_schemaPublished.test_and_set();
}
return *this;
}
/**
* Construct from a publisher handle; recommended to use
* StructTopic::Publish() instead.
*
* @param handle Native handle
*/
explicit StructPublisher(NT_Publisher handle) : Publisher{handle} {}
/**
* Publish a new value.
*
* @param value value to publish
* @param time timestamp; 0 indicates current NT time should be used
*/
void Set(const T& value, int64_t time = 0) {
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddStructSchema<T>();
}
uint8_t buf[S::kSize];
S::Pack(buf, value);
::nt::SetRaw(m_pubHandle, buf, time);
}
/**
* Publish a default value.
* On reconnect, a default value will never be used in preference to a
* published value.
*
* @param value value
*/
void SetDefault(const T& value) {
if (!m_schemaPublished.test_and_set()) {
GetTopic().GetInstance().template AddStructSchema<T>();
}
uint8_t buf[S::kSize];
S::Pack(buf, value);
::nt::SetDefaultRaw(m_pubHandle, buf);
}
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructTopic<T>{::nt::GetTopicFromHandle(m_pubHandle)};
}
private:
std::atomic_flag m_schemaPublished = ATOMIC_FLAG_INIT;
};
/**
* NetworkTables struct-encoded value entry.
*
* @note Unlike NetworkTableEntry, the entry goes away when this is destroyed.
*/
template <wpi::StructSerializable T>
class StructEntry final : public StructSubscriber<T>,
public StructPublisher<T> {
public:
using SubscriberType = StructSubscriber<T>;
using PublisherType = StructPublisher<T>;
using TopicType = StructTopic<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
StructEntry() = default;
/**
* Construct from an entry handle; recommended to use
* StructTopic::GetEntry() instead.
*
* @param handle Native handle
* @param defaultValue Default value
*/
StructEntry(NT_Entry handle, T defaultValue)
: StructSubscriber<T>{handle, std::move(defaultValue)},
StructPublisher<T>{handle} {}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
explicit operator bool() const { return this->m_subHandle != 0; }
/**
* Gets the native handle for the entry.
*
* @return Native handle
*/
NT_Entry GetHandle() const { return this->m_subHandle; }
/**
* Get the corresponding topic.
*
* @return Topic
*/
TopicType GetTopic() const {
return StructTopic<T>{::nt::GetTopicFromHandle(this->m_subHandle)};
}
/**
* Stops publishing the entry if it's published.
*/
void Unpublish() { ::nt::Unpublish(this->m_pubHandle); }
};
/**
* NetworkTables struct-encoded value topic.
*/
template <wpi::StructSerializable T>
class StructTopic final : public Topic {
public:
using SubscriberType = StructSubscriber<T>;
using PublisherType = StructPublisher<T>;
using EntryType = StructEntry<T>;
using ValueType = T;
using ParamType = const T&;
using TimestampedValueType = Timestamped<T>;
StructTopic() = default;
/**
* Construct from a topic handle; recommended to use
* NetworkTableInstance::GetStructTopic() instead.
*
* @param handle Native handle
*/
explicit StructTopic(NT_Topic handle) : Topic{handle} {}
/**
* Construct from a generic topic.
*
* @param topic Topic
*/
explicit StructTopic(Topic topic) : Topic{topic} {}
/**
* Create a new subscriber to the topic.
*
* <p>The subscriber is only active as long as the returned object
* is not destroyed.
*
* @note Subscribers that do not match the published data type do not return
* any values. To determine if the data type matches, use the appropriate
* Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options subscribe options
* @return subscriber
*/
[[nodiscard]]
SubscriberType Subscribe(
T defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) {
return StructSubscriber<T>{
::nt::Subscribe(m_handle, NT_RAW, wpi::GetStructTypeString<T>(),
options),
std::move(defaultValue)};
}
/**
* Create a new publisher to the topic.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
return StructPublisher<T>{::nt::Publish(
m_handle, NT_RAW, wpi::GetStructTypeString<T>(), options)};
}
/**
* Create a new publisher to the topic, with type string and initial
* properties.
*
* The publisher is only active as long as the returned object
* is not destroyed.
*
* @note It is not possible to publish two different data types to the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored). To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param properties JSON properties
* @param options publish options
* @return publisher
*/
[[nodiscard]]
PublisherType PublishEx(
const wpi::json& properties,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructPublisher<T>{::nt::PublishEx(
m_handle, NT_RAW, wpi::GetStructTypeString<T>(), properties, options)};
}
/**
* Create a new entry for the topic.
*
* Entries act as a combination of a subscriber and a weak publisher. The
* subscriber is active as long as the entry is not destroyed. The publisher
* is created when the entry is first written to, and remains active until
* either Unpublish() is called or the entry is destroyed.
*
* @note It is not possible to use two different data types with the same
* topic. Conflicts between publishers are typically resolved by the
* server on a first-come, first-served basis. Any published values that
* do not match the topic's data type are dropped (ignored), and the entry
* will show no new values if the data type does not match. To determine
* if the data type matches, use the appropriate Topic functions.
*
* @param defaultValue default value used when a default is not provided to a
* getter function
* @param options publish and/or subscribe options
* @return entry
*/
[[nodiscard]]
EntryType GetEntry(T defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return StructEntry<T>{
::nt::GetEntry(m_handle, NT_RAW, wpi::GetStructTypeString<T>(),
options),
std::move(defaultValue)};
}
};
} // namespace nt

View File

@@ -6,7 +6,6 @@
#include <string>
#include "networktables/NetworkTableInstance.h"
#include "networktables/NetworkTableType.h"
#include "networktables/Topic.h"
#include "ntcore_c.h"
@@ -14,10 +13,6 @@
namespace nt {
inline NetworkTableInstance Topic::GetInstance() const {
return NetworkTableInstance{GetInstanceFromHandle(m_handle)};
}
inline std::string Topic::GetName() const {
return ::nt::GetTopicName(m_handle);
}

View File

@@ -1435,6 +1435,44 @@ NT_Listener NT_AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level,
/** @} */
/**
* @defgroup ntcore_schema_cfunc Schema Functions
* @{
*/
/**
* Returns whether there is a data schema already registered with the given
* name. This does NOT perform a check as to whether the schema has already
* been published by another node on the network.
*
* @param inst instance
* @param name Name (the string passed as the data type for topics using this
* schema)
* @return True if schema already registered
*/
NT_Bool NT_HasSchema(NT_Inst inst, const char* name);
/**
* Registers a data schema. Data schemas provide information for how a
* certain data type string can be decoded. The type string of a data schema
* indicates the type of the schema itself (e.g. "protobuf" for protobuf
* schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are
* published just like normal topics, with the name being generated from the
* provided name: "/.schema/<name>". Duplicate calls to this function with
* the same name are silently ignored.
*
* @param inst instance
* @param name Name (the string passed as the data type for topics using this
* schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
* @param schemaSize Size of schema data
*/
void NT_AddSchema(NT_Inst inst, const char* name, const char* type,
const uint8_t* schema, size_t schemaSize);
/** @} */
/**
* @defgroup ntcore_interop_cfunc Interop Utility Functions
* @{

View File

@@ -1300,6 +1300,66 @@ NT_Listener AddLogger(NT_Inst inst, unsigned int min_level,
NT_Listener AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level,
unsigned int max_level);
/** @} */
/**
* @defgroup ntcore_schema_func Schema Functions
* @{
*/
/**
* Returns whether there is a data schema already registered with the given
* name. This does NOT perform a check as to whether the schema has already
* been published by another node on the network.
*
* @param inst instance
* @param name Name (the string passed as the data type for topics using this
* schema)
* @return True if schema already registered
*/
bool HasSchema(NT_Inst inst, std::string_view name);
/**
* Registers a data schema. Data schemas provide information for how a
* certain data type string can be decoded. The type string of a data schema
* indicates the type of the schema itself (e.g. "protobuf" for protobuf
* schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are
* published just like normal topics, with the name being generated from the
* provided name: "/.schema/<name>". Duplicate calls to this function with
* the same name are silently ignored.
*
* @param inst instance
* @param name Name (the string passed as the data type for topics using this
* schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
void AddSchema(NT_Inst inst, std::string_view name, std::string_view type,
std::span<const uint8_t> schema);
/**
* Registers a data schema. Data schemas provide information for how a
* certain data type string can be decoded. The type string of a data schema
* indicates the type of the schema itself (e.g. "protobuf" for protobuf
* schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are
* published just like normal topics, with the name being generated from the
* provided name: "/.schema/<name>". Duplicate calls to this function with
* the same name are silently ignored.
*
* @param inst instance
* @param name Name (the string passed as the data type for topics using this
* schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
inline void AddSchema(NT_Inst inst, std::string_view name,
std::string_view type, std::string_view schema) {
AddSchema(
inst, name, type,
std::span<const uint8_t>{reinterpret_cast<const uint8_t*>(schema.data()),
schema.size()});
}
/** @} */
/** @} */

View File

@@ -1,6 +1,7 @@
apply plugin: 'maven-publish'
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'com.google.protobuf'
def baseArtifactId = project.baseId
def artifactGroupId = project.groupId
@@ -141,3 +142,27 @@ jacocoTestReport {
html.required = true
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.21.12'
}
plugins {
quickbuf {
artifact = 'us.hebi.quickbuf:protoc-gen-quickbuf:1.3.2'
}
}
generateProtoTasks {
all().configureEach { task ->
task.builtins {
cpp {}
remove java
}
task.plugins {
quickbuf {
option "gen_descriptors=true"
}
}
}
}
}

View File

@@ -30,11 +30,11 @@ model {
sources {
cpp {
source {
srcDirs 'src/main/native/cpp'
include '**/*.cpp'
srcDirs 'src/main/native/cpp', "$buildDir/generated/source/proto/main/cpp"
include '**/*.cpp', '**/*.cc'
}
exportedHeaders {
srcDirs 'src/main/native/include'
srcDirs 'src/main/native/include', "$buildDir/generated/source/proto/main/cpp"
}
}
}
@@ -43,6 +43,9 @@ model {
it.buildable = false
return
}
it.tasks.withType(CppCompile) {
it.dependsOn generateProto
}
if (project.hasProperty('extraSetup')) {
extraSetup(it)
}

View File

@@ -41,15 +41,15 @@ model {
sources {
cpp {
source {
srcDirs 'src/main/native/cpp'
srcDirs 'src/main/native/cpp', "$buildDir/generated/source/proto/main/cpp"
if (project.hasProperty('generatedSources')) {
srcDir generatedSources
}
include '**/*.cpp'
include '**/*.cpp', '**/*.cc'
exclude '**/jni/**/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
srcDirs 'src/main/native/include', "$buildDir/generated/source/proto/main/cpp"
if (project.hasProperty('generatedHeaders')) {
srcDir generatedHeaders
}
@@ -67,6 +67,9 @@ model {
if (!project.hasProperty('noWpiutil')) {
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
}
it.tasks.withType(CppCompile) {
it.dependsOn generateProto
}
if (project.hasProperty('splitSetup')) {
splitSetup(it)
}

View File

@@ -10,4 +10,6 @@ suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
checks="(LocalVariableName|MemberName|MethodName|MethodTypeParameterName|ParameterName)" />
<suppress files=".*JNI.*"
checks="(EmptyLineSeparator|LineLength|MissingJavadocMethod|ParameterName)" />
<suppress files=".*/quickbuf/.*"
checks="(CustomImportOrder|EmptyLineSeparator|LineLength|JavadocParagraph|MissingJavadocMethod|OverloadMethodsDeclarationOrder|SummaryJavadoc|UnnecessaryParentheses|OperatorWrap|JavadocMethod|JavadocTagContinuationIndentation)" />
</suppressions>

View File

@@ -8,6 +8,7 @@
<exclude-pattern>.*/*JNI.*</exclude-pattern>
<exclude-pattern>.*/*IntegrationTests.*</exclude-pattern>
<exclude-pattern>.*/quickbuf/.*</exclude-pattern>
<rule ref="category/java/bestpractices.xml">
<exclude name="AccessorClassGeneration" />

View File

@@ -5,6 +5,49 @@ include(CompileWarnings)
include(AddTest)
include(DownloadAndCheck)
file(GLOB wpimath_proto_src src/main/proto/*.proto)
protobuf_generate_cpp(WPIMATH_PROTO_SRCS WPIMATH_PROTO_HDRS PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/protobuf" PROTOS ${wpimath_proto_src})
function(quickbuf_generate SRCS JAVA_PACKAGE)
if(NOT ARGN)
message(SEND_ERROR "Error: PROTOBUF_GENERATE_QUICKBUF() called without any proto files")
return()
endif()
set(_generated_srcs_all)
foreach(_proto ${ARGN})
get_filename_component(_abs_file ${_proto} ABSOLUTE)
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
get_filename_component(_basename ${_proto} NAME_WLE)
file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir})
# convert to QuickBuffers Java case (geometry2d -> Geometry2D)
string(REGEX MATCHALL "[A-Za-z_]+|[0-9]+" _name_components ${_basename})
set(_name_components_out)
foreach(_part ${_name_components})
string(SUBSTRING ${_part} 0 1 _first_letter)
string(TOUPPER ${_first_letter} _first_letter)
string(REGEX REPLACE "^.(.*)" "${_first_letter}\\1" _part_out "${_part}")
list(APPEND _name_components_out ${_part_out})
endforeach()
list(JOIN _name_components_out "" _basename_title)
set(_generated_src "${CMAKE_CURRENT_BINARY_DIR}/quickbuf/${JAVA_PACKAGE}/${_basename_title}.java")
list(APPEND _generated_srcs_all ${_generated_src})
add_custom_command(
OUTPUT ${_generated_src}
COMMAND protobuf::protoc
ARGS --plugin=protoc-gen-quickbuf=${Quickbuf_EXECUTABLE} --quickbuf_out=gen_descriptors=true:${CMAKE_CURRENT_BINARY_DIR}/quickbuf -I${_abs_dir} ${_abs_file}
DEPENDS ${_abs_file} protobuf::protoc
COMMENT "Running quickbuf protocol buffer compiler on ${_proto}"
VERBATIM )
endforeach()
set(${SRCS} ${_generated_srcs_all} PARENT_SCOPE)
endfunction()
file(GLOB wpimath_jni_src src/main/native/cpp/jni/WPIMathJNI_DARE.cpp
src/main/native/cpp/jni/WPIMathJNI_Eigen.cpp
src/main/native/cpp/jni/WPIMathJNI_Exceptions.cpp
@@ -19,6 +62,8 @@ if (WITH_JAVA)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
quickbuf_generate(WPIMATH_QUICKBUF_SRCS "edu/wpi/first/math/proto" ${wpimath_proto_src})
if(NOT EXISTS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/ejml-simple-0.43.1.jar")
set(BASE_URL "https://search.maven.org/remotecontent?filepath=")
set(JAR_ROOT "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml")
@@ -45,8 +90,10 @@ if (WITH_JAVA)
file(GLOB EJML_JARS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/*.jar")
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
file(GLOB QUICKBUF_JAR
${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar)
set(CMAKE_JAVA_INCLUDE_PATH wpimath.jar ${EJML_JARS} ${JACKSON_JARS})
set(CMAKE_JAVA_INCLUDE_PATH wpimath.jar ${EJML_JARS} ${JACKSON_JARS} ${QUICKBUF_JAR})
execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/generate_numbers.py ${WPILIB_BINARY_DIR}/wpimath RESULT_VARIABLE generateResult)
if(NOT (generateResult EQUAL "0"))
@@ -61,7 +108,7 @@ if (WITH_JAVA)
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java ${WPILIB_BINARY_DIR}/wpimath/generated/*.java)
add_jar(wpimath_jar ${JAVA_SOURCES} INCLUDE_JARS ${EJML_JARS} wpiutil_jar OUTPUT_NAME wpimath GENERATE_NATIVE_HEADERS wpimath_jni_headers)
add_jar(wpimath_jar ${JAVA_SOURCES} ${WPIMATH_QUICKBUF_SRCS} INCLUDE_JARS ${EJML_JARS} wpiutil_jar OUTPUT_NAME wpimath GENERATE_NATIVE_HEADERS wpimath_jni_headers)
get_property(WPIMATH_JAR_FILE TARGET wpimath_jar PROPERTY JAR_FILE)
install(FILES ${WPIMATH_JAR_FILE} DESTINATION "${java_lib_dest}")
@@ -85,7 +132,7 @@ file(GLOB_RECURSE wpimath_native_src src/main/native/cpp/*.cpp)
list(REMOVE_ITEM wpimath_native_src ${wpimath_jni_src})
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS FALSE)
add_library(wpimath ${wpimath_native_src})
add_library(wpimath ${wpimath_native_src} ${WPIMATH_PROTO_SRCS})
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
set_target_properties(wpimath PROPERTIES DEBUG_POSTFIX "d")
@@ -116,6 +163,7 @@ target_include_directories(wpimath SYSTEM PUBLIC
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/wpimath")
target_include_directories(wpimath PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/protobuf>
$<INSTALL_INTERFACE:${include_dest}/wpimath>)
install(TARGETS wpimath EXPORT wpimath)

View File

@@ -41,6 +41,7 @@ dependencies {
api "com.fasterxml.jackson.core:jackson-annotations:2.15.2"
api "com.fasterxml.jackson.core:jackson-core:2.15.2"
api "com.fasterxml.jackson.core:jackson-databind:2.15.2"
api "us.hebi.quickbuf:quickbuf-runtime:1.3.2"
}
def wpilibNumberFileInput = file("src/generate/GenericNumber.java.jinja")

View File

@@ -9,10 +9,15 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.proto.Geometry2D.ProtobufPose2d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/** Represents a 2D pose containing translational and rotational elements. */
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -305,4 +310,83 @@ public class Pose2d implements Interpolatable<Pose2d> {
return this.exp(scaledTwist);
}
}
public static final class AStruct implements Struct<Pose2d> {
@Override
public Class<Pose2d> getTypeClass() {
return Pose2d.class;
}
@Override
public String getTypeString() {
return "struct:Pose2d";
}
@Override
public int getSize() {
return Translation2d.struct.getSize() + Rotation2d.struct.getSize();
}
@Override
public String getSchema() {
return "Translation2d translation;Rotation2d rotation";
}
@Override
public Struct<?>[] getNested() {
return new Struct<?>[] {Translation2d.struct, Rotation2d.struct};
}
@Override
public Pose2d unpack(ByteBuffer bb) {
Translation2d translation = Translation2d.struct.unpack(bb);
Rotation2d rotation = Rotation2d.struct.unpack(bb);
return new Pose2d(translation, rotation);
}
@Override
public void pack(ByteBuffer bb, Pose2d value) {
Translation2d.struct.pack(bb, value.m_translation);
Rotation2d.struct.pack(bb, value.m_rotation);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Pose2d, ProtobufPose2d> {
@Override
public Class<Pose2d> getTypeClass() {
return Pose2d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufPose2d.getDescriptor();
}
@Override
public Protobuf<?, ?>[] getNested() {
return new Protobuf<?, ?>[] {Translation2d.proto, Rotation2d.proto};
}
@Override
public ProtobufPose2d createMessage() {
return ProtobufPose2d.newInstance();
}
@Override
public Pose2d unpack(ProtobufPose2d msg) {
return new Pose2d(
Translation2d.proto.unpack(msg.getTranslation()),
Rotation2d.proto.unpack(msg.getRotation()));
}
@Override
public void pack(ProtobufPose2d msg, Pose2d value) {
Translation2d.proto.pack(msg.getMutableTranslation(), value.m_translation);
Rotation2d.proto.pack(msg.getMutableRotation(), value.m_rotation);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -10,7 +10,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.WPIMathJNI;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.proto.Geometry3D.ProtobufPose3d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/** Represents a 3D pose containing translational and rotational elements. */
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -318,4 +323,83 @@ public class Pose3d implements Interpolatable<Pose3d> {
return this.exp(scaledTwist);
}
}
public static final class AStruct implements Struct<Pose3d> {
@Override
public Class<Pose3d> getTypeClass() {
return Pose3d.class;
}
@Override
public String getTypeString() {
return "struct:Pose3d";
}
@Override
public int getSize() {
return Translation3d.struct.getSize() + Rotation3d.struct.getSize();
}
@Override
public String getSchema() {
return "Translation3d translation;Rotation3d rotation";
}
@Override
public Struct<?>[] getNested() {
return new Struct<?>[] {Translation3d.struct, Rotation3d.struct};
}
@Override
public Pose3d unpack(ByteBuffer bb) {
Translation3d translation = Translation3d.struct.unpack(bb);
Rotation3d rotation = Rotation3d.struct.unpack(bb);
return new Pose3d(translation, rotation);
}
@Override
public void pack(ByteBuffer bb, Pose3d value) {
Translation3d.struct.pack(bb, value.m_translation);
Rotation3d.struct.pack(bb, value.m_rotation);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Pose3d, ProtobufPose3d> {
@Override
public Class<Pose3d> getTypeClass() {
return Pose3d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufPose3d.getDescriptor();
}
@Override
public Protobuf<?, ?>[] getNested() {
return new Protobuf<?, ?>[] {Translation3d.proto, Rotation3d.proto};
}
@Override
public ProtobufPose3d createMessage() {
return ProtobufPose3d.newInstance();
}
@Override
public Pose3d unpack(ProtobufPose3d msg) {
return new Pose3d(
Translation3d.proto.unpack(msg.getTranslation()),
Rotation3d.proto.unpack(msg.getRotation()));
}
@Override
public void pack(ProtobufPose3d msg, Pose3d value) {
Translation3d.proto.pack(msg.getMutableTranslation(), value.m_translation);
Rotation3d.proto.pack(msg.getMutableRotation(), value.m_rotation);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -11,7 +11,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.Vector;
import edu.wpi.first.math.numbers.N3;
import edu.wpi.first.math.proto.Geometry3D.ProtobufQuaternion;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
@@ -400,4 +405,74 @@ public class Quaternion {
return VecBuilder.fill(coeff * getX(), coeff * getY(), coeff * getZ());
}
public static final class AStruct implements Struct<Quaternion> {
@Override
public Class<Quaternion> getTypeClass() {
return Quaternion.class;
}
@Override
public String getTypeString() {
return "struct:Quaternion";
}
@Override
public int getSize() {
return kSizeDouble * 4;
}
@Override
public String getSchema() {
return "double w;double x;double y;double z";
}
@Override
public Quaternion unpack(ByteBuffer bb) {
double w = bb.getDouble();
double x = bb.getDouble();
double y = bb.getDouble();
double z = bb.getDouble();
return new Quaternion(w, x, y, z);
}
@Override
public void pack(ByteBuffer bb, Quaternion value) {
bb.putDouble(value.getW());
bb.putDouble(value.getX());
bb.putDouble(value.getY());
bb.putDouble(value.getZ());
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Quaternion, ProtobufQuaternion> {
@Override
public Class<Quaternion> getTypeClass() {
return Quaternion.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufQuaternion.getDescriptor();
}
@Override
public ProtobufQuaternion createMessage() {
return ProtobufQuaternion.newInstance();
}
@Override
public Quaternion unpack(ProtobufQuaternion msg) {
return new Quaternion(msg.getW(), msg.getX(), msg.getY(), msg.getZ());
}
@Override
public void pack(ProtobufQuaternion msg, Quaternion value) {
msg.setW(value.getW()).setX(value.getX()).setY(value.getY()).setZ(value.getZ());
}
}
public static final AProto proto = new AProto();
}

View File

@@ -10,8 +10,13 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.proto.Geometry2D.ProtobufRotation2d;
import edu.wpi.first.math.util.Units;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/**
* A rotation in a 2D coordinate frame represented by a point on the unit circle (cosine and sine).
@@ -256,4 +261,67 @@ public class Rotation2d implements Interpolatable<Rotation2d> {
public Rotation2d interpolate(Rotation2d endValue, double t) {
return plus(endValue.minus(this).times(MathUtil.clamp(t, 0, 1)));
}
public static final class AStruct implements Struct<Rotation2d> {
@Override
public Class<Rotation2d> getTypeClass() {
return Rotation2d.class;
}
@Override
public String getTypeString() {
return "struct:Rotation2d";
}
@Override
public int getSize() {
return kSizeDouble;
}
@Override
public String getSchema() {
return "double value";
}
@Override
public Rotation2d unpack(ByteBuffer bb) {
return new Rotation2d(bb.getDouble());
}
@Override
public void pack(ByteBuffer bb, Rotation2d value) {
bb.putDouble(value.m_value);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Rotation2d, ProtobufRotation2d> {
@Override
public Class<Rotation2d> getTypeClass() {
return Rotation2d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufRotation2d.getDescriptor();
}
@Override
public ProtobufRotation2d createMessage() {
return ProtobufRotation2d.newInstance();
}
@Override
public Rotation2d unpack(ProtobufRotation2d msg) {
return new Rotation2d(msg.getValue());
}
@Override
public void pack(ProtobufRotation2d msg, Rotation2d value) {
msg.setValue(value.m_value);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -17,8 +17,13 @@ import edu.wpi.first.math.VecBuilder;
import edu.wpi.first.math.Vector;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.numbers.N3;
import edu.wpi.first.math.proto.Geometry3D.ProtobufRotation3d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
import us.hebi.quickbuf.Descriptors.Descriptor;
/** A rotation in a 3D coordinate frame represented by a quaternion. */
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -434,4 +439,77 @@ public class Rotation3d implements Interpolatable<Rotation3d> {
public Rotation3d interpolate(Rotation3d endValue, double t) {
return plus(endValue.minus(this).times(MathUtil.clamp(t, 0, 1)));
}
public static final class AStruct implements Struct<Rotation3d> {
@Override
public Class<Rotation3d> getTypeClass() {
return Rotation3d.class;
}
@Override
public String getTypeString() {
return "struct:Rotation3d";
}
@Override
public int getSize() {
return Quaternion.struct.getSize();
}
@Override
public String getSchema() {
return "Quaternion q";
}
@Override
public Struct<?>[] getNested() {
return new Struct<?>[] {Quaternion.struct};
}
@Override
public Rotation3d unpack(ByteBuffer bb) {
return new Rotation3d(Quaternion.struct.unpack(bb));
}
@Override
public void pack(ByteBuffer bb, Rotation3d value) {
Quaternion.struct.pack(bb, value.m_q);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Rotation3d, ProtobufRotation3d> {
@Override
public Class<Rotation3d> getTypeClass() {
return Rotation3d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufRotation3d.getDescriptor();
}
@Override
public Protobuf<?, ?>[] getNested() {
return new Protobuf<?, ?>[] {Quaternion.proto};
}
@Override
public ProtobufRotation3d createMessage() {
return ProtobufRotation3d.newInstance();
}
@Override
public Rotation3d unpack(ProtobufRotation3d msg) {
return new Rotation3d(Quaternion.proto.unpack(msg.getQ()));
}
@Override
public void pack(ProtobufRotation3d msg, Rotation3d value) {
Quaternion.proto.pack(msg.getMutableQ(), value.m_q);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -4,7 +4,12 @@
package edu.wpi.first.math.geometry;
import edu.wpi.first.math.proto.Geometry2D.ProtobufTransform2d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/** Represents a transformation for a Pose2d in the pose's frame. */
public class Transform2d {
@@ -163,4 +168,83 @@ public class Transform2d {
public int hashCode() {
return Objects.hash(m_translation, m_rotation);
}
public static final class AStruct implements Struct<Transform2d> {
@Override
public Class<Transform2d> getTypeClass() {
return Transform2d.class;
}
@Override
public String getTypeString() {
return "struct:Transform2d";
}
@Override
public int getSize() {
return Translation2d.struct.getSize() + Rotation2d.struct.getSize();
}
@Override
public String getSchema() {
return "Translation2d translation;Rotation2d rotation";
}
@Override
public Struct<?>[] getNested() {
return new Struct<?>[] {Translation2d.struct, Rotation2d.struct};
}
@Override
public Transform2d unpack(ByteBuffer bb) {
Translation2d translation = Translation2d.struct.unpack(bb);
Rotation2d rotation = Rotation2d.struct.unpack(bb);
return new Transform2d(translation, rotation);
}
@Override
public void pack(ByteBuffer bb, Transform2d value) {
Translation2d.struct.pack(bb, value.m_translation);
Rotation2d.struct.pack(bb, value.m_rotation);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Transform2d, ProtobufTransform2d> {
@Override
public Class<Transform2d> getTypeClass() {
return Transform2d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTransform2d.getDescriptor();
}
@Override
public Protobuf<?, ?>[] getNested() {
return new Protobuf<?, ?>[] {Translation2d.proto, Rotation2d.proto};
}
@Override
public ProtobufTransform2d createMessage() {
return ProtobufTransform2d.newInstance();
}
@Override
public Transform2d unpack(ProtobufTransform2d msg) {
return new Transform2d(
Translation2d.proto.unpack(msg.getTranslation()),
Rotation2d.proto.unpack(msg.getRotation()));
}
@Override
public void pack(ProtobufTransform2d msg, Transform2d value) {
Translation2d.proto.pack(msg.getMutableTranslation(), value.m_translation);
Rotation2d.proto.pack(msg.getMutableRotation(), value.m_rotation);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -4,7 +4,12 @@
package edu.wpi.first.math.geometry;
import edu.wpi.first.math.proto.Geometry3D.ProtobufTransform3d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/** Represents a transformation for a Pose3d in the pose's frame. */
public class Transform3d {
@@ -173,4 +178,83 @@ public class Transform3d {
public int hashCode() {
return Objects.hash(m_translation, m_rotation);
}
public static final class AStruct implements Struct<Transform3d> {
@Override
public Class<Transform3d> getTypeClass() {
return Transform3d.class;
}
@Override
public String getTypeString() {
return "struct:Transform3d";
}
@Override
public int getSize() {
return Translation3d.struct.getSize() + Rotation3d.struct.getSize();
}
@Override
public String getSchema() {
return "Translation3d translation;Rotation3d rotation";
}
@Override
public Struct<?>[] getNested() {
return new Struct<?>[] {Translation3d.struct, Rotation3d.struct};
}
@Override
public Transform3d unpack(ByteBuffer bb) {
Translation3d translation = Translation3d.struct.unpack(bb);
Rotation3d rotation = Rotation3d.struct.unpack(bb);
return new Transform3d(translation, rotation);
}
@Override
public void pack(ByteBuffer bb, Transform3d value) {
Translation3d.struct.pack(bb, value.m_translation);
Rotation3d.struct.pack(bb, value.m_rotation);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Transform3d, ProtobufTransform3d> {
@Override
public Class<Transform3d> getTypeClass() {
return Transform3d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTransform3d.getDescriptor();
}
@Override
public Protobuf<?, ?>[] getNested() {
return new Protobuf<?, ?>[] {Translation3d.proto, Rotation3d.proto};
}
@Override
public ProtobufTransform3d createMessage() {
return ProtobufTransform3d.newInstance();
}
@Override
public Transform3d unpack(ProtobufTransform3d msg) {
return new Transform3d(
Translation3d.proto.unpack(msg.getTranslation()),
Rotation3d.proto.unpack(msg.getRotation()));
}
@Override
public void pack(ProtobufTransform3d msg, Transform3d value) {
Translation3d.proto.pack(msg.getMutableTranslation(), value.m_translation);
Rotation3d.proto.pack(msg.getMutableRotation(), value.m_rotation);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -10,10 +10,15 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.proto.Geometry2D.ProtobufTranslation2d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/**
* Represents a translation in 2D space. This object can be used to represent a point or a vector.
@@ -229,4 +234,70 @@ public class Translation2d implements Interpolatable<Translation2d> {
MathUtil.interpolate(this.getX(), endValue.getX(), t),
MathUtil.interpolate(this.getY(), endValue.getY(), t));
}
public static final class AStruct implements Struct<Translation2d> {
@Override
public Class<Translation2d> getTypeClass() {
return Translation2d.class;
}
@Override
public String getTypeString() {
return "struct:Translation2d";
}
@Override
public int getSize() {
return kSizeDouble * 2;
}
@Override
public String getSchema() {
return "double x;double y";
}
@Override
public Translation2d unpack(ByteBuffer bb) {
double x = bb.getDouble();
double y = bb.getDouble();
return new Translation2d(x, y);
}
@Override
public void pack(ByteBuffer bb, Translation2d value) {
bb.putDouble(value.m_x);
bb.putDouble(value.m_y);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Translation2d, ProtobufTranslation2d> {
@Override
public Class<Translation2d> getTypeClass() {
return Translation2d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTranslation2d.getDescriptor();
}
@Override
public ProtobufTranslation2d createMessage() {
return ProtobufTranslation2d.newInstance();
}
@Override
public Translation2d unpack(ProtobufTranslation2d msg) {
return new Translation2d(msg.getX(), msg.getY());
}
@Override
public void pack(ProtobufTranslation2d msg, Translation2d value) {
msg.setX(value.m_x).setY(value.m_y);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -10,7 +10,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.interpolation.Interpolatable;
import edu.wpi.first.math.proto.Geometry3D.ProtobufTranslation3d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/**
* Represents a translation in 3D space. This object can be used to represent a point or a vector.
@@ -231,4 +236,72 @@ public class Translation3d implements Interpolatable<Translation3d> {
MathUtil.interpolate(this.getY(), endValue.getY(), t),
MathUtil.interpolate(this.getZ(), endValue.getZ(), t));
}
public static final class AStruct implements Struct<Translation3d> {
@Override
public Class<Translation3d> getTypeClass() {
return Translation3d.class;
}
@Override
public String getTypeString() {
return "struct:Translation3d";
}
@Override
public int getSize() {
return kSizeDouble * 3;
}
@Override
public String getSchema() {
return "double x;double y;double z";
}
@Override
public Translation3d unpack(ByteBuffer bb) {
double x = bb.getDouble();
double y = bb.getDouble();
double z = bb.getDouble();
return new Translation3d(x, y, z);
}
@Override
public void pack(ByteBuffer bb, Translation3d value) {
bb.putDouble(value.m_x);
bb.putDouble(value.m_y);
bb.putDouble(value.m_z);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Translation3d, ProtobufTranslation3d> {
@Override
public Class<Translation3d> getTypeClass() {
return Translation3d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTranslation3d.getDescriptor();
}
@Override
public ProtobufTranslation3d createMessage() {
return ProtobufTranslation3d.newInstance();
}
@Override
public Translation3d unpack(ProtobufTranslation3d msg) {
return new Translation3d(msg.getX(), msg.getY(), msg.getZ());
}
@Override
public void pack(ProtobufTranslation3d msg, Translation3d value) {
msg.setX(value.m_x).setY(value.m_y).setZ(value.m_z);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -4,7 +4,12 @@
package edu.wpi.first.math.geometry;
import edu.wpi.first.math.proto.Geometry2D.ProtobufTwist2d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/**
* A change in distance along a 2D arc since the last pose update. We can use ideas from
@@ -62,4 +67,72 @@ public class Twist2d {
public int hashCode() {
return Objects.hash(dx, dy, dtheta);
}
public static final class AStruct implements Struct<Twist2d> {
@Override
public Class<Twist2d> getTypeClass() {
return Twist2d.class;
}
@Override
public String getTypeString() {
return "struct:Twist2d";
}
@Override
public int getSize() {
return kSizeDouble * 3;
}
@Override
public String getSchema() {
return "double dx;double dy;double dtheta";
}
@Override
public Twist2d unpack(ByteBuffer bb) {
double dx = bb.getDouble();
double dy = bb.getDouble();
double dtheta = bb.getDouble();
return new Twist2d(dx, dy, dtheta);
}
@Override
public void pack(ByteBuffer bb, Twist2d value) {
bb.putDouble(value.dx);
bb.putDouble(value.dy);
bb.putDouble(value.dtheta);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Twist2d, ProtobufTwist2d> {
@Override
public Class<Twist2d> getTypeClass() {
return Twist2d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTwist2d.getDescriptor();
}
@Override
public ProtobufTwist2d createMessage() {
return ProtobufTwist2d.newInstance();
}
@Override
public Twist2d unpack(ProtobufTwist2d msg) {
return new Twist2d(msg.getDx(), msg.getDy(), msg.getDtheta());
}
@Override
public void pack(ProtobufTwist2d msg, Twist2d value) {
msg.setDx(value.dx).setDy(value.dy).setDtheta(value.dtheta);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -4,7 +4,12 @@
package edu.wpi.first.math.geometry;
import edu.wpi.first.math.proto.Geometry3D.ProtobufTwist3d;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.Objects;
import us.hebi.quickbuf.Descriptors.Descriptor;
/**
* A change in distance along a 3D arc since the last pose update. We can use ideas from
@@ -82,4 +87,84 @@ public class Twist3d {
public int hashCode() {
return Objects.hash(dx, dy, dz, rx, ry, rz);
}
public static final class AStruct implements Struct<Twist3d> {
@Override
public Class<Twist3d> getTypeClass() {
return Twist3d.class;
}
@Override
public String getTypeString() {
return "struct:Twist3d";
}
@Override
public int getSize() {
return kSizeDouble * 6;
}
@Override
public String getSchema() {
return "double dx;double dy;double dz;double rx;double ry;double rz";
}
@Override
public Twist3d unpack(ByteBuffer bb) {
double dx = bb.getDouble();
double dy = bb.getDouble();
double dz = bb.getDouble();
double rx = bb.getDouble();
double ry = bb.getDouble();
double rz = bb.getDouble();
return new Twist3d(dx, dy, dz, rx, ry, rz);
}
@Override
public void pack(ByteBuffer bb, Twist3d value) {
bb.putDouble(value.dx);
bb.putDouble(value.dy);
bb.putDouble(value.dz);
bb.putDouble(value.rx);
bb.putDouble(value.ry);
bb.putDouble(value.rz);
}
}
public static final AStruct struct = new AStruct();
public static final class AProto implements Protobuf<Twist3d, ProtobufTwist3d> {
@Override
public Class<Twist3d> getTypeClass() {
return Twist3d.class;
}
@Override
public Descriptor getDescriptor() {
return ProtobufTwist3d.getDescriptor();
}
@Override
public ProtobufTwist3d createMessage() {
return ProtobufTwist3d.newInstance();
}
@Override
public Twist3d unpack(ProtobufTwist3d msg) {
return new Twist3d(
msg.getDx(), msg.getDy(), msg.getDz(), msg.getRx(), msg.getRy(), msg.getRz());
}
@Override
public void pack(ProtobufTwist3d msg, Twist3d value) {
msg.setDx(value.dx)
.setDy(value.dy)
.setDz(value.dz)
.setRx(value.rx)
.setRy(value.ry)
.setRz(value.rz);
}
}
public static final AProto proto = new AProto();
}

View File

@@ -9,6 +9,7 @@
#include <wpi/json.h>
#include "frc/MathUtil.h"
#include "geometry2d.pb.h"
using namespace frc;
@@ -108,3 +109,23 @@ void frc::from_json(const wpi::json& json, Pose2d& pose) {
pose = Pose2d{json.at("translation").get<Translation2d>(),
json.at("rotation").get<Rotation2d>()};
}
google::protobuf::Message* wpi::Protobuf<frc::Pose2d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufPose2d>(
arena);
}
frc::Pose2d wpi::Protobuf<frc::Pose2d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufPose2d*>(&msg);
return Pose2d{wpi::UnpackProtobuf<frc::Translation2d>(m->translation()),
wpi::UnpackProtobuf<frc::Rotation2d>(m->rotation())};
}
void wpi::Protobuf<frc::Pose2d>::Pack(google::protobuf::Message* msg,
const frc::Pose2d& value) {
auto m = static_cast<wpi::proto::ProtobufPose2d*>(msg);
wpi::PackProtobuf(m->mutable_translation(), value.Translation());
wpi::PackProtobuf(m->mutable_rotation(), value.Rotation());
}

View File

@@ -9,6 +9,8 @@
#include <Eigen/Core>
#include <wpi/json.h>
#include "geometry3d.pb.h"
using namespace frc;
namespace {
@@ -187,3 +189,23 @@ void frc::from_json(const wpi::json& json, Pose3d& pose) {
pose = Pose3d{json.at("translation").get<Translation3d>(),
json.at("rotation").get<Rotation3d>()};
}
google::protobuf::Message* wpi::Protobuf<frc::Pose3d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufPose3d>(
arena);
}
frc::Pose3d wpi::Protobuf<frc::Pose3d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufPose3d*>(&msg);
return Pose3d{wpi::UnpackProtobuf<frc::Translation3d>(m->translation()),
wpi::UnpackProtobuf<frc::Rotation3d>(m->rotation())};
}
void wpi::Protobuf<frc::Pose3d>::Pack(google::protobuf::Message* msg,
const frc::Pose3d& value) {
auto m = static_cast<wpi::proto::ProtobufPose3d*>(msg);
wpi::PackProtobuf(m->mutable_translation(), value.Translation());
wpi::PackProtobuf(m->mutable_rotation(), value.Rotation());
}

View File

@@ -8,6 +8,8 @@
#include <wpi/json.h>
#include "geometry3d.pb.h"
using namespace frc;
Quaternion::Quaternion(double w, double x, double y, double z)
@@ -230,3 +232,24 @@ void frc::from_json(const wpi::json& json, Quaternion& quaternion) {
Quaternion{json.at("W").get<double>(), json.at("X").get<double>(),
json.at("Y").get<double>(), json.at("Z").get<double>()};
}
google::protobuf::Message* wpi::Protobuf<frc::Quaternion>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufQuaternion>(
arena);
}
frc::Quaternion wpi::Protobuf<frc::Quaternion>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufQuaternion*>(&msg);
return frc::Quaternion{m->w(), m->x(), m->y(), m->z()};
}
void wpi::Protobuf<frc::Quaternion>::Pack(google::protobuf::Message* msg,
const frc::Quaternion& value) {
auto m = static_cast<wpi::proto::ProtobufQuaternion*>(msg);
m->set_w(value.W());
m->set_x(value.X());
m->set_y(value.Y());
m->set_z(value.Z());
}

View File

@@ -8,6 +8,7 @@
#include <wpi/json.h>
#include "geometry2d.pb.h"
#include "units/math.h"
using namespace frc;
@@ -19,3 +20,21 @@ void frc::to_json(wpi::json& json, const Rotation2d& rotation) {
void frc::from_json(const wpi::json& json, Rotation2d& rotation) {
rotation = Rotation2d{units::radian_t{json.at("radians").get<double>()}};
}
google::protobuf::Message* wpi::Protobuf<frc::Rotation2d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufRotation2d>(
arena);
}
frc::Rotation2d wpi::Protobuf<frc::Rotation2d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufRotation2d*>(&msg);
return frc::Rotation2d{units::radian_t{m->value()}};
}
void wpi::Protobuf<frc::Rotation2d>::Pack(google::protobuf::Message* msg,
const frc::Rotation2d& value) {
auto m = static_cast<wpi::proto::ProtobufRotation2d*>(msg);
m->set_value(value.Radians().value());
}

View File

@@ -13,6 +13,7 @@
#include <wpi/json.h>
#include "frc/fmt/Eigen.h"
#include "geometry3d.pb.h"
#include "units/math.h"
#include "wpimath/MathShared.h"
@@ -261,3 +262,21 @@ void frc::to_json(wpi::json& json, const Rotation3d& rotation) {
void frc::from_json(const wpi::json& json, Rotation3d& rotation) {
rotation = Rotation3d{json.at("quaternion").get<Quaternion>()};
}
google::protobuf::Message* wpi::Protobuf<frc::Rotation3d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufRotation3d>(
arena);
}
frc::Rotation3d wpi::Protobuf<frc::Rotation3d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufRotation3d*>(&msg);
return Rotation3d{wpi::UnpackProtobuf<frc::Quaternion>(m->q())};
}
void wpi::Protobuf<frc::Rotation3d>::Pack(google::protobuf::Message* msg,
const frc::Rotation3d& value) {
auto m = static_cast<wpi::proto::ProtobufRotation3d*>(msg);
wpi::PackProtobuf(m->mutable_q(), value.GetQuaternion());
}

View File

@@ -5,6 +5,7 @@
#include "frc/geometry/Transform2d.h"
#include "frc/geometry/Pose2d.h"
#include "geometry2d.pb.h"
using namespace frc;
@@ -21,3 +22,23 @@ Transform2d::Transform2d(Pose2d initial, Pose2d final) {
Transform2d Transform2d::operator+(const Transform2d& other) const {
return Transform2d{Pose2d{}, Pose2d{}.TransformBy(*this).TransformBy(other)};
}
google::protobuf::Message* wpi::Protobuf<frc::Transform2d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<
wpi::proto::ProtobufTransform2d>(arena);
}
frc::Transform2d wpi::Protobuf<frc::Transform2d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTransform2d*>(&msg);
return Transform2d{wpi::UnpackProtobuf<frc::Translation2d>(m->translation()),
wpi::UnpackProtobuf<frc::Rotation2d>(m->rotation())};
}
void wpi::Protobuf<frc::Transform2d>::Pack(google::protobuf::Message* msg,
const frc::Transform2d& value) {
auto m = static_cast<wpi::proto::ProtobufTransform2d*>(msg);
wpi::PackProtobuf(m->mutable_translation(), value.Translation());
wpi::PackProtobuf(m->mutable_rotation(), value.Rotation());
}

View File

@@ -5,6 +5,7 @@
#include "frc/geometry/Transform3d.h"
#include "frc/geometry/Pose3d.h"
#include "geometry3d.pb.h"
using namespace frc;
@@ -35,3 +36,23 @@ Transform3d Transform3d::Inverse() const {
Transform3d Transform3d::operator+(const Transform3d& other) const {
return Transform3d{Pose3d{}, Pose3d{}.TransformBy(*this).TransformBy(other)};
}
google::protobuf::Message* wpi::Protobuf<frc::Transform3d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<
wpi::proto::ProtobufTransform3d>(arena);
}
frc::Transform3d wpi::Protobuf<frc::Transform3d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTransform3d*>(&msg);
return Transform3d{wpi::UnpackProtobuf<frc::Translation3d>(m->translation()),
wpi::UnpackProtobuf<frc::Rotation3d>(m->rotation())};
}
void wpi::Protobuf<frc::Transform3d>::Pack(google::protobuf::Message* msg,
const frc::Transform3d& value) {
auto m = static_cast<wpi::proto::ProtobufTransform3d*>(msg);
wpi::PackProtobuf(m->mutable_translation(), value.Translation());
wpi::PackProtobuf(m->mutable_rotation(), value.Rotation());
}

View File

@@ -6,6 +6,7 @@
#include <wpi/json.h>
#include "geometry2d.pb.h"
#include "units/math.h"
using namespace frc;
@@ -48,3 +49,22 @@ void frc::from_json(const wpi::json& json, Translation2d& translation) {
translation = Translation2d{units::meter_t{json.at("x").get<double>()},
units::meter_t{json.at("y").get<double>()}};
}
google::protobuf::Message* wpi::Protobuf<frc::Translation2d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<
wpi::proto::ProtobufTranslation2d>(arena);
}
frc::Translation2d wpi::Protobuf<frc::Translation2d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTranslation2d*>(&msg);
return frc::Translation2d{units::meter_t{m->x()}, units::meter_t{m->y()}};
}
void wpi::Protobuf<frc::Translation2d>::Pack(google::protobuf::Message* msg,
const frc::Translation2d& value) {
auto m = static_cast<wpi::proto::ProtobufTranslation2d*>(msg);
m->set_x(value.X().value());
m->set_y(value.Y().value());
}

View File

@@ -6,6 +6,7 @@
#include <wpi/json.h>
#include "geometry3d.pb.h"
#include "units/length.h"
#include "units/math.h"
@@ -52,3 +53,24 @@ void frc::from_json(const wpi::json& json, Translation3d& translation) {
units::meter_t{json.at("y").get<double>()},
units::meter_t{json.at("z").get<double>()}};
}
google::protobuf::Message* wpi::Protobuf<frc::Translation3d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<
wpi::proto::ProtobufTranslation3d>(arena);
}
frc::Translation3d wpi::Protobuf<frc::Translation3d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTranslation3d*>(&msg);
return frc::Translation3d{units::meter_t{m->x()}, units::meter_t{m->y()},
units::meter_t{m->z()}};
}
void wpi::Protobuf<frc::Translation3d>::Pack(google::protobuf::Message* msg,
const frc::Translation3d& value) {
auto m = static_cast<wpi::proto::ProtobufTranslation3d*>(msg);
m->set_x(value.X().value());
m->set_y(value.Y().value());
m->set_z(value.Z().value());
}

View File

@@ -0,0 +1,30 @@
// 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 "frc/geometry/Twist2d.h"
#include "geometry2d.pb.h"
using namespace frc;
google::protobuf::Message* wpi::Protobuf<frc::Twist2d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufTwist2d>(
arena);
}
frc::Twist2d wpi::Protobuf<frc::Twist2d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTwist2d*>(&msg);
return frc::Twist2d{units::meter_t{m->dx()}, units::meter_t{m->dy()},
units::radian_t{m->dtheta()}};
}
void wpi::Protobuf<frc::Twist2d>::Pack(google::protobuf::Message* msg,
const frc::Twist2d& value) {
auto m = static_cast<wpi::proto::ProtobufTwist2d*>(msg);
m->set_dx(value.dx.value());
m->set_dy(value.dy.value());
m->set_dtheta(value.dtheta.value());
}

View File

@@ -0,0 +1,34 @@
// 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 "frc/geometry/Twist3d.h"
#include "geometry3d.pb.h"
using namespace frc;
google::protobuf::Message* wpi::Protobuf<frc::Twist3d>::New(
google::protobuf::Arena* arena) {
return google::protobuf::Arena::CreateMessage<wpi::proto::ProtobufTwist3d>(
arena);
}
frc::Twist3d wpi::Protobuf<frc::Twist3d>::Unpack(
const google::protobuf::Message& msg) {
auto m = static_cast<const wpi::proto::ProtobufTwist3d*>(&msg);
return frc::Twist3d{units::meter_t{m->dx()}, units::meter_t{m->dy()},
units::meter_t{m->dz()}, units::radian_t{m->rx()},
units::radian_t{m->ry()}, units::radian_t{m->rz()}};
}
void wpi::Protobuf<frc::Twist3d>::Pack(google::protobuf::Message* msg,
const frc::Twist3d& value) {
auto m = static_cast<wpi::proto::ProtobufTwist3d*>(msg);
m->set_dx(value.dx.value());
m->set_dy(value.dy.value());
m->set_dz(value.dz.value());
m->set_rx(value.rx.value());
m->set_ry(value.ry.value());
m->set_rz(value.rz.value());
}

View File

@@ -9,7 +9,10 @@
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Rotation2d.h"
#include "frc/geometry/Transform2d.h"
#include "frc/geometry/Translation2d.h"
#include "frc/geometry/Twist2d.h"
@@ -212,4 +215,38 @@ void from_json(const wpi::json& json, Pose2d& pose);
} // namespace frc
template <>
struct wpi::Struct<frc::Pose2d> {
static constexpr std::string_view kTypeString = "struct:Pose2d";
static constexpr size_t kSize = wpi::Struct<frc::Translation2d>::kSize +
wpi::Struct<frc::Rotation2d>::kSize;
static constexpr std::string_view kSchema =
"Translation2d translation;Rotation2d rotation";
static frc::Pose2d Unpack(std::span<const uint8_t, kSize> data) {
return {wpi::UnpackStruct<frc::Translation2d, 0>(data),
wpi::UnpackStruct<frc::Rotation2d, kRotationOff>(data)};
}
static void Pack(std::span<uint8_t, kSize> data, const frc::Pose2d& value) {
wpi::PackStruct<0>(data, value.Translation());
wpi::PackStruct<kRotationOff>(data, value.Rotation());
}
static void ForEachNested(
std::invocable<std::string_view, std::string_view> auto fn) {
wpi::ForEachStructSchema<frc::Translation2d>(fn);
wpi::ForEachStructSchema<frc::Rotation2d>(fn);
}
private:
static constexpr size_t kRotationOff = wpi::Struct<frc::Translation2d>::kSize;
};
static_assert(wpi::HasNestedStruct<frc::Pose2d>);
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Pose2d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Pose2d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg, const frc::Pose2d& value);
};
#include "frc/geometry/Pose2d.inc"

View File

@@ -6,8 +6,11 @@
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Pose2d.h"
#include "frc/geometry/Rotation3d.h"
#include "frc/geometry/Transform3d.h"
#include "frc/geometry/Translation3d.h"
#include "frc/geometry/Twist3d.h"
@@ -213,3 +216,37 @@ WPILIB_DLLEXPORT
void from_json(const wpi::json& json, Pose3d& pose);
} // namespace frc
template <>
struct wpi::Struct<frc::Pose3d> {
static constexpr std::string_view kTypeString = "struct:Pose3d";
static constexpr size_t kSize = wpi::Struct<frc::Translation3d>::kSize +
wpi::Struct<frc::Rotation3d>::kSize;
static constexpr std::string_view kSchema =
"Translation3d translation;Rotation3d rotation";
static frc::Pose3d Unpack(std::span<const uint8_t, kSize> data) {
return {wpi::UnpackStruct<frc::Translation3d, 0>(data),
wpi::UnpackStruct<frc::Rotation3d, kRotationOff>(data)};
}
static void Pack(std::span<uint8_t, kSize> data, const frc::Pose3d& value) {
wpi::PackStruct<0>(data, value.Translation());
wpi::PackStruct<kRotationOff>(data, value.Rotation());
}
static void ForEachNested(
std::invocable<std::string_view, std::string_view> auto fn) {
wpi::ForEachStructSchema<frc::Translation3d>(fn);
wpi::ForEachStructSchema<frc::Rotation3d>(fn);
}
private:
static constexpr size_t kRotationOff = wpi::Struct<frc::Translation3d>::kSize;
};
static_assert(wpi::HasNestedStruct<frc::Pose3d>);
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Pose3d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Pose3d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg, const frc::Pose3d& value);
};

View File

@@ -7,6 +7,8 @@
#include <Eigen/Core>
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
namespace frc {
@@ -187,3 +189,32 @@ WPILIB_DLLEXPORT
void from_json(const wpi::json& json, Quaternion& quaternion);
} // namespace frc
template <>
struct wpi::Struct<frc::Quaternion> {
static constexpr std::string_view kTypeString = "struct:Quaternion";
static constexpr size_t kSize = 32;
static constexpr std::string_view kSchema =
"double w;double x;double y;double z";
static frc::Quaternion Unpack(std::span<const uint8_t, 32> data) {
return {wpi::UnpackStruct<double, 0>(data),
wpi::UnpackStruct<double, 8>(data),
wpi::UnpackStruct<double, 16>(data),
wpi::UnpackStruct<double, 24>(data)};
}
static void Pack(std::span<uint8_t, 32> data, const frc::Quaternion& value) {
wpi::PackStruct<0>(data, value.W());
wpi::PackStruct<8>(data, value.X());
wpi::PackStruct<16>(data, value.Y());
wpi::PackStruct<24>(data, value.Z());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Quaternion> {
static constexpr std::string_view kTypeString = "proto:Quaternion";
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Quaternion Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Quaternion& value);
};

View File

@@ -6,6 +6,8 @@
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "units/angle.h"
@@ -176,4 +178,25 @@ void from_json(const wpi::json& json, Rotation2d& rotation);
} // namespace frc
template <>
struct wpi::Struct<frc::Rotation2d> {
static constexpr std::string_view kTypeString = "struct:Rotation2d";
static constexpr size_t kSize = 8;
static constexpr std::string_view kSchema = "double value";
static frc::Rotation2d Unpack(std::span<const uint8_t, 8> data) {
return units::radian_t{wpi::UnpackStruct<double>(data)};
}
static void Pack(std::span<uint8_t, 8> data, const frc::Rotation2d& value) {
wpi::PackStruct(data, value.Radians().value());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Rotation2d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Rotation2d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Rotation2d& value);
};
#include "frc/geometry/Rotation2d.inc"

View File

@@ -7,6 +7,8 @@
#include <Eigen/Core>
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Quaternion.h"
#include "frc/geometry/Rotation2d.h"
@@ -194,3 +196,31 @@ WPILIB_DLLEXPORT
void from_json(const wpi::json& json, Rotation3d& rotation);
} // namespace frc
template <>
struct wpi::Struct<frc::Rotation3d> {
static constexpr std::string_view kTypeString = "struct:Rotation3d";
static constexpr size_t kSize = wpi::Struct<frc::Quaternion>::kSize;
static constexpr std::string_view kSchema = "Quaternion q";
static frc::Rotation3d Unpack(std::span<const uint8_t, kSize> data) {
return frc::Rotation3d{wpi::UnpackStruct<frc::Quaternion, 0>(data)};
}
static void Pack(std::span<uint8_t, kSize> data,
const frc::Rotation3d& value) {
wpi::PackStruct<0>(data, value.GetQuaternion());
}
static void ForEachNested(
std::invocable<std::string_view, std::string_view> auto fn) {
wpi::ForEachStructSchema<frc::Quaternion>(fn);
}
};
static_assert(wpi::HasNestedStruct<frc::Rotation3d>);
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Rotation3d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Rotation3d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Rotation3d& value);
};

View File

@@ -5,6 +5,8 @@
#pragma once
#include <wpi/SymbolExports.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Translation2d.h"
@@ -124,4 +126,40 @@ class WPILIB_DLLEXPORT Transform2d {
};
} // namespace frc
template <>
struct wpi::Struct<frc::Transform2d> {
static constexpr std::string_view kTypeString = "struct:Transform2d";
static constexpr size_t kSize = wpi::Struct<frc::Translation2d>::kSize +
wpi::Struct<frc::Rotation2d>::kSize;
static constexpr std::string_view kSchema =
"Translation2d translation;Rotation2d rotation";
static frc::Transform2d Unpack(std::span<const uint8_t, kSize> data) {
return {wpi::UnpackStruct<frc::Translation2d, 0>(data),
wpi::UnpackStruct<frc::Rotation2d, kRotationOff>(data)};
}
static void Pack(std::span<uint8_t, kSize> data,
const frc::Transform2d& value) {
wpi::PackStruct<0>(data, value.Translation());
wpi::PackStruct<kRotationOff>(data, value.Rotation());
}
static void ForEachNested(
std::invocable<std::string_view, std::string_view> auto fn) {
wpi::ForEachStructSchema<frc::Translation2d>(fn);
wpi::ForEachStructSchema<frc::Rotation2d>(fn);
}
private:
static constexpr size_t kRotationOff = wpi::Struct<frc::Translation2d>::kSize;
};
static_assert(wpi::HasNestedStruct<frc::Transform2d>);
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Transform2d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Transform2d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Transform2d& value);
};
#include "frc/geometry/Transform2d.inc"

View File

@@ -5,6 +5,8 @@
#pragma once
#include <wpi/SymbolExports.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Translation3d.h"
@@ -129,3 +131,39 @@ class WPILIB_DLLEXPORT Transform3d {
Rotation3d m_rotation;
};
} // namespace frc
template <>
struct wpi::Struct<frc::Transform3d> {
static constexpr std::string_view kTypeString = "struct:Transform3d";
static constexpr size_t kSize = wpi::Struct<frc::Translation3d>::kSize +
wpi::Struct<frc::Rotation3d>::kSize;
static constexpr std::string_view kSchema =
"Translation3d translation;Rotation3d rotation";
static frc::Transform3d Unpack(std::span<const uint8_t, kSize> data) {
return {wpi::UnpackStruct<frc::Translation3d, 0>(data),
wpi::UnpackStruct<frc::Rotation3d, kRotationOff>(data)};
}
static void Pack(std::span<uint8_t, kSize> data,
const frc::Transform3d& value) {
wpi::PackStruct<0>(data, value.Translation());
wpi::PackStruct<kRotationOff>(data, value.Rotation());
}
static void ForEachNested(
std::invocable<std::string_view, std::string_view> auto fn) {
wpi::ForEachStructSchema<frc::Translation3d>(fn);
wpi::ForEachStructSchema<frc::Rotation3d>(fn);
}
private:
static constexpr size_t kRotationOff = wpi::Struct<frc::Translation3d>::kSize;
};
static_assert(wpi::HasNestedStruct<frc::Transform3d>);
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Transform3d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Transform3d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Transform3d& value);
};

View File

@@ -9,6 +9,8 @@
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Rotation2d.h"
#include "units/length.h"
@@ -198,4 +200,28 @@ void from_json(const wpi::json& json, Translation2d& state);
} // namespace frc
template <>
struct wpi::Struct<frc::Translation2d> {
static constexpr std::string_view kTypeString = "struct:Translation2d";
static constexpr size_t kSize = 16;
static constexpr std::string_view kSchema = "double x;double y";
static frc::Translation2d Unpack(std::span<const uint8_t, 16> data) {
return {units::meter_t{wpi::UnpackStruct<double, 0>(data)},
units::meter_t{wpi::UnpackStruct<double, 8>(data)}};
}
static void Pack(std::span<uint8_t, 16> data,
const frc::Translation2d& value) {
wpi::PackStruct<0>(data, value.X().value());
wpi::PackStruct<8>(data, value.Y().value());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Translation2d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Translation2d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Translation2d& value);
};
#include "frc/geometry/Translation2d.inc"

View File

@@ -6,6 +6,8 @@
#include <wpi/SymbolExports.h>
#include <wpi/json_fwd.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Rotation3d.h"
#include "frc/geometry/Translation2d.h"
@@ -183,4 +185,30 @@ void from_json(const wpi::json& json, Translation3d& state);
} // namespace frc
template <>
struct wpi::Struct<frc::Translation3d> {
static constexpr std::string_view kTypeString = "struct:Translation3d";
static constexpr size_t kSize = 24;
static constexpr std::string_view kSchema = "double x;double y;double z";
static frc::Translation3d Unpack(std::span<const uint8_t, 24> data) {
return {units::meter_t{wpi::UnpackStruct<double, 0>(data)},
units::meter_t{wpi::UnpackStruct<double, 8>(data)},
units::meter_t{wpi::UnpackStruct<double, 16>(data)}};
}
static void Pack(std::span<uint8_t, 24> data,
const frc::Translation3d& value) {
wpi::PackStruct<0>(data, value.X().value());
wpi::PackStruct<8>(data, value.Y().value());
wpi::PackStruct<16>(data, value.Z().value());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Translation3d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Translation3d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg,
const frc::Translation3d& value);
};
#include "frc/geometry/Translation3d.inc"

View File

@@ -5,6 +5,8 @@
#pragma once
#include <wpi/SymbolExports.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "units/angle.h"
#include "units/length.h"
@@ -57,3 +59,28 @@ struct WPILIB_DLLEXPORT Twist2d {
}
};
} // namespace frc
template <>
struct wpi::Struct<frc::Twist2d> {
static constexpr std::string_view kTypeString = "struct:Twist2d";
static constexpr size_t kSize = 24;
static constexpr std::string_view kSchema =
"double dx;double dy;double dtheta";
static frc::Twist2d Unpack(std::span<const uint8_t, 24> data) {
return {units::meter_t{wpi::UnpackStruct<double, 0>(data)},
units::meter_t{wpi::UnpackStruct<double, 8>(data)},
units::radian_t{wpi::UnpackStruct<double, 16>(data)}};
}
static void Pack(std::span<uint8_t, 24> data, const frc::Twist2d& value) {
wpi::PackStruct<0>(data, value.dx.value());
wpi::PackStruct<8>(data, value.dy.value());
wpi::PackStruct<16>(data, value.dtheta.value());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Twist2d> {
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Twist2d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg, const frc::Twist2d& value);
};

View File

@@ -5,6 +5,8 @@
#pragma once
#include <wpi/SymbolExports.h>
#include <wpi/protobuf/Protobuf.h>
#include <wpi/struct/Struct.h>
#include "frc/geometry/Rotation3d.h"
#include "units/angle.h"
@@ -77,3 +79,35 @@ struct WPILIB_DLLEXPORT Twist3d {
}
};
} // namespace frc
template <>
struct wpi::Struct<frc::Twist3d> {
static constexpr std::string_view kTypeString = "struct:Twist3d";
static constexpr size_t kSize = 48;
static constexpr std::string_view kSchema =
"double dx;double dy;double dz;double rx;double ry;double rz";
static frc::Twist3d Unpack(std::span<const uint8_t, 48> data) {
return {units::meter_t{wpi::UnpackStruct<double, 0>(data)},
units::meter_t{wpi::UnpackStruct<double, 8>(data)},
units::meter_t{wpi::UnpackStruct<double, 16>(data)},
units::radian_t{wpi::UnpackStruct<double, 24>(data)},
units::radian_t{wpi::UnpackStruct<double, 32>(data)},
units::radian_t{wpi::UnpackStruct<double, 40>(data)}};
}
static void Pack(std::span<uint8_t, 48> data, const frc::Twist3d& value) {
wpi::PackStruct<0>(data, value.dx.value());
wpi::PackStruct<8>(data, value.dy.value());
wpi::PackStruct<16>(data, value.dz.value());
wpi::PackStruct<24>(data, value.rx.value());
wpi::PackStruct<32>(data, value.ry.value());
wpi::PackStruct<40>(data, value.rz.value());
}
};
template <>
struct WPILIB_DLLEXPORT wpi::Protobuf<frc::Twist3d> {
static constexpr std::string_view kTypeString = "proto:Twist3d";
static google::protobuf::Message* New(google::protobuf::Arena* arena);
static frc::Twist3d Unpack(const google::protobuf::Message& msg);
static void Pack(google::protobuf::Message* msg, const frc::Twist3d& value);
};

View File

@@ -0,0 +1,37 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufArmFeedforward {
double ks = 1;
double kg = 2;
double kv = 3;
double ka = 4;
}
message ProtobufDifferentialDriveFeedforward {
double kv_linear = 1;
double ka_linear = 2;
double kv_angular = 3;
double ka_angular = 4;
}
message ProtobufElevatorFeedforward {
double ks = 1;
double kg = 2;
double kv = 3;
double ka = 4;
}
message ProtobufSimpleMotorFeedforward {
double ks = 1;
double kv = 2;
double ka = 3;
}
message ProtobufDifferentialDriveWheelVoltages {
double left = 1;
double right = 2;
}

View File

@@ -0,0 +1,30 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufTranslation2d {
double x = 1;
double y = 2;
}
message ProtobufRotation2d {
double value = 1;
}
message ProtobufPose2d {
ProtobufTranslation2d translation = 1;
ProtobufRotation2d rotation = 2;
}
message ProtobufTransform2d {
ProtobufTranslation2d translation = 1;
ProtobufRotation2d rotation = 2;
}
message ProtobufTwist2d {
double dx = 1;
double dy = 2;
double dtheta = 3;
}

View File

@@ -0,0 +1,41 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufTranslation3d {
double x = 1;
double y = 2;
double z = 3;
}
message ProtobufQuaternion {
double w = 1;
double x = 2;
double y = 3;
double z = 4;
}
message ProtobufRotation3d {
ProtobufQuaternion q = 1;
}
message ProtobufPose3d {
ProtobufTranslation3d translation = 1;
ProtobufRotation3d rotation = 2;
}
message ProtobufTransform3d {
ProtobufTranslation3d translation = 1;
ProtobufRotation3d rotation = 2;
}
message ProtobufTwist3d {
double dx = 1;
double dy = 2;
double dz = 3;
double rx = 4;
double ry = 5;
double rz = 6;
}

View File

@@ -0,0 +1,64 @@
syntax = "proto3";
package wpi.proto;
import "geometry2d.proto";
option java_package = "edu.wpi.first.math.proto";
message ProtobufChassisSpeeds {
double vx = 1;
double vy = 2;
double omega = 3;
}
message ProtobufDifferentialDriveKinematics {
double track_width = 1;
}
message ProtobufDifferentialDriveWheelSpeeds {
double left = 1;
double right = 2;
}
message ProtobufMecanumDriveKinematics {
ProtobufTranslation2d front_left = 1;
ProtobufTranslation2d front_right = 2;
ProtobufTranslation2d rear_left = 3;
ProtobufTranslation2d rear_right = 4;
}
message ProtobufMecanumDriveMotorVoltages {
double front_left = 1;
double front_right = 2;
double rear_left = 3;
double rear_right = 4;
}
message ProtobufMecanumDriveWheelPositions {
double front_left = 1;
double front_right = 2;
double rear_left = 3;
double rear_right = 4;
}
message ProtobufMecanumDriveWheelSpeeds {
double front_left = 1;
double front_right = 2;
double rear_left = 3;
double rear_right = 4;
}
message ProtobufSwerveDriveKinematics {
repeated ProtobufTranslation2d modules = 1;
}
message ProtobufSwerveModulePosition {
double distance = 1;
ProtobufRotation2d angle = 2;
}
message ProtobufSwerveModuleState {
double speed = 1;
ProtobufRotation2d angle = 2;
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufDCMotor {
double nominal_voltage = 1;
double stall_torque = 2;
double stall_current = 3;
double free_current = 4;
double free_speed = 5;
double r = 6;
double kv = 7;
double kt = 8;
}

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufCubicHermiteSpline {
repeated double x_initial = 1;
repeated double x_final = 2;
repeated double y_initial = 3;
repeated double y_final = 4;
}
message ProtobufQuinticHermiteSpline {
repeated double x_initial = 1;
repeated double x_final = 2;
repeated double y_initial = 3;
repeated double y_final = 4;
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package wpi.proto;
import "wpimath.proto";
option java_package = "edu.wpi.first.math.proto";
message ProtobufLinearSystem {
uint32 num_states = 1;
uint32 num_inputs = 2;
uint32 num_outputs = 3;
ProtobufMatrix a = 4;
ProtobufMatrix b = 5;
ProtobufMatrix c = 6;
ProtobufMatrix d = 7;
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package wpi.proto;
import "geometry2d.proto";
option java_package = "edu.wpi.first.math.proto";
message ProtobufTrajectoryState {
double time = 1;
double velocity = 2;
double acceleration = 3;
ProtobufPose2d pose = 4;
double curvature = 5;
}
message ProtobufTrajectory {
double total_time = 1;
repeated ProtobufTrajectoryState states = 2;
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package wpi.proto;
option java_package = "edu.wpi.first.math.proto";
message ProtobufMatrix {
uint32 num_rows = 1;
uint32 num_cols = 2;
repeated double data = 3;
}
message ProtobufVector {
repeated double rows = 1;
}

View File

@@ -41,6 +41,7 @@ repoRootNameOverride {
includeOtherLibs {
^fmt/
^google/
^gmock/
^gtest/
}

View File

@@ -34,13 +34,27 @@ if (WITH_JAVA)
file(GLOB JACKSON_JARS
${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar)
set(CMAKE_JAVA_INCLUDE_PATH wpiutil.jar ${JACKSON_JARS})
if(NOT EXISTS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/quickbuf-runtime-1.3.2.jar")
set(BASE_URL "https://search.maven.org/remotecontent?filepath=")
set(JAR_ROOT "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf")
message(STATUS "Downloading Quickbuf jarfile...")
file(DOWNLOAD "${BASE_URL}us/hebi/quickbuf/quickbuf-runtime/1.3.2/quickbuf-runtime-1.3.2.jar"
"${JAR_ROOT}/quickbuf-runtime-1.3.2.jar")
message(STATUS "Downloaded.")
endif()
file(GLOB QUICKBUF_JAR
${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar)
set(CMAKE_JAVA_INCLUDE_PATH wpiutil.jar ${JACKSON_JARS} ${QUICKBUF_JAR})
set(CMAKE_JNI_TARGET true)
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
add_jar(wpiutil_jar ${JAVA_SOURCES} INCLUDE_JARS ${JACKSON_JARS} OUTPUT_NAME wpiutil GENERATE_NATIVE_HEADERS wpiutil_jni_headers)
add_jar(wpiutil_jar ${JAVA_SOURCES} INCLUDE_JARS ${JACKSON_JARS} ${QUICKBUF_JAR} OUTPUT_NAME wpiutil GENERATE_NATIVE_HEADERS wpiutil_jni_headers)
get_property(WPIUTIL_JAR_FILE TARGET wpiutil_jar PROPERTY JAR_FILE)
install(FILES ${WPIUTIL_JAR_FILE} DESTINATION "${java_lib_dest}")
@@ -98,7 +112,7 @@ if (MSVC)
target_compile_definitions(wpiutil PRIVATE -D_CRT_SECURE_NO_WARNINGS)
endif()
wpilib_target_warnings(wpiutil)
target_link_libraries(wpiutil Threads::Threads ${CMAKE_DL_LIBS})
target_link_libraries(wpiutil protobuf::libprotobuf Threads::Threads ${CMAKE_DL_LIBS})
if (ATOMIC)
target_link_libraries(wpiutil ${ATOMIC})

View File

@@ -294,6 +294,7 @@ dependencies {
api "com.fasterxml.jackson.core:jackson-annotations:2.15.2"
api "com.fasterxml.jackson.core:jackson-core:2.15.2"
api "com.fasterxml.jackson.core:jackson-databind:2.15.2"
api 'us.hebi.quickbuf:quickbuf-runtime:1.3.2'
printlogImplementation sourceSets.main.output
}

311
wpiutil/doc/struct.adoc Normal file
View File

@@ -0,0 +1,311 @@
= WPILib Packed Struct Serialization Specification, Version 1.0
WPILib Developers <wpilib@wpi.edu>
Revision 1.0 (0x0100), 6/8/2023
:toc:
:toc-placement: preamble
:sectanchors:
A simple format and schema for serialization of packed fixed size structured data.
[[motivation]]
== Motivation
Schema-based serialization formats such as Protobuf and Flatbuffers are extremely flexible and can handle data type evolution, complex nested data structures, variable size / repeated data, optional fields, etc. However, this flexibility comes at a cost in both serialized data size and processing overhead. Many simple data structures, such as screen coordinates or robot poses, are fixed in size and can be stored much more compactly and serialized/deserialized much more quickly, especially on embedded or real-time platforms.
Simply storing a C-style packed structure is very compact and fast, but information about the layout of the structure and the meaning of each member must be separately communicated for introspection by other tools such as interactive dashboards for data analysis of individual structure members. The motivation for this standard layout and schema is to provide a standardized means to communicate this information and enable dynamic decoding.
Python's struct module uses a character-based approach to describe data layout of structures, but has no provisions for naming each member to communicate intent/meaning.
[[references]]
== References
[[c-struct-declaration]]
* Struct declaration, https://en.cppreference.com/w/c/language/struct
[[definitions]]
== Definitions
[[schema]]
== Schema
The schema is a text-based format with similar syntax to the list of variable declarations in a C structure. The C syntax is flexible, easy to parse, and matches the intent of specifying a fixed size structure.
Each member of the struct is defined by a single declaration. Each declaration is either a standard declaration or a bit-field declaration. Declarations are separated by semicolons. The last declaration may optionally have a trailing semicolon. Empty declarations (e.g. two semicolons back-to-back or separated by only whitespace) are allowed but are ignored. Unlike C structures, every declaration must be separated by a semicolon; commas cannot be used to declare multiple members with the same type. Declarations may also start and end with whitespace.
[[variable]]
=== Standard Declaration
Standard declarations declare a member of a certain type or a fixed-size array of that type. The structure of a standard declaration is:
* optional enum specification (integer data types only)
* optional whitespace
* type name
* whitespace
* identifier name
* optional array size, consisting of:
* optional whitespace
* `[`
* optional whitespace
* size of array
* optional whitespace
* `]`
The type name may be one of these:
[cols="1,1,3", options="header"]
|===
|Type Name|Description|Payload Data Contents
|`bool`|boolean|single byte (0=false, 1=true)
|`char`|character|single byte (assumed UTF-8)
|`int8`|integer|1-byte (8-bit) signed value
|`int16`|integer|2-byte (16-bit) signed value
|`int32`|integer|4-byte (32-bit) signed value
|`int64`|integer|8-byte (64-bit) signed value
|`uint8`|unsigned integer|1-byte (8-bit) unsigned value
|`uint16`|unsigned integer|2-byte (16-bit) unsigned value
|`uint32`|unsigned integer|4-byte (32-bit) unsigned value
|`uint64`|unsigned integer|8-byte (64-bit) unsigned value
|`float` or `float32`|float|4-byte (32-bit) IEEE-754 value
|`double` or `float64`|double|8-byte (64-bit) IEEE-754 value
|===
If it is not one of the above, the type name must be the name of another struct.
Examples of valid standard declarations:
* `bool value` (boolean value, 1 byte)
* `double arr[4]` (array of 4 doubles, 32 bytes total)
* `enum {a=1, b=2} int8 val` (enumerated value, 1 byte)
[[enum]]
==== Enum Specification
Integer declarations may have an enum specification to provide meaning to specific values. Values that are not specified may be communicated, but have no specific defined meaning. The structure of an enum specification is:
* optional `enum`
* optional whitespace
* `{`
* zero or more enum values, consisting of:
* optional whitespace
* identifier
* optional whitespace
* `=`
* optional whitespace
* integer value
* optional whitespace
* comma (optional for last value)
* optional whitespace
* `}`
Examples of valid enum specifications:
* `enum{}`
* `enum { a = 1 }`
* `enum{a=1,b=2,}`
* `{a=1}`
Examples of invalid enum specifications:
* `enum` (no `{}`)
* `enum{=2}` (missing identifier)
* `enum{a=1,b,c}` (missing values)
[[]]
=== Bit-field Declaration
Bit-field declarations declare a member with an explicit width in bits. The structure of a bit-field declaration is:
* optional enum specification (integer data types only)
* optional whitespace
* type name; must be boolean or one of the integer data types
* whitespace
* identifier name
* optional whitespace
* colon (`:`)
* optional whitespace
* integer number of bits; minimum 1; maximum 1 for boolean types; for integer types, maximum is the width of the type (e.g. 32 for int32)
As with non-bit-field integer variable declarations, an enum can be specified for integer bit-fields (e.g. `enum {a=1, b=2} uint32 value : 2`).
It is not possible to have an array of bit-fields.
Examples of valid bit-field declarations:
* `bool value : 1`
* `enum{a=1,b=2}int8 value:2`
Examples of invalid bit-field declarations:
* `double val:2` (must be integer or boolean)
* `int32 val[2]:2` (cannot be array)
* `bool val:3` (bool must be 1 bit)
* `int16 val:17` (bit field larger than storage size)
[[layout]]
== Data Layout
Members are stored in the same order they appear in the schema. Individual members are stored in little-endian order. Members are not aligned to any particular boundary; no byte-level padding is present in the data.
[source]
----
bool b;
int16 i;
----
results in a 3-byte encoding:
`bbbbbbbb iiiiiiii iiiiiiii`
where the first `iiiiiiii` is the least significant byte of `i`.
[[layout-array]]
=== Array Data Layout
For array members, the individual items of the array are stored consecutively with no padding between each item.
[source]
----
int16 i[2];
----
results in a 4-byte encoding:
`i0i0i0i0 i0i0i0i0 i1i1i1i1 i1i1i1i1`
where `i0` is the first element of the array, `i1` is the second element.
[[layout-nested-structure]]
Nested structures also have no surrounding padding.
Given the Inner schema
[source]
----
int16 i;
int8 x;
----
and an outer schema of
[source]
----
char c;
Inner s;
bool b;
----
results in a 5-byte encoding:
`cccccccc iiiiiiii iiiiiiii xxxxxxxx bbbbbbbb`
[[layout-bit-field]]
=== Bit-Field Data Layout
Multiple adjacant bit-fields of the same integer type width are packed together to fit in the minimum number of multiples of that type. The bit-fields are packed, starting from the least significant bit, in the order they appear in the schema. Individual bit-fields must not span across multiple underlying types; if a bit-field is larger than the remaining space in the data type, a new element of that type is started and the bit-field starts from the least significant bit of the new element. Unused bits should be set to 0 during serialization and must be ignored during deserialization.
Boolean bit-fields are always a single bit wide. The underlying data type is by default uint8, but if a boolean bit-field immediately follows a bit-field of another integer type (and fits), it is packed into that type.
[source]
----
int8 a:4;
int16 b:4;
----
results in a 3-byte encoding:
`0000aaaa 0000bbbb 00000000`
as the integer type widths are different, even though the bits would fit.
[source]
----
int16 a:4;
uint16 b:5;
bool c:1;
int16 d:7;
----
results in a 4-byte encoding:
`bbbbaaaa 000000cb 0ddddddd 00000000`
As `c` is packed into the preceding int16, and `d` is too large to fit in the remaining bits of the first type.
[source]
----
uint8 a:4;
int8 b:2;
bool c:1;
int16 d:1;
----
results in a 3-byte encoding:
`0cbbaaaa 0000000d 00000000`
as `d` is int16, versus the `int8` of the previous values.
[source]
----
bool a:1;
bool b:1;
int8 c:2;
----
results in a 1-byte encoding:
`0000ccba`
as `c` is an int8.
[source]
----
bool a:1;
bool b:1;
int16 c:2;
----
results in a 3-byte encoding:
`000000ba 000000cc 00000000`
as `c` is an int16.
Bit-fields do not "look inside" of nested structures. Given Inner
[source]
----
int8 a:1;
----
and outer
[source]
----
int8 b:1;
Outer s;
int8 c:1;
----
the result is a 3-byte encoding:
`0000000b 0000000a 0000000c`
[[layout-character-arrays]]
=== Character Array (String) Data Layout
Character arrays, as with other arrays, must be fixed length. The text they contain should be UTF-8. If a string is shorter than the length of the character array, the string starts at the first byte of the array, and any unused bytes at the end of the array must be filled with 0.
[source]
----
char s[4];
----
with a string of "a" results in:
`01100001 00000000 00000000 00000000`
with a string of "abcd" results in:
`01100001 01100010 01100011 01100100`

View File

@@ -4,7 +4,14 @@
package edu.wpi.first.util.datalog;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.struct.Struct;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A data log. The log file is created immediately upon construction with a temporary filename. The
@@ -104,6 +111,130 @@ public final class DataLog implements AutoCloseable {
DataLogJNI.resume(m_impl);
}
/**
* Returns whether there is a data schema already registered with the given name.
*
* @param name Name (the string passed as the data type for records using this schema)
* @return True if schema already registered
*/
public boolean hasSchema(String name) {
return m_schemaSet.contains(name);
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In the data log, schemas
* are saved just like normal records, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for records using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
* @param timestamp Time stamp (may be 0 to indicate now)
*/
public void addSchema(String name, String type, byte[] schema, long timestamp) {
if (!m_schemaSet.add(name)) {
return;
}
DataLogJNI.addSchema(m_impl, name, type, schema, timestamp);
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In the data log, schemas
* are saved just like normal records, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for records using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
public void addSchema(String name, String type, byte[] schema) {
addSchema(name, type, schema, 0);
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In the data log, schemas
* are saved just like normal records, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for records using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
* @param timestamp Time stamp (may be 0 to indicate now)
*/
public void addSchema(String name, String type, String schema, long timestamp) {
if (!m_schemaSet.add(name)) {
return;
}
DataLogJNI.addSchemaString(m_impl, name, type, schema, timestamp);
}
/**
* Registers a data schema. Data schemas provide information for how a certain data type string
* can be decoded. The type string of a data schema indicates the type of the schema itself (e.g.
* "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In the data log, schemas
* are saved just like normal records, with the name being generated from the provided name:
* "/.schema/name". Duplicate calls to this function with the same name are silently ignored.
*
* @param name Name (the string passed as the data type for records using this schema)
* @param type Type of schema (e.g. "protobuf", "struct", etc)
* @param schema Schema data
*/
public void addSchema(String name, String type, String schema) {
addSchema(name, type, schema, 0);
}
/**
* Registers a protobuf schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param proto protobuf serialization object
* @param timestamp Time stamp (0 to indicate now)
*/
public void addSchema(Protobuf<?, ?> proto, long timestamp) {
final long actualTimestamp = timestamp == 0 ? WPIUtilJNI.now() : timestamp;
proto.forEachDescriptor(
this::hasSchema,
(typeString, schema) ->
addSchema(typeString, "proto:FileDescriptorProto", schema, actualTimestamp));
}
/**
* Registers a protobuf schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param proto protobuf serialization object
*/
public void addSchema(Protobuf<?, ?> proto) {
addSchema(proto, 0);
}
/**
* Registers a struct schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param struct struct serialization object
* @param timestamp Time stamp (0 to indicate now)
*/
public void addSchema(Struct<?> struct, long timestamp) {
addSchemaImpl(struct, timestamp == 0 ? WPIUtilJNI.now() : timestamp, new HashSet<>());
}
/**
* Registers a struct schema. Duplicate calls to this function with the same name are silently
* ignored.
*
* @param struct struct serialization object
*/
public void addSchema(Struct<?> struct) {
addSchema(struct, 0);
}
/**
* Start an entry. Duplicate names are allowed (with the same type), and result in the same index
* being returned (start/finish are reference counted). A duplicate name with a different type
@@ -358,5 +489,22 @@ public final class DataLog implements AutoCloseable {
return m_impl;
}
private void addSchemaImpl(Struct<?> struct, long timestamp, Set<String> seen) {
String typeString = struct.getTypeString();
if (hasSchema(typeString)) {
return;
}
if (!seen.add(typeString)) {
throw new UnsupportedOperationException(typeString + ": circular reference with " + seen);
}
addSchema(typeString, "structschema", struct.getSchema(), timestamp);
for (Struct<?> inner : struct.getNested()) {
addSchemaImpl(inner, timestamp, seen);
}
seen.remove(typeString);
}
private long m_impl;
private final ConcurrentMap<String, Integer> m_schemaMap = new ConcurrentHashMap<>();
private final Set<String> m_schemaSet = m_schemaMap.keySet();
}

View File

@@ -18,6 +18,11 @@ public class DataLogJNI extends WPIUtilJNI {
static native void resume(long impl);
static native void addSchema(long impl, String name, String type, byte[] schema, long timestamp);
static native void addSchemaString(
long impl, String name, String type, String schema, long timestamp);
static native int start(long impl, String name, String type, String metadata, long timestamp);
static native void finish(long impl, int entry, long timestamp);

View File

@@ -0,0 +1,117 @@
// 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.
package edu.wpi.first.util.datalog;
import edu.wpi.first.util.protobuf.Protobuf;
import edu.wpi.first.util.protobuf.ProtobufBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import us.hebi.quickbuf.ProtoMessage;
/**
* Log protobuf-encoded values.
*
* @param <T> value class
*/
public final class ProtobufLogEntry<T> extends DataLogEntry {
private ProtobufLogEntry(
DataLog log, String name, Protobuf<T, ?> proto, String metadata, long timestamp) {
super(log, name, proto.getTypeString(), metadata, timestamp);
m_buf = ProtobufBuffer.create(proto);
log.addSchema(proto, timestamp);
}
/**
* Creates a protobuf-encoded log entry.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param log datalog
* @param name name of the entry
* @param proto protobuf serialization implementation
* @param metadata metadata
* @param timestamp entry creation timestamp (0=now)
* @return ProtobufLogEntry
*/
public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
DataLog log, String name, Protobuf<T, MessageType> proto, String metadata, long timestamp) {
return new ProtobufLogEntry<T>(log, name, proto, metadata, timestamp);
}
/**
* Creates a protobuf-encoded log entry.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param log datalog
* @param name name of the entry
* @param proto protobuf serialization implementation
* @param metadata metadata
* @return ProtobufLogEntry
*/
public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
DataLog log, String name, Protobuf<T, MessageType> proto, String metadata) {
return create(log, name, proto, metadata, 0);
}
/**
* Creates a protobuf-encoded log entry.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param log datalog
* @param name name of the entry
* @param proto protobuf serialization implementation
* @param timestamp entry creation timestamp (0=now)
* @return ProtobufLogEntry
*/
public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
DataLog log, String name, Protobuf<T, MessageType> proto, long timestamp) {
return create(log, name, proto, "", timestamp);
}
/**
* Creates a protobuf-encoded log entry.
*
* @param <T> value class (inferred from proto)
* @param <MessageType> protobuf message type (inferred from proto)
* @param log datalog
* @param name name of the entry
* @param proto protobuf serialization implementation
* @return ProtobufLogEntry
*/
public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
DataLog log, String name, Protobuf<T, MessageType> proto) {
return create(log, name, proto, 0);
}
/**
* Appends a record to the log.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(T value, long timestamp) {
try {
synchronized (m_buf) {
ByteBuffer bb = m_buf.write(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
}
} catch (IOException e) {
// ignore
}
}
/**
* Appends a record to the log.
*
* @param value Value to record
*/
public void append(T value) {
append(value, 0);
}
private final ProtobufBuffer<T, ?> m_buf;
}

View File

@@ -0,0 +1,140 @@
// 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.
package edu.wpi.first.util.datalog;
import edu.wpi.first.util.struct.Struct;
import edu.wpi.first.util.struct.StructBuffer;
import java.nio.ByteBuffer;
import java.util.Collection;
/**
* Log struct-encoded array values.
*
* @param <T> value class
*/
public final class StructArrayLogEntry<T> extends DataLogEntry {
private StructArrayLogEntry(
DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
super(log, name, struct.getTypeString() + "[]", metadata, timestamp);
m_buf = StructBuffer.create(struct);
log.addSchema(struct, timestamp);
}
/**
* Creates a struct-encoded array log entry.
*
* @param <T> value class (inferred from struct)
* @param log datalog
* @param name name of the entry
* @param struct struct serialization implementation
* @param metadata metadata
* @param timestamp entry creation timestamp (0=now)
* @return StructArrayLogEntry
*/
public static <T> StructArrayLogEntry<T> create(
DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
return new StructArrayLogEntry<T>(log, name, struct, metadata, timestamp);
}
/**
* Creates a struct-encoded array log entry.
*
* @param <T> value class (inferred from struct)
* @param log datalog
* @param name name of the entry
* @param struct struct serialization implementation
* @param metadata metadata
* @return StructArrayLogEntry
*/
public static <T> StructArrayLogEntry<T> create(
DataLog log, String name, Struct<T> struct, String metadata) {
return create(log, name, struct, metadata, 0);
}
/**
* Creates a struct-encoded array log entry.
*
* @param <T> value class (inferred from struct)
* @param log datalog
* @param name name of the entry
* @param struct struct serialization implementation
* @param timestamp entry creation timestamp (0=now)
* @return StructArrayLogEntry
*/
public static <T> StructArrayLogEntry<T> create(
DataLog log, String name, Struct<T> struct, long timestamp) {
return create(log, name, struct, "", timestamp);
}
/**
* Creates a struct-encoded array log entry.
*
* @param <T> value class (inferred from struct)
* @param log datalog
* @param name name of the entry
* @param struct struct serialization implementation
* @return StructArrayLogEntry
*/
public static <T> StructArrayLogEntry<T> create(DataLog log, String name, Struct<T> struct) {
return create(log, name, struct, 0);
}
/**
* Ensures sufficient buffer space is available for the given number of elements.
*
* @param nelem number of elements
*/
public void reserve(int nelem) {
synchronized (m_buf) {
m_buf.reserve(nelem);
}
}
/**
* Appends a record to the log.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(T[] value, long timestamp) {
synchronized (this) {
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
}
}
/**
* Appends a record to the log.
*
* @param value Value to record
*/
public void append(T[] value) {
append(value, 0);
}
/**
* Appends a record to the log.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(Collection<T> value, long timestamp) {
synchronized (m_buf) {
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
}
}
/**
* Appends a record to the log.
*
* @param value Value to record
*/
public void append(Collection<T> value) {
append(value, 0);
}
private final StructBuffer<T> m_buf;
}

Some files were not shown because too many files have changed in this diff Show More