mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-30 02:31:44 +00:00
[wpiutil] Struct: Add info template parameter pack (#6086)
This allows using Struct in a dynamically typed environment by passing additional information to the Struct serialization functions.
This commit is contained in:
@@ -37,9 +37,11 @@ class ProtobufTopic;
|
||||
class RawTopic;
|
||||
class StringArrayTopic;
|
||||
class StringTopic;
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayTopic;
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructTopic;
|
||||
class Topic;
|
||||
|
||||
|
||||
@@ -37,9 +37,11 @@ class ProtobufTopic;
|
||||
class RawTopic;
|
||||
class StringArrayTopic;
|
||||
class StringTopic;
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayTopic;
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructTopic;
|
||||
class Subscriber;
|
||||
class Topic;
|
||||
@@ -260,19 +262,24 @@ class NetworkTableInstance final {
|
||||
* Gets a raw struct serialized value topic.
|
||||
*
|
||||
* @param name topic name
|
||||
* @param info optional struct type info
|
||||
* @return Topic
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
StructTopic<T> GetStructTopic(std::string_view name) const;
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
StructTopic<T, I...> GetStructTopic(std::string_view name, I... info) const;
|
||||
|
||||
/**
|
||||
* Gets a raw struct serialized array topic.
|
||||
*
|
||||
* @param name topic name
|
||||
* @param info optional struct type info
|
||||
* @return Topic
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
StructArrayTopic<T> GetStructArrayTopic(std::string_view name) const;
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
StructArrayTopic<T, I...> GetStructArrayTopic(std::string_view name,
|
||||
I... info) const;
|
||||
|
||||
/**
|
||||
* Get Published Topics.
|
||||
@@ -818,10 +825,12 @@ class NetworkTableInstance final {
|
||||
* Registers a struct schema. Duplicate calls to this function with the same
|
||||
* name are silently ignored.
|
||||
*
|
||||
* @param T struct serializable type
|
||||
* @tparam T struct serializable type
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
void AddStructSchema();
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
void AddStructSchema(const I&... info);
|
||||
|
||||
/**
|
||||
* Equality operator. Returns true if both instances refer to the same
|
||||
|
||||
@@ -44,16 +44,18 @@ inline ProtobufTopic<T> NetworkTableInstance::GetProtobufTopic(
|
||||
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 <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
inline StructTopic<T, I...> NetworkTableInstance::GetStructTopic(
|
||||
std::string_view name, I... info) const {
|
||||
return StructTopic<T, I...>{GetTopic(name), std::move(info)...};
|
||||
}
|
||||
|
||||
template <wpi::StructSerializable T>
|
||||
inline StructArrayTopic<T> NetworkTableInstance::GetStructArrayTopic(
|
||||
std::string_view name) const {
|
||||
return StructArrayTopic<T>{GetTopic(name)};
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
inline StructArrayTopic<T, I...> NetworkTableInstance::GetStructArrayTopic(
|
||||
std::string_view name, I... info) const {
|
||||
return StructArrayTopic<T, I...>{GetTopic(name), std::move(info)...};
|
||||
}
|
||||
|
||||
inline std::vector<Topic> NetworkTableInstance::GetTopics() {
|
||||
@@ -272,11 +274,14 @@ void NetworkTableInstance::AddProtobufSchema(wpi::ProtobufMessage<T>& msg) {
|
||||
});
|
||||
}
|
||||
|
||||
template <wpi::StructSerializable T>
|
||||
void NetworkTableInstance::AddStructSchema() {
|
||||
wpi::ForEachStructSchema<T>([this](auto typeString, auto schema) {
|
||||
AddSchema(typeString, "structschema", schema);
|
||||
});
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
void NetworkTableInstance::AddStructSchema(const I&... info) {
|
||||
wpi::ForEachStructSchema<T>(
|
||||
[this](auto typeString, auto schema) {
|
||||
AddSchema(typeString, "structschema", schema);
|
||||
},
|
||||
info...);
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -24,18 +26,20 @@
|
||||
|
||||
namespace nt {
|
||||
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayTopic;
|
||||
|
||||
/**
|
||||
* NetworkTables struct-encoded value array subscriber.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArraySubscriber : public Subscriber {
|
||||
using S = wpi::Struct<T>;
|
||||
using S = wpi::Struct<T, I...>;
|
||||
|
||||
public:
|
||||
using TopicType = StructArrayTopic<T>;
|
||||
using TopicType = StructArrayTopic<T, I...>;
|
||||
using ValueType = std::vector<T>;
|
||||
using ParamType = std::span<const T>;
|
||||
using TimestampedValueType = Timestamped<ValueType>;
|
||||
@@ -48,15 +52,17 @@ class StructArraySubscriber : public Subscriber {
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param defaultValue Default value
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
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)
|
||||
StructArraySubscriber(NT_Subscriber handle, U&& defaultValue, I... info)
|
||||
: Subscriber{handle},
|
||||
m_defaultValue{defaultValue.begin(), defaultValue.end()} {
|
||||
m_defaultValue{defaultValue.begin(), defaultValue.end()},
|
||||
m_info{std::move(info)...} {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +127,7 @@ class StructArraySubscriber : public Subscriber {
|
||||
#endif
|
||||
TimestampedValueType GetAtomic(U&& defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
size_t size = S::GetSize();
|
||||
size_t size = std::apply(S::GetSize, m_info);
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
|
||||
return {0, 0, std::forward<U>(defaultValue)};
|
||||
@@ -130,8 +136,12 @@ class StructArraySubscriber : public Subscriber {
|
||||
rv.value.reserve(view.value.size() / size);
|
||||
for (auto in = view.value.begin(), end = view.value.end(); in != end;
|
||||
in += size) {
|
||||
rv.value.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
rv.value.emplace_back(S::Unpack(
|
||||
std::span<const uint8_t>{std::to_address(in), size}, info...));
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -146,7 +156,7 @@ class StructArraySubscriber : public Subscriber {
|
||||
*/
|
||||
TimestampedValueType GetAtomic(std::span<const T> defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
size_t size = S::GetSize();
|
||||
size_t size = std::apply(S::GetSize, m_info);
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
|
||||
return {0, 0, {defaultValue.begin(), defaultValue.end()}};
|
||||
@@ -155,8 +165,12 @@ class StructArraySubscriber : public Subscriber {
|
||||
rv.value.reserve(view.value.size() / size);
|
||||
for (auto in = view.value.begin(), end = view.value.end(); in != end;
|
||||
in += size) {
|
||||
rv.value.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
rv.value.emplace_back(S::Unpack(
|
||||
std::span<const uint8_t>{std::to_address(in), size}, info...));
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -176,7 +190,7 @@ class StructArraySubscriber : public Subscriber {
|
||||
auto raw = ::nt::ReadQueueRaw(m_subHandle);
|
||||
std::vector<TimestampedValueType> rv;
|
||||
rv.reserve(raw.size());
|
||||
size_t size = S::GetSize();
|
||||
size_t size = std::apply(S::GetSize, m_info);
|
||||
for (auto&& r : raw) {
|
||||
if (r.value.size() == 0 || (r.value.size() % size) != 0) {
|
||||
continue;
|
||||
@@ -185,8 +199,13 @@ class StructArraySubscriber : public Subscriber {
|
||||
values.reserve(r.value.size() / size);
|
||||
for (auto in = r.value.begin(), end = r.value.end(); in != end;
|
||||
in += size) {
|
||||
values.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size}));
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
values.emplace_back(
|
||||
S::Unpack(std::span<const uint8_t>{std::to_address(in), size},
|
||||
info...));
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
rv.emplace_back(r.time, r.serverTime, std::move(values));
|
||||
}
|
||||
@@ -199,22 +218,29 @@ class StructArraySubscriber : public Subscriber {
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructArrayTopic<T>{::nt::GetTopicFromHandle(m_subHandle)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayTopic<T, I...>{
|
||||
::nt::GetTopicFromHandle(m_subHandle), info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
ValueType m_defaultValue;
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
* NetworkTables struct-encoded value array publisher.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayPublisher : public Publisher {
|
||||
using S = wpi::Struct<T>;
|
||||
using S = wpi::Struct<T, I...>;
|
||||
|
||||
public:
|
||||
using TopicType = StructArrayTopic<T>;
|
||||
using TopicType = StructArrayTopic<T, I...>;
|
||||
using ValueType = std::vector<T>;
|
||||
using ParamType = std::span<const T>;
|
||||
|
||||
@@ -227,8 +253,10 @@ class StructArrayPublisher : public Publisher {
|
||||
* StructTopic::Publish() instead.
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructArrayPublisher(NT_Publisher handle) : Publisher{handle} {}
|
||||
explicit StructArrayPublisher(NT_Publisher handle, I... info)
|
||||
: Publisher{handle}, m_info{std::move(info)...} {}
|
||||
|
||||
StructArrayPublisher(const StructArrayPublisher&) = delete;
|
||||
StructArrayPublisher& operator=(const StructArrayPublisher&) = delete;
|
||||
@@ -236,7 +264,9 @@ class StructArrayPublisher : public Publisher {
|
||||
StructArrayPublisher(StructArrayPublisher&& rhs)
|
||||
: Publisher{std::move(rhs)},
|
||||
m_buf{std::move(rhs.m_buf)},
|
||||
m_schemaPublished{rhs.m_schemaPublished} {}
|
||||
m_schemaPublished{
|
||||
rhs.m_schemaPublished.load(std::memory_order_relaxed)},
|
||||
m_info{std::move(rhs.m_info)} {}
|
||||
|
||||
StructArrayPublisher& operator=(StructArrayPublisher&& rhs) {
|
||||
Publisher::operator=(std::move(rhs));
|
||||
@@ -244,6 +274,7 @@ class StructArrayPublisher : public Publisher {
|
||||
m_schemaPublished.store(
|
||||
rhs.m_schemaPublished.load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
m_info = std::move(rhs.m_info);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -259,11 +290,17 @@ class StructArrayPublisher : public Publisher {
|
||||
std::convertible_to<std::ranges::range_value_t<U>, T>
|
||||
#endif
|
||||
void Set(U&& value, int64_t time = 0) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
}
|
||||
m_buf.Write(std::forward<U>(value),
|
||||
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); });
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>(info...);
|
||||
}
|
||||
m_buf.Write(
|
||||
std::forward<U>(value),
|
||||
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,8 +310,14 @@ class StructArrayPublisher : public Publisher {
|
||||
* @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); });
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
m_buf.Write(
|
||||
value,
|
||||
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,11 +333,17 @@ class StructArrayPublisher : public Publisher {
|
||||
std::convertible_to<std::ranges::range_value_t<U>, T>
|
||||
#endif
|
||||
void SetDefault(U&& value) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
}
|
||||
m_buf.Write(std::forward<U>(value),
|
||||
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); });
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>(info...);
|
||||
}
|
||||
m_buf.Write(
|
||||
std::forward<U>(value),
|
||||
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,8 +354,14 @@ class StructArrayPublisher : public Publisher {
|
||||
* @param value value
|
||||
*/
|
||||
void SetDefault(std::span<const T> value) {
|
||||
m_buf.Write(value,
|
||||
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); });
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
m_buf.Write(
|
||||
value,
|
||||
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,12 +370,18 @@ class StructArrayPublisher : public Publisher {
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructArrayTopic<T>{::nt::GetTopicFromHandle(m_pubHandle)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayTopic<T, I...>{
|
||||
::nt::GetTopicFromHandle(m_pubHandle), info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
wpi::StructArrayBuffer<T> m_buf;
|
||||
wpi::StructArrayBuffer<T, I...> m_buf;
|
||||
std::atomic_bool m_schemaPublished{false};
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -328,13 +389,14 @@ class StructArrayPublisher : public Publisher {
|
||||
*
|
||||
* @note Unlike NetworkTableEntry, the entry goes away when this is destroyed.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
class StructArrayEntry final : public StructArraySubscriber<T>,
|
||||
public StructArrayPublisher<T> {
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayEntry final : public StructArraySubscriber<T, I...>,
|
||||
public StructArrayPublisher<T, I...> {
|
||||
public:
|
||||
using SubscriberType = StructArraySubscriber<T>;
|
||||
using PublisherType = StructArrayPublisher<T>;
|
||||
using TopicType = StructArrayTopic<T>;
|
||||
using SubscriberType = StructArraySubscriber<T, I...>;
|
||||
using PublisherType = StructArrayPublisher<T, I...>;
|
||||
using TopicType = StructArrayTopic<T, I...>;
|
||||
using ValueType = std::vector<T>;
|
||||
using ParamType = std::span<const T>;
|
||||
|
||||
@@ -348,15 +410,16 @@ class StructArrayEntry final : public StructArraySubscriber<T>,
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param defaultValue Default value
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
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} {
|
||||
StructArrayEntry(NT_Entry handle, U&& defaultValue, const I&... info)
|
||||
: StructArraySubscriber<T, I...>{handle, defaultValue, info...},
|
||||
StructArrayPublisher<T, I...>{handle, info...} {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,7 +442,7 @@ class StructArrayEntry final : public StructArraySubscriber<T>,
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructArrayTopic<T>{::nt::GetTopicFromHandle(this->m_subHandle)};
|
||||
return StructArraySubscriber<T, I...>::GetTopic();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,12 +454,13 @@ class StructArrayEntry final : public StructArraySubscriber<T>,
|
||||
/**
|
||||
* NetworkTables struct-encoded value array topic.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructArrayTopic final : public Topic {
|
||||
public:
|
||||
using SubscriberType = StructArraySubscriber<T>;
|
||||
using PublisherType = StructArrayPublisher<T>;
|
||||
using EntryType = StructArrayEntry<T>;
|
||||
using SubscriberType = StructArraySubscriber<T, I...>;
|
||||
using PublisherType = StructArrayPublisher<T, I...>;
|
||||
using EntryType = StructArrayEntry<T, I...>;
|
||||
using ValueType = std::vector<T>;
|
||||
using ParamType = std::span<const T>;
|
||||
using TimestampedValueType = Timestamped<ValueType>;
|
||||
@@ -408,15 +472,19 @@ class StructArrayTopic final : public Topic {
|
||||
* NetworkTableInstance::GetStructTopic() instead.
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructArrayTopic(NT_Topic handle) : Topic{handle} {}
|
||||
explicit StructArrayTopic(NT_Topic handle, I... info)
|
||||
: Topic{handle}, m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Construct from a generic topic.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructArrayTopic(Topic topic) : Topic{topic} {}
|
||||
explicit StructArrayTopic(Topic topic, I... info)
|
||||
: Topic{topic}, m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Create a new subscriber to the topic.
|
||||
@@ -441,11 +509,17 @@ class StructArrayTopic final : public Topic {
|
||||
[[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};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArraySubscriber<T, I...>{
|
||||
::nt::Subscribe(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
options),
|
||||
defaultValue, info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,11 +541,17 @@ class StructArrayTopic final : public Topic {
|
||||
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};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArraySubscriber<T, I...>{
|
||||
::nt::Subscribe(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
options),
|
||||
defaultValue, info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,9 +571,17 @@ class StructArrayTopic final : public Topic {
|
||||
*/
|
||||
[[nodiscard]]
|
||||
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
|
||||
return StructArrayPublisher<T>{::nt::Publish(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(), options)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayPublisher<T, I...>{
|
||||
::nt::Publish(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
options),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,10 +605,17 @@ class StructArrayTopic final : public Topic {
|
||||
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)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayPublisher<T, I...>{
|
||||
::nt::PublishEx(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
properties, options),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,11 +646,17 @@ class StructArrayTopic final : public Topic {
|
||||
[[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};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayEntry<T, I...>{
|
||||
::nt::GetEntry(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
options),
|
||||
defaultValue, info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -581,12 +682,21 @@ class StructArrayTopic final : public Topic {
|
||||
[[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};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructArrayEntry<T, I...>{
|
||||
::nt::GetEntry(
|
||||
m_handle, NT_RAW,
|
||||
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
|
||||
info...),
|
||||
options),
|
||||
defaultValue, info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -23,25 +25,20 @@
|
||||
|
||||
namespace nt {
|
||||
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructTopic;
|
||||
|
||||
/**
|
||||
* NetworkTables struct-encoded value subscriber.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
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;
|
||||
}
|
||||
}();
|
||||
using S = wpi::Struct<T, I...>;
|
||||
|
||||
public:
|
||||
using TopicType = StructTopic<T>;
|
||||
using TopicType = StructTopic<T, I...>;
|
||||
using ValueType = T;
|
||||
using ParamType = const T&;
|
||||
using TimestampedValueType = Timestamped<T>;
|
||||
@@ -54,9 +51,12 @@ class StructSubscriber : public Subscriber {
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param defaultValue Default value
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
StructSubscriber(NT_Subscriber handle, T defaultValue)
|
||||
: Subscriber{handle}, m_defaultValue{std::move(defaultValue)} {}
|
||||
StructSubscriber(NT_Subscriber handle, T defaultValue, I... info)
|
||||
: Subscriber{handle},
|
||||
m_defaultValue{std::move(defaultValue)},
|
||||
m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Get the last published value.
|
||||
@@ -88,12 +88,16 @@ class StructSubscriber : public Subscriber {
|
||||
* @return true if successful
|
||||
*/
|
||||
bool GetInto(T* out) {
|
||||
wpi::SmallVector<uint8_t, kBufSize> buf;
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() < S::GetSize()) {
|
||||
if (view.value.size() < std::apply(S::GetSize, m_info)) {
|
||||
return false;
|
||||
} else {
|
||||
wpi::UnpackStructInto(out, view.value);
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
wpi::UnpackStructInto(out, view.value, info...);
|
||||
},
|
||||
m_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -116,12 +120,16 @@ class StructSubscriber : public Subscriber {
|
||||
* @return timestamped value
|
||||
*/
|
||||
TimestampedValueType GetAtomic(const T& defaultValue) const {
|
||||
wpi::SmallVector<uint8_t, kBufSize> buf;
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
|
||||
if (view.value.size() < S::GetSize()) {
|
||||
if (view.value.size() < std::apply(S::GetSize, m_info)) {
|
||||
return {0, 0, defaultValue};
|
||||
} else {
|
||||
return {view.time, view.serverTime, S::Unpack(view.value)};
|
||||
return {
|
||||
view.time, view.serverTime,
|
||||
std::apply(
|
||||
[&](const I&... info) { return S::Unpack(view.value, info...); },
|
||||
m_info)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,11 +149,16 @@ class StructSubscriber : public Subscriber {
|
||||
std::vector<TimestampedValueType> rv;
|
||||
rv.reserve(raw.size());
|
||||
for (auto&& r : raw) {
|
||||
if (r.value.size() < S::GetSize()) {
|
||||
if (r.value.size() < std::apply(S::GetSize, m_info)) {
|
||||
continue;
|
||||
} else {
|
||||
rv.emplace_back(r.time, r.serverTime,
|
||||
S::Unpack(std::span<const uint8_t>(r.value)));
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
rv.emplace_back(
|
||||
r.time, r.serverTime,
|
||||
S::Unpack(std::span<const uint8_t>(r.value), info...));
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
@@ -157,22 +170,29 @@ class StructSubscriber : public Subscriber {
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructTopic<T>{::nt::GetTopicFromHandle(m_subHandle)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructTopic<T, I...>{::nt::GetTopicFromHandle(m_subHandle),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
ValueType m_defaultValue;
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
* NetworkTables struct-encoded value publisher.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructPublisher : public Publisher {
|
||||
using S = wpi::Struct<T>;
|
||||
using S = wpi::Struct<T, I...>;
|
||||
|
||||
public:
|
||||
using TopicType = StructTopic<T>;
|
||||
using TopicType = StructTopic<T, I...>;
|
||||
using ValueType = T;
|
||||
using ParamType = const T&;
|
||||
|
||||
@@ -184,13 +204,17 @@ class StructPublisher : public Publisher {
|
||||
StructPublisher& operator=(const StructPublisher&) = delete;
|
||||
|
||||
StructPublisher(StructPublisher&& rhs)
|
||||
: Publisher{std::move(rhs)}, m_schemaPublished{rhs.m_schemaPublished} {}
|
||||
: Publisher{std::move(rhs)},
|
||||
m_schemaPublished{
|
||||
rhs.m_schemaPublished.load(std::memory_order_relaxed)},
|
||||
m_info{std::move(rhs.m_info)} {}
|
||||
|
||||
StructPublisher& operator=(StructPublisher&& rhs) {
|
||||
Publisher::operator=(std::move(rhs));
|
||||
m_schemaPublished.store(
|
||||
rhs.m_schemaPublished.load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
m_info = std::move(rhs.m_info);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -199,8 +223,10 @@ class StructPublisher : public Publisher {
|
||||
* StructTopic::Publish() instead.
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructPublisher(NT_Publisher handle) : Publisher{handle} {}
|
||||
explicit StructPublisher(NT_Publisher handle, I... info)
|
||||
: Publisher{handle}, m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Publish a new value.
|
||||
@@ -210,18 +236,24 @@ class StructPublisher : public Publisher {
|
||||
*/
|
||||
void Set(const T& value, int64_t time = 0) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>(info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
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);
|
||||
if constexpr (sizeof...(I) == 0) {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetRaw(m_pubHandle, buf, time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(std::apply(S::GetSize, m_info));
|
||||
std::apply([&](const I&... info) { S::Pack(buf, value, info...); }, m_info);
|
||||
::nt::SetRaw(m_pubHandle, buf, time);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,18 +265,24 @@ class StructPublisher : public Publisher {
|
||||
*/
|
||||
void SetDefault(const T& value) {
|
||||
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>();
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
GetTopic().GetInstance().template AddStructSchema<T>(info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
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);
|
||||
if constexpr (sizeof...(I) == 0) {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, value);
|
||||
::nt::SetDefaultRaw(m_pubHandle, buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(std::apply(S::GetSize, m_info));
|
||||
std::apply([&](const I&... info) { S::Pack(buf, value, info...); }, m_info);
|
||||
::nt::SetDefaultRaw(m_pubHandle, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,11 +291,17 @@ class StructPublisher : public Publisher {
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructTopic<T>{::nt::GetTopicFromHandle(m_pubHandle)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructTopic<T, I...>{::nt::GetTopicFromHandle(m_pubHandle),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_bool m_schemaPublished{false};
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -265,13 +309,14 @@ class StructPublisher : public Publisher {
|
||||
*
|
||||
* @note Unlike NetworkTableEntry, the entry goes away when this is destroyed.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
class StructEntry final : public StructSubscriber<T>,
|
||||
public StructPublisher<T> {
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructEntry final : public StructSubscriber<T, I...>,
|
||||
public StructPublisher<T, I...> {
|
||||
public:
|
||||
using SubscriberType = StructSubscriber<T>;
|
||||
using PublisherType = StructPublisher<T>;
|
||||
using TopicType = StructTopic<T>;
|
||||
using SubscriberType = StructSubscriber<T, I...>;
|
||||
using PublisherType = StructPublisher<T, I...>;
|
||||
using TopicType = StructTopic<T, I...>;
|
||||
using ValueType = T;
|
||||
using ParamType = const T&;
|
||||
|
||||
@@ -285,10 +330,11 @@ class StructEntry final : public StructSubscriber<T>,
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param defaultValue Default value
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
StructEntry(NT_Entry handle, T defaultValue)
|
||||
: StructSubscriber<T>{handle, std::move(defaultValue)},
|
||||
StructPublisher<T>{handle} {}
|
||||
StructEntry(NT_Entry handle, T defaultValue, const I&... info)
|
||||
: StructSubscriber<T, I...>{handle, std::move(defaultValue), info...},
|
||||
StructPublisher<T, I...>{handle, info...} {}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
@@ -309,9 +355,7 @@ class StructEntry final : public StructSubscriber<T>,
|
||||
*
|
||||
* @return Topic
|
||||
*/
|
||||
TopicType GetTopic() const {
|
||||
return StructTopic<T>{::nt::GetTopicFromHandle(this->m_subHandle)};
|
||||
}
|
||||
TopicType GetTopic() const { return StructSubscriber<T, I...>::GetTopic(); }
|
||||
|
||||
/**
|
||||
* Stops publishing the entry if it's published.
|
||||
@@ -322,12 +366,13 @@ class StructEntry final : public StructSubscriber<T>,
|
||||
/**
|
||||
* NetworkTables struct-encoded value topic.
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
class StructTopic final : public Topic {
|
||||
public:
|
||||
using SubscriberType = StructSubscriber<T>;
|
||||
using PublisherType = StructPublisher<T>;
|
||||
using EntryType = StructEntry<T>;
|
||||
using SubscriberType = StructSubscriber<T, I...>;
|
||||
using PublisherType = StructPublisher<T, I...>;
|
||||
using EntryType = StructEntry<T, I...>;
|
||||
using ValueType = T;
|
||||
using ParamType = const T&;
|
||||
using TimestampedValueType = Timestamped<T>;
|
||||
@@ -339,15 +384,19 @@ class StructTopic final : public Topic {
|
||||
* NetworkTableInstance::GetStructTopic() instead.
|
||||
*
|
||||
* @param handle Native handle
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructTopic(NT_Topic handle) : Topic{handle} {}
|
||||
explicit StructTopic(NT_Topic handle, I... info)
|
||||
: Topic{handle}, m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Construct from a generic topic.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
explicit StructTopic(Topic topic) : Topic{topic} {}
|
||||
explicit StructTopic(Topic topic, I... info)
|
||||
: Topic{topic}, m_info{std::move(info)...} {}
|
||||
|
||||
/**
|
||||
* Create a new subscriber to the topic.
|
||||
@@ -367,10 +416,15 @@ class StructTopic final : public Topic {
|
||||
[[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)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructSubscriber<T, I...>{
|
||||
::nt::Subscribe(m_handle, NT_RAW,
|
||||
wpi::GetStructTypeString<T, I...>(info...),
|
||||
options),
|
||||
std::move(defaultValue), info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,8 +444,15 @@ class StructTopic final : public Topic {
|
||||
*/
|
||||
[[nodiscard]]
|
||||
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
|
||||
return StructPublisher<T>{::nt::Publish(
|
||||
m_handle, NT_RAW, wpi::GetStructTypeString<T>(), options)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructPublisher<T, I...>{
|
||||
::nt::Publish(m_handle, NT_RAW,
|
||||
wpi::GetStructTypeString<T, I...>(info...),
|
||||
options),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,8 +476,15 @@ class StructTopic final : public Topic {
|
||||
PublisherType PublishEx(
|
||||
const wpi::json& properties,
|
||||
const PubSubOptions& options = kDefaultPubSubOptions) {
|
||||
return StructPublisher<T>{::nt::PublishEx(
|
||||
m_handle, NT_RAW, wpi::GetStructTypeString<T>(), properties, options)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructPublisher<T, I...>{
|
||||
::nt::PublishEx(m_handle, NT_RAW,
|
||||
wpi::GetStructTypeString<T, I...>(info...),
|
||||
properties, options),
|
||||
info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,11 +510,19 @@ class StructTopic final : public Topic {
|
||||
[[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)};
|
||||
return std::apply(
|
||||
[&](const I&... info) {
|
||||
return StructEntry<T, I...>{
|
||||
::nt::GetEntry(m_handle, NT_RAW,
|
||||
wpi::GetStructTypeString<T, I...>(info...),
|
||||
options),
|
||||
std::move(defaultValue), info...};
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -12,23 +12,35 @@
|
||||
|
||||
namespace {
|
||||
struct Inner {
|
||||
int a;
|
||||
int b;
|
||||
int a = 0;
|
||||
int b = 0;
|
||||
};
|
||||
|
||||
struct Outer {
|
||||
Inner inner;
|
||||
int c;
|
||||
int c = 0;
|
||||
};
|
||||
|
||||
struct Inner2 {
|
||||
int a;
|
||||
int b;
|
||||
int a = 0;
|
||||
int b = 0;
|
||||
};
|
||||
|
||||
struct Outer2 {
|
||||
Inner2 inner;
|
||||
int c;
|
||||
int c = 0;
|
||||
};
|
||||
|
||||
struct ThingA {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
struct ThingB {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
struct Info1 {
|
||||
int info = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -110,6 +122,36 @@ struct wpi::Struct<Outer2> {
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingA> {
|
||||
static constexpr std::string_view GetTypeString() { return "struct:ThingA"; }
|
||||
static constexpr size_t GetSize() { return 1; }
|
||||
static constexpr std::string_view GetSchema() { return "uint8 value"; }
|
||||
static ThingA Unpack(std::span<const uint8_t> data) {
|
||||
return ThingA{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingA& value) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingB, Info1> {
|
||||
static constexpr std::string_view GetTypeString(const Info1&) {
|
||||
return "struct:ThingB";
|
||||
}
|
||||
static constexpr size_t GetSize(const Info1&) { return 1; }
|
||||
static constexpr std::string_view GetSchema(const Info1&) {
|
||||
return "uint8 value";
|
||||
}
|
||||
static ThingB Unpack(std::span<const uint8_t> data, const Info1&) {
|
||||
return ThingB{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingB& value, const Info1&) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
namespace nt {
|
||||
|
||||
class StructTest : public ::testing::Test {
|
||||
@@ -293,4 +335,116 @@ TEST_F(StructTest, InnerArrayNonconstexpr) {
|
||||
ASSERT_EQ(vals[0].value[0].b, 2);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructA) {
|
||||
nt::StructTopic<ThingA> topic = inst.GetStructTopic<ThingA>("a");
|
||||
nt::StructPublisher<ThingA> pub = topic.Publish();
|
||||
nt::StructPublisher<ThingA> pub2 = topic.PublishEx({{}});
|
||||
nt::StructSubscriber<ThingA> sub = topic.Subscribe({});
|
||||
nt::StructEntry<ThingA> entry = topic.GetEntry({});
|
||||
pub.SetDefault({});
|
||||
pub.Set({}, 5);
|
||||
sub.Get();
|
||||
sub.Get({});
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic({});
|
||||
entry.SetDefault({});
|
||||
entry.Set({}, 6);
|
||||
entry.Get({});
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructArrayA) {
|
||||
nt::StructArrayTopic<ThingA> topic = inst.GetStructArrayTopic<ThingA>("a");
|
||||
nt::StructArrayPublisher<ThingA> pub = topic.Publish();
|
||||
nt::StructArrayPublisher<ThingA> pub2 = topic.PublishEx({{}});
|
||||
nt::StructArraySubscriber<ThingA> sub = topic.Subscribe({});
|
||||
nt::StructArrayEntry<ThingA> entry = topic.GetEntry({});
|
||||
pub.SetDefault({{ThingA{}, ThingA{}}});
|
||||
pub.Set({{ThingA{}, ThingA{}}}, 5);
|
||||
sub.Get();
|
||||
sub.Get({});
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic({});
|
||||
entry.SetDefault({{ThingA{}, ThingA{}}});
|
||||
entry.Set({{ThingA{}, ThingA{}}}, 6);
|
||||
entry.Get({});
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructFixedArrayA) {
|
||||
nt::StructTopic<std::array<ThingA, 2>> topic =
|
||||
inst.GetStructTopic<std::array<ThingA, 2>>("a");
|
||||
nt::StructPublisher<std::array<ThingA, 2>> pub = topic.Publish();
|
||||
nt::StructPublisher<std::array<ThingA, 2>> pub2 = topic.PublishEx({{}});
|
||||
nt::StructSubscriber<std::array<ThingA, 2>> sub = topic.Subscribe({});
|
||||
nt::StructEntry<std::array<ThingA, 2>> entry = topic.GetEntry({});
|
||||
std::array<ThingA, 2> arr;
|
||||
pub.SetDefault(arr);
|
||||
pub.Set(arr, 5);
|
||||
sub.Get();
|
||||
sub.Get(arr);
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic(arr);
|
||||
entry.SetDefault(arr);
|
||||
entry.Set(arr, 6);
|
||||
entry.Get(arr);
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructB) {
|
||||
Info1 info;
|
||||
nt::StructTopic<ThingB, Info1> topic =
|
||||
inst.GetStructTopic<ThingB, Info1>("b", info);
|
||||
nt::StructPublisher<ThingB, Info1> pub = topic.Publish();
|
||||
nt::StructPublisher<ThingB, Info1> pub2 = topic.PublishEx({{}});
|
||||
nt::StructSubscriber<ThingB, Info1> sub = topic.Subscribe({});
|
||||
nt::StructEntry<ThingB, Info1> entry = topic.GetEntry({});
|
||||
pub.SetDefault({});
|
||||
pub.Set({}, 5);
|
||||
sub.Get();
|
||||
sub.Get({});
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic({});
|
||||
entry.SetDefault({});
|
||||
entry.Set({}, 6);
|
||||
entry.Get({});
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructArrayB) {
|
||||
Info1 info;
|
||||
nt::StructArrayTopic<ThingB, Info1> topic =
|
||||
inst.GetStructArrayTopic<ThingB, Info1>("b", info);
|
||||
nt::StructArrayPublisher<ThingB, Info1> pub = topic.Publish();
|
||||
nt::StructArrayPublisher<ThingB, Info1> pub2 = topic.PublishEx({{}});
|
||||
nt::StructArraySubscriber<ThingB, Info1> sub = topic.Subscribe({});
|
||||
nt::StructArrayEntry<ThingB, Info1> entry = topic.GetEntry({});
|
||||
pub.SetDefault({{ThingB{}, ThingB{}}});
|
||||
pub.Set({{ThingB{}, ThingB{}}}, 5);
|
||||
sub.Get();
|
||||
sub.Get({});
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic({});
|
||||
entry.SetDefault({{ThingB{}, ThingB{}}});
|
||||
entry.Set({{ThingB{}, ThingB{}}}, 6);
|
||||
entry.Get({});
|
||||
}
|
||||
|
||||
TEST_F(StructTest, StructFixedArrayB) {
|
||||
Info1 info;
|
||||
nt::StructTopic<std::array<ThingB, 2>, Info1> topic =
|
||||
inst.GetStructTopic<std::array<ThingB, 2>, Info1>("b", info);
|
||||
nt::StructPublisher<std::array<ThingB, 2>, Info1> pub = topic.Publish();
|
||||
nt::StructPublisher<std::array<ThingB, 2>, Info1> pub2 =
|
||||
topic.PublishEx({{}});
|
||||
nt::StructSubscriber<std::array<ThingB, 2>, Info1> sub = topic.Subscribe({});
|
||||
nt::StructEntry<std::array<ThingB, 2>, Info1> entry = topic.GetEntry({});
|
||||
std::array<ThingB, 2> arr;
|
||||
pub.SetDefault(arr);
|
||||
pub.Set(arr, 5);
|
||||
sub.Get();
|
||||
sub.Get(arr);
|
||||
sub.GetAtomic();
|
||||
sub.GetAtomic(arr);
|
||||
entry.SetDefault(arr);
|
||||
entry.Set(arr, 6);
|
||||
entry.Get(arr);
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <version>
|
||||
@@ -263,16 +264,20 @@ class DataLog final {
|
||||
* name are silently ignored.
|
||||
*
|
||||
* @tparam T struct serializable type
|
||||
* @param info optional struct type info
|
||||
* @param timestamp Time stamp (0 to indicate now)
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
void AddStructSchema(int64_t timestamp = 0) {
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
void AddStructSchema(const I&... info, int64_t timestamp = 0) {
|
||||
if (timestamp == 0) {
|
||||
timestamp = Now();
|
||||
}
|
||||
ForEachStructSchema<T>([this, timestamp](auto typeString, auto schema) {
|
||||
AddSchema(typeString, "structschema", schema, timestamp);
|
||||
});
|
||||
ForEachStructSchema<T>(
|
||||
[this, timestamp](auto typeString, auto schema) {
|
||||
this->AddSchema(typeString, "structschema", schema, timestamp);
|
||||
},
|
||||
info...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -946,19 +951,22 @@ class StringArrayLogEntry : public DataLogEntry {
|
||||
/**
|
||||
* Log raw struct serializable objects.
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
class StructLogEntry : public DataLogEntry {
|
||||
using S = Struct<T>;
|
||||
using S = Struct<T, I...>;
|
||||
|
||||
public:
|
||||
StructLogEntry() = default;
|
||||
StructLogEntry(DataLog& log, std::string_view name, int64_t timestamp = 0)
|
||||
: StructLogEntry{log, name, {}, timestamp} {}
|
||||
StructLogEntry(DataLog& log, std::string_view name, I... info,
|
||||
int64_t timestamp = 0)
|
||||
: StructLogEntry{log, name, {}, std::move(info)..., timestamp} {}
|
||||
StructLogEntry(DataLog& log, std::string_view name, std::string_view metadata,
|
||||
int64_t timestamp = 0) {
|
||||
I... info, int64_t timestamp = 0)
|
||||
: m_info{std::move(info)...} {
|
||||
m_log = &log;
|
||||
log.AddStructSchema<T>(timestamp);
|
||||
m_entry = log.Start(name, S::kTypeString, metadata, timestamp);
|
||||
log.AddStructSchema<T, I...>(info..., timestamp);
|
||||
m_entry = log.Start(name, S::GetTypeString(info...), metadata, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -968,38 +976,46 @@ class StructLogEntry : public DataLogEntry {
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Append(const T& data, int64_t timestamp = 0) {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, data);
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
} else {
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(S::GetSize());
|
||||
S::Pack(buf, data);
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
if constexpr (sizeof...(I) == 0) {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, data);
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(std::apply(S::GetSize, m_info));
|
||||
std::apply([&](const I&... info) { S::Pack(buf, data, info...); }, m_info);
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
|
||||
private:
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log raw struct serializable array of objects.
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
class StructArrayLogEntry : public DataLogEntry {
|
||||
using S = Struct<T>;
|
||||
using S = Struct<T, I...>;
|
||||
|
||||
public:
|
||||
StructArrayLogEntry() = default;
|
||||
StructArrayLogEntry(DataLog& log, std::string_view name,
|
||||
StructArrayLogEntry(DataLog& log, std::string_view name, I... info,
|
||||
int64_t timestamp = 0)
|
||||
: StructArrayLogEntry{log, name, {}, timestamp} {}
|
||||
: StructArrayLogEntry{log, name, {}, std::move(info)..., timestamp} {}
|
||||
StructArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0) {
|
||||
std::string_view metadata, I... info,
|
||||
int64_t timestamp = 0)
|
||||
: m_info{std::move(info)...} {
|
||||
m_log = &log;
|
||||
log.AddStructSchema<T>(timestamp);
|
||||
m_entry =
|
||||
log.Start(name, MakeStructArrayTypeString<T, std::dynamic_extent>(),
|
||||
metadata, timestamp);
|
||||
log.AddStructSchema<T, I...>(info..., timestamp);
|
||||
m_entry = log.Start(
|
||||
name, MakeStructArrayTypeString<T, std::dynamic_extent>(info...),
|
||||
metadata, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1014,9 +1030,14 @@ class StructArrayLogEntry : public DataLogEntry {
|
||||
std::convertible_to<std::ranges::range_value_t<U>, T>
|
||||
#endif
|
||||
void Append(U&& data, int64_t timestamp = 0) {
|
||||
m_buf.Write(std::forward<U>(data), [&](auto bytes) {
|
||||
m_log->AppendRaw(m_entry, bytes, timestamp);
|
||||
});
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
m_buf.Write(
|
||||
std::forward<U>(data),
|
||||
[&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1026,12 +1047,19 @@ class StructArrayLogEntry : public DataLogEntry {
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Append(std::span<const T> data, int64_t timestamp = 0) {
|
||||
m_buf.Write(
|
||||
data, [&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); });
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
m_buf.Write(
|
||||
data,
|
||||
[&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); },
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
StructArrayBuffer<T> m_buf;
|
||||
StructArrayBuffer<T, I...> m_buf;
|
||||
[[no_unique_address]] std::tuple<I...> m_info;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,8 +32,9 @@ namespace wpi {
|
||||
* StructSerializable concept.
|
||||
*
|
||||
* @tparam T type to serialize/deserialize
|
||||
* @tparam I optional struct type info
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename T, typename... I>
|
||||
struct Struct {};
|
||||
|
||||
/**
|
||||
@@ -63,23 +64,31 @@ struct Struct {};
|
||||
* If the struct has nested structs, implementations should also meet the
|
||||
* requirements of HasNestedStruct<T>.
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename T, typename... I>
|
||||
concept StructSerializable = requires(std::span<const uint8_t> in,
|
||||
std::span<uint8_t> out, T&& value) {
|
||||
typename Struct<typename std::remove_cvref_t<T>>;
|
||||
std::span<uint8_t> out, T&& value,
|
||||
const I&... info) {
|
||||
typename Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>;
|
||||
{
|
||||
Struct<typename std::remove_cvref_t<T>>::GetTypeString()
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::GetTypeString(info...)
|
||||
} -> std::convertible_to<std::string_view>;
|
||||
{
|
||||
Struct<typename std::remove_cvref_t<T>>::GetSize()
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::GetSize(info...)
|
||||
} -> std::convertible_to<size_t>;
|
||||
{
|
||||
Struct<typename std::remove_cvref_t<T>>::GetSchema()
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::GetSchema(info...)
|
||||
} -> std::convertible_to<std::string_view>;
|
||||
{
|
||||
Struct<typename std::remove_cvref_t<T>>::Unpack(in)
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::Unpack(in, info...)
|
||||
} -> std::same_as<typename std::remove_cvref_t<T>>;
|
||||
Struct<typename std::remove_cvref_t<T>>::Pack(out, std::forward<T>(value));
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::Pack(out, std::forward<T>(value),
|
||||
info...);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,10 +98,12 @@ concept StructSerializable = requires(std::span<const uint8_t> in,
|
||||
* wpi::Struct<T> static member `void UnpackInto(T*, std::span<const uint8_t>)`
|
||||
* to update the pointed-to T with the contents of the span.
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename T, typename... I>
|
||||
concept MutableStructSerializable =
|
||||
StructSerializable<T> && requires(T* out, std::span<const uint8_t> in) {
|
||||
Struct<typename std::remove_cvref_t<T>>::UnpackInto(out, in);
|
||||
StructSerializable<T, I...> &&
|
||||
requires(T* out, std::span<const uint8_t> in, const I&... info) {
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::UnpackInto(out, in, info...);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -104,11 +115,13 @@ concept MutableStructSerializable =
|
||||
* fn)` (or equivalent) and call ForEachNestedStruct<Type> on each nested struct
|
||||
* type.
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename T, typename... I>
|
||||
concept HasNestedStruct =
|
||||
StructSerializable<T> &&
|
||||
requires(function_ref<void(std::string_view, std::string_view)> fn) {
|
||||
Struct<typename std::remove_cvref_t<T>>::ForEachNested(fn);
|
||||
StructSerializable<T, I...> &&
|
||||
requires(function_ref<void(std::string_view, std::string_view)> fn,
|
||||
const I&... info) {
|
||||
Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>::ForEachNested(fn, info...);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -116,11 +129,14 @@ concept HasNestedStruct =
|
||||
*
|
||||
* @tparam T object type
|
||||
* @param data raw struct data
|
||||
* @param info optional struct type info
|
||||
* @return Deserialized object
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
inline T UnpackStruct(std::span<const uint8_t> data) {
|
||||
return Struct<T>::Unpack(data);
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline T UnpackStruct(std::span<const uint8_t> data, const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
return S::Unpack(data, info...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,11 +146,14 @@ inline T UnpackStruct(std::span<const uint8_t> data) {
|
||||
* @tparam T object type
|
||||
* @tparam Offset starting offset
|
||||
* @param data raw struct data
|
||||
* @param info optional struct type info
|
||||
* @return Deserialized object
|
||||
*/
|
||||
template <StructSerializable T, size_t Offset>
|
||||
inline T UnpackStruct(std::span<const uint8_t> data) {
|
||||
return Struct<T>::Unpack(data.subspan(Offset));
|
||||
template <typename T, size_t Offset, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline T UnpackStruct(std::span<const uint8_t> data, const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
return S::Unpack(data.subspan(Offset), info...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,10 +161,14 @@ inline T UnpackStruct(std::span<const uint8_t> data) {
|
||||
*
|
||||
* @param data struct storage (mutable, output)
|
||||
* @param value object
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
inline void PackStruct(std::span<uint8_t> data, T&& value) {
|
||||
Struct<typename std::remove_cvref_t<T>>::Pack(data, std::forward<T>(value));
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline void PackStruct(std::span<uint8_t> data, T&& value, const I&... info) {
|
||||
using S = Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>;
|
||||
S::Pack(data, std::forward<T>(value), info...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,11 +178,14 @@ inline void PackStruct(std::span<uint8_t> data, T&& value) {
|
||||
* @tparam Offset starting offset
|
||||
* @param data struct storage (mutable, output)
|
||||
* @param value object
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
template <size_t Offset, StructSerializable T>
|
||||
inline void PackStruct(std::span<uint8_t> data, T&& value) {
|
||||
Struct<typename std::remove_cvref_t<T>>::Pack(data.subspan(Offset),
|
||||
std::forward<T>(value));
|
||||
template <size_t Offset, typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline void PackStruct(std::span<uint8_t> data, T&& value, const I&... info) {
|
||||
using S = Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>;
|
||||
S::Pack(data.subspan(Offset), std::forward<T>(value), info...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,13 +193,17 @@ inline void PackStruct(std::span<uint8_t> data, T&& value) {
|
||||
*
|
||||
* @param out object (output)
|
||||
* @param data raw struct data
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
inline void UnpackStructInto(T* out, std::span<const uint8_t> data) {
|
||||
if constexpr (MutableStructSerializable<T>) {
|
||||
Struct<T>::UnpackInto(out, data);
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline void UnpackStructInto(T* out, std::span<const uint8_t> data,
|
||||
const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
if constexpr (MutableStructSerializable<T, I...>) {
|
||||
S::UnpackInto(out, data, info...);
|
||||
} else {
|
||||
*out = UnpackStruct<T>(data);
|
||||
*out = UnpackStruct<T>(data, info...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,13 +215,17 @@ inline void UnpackStructInto(T* out, std::span<const uint8_t> data) {
|
||||
* @tparam Offset starting offset
|
||||
* @param out object (output)
|
||||
* @param data raw struct data
|
||||
* @param info optional struct type info
|
||||
*/
|
||||
template <size_t Offset, StructSerializable T>
|
||||
inline void UnpackStructInto(T* out, std::span<const uint8_t> data) {
|
||||
if constexpr (MutableStructSerializable<T>) {
|
||||
Struct<T>::UnpackInto(out, data.subspan(Offset));
|
||||
template <size_t Offset, typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
inline void UnpackStructInto(T* out, std::span<const uint8_t> data,
|
||||
const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
if constexpr (MutableStructSerializable<T, I...>) {
|
||||
S::UnpackInto(out, data.subspan(Offset), info...);
|
||||
} else {
|
||||
*out = UnpackStruct<T, Offset>(data);
|
||||
*out = UnpackStruct<T, Offset>(data, info...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,28 +233,37 @@ inline void UnpackStructInto(T* out, std::span<const uint8_t> data) {
|
||||
* Get the type string for a raw struct serializable type
|
||||
*
|
||||
* @tparam T serializable type
|
||||
* @param info optional struct type info
|
||||
* @return type string
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
constexpr auto GetStructTypeString() {
|
||||
return Struct<T>::GetTypeString();
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr auto GetStructTypeString(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
return S::GetTypeString(info...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size for a raw struct serializable type
|
||||
*
|
||||
* @tparam T serializable type
|
||||
* @param info optional struct type info
|
||||
* @return size
|
||||
*/
|
||||
template <StructSerializable T>
|
||||
constexpr size_t GetStructSize() {
|
||||
return Struct<T>::GetSize();
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr size_t GetStructSize(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
return S::GetSize(info...);
|
||||
}
|
||||
|
||||
template <StructSerializable T, size_t N>
|
||||
constexpr auto MakeStructArrayTypeString() {
|
||||
if constexpr (is_constexpr([] { Struct<T>::GetTypeString(); })) {
|
||||
constexpr auto typeString = Struct<T>::GetTypeString();
|
||||
template <typename T, size_t N, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr auto MakeStructArrayTypeString(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
if constexpr (sizeof...(I) == 0 &&
|
||||
is_constexpr([&] { S::GetTypeString(info...); })) {
|
||||
constexpr auto typeString = S::GetTypeString(info...);
|
||||
using namespace literals;
|
||||
if constexpr (N == std::dynamic_extent) {
|
||||
return Concat(
|
||||
@@ -235,17 +278,20 @@ constexpr auto MakeStructArrayTypeString() {
|
||||
}
|
||||
} else {
|
||||
if constexpr (N == std::dynamic_extent) {
|
||||
return fmt::format("{}[]", Struct<T>::GetTypeString());
|
||||
return fmt::format("{}[]", S::GetTypeString(info...));
|
||||
} else {
|
||||
return fmt::format("{}[{}]", Struct<T>::GetTypeString(), N);
|
||||
return fmt::format("{}[{}]", S::GetTypeString(info...), N);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <StructSerializable T, size_t N>
|
||||
consteval auto MakeStructArraySchema() {
|
||||
if constexpr (is_constexpr([] { Struct<T>::GetSchema(); })) {
|
||||
constexpr auto schema = Struct<T>::GetSchema();
|
||||
template <typename T, size_t N, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr auto MakeStructArraySchema(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
if constexpr (sizeof...(I) == 0 &&
|
||||
is_constexpr([&] { S::GetSchema(info...); })) {
|
||||
constexpr auto schema = S::GetSchema(info...);
|
||||
using namespace literals;
|
||||
if constexpr (N == std::dynamic_extent) {
|
||||
return Concat(
|
||||
@@ -258,36 +304,45 @@ consteval auto MakeStructArraySchema() {
|
||||
}
|
||||
} else {
|
||||
if constexpr (N == std::dynamic_extent) {
|
||||
return fmt::format("{}[]", Struct<T>::GetSchema());
|
||||
return fmt::format("{}[]", S::GetSchema(info...));
|
||||
} else {
|
||||
return fmt::format("{}[{}]", Struct<T>::GetSchema(), N);
|
||||
return fmt::format("{}[{}]", S::GetSchema(info...), N);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <StructSerializable T>
|
||||
constexpr std::string_view GetStructSchema() {
|
||||
return Struct<T>::GetSchema();
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr std::string_view GetStructSchema(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
return S::GetSchema(info...);
|
||||
}
|
||||
|
||||
template <StructSerializable T>
|
||||
constexpr std::span<const uint8_t> GetStructSchemaBytes() {
|
||||
auto schema = Struct<T>::GetSchema();
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
constexpr std::span<const uint8_t> GetStructSchemaBytes(const I&... info) {
|
||||
using S = Struct<T, typename std::remove_cvref_t<I>...>;
|
||||
auto schema = S::GetSchema(info...);
|
||||
return {reinterpret_cast<const uint8_t*>(schema.data()), schema.size()};
|
||||
}
|
||||
|
||||
template <StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
void ForEachStructSchema(
|
||||
std::invocable<std::string_view, std::string_view> auto fn) {
|
||||
if constexpr (HasNestedStruct<T>) {
|
||||
Struct<typename std::remove_cvref_t<T>>::ForEachNested(fn);
|
||||
std::invocable<std::string_view, std::string_view> auto fn,
|
||||
const I&... info) {
|
||||
using S = Struct<typename std::remove_cvref_t<T>,
|
||||
typename std::remove_cvref_t<I>...>;
|
||||
if constexpr (HasNestedStruct<T, I...>) {
|
||||
S::ForEachNested(fn, info...);
|
||||
}
|
||||
fn(Struct<T>::GetTypeString(), Struct<T>::GetSchema());
|
||||
fn(S::GetTypeString(info...), S::GetSchema(info...));
|
||||
}
|
||||
|
||||
template <StructSerializable T>
|
||||
template <typename T, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
class StructArrayBuffer {
|
||||
using S = Struct<T>;
|
||||
using S = Struct<T, I...>;
|
||||
|
||||
public:
|
||||
StructArrayBuffer() = default;
|
||||
@@ -306,15 +361,15 @@ class StructArrayBuffer {
|
||||
std::convertible_to<std::ranges::range_value_t<U>, T> &&
|
||||
#endif
|
||||
std::invocable<F, std::span<const uint8_t>>
|
||||
void Write(U&& data, F&& func) {
|
||||
auto size = S::GetSize();
|
||||
void Write(U&& data, F&& func, const I&... info) {
|
||||
auto size = S::GetSize(info...);
|
||||
if ((std::size(data) * size) < 256) {
|
||||
// use the stack
|
||||
uint8_t buf[256];
|
||||
auto out = buf;
|
||||
for (auto&& val : data) {
|
||||
S::Pack(std::span<uint8_t>{std::to_address(out), size},
|
||||
std::forward<decltype(val)>(val));
|
||||
std::forward<decltype(val)>(val), info...);
|
||||
out += size;
|
||||
}
|
||||
func(std::span<uint8_t>{buf, out});
|
||||
@@ -324,7 +379,7 @@ class StructArrayBuffer {
|
||||
auto out = m_buf.begin();
|
||||
for (auto&& val : data) {
|
||||
S::Pack(std::span<uint8_t>{std::to_address(out), size},
|
||||
std::forward<decltype(val)>(val));
|
||||
std::forward<decltype(val)>(val), info...);
|
||||
out += size;
|
||||
}
|
||||
func(m_buf);
|
||||
@@ -339,39 +394,48 @@ class StructArrayBuffer {
|
||||
/**
|
||||
* Raw struct support for fixed-size arrays of other structs.
|
||||
*/
|
||||
template <StructSerializable T, size_t N>
|
||||
struct Struct<std::array<T, N>> {
|
||||
static constexpr auto GetTypeString() {
|
||||
return MakeStructArrayTypeString<T, N>();
|
||||
template <typename T, size_t N, typename... I>
|
||||
requires StructSerializable<T, I...>
|
||||
struct Struct<std::array<T, N>, I...> {
|
||||
static constexpr auto GetTypeString(const I&... info) {
|
||||
return MakeStructArrayTypeString<T, N>(info...);
|
||||
}
|
||||
static constexpr size_t GetSize() { return N * GetStructSize<T>(); }
|
||||
static constexpr auto GetSchema() { return MakeStructArraySchema<T, N>(); }
|
||||
static std::array<T, N> Unpack(std::span<const uint8_t> data) {
|
||||
auto size = GetStructSize<T>();
|
||||
static constexpr size_t GetSize(const I&... info) {
|
||||
return N * GetStructSize<T>(info...);
|
||||
}
|
||||
static constexpr auto GetSchema(const I&... info) {
|
||||
return MakeStructArraySchema<T, N>(info...);
|
||||
}
|
||||
static std::array<T, N> Unpack(std::span<const uint8_t> data,
|
||||
const I&... info) {
|
||||
auto size = GetStructSize<T>(info...);
|
||||
std::array<T, N> result;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
result[i] = UnpackStruct<T, 0>(data);
|
||||
result[i] = UnpackStruct<T, 0>(data, info...);
|
||||
data = data.subspan(size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, std::span<const T, N> values) {
|
||||
auto size = GetStructSize<T>();
|
||||
static void Pack(std::span<uint8_t> data, std::span<const T, N> values,
|
||||
const I&... info) {
|
||||
auto size = GetStructSize<T>(info...);
|
||||
std::span<uint8_t> unsizedData = data;
|
||||
for (auto&& val : values) {
|
||||
PackStruct(unsizedData, val);
|
||||
PackStruct(unsizedData, val, info...);
|
||||
unsizedData = unsizedData.subspan(size);
|
||||
}
|
||||
}
|
||||
static void UnpackInto(std::array<T, N>* out, std::span<const uint8_t> data) {
|
||||
UnpackInto(std::span{*out}, data);
|
||||
static void UnpackInto(std::array<T, N>* out, std::span<const uint8_t> data,
|
||||
const I&... info) {
|
||||
UnpackInto(std::span{*out}, data, info...);
|
||||
}
|
||||
// alternate span-based function
|
||||
static void UnpackInto(std::span<T, N> out, std::span<const uint8_t> data) {
|
||||
auto size = GetStructSize<T>();
|
||||
static void UnpackInto(std::span<T, N> out, std::span<const uint8_t> data,
|
||||
const I&... info) {
|
||||
auto size = GetStructSize<T>(info...);
|
||||
std::span<const uint8_t> unsizedData = data;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
UnpackStructInto(&out[i], unsizedData);
|
||||
UnpackStructInto(&out[i], unsizedData, info...);
|
||||
unsizedData = unsizedData.subspan(size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,122 @@
|
||||
// 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 <array>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wpi/DataLog.h"
|
||||
|
||||
namespace {
|
||||
struct ThingA {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
struct ThingB {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
struct ThingC {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
struct Info1 {
|
||||
int info = 0;
|
||||
};
|
||||
|
||||
struct Info2 {
|
||||
int info = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingA> {
|
||||
static constexpr std::string_view GetTypeString() { return "struct:ThingA"; }
|
||||
static constexpr size_t GetSize() { return 1; }
|
||||
static constexpr std::string_view GetSchema() { return "uint8 value"; }
|
||||
static ThingA Unpack(std::span<const uint8_t> data) {
|
||||
return ThingA{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingA& value) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingB, Info1> {
|
||||
static constexpr std::string_view GetTypeString(const Info1&) {
|
||||
return "struct:ThingB";
|
||||
}
|
||||
static constexpr size_t GetSize(const Info1&) { return 1; }
|
||||
static constexpr std::string_view GetSchema(const Info1&) {
|
||||
return "uint8 value";
|
||||
}
|
||||
static ThingB Unpack(std::span<const uint8_t> data, const Info1&) {
|
||||
return ThingB{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingB& value, const Info1&) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingC> {
|
||||
static constexpr std::string_view GetTypeString() { return "struct:ThingC"; }
|
||||
static constexpr size_t GetSize() { return 1; }
|
||||
static constexpr std::string_view GetSchema() { return "uint8 value"; }
|
||||
static ThingC Unpack(std::span<const uint8_t> data) {
|
||||
return ThingC{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingC& value) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingC, Info1> {
|
||||
static constexpr std::string_view GetTypeString(const Info1&) {
|
||||
return "struct:ThingC";
|
||||
}
|
||||
static constexpr size_t GetSize(const Info1&) { return 1; }
|
||||
static constexpr std::string_view GetSchema(const Info1&) {
|
||||
return "uint8 value";
|
||||
}
|
||||
static ThingC Unpack(std::span<const uint8_t> data, const Info1&) {
|
||||
return ThingC{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingC& value, const Info1&) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Struct<ThingC, Info2> {
|
||||
static constexpr std::string_view GetTypeString(const Info2&) {
|
||||
return "struct:ThingC";
|
||||
}
|
||||
static constexpr size_t GetSize(const Info2&) { return 1; }
|
||||
static constexpr std::string_view GetSchema(const Info2&) {
|
||||
return "uint8 value";
|
||||
}
|
||||
static ThingC Unpack(std::span<const uint8_t> data, const Info2&) {
|
||||
return ThingC{.x = data[0]};
|
||||
}
|
||||
static void Pack(std::span<uint8_t> data, const ThingC& value, const Info2&) {
|
||||
data[0] = value.x;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(wpi::StructSerializable<ThingA>);
|
||||
static_assert(!wpi::StructSerializable<ThingA, Info1>);
|
||||
|
||||
static_assert(!wpi::StructSerializable<ThingB>);
|
||||
static_assert(wpi::StructSerializable<ThingB, Info1>);
|
||||
static_assert(!wpi::StructSerializable<ThingB, Info2>);
|
||||
|
||||
static_assert(wpi::StructSerializable<ThingC>);
|
||||
static_assert(wpi::StructSerializable<ThingC, Info1>);
|
||||
static_assert(wpi::StructSerializable<ThingC, Info2>);
|
||||
|
||||
TEST(DataLogTest, SimpleInt) {
|
||||
std::vector<uint8_t> data;
|
||||
{
|
||||
@@ -16,3 +128,122 @@ TEST(DataLogTest, SimpleInt) {
|
||||
}
|
||||
ASSERT_EQ(data.size(), 66u);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructA) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
[[maybe_unused]] wpi::log::StructLogEntry<ThingA> entry0;
|
||||
wpi::log::StructLogEntry<ThingA> entry{log, "a", 5};
|
||||
entry.Append(ThingA{});
|
||||
entry.Append(ThingA{}, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructArrayA) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
[[maybe_unused]] wpi::log::StructArrayLogEntry<ThingA> entry0;
|
||||
wpi::log::StructArrayLogEntry<ThingA> entry{log, "a", 5};
|
||||
entry.Append({{ThingA{}, ThingA{}}});
|
||||
entry.Append({{ThingA{}, ThingA{}}}, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructFixedArrayA) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
[[maybe_unused]] wpi::log::StructArrayLogEntry<std::array<ThingA, 2>> entry0;
|
||||
wpi::log::StructLogEntry<std::array<ThingA, 2>> entry{log, "a", 5};
|
||||
std::array<ThingA, 2> arr;
|
||||
entry.Append(arr);
|
||||
entry.Append(arr, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructB) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
Info1 info;
|
||||
[[maybe_unused]] wpi::log::StructLogEntry<ThingB, Info1> entry0;
|
||||
wpi::log::StructLogEntry<ThingB, Info1> entry{log, "b", info, 5};
|
||||
entry.Append(ThingB{});
|
||||
entry.Append(ThingB{}, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructArrayB) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
Info1 info;
|
||||
[[maybe_unused]] wpi::log::StructArrayLogEntry<ThingB, Info1> entry0;
|
||||
wpi::log::StructArrayLogEntry<ThingB, Info1> entry{log, "a", info, 5};
|
||||
entry.Append({{ThingB{}, ThingB{}}});
|
||||
entry.Append({{ThingB{}, ThingB{}}}, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructFixedArrayB) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
Info1 info;
|
||||
wpi::log::StructLogEntry<std::array<ThingB, 2>, Info1> entry{log, "a", info,
|
||||
5};
|
||||
std::array<ThingB, 2> arr;
|
||||
entry.Append(arr);
|
||||
entry.Append(arr, 7);
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructC) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
{
|
||||
wpi::log::StructLogEntry<ThingC> entry{log, "c", 5};
|
||||
entry.Append(ThingC{});
|
||||
entry.Append(ThingC{}, 7);
|
||||
}
|
||||
{
|
||||
Info1 info;
|
||||
wpi::log::StructLogEntry<ThingC, Info1> entry{log, "c1", info, 5};
|
||||
entry.Append(ThingC{});
|
||||
entry.Append(ThingC{}, 7);
|
||||
}
|
||||
{
|
||||
Info2 info;
|
||||
wpi::log::StructLogEntry<ThingC, Info2> entry{log, "c2", info, 5};
|
||||
entry.Append(ThingC{});
|
||||
entry.Append(ThingC{}, 7);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructArrayC) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
{
|
||||
wpi::log::StructArrayLogEntry<ThingC> entry{log, "c", 5};
|
||||
entry.Append({{ThingC{}, ThingC{}}});
|
||||
entry.Append({{ThingC{}, ThingC{}}}, 7);
|
||||
}
|
||||
{
|
||||
Info1 info;
|
||||
wpi::log::StructArrayLogEntry<ThingC, Info1> entry{log, "c1", info, 5};
|
||||
entry.Append({{ThingC{}, ThingC{}}});
|
||||
entry.Append({{ThingC{}, ThingC{}}}, 7);
|
||||
}
|
||||
{
|
||||
Info2 info;
|
||||
wpi::log::StructArrayLogEntry<ThingC, Info2> entry{log, "c2", info, 5};
|
||||
entry.Append({{ThingC{}, ThingC{}}});
|
||||
entry.Append({{ThingC{}, ThingC{}}}, 7);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DataLogTest, StructFixedArrayC) {
|
||||
wpi::log::DataLog log{[](auto) {}};
|
||||
std::array<ThingC, 2> arr;
|
||||
{
|
||||
wpi::log::StructLogEntry<std::array<ThingC, 2>> entry{log, "c", 5};
|
||||
entry.Append(arr);
|
||||
entry.Append(arr, 7);
|
||||
}
|
||||
{
|
||||
Info1 info;
|
||||
wpi::log::StructLogEntry<std::array<ThingC, 2>, Info1> entry{log, "c1",
|
||||
info, 5};
|
||||
entry.Append(arr);
|
||||
entry.Append(arr, 7);
|
||||
}
|
||||
{
|
||||
Info2 info;
|
||||
wpi::log::StructLogEntry<std::array<ThingC, 2>, Info2> entry{log, "c2",
|
||||
info, 5};
|
||||
entry.Append(arr);
|
||||
entry.Append(arr, 7);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user