mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
[wpiutil] Change Struct to allow non-constexpr implementation (#5992)
This required changing the constant values (e.g. kSize) into functions (e.g. GetSize()). Fixed implementations of ForEachNested to be inline (as these are actually templates). Also added a ntcore Struct test.
This commit is contained in:
@@ -7,9 +7,9 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -121,16 +121,17 @@ class StructArraySubscriber : public Subscriber {
|
||||
#endif
|
||||
TimestampedValueType GetAtomic(U&& defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
size_t size = S::GetSize();
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() == 0 || (view.value.size() % S::kSize) != 0) {
|
||||
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
|
||||
return {0, 0, std::forward<U>(defaultValue)};
|
||||
}
|
||||
TimestampedValueType rv{view.time, view.serverTime, {}};
|
||||
rv.value.reserve(view.value.size() / S::kSize);
|
||||
rv.value.reserve(view.value.size() / size);
|
||||
for (auto in = view.value.begin(), end = view.value.end(); in != end;
|
||||
in += S::kSize) {
|
||||
in += size) {
|
||||
rv.value.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -145,16 +146,17 @@ class StructArraySubscriber : public Subscriber {
|
||||
*/
|
||||
TimestampedValueType GetAtomic(std::span<const T> defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
size_t size = S::GetSize();
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() == 0 || (view.value.size() % S::kSize) != 0) {
|
||||
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
|
||||
return {0, 0, {defaultValue.begin(), defaultValue.end()}};
|
||||
}
|
||||
TimestampedValueType rv{view.time, view.serverTime, {}};
|
||||
rv.value.reserve(view.value.size() / S::kSize);
|
||||
rv.value.reserve(view.value.size() / size);
|
||||
for (auto in = view.value.begin(), end = view.value.end(); in != end;
|
||||
in += S::kSize) {
|
||||
in += size) {
|
||||
rv.value.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -174,16 +176,17 @@ class StructArraySubscriber : public Subscriber {
|
||||
auto raw = ::nt::ReadQueueRaw(m_subHandle);
|
||||
std::vector<TimestampedValueType> rv;
|
||||
rv.reserve(raw.size());
|
||||
size_t size = S::GetSize();
|
||||
for (auto&& r : raw) {
|
||||
if (r.value.size() == 0 || (r.value.size() % S::kSize) != 0) {
|
||||
if (r.value.size() == 0 || (r.value.size() % size) != 0) {
|
||||
continue;
|
||||
}
|
||||
std::vector<T> values;
|
||||
values.reserve(r.value.size() / S::kSize);
|
||||
values.reserve(r.value.size() / size);
|
||||
for (auto in = r.value.begin(), end = r.value.end(); in != end;
|
||||
in += S::kSize) {
|
||||
in += size) {
|
||||
values.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t, S::kSize>{in, in + S::kSize}));
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
}
|
||||
rv.emplace_back(r.time, r.serverTime, std::move(values));
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ class StructTopic;
|
||||
template <wpi::StructSerializable T>
|
||||
class StructSubscriber : public Subscriber {
|
||||
using S = wpi::Struct<T>;
|
||||
static constexpr size_t kBufSize = []() -> size_t {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
return S::GetSize();
|
||||
} else {
|
||||
return 128;
|
||||
}
|
||||
}();
|
||||
|
||||
public:
|
||||
using TopicType = StructTopic<T>;
|
||||
@@ -81,12 +88,12 @@ class StructSubscriber : public Subscriber {
|
||||
* @return true if successful
|
||||
*/
|
||||
bool GetInto(T* out) {
|
||||
wpi::SmallVector<uint8_t, S::kSize> buf;
|
||||
wpi::SmallVector<uint8_t, kBufSize> buf;
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() < S::kSize) {
|
||||
if (view.value.size() < S::GetSize()) {
|
||||
return false;
|
||||
} else {
|
||||
wpi::UnpackStructInto(out, view.value.subspan<0, S::kSize>());
|
||||
wpi::UnpackStructInto(out, view.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -109,13 +116,12 @@ class StructSubscriber : public Subscriber {
|
||||
* @return timestamped value
|
||||
*/
|
||||
TimestampedValueType GetAtomic(const T& defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, S::kSize> buf;
|
||||
wpi::SmallVector<uint8_t, kBufSize> buf;
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() < S::kSize) {
|
||||
if (view.value.size() < S::GetSize()) {
|
||||
return {0, 0, defaultValue};
|
||||
} else {
|
||||
return {view.time, view.serverTime,
|
||||
S::Unpack(view.value.subspan<0, S::kSize>())};
|
||||
return {view.time, view.serverTime, S::Unpack(view.value)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +141,11 @@ class StructSubscriber : public Subscriber {
|
||||
std::vector<TimestampedValueType> rv;
|
||||
rv.reserve(raw.size());
|
||||
for (auto&& r : raw) {
|
||||
if (r.value.size() < S::kSize) {
|
||||
if (r.value.size() < S::GetSize()) {
|
||||
continue;
|
||||
} else {
|
||||
rv.emplace_back(
|
||||
r.time, r.serverTime,
|
||||
S::Unpack(
|
||||
std::span<const uint8_t>(r.value).subspan<0, S::kSize>()));
|
||||
rv.emplace_back(r.time, r.serverTime,
|
||||
S::Unpack(std::span<const uint8_t>(r.value)));
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
@@ -208,9 +212,16 @@ class StructPublisher : public Publisher {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
}
|
||||
uint8_t buf[S::kSize];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetRaw(m_pubHandle, buf, time);
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetRaw(m_pubHandle, buf, time);
|
||||
} else {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(S::GetSize());
|
||||
S::Pack(buf, value);
|
||||
::nt::SetRaw(m_pubHandle, buf, time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,9 +235,16 @@ class StructPublisher : public Publisher {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
}
|
||||
uint8_t buf[S::kSize];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetDefaultRaw(m_pubHandle, buf);
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetDefaultRaw(m_pubHandle, buf);
|
||||
} else {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(S::GetSize());
|
||||
S::Pack(buf, value);
|
||||
::nt::SetDefaultRaw(m_pubHandle, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
296
ntcore/src/test/native/cpp/StructTest.cpp
Normal file
296
ntcore/src/test/native/cpp/StructTest.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
#include <wpi/SpanMatcher.h>
|
||||
#include <wpi/struct/Struct.h>
|
||||
|
||||
#include "networktables/NetworkTableInstance.h"
|
||||
#include "networktables/StructArrayTopic.h"
|
||||
#include "networktables/StructTopic.h"
|
||||
|
||||
namespace {
|
||||
struct Inner {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
struct Outer {
|
||||
Inner inner;
|
||||
int c;
|
||||
};
|
||||
|
||||
struct Inner2 {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
struct Outer2 {
|
||||
Inner2 inner;
|
||||
int c;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<Inner> {
|
||||
static constexpr std::string_view GetTypeString() { return "struct:Inner"; }
|
||||
static constexpr size_t GetSize() { return 8; }
|
||||
static constexpr std::string_view GetSchema() { return "int32 a; int32 b"; }
|
||||
|
||||
static Inner Unpack(std::span<const uint8_t> data) {
|
||||
return {wpi::UnpackStruct<int32_t, 0>(data),
|
||||
wpi::UnpackStruct<int32_t, 4>(data)};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const Inner& value) {
|
||||
wpi::PackStruct<0>(data, value.a);
|
||||
wpi::PackStruct<4>(data, value.b);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<Outer> {
|
||||
static constexpr std::string_view GetTypeString() { return "struct:Outer"; }
|
||||
static constexpr size_t GetSize() { return wpi::GetStructSize<Inner>() + 4; }
|
||||
static constexpr std::string_view GetSchema() {
|
||||
return "Inner inner; int32 c";
|
||||
}
|
||||
|
||||
static Outer Unpack(std::span<const uint8_t> data) {
|
||||
constexpr size_t innerSize = wpi::GetStructSize<Inner>();
|
||||
return {wpi::UnpackStruct<Inner, 0>(data),
|
||||
wpi::UnpackStruct<int32_t, innerSize>(data)};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const Outer& value) {
|
||||
constexpr size_t innerSize = wpi::GetStructSize<Inner>();
|
||||
wpi::PackStruct<0>(data, value.inner);
|
||||
wpi::PackStruct<innerSize>(data, value.c);
|
||||
}
|
||||
static void ForEachNested(
|
||||
std::invocable<std::string_view, std::string_view> auto fn) {
|
||||
wpi::ForEachStructSchema<Inner>(fn);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<Inner2> {
|
||||
static std::string_view GetTypeString() { return "struct:Inner2"; }
|
||||
static size_t GetSize() { return 8; }
|
||||
static std::string_view GetSchema() { return "int32 a; int32 b"; }
|
||||
|
||||
static Inner2 Unpack(std::span<const uint8_t> data) {
|
||||
return {wpi::UnpackStruct<int32_t, 0>(data),
|
||||
wpi::UnpackStruct<int32_t, 4>(data)};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const Inner2& value) {
|
||||
wpi::PackStruct<0>(data, value.a);
|
||||
wpi::PackStruct<4>(data, value.b);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<Outer2> {
|
||||
static std::string_view GetTypeString() { return "struct:Outer2"; }
|
||||
static size_t GetSize() { return wpi::GetStructSize<Inner>() + 4; }
|
||||
static std::string_view GetSchema() { return "Inner2 inner; int32 c"; }
|
||||
|
||||
static Outer2 Unpack(std::span<const uint8_t> data) {
|
||||
size_t innerSize = wpi::GetStructSize<Inner2>();
|
||||
return {wpi::UnpackStruct<Inner2, 0>(data),
|
||||
wpi::UnpackStruct<int32_t>(data.subspan(innerSize))};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const Outer2& value) {
|
||||
size_t innerSize = wpi::GetStructSize<Inner2>();
|
||||
wpi::PackStruct<0>(data, value.inner);
|
||||
wpi::PackStruct(data.subspan(innerSize), value.c);
|
||||
}
|
||||
static void ForEachNested(
|
||||
std::invocable<std::string_view, std::string_view> auto fn) {
|
||||
wpi::ForEachStructSchema<Inner2>(fn);
|
||||
}
|
||||
};
|
||||
|
||||
namespace nt {
|
||||
|
||||
class StructTest : public ::testing::Test {
|
||||
public:
|
||||
StructTest() { inst = nt::NetworkTableInstance::Create(); }
|
||||
~StructTest() { nt::NetworkTableInstance::Destroy(inst); }
|
||||
|
||||
nt::NetworkTableInstance inst;
|
||||
};
|
||||
|
||||
TEST_F(StructTest, InnerConstexpr) {
|
||||
nt::StructTopic<Inner> topic = inst.GetStructTopic<Inner>("inner");
|
||||
nt::StructPublisher<Inner> pub = topic.Publish();
|
||||
nt::StructSubscriber<Inner> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Inner");
|
||||
|
||||
pub.SetDefault({0, 1});
|
||||
Inner val = sub.Get();
|
||||
ASSERT_EQ(val.a, 0);
|
||||
ASSERT_EQ(val.b, 1);
|
||||
|
||||
pub.Set({1, 2});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.a, 1);
|
||||
ASSERT_EQ(atomicVal.value.b, 2);
|
||||
|
||||
Inner val2;
|
||||
sub.GetInto(&val2);
|
||||
ASSERT_EQ(val2.a, 1);
|
||||
ASSERT_EQ(val2.b, 2);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.a, 1);
|
||||
ASSERT_EQ(vals[0].value.b, 2);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, InnerNonconstexpr) {
|
||||
nt::StructTopic<Inner2> topic = inst.GetStructTopic<Inner2>("inner2");
|
||||
nt::StructPublisher<Inner2> pub = topic.Publish();
|
||||
nt::StructSubscriber<Inner2> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Inner2");
|
||||
|
||||
pub.SetDefault({0, 1});
|
||||
Inner2 val = sub.Get();
|
||||
ASSERT_EQ(val.a, 0);
|
||||
ASSERT_EQ(val.b, 1);
|
||||
|
||||
pub.Set({1, 2});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.a, 1);
|
||||
ASSERT_EQ(atomicVal.value.b, 2);
|
||||
|
||||
Inner2 val2;
|
||||
sub.GetInto(&val2);
|
||||
ASSERT_EQ(val2.a, 1);
|
||||
ASSERT_EQ(val2.b, 2);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.a, 1);
|
||||
ASSERT_EQ(vals[0].value.b, 2);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, OuterConstexpr) {
|
||||
nt::StructTopic<Outer> topic = inst.GetStructTopic<Outer>("outer");
|
||||
nt::StructPublisher<Outer> pub = topic.Publish();
|
||||
nt::StructSubscriber<Outer> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Outer");
|
||||
|
||||
pub.SetDefault({{0, 1}, 2});
|
||||
Outer val = sub.Get();
|
||||
ASSERT_EQ(val.inner.a, 0);
|
||||
ASSERT_EQ(val.inner.b, 1);
|
||||
ASSERT_EQ(val.c, 2);
|
||||
|
||||
pub.Set({{1, 2}, 3});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.inner.a, 1);
|
||||
ASSERT_EQ(atomicVal.value.inner.b, 2);
|
||||
ASSERT_EQ(atomicVal.value.c, 3);
|
||||
|
||||
Outer val2;
|
||||
sub.GetInto(&val2);
|
||||
ASSERT_EQ(val2.inner.a, 1);
|
||||
ASSERT_EQ(val2.inner.b, 2);
|
||||
ASSERT_EQ(val2.c, 3);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.inner.a, 1);
|
||||
ASSERT_EQ(vals[0].value.inner.b, 2);
|
||||
ASSERT_EQ(vals[0].value.c, 3);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, OuterNonconstexpr) {
|
||||
nt::StructTopic<Outer2> topic = inst.GetStructTopic<Outer2>("outer2");
|
||||
nt::StructPublisher<Outer2> pub = topic.Publish();
|
||||
nt::StructSubscriber<Outer2> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Outer2");
|
||||
|
||||
pub.SetDefault({{0, 1}, 2});
|
||||
Outer2 val = sub.Get();
|
||||
ASSERT_EQ(val.inner.a, 0);
|
||||
ASSERT_EQ(val.inner.b, 1);
|
||||
ASSERT_EQ(val.c, 2);
|
||||
|
||||
pub.Set({{1, 2}, 3});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.inner.a, 1);
|
||||
ASSERT_EQ(atomicVal.value.inner.b, 2);
|
||||
ASSERT_EQ(atomicVal.value.c, 3);
|
||||
|
||||
Outer2 val2;
|
||||
sub.GetInto(&val2);
|
||||
ASSERT_EQ(val2.inner.a, 1);
|
||||
ASSERT_EQ(val2.inner.b, 2);
|
||||
ASSERT_EQ(val2.c, 3);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.inner.a, 1);
|
||||
ASSERT_EQ(vals[0].value.inner.b, 2);
|
||||
ASSERT_EQ(vals[0].value.c, 3);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, InnerArrayConstexpr) {
|
||||
nt::StructArrayTopic<Inner> topic = inst.GetStructArrayTopic<Inner>("innerA");
|
||||
nt::StructArrayPublisher<Inner> pub = topic.Publish();
|
||||
nt::StructArraySubscriber<Inner> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Inner[]");
|
||||
|
||||
pub.SetDefault({{{0, 1}}});
|
||||
auto val = sub.Get();
|
||||
ASSERT_EQ(val.size(), 1u);
|
||||
ASSERT_EQ(val[0].a, 0);
|
||||
ASSERT_EQ(val[0].b, 1);
|
||||
|
||||
pub.Set({{{1, 2}}});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.size(), 1u);
|
||||
ASSERT_EQ(atomicVal.value[0].a, 1);
|
||||
ASSERT_EQ(atomicVal.value[0].b, 2);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value[0].a, 1);
|
||||
ASSERT_EQ(vals[0].value[0].b, 2);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, InnerArrayNonconstexpr) {
|
||||
nt::StructArrayTopic<Inner2> topic =
|
||||
inst.GetStructArrayTopic<Inner2>("innerA2");
|
||||
nt::StructArrayPublisher<Inner2> pub = topic.Publish();
|
||||
nt::StructArraySubscriber<Inner2> sub = topic.Subscribe({});
|
||||
|
||||
ASSERT_EQ(topic.GetTypeString(), "struct:Inner2[]");
|
||||
|
||||
pub.SetDefault({{{0, 1}}});
|
||||
auto val = sub.Get();
|
||||
ASSERT_EQ(val.size(), 1u);
|
||||
ASSERT_EQ(val[0].a, 0);
|
||||
ASSERT_EQ(val[0].b, 1);
|
||||
|
||||
pub.Set({{{1, 2}}});
|
||||
auto atomicVal = sub.GetAtomic();
|
||||
ASSERT_EQ(atomicVal.value.size(), 1u);
|
||||
ASSERT_EQ(atomicVal.value[0].a, 1);
|
||||
ASSERT_EQ(atomicVal.value[0].b, 2);
|
||||
|
||||
auto vals = sub.ReadQueue();
|
||||
ASSERT_EQ(vals.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value.size(), 1u);
|
||||
ASSERT_EQ(vals[0].value[0].a, 1);
|
||||
ASSERT_EQ(vals[0].value[0].b, 2);
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
Reference in New Issue
Block a user