mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
Compare commits
29 Commits
v2024.1.1-
...
v2024.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abb2857e03 | ||
|
|
b14a61e1c0 | ||
|
|
cf54d9ccb7 | ||
|
|
ecb7cfa9ef | ||
|
|
7c6fe56cf2 | ||
|
|
85147bf69e | ||
|
|
244163acad | ||
|
|
820728503d | ||
|
|
45f307d87e | ||
|
|
4ce4d63efc | ||
|
|
579007ceb3 | ||
|
|
3f3a169149 | ||
|
|
7501e4ac88 | ||
|
|
99630d2e78 | ||
|
|
02cbbc997d | ||
|
|
ed93889e17 | ||
|
|
da70e4c262 | ||
|
|
e814595ea7 | ||
|
|
f98c943445 | ||
|
|
b3eb64b0f7 | ||
|
|
7d9ba256c2 | ||
|
|
1f6492e3d8 | ||
|
|
638f04f626 | ||
|
|
210255bfff | ||
|
|
896772c750 | ||
|
|
fd427f6c82 | ||
|
|
c0b4c6cce6 | ||
|
|
9a0aafd8ab | ||
|
|
1c724884ca |
11
.github/workflows/cmake.yml
vendored
11
.github/workflows/cmake.yml
vendored
@@ -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)
|
||||
|
||||
8
.github/workflows/sanitizers.yml
vendored
8
.github/workflows/sanitizers.yml
vendored
@@ -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"
|
||||
|
||||
|
||||
8
.github/workflows/upstream-utils.yml
vendored
8
.github/workflows/upstream-utils.yml
vendored
@@ -41,6 +41,10 @@ jobs:
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_gcem.py
|
||||
- name: Run update_json.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_json.py
|
||||
- name: Run update_libuv.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
@@ -61,6 +65,10 @@ jobs:
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_memory.py
|
||||
- name: Run update_protobuf.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./update_protobuf.py
|
||||
- name: Add untracked files to index so they count as changes
|
||||
run: git add -A
|
||||
- name: Check output
|
||||
|
||||
@@ -31,6 +31,7 @@ includeOtherLibs {
|
||||
^cscore
|
||||
^fmt/
|
||||
^gtest/
|
||||
^google/
|
||||
^hal/
|
||||
^imgui
|
||||
^implot
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -18,9 +18,13 @@ By default, all libraries except for the HAL and WPILib get built with a default
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The most common prerequisite is going to be OpenCV. 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.
|
||||
The jinja2 pip package is needed to generate classes for NT4's pubsub.
|
||||
|
||||
In addition, 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.
|
||||
The protobuf library and compiler are needed for protobuf generation. The QuickBuffers protoc-gen package is also required when Java is being built; this can be obtained from https://github.com/HebiRobotics/QuickBuffers/releases/.
|
||||
|
||||
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.
|
||||
|
||||
If you are building with unit tests or simulation modules, you will also need an Internet connection for the initial setup process, as CMake will clone google-test and imgui from GitHub.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ AprilTagFieldLayout::AprilTagFieldLayout(std::string_view path) {
|
||||
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
|
||||
}
|
||||
|
||||
wpi::json json = wpi::json::parse({fileBuffer->begin(), fileBuffer->end()});
|
||||
wpi::json json = wpi::json::parse(fileBuffer->begin(), fileBuffer->end());
|
||||
|
||||
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
|
||||
m_apriltags[tag.ID] = tag;
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -105,7 +105,7 @@ bool ReadConfig() {
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse({fileBuffer->begin(), fileBuffer->end()});
|
||||
j = wpi::json::parse(fileBuffer->begin(), fileBuffer->end());
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
fmt::print(stderr, "config error in '{}': byte {}: {}\n", configFile,
|
||||
e.byte, e.what());
|
||||
|
||||
@@ -121,7 +121,13 @@ doxygen {
|
||||
exclude 'wpinet/uv/**'
|
||||
|
||||
// json
|
||||
exclude 'wpi/adl_serializer.h'
|
||||
exclude 'wpi/byte_container_with_subtype.h'
|
||||
exclude 'wpi/detail/**'
|
||||
exclude 'wpi/json.h'
|
||||
exclude 'wpi/json_fwd.h'
|
||||
exclude 'wpi/ordered_map.h'
|
||||
exclude 'wpi/thirdparty/**'
|
||||
|
||||
// memory
|
||||
exclude 'wpi/memory/**'
|
||||
@@ -136,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>^^',
|
||||
|
||||
@@ -24,6 +24,7 @@ includeOtherLibs {
|
||||
^fmt/
|
||||
^fields/
|
||||
^frc/
|
||||
^google/
|
||||
^imgui
|
||||
^networktables/
|
||||
^ntcore
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpigui.h>
|
||||
@@ -139,7 +138,7 @@ static bool LoadWindowStorageImpl(const std::string& filename) {
|
||||
} else {
|
||||
try {
|
||||
return JsonToWindow(
|
||||
wpi::json::parse({fileBuffer->begin(), fileBuffer->end()}),
|
||||
wpi::json::parse(fileBuffer->begin(), fileBuffer->end()),
|
||||
filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
@@ -166,7 +165,7 @@ static bool LoadStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
}
|
||||
try {
|
||||
storage->FromJson(
|
||||
wpi::json::parse({fileBuffer->begin(), fileBuffer->end()}),
|
||||
wpi::json::parse(fileBuffer->begin(), fileBuffer->end()),
|
||||
filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
|
||||
@@ -448,7 +448,7 @@ bool FieldInfo::LoadJson(std::span<const char> is, std::string_view filename) {
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse({is.data(), is.size()});
|
||||
j = wpi::json::parse(is);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
fmt::print(stderr, "GUI: JSON: could not parse: {}\n", e.what());
|
||||
return false;
|
||||
|
||||
@@ -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", "");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -29,7 +29,7 @@ DEFINE_CAPI(int32_t, UserFaults5V, 0)
|
||||
DEFINE_CAPI(int32_t, UserFaults3V3, 0)
|
||||
DEFINE_CAPI(double, BrownoutVoltage, 6.75)
|
||||
DEFINE_CAPI(double, CPUTemp, 45.0)
|
||||
DEFINE_CAPI(int32_t, TeamNumber, 0);
|
||||
DEFINE_CAPI(int32_t, TeamNumber, 0)
|
||||
|
||||
int32_t HALSIM_RegisterRoboRioSerialNumberCallback(
|
||||
HAL_RoboRioStringCallback callback, void* param, HAL_Bool initialNotify) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "ConnectionList.h"
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "IListenerStorage.h"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -242,7 +242,11 @@ void NetworkClient3::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.error.connect([this, &tcp](uv::Error err) {
|
||||
DEBUG3("NT3 TCP error {}", err.str());
|
||||
if (!tcp.IsLoopClosing()) {
|
||||
DoDisconnect(err.str());
|
||||
// we could be in the middle of sending data, so defer disconnect
|
||||
uv::Timer::SingleShot(m_loop, uv::Timer::Time{0},
|
||||
[this, reason = std::string{err.str()}] {
|
||||
DoDisconnect(reason);
|
||||
});
|
||||
}
|
||||
});
|
||||
tcp.end.connect([this, &tcp] {
|
||||
@@ -412,7 +416,10 @@ void NetworkClient::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp,
|
||||
m_clientImpl->SendInitial();
|
||||
ws.closed.connect([this, &ws](uint16_t, std::string_view reason) {
|
||||
if (!ws.GetStream().IsLoopClosing()) {
|
||||
DoDisconnect(reason);
|
||||
// we could be in the middle of sending data, so defer disconnect
|
||||
uv::Timer::SingleShot(
|
||||
m_loop, uv::Timer::Time{0},
|
||||
[this, reason = std::string{reason}] { DoDisconnect(reason); });
|
||||
}
|
||||
});
|
||||
ws.text.connect([this](std::string_view data, bool) {
|
||||
|
||||
@@ -719,7 +719,7 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setTopicProperty
|
||||
{
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(JStringRef{env, value});
|
||||
j = wpi::json::parse(std::string_view{JStringRef{env, value}});
|
||||
} catch (wpi::json::parse_error& err) {
|
||||
illegalArgEx.Throw(
|
||||
env, fmt::format("could not parse value JSON: {}", err.what()));
|
||||
@@ -763,7 +763,7 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setTopicProperties
|
||||
{
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(JStringRef{env, properties});
|
||||
j = wpi::json::parse(std::string_view{JStringRef{env, properties}});
|
||||
} catch (wpi::json::parse_error& err) {
|
||||
illegalArgEx.Throw(
|
||||
env, fmt::format("could not parse properties JSON: {}", err.what()));
|
||||
@@ -828,7 +828,7 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_publishEx
|
||||
{
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(JStringRef{env, properties});
|
||||
j = wpi::json::parse(std::string_view{JStringRef{env, properties}});
|
||||
} catch (wpi::json::parse_error& err) {
|
||||
illegalArgEx.Throw(
|
||||
env, fmt::format("could not parse properties JSON: {}", err.what()));
|
||||
|
||||
@@ -280,9 +280,15 @@ void NetworkOutgoingQueue<MessageType>::SendOutgoing(uint64_t curTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (unsent < 0) {
|
||||
return; // error
|
||||
}
|
||||
if (unsent == 0) {
|
||||
// finish writing any partial buffers
|
||||
unsent = m_wire.Flush();
|
||||
if (unsent < 0) {
|
||||
return; // error
|
||||
}
|
||||
}
|
||||
int delta = it - msgs.begin() - unsent;
|
||||
for (auto&& msg : std::span{msgs}.subspan(0, delta)) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <wpi/MessagePack.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
@@ -524,6 +524,9 @@ void ServerImpl::ClientData4::SendAnnounce(TopicData* topic,
|
||||
WireEncodeAnnounce(os, topic->name, topic->id, topic->typeStr,
|
||||
topic->properties, pubuid);
|
||||
});
|
||||
if (unsent < 0) {
|
||||
return; // error
|
||||
}
|
||||
if (unsent == 0 && m_wire.Flush() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -544,6 +547,9 @@ void ServerImpl::ClientData4::SendUnannounce(TopicData* topic) {
|
||||
if (m_local) {
|
||||
int unsent = m_wire.WriteText(
|
||||
[&](auto& os) { WireEncodeUnannounce(os, topic->name, topic->id); });
|
||||
if (unsent < 0) {
|
||||
return; // error
|
||||
}
|
||||
if (unsent == 0 && m_wire.Flush() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -565,6 +571,9 @@ void ServerImpl::ClientData4::SendPropertiesUpdate(TopicData* topic,
|
||||
int unsent = m_wire.WriteText([&](auto& os) {
|
||||
WireEncodePropertiesUpdate(os, topic->name, update, ack);
|
||||
});
|
||||
if (unsent < 0) {
|
||||
return; // error
|
||||
}
|
||||
if (unsent == 0 && m_wire.Flush() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ int WebSocketConnection::Flush() {
|
||||
m_ws_frames.reserve(m_frames.size());
|
||||
for (auto&& frame : m_frames) {
|
||||
m_ws_frames.emplace_back(
|
||||
frame.opcode, std::span{&m_bufs[frame.start], &m_bufs[frame.end]});
|
||||
frame.opcode,
|
||||
std::span{m_bufs}.subspan(frame.start, frame.end - frame.start));
|
||||
}
|
||||
|
||||
auto unsentFrames = m_ws.TrySendFrames(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/mpack.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
474
ntcore/src/main/native/include/networktables/ProtobufTopic.h
Normal file
474
ntcore/src/main/native/include/networktables/ProtobufTopic.h
Normal 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
|
||||
593
ntcore/src/main/native/include/networktables/StructArrayTopic.h
Normal file
593
ntcore/src/main/native/include/networktables/StructArrayTopic.h
Normal 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
|
||||
438
ntcore/src/main/native/include/networktables/StructTopic.h
Normal file
438
ntcore/src/main/native/include/networktables/StructTopic.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* @{
|
||||
|
||||
@@ -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()});
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ TEST_F(WireDecodeTextClientTest, ErrorEmpty) {
|
||||
logger,
|
||||
Call(_, _, _,
|
||||
"could not decode JSON message: [json.exception.parse_error.101] "
|
||||
"parse error at 1: syntax error - "
|
||||
"unexpected end of input; expected '[', '{', or a literal"sv));
|
||||
"parse error at line 1, column 1: syntax error while parsing value "
|
||||
"- unexpected end of input; expected '[', '{', or a literal"sv));
|
||||
net::WireDecodeText("", handler, logger);
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ TEST_F(WireDecodeTextClientTest, ErrorBadJson1) {
|
||||
logger,
|
||||
Call(_, _, _,
|
||||
"could not decode JSON message: [json.exception.parse_error.101] "
|
||||
"parse error at 2: syntax error - "
|
||||
"unexpected end of input; expected '[', '{', or a literal"sv));
|
||||
"parse error at line 1, column 2: syntax error while parsing value "
|
||||
"- unexpected end of input; expected '[', '{', or a literal"sv));
|
||||
net::WireDecodeText("[", handler, logger);
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ TEST_F(WireDecodeTextClientTest, ErrorBadJson2) {
|
||||
logger,
|
||||
Call(_, _, _,
|
||||
"could not decode JSON message: [json.exception.parse_error.101] "
|
||||
"parse error at 3: syntax error - "
|
||||
"unexpected end of input; expected string literal"sv));
|
||||
"parse error at line 1, column 3: syntax error while parsing object "
|
||||
"key - unexpected end of input; expected string literal"sv));
|
||||
net::WireDecodeText("[{", handler, logger);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ def tagList = [
|
||||
"SmartDashboard", "Shuffleboard", "Sendable", "DataLog",
|
||||
|
||||
/* --- Controls --- */
|
||||
"PID", "State-Space", "Ramsete", "Path Following", "Trajectory", "SysId",
|
||||
"Simulation", "Trapezoid Profile", "Profiled PID", "Odometry", "LQR",
|
||||
"Exponential Profile", "PID", "State-Space", "Ramsete", "Path Following", "Trajectory",
|
||||
"SysId", "Simulation", "Trapezoid Profile", "Profiled PID", "Odometry", "LQR",
|
||||
"Pose Estimator",
|
||||
|
||||
/* --- Hardware --- */
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
#include <fmt/format.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/math.h>
|
||||
#include <wpi/MemoryBuffer.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
|
||||
#include "sysid/Util.h"
|
||||
#include "sysid/analysis/FilteringUtils.h"
|
||||
@@ -451,13 +451,13 @@ AnalysisManager::AnalysisManager(std::string_view path, Settings& settings,
|
||||
{
|
||||
// Read JSON from the specified path
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{path, ec};
|
||||
|
||||
if (ec) {
|
||||
std::unique_ptr<wpi::MemoryBuffer> fileBuffer =
|
||||
wpi::MemoryBuffer::GetFile(path, ec);
|
||||
if (fileBuffer == nullptr || ec) {
|
||||
throw FileReadingError(path);
|
||||
}
|
||||
|
||||
is >> m_json;
|
||||
m_json = wpi::json::parse(fileBuffer->begin(), fileBuffer->end());
|
||||
|
||||
WPI_INFO(m_logger, "Read {}", path);
|
||||
}
|
||||
@@ -469,13 +469,13 @@ AnalysisManager::AnalysisManager(std::string_view path, Settings& settings,
|
||||
|
||||
// Read JSON from the specified path
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{newPath, ec};
|
||||
|
||||
if (ec) {
|
||||
std::unique_ptr<wpi::MemoryBuffer> fileBuffer =
|
||||
wpi::MemoryBuffer::GetFile(path, ec);
|
||||
if (fileBuffer == nullptr || ec) {
|
||||
throw FileReadingError(newPath);
|
||||
}
|
||||
|
||||
is >> m_json;
|
||||
m_json = wpi::json::parse(fileBuffer->begin(), fileBuffer->end());
|
||||
|
||||
WPI_INFO(m_logger, "Read {}", newPath);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/MemoryBuffer.h>
|
||||
#include <wpi/fmt/raw_ostream.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "sysid/Util.h"
|
||||
@@ -34,14 +34,13 @@ static constexpr size_t kRVelCol = 8;
|
||||
|
||||
static wpi::json GetJSON(std::string_view path, wpi::Logger& logger) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream input{path, ec};
|
||||
|
||||
if (ec) {
|
||||
std::unique_ptr<wpi::MemoryBuffer> fileBuffer =
|
||||
wpi::MemoryBuffer::GetFile(path, ec);
|
||||
if (fileBuffer == nullptr || ec) {
|
||||
throw std::runtime_error(fmt::format("Unable to read: {}", path));
|
||||
}
|
||||
|
||||
wpi::json json;
|
||||
input >> json;
|
||||
wpi::json json = wpi::json::parse(fileBuffer->begin(), fileBuffer->end());
|
||||
WPI_INFO(logger, "Read frc-characterization JSON from {}", path);
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <frc/filter/LinearFilter.h>
|
||||
#include <units/time.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/array.h>
|
||||
|
||||
#include "sysid/analysis/AnalysisManager.h"
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tyler Veness <calcmogul@gmail.com>
|
||||
Date: Fri, 8 Sep 2023 19:21:41 -0700
|
||||
Subject: [PATCH 1/4] Remove version from namespace
|
||||
|
||||
---
|
||||
include/nlohmann/detail/abi_macros.hpp | 45 ++------------------------
|
||||
1 file changed, 3 insertions(+), 42 deletions(-)
|
||||
|
||||
diff --git a/include/nlohmann/detail/abi_macros.hpp b/include/nlohmann/detail/abi_macros.hpp
|
||||
index 0d3108d166602886d41b5f0fec1e56dd3dbe7e3c..ce9291306cdd9a9baeb8fbb77ca1dc33959e0d36 100644
|
||||
--- a/include/nlohmann/detail/abi_macros.hpp
|
||||
+++ b/include/nlohmann/detail/abi_macros.hpp
|
||||
@@ -42,40 +42,6 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
-#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
- #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
-#endif
|
||||
-
|
||||
-// Construct the namespace ABI tags component
|
||||
-#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
|
||||
-#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
|
||||
- NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
|
||||
-
|
||||
-#define NLOHMANN_JSON_ABI_TAGS \
|
||||
- NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
- NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
- NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
|
||||
-
|
||||
-// Construct the namespace version component
|
||||
-#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
- _v ## major ## _ ## minor ## _ ## patch
|
||||
-#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
||||
- NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
||||
-
|
||||
-#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
-#define NLOHMANN_JSON_NAMESPACE_VERSION
|
||||
-#else
|
||||
-#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
||||
- NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
||||
- NLOHMANN_JSON_VERSION_MINOR, \
|
||||
- NLOHMANN_JSON_VERSION_PATCH)
|
||||
-#endif
|
||||
-
|
||||
-// Combine namespace components
|
||||
-#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
||||
-#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
||||
- NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
||||
-
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE
|
||||
#define NLOHMANN_JSON_NAMESPACE \
|
||||
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||
@@ -84,17 +50,12 @@
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
-#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||
- namespace nlohmann \
|
||||
- { \
|
||||
- inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||
- NLOHMANN_JSON_ABI_TAGS, \
|
||||
- NLOHMANN_JSON_NAMESPACE_VERSION) \
|
||||
+#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||
+ namespace nlohmann \
|
||||
{
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
||||
-#define NLOHMANN_JSON_NAMESPACE_END \
|
||||
- } /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
||||
+#define NLOHMANN_JSON_NAMESPACE_END \
|
||||
} // namespace nlohmann
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tyler Veness <calcmogul@gmail.com>
|
||||
Date: Thu, 7 Sep 2023 22:02:27 -0700
|
||||
Subject: [PATCH 2/4] Make serializer public
|
||||
|
||||
---
|
||||
include/nlohmann/detail/output/serializer.hpp | 4 +++-
|
||||
include/nlohmann/json.hpp | 3 +--
|
||||
2 files changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp
|
||||
index 500fc55ec5e5895ead2b372a6fe79ae941d88d83..7674d134a4d9f230aa4e432294c19dac8dd366b1 100644
|
||||
--- a/include/nlohmann/detail/output/serializer.hpp
|
||||
+++ b/include/nlohmann/detail/output/serializer.hpp
|
||||
@@ -373,7 +373,7 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
- JSON_PRIVATE_UNLESS_TESTED:
|
||||
+ public:
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
|
||||
@@ -696,6 +696,7 @@ class serializer
|
||||
return false;
|
||||
}
|
||||
|
||||
+ public:
|
||||
/*!
|
||||
@brief dump an integer
|
||||
|
||||
@@ -876,6 +877,7 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
+ private:
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
|
||||
index 18a7c875774527a2e08c5ab72e5564aa50381608..c462cade8a7167a00697f6f940be35c5609a283c 100644
|
||||
--- a/include/nlohmann/json.hpp
|
||||
+++ b/include/nlohmann/json.hpp
|
||||
@@ -153,10 +153,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>;
|
||||
template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;
|
||||
|
||||
- JSON_PRIVATE_UNLESS_TESTED:
|
||||
+ public:
|
||||
using serializer = ::nlohmann::detail::serializer<basic_json>;
|
||||
|
||||
- public:
|
||||
using value_t = detail::value_t;
|
||||
/// JSON Pointer, see @ref nlohmann::json_pointer
|
||||
using json_pointer = ::nlohmann::json_pointer<StringType>;
|
||||
@@ -0,0 +1,22 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tyler Veness <calcmogul@gmail.com>
|
||||
Date: Fri, 8 Sep 2023 21:42:01 -0700
|
||||
Subject: [PATCH 3/4] Make dump_escaped() take std::string_view
|
||||
|
||||
---
|
||||
include/nlohmann/detail/output/serializer.hpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp
|
||||
index 7674d134a4d9f230aa4e432294c19dac8dd366b1..ecc4f7d500b9e0bc15917503061a4db100391366 100644
|
||||
--- a/include/nlohmann/detail/output/serializer.hpp
|
||||
+++ b/include/nlohmann/detail/output/serializer.hpp
|
||||
@@ -388,7 +388,7 @@ class serializer
|
||||
|
||||
@complexity Linear in the length of string @a s.
|
||||
*/
|
||||
- void dump_escaped(const string_t& s, const bool ensure_ascii)
|
||||
+ void dump_escaped(std::string_view s, const bool ensure_ascii)
|
||||
{
|
||||
std::uint32_t codepoint{};
|
||||
std::uint8_t state = UTF8_ACCEPT;
|
||||
133
upstream_utils/json_patches/0004-Add-llvm-stream-support.patch
Normal file
133
upstream_utils/json_patches/0004-Add-llvm-stream-support.patch
Normal file
@@ -0,0 +1,133 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: PJ Reiniger <pj.reiniger@gmail.com>
|
||||
Date: Wed, 20 Sep 2023 02:23:10 -0400
|
||||
Subject: [PATCH 4/4] Add llvm stream support
|
||||
|
||||
---
|
||||
.../detail/output/output_adapters.hpp | 26 +++++++++++++++++++
|
||||
include/nlohmann/detail/output/serializer.hpp | 11 ++++++--
|
||||
include/nlohmann/json.hpp | 24 +++++++++++++++++
|
||||
3 files changed, 59 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/include/nlohmann/detail/output/output_adapters.hpp b/include/nlohmann/detail/output/output_adapters.hpp
|
||||
index 630bd8f73f38b7bf18be571217873f6215e6e31a..78addc557eec3b2a31cde78fb4c6f7f6efc7e777 100644
|
||||
--- a/include/nlohmann/detail/output/output_adapters.hpp
|
||||
+++ b/include/nlohmann/detail/output/output_adapters.hpp
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
+#include <wpi/raw_ostream.h>
|
||||
+
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -118,6 +120,27 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
StringType& str;
|
||||
};
|
||||
|
||||
+template<typename CharType>
|
||||
+class raw_ostream_adapter : public output_adapter_protocol<CharType>
|
||||
+{
|
||||
+ public:
|
||||
+ explicit raw_ostream_adapter(raw_ostream& s) noexcept
|
||||
+ : os(s) {}
|
||||
+
|
||||
+
|
||||
+ void write_character(CharType c) override {
|
||||
+ os << c;
|
||||
+ }
|
||||
+
|
||||
+ JSON_HEDLEY_NON_NULL(2)
|
||||
+ void write_characters(const CharType* s, std::size_t length) override {
|
||||
+ os.write(s, length);
|
||||
+ }
|
||||
+
|
||||
+ private:
|
||||
+ raw_ostream& os;
|
||||
+};
|
||||
+
|
||||
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
||||
class output_adapter
|
||||
{
|
||||
@@ -134,6 +157,9 @@ class output_adapter
|
||||
output_adapter(StringType& s)
|
||||
: oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
|
||||
|
||||
+ output_adapter(raw_ostream& os)
|
||||
+ : oa(std::make_shared<raw_ostream_adapter<CharType>>(os)) {}
|
||||
+
|
||||
operator output_adapter_t<CharType>()
|
||||
{
|
||||
return oa;
|
||||
diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp
|
||||
index ecc4f7d500b9e0bc15917503061a4db100391366..bb392a985b57b79020c949593c155052a4271d6b 100644
|
||||
--- a/include/nlohmann/detail/output/serializer.hpp
|
||||
+++ b/include/nlohmann/detail/output/serializer.hpp
|
||||
@@ -65,15 +65,22 @@ class serializer
|
||||
@param[in] error_handler_ how to react on decoding errors
|
||||
*/
|
||||
serializer(output_adapter_t<char> s, const char ichar,
|
||||
- error_handler_t error_handler_ = error_handler_t::strict)
|
||||
+ error_handler_t error_handler_ = error_handler_t::strict,
|
||||
+ size_t indent_init_len = 512)
|
||||
: o(std::move(s))
|
||||
, loc(std::localeconv())
|
||||
, thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||
, indent_char(ichar)
|
||||
- , indent_string(512, indent_char)
|
||||
+ , indent_string(indent_init_len, indent_char)
|
||||
, error_handler(error_handler_)
|
||||
{}
|
||||
+
|
||||
+ serializer(raw_ostream& os, const char ichar,
|
||||
+ size_t indent_init_len = 512,
|
||||
+ error_handler_t error_handler_ = error_handler_t::strict)
|
||||
+ : serializer(output_adapter<char>(os), ichar, error_handler_, indent_init_len)
|
||||
+ {}
|
||||
|
||||
// delete because of pointer members
|
||||
serializer(const serializer&) = delete;
|
||||
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
|
||||
index c462cade8a7167a00697f6f940be35c5609a283c..ad98956ba880f844ed1a17765266880f6ea08b2f 100644
|
||||
--- a/include/nlohmann/json.hpp
|
||||
+++ b/include/nlohmann/json.hpp
|
||||
@@ -1275,6 +1275,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return result;
|
||||
}
|
||||
|
||||
+ void dump(raw_ostream& os, const int indent = -1,
|
||||
+ const char indent_char = ' ',
|
||||
+ const bool ensure_ascii = false,
|
||||
+ const error_handler_t error_handler = error_handler_t::strict) const {
|
||||
+ serializer s(os, indent_char);
|
||||
+
|
||||
+ if (indent >= 0)
|
||||
+ {
|
||||
+ s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ s.dump(*this, false, ensure_ascii, 0);
|
||||
+ }
|
||||
+
|
||||
+ os.flush();
|
||||
+ }
|
||||
+
|
||||
/// @brief return the type of the JSON value (explicit)
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/type/
|
||||
constexpr value_t type() const noexcept
|
||||
@@ -3990,6 +4008,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return o << j;
|
||||
}
|
||||
#endif // JSON_NO_IO
|
||||
+
|
||||
+ friend raw_ostream& operator<<(raw_ostream& o, const basic_json& j)
|
||||
+ {
|
||||
+ j.dump(o, 0);
|
||||
+ return o;
|
||||
+ }
|
||||
/// @}
|
||||
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 14:13:07 -0700
|
||||
Subject: [PATCH 01/11] Fix sign-compare warnings
|
||||
|
||||
---
|
||||
src/google/protobuf/compiler/importer.cc | 2 +-
|
||||
src/google/protobuf/compiler/parser.cc | 4 ++--
|
||||
src/google/protobuf/io/io_win32.cc | 2 +-
|
||||
src/google/protobuf/stubs/stringprintf.cc | 4 ++--
|
||||
src/google/protobuf/stubs/strutil.cc | 2 +-
|
||||
src/google/protobuf/stubs/substitute.cc | 2 +-
|
||||
src/google/protobuf/util/field_mask_util.cc | 2 +-
|
||||
src/google/protobuf/util/internal/datapiece.cc | 7 +++++++
|
||||
.../protobuf/util/internal/default_value_objectwriter.cc | 4 ++--
|
||||
.../protobuf/util/internal/default_value_objectwriter.h | 2 +-
|
||||
src/google/protobuf/util/internal/json_escaping.cc | 6 +++---
|
||||
src/google/protobuf/util/internal/json_objectwriter.h | 2 +-
|
||||
src/google/protobuf/util/internal/json_stream_parser.cc | 8 ++++----
|
||||
src/google/protobuf/util/internal/proto_writer.cc | 2 +-
|
||||
.../protobuf/util/internal/protostream_objectwriter.cc | 2 +-
|
||||
src/google/protobuf/util/internal/utility.cc | 2 +-
|
||||
src/google/protobuf/util/json_util.cc | 2 +-
|
||||
17 files changed, 31 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/src/google/protobuf/compiler/importer.cc b/src/google/protobuf/compiler/importer.cc
|
||||
index f1e26f8bdd1d3619acd8827f9a2a0e6b2acdd124..678e87eb03cc3959a1890327cd1e918cb1896fa3 100644
|
||||
--- a/src/google/protobuf/compiler/importer.cc
|
||||
+++ b/src/google/protobuf/compiler/importer.cc
|
||||
@@ -398,7 +398,7 @@ DiskSourceTree::DiskFileToVirtualFile(const std::string& disk_file,
|
||||
int mapping_index = -1;
|
||||
std::string canonical_disk_file = CanonicalizePath(disk_file);
|
||||
|
||||
- for (int i = 0; i < mappings_.size(); i++) {
|
||||
+ for (size_t i = 0; i < mappings_.size(); i++) {
|
||||
// Apply the mapping in reverse.
|
||||
if (ApplyMapping(canonical_disk_file, mappings_[i].disk_path,
|
||||
mappings_[i].virtual_path, virtual_file)) {
|
||||
diff --git a/src/google/protobuf/compiler/parser.cc b/src/google/protobuf/compiler/parser.cc
|
||||
index 5bd37d147bc449444f875f89367a208a32a9146e..e36a4a74359fcace20c017f241d58930660b9381 100644
|
||||
--- a/src/google/protobuf/compiler/parser.cc
|
||||
+++ b/src/google/protobuf/compiler/parser.cc
|
||||
@@ -159,7 +159,7 @@ bool IsLowerUnderscore(const std::string& name) {
|
||||
}
|
||||
|
||||
bool IsNumberFollowUnderscore(const std::string& name) {
|
||||
- for (int i = 1; i < name.length(); i++) {
|
||||
+ for (size_t i = 1; i < name.length(); i++) {
|
||||
const char c = name[i];
|
||||
if (IsNumber(c) && name[i - 1] == '_') {
|
||||
return true;
|
||||
@@ -500,7 +500,7 @@ void Parser::LocationRecorder::AttachComments(
|
||||
if (!trailing->empty()) {
|
||||
location_->mutable_trailing_comments()->swap(*trailing);
|
||||
}
|
||||
- for (int i = 0; i < detached_comments->size(); ++i) {
|
||||
+ for (size_t i = 0; i < detached_comments->size(); ++i) {
|
||||
location_->add_leading_detached_comments()->swap((*detached_comments)[i]);
|
||||
}
|
||||
detached_comments->clear();
|
||||
diff --git a/src/google/protobuf/io/io_win32.cc b/src/google/protobuf/io/io_win32.cc
|
||||
index 4e8190880918f1ba155d75db76d6c1ee0b003247..78c07d0d771b9c227c6cd930fc91d272fd67500f 100644
|
||||
--- a/src/google/protobuf/io/io_win32.cc
|
||||
+++ b/src/google/protobuf/io/io_win32.cc
|
||||
@@ -198,7 +198,7 @@ wstring normalize(wstring path) {
|
||||
// Join all segments.
|
||||
bool first = true;
|
||||
std::wstringstream result;
|
||||
- for (int i = 0; i < segments.size(); ++i) {
|
||||
+ for (size_t i = 0; i < segments.size(); ++i) {
|
||||
if (!first) {
|
||||
result << L'\\';
|
||||
}
|
||||
diff --git a/src/google/protobuf/stubs/stringprintf.cc b/src/google/protobuf/stubs/stringprintf.cc
|
||||
index a6ad4c0da4080f5241c26176046a3add5247e25c..8b890f47c386f0d6b0ab9fd9928fae03edd076eb 100644
|
||||
--- a/src/google/protobuf/stubs/stringprintf.cc
|
||||
+++ b/src/google/protobuf/stubs/stringprintf.cc
|
||||
@@ -149,10 +149,10 @@ std::string StringPrintfVector(const char* format,
|
||||
// or displaying random chunks of memory to users.
|
||||
|
||||
const char* cstr[kStringPrintfVectorMaxArgs];
|
||||
- for (int i = 0; i < v.size(); ++i) {
|
||||
+ for (size_t i = 0; i < v.size(); ++i) {
|
||||
cstr[i] = v[i].c_str();
|
||||
}
|
||||
- for (int i = v.size(); i < GOOGLE_ARRAYSIZE(cstr); ++i) {
|
||||
+ for (size_t i = v.size(); i < GOOGLE_ARRAYSIZE(cstr); ++i) {
|
||||
cstr[i] = &string_printf_empty_block[0];
|
||||
}
|
||||
|
||||
diff --git a/src/google/protobuf/stubs/strutil.cc b/src/google/protobuf/stubs/strutil.cc
|
||||
index 594c8eac6a6ebff6d8bc8cc8518e3fd521f24da1..3462e91ff273dc071628f06b91698a0f166514fc 100644
|
||||
--- a/src/google/protobuf/stubs/strutil.cc
|
||||
+++ b/src/google/protobuf/stubs/strutil.cc
|
||||
@@ -697,7 +697,7 @@ bool safe_parse_positive_int(std::string text, IntType *value_p) {
|
||||
IntType value = 0;
|
||||
const IntType vmax = std::numeric_limits<IntType>::max();
|
||||
assert(vmax > 0);
|
||||
- assert(vmax >= base);
|
||||
+ assert(static_cast<int>(vmax) >= base);
|
||||
const IntType vmax_over_base = vmax / base;
|
||||
const char* start = text.data();
|
||||
const char* end = start + text.size();
|
||||
diff --git a/src/google/protobuf/stubs/substitute.cc b/src/google/protobuf/stubs/substitute.cc
|
||||
index d301682ee3377760430839bc5d6530621333e48d..8c75b2562e43d9d4ade3ef187d38e2e81b43e2c7 100644
|
||||
--- a/src/google/protobuf/stubs/substitute.cc
|
||||
+++ b/src/google/protobuf/stubs/substitute.cc
|
||||
@@ -128,7 +128,7 @@ void SubstituteAndAppend(std::string* output, const char* format,
|
||||
}
|
||||
}
|
||||
|
||||
- GOOGLE_DCHECK_EQ(target - output->data(), output->size());
|
||||
+ GOOGLE_DCHECK_EQ(target - output->data(), static_cast<int>(output->size()));
|
||||
}
|
||||
|
||||
} // namespace strings
|
||||
diff --git a/src/google/protobuf/util/field_mask_util.cc b/src/google/protobuf/util/field_mask_util.cc
|
||||
index 700e59004a083c731477bcc0bb4d5c36d06f306c..9a40b851a9e51d30b286ff5d89707bf9f279d0c0 100644
|
||||
--- a/src/google/protobuf/util/field_mask_util.cc
|
||||
+++ b/src/google/protobuf/util/field_mask_util.cc
|
||||
@@ -366,7 +366,7 @@ void FieldMaskTree::RemovePath(const std::string& path,
|
||||
Node* node = &root_;
|
||||
const Descriptor* current_descriptor = descriptor;
|
||||
Node* new_branch_node = nullptr;
|
||||
- for (int i = 0; i < parts.size(); ++i) {
|
||||
+ for (size_t i = 0; i < parts.size(); ++i) {
|
||||
nodes[i] = node;
|
||||
const FieldDescriptor* field_descriptor =
|
||||
current_descriptor->FindFieldByName(parts[i]);
|
||||
diff --git a/src/google/protobuf/util/internal/datapiece.cc b/src/google/protobuf/util/internal/datapiece.cc
|
||||
index 3e7aa84da7181e2ab270e181b9f63deb1905542f..56f4a18fa4afc64708938fa5352937cdd17b5747 100644
|
||||
--- a/src/google/protobuf/util/internal/datapiece.cc
|
||||
+++ b/src/google/protobuf/util/internal/datapiece.cc
|
||||
@@ -53,6 +53,10 @@ namespace {
|
||||
|
||||
template <typename To, typename From>
|
||||
util::StatusOr<To> ValidateNumberConversion(To after, From before) {
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
+#endif
|
||||
if (after == before &&
|
||||
MathUtil::Sign<From>(before) == MathUtil::Sign<To>(after)) {
|
||||
return after;
|
||||
@@ -62,6 +66,9 @@ util::StatusOr<To> ValidateNumberConversion(To after, From before) {
|
||||
: std::is_same<From, double>::value ? DoubleAsString(before)
|
||||
: FloatAsString(before));
|
||||
}
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic pop
|
||||
+#endif
|
||||
}
|
||||
|
||||
// For general conversion between
|
||||
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc
|
||||
index 7f61cdafa7c771a69364c5e9c49667535b16d957..a7d4ce78bd47e0250def474df8937927be9ef116 100644
|
||||
--- a/src/google/protobuf/util/internal/default_value_objectwriter.cc
|
||||
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc
|
||||
@@ -312,7 +312,7 @@ void DefaultValueObjectWriter::Node::PopulateChildren(
|
||||
std::unordered_map<std::string, int> orig_children_map;
|
||||
|
||||
// Creates a map of child nodes to speed up lookup.
|
||||
- for (int i = 0; i < children_.size(); ++i) {
|
||||
+ for (size_t i = 0; i < children_.size(); ++i) {
|
||||
InsertIfNotPresent(&orig_children_map, children_[i]->name_, i);
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ void DefaultValueObjectWriter::Node::PopulateChildren(
|
||||
new_children.push_back(child.release());
|
||||
}
|
||||
// Adds all leftover nodes in children_ to the beginning of new_child.
|
||||
- for (int i = 0; i < children_.size(); ++i) {
|
||||
+ for (size_t i = 0; i < children_.size(); ++i) {
|
||||
if (children_[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.h b/src/google/protobuf/util/internal/default_value_objectwriter.h
|
||||
index a9e1673fa1e4ed35ab6890a44eed1d362265d914..1a151ab25951f8b0e1c9c724253b16524b88530a 100644
|
||||
--- a/src/google/protobuf/util/internal/default_value_objectwriter.h
|
||||
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.h
|
||||
@@ -154,7 +154,7 @@ class PROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
|
||||
bool preserve_proto_field_names, bool use_ints_for_enums,
|
||||
FieldScrubCallBack field_scrub_callback);
|
||||
virtual ~Node() {
|
||||
- for (int i = 0; i < children_.size(); ++i) {
|
||||
+ for (size_t i = 0; i < children_.size(); ++i) {
|
||||
delete children_[i];
|
||||
}
|
||||
}
|
||||
diff --git a/src/google/protobuf/util/internal/json_escaping.cc b/src/google/protobuf/util/internal/json_escaping.cc
|
||||
index e4fa8cf788642e4a9d9c0460c287b1c24891b9fa..c192ddca7aff3984ffcbf82e13585bdf34ad8aae 100644
|
||||
--- a/src/google/protobuf/util/internal/json_escaping.cc
|
||||
+++ b/src/google/protobuf/util/internal/json_escaping.cc
|
||||
@@ -179,7 +179,7 @@ bool ReadCodePoint(StringPiece str, int index, uint32_t* cp,
|
||||
// the last unicode code point.
|
||||
*num_read = 0;
|
||||
}
|
||||
- while (*num_left > 0 && index < str.size()) {
|
||||
+ while (*num_left > 0 && index < static_cast<int>(str.size())) {
|
||||
uint32_t ch = static_cast<uint8_t>(str[index++]);
|
||||
--(*num_left);
|
||||
++(*num_read);
|
||||
@@ -309,7 +309,7 @@ void JsonEscaping::Escape(strings::ByteSource* input,
|
||||
while (input->Available() > 0) {
|
||||
StringPiece str = input->Peek();
|
||||
StringPiece escaped;
|
||||
- int i = 0;
|
||||
+ size_t i = 0;
|
||||
int num_read;
|
||||
bool ok;
|
||||
bool cp_was_split = num_left > 0;
|
||||
@@ -349,7 +349,7 @@ void JsonEscaping::Escape(StringPiece input, strings::ByteSink* output) {
|
||||
const char* p = input.data();
|
||||
|
||||
bool can_skip_escaping = true;
|
||||
- for (int i = 0; i < len; i++) {
|
||||
+ for (size_t i = 0; i < len; i++) {
|
||||
char c = p[i];
|
||||
if (c < 0x20 || c >= 0x7F || c == '"' || c == '<' || c == '>' ||
|
||||
c == '\\') {
|
||||
diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h
|
||||
index cb7dff6e9fe79858a73b2c7501106fe8d05ccac5..92348da3b5c3f07e6146136352f976c94fe54340 100644
|
||||
--- a/src/google/protobuf/util/internal/json_objectwriter.h
|
||||
+++ b/src/google/protobuf/util/internal/json_objectwriter.h
|
||||
@@ -102,7 +102,7 @@ class PROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
|
||||
if (!indent_string.empty()) {
|
||||
indent_char_ = indent_string[0];
|
||||
indent_count_ = indent_string.length();
|
||||
- for (int i = 1; i < indent_string.length(); i++) {
|
||||
+ for (size_t i = 1; i < indent_string.length(); i++) {
|
||||
if (indent_char_ != indent_string_[i]) {
|
||||
indent_char_ = '\0';
|
||||
indent_count_ = 0;
|
||||
diff --git a/src/google/protobuf/util/internal/json_stream_parser.cc b/src/google/protobuf/util/internal/json_stream_parser.cc
|
||||
index 5c34dbccafb9f40249ba3c0b7318b2e897f203dc..e786781f6de23c8a7ea282d061df6032111f6d21 100644
|
||||
--- a/src/google/protobuf/util/internal/json_stream_parser.cc
|
||||
+++ b/src/google/protobuf/util/internal/json_stream_parser.cc
|
||||
@@ -80,7 +80,7 @@ inline void ReplaceInvalidCodePoints(StringPiece str,
|
||||
const std::string& replacement,
|
||||
std::string* dst) {
|
||||
while (!str.empty()) {
|
||||
- int n_valid_bytes = internal::UTF8SpnStructurallyValid(str);
|
||||
+ size_t n_valid_bytes = internal::UTF8SpnStructurallyValid(str);
|
||||
StringPiece valid_part = str.substr(0, n_valid_bytes);
|
||||
StrAppend(dst, valid_part);
|
||||
|
||||
@@ -98,7 +98,7 @@ inline void ReplaceInvalidCodePoints(StringPiece str,
|
||||
|
||||
static bool ConsumeKey(StringPiece* input, StringPiece* key) {
|
||||
if (input->empty() || !IsLetter((*input)[0])) return false;
|
||||
- int len = 1;
|
||||
+ size_t len = 1;
|
||||
for (; len < input->size(); ++len) {
|
||||
if (!IsAlphanumeric((*input)[len])) {
|
||||
break;
|
||||
@@ -113,7 +113,7 @@ static bool ConsumeKey(StringPiece* input, StringPiece* key) {
|
||||
static bool ConsumeKeyPermissive(StringPiece* input,
|
||||
StringPiece* key) {
|
||||
if (input->empty() || !IsLetter((*input)[0])) return false;
|
||||
- int len = 1;
|
||||
+ size_t len = 1;
|
||||
for (; len < input->size(); ++len) {
|
||||
if (IsKeySeparator((*input)[len])) {
|
||||
break;
|
||||
@@ -946,7 +946,7 @@ util::Status JsonStreamParser::ParseKey() {
|
||||
JsonStreamParser::TokenType JsonStreamParser::GetNextTokenType() {
|
||||
SkipWhitespace();
|
||||
|
||||
- int size = p_.size();
|
||||
+ size_t size = p_.size();
|
||||
if (size == 0) {
|
||||
// If we ran out of data, report unknown and we'll place the previous parse
|
||||
// type onto the stack and try again when we have more data.
|
||||
diff --git a/src/google/protobuf/util/internal/proto_writer.cc b/src/google/protobuf/util/internal/proto_writer.cc
|
||||
index afa5e2e474b6960b8826a40b73615d5dffd971de..11b6df13d8f4f9506e828c39d6e74bc8acceb23d 100644
|
||||
--- a/src/google/protobuf/util/internal/proto_writer.cc
|
||||
+++ b/src/google/protobuf/util/internal/proto_writer.cc
|
||||
@@ -408,7 +408,7 @@ std::string ProtoWriter::ProtoElement::ToString() const {
|
||||
if (!ow_->IsRepeated(*(now->parent_field_)) ||
|
||||
now->parent()->parent_field_ != now->parent_field_) {
|
||||
std::string name = now->parent_field_->name();
|
||||
- int i = 0;
|
||||
+ size_t i = 0;
|
||||
while (i < name.size() &&
|
||||
(ascii_isalnum(name[i]) || name[i] == '_'))
|
||||
++i;
|
||||
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc
|
||||
index ecb219e06e514b1a6ba0e3e343126a75852d0a1d..ce94cfcefb417203f80142c54003efea283f6a1c 100644
|
||||
--- a/src/google/protobuf/util/internal/protostream_objectwriter.cc
|
||||
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc
|
||||
@@ -378,7 +378,7 @@ void ProtoStreamObjectWriter::AnyWriter::StartAny(const DataPiece& value) {
|
||||
|
||||
// Now we know the proto type and can interpret all data fields we gathered
|
||||
// before the "@type" field.
|
||||
- for (int i = 0; i < uninterpreted_events_.size(); ++i) {
|
||||
+ for (size_t i = 0; i < uninterpreted_events_.size(); ++i) {
|
||||
uninterpreted_events_[i].Replay(this);
|
||||
}
|
||||
}
|
||||
diff --git a/src/google/protobuf/util/internal/utility.cc b/src/google/protobuf/util/internal/utility.cc
|
||||
index 918ee17d9b040ae1bf9d98e3f46f75770c471393..3c4ac086d594d67b334cbc1dc046c281cd59a374 100644
|
||||
--- a/src/google/protobuf/util/internal/utility.cc
|
||||
+++ b/src/google/protobuf/util/internal/utility.cc
|
||||
@@ -345,7 +345,7 @@ void DeleteWellKnownTypes() { delete well_known_types_; }
|
||||
|
||||
void InitWellKnownTypes() {
|
||||
well_known_types_ = new std::set<std::string>;
|
||||
- for (int i = 0; i < GOOGLE_ARRAYSIZE(well_known_types_name_array_); ++i) {
|
||||
+ for (size_t i = 0; i < GOOGLE_ARRAYSIZE(well_known_types_name_array_); ++i) {
|
||||
well_known_types_->insert(well_known_types_name_array_[i]);
|
||||
}
|
||||
google::protobuf::internal::OnShutdown(&DeleteWellKnownTypes);
|
||||
diff --git a/src/google/protobuf/util/json_util.cc b/src/google/protobuf/util/json_util.cc
|
||||
index c39c10d87b7d8bf6fc18cae1ce459257c45945d6..a9b1c52a73c86d3e3655ba0748f2a82c68bd40ce 100644
|
||||
--- a/src/google/protobuf/util/json_util.cc
|
||||
+++ b/src/google/protobuf/util/json_util.cc
|
||||
@@ -64,7 +64,7 @@ ZeroCopyStreamByteSink::~ZeroCopyStreamByteSink() {
|
||||
|
||||
void ZeroCopyStreamByteSink::Append(const char* bytes, size_t len) {
|
||||
while (true) {
|
||||
- if (len <= buffer_size_) { // NOLINT
|
||||
+ if (static_cast<int>(len) <= buffer_size_) { // NOLINT
|
||||
memcpy(buffer_, bytes, len);
|
||||
buffer_ = static_cast<char*>(buffer_) + len;
|
||||
buffer_size_ -= len;
|
||||
@@ -0,0 +1,22 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 14:41:39 -0700
|
||||
Subject: [PATCH 02/11] Remove redundant move
|
||||
|
||||
---
|
||||
src/google/protobuf/extension_set.h | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/google/protobuf/extension_set.h b/src/google/protobuf/extension_set.h
|
||||
index 0e6d0521104d7f721bdbad1dd593233b035c5b85..b5343689ef7c16442995746439bbe8928022c593 100644
|
||||
--- a/src/google/protobuf/extension_set.h
|
||||
+++ b/src/google/protobuf/extension_set.h
|
||||
@@ -714,7 +714,7 @@ class PROTOBUF_EXPORT ExtensionSet {
|
||||
static KeyValueFunctor ForEach(Iterator begin, Iterator end,
|
||||
KeyValueFunctor func) {
|
||||
for (Iterator it = begin; it != end; ++it) func(it->first, it->second);
|
||||
- return std::move(func);
|
||||
+ return func;
|
||||
}
|
||||
|
||||
// Applies a functor to the <int, Extension&> pairs in sorted order.
|
||||
@@ -0,0 +1,168 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 15:00:20 -0700
|
||||
Subject: [PATCH 03/11] Fix maybe-uninitialized warnings
|
||||
|
||||
---
|
||||
src/google/protobuf/arena.cc | 6 +++---
|
||||
src/google/protobuf/arena_impl.h | 4 ++--
|
||||
src/google/protobuf/extension_set_inl.h | 2 +-
|
||||
.../protobuf/generated_message_tctable_lite.cc | 16 ++++++++--------
|
||||
src/google/protobuf/io/coded_stream.cc | 2 +-
|
||||
5 files changed, 15 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/google/protobuf/arena.cc b/src/google/protobuf/arena.cc
|
||||
index 6ba508a5f0f3436d52ccc326cc932ceed3cd79fa..194404213ee5f6ad05b1e01f2bff23859d60dc56 100644
|
||||
--- a/src/google/protobuf/arena.cc
|
||||
+++ b/src/google/protobuf/arena.cc
|
||||
@@ -411,7 +411,7 @@ uint64_t ThreadSafeArena::Reset() {
|
||||
std::pair<void*, SerialArena::CleanupNode*>
|
||||
ThreadSafeArena::AllocateAlignedWithCleanup(size_t n,
|
||||
const std::type_info* type) {
|
||||
- SerialArena* arena;
|
||||
+ SerialArena* arena = nullptr;
|
||||
if (PROTOBUF_PREDICT_TRUE(!alloc_policy_.should_record_allocs() &&
|
||||
GetSerialArenaFast(&arena))) {
|
||||
return arena->AllocateAlignedWithCleanup(n, alloc_policy_.get());
|
||||
@@ -421,7 +421,7 @@ ThreadSafeArena::AllocateAlignedWithCleanup(size_t n,
|
||||
}
|
||||
|
||||
void ThreadSafeArena::AddCleanup(void* elem, void (*cleanup)(void*)) {
|
||||
- SerialArena* arena;
|
||||
+ SerialArena* arena = nullptr;
|
||||
if (PROTOBUF_PREDICT_FALSE(!GetSerialArenaFast(&arena))) {
|
||||
arena = GetSerialArenaFallback(&thread_cache());
|
||||
}
|
||||
@@ -433,7 +433,7 @@ void* ThreadSafeArena::AllocateAlignedFallback(size_t n,
|
||||
const std::type_info* type) {
|
||||
if (alloc_policy_.should_record_allocs()) {
|
||||
alloc_policy_.RecordAlloc(type, n);
|
||||
- SerialArena* arena;
|
||||
+ SerialArena* arena = nullptr;
|
||||
if (PROTOBUF_PREDICT_TRUE(GetSerialArenaFast(&arena))) {
|
||||
return arena->AllocateAligned(n, alloc_policy_.get());
|
||||
}
|
||||
diff --git a/src/google/protobuf/arena_impl.h b/src/google/protobuf/arena_impl.h
|
||||
index 76727688b558354be18f0c6576db1d4619348ef4..8af70c48f13feece7cf8580e71845ec267ddaddb 100644
|
||||
--- a/src/google/protobuf/arena_impl.h
|
||||
+++ b/src/google/protobuf/arena_impl.h
|
||||
@@ -482,7 +482,7 @@ class PROTOBUF_EXPORT ThreadSafeArena {
|
||||
|
||||
template <AllocationClient alloc_client = AllocationClient::kDefault>
|
||||
void* AllocateAligned(size_t n, const std::type_info* type) {
|
||||
- SerialArena* arena;
|
||||
+ SerialArena* arena = nullptr;
|
||||
if (PROTOBUF_PREDICT_TRUE(!alloc_policy_.should_record_allocs() &&
|
||||
GetSerialArenaFast(&arena))) {
|
||||
return arena->AllocateAligned<alloc_client>(n, AllocPolicy());
|
||||
@@ -492,7 +492,7 @@ class PROTOBUF_EXPORT ThreadSafeArena {
|
||||
}
|
||||
|
||||
void ReturnArrayMemory(void* p, size_t size) {
|
||||
- SerialArena* arena;
|
||||
+ SerialArena* arena = nullptr;
|
||||
if (PROTOBUF_PREDICT_TRUE(GetSerialArenaFast(&arena))) {
|
||||
arena->ReturnArrayMemory(p, size);
|
||||
}
|
||||
diff --git a/src/google/protobuf/extension_set_inl.h b/src/google/protobuf/extension_set_inl.h
|
||||
index e4e711721d4d8690e1e87fc8e31df1b5836d4fd7..50c04cd41feccf6cb5fda2740d6d4adb874645d7 100644
|
||||
--- a/src/google/protobuf/extension_set_inl.h
|
||||
+++ b/src/google/protobuf/extension_set_inl.h
|
||||
@@ -206,7 +206,7 @@ const char* ExtensionSet::ParseMessageSetItemTmpl(
|
||||
const char* ptr, const Msg* extendee, internal::InternalMetadata* metadata,
|
||||
internal::ParseContext* ctx) {
|
||||
std::string payload;
|
||||
- uint32_t type_id;
|
||||
+ uint32_t type_id = 0;
|
||||
enum class State { kNoTag, kHasType, kHasPayload, kDone };
|
||||
State state = State::kNoTag;
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
index 9993811dca3229edc766061c4a8d54933bcb0eba..2268b2be4d4c60c545765469549d73c6a468dac8 100644
|
||||
--- a/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
+++ b/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
@@ -770,7 +770,7 @@ PROTOBUF_NOINLINE const char* TcParser::SingularVarBigint(
|
||||
};
|
||||
volatile Spill spill = {data.data, msg, table, hasbits};
|
||||
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
PROTOBUF_ASSUME(static_cast<int8_t>(*ptr) < 0);
|
||||
ptr = ParseVarint(ptr, &tmp);
|
||||
|
||||
@@ -845,7 +845,7 @@ PROTOBUF_ALWAYS_INLINE const char* TcParser::RepeatedVarint(
|
||||
auto expected_tag = UnalignedLoad<TagType>(ptr);
|
||||
do {
|
||||
ptr += sizeof(TagType);
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr, &tmp);
|
||||
if (ptr == nullptr) {
|
||||
return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
@@ -1001,7 +1001,7 @@ PROTOBUF_ALWAYS_INLINE const char* TcParser::SingularEnum(
|
||||
}
|
||||
const char* ptr2 = ptr; // Save for unknown enum case
|
||||
ptr += sizeof(TagType); // Consume tag
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr, &tmp);
|
||||
if (ptr == nullptr) {
|
||||
return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
@@ -1052,7 +1052,7 @@ PROTOBUF_ALWAYS_INLINE const char* TcParser::RepeatedEnum(
|
||||
do {
|
||||
const char* ptr2 = ptr; // save for unknown enum case
|
||||
ptr += sizeof(TagType);
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr, &tmp);
|
||||
if (ptr == nullptr) {
|
||||
return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
@@ -1477,7 +1477,7 @@ const char* TcParser::MpVarint(PROTOBUF_TC_PARAM_DECL) {
|
||||
|
||||
// Parse the value:
|
||||
const char* ptr2 = ptr; // save for unknown enum case
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr, &tmp);
|
||||
if (ptr == nullptr) return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
|
||||
@@ -1542,7 +1542,7 @@ const char* TcParser::MpRepeatedVarint(PROTOBUF_TC_PARAM_DECL) {
|
||||
const char* ptr2 = ptr;
|
||||
uint32_t next_tag;
|
||||
do {
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr2, &tmp);
|
||||
if (ptr == nullptr) return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
field.Add(is_zigzag ? WireFormatLite::ZigZagDecode64(tmp) : tmp);
|
||||
@@ -1554,7 +1554,7 @@ const char* TcParser::MpRepeatedVarint(PROTOBUF_TC_PARAM_DECL) {
|
||||
const char* ptr2 = ptr;
|
||||
uint32_t next_tag;
|
||||
do {
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr2, &tmp);
|
||||
if (ptr == nullptr) return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
if (is_validated_enum) {
|
||||
@@ -1575,7 +1575,7 @@ const char* TcParser::MpRepeatedVarint(PROTOBUF_TC_PARAM_DECL) {
|
||||
const char* ptr2 = ptr;
|
||||
uint32_t next_tag;
|
||||
do {
|
||||
- uint64_t tmp;
|
||||
+ uint64_t tmp = 0;
|
||||
ptr = ParseVarint(ptr2, &tmp);
|
||||
if (ptr == nullptr) return Error(PROTOBUF_TC_PARAM_PASS);
|
||||
field.Add(static_cast<bool>(tmp));
|
||||
diff --git a/src/google/protobuf/io/coded_stream.cc b/src/google/protobuf/io/coded_stream.cc
|
||||
index 487e1b8a379b86bfa3097d68a64ee0a727d36cab..53997901f333292f71ac52e7f9c876bd918f7bf6 100644
|
||||
--- a/src/google/protobuf/io/coded_stream.cc
|
||||
+++ b/src/google/protobuf/io/coded_stream.cc
|
||||
@@ -462,7 +462,7 @@ int64_t CodedInputStream::ReadVarint32Fallback(uint32_t first_byte_or_zero) {
|
||||
(buffer_end_ > buffer_ && !(buffer_end_[-1] & 0x80))) {
|
||||
GOOGLE_DCHECK_NE(first_byte_or_zero, 0)
|
||||
<< "Caller should provide us with *buffer_ when buffer is non-empty";
|
||||
- uint32_t temp;
|
||||
+ uint32_t temp = 0;
|
||||
::std::pair<bool, const uint8_t*> p =
|
||||
ReadVarint32FromArray(first_byte_or_zero, buffer_, &temp);
|
||||
if (!p.first) return -1;
|
||||
@@ -0,0 +1,38 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 15:03:38 -0700
|
||||
Subject: [PATCH 04/11] Fix coded_stream WriteRaw
|
||||
|
||||
---
|
||||
src/google/protobuf/implicit_weak_message.h | 2 +-
|
||||
src/google/protobuf/io/coded_stream.h | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/google/protobuf/implicit_weak_message.h b/src/google/protobuf/implicit_weak_message.h
|
||||
index b894ab4809090011f381828857d1674a6b7bff9b..c358c14c06f773d87f5089c75566a5a6fcba13e6 100644
|
||||
--- a/src/google/protobuf/implicit_weak_message.h
|
||||
+++ b/src/google/protobuf/implicit_weak_message.h
|
||||
@@ -100,7 +100,7 @@ class PROTOBUF_EXPORT ImplicitWeakMessage : public MessageLite {
|
||||
if (data_ == nullptr) {
|
||||
return target;
|
||||
}
|
||||
- return stream->WriteRaw(data_->data(), static_cast<int>(data_->size()),
|
||||
+ return stream->WriteRaw(data_->data(), data_->size(),
|
||||
target);
|
||||
}
|
||||
|
||||
diff --git a/src/google/protobuf/io/coded_stream.h b/src/google/protobuf/io/coded_stream.h
|
||||
index c8fc994f916d047c0a7b176e53c9e946ebd752de..6c0dd4ab4099d1d748957af8bfc5f8c59c2aa3d6 100644
|
||||
--- a/src/google/protobuf/io/coded_stream.h
|
||||
+++ b/src/google/protobuf/io/coded_stream.h
|
||||
@@ -677,8 +677,8 @@ class PROTOBUF_EXPORT EpsCopyOutputStream {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
- uint8_t* WriteRaw(const void* data, int size, uint8_t* ptr) {
|
||||
- if (PROTOBUF_PREDICT_FALSE(end_ - ptr < size)) {
|
||||
+ uint8_t* WriteRaw(const void* data, size_t size, uint8_t* ptr) {
|
||||
+ if (PROTOBUF_PREDICT_FALSE(end_ - ptr < static_cast<int>(size))) {
|
||||
return WriteRawFallback(data, size, ptr);
|
||||
}
|
||||
std::memcpy(ptr, data, size);
|
||||
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 15:13:45 -0700
|
||||
Subject: [PATCH 05/11] Suppress enum-enum conversion warning
|
||||
|
||||
---
|
||||
src/google/protobuf/generated_message_tctable_impl.h | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h
|
||||
index 21fa5332d39b24e0bdb6432f27183df743d512c6..ce3d9110e28706ca18dcf0f2c94feba75f447dc7 100644
|
||||
--- a/src/google/protobuf/generated_message_tctable_impl.h
|
||||
+++ b/src/google/protobuf/generated_message_tctable_impl.h
|
||||
@@ -180,6 +180,12 @@ static_assert(kFmtShift + kFmtBits == 12, "number of bits changed");
|
||||
// This assertion should not change unless the storage width changes:
|
||||
static_assert(kFmtShift + kFmtBits <= 16, "too many bits");
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic push
|
||||
+#if __GNUC__ >= 12 || (__GNUC__ == 11 && __GNUC_MINOR >= 1)
|
||||
+#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
|
||||
+#endif
|
||||
+#endif
|
||||
// Convenience aliases (16 bits, with format):
|
||||
enum FieldType : uint16_t {
|
||||
// Numeric types:
|
||||
@@ -232,6 +238,9 @@ enum FieldType : uint16_t {
|
||||
// Map types:
|
||||
kMap = kFkMap,
|
||||
};
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic pop
|
||||
+#endif
|
||||
|
||||
// clang-format on
|
||||
} // namespace field_layout
|
||||
@@ -0,0 +1,23 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 15:16:46 -0700
|
||||
Subject: [PATCH 06/11] Fix noreturn function returning
|
||||
|
||||
---
|
||||
src/google/protobuf/generated_message_tctable_impl.h | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h
|
||||
index ce3d9110e28706ca18dcf0f2c94feba75f447dc7..a8f8bdc192081763b0ad8c685384ae68d901736c 100644
|
||||
--- a/src/google/protobuf/generated_message_tctable_impl.h
|
||||
+++ b/src/google/protobuf/generated_message_tctable_impl.h
|
||||
@@ -262,6 +262,9 @@ template <size_t align>
|
||||
#endif
|
||||
void AlignFail(uintptr_t address) {
|
||||
GOOGLE_LOG(FATAL) << "Unaligned (" << align << ") access at " << address;
|
||||
+#ifdef __GNUC__
|
||||
+ __builtin_unreachable();
|
||||
+#endif
|
||||
}
|
||||
|
||||
extern template void AlignFail<4>(uintptr_t);
|
||||
@@ -0,0 +1,29 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Sat, 10 Jun 2023 15:59:45 -0700
|
||||
Subject: [PATCH 07/11] Work around GCC 12 restrict warning compiler bug
|
||||
|
||||
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329
|
||||
---
|
||||
src/google/protobuf/io/tokenizer.cc | 7 +++++++
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
diff --git a/src/google/protobuf/io/tokenizer.cc b/src/google/protobuf/io/tokenizer.cc
|
||||
index f9e07763e7362bd37267619336db841d0ae9df25..30d62ac9647b897c2e7c8ad43cd27ff0e08922a2 100644
|
||||
--- a/src/google/protobuf/io/tokenizer.cc
|
||||
+++ b/src/google/protobuf/io/tokenizer.cc
|
||||
@@ -585,7 +585,14 @@ Tokenizer::NextCommentStatus Tokenizer::TryConsumeCommentStart() {
|
||||
} else {
|
||||
// Oops, it was just a slash. Return it.
|
||||
current_.type = TYPE_SYMBOL;
|
||||
+#if defined(__GNUC__) && !defined(__clang__)
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wrestrict"
|
||||
+#endif
|
||||
current_.text = "/";
|
||||
+#if defined(__GNUC__) && !defined(__clang__)
|
||||
+#pragma GCC diagnostic pop
|
||||
+#endif
|
||||
current_.line = line_;
|
||||
current_.column = column_ - 1;
|
||||
current_.end_column = column_;
|
||||
@@ -0,0 +1,24 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Tue, 13 Jun 2023 23:56:15 -0700
|
||||
Subject: [PATCH 08/11] Disable MSVC switch warning
|
||||
|
||||
---
|
||||
src/google/protobuf/generated_message_reflection.cc | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_reflection.cc b/src/google/protobuf/generated_message_reflection.cc
|
||||
index 2a807e066bb748f214e008971309ef15473344b5..599dde80b671085d87ff1812929cafe8d2aecf75 100644
|
||||
--- a/src/google/protobuf/generated_message_reflection.cc
|
||||
+++ b/src/google/protobuf/generated_message_reflection.cc
|
||||
@@ -75,6 +75,10 @@ using google::protobuf::internal::RepeatedPtrFieldBase;
|
||||
using google::protobuf::internal::StringSpaceUsedExcludingSelfLong;
|
||||
using google::protobuf::internal::WrappedMutex;
|
||||
|
||||
+#ifdef _MSC_VER
|
||||
+#pragma warning(disable : 4065)
|
||||
+#endif
|
||||
+
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Tue, 13 Jun 2023 23:58:50 -0700
|
||||
Subject: [PATCH 09/11] Disable unused function warning
|
||||
|
||||
---
|
||||
src/google/protobuf/generated_message_tctable_lite.cc | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
index 2268b2be4d4c60c545765469549d73c6a468dac8..23557a614752a9f0c93d8bd56724f3bc0f962185 100644
|
||||
--- a/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
+++ b/src/google/protobuf/generated_message_tctable_lite.cc
|
||||
@@ -43,6 +43,10 @@
|
||||
#include <google/protobuf/port_def.inc>
|
||||
// clang-format on
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
+#endif
|
||||
+
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace internal {
|
||||
@@ -0,0 +1,95 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Wed, 14 Jun 2023 00:02:26 -0700
|
||||
Subject: [PATCH 10/11] Disable pedantic warning
|
||||
|
||||
---
|
||||
src/google/protobuf/descriptor.h | 8 ++++++++
|
||||
src/google/protobuf/generated_message_reflection.cc | 2 ++
|
||||
src/google/protobuf/parse_context.h | 8 ++++++++
|
||||
src/google/protobuf/stubs/common.cc | 4 ++--
|
||||
4 files changed, 20 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/google/protobuf/descriptor.h b/src/google/protobuf/descriptor.h
|
||||
index 6e536e5705f8df4f7c13638d50c114cbfb92fb4a..bee3e32b9f1d5ba47b83d1e388716a3c3b6e82c6 100644
|
||||
--- a/src/google/protobuf/descriptor.h
|
||||
+++ b/src/google/protobuf/descriptor.h
|
||||
@@ -80,6 +80,10 @@
|
||||
#define PROTOBUF_EXPORT
|
||||
#endif
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
+#endif
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
@@ -2434,6 +2438,10 @@ inline FileDescriptor::Syntax FileDescriptor::syntax() const {
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic pop
|
||||
+#endif
|
||||
+
|
||||
#undef PROTOBUF_INTERNAL_CHECK_CLASS_SIZE
|
||||
#include <google/protobuf/port_undef.inc>
|
||||
|
||||
diff --git a/src/google/protobuf/generated_message_reflection.cc b/src/google/protobuf/generated_message_reflection.cc
|
||||
index 599dde80b671085d87ff1812929cafe8d2aecf75..aaed21920908b329e22c2e0d92f69397996a9f93 100644
|
||||
--- a/src/google/protobuf/generated_message_reflection.cc
|
||||
+++ b/src/google/protobuf/generated_message_reflection.cc
|
||||
@@ -77,6 +77,8 @@ using google::protobuf::internal::WrappedMutex;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4065)
|
||||
+#elif defined(__GNUC__)
|
||||
+#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
|
||||
namespace google {
|
||||
diff --git a/src/google/protobuf/parse_context.h b/src/google/protobuf/parse_context.h
|
||||
index 7aea50cdc385f0ed01b3989e12276494bf574939..97daae09cbff11fd3b4b99cee935aeb542c42eb4 100644
|
||||
--- a/src/google/protobuf/parse_context.h
|
||||
+++ b/src/google/protobuf/parse_context.h
|
||||
@@ -52,6 +52,10 @@
|
||||
// Must be included last.
|
||||
#include <google/protobuf/port_def.inc>
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic push
|
||||
+#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
+#endif
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
@@ -1020,6 +1024,10 @@ PROTOBUF_NODISCARD PROTOBUF_EXPORT const char* UnknownFieldParse(
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
+#ifdef __GNUC__
|
||||
+#pragma GCC diagnostic pop
|
||||
+#endif
|
||||
+
|
||||
#include <google/protobuf/port_undef.inc>
|
||||
|
||||
#endif // GOOGLE_PROTOBUF_PARSE_CONTEXT_H__
|
||||
diff --git a/src/google/protobuf/stubs/common.cc b/src/google/protobuf/stubs/common.cc
|
||||
index e0a807ffbbc94d07176e20db230204384170607b..1423021b846966eb02d36c10df488f8aa0082a64 100644
|
||||
--- a/src/google/protobuf/stubs/common.cc
|
||||
+++ b/src/google/protobuf/stubs/common.cc
|
||||
@@ -277,11 +277,11 @@ LogHandler* SetLogHandler(LogHandler* new_func) {
|
||||
|
||||
LogSilencer::LogSilencer() {
|
||||
++internal::log_silencer_count_;
|
||||
-};
|
||||
+}
|
||||
|
||||
LogSilencer::~LogSilencer() {
|
||||
--internal::log_silencer_count_;
|
||||
-};
|
||||
+}
|
||||
|
||||
// ===================================================================
|
||||
// emulates google3/base/callback.cc
|
||||
@@ -0,0 +1,35 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Johnson <johnson.peter@gmail.com>
|
||||
Date: Mon, 9 Oct 2023 19:28:08 -0700
|
||||
Subject: [PATCH 11/11] Avoid use of sprintf
|
||||
|
||||
---
|
||||
src/google/protobuf/stubs/strutil.cc | 14 +++++++++++---
|
||||
1 file changed, 11 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/google/protobuf/stubs/strutil.cc b/src/google/protobuf/stubs/strutil.cc
|
||||
index 3462e91ff273dc071628f06b91698a0f166514fc..e063d0d3039c87d55a6df0dbfe50f365184fe292 100644
|
||||
--- a/src/google/protobuf/stubs/strutil.cc
|
||||
+++ b/src/google/protobuf/stubs/strutil.cc
|
||||
@@ -503,10 +503,18 @@ int CEscapeInternal(const char* src, int src_len, char* dest,
|
||||
(last_hex_escape && isxdigit(*src)))) {
|
||||
if (dest_len - used < 4) // need space for 4 letter escape
|
||||
return -1;
|
||||
- sprintf(dest + used, (use_hex ? "\\x%02x" : "\\%03o"),
|
||||
- static_cast<uint8_t>(*src));
|
||||
+ dest[used++] = '\\';
|
||||
+ if (use_hex) {
|
||||
+ constexpr char hexdigits[] = "0123456789abcdef";
|
||||
+ dest[used++] = 'x';
|
||||
+ dest[used++] = hexdigits[(static_cast<uint8_t>(*src) >> 4) & 0xf];
|
||||
+ dest[used++] = hexdigits[static_cast<uint8_t>(*src) & 0xf];
|
||||
+ } else {
|
||||
+ dest[used++] = '0' + ((static_cast<uint8_t>(*src) >> 6) & 0x3);
|
||||
+ dest[used++] = '0' + ((static_cast<uint8_t>(*src) >> 3) & 0x7);
|
||||
+ dest[used++] = '0' + (static_cast<uint8_t>(*src) & 0x7);
|
||||
+ }
|
||||
is_hex_escape = use_hex;
|
||||
- used += 4;
|
||||
} else {
|
||||
dest[used++] = *src; break;
|
||||
}
|
||||
78
upstream_utils/update_json.py
Executable file
78
upstream_utils/update_json.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from upstream_utils import (
|
||||
get_repo_root,
|
||||
clone_repo,
|
||||
walk_if,
|
||||
git_am,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
upstream_root = clone_repo("https://github.com/nlohmann/json", "v3.11.2")
|
||||
wpilib_root = get_repo_root()
|
||||
wpiutil = os.path.join(wpilib_root, "wpiutil")
|
||||
|
||||
# Apply patches to upstream Git repo
|
||||
os.chdir(upstream_root)
|
||||
for f in [
|
||||
"0001-Remove-version-from-namespace.patch",
|
||||
"0002-Make-serializer-public.patch",
|
||||
"0003-Make-dump_escaped-take-std-string_view.patch",
|
||||
"0004-Add-llvm-stream-support.patch",
|
||||
]:
|
||||
git_am(
|
||||
os.path.join(wpilib_root, "upstream_utils/json_patches", f),
|
||||
use_threeway=True,
|
||||
)
|
||||
|
||||
# Delete old install
|
||||
for d in [
|
||||
"src/main/native/thirdparty/json/include",
|
||||
]:
|
||||
shutil.rmtree(os.path.join(wpiutil, d), ignore_errors=True)
|
||||
|
||||
# Create lists of source and destination files
|
||||
os.chdir(os.path.join(upstream_root, "include/nlohmann"))
|
||||
files = walk_if(".", lambda dp, f: True)
|
||||
src_include_files = [
|
||||
os.path.join(os.path.join(upstream_root, "include/nlohmann"), f) for f in files
|
||||
]
|
||||
wpiutil_json_root = os.path.join(
|
||||
wpiutil, "src/main/native/thirdparty/json/include/wpi"
|
||||
)
|
||||
dest_include_files = [
|
||||
os.path.join(wpiutil_json_root, f.replace(".hpp", ".h")) for f in files
|
||||
]
|
||||
|
||||
# Copy json header files into allwpilib
|
||||
for i in range(len(src_include_files)):
|
||||
dest_dir = os.path.dirname(dest_include_files[i])
|
||||
if not os.path.exists(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
shutil.copyfile(src_include_files[i], dest_include_files[i])
|
||||
|
||||
for include_file in dest_include_files:
|
||||
with open(include_file) as f:
|
||||
content = f.read()
|
||||
|
||||
# Rename namespace from nlohmann to wpi
|
||||
content = content.replace("namespace nlohmann", "namespace wpi")
|
||||
content = content.replace("nlohmann::", "wpi::")
|
||||
|
||||
# Fix internal includes
|
||||
content = content.replace(".hpp>", ".h>")
|
||||
content = content.replace("include <nlohmann/", "include <wpi/")
|
||||
|
||||
# Fix include guards and other #defines
|
||||
content = content.replace("NLOHMANN_", "WPI_")
|
||||
|
||||
with open(include_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -171,7 +171,7 @@ def overwrite_tests(wpiutil_root, llvm_root):
|
||||
|
||||
|
||||
def main():
|
||||
upstream_root = clone_repo("https://github.com/llvm/llvm-project", "llvmorg-17.0.2")
|
||||
upstream_root = clone_repo("https://github.com/llvm/llvm-project", "llvmorg-17.0.3")
|
||||
wpilib_root = get_repo_root()
|
||||
wpiutil = os.path.join(wpilib_root, "wpiutil")
|
||||
|
||||
|
||||
307
upstream_utils/update_protobuf.py
Executable file
307
upstream_utils/update_protobuf.py
Executable file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from upstream_utils import (
|
||||
get_repo_root,
|
||||
clone_repo,
|
||||
comment_out_invalid_includes,
|
||||
copy_to,
|
||||
walk_cwd_and_copy_if,
|
||||
walk_if,
|
||||
git_am,
|
||||
)
|
||||
|
||||
protobuf_lite_sources = set(
|
||||
[
|
||||
"google/protobuf/any_lite.cc",
|
||||
"google/protobuf/arena.cc",
|
||||
"google/protobuf/arenastring.cc",
|
||||
"google/protobuf/arenaz_sampler.cc",
|
||||
"google/protobuf/extension_set.cc",
|
||||
"google/protobuf/generated_enum_util.cc",
|
||||
"google/protobuf/generated_message_tctable_lite.cc",
|
||||
"google/protobuf/generated_message_util.cc",
|
||||
"google/protobuf/implicit_weak_message.cc",
|
||||
"google/protobuf/inlined_string_field.cc",
|
||||
"google/protobuf/io/coded_stream.cc",
|
||||
"google/protobuf/io/io_win32.cc",
|
||||
"google/protobuf/io/strtod.cc",
|
||||
"google/protobuf/io/zero_copy_stream.cc",
|
||||
"google/protobuf/io/zero_copy_stream_impl.cc",
|
||||
"google/protobuf/io/zero_copy_stream_impl_lite.cc",
|
||||
"google/protobuf/map.cc",
|
||||
"google/protobuf/message_lite.cc",
|
||||
"google/protobuf/parse_context.cc",
|
||||
"google/protobuf/repeated_field.cc",
|
||||
"google/protobuf/repeated_ptr_field.cc",
|
||||
"google/protobuf/stubs/bytestream.cc",
|
||||
"google/protobuf/stubs/common.cc",
|
||||
"google/protobuf/stubs/int128.cc",
|
||||
"google/protobuf/stubs/status.cc",
|
||||
"google/protobuf/stubs/statusor.cc",
|
||||
"google/protobuf/stubs/stringpiece.cc",
|
||||
"google/protobuf/stubs/stringprintf.cc",
|
||||
"google/protobuf/stubs/structurally_valid.cc",
|
||||
"google/protobuf/stubs/strutil.cc",
|
||||
"google/protobuf/stubs/time.cc",
|
||||
"google/protobuf/wire_format_lite.cc",
|
||||
]
|
||||
)
|
||||
|
||||
protobuf_lite_includes = set(
|
||||
[
|
||||
"google/protobuf/any.h",
|
||||
"google/protobuf/arena.h",
|
||||
"google/protobuf/arena_impl.h",
|
||||
"google/protobuf/arenastring.h",
|
||||
"google/protobuf/arenaz_sampler.h",
|
||||
"google/protobuf/endian.h",
|
||||
"google/protobuf/explicitly_constructed.h",
|
||||
"google/protobuf/extension_set.h",
|
||||
"google/protobuf/extension_set_inl.h",
|
||||
"google/protobuf/generated_enum_util.h",
|
||||
"google/protobuf/generated_message_tctable_decl.h",
|
||||
"google/protobuf/generated_message_tctable_impl.h",
|
||||
"google/protobuf/generated_message_util.h",
|
||||
"google/protobuf/has_bits.h",
|
||||
"google/protobuf/implicit_weak_message.h",
|
||||
"google/protobuf/inlined_string_field.h",
|
||||
"google/protobuf/io/coded_stream.h",
|
||||
"google/protobuf/io/io_win32.h",
|
||||
"google/protobuf/io/strtod.h",
|
||||
"google/protobuf/io/zero_copy_stream.h",
|
||||
"google/protobuf/io/zero_copy_stream_impl.h",
|
||||
"google/protobuf/io/zero_copy_stream_impl_lite.h",
|
||||
"google/protobuf/map.h",
|
||||
"google/protobuf/map_entry_lite.h",
|
||||
"google/protobuf/map_field_lite.h",
|
||||
"google/protobuf/map_type_handler.h",
|
||||
"google/protobuf/message_lite.h",
|
||||
"google/protobuf/metadata_lite.h",
|
||||
"google/protobuf/parse_context.h",
|
||||
"google/protobuf/port.h",
|
||||
"google/protobuf/repeated_field.h",
|
||||
"google/protobuf/repeated_ptr_field.h",
|
||||
"google/protobuf/stubs/bytestream.h",
|
||||
"google/protobuf/stubs/callback.h",
|
||||
"google/protobuf/stubs/casts.h",
|
||||
"google/protobuf/stubs/common.h",
|
||||
"google/protobuf/stubs/hash.h",
|
||||
"google/protobuf/stubs/logging.h",
|
||||
"google/protobuf/stubs/macros.h",
|
||||
"google/protobuf/stubs/map_util.h",
|
||||
"google/protobuf/stubs/mutex.h",
|
||||
"google/protobuf/stubs/once.h",
|
||||
"google/protobuf/stubs/platform_macros.h",
|
||||
"google/protobuf/stubs/port.h",
|
||||
"google/protobuf/stubs/status.h",
|
||||
"google/protobuf/stubs/stl_util.h",
|
||||
"google/protobuf/stubs/stringpiece.h",
|
||||
"google/protobuf/stubs/strutil.h",
|
||||
"google/protobuf/stubs/template_util.h",
|
||||
"google/protobuf/wire_format_lite.h",
|
||||
]
|
||||
)
|
||||
|
||||
protobuf_sources = set(
|
||||
[
|
||||
"google/protobuf/any.cc",
|
||||
"google/protobuf/any.pb.cc",
|
||||
"google/protobuf/api.pb.cc",
|
||||
"google/protobuf/compiler/importer.cc",
|
||||
"google/protobuf/compiler/parser.cc",
|
||||
"google/protobuf/descriptor.cc",
|
||||
"google/protobuf/descriptor.pb.cc",
|
||||
"google/protobuf/descriptor_database.cc",
|
||||
"google/protobuf/duration.pb.cc",
|
||||
"google/protobuf/dynamic_message.cc",
|
||||
"google/protobuf/empty.pb.cc",
|
||||
"google/protobuf/extension_set_heavy.cc",
|
||||
"google/protobuf/field_mask.pb.cc",
|
||||
"google/protobuf/generated_message_bases.cc",
|
||||
"google/protobuf/generated_message_reflection.cc",
|
||||
"google/protobuf/generated_message_tctable_full.cc",
|
||||
"google/protobuf/io/gzip_stream.cc",
|
||||
"google/protobuf/io/printer.cc",
|
||||
"google/protobuf/io/tokenizer.cc",
|
||||
"google/protobuf/map_field.cc",
|
||||
"google/protobuf/message.cc",
|
||||
"google/protobuf/reflection_ops.cc",
|
||||
"google/protobuf/service.cc",
|
||||
"google/protobuf/source_context.pb.cc",
|
||||
"google/protobuf/struct.pb.cc",
|
||||
"google/protobuf/stubs/substitute.cc",
|
||||
"google/protobuf/text_format.cc",
|
||||
"google/protobuf/timestamp.pb.cc",
|
||||
"google/protobuf/type.pb.cc",
|
||||
"google/protobuf/unknown_field_set.cc",
|
||||
"google/protobuf/util/delimited_message_util.cc",
|
||||
"google/protobuf/util/field_comparator.cc",
|
||||
"google/protobuf/util/field_mask_util.cc",
|
||||
"google/protobuf/util/internal/datapiece.cc",
|
||||
"google/protobuf/util/internal/default_value_objectwriter.cc",
|
||||
"google/protobuf/util/internal/error_listener.cc",
|
||||
"google/protobuf/util/internal/field_mask_utility.cc",
|
||||
"google/protobuf/util/internal/json_escaping.cc",
|
||||
"google/protobuf/util/internal/json_objectwriter.cc",
|
||||
"google/protobuf/util/internal/json_stream_parser.cc",
|
||||
"google/protobuf/util/internal/object_writer.cc",
|
||||
"google/protobuf/util/internal/proto_writer.cc",
|
||||
"google/protobuf/util/internal/protostream_objectsource.cc",
|
||||
"google/protobuf/util/internal/protostream_objectwriter.cc",
|
||||
"google/protobuf/util/internal/type_info.cc",
|
||||
"google/protobuf/util/internal/utility.cc",
|
||||
"google/protobuf/util/json_util.cc",
|
||||
"google/protobuf/util/message_differencer.cc",
|
||||
"google/protobuf/util/time_util.cc",
|
||||
"google/protobuf/util/type_resolver_util.cc",
|
||||
"google/protobuf/wire_format.cc",
|
||||
"google/protobuf/wrappers.pb.cc",
|
||||
]
|
||||
)
|
||||
|
||||
protobuf_includes = set(
|
||||
[
|
||||
"google/protobuf/any.pb.h",
|
||||
"google/protobuf/api.pb.h",
|
||||
"google/protobuf/compiler/importer.h",
|
||||
"google/protobuf/compiler/parser.h",
|
||||
"google/protobuf/descriptor.h",
|
||||
"google/protobuf/descriptor.pb.h",
|
||||
"google/protobuf/descriptor_database.h",
|
||||
"google/protobuf/duration.pb.h",
|
||||
"google/protobuf/dynamic_message.h",
|
||||
"google/protobuf/empty.pb.h",
|
||||
"google/protobuf/field_access_listener.h",
|
||||
"google/protobuf/field_mask.pb.h",
|
||||
"google/protobuf/generated_enum_reflection.h",
|
||||
"google/protobuf/generated_message_bases.h",
|
||||
"google/protobuf/generated_message_reflection.h",
|
||||
"google/protobuf/io/gzip_stream.h",
|
||||
"google/protobuf/io/printer.h",
|
||||
"google/protobuf/io/tokenizer.h",
|
||||
"google/protobuf/map_entry.h",
|
||||
"google/protobuf/map_field.h",
|
||||
"google/protobuf/map_field_inl.h",
|
||||
"google/protobuf/message.h",
|
||||
"google/protobuf/metadata.h",
|
||||
"google/protobuf/reflection.h",
|
||||
"google/protobuf/reflection_internal.h",
|
||||
"google/protobuf/reflection_ops.h",
|
||||
"google/protobuf/service.h",
|
||||
"google/protobuf/source_context.pb.h",
|
||||
"google/protobuf/struct.pb.h",
|
||||
"google/protobuf/text_format.h",
|
||||
"google/protobuf/timestamp.pb.h",
|
||||
"google/protobuf/type.pb.h",
|
||||
"google/protobuf/unknown_field_set.h",
|
||||
"google/protobuf/util/delimited_message_util.h",
|
||||
"google/protobuf/util/field_comparator.h",
|
||||
"google/protobuf/util/field_mask_util.h",
|
||||
"google/protobuf/util/json_util.h",
|
||||
"google/protobuf/util/message_differencer.h",
|
||||
"google/protobuf/util/time_util.h",
|
||||
"google/protobuf/util/type_resolver.h",
|
||||
"google/protobuf/util/type_resolver_util.h",
|
||||
"google/protobuf/wire_format.h",
|
||||
"google/protobuf/wrappers.pb.h",
|
||||
]
|
||||
)
|
||||
|
||||
protobuf_internal_includes = set(
|
||||
[
|
||||
"google/protobuf/port_def.inc",
|
||||
"google/protobuf/port_undef.inc",
|
||||
"google/protobuf/stubs/int128.h",
|
||||
"google/protobuf/stubs/mathutil.h",
|
||||
"google/protobuf/stubs/statusor.h",
|
||||
"google/protobuf/stubs/status_macros.h",
|
||||
"google/protobuf/stubs/stringprintf.h",
|
||||
"google/protobuf/stubs/substitute.h",
|
||||
"google/protobuf/stubs/time.h",
|
||||
"google/protobuf/util/internal/constants.h",
|
||||
"google/protobuf/util/internal/datapiece.h",
|
||||
"google/protobuf/util/internal/default_value_objectwriter.h",
|
||||
"google/protobuf/util/internal/error_listener.h",
|
||||
"google/protobuf/util/internal/field_mask_utility.h",
|
||||
"google/protobuf/util/internal/json_escaping.h",
|
||||
"google/protobuf/util/internal/json_objectwriter.h",
|
||||
"google/protobuf/util/internal/json_stream_parser.h",
|
||||
"google/protobuf/util/internal/location_tracker.h",
|
||||
"google/protobuf/util/internal/object_location_tracker.h",
|
||||
"google/protobuf/util/internal/object_source.h",
|
||||
"google/protobuf/util/internal/object_writer.h",
|
||||
"google/protobuf/util/internal/proto_writer.h",
|
||||
"google/protobuf/util/internal/protostream_objectsource.h",
|
||||
"google/protobuf/util/internal/protostream_objectwriter.h",
|
||||
"google/protobuf/util/internal/structured_objectwriter.h",
|
||||
"google/protobuf/util/internal/type_info.h",
|
||||
"google/protobuf/util/internal/utility.h",
|
||||
]
|
||||
)
|
||||
|
||||
use_src_files = protobuf_lite_sources | protobuf_sources
|
||||
use_include_files = (
|
||||
protobuf_lite_includes | protobuf_includes | protobuf_internal_includes
|
||||
)
|
||||
|
||||
|
||||
def matches(dp, f, files):
|
||||
if not dp.startswith("./src/"):
|
||||
return False
|
||||
p = dp[6:] + "/" + f
|
||||
return p in files
|
||||
|
||||
|
||||
def main():
|
||||
upstream_root = clone_repo(
|
||||
"https://github.com/protocolbuffers/protobuf", "v3.21.12"
|
||||
)
|
||||
wpilib_root = get_repo_root()
|
||||
wpiutil = os.path.join(wpilib_root, "wpiutil")
|
||||
|
||||
# Apply patches to upstream Git repo
|
||||
os.chdir(upstream_root)
|
||||
for f in [
|
||||
"0001-Fix-sign-compare-warnings.patch",
|
||||
"0002-Remove-redundant-move.patch",
|
||||
"0003-Fix-maybe-uninitialized-warnings.patch",
|
||||
"0004-Fix-coded_stream-WriteRaw.patch",
|
||||
"0005-Suppress-enum-enum-conversion-warning.patch",
|
||||
"0006-Fix-noreturn-function-returning.patch",
|
||||
"0007-Work-around-GCC-12-restrict-warning-compiler-bug.patch",
|
||||
"0008-Disable-MSVC-switch-warning.patch",
|
||||
"0009-Disable-unused-function-warning.patch",
|
||||
"0010-Disable-pedantic-warning.patch",
|
||||
"0011-Avoid-use-of-sprintf.patch",
|
||||
]:
|
||||
git_am(os.path.join(wpilib_root, "upstream_utils/protobuf_patches", f))
|
||||
|
||||
# Delete old install
|
||||
for d in [
|
||||
"src/main/native/thirdparty/protobuf/src",
|
||||
"src/main/native/thirdparty/protobuf/include",
|
||||
]:
|
||||
shutil.rmtree(os.path.join(wpiutil, d), ignore_errors=True)
|
||||
|
||||
# Copy protobuf source files into allwpilib
|
||||
src_files = walk_if(".", lambda dp, f: matches(dp, f, use_src_files))
|
||||
src_files = [f[22:] for f in src_files]
|
||||
os.chdir(os.path.join(upstream_root, "src/google/protobuf"))
|
||||
copy_to(src_files, os.path.join(wpiutil, "src/main/native/thirdparty/protobuf/src"))
|
||||
|
||||
# Copy protobuf header files into allwpilib
|
||||
os.chdir(upstream_root)
|
||||
include_files = walk_if(".", lambda dp, f: matches(dp, f, use_include_files))
|
||||
include_files = [f[6:] for f in include_files]
|
||||
os.chdir(os.path.join(upstream_root, "src"))
|
||||
copy_to(
|
||||
include_files,
|
||||
os.path.join(wpiutil, "src/main/native/thirdparty/protobuf/include"),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -28,10 +28,11 @@ public final class Commands {
|
||||
/**
|
||||
* Constructs a command that does nothing until interrupted.
|
||||
*
|
||||
* @param requirements Subsystems to require
|
||||
* @return the command
|
||||
*/
|
||||
public static Command idle() {
|
||||
return run(() -> {});
|
||||
public static Command idle(Subsystem... requirements) {
|
||||
return run(() -> {}, requirements);
|
||||
}
|
||||
|
||||
// Action Commands
|
||||
|
||||
@@ -25,8 +25,8 @@ CommandPtr cmd::None() {
|
||||
return InstantCommand().ToPtr();
|
||||
}
|
||||
|
||||
CommandPtr cmd::Idle() {
|
||||
return Run([] {});
|
||||
CommandPtr cmd::Idle(Requirements requirements) {
|
||||
return Run([] {}, requirements);
|
||||
}
|
||||
|
||||
CommandPtr cmd::RunOnce(std::function<void()> action,
|
||||
|
||||
@@ -33,10 +33,11 @@ CommandPtr None();
|
||||
/**
|
||||
* Constructs a command that does nothing until interrupted.
|
||||
*
|
||||
* @param requirements Subsystems to require
|
||||
* @return the command
|
||||
*/
|
||||
[[nodiscard]]
|
||||
CommandPtr Idle();
|
||||
CommandPtr Idle(Requirements requirements = {});
|
||||
|
||||
// Action Commands
|
||||
|
||||
|
||||
@@ -260,6 +260,18 @@ int Counter::GetFPGAIndex() const {
|
||||
return m_index;
|
||||
}
|
||||
|
||||
void Counter::SetDistancePerPulse(double distancePerPulse) {
|
||||
m_distancePerPulse = distancePerPulse;
|
||||
}
|
||||
|
||||
double Counter::GetDistance() const {
|
||||
return Get() * m_distancePerPulse;
|
||||
}
|
||||
|
||||
double Counter::GetRate() const {
|
||||
return m_distancePerPulse / GetPeriod().value();
|
||||
}
|
||||
|
||||
int Counter::Get() const {
|
||||
int32_t status = 0;
|
||||
int value = HAL_GetCounter(m_counter, &status);
|
||||
|
||||
@@ -343,6 +343,34 @@ class Counter : public CounterBase,
|
||||
|
||||
int GetFPGAIndex() const;
|
||||
|
||||
/**
|
||||
* Set the distance per pulse for this counter. This sets the multiplier used
|
||||
* to determine the distance driven based on the count value from the encoder.
|
||||
* Set this value based on the Pulses per Revolution and factor in any gearing
|
||||
* reductions. This distance can be in any units you like, linear or angular.
|
||||
*
|
||||
* @param distancePerPulse The scale factor that will be used to convert
|
||||
* pulses to useful units.
|
||||
*/
|
||||
void SetDistancePerPulse(double distancePerPulse);
|
||||
|
||||
/**
|
||||
* Read the current scaled counter value. Read the value at this instant,
|
||||
* scaled by the distance per pulse (defaults to 1).
|
||||
*
|
||||
* @return The distance since the last reset
|
||||
*/
|
||||
double GetDistance() const;
|
||||
|
||||
/**
|
||||
* Get the current rate of the Counter. Read the current rate of the counter
|
||||
* accounting for the distance per pulse value. The default value for distance
|
||||
* per pulse (1) yields units of pulses per second.
|
||||
*
|
||||
* @return The rate in units/sec
|
||||
*/
|
||||
double GetRate() const;
|
||||
|
||||
// CounterBase interface
|
||||
/**
|
||||
* Read the current counter value.
|
||||
@@ -434,6 +462,7 @@ class Counter : public CounterBase,
|
||||
|
||||
private:
|
||||
int m_index = 0; // The index of this counter.
|
||||
double m_distancePerPulse = 1;
|
||||
|
||||
friend class DigitalGlitchFilter;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 <numbers>
|
||||
|
||||
#include <frc/Joystick.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
#include <frc/controller/SimpleMotorFeedforward.h>
|
||||
#include <frc/trajectory/ExponentialProfile.h>
|
||||
#include <units/acceleration.h>
|
||||
#include <units/length.h>
|
||||
#include <units/time.h>
|
||||
#include <units/velocity.h>
|
||||
#include <units/voltage.h>
|
||||
|
||||
#include "ExampleSmartMotorController.h"
|
||||
|
||||
class Robot : public frc::TimedRobot {
|
||||
public:
|
||||
static constexpr units::second_t kDt = 20_ms;
|
||||
|
||||
Robot() {
|
||||
// Note: These gains are fake, and will have to be tuned for your robot.
|
||||
m_motor.SetPID(1.3, 0.0, 0.7);
|
||||
}
|
||||
|
||||
void TeleopPeriodic() override {
|
||||
if (m_joystick.GetRawButtonPressed(2)) {
|
||||
m_goal = {5_m, 0_mps};
|
||||
} else if (m_joystick.GetRawButtonPressed(3)) {
|
||||
m_goal = {0_m, 0_mps};
|
||||
}
|
||||
|
||||
// Retrieve the profiled setpoint for the next timestep. This setpoint moves
|
||||
// toward the goal while obeying the constraints.
|
||||
auto next = m_profile.Calculate(kDt, m_goal, m_setpoint);
|
||||
|
||||
// Send setpoint to offboard controller PID
|
||||
m_motor.SetSetpoint(
|
||||
ExampleSmartMotorController::PIDMode::kPosition,
|
||||
m_setpoint.position.value(),
|
||||
m_feedforward.Calculate(m_setpoint.velocity, next.velocity, kDt) /
|
||||
12_V);
|
||||
|
||||
m_setpoint = next;
|
||||
}
|
||||
|
||||
private:
|
||||
frc::Joystick m_joystick{1};
|
||||
ExampleSmartMotorController m_motor{1};
|
||||
frc::SimpleMotorFeedforward<units::meters> m_feedforward{
|
||||
// Note: These gains are fake, and will have to be tuned for your robot.
|
||||
1_V, 1_V / 1_mps, 1_V / 1_mps_sq};
|
||||
|
||||
// Create a motion profile with the given maximum velocity and maximum
|
||||
// acceleration constraints for the next setpoint.
|
||||
frc::ExponentialProfile<units::meters, units::volts> m_profile{
|
||||
{10_V, 1_V / 1_mps, 1_V / 1_mps_sq}};
|
||||
frc::ExponentialProfile<units::meters, units::volts>::State m_goal;
|
||||
frc::ExponentialProfile<units::meters, units::volts>::State m_setpoint;
|
||||
};
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() {
|
||||
return frc::StartRobot<Robot>();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 <frc/motorcontrol/MotorController.h>
|
||||
|
||||
/**
|
||||
* A simplified stub class that simulates the API of a common "smart" motor
|
||||
* controller.
|
||||
*
|
||||
* <p>Has no actual functionality.
|
||||
*/
|
||||
class ExampleSmartMotorController : public frc::MotorController {
|
||||
public:
|
||||
enum PIDMode { kPosition, kVelocity, kMovementWitchcraft };
|
||||
|
||||
/**
|
||||
* Creates a new ExampleSmartMotorController.
|
||||
*
|
||||
* @param port The port for the controller.
|
||||
*/
|
||||
explicit ExampleSmartMotorController(int port) {}
|
||||
|
||||
/**
|
||||
* Example method for setting the PID gains of the smart controller.
|
||||
*
|
||||
* @param kp The proportional gain.
|
||||
* @param ki The integral gain.
|
||||
* @param kd The derivative gain.
|
||||
*/
|
||||
void SetPID(double kp, double ki, double kd) {}
|
||||
|
||||
/**
|
||||
* Example method for setting the setpoint of the smart controller in PID
|
||||
* mode.
|
||||
*
|
||||
* @param mode The mode of the PID controller.
|
||||
* @param setpoint The controller setpoint.
|
||||
* @param arbFeedforward An arbitrary feedforward output (from -1 to 1).
|
||||
*/
|
||||
void SetSetpoint(PIDMode mode, double setpoint, double arbFeedforward) {}
|
||||
|
||||
/**
|
||||
* Places this motor controller in follower mode.
|
||||
*
|
||||
* @param leader The leader to follow.
|
||||
*/
|
||||
void Follow(ExampleSmartMotorController leader) {}
|
||||
|
||||
/**
|
||||
* Returns the encoder distance.
|
||||
*
|
||||
* @return The current encoder distance.
|
||||
*/
|
||||
double GetEncoderDistance() { return 0; }
|
||||
|
||||
/**
|
||||
* Returns the encoder rate.
|
||||
*
|
||||
* @return The current encoder rate.
|
||||
*/
|
||||
double GetEncoderRate() { return 0; }
|
||||
|
||||
/**
|
||||
* Resets the encoder to zero distance.
|
||||
*/
|
||||
void ResetEncoder() {}
|
||||
|
||||
void Set(double speed) override {}
|
||||
|
||||
double Get() const override { return 0; }
|
||||
|
||||
void SetInverted(bool isInverted) override {}
|
||||
|
||||
bool GetInverted() const override { return false; }
|
||||
|
||||
void Disable() override {}
|
||||
|
||||
void StopMotor() override {}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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 "Robot.h"
|
||||
|
||||
#include "Constants.h"
|
||||
|
||||
void Robot::RobotPeriodic() {
|
||||
// Update the telemetry, including mechanism visualization, regardless of
|
||||
// mode.
|
||||
m_elevator.UpdateTelemetry();
|
||||
}
|
||||
|
||||
void Robot::SimulationPeriodic() {
|
||||
// Update the simulation model.
|
||||
m_elevator.SimulationPeriodic();
|
||||
}
|
||||
|
||||
void Robot::TeleopInit() {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_elevator.Reset();
|
||||
}
|
||||
|
||||
void Robot::TeleopPeriodic() {
|
||||
if (m_joystick.GetTrigger()) {
|
||||
// Here, we set the constant setpoint of 0.75 meters.
|
||||
m_elevator.ReachGoal(Constants::kSetpoint);
|
||||
} else {
|
||||
// Otherwise, we update the setpoint to 0.
|
||||
m_elevator.ReachGoal(Constants::kLowerSetpoint);
|
||||
}
|
||||
}
|
||||
|
||||
void Robot::DisabledInit() {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_elevator.Stop();
|
||||
}
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() {
|
||||
return frc::StartRobot<Robot>();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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 "subsystems/Elevator.h"
|
||||
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/StateSpaceUtil.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
|
||||
Elevator::Elevator() {
|
||||
m_encoder.SetDistancePerPulse(Constants::kArmEncoderDistPerPulse);
|
||||
|
||||
// Put Mechanism 2d to SmartDashboard
|
||||
// To view the Elevator visualization, select Network Tables -> SmartDashboard
|
||||
// -> Elevator Sim
|
||||
frc::SmartDashboard::PutData("Elevator Sim", &m_mech2d);
|
||||
}
|
||||
|
||||
void Elevator::SimulationPeriodic() {
|
||||
// In this method, we update our simulation of what our elevator is doing
|
||||
// First, we set our "inputs" (voltages)
|
||||
m_elevatorSim.SetInput(frc::Vectord<1>{
|
||||
m_motorSim.GetSpeed() * frc::RobotController::GetInputVoltage()});
|
||||
|
||||
// Next, we update it. The standard loop time is 20ms.
|
||||
m_elevatorSim.Update(20_ms);
|
||||
|
||||
// Finally, we set our simulated encoder's readings and simulated battery
|
||||
// voltage
|
||||
m_encoderSim.SetDistance(m_elevatorSim.GetPosition().value());
|
||||
// SimBattery estimates loaded battery voltages
|
||||
frc::sim::RoboRioSim::SetVInVoltage(
|
||||
frc::sim::BatterySim::Calculate({m_elevatorSim.GetCurrentDraw()}));
|
||||
}
|
||||
|
||||
void Elevator::UpdateTelemetry() {
|
||||
// Update the Elevator length based on the simulated elevator height
|
||||
m_elevatorMech2d->SetLength(m_encoder.GetDistance());
|
||||
}
|
||||
|
||||
void Elevator::ReachGoal(units::meter_t goal) {
|
||||
frc::ExponentialProfile<units::meters, units::volts>::State goalState{goal,
|
||||
0_mps};
|
||||
|
||||
auto next = m_profile.Calculate(20_ms, m_setpoint, goalState);
|
||||
|
||||
auto pidOutput = m_controller.Calculate(m_encoder.GetDistance(),
|
||||
m_setpoint.position / 1_m);
|
||||
auto feedforwardOutput =
|
||||
m_feedforward.Calculate(m_setpoint.velocity, next.velocity, 20_ms);
|
||||
|
||||
m_motor.SetVoltage(units::volt_t{pidOutput} + feedforwardOutput);
|
||||
|
||||
m_setpoint = next;
|
||||
}
|
||||
|
||||
void Elevator::Reset() {
|
||||
m_setpoint = {m_encoder.GetDistance() * 1_m, 0_mps};
|
||||
}
|
||||
|
||||
void Elevator::Stop() {
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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 <numbers>
|
||||
|
||||
#include <units/acceleration.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/mass.h>
|
||||
#include <units/time.h>
|
||||
#include <units/velocity.h>
|
||||
#include <units/voltage.h>
|
||||
|
||||
/**
|
||||
* The Constants header provides a convenient place for teams to hold robot-wide
|
||||
* numerical or bool constants. This should not be used for any other purpose.
|
||||
*
|
||||
* It is generally a good idea to place constants into subsystem- or
|
||||
* command-specific namespaces within this header, which can then be used where
|
||||
* they are needed.
|
||||
*/
|
||||
|
||||
namespace Constants {
|
||||
|
||||
static constexpr int kMotorPort = 0;
|
||||
static constexpr int kEncoderAChannel = 0;
|
||||
static constexpr int kEncoderBChannel = 1;
|
||||
static constexpr int kJoystickPort = 0;
|
||||
|
||||
static constexpr double kElevatorKp = 0.75;
|
||||
static constexpr double kElevatorKi = 0.0;
|
||||
static constexpr double kElevatorKd = 0.0;
|
||||
|
||||
static constexpr units::volt_t kElevatorMaxV = 10_V;
|
||||
static constexpr units::volt_t kElevatorkS = 0.0_V;
|
||||
static constexpr units::volt_t kElevatorkG = 0.62_V;
|
||||
static constexpr auto kElevatorkV = 3.9_V / 1_mps;
|
||||
static constexpr auto kElevatorkA = 0.06_V / 1_mps_sq;
|
||||
|
||||
static constexpr double kElevatorGearing = 5.0;
|
||||
static constexpr units::meter_t kElevatorDrumRadius = 1_in;
|
||||
static constexpr units::kilogram_t kCarriageMass = 12_lb;
|
||||
|
||||
static constexpr units::meter_t kSetpoint = 42.875_in;
|
||||
static constexpr units::meter_t kLowerSetpoint = 15_in;
|
||||
static constexpr units::meter_t kMinElevatorHeight = 0_cm;
|
||||
static constexpr units::meter_t kMaxElevatorHeight = 50_in;
|
||||
|
||||
// distance per pulse = (distance per revolution) / (pulses per revolution)
|
||||
// = (Pi * D) / ppr
|
||||
static constexpr double kArmEncoderDistPerPulse =
|
||||
2.0 * std::numbers::pi * kElevatorDrumRadius.value() / 4096.0;
|
||||
|
||||
} // namespace Constants
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 <frc/Joystick.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
|
||||
#include "subsystems/Elevator.h"
|
||||
|
||||
/**
|
||||
* This is a sample program to demonstrate the use of elevator simulation.
|
||||
*/
|
||||
class Robot : public frc::TimedRobot {
|
||||
public:
|
||||
void RobotInit() override {}
|
||||
void RobotPeriodic() override;
|
||||
void SimulationPeriodic() override;
|
||||
void TeleopInit() override;
|
||||
void TeleopPeriodic() override;
|
||||
void DisabledInit() override;
|
||||
|
||||
private:
|
||||
frc::Joystick m_joystick{Constants::kJoystickPort};
|
||||
Elevator m_elevator;
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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 <frc/Encoder.h>
|
||||
#include <frc/controller/ElevatorFeedforward.h>
|
||||
#include <frc/controller/PIDController.h>
|
||||
#include <frc/motorcontrol/PWMSparkMax.h>
|
||||
#include <frc/simulation/BatterySim.h>
|
||||
#include <frc/simulation/ElevatorSim.h>
|
||||
#include <frc/simulation/EncoderSim.h>
|
||||
#include <frc/simulation/PWMSim.h>
|
||||
#include <frc/simulation/RoboRioSim.h>
|
||||
#include <frc/smartdashboard/Mechanism2d.h>
|
||||
#include <frc/smartdashboard/MechanismLigament2d.h>
|
||||
#include <frc/smartdashboard/MechanismRoot2d.h>
|
||||
#include <frc/trajectory/ExponentialProfile.h>
|
||||
#include <units/length.h>
|
||||
|
||||
#include "Constants.h"
|
||||
|
||||
class Elevator {
|
||||
public:
|
||||
Elevator();
|
||||
void SimulationPeriodic();
|
||||
void UpdateTelemetry();
|
||||
void ReachGoal(units::meter_t goal);
|
||||
void Reset();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
// This gearbox represents a gearbox containing 4 Vex 775pro motors.
|
||||
frc::DCMotor m_elevatorGearbox = frc::DCMotor::NEO(2);
|
||||
|
||||
// Standard classes for controlling our elevator
|
||||
frc::ExponentialProfile<units::meters, units::volts>::Constraints
|
||||
m_constraints{Constants::kElevatorMaxV, Constants::kElevatorkV,
|
||||
Constants::kElevatorkA};
|
||||
frc::ExponentialProfile<units::meters, units::volts> m_profile{m_constraints};
|
||||
frc::ExponentialProfile<units::meters, units::volts>::State m_setpoint;
|
||||
|
||||
frc::PIDController m_controller{
|
||||
Constants::kElevatorKp, Constants::kElevatorKi, Constants::kElevatorKd};
|
||||
|
||||
frc::ElevatorFeedforward m_feedforward{
|
||||
Constants::kElevatorkS, Constants::kElevatorkG, Constants::kElevatorkV,
|
||||
Constants::kElevatorkA};
|
||||
frc::Encoder m_encoder{Constants::kEncoderAChannel,
|
||||
Constants::kEncoderBChannel};
|
||||
frc::PWMSparkMax m_motor{Constants::kMotorPort};
|
||||
frc::sim::PWMSim m_motorSim{m_motor};
|
||||
|
||||
// Simulation classes help us simulate what's going on, including gravity.
|
||||
frc::sim::ElevatorSim m_elevatorSim{m_elevatorGearbox,
|
||||
Constants::kElevatorGearing,
|
||||
Constants::kCarriageMass,
|
||||
Constants::kElevatorDrumRadius,
|
||||
Constants::kMinElevatorHeight,
|
||||
Constants::kMaxElevatorHeight,
|
||||
true,
|
||||
0_m,
|
||||
{0.005}};
|
||||
frc::sim::EncoderSim m_encoderSim{m_encoder};
|
||||
|
||||
// Create a Mechanism2d display of an elevator
|
||||
frc::Mechanism2d m_mech2d{10_in / 1_m, 51_in / 1_m};
|
||||
frc::MechanismRoot2d* m_elevatorRoot =
|
||||
m_mech2d.GetRoot("Elevator Root", 5_in / 1_m, 0.5_in / 1_m);
|
||||
frc::MechanismLigament2d* m_elevatorMech2d =
|
||||
m_elevatorRoot->Append<frc::MechanismLigament2d>(
|
||||
"Elevator", m_elevatorSim.GetPosition().value(), 90_deg);
|
||||
};
|
||||
@@ -48,9 +48,17 @@ frc::SwerveModulePosition SwerveModule::GetPosition() const {
|
||||
|
||||
void SwerveModule::SetDesiredState(
|
||||
const frc::SwerveModuleState& referenceState) {
|
||||
frc::Rotation2d encoderRotation{
|
||||
units::radian_t{m_turningEncoder.GetDistance()}};
|
||||
|
||||
// Optimize the reference state to avoid spinning further than 90 degrees
|
||||
const auto state = frc::SwerveModuleState::Optimize(
|
||||
referenceState, units::radian_t{m_turningEncoder.GetDistance()});
|
||||
auto state =
|
||||
frc::SwerveModuleState::Optimize(referenceState, encoderRotation);
|
||||
|
||||
// Scale speed by cosine of angle error. This scales down movement
|
||||
// perpendicular to the desired direction of travel that can occur when
|
||||
// modules change directions. This results in smoother driving.
|
||||
state.speed *= (state.angle - encoderRotation).Cos();
|
||||
|
||||
// Calculate the drive output from the drive PID controller.
|
||||
const auto driveOutput = m_drivePIDController.Calculate(
|
||||
|
||||
@@ -55,9 +55,17 @@ frc::SwerveModulePosition SwerveModule::GetPosition() {
|
||||
|
||||
void SwerveModule::SetDesiredState(
|
||||
const frc::SwerveModuleState& referenceState) {
|
||||
frc::Rotation2d encoderRotation{
|
||||
units::radian_t{m_turningEncoder.GetDistance()}};
|
||||
|
||||
// Optimize the reference state to avoid spinning further than 90 degrees
|
||||
const auto state = frc::SwerveModuleState::Optimize(
|
||||
referenceState, units::radian_t{m_turningEncoder.GetDistance()});
|
||||
auto state =
|
||||
frc::SwerveModuleState::Optimize(referenceState, encoderRotation);
|
||||
|
||||
// Scale speed by cosine of angle error. This scales down movement
|
||||
// perpendicular to the desired direction of travel that can occur when
|
||||
// modules change directions. This results in smoother driving.
|
||||
state.speed *= (state.angle - encoderRotation).Cos();
|
||||
|
||||
// Calculate the drive output from the drive PID controller.
|
||||
const auto driveOutput = m_drivePIDController.Calculate(
|
||||
|
||||
@@ -48,9 +48,17 @@ frc::SwerveModulePosition SwerveModule::GetPosition() const {
|
||||
|
||||
void SwerveModule::SetDesiredState(
|
||||
const frc::SwerveModuleState& referenceState) {
|
||||
frc::Rotation2d encoderRotation{
|
||||
units::radian_t{m_turningEncoder.GetDistance()}};
|
||||
|
||||
// Optimize the reference state to avoid spinning further than 90 degrees
|
||||
const auto state = frc::SwerveModuleState::Optimize(
|
||||
referenceState, units::radian_t{m_turningEncoder.GetDistance()});
|
||||
auto state =
|
||||
frc::SwerveModuleState::Optimize(referenceState, encoderRotation);
|
||||
|
||||
// Scale speed by cosine of angle error. This scales down movement
|
||||
// perpendicular to the desired direction of travel that can occur when
|
||||
// modules change directions. This results in smoother driving.
|
||||
state.speed *= (state.angle - encoderRotation).Cos();
|
||||
|
||||
// Calculate the drive output from the drive PID controller.
|
||||
const auto driveOutput = m_drivePIDController.Calculate(
|
||||
|
||||
@@ -205,6 +205,20 @@
|
||||
"gradlebase": "cpp",
|
||||
"commandversion": 2
|
||||
},
|
||||
{
|
||||
"name": "Elevator with exponential profiled PID",
|
||||
"description": "Reach elevator position setpoints with exponential profiles and smart motor controller PID.",
|
||||
"tags": [
|
||||
"Basic Robot",
|
||||
"Elevator",
|
||||
"Exponential Profile",
|
||||
"Smart Motor Controller",
|
||||
"Joystick"
|
||||
],
|
||||
"foldername": "ElevatorExponentialProfile",
|
||||
"gradlebase": "cpp",
|
||||
"commandversion": 2
|
||||
},
|
||||
{
|
||||
"name": "Elevator with trapezoid profiled PID",
|
||||
"description": "Reach elevator position setpoints with trapezoid profiles and smart motor controller PID.",
|
||||
@@ -784,6 +798,21 @@
|
||||
"gradlebase": "cpp",
|
||||
"commandversion": 2
|
||||
},
|
||||
{
|
||||
"name": "Elevator Exponential Profile Simulation",
|
||||
"description": "Simulate an elevator.",
|
||||
"tags": [
|
||||
"Basic Robot",
|
||||
"Elevator",
|
||||
"State-Space",
|
||||
"Simulation",
|
||||
"Mechanism2d",
|
||||
"Profiled PID"
|
||||
],
|
||||
"foldername": "ElevatorExponentialSimulation",
|
||||
"gradlebase": "cpp",
|
||||
"commandversion": 2
|
||||
},
|
||||
{
|
||||
"name": "DifferentialDrivePoseEstimator",
|
||||
"description": "Combine differential-drive odometry with vision data using DifferentialDrivePoseEstimator.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user