From 1dd95f269a5440b86740866c1706f4ca675f966c Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 22 Dec 2023 18:53:10 -0800 Subject: [PATCH] [wpiutil] Struct: Add info template parameter pack This allows using Struct in a dynamically typed environment by passing additional information to the Struct serialization functions. --- .../include/networktables/NetworkTable.h | 6 +- .../networktables/NetworkTableInstance.h | 24 +- .../networktables/NetworkTableInstance.inc | 28 +- .../include/networktables/StructArrayTopic.h | 193 ++++++++------ .../include/networktables/StructTopic.h | 162 +++++++----- ntcore/src/test/native/cpp/StructTest.cpp | 149 +++++++++++ wpiutil/src/main/native/include/wpi/DataLog.h | 81 +++--- .../main/native/include/wpi/struct/Struct.h | 247 +++++++++++------- wpiutil/src/test/native/cpp/DataLogTest.cpp | 233 +++++++++++++++++ 9 files changed, 826 insertions(+), 297 deletions(-) create mode 100644 wpiutil/src/test/native/cpp/DataLogTest.cpp diff --git a/ntcore/src/main/native/include/networktables/NetworkTable.h b/ntcore/src/main/native/include/networktables/NetworkTable.h index e03ea9e99ad..e2d14212baa 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTable.h +++ b/ntcore/src/main/native/include/networktables/NetworkTable.h @@ -37,9 +37,11 @@ class ProtobufTopic; class RawTopic; class StringArrayTopic; class StringTopic; -template +template + requires wpi::StructSerializable class StructArrayTopic; -template +template + requires wpi::StructSerializable class StructTopic; class Topic; diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.h b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h index 06e2cd6483c..f957ca719ae 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTableInstance.h +++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h @@ -37,9 +37,11 @@ class ProtobufTopic; class RawTopic; class StringArrayTopic; class StringTopic; -template +template + requires wpi::StructSerializable class StructArrayTopic; -template +template + requires wpi::StructSerializable class StructTopic; class Subscriber; class Topic; @@ -262,8 +264,9 @@ class NetworkTableInstance final { * @param name topic name * @return Topic */ - template - StructTopic GetStructTopic(std::string_view name) const; + template + requires wpi::StructSerializable + StructTopic GetStructTopic(std::string_view name) const; /** * Gets a raw struct serialized array topic. @@ -271,8 +274,9 @@ class NetworkTableInstance final { * @param name topic name * @return Topic */ - template - StructArrayTopic GetStructArrayTopic(std::string_view name) const; + template + requires wpi::StructSerializable + StructArrayTopic GetStructArrayTopic(std::string_view name) const; /** * Get Published Topics. @@ -818,10 +822,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 - void AddStructSchema(); + template + requires wpi::StructSerializable + void AddStructSchema(const I&... info); /** * Equality operator. Returns true if both instances refer to the same diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc index b4bbee16c00..2f98b3e2f1c 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc +++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc @@ -11,6 +11,7 @@ #include "networktables/NetworkTableInstance.h" #include "networktables/Topic.h" #include "ntcore_c.h" +#include "wpi/struct/Struct.h" namespace nt { @@ -44,16 +45,18 @@ inline ProtobufTopic NetworkTableInstance::GetProtobufTopic( return ProtobufTopic{GetTopic(name)}; } -template -inline StructTopic NetworkTableInstance::GetStructTopic( +template + requires wpi::StructSerializable +inline StructTopic NetworkTableInstance::GetStructTopic( std::string_view name) const { - return StructTopic{GetTopic(name)}; + return StructTopic{GetTopic(name)}; } -template -inline StructArrayTopic NetworkTableInstance::GetStructArrayTopic( +template + requires wpi::StructSerializable +inline StructArrayTopic NetworkTableInstance::GetStructArrayTopic( std::string_view name) const { - return StructArrayTopic{GetTopic(name)}; + return StructArrayTopic{GetTopic(name)}; } inline std::vector NetworkTableInstance::GetTopics() { @@ -272,11 +275,14 @@ void NetworkTableInstance::AddProtobufSchema(wpi::ProtobufMessage& msg) { }); } -template -void NetworkTableInstance::AddStructSchema() { - wpi::ForEachStructSchema([this](auto typeString, auto schema) { - AddSchema(typeString, "structschema", schema); - }); +template + requires wpi::StructSerializable +void NetworkTableInstance::AddStructSchema(const I&... info) { + wpi::ForEachStructSchema( + [this](auto typeString, auto schema) { + AddSchema(typeString, "structschema", schema); + }, + info...); } #ifdef __clang__ diff --git a/ntcore/src/main/native/include/networktables/StructArrayTopic.h b/ntcore/src/main/native/include/networktables/StructArrayTopic.h index f3529eafd28..e783e80b423 100644 --- a/ntcore/src/main/native/include/networktables/StructArrayTopic.h +++ b/ntcore/src/main/native/include/networktables/StructArrayTopic.h @@ -24,18 +24,20 @@ namespace nt { -template +template + requires wpi::StructSerializable class StructArrayTopic; /** * NetworkTables struct-encoded value array subscriber. */ -template +template + requires wpi::StructSerializable class StructArraySubscriber : public Subscriber { - using S = wpi::Struct; + using S = wpi::Struct; public: - using TopicType = StructArrayTopic; + using TopicType = StructArrayTopic; using ValueType = std::vector; using ParamType = std::span; using TimestampedValueType = Timestamped; @@ -64,9 +66,10 @@ class StructArraySubscriber : public Subscriber { * If no value has been published or the value cannot be unpacked, returns the * stored default value. * + * @param info optional struct type info * @return value */ - ValueType Get() const { return Get(m_defaultValue); } + ValueType Get(const I&... info) const { return Get(m_defaultValue, info...); } /** * Get the last published value. @@ -74,6 +77,7 @@ class StructArraySubscriber : public Subscriber { * passed defaultValue. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return value */ template @@ -81,8 +85,8 @@ class StructArraySubscriber : public Subscriber { requires std::ranges::range && std::convertible_to, T> #endif - ValueType Get(U&& defaultValue) const { - return GetAtomic(std::forward(defaultValue)).value; + ValueType Get(U&& defaultValue, const I&... info) const { + return GetAtomic(std::forward(defaultValue), info...).value; } /** @@ -91,10 +95,11 @@ class StructArraySubscriber : public Subscriber { * passed defaultValue. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return value */ - ValueType Get(std::span defaultValue) const { - return GetAtomic(defaultValue).value; + ValueType Get(std::span defaultValue, const I&... info) const { + return GetAtomic(defaultValue, info...).value; } /** @@ -102,9 +107,12 @@ class StructArraySubscriber : public Subscriber { * If no value has been published or the value cannot be unpacked, returns the * stored default value and a timestamp of 0. * + * @param info optional struct type info * @return timestamped value */ - TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); } + TimestampedValueType GetAtomic(const I&... info) const { + return GetAtomic(m_defaultValue, info...); + } /** * Get the last published value along with its timestamp. @@ -112,6 +120,7 @@ class StructArraySubscriber : public Subscriber { * passed defaultValue and a timestamp of 0. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return timestamped value */ template @@ -119,9 +128,9 @@ class StructArraySubscriber : public Subscriber { requires std::ranges::range && std::convertible_to, T> #endif - TimestampedValueType GetAtomic(U&& defaultValue) const { + TimestampedValueType GetAtomic(U&& defaultValue, const I&... info) const { wpi::SmallVector buf; - size_t size = S::GetSize(); + size_t size = S::GetSize(info...); TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {}); if (view.value.size() == 0 || (view.value.size() % size) != 0) { return {0, 0, std::forward(defaultValue)}; @@ -130,8 +139,8 @@ 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{std::to_address(in), size})); + rv.value.emplace_back(S::Unpack( + std::span{std::to_address(in), size}, info...)); } return rv; } @@ -142,11 +151,13 @@ class StructArraySubscriber : public Subscriber { * passed defaultValue and a timestamp of 0. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return timestamped value */ - TimestampedValueType GetAtomic(std::span defaultValue) const { + TimestampedValueType GetAtomic(std::span defaultValue, + const I&... info) const { wpi::SmallVector buf; - size_t size = S::GetSize(); + size_t size = S::GetSize(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 +166,8 @@ 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{std::to_address(in), size})); + rv.value.emplace_back(S::Unpack( + std::span{std::to_address(in), size}, info...)); } return rv; } @@ -169,14 +180,15 @@ class StructArraySubscriber : public Subscriber { * @note The "poll storage" subscribe option can be used to set the queue * depth. * + * @param info optional struct type info * @return Array of timestamped values; empty array if no valid new changes * have been published since the previous call. */ - std::vector ReadQueue() { + std::vector ReadQueue(const I&... info) { auto raw = ::nt::ReadQueueRaw(m_subHandle); std::vector rv; rv.reserve(raw.size()); - size_t size = S::GetSize(); + size_t size = S::GetSize(info...); for (auto&& r : raw) { if (r.value.size() == 0 || (r.value.size() % size) != 0) { continue; @@ -185,8 +197,8 @@ 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{std::to_address(in), size})); + values.emplace_back(S::Unpack( + std::span{std::to_address(in), size}, info...)); } rv.emplace_back(r.time, r.serverTime, std::move(values)); } @@ -199,7 +211,7 @@ class StructArraySubscriber : public Subscriber { * @return Topic */ TopicType GetTopic() const { - return StructArrayTopic{::nt::GetTopicFromHandle(m_subHandle)}; + return StructArrayTopic{::nt::GetTopicFromHandle(m_subHandle)}; } private: @@ -209,12 +221,13 @@ class StructArraySubscriber : public Subscriber { /** * NetworkTables struct-encoded value array publisher. */ -template +template + requires wpi::StructSerializable class StructArrayPublisher : public Publisher { - using S = wpi::Struct; + using S = wpi::Struct; public: - using TopicType = StructArrayTopic; + using TopicType = StructArrayTopic; using ValueType = std::vector; using ParamType = std::span; @@ -251,6 +264,7 @@ class StructArrayPublisher : public Publisher { * Publish a new value. * * @param value value to publish + * @param info optional struct type info * @param time timestamp; 0 indicates current NT time should be used */ template @@ -258,23 +272,26 @@ class StructArrayPublisher : public Publisher { requires std::ranges::range && std::convertible_to, T> #endif - void Set(U&& value, int64_t time = 0) { + void Set(U&& value, const I&... info, int64_t time = 0) { if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); + GetTopic().GetInstance().template AddStructSchema(info...); } - m_buf.Write(std::forward(value), - [&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); }); + m_buf.Write( + std::forward(value), + [&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); }, info...); } /** * Publish a new value. * * @param value value to publish + * @param info optional struct type info * @param time timestamp; 0 indicates current NT time should be used */ - void Set(std::span value, int64_t time = 0) { - m_buf.Write(value, - [&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); }); + void Set(std::span value, const I&... info, int64_t time = 0) { + m_buf.Write( + value, [&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); }, + info...); } /** @@ -283,18 +300,20 @@ class StructArrayPublisher : public Publisher { * published value. * * @param value value + * @param info optional struct type info */ template #if __cpp_lib_ranges >= 201911L requires std::ranges::range && std::convertible_to, T> #endif - void SetDefault(U&& value) { + void SetDefault(U&& value, const I&... info) { if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); + GetTopic().GetInstance().template AddStructSchema(info...); } - m_buf.Write(std::forward(value), - [&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); }); + m_buf.Write( + std::forward(value), + [&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); }, info...); } /** @@ -303,10 +322,12 @@ class StructArrayPublisher : public Publisher { * published value. * * @param value value + * @param info optional struct type info */ - void SetDefault(std::span value) { - m_buf.Write(value, - [&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); }); + void SetDefault(std::span value, const I&... info) { + m_buf.Write( + value, [&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); }, + info...); } /** @@ -315,11 +336,11 @@ class StructArrayPublisher : public Publisher { * @return Topic */ TopicType GetTopic() const { - return StructArrayTopic{::nt::GetTopicFromHandle(m_pubHandle)}; + return StructArrayTopic{::nt::GetTopicFromHandle(m_pubHandle)}; } private: - wpi::StructArrayBuffer m_buf; + wpi::StructArrayBuffer m_buf; std::atomic_bool m_schemaPublished{false}; }; @@ -328,13 +349,14 @@ class StructArrayPublisher : public Publisher { * * @note Unlike NetworkTableEntry, the entry goes away when this is destroyed. */ -template -class StructArrayEntry final : public StructArraySubscriber, - public StructArrayPublisher { +template + requires wpi::StructSerializable +class StructArrayEntry final : public StructArraySubscriber, + public StructArrayPublisher { public: - using SubscriberType = StructArraySubscriber; - using PublisherType = StructArrayPublisher; - using TopicType = StructArrayTopic; + using SubscriberType = StructArraySubscriber; + using PublisherType = StructArrayPublisher; + using TopicType = StructArrayTopic; using ValueType = std::vector; using ParamType = std::span; @@ -355,8 +377,8 @@ class StructArrayEntry final : public StructArraySubscriber, std::convertible_to, T> #endif StructArrayEntry(NT_Entry handle, U&& defaultValue) - : StructArraySubscriber{handle, defaultValue}, - StructArrayPublisher{handle} { + : StructArraySubscriber{handle, defaultValue}, + StructArrayPublisher{handle} { } /** @@ -379,7 +401,8 @@ class StructArrayEntry final : public StructArraySubscriber, * @return Topic */ TopicType GetTopic() const { - return StructArrayTopic{::nt::GetTopicFromHandle(this->m_subHandle)}; + return StructArrayTopic{ + ::nt::GetTopicFromHandle(this->m_subHandle)}; } /** @@ -391,12 +414,13 @@ class StructArrayEntry final : public StructArraySubscriber, /** * NetworkTables struct-encoded value array topic. */ -template +template + requires wpi::StructSerializable class StructArrayTopic final : public Topic { public: - using SubscriberType = StructArraySubscriber; - using PublisherType = StructArrayPublisher; - using EntryType = StructArrayEntry; + using SubscriberType = StructArraySubscriber; + using PublisherType = StructArrayPublisher; + using EntryType = StructArrayEntry; using ValueType = std::vector; using ParamType = std::span; using TimestampedValueType = Timestamped; @@ -430,6 +454,7 @@ class StructArrayTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options subscribe options * @return subscriber */ @@ -440,11 +465,13 @@ class StructArrayTopic final : public Topic { #endif [[nodiscard]] SubscriberType Subscribe( - U&& defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArraySubscriber{ + U&& defaultValue, const I&... info, + const PubSubOptions& options = kDefaultPubSubOptions) { + return StructArraySubscriber{ ::nt::Subscribe( m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options), + wpi::MakeStructArrayTypeString(info...), + options), defaultValue}; } @@ -460,17 +487,19 @@ class StructArrayTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options subscribe options * @return subscriber */ [[nodiscard]] SubscriberType Subscribe( - std::span defaultValue, + std::span defaultValue, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArraySubscriber{ + return StructArraySubscriber{ ::nt::Subscribe( m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options), + wpi::MakeStructArrayTypeString(info...), + options), defaultValue}; } @@ -486,14 +515,17 @@ class StructArrayTopic final : public Topic { * do not match the topic's data type are dropped (ignored). To determine * if the data type matches, use the appropriate Topic functions. * + * @param info optional struct type info * @param options publish options * @return publisher */ [[nodiscard]] - PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayPublisher{::nt::Publish( + PublisherType Publish(const I&... info, + const PubSubOptions& options = kDefaultPubSubOptions) { + return StructArrayPublisher{::nt::Publish( m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options)}; + wpi::MakeStructArrayTypeString(info...), + options)}; } /** @@ -510,17 +542,18 @@ class StructArrayTopic final : public Topic { * if the data type matches, use the appropriate Topic functions. * * @param properties JSON properties + * @param info optional struct type info * @param options publish options * @return publisher */ [[nodiscard]] PublisherType PublishEx( - const wpi::json& properties, + const wpi::json& properties, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayPublisher{::nt::PublishEx( + return StructArrayPublisher{::nt::PublishEx( m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), properties, - options)}; + wpi::MakeStructArrayTypeString(info...), + properties, options)}; } /** @@ -540,6 +573,7 @@ class StructArrayTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options publish and/or subscribe options * @return entry */ @@ -549,12 +583,13 @@ class StructArrayTopic final : public Topic { std::convertible_to, T> #endif [[nodiscard]] - EntryType GetEntry(U&& defaultValue, + EntryType GetEntry(U&& defaultValue, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayEntry{ - ::nt::GetEntry(m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), - options), + return StructArrayEntry{ + ::nt::GetEntry( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString(info...), + options), defaultValue}; } @@ -575,16 +610,18 @@ class StructArrayTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options publish and/or subscribe options * @return entry */ [[nodiscard]] - EntryType GetEntry(std::span defaultValue, + EntryType GetEntry(std::span defaultValue, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayEntry{ - ::nt::GetEntry(m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), - options), + return StructArrayEntry{ + ::nt::GetEntry( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString(info...), + options), defaultValue}; } }; diff --git a/ntcore/src/main/native/include/networktables/StructTopic.h b/ntcore/src/main/native/include/networktables/StructTopic.h index 08d7a770b09..9f27e01278c 100644 --- a/ntcore/src/main/native/include/networktables/StructTopic.h +++ b/ntcore/src/main/native/include/networktables/StructTopic.h @@ -23,25 +23,20 @@ namespace nt { -template +template + requires wpi::StructSerializable class StructTopic; /** * NetworkTables struct-encoded value subscriber. */ -template +template + requires wpi::StructSerializable class StructSubscriber : public Subscriber { - using S = wpi::Struct; - static constexpr size_t kBufSize = []() -> size_t { - if constexpr (wpi::is_constexpr([] { S::GetSize(); })) { - return S::GetSize(); - } else { - return 128; - } - }(); + using S = wpi::Struct; public: - using TopicType = StructTopic; + using TopicType = StructTopic; using ValueType = T; using ParamType = const T&; using TimestampedValueType = Timestamped; @@ -63,9 +58,10 @@ class StructSubscriber : public Subscriber { * If no value has been published or the value cannot be unpacked, returns the * stored default value. * + * @param info optional struct type info * @return value */ - ValueType Get() const { return Get(m_defaultValue); } + ValueType Get(const I&... info) const { return Get(m_defaultValue, info...); } /** * Get the last published value. @@ -73,10 +69,11 @@ class StructSubscriber : public Subscriber { * passed defaultValue. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return value */ - ValueType Get(const T& defaultValue) const { - return GetAtomic(defaultValue).value; + ValueType Get(const T& defaultValue, const I&... info) const { + return GetAtomic(defaultValue, info...).value; } /** @@ -85,15 +82,16 @@ class StructSubscriber : public Subscriber { * unpacked, does not replace the contents and returns false. * * @param[out] out object to replace contents of + * @param info optional struct type info * @return true if successful */ - bool GetInto(T* out) { - wpi::SmallVector buf; + bool GetInto(T* out, const I&... info) { + wpi::SmallVector buf; TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {}); - if (view.value.size() < S::GetSize()) { + if (view.value.size() < S::GetSize(info...)) { return false; } else { - wpi::UnpackStructInto(out, view.value); + wpi::UnpackStructInto(out, view.value, info...); return true; } } @@ -103,9 +101,12 @@ class StructSubscriber : public Subscriber { * If no value has been published or the value cannot be unpacked, returns the * stored default value and a timestamp of 0. * + * @param info optional struct type info * @return timestamped value */ - TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); } + TimestampedValueType GetAtomic(const I&... info) const { + return GetAtomic(m_defaultValue, info...); + } /** * Get the last published value along with its timestamp. @@ -113,15 +114,17 @@ class StructSubscriber : public Subscriber { * passed defaultValue and a timestamp of 0. * * @param defaultValue default value to return if no value has been published + * @param info optional struct type info * @return timestamped value */ - TimestampedValueType GetAtomic(const T& defaultValue) const { - wpi::SmallVector buf; + TimestampedValueType GetAtomic(const T& defaultValue, + const I&... info) const { + wpi::SmallVector buf; TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {}); - if (view.value.size() < S::GetSize()) { + if (view.value.size() < S::GetSize(info...)) { return {0, 0, defaultValue}; } else { - return {view.time, view.serverTime, S::Unpack(view.value)}; + return {view.time, view.serverTime, S::Unpack(view.value, info...)}; } } @@ -133,19 +136,20 @@ class StructSubscriber : public Subscriber { * @note The "poll storage" subscribe option can be used to set the queue * depth. * + * @param info optional struct type info * @return Array of timestamped values; empty array if no valid new changes * have been published since the previous call. */ - std::vector ReadQueue() { + std::vector ReadQueue(const I&... info) { auto raw = ::nt::ReadQueueRaw(m_subHandle); std::vector rv; rv.reserve(raw.size()); for (auto&& r : raw) { - if (r.value.size() < S::GetSize()) { + if (r.value.size() < S::GetSize(info...)) { continue; } else { rv.emplace_back(r.time, r.serverTime, - S::Unpack(std::span(r.value))); + S::Unpack(std::span(r.value), info...)); } } return rv; @@ -157,7 +161,7 @@ class StructSubscriber : public Subscriber { * @return Topic */ TopicType GetTopic() const { - return StructTopic{::nt::GetTopicFromHandle(m_subHandle)}; + return StructTopic{::nt::GetTopicFromHandle(m_subHandle)}; } private: @@ -167,12 +171,13 @@ class StructSubscriber : public Subscriber { /** * NetworkTables struct-encoded value publisher. */ -template +template + requires wpi::StructSerializable class StructPublisher : public Publisher { - using S = wpi::Struct; + using S = wpi::Struct; public: - using TopicType = StructTopic; + using TopicType = StructTopic; using ValueType = T; using ParamType = const T&; @@ -206,20 +211,22 @@ class StructPublisher : public Publisher { * Publish a new value. * * @param value value to publish + * @param info optional struct type info * @param time timestamp; 0 indicates current NT time should be used */ - void Set(const T& value, int64_t time = 0) { + void Set(const T& value, const I&... info, int64_t time = 0) { if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); + GetTopic().GetInstance().template AddStructSchema(info...); } - if constexpr (wpi::is_constexpr([] { S::GetSize(); })) { - uint8_t buf[S::GetSize()]; - S::Pack(buf, value); + if constexpr (sizeof...(I) == 0 && + wpi::is_constexpr([&] { S::GetSize(info...); })) { + uint8_t buf[S::GetSize(info...)]; + S::Pack(buf, value, info...); ::nt::SetRaw(m_pubHandle, buf, time); } else { wpi::SmallVector buf; - buf.resize_for_overwrite(S::GetSize()); - S::Pack(buf, value); + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, value, info...); ::nt::SetRaw(m_pubHandle, buf, time); } } @@ -230,19 +237,21 @@ class StructPublisher : public Publisher { * published value. * * @param value value + * @param info optional struct type info */ - void SetDefault(const T& value) { + void SetDefault(const T& value, const I&... info) { if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); + GetTopic().GetInstance().template AddStructSchema(info...); } - if constexpr (wpi::is_constexpr([] { S::GetSize(); })) { - uint8_t buf[S::GetSize()]; - S::Pack(buf, value); + if constexpr (sizeof...(I) == 0 && + wpi::is_constexpr([&] { S::GetSize(info...); })) { + uint8_t buf[S::GetSize(info...)]; + S::Pack(buf, value, info...); ::nt::SetDefaultRaw(m_pubHandle, buf); } else { wpi::SmallVector buf; - buf.resize_for_overwrite(S::GetSize()); - S::Pack(buf, value); + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, value, info...); ::nt::SetDefaultRaw(m_pubHandle, buf); } } @@ -253,7 +262,7 @@ class StructPublisher : public Publisher { * @return Topic */ TopicType GetTopic() const { - return StructTopic{::nt::GetTopicFromHandle(m_pubHandle)}; + return StructTopic{::nt::GetTopicFromHandle(m_pubHandle)}; } private: @@ -265,13 +274,14 @@ class StructPublisher : public Publisher { * * @note Unlike NetworkTableEntry, the entry goes away when this is destroyed. */ -template -class StructEntry final : public StructSubscriber, - public StructPublisher { +template + requires wpi::StructSerializable +class StructEntry final : public StructSubscriber, + public StructPublisher { public: - using SubscriberType = StructSubscriber; - using PublisherType = StructPublisher; - using TopicType = StructTopic; + using SubscriberType = StructSubscriber; + using PublisherType = StructPublisher; + using TopicType = StructTopic; using ValueType = T; using ParamType = const T&; @@ -287,8 +297,8 @@ class StructEntry final : public StructSubscriber, * @param defaultValue Default value */ StructEntry(NT_Entry handle, T defaultValue) - : StructSubscriber{handle, std::move(defaultValue)}, - StructPublisher{handle} {} + : StructSubscriber{handle, std::move(defaultValue)}, + StructPublisher{handle} {} /** * Determines if the native handle is valid. @@ -310,7 +320,7 @@ class StructEntry final : public StructSubscriber, * @return Topic */ TopicType GetTopic() const { - return StructTopic{::nt::GetTopicFromHandle(this->m_subHandle)}; + return StructTopic{::nt::GetTopicFromHandle(this->m_subHandle)}; } /** @@ -322,12 +332,13 @@ class StructEntry final : public StructSubscriber, /** * NetworkTables struct-encoded value topic. */ -template +template + requires wpi::StructSerializable class StructTopic final : public Topic { public: - using SubscriberType = StructSubscriber; - using PublisherType = StructPublisher; - using EntryType = StructEntry; + using SubscriberType = StructSubscriber; + using PublisherType = StructPublisher; + using EntryType = StructEntry; using ValueType = T; using ParamType = const T&; using TimestampedValueType = Timestamped; @@ -361,15 +372,17 @@ class StructTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options subscribe options * @return subscriber */ [[nodiscard]] SubscriberType Subscribe( - T defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructSubscriber{ - ::nt::Subscribe(m_handle, NT_RAW, wpi::GetStructTypeString(), - options), + T defaultValue, const I&... info, + const PubSubOptions& options = kDefaultPubSubOptions) { + return StructSubscriber{ + ::nt::Subscribe(m_handle, NT_RAW, + wpi::GetStructTypeString(info...), options), std::move(defaultValue)}; } @@ -385,13 +398,15 @@ class StructTopic final : public Topic { * do not match the topic's data type are dropped (ignored). To determine * if the data type matches, use the appropriate Topic functions. * + * @param info optional struct type info * @param options publish options * @return publisher */ [[nodiscard]] - PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) { - return StructPublisher{::nt::Publish( - m_handle, NT_RAW, wpi::GetStructTypeString(), options)}; + PublisherType Publish(const I&... info, + const PubSubOptions& options = kDefaultPubSubOptions) { + return StructPublisher{::nt::Publish( + m_handle, NT_RAW, wpi::GetStructTypeString(info...), options)}; } /** @@ -408,15 +423,17 @@ class StructTopic final : public Topic { * if the data type matches, use the appropriate Topic functions. * * @param properties JSON properties + * @param info optional struct type info * @param options publish options * @return publisher */ [[nodiscard]] PublisherType PublishEx( - const wpi::json& properties, + const wpi::json& properties, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructPublisher{::nt::PublishEx( - m_handle, NT_RAW, wpi::GetStructTypeString(), properties, options)}; + return StructPublisher{::nt::PublishEx( + m_handle, NT_RAW, wpi::GetStructTypeString(info...), + properties, options)}; } /** @@ -436,15 +453,16 @@ class StructTopic final : public Topic { * * @param defaultValue default value used when a default is not provided to a * getter function + * @param info optional struct type info * @param options publish and/or subscribe options * @return entry */ [[nodiscard]] - EntryType GetEntry(T defaultValue, + EntryType GetEntry(T defaultValue, const I&... info, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructEntry{ - ::nt::GetEntry(m_handle, NT_RAW, wpi::GetStructTypeString(), - options), + return StructEntry{ + ::nt::GetEntry(m_handle, NT_RAW, + wpi::GetStructTypeString(info...), options), std::move(defaultValue)}; } }; diff --git a/ntcore/src/test/native/cpp/StructTest.cpp b/ntcore/src/test/native/cpp/StructTest.cpp index 9cf0bc55f98..c7162e22c15 100644 --- a/ntcore/src/test/native/cpp/StructTest.cpp +++ b/ntcore/src/test/native/cpp/StructTest.cpp @@ -30,6 +30,18 @@ struct Outer2 { Inner2 inner; int c; }; + +struct ThingA { + int x; +}; + +struct ThingB { + int x; +}; + +struct Info1 { + int info; +}; } // namespace template <> @@ -110,6 +122,36 @@ struct wpi::Struct { } }; +template <> +struct wpi::Struct { + 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 data) { + return ThingA{.x = data[0]}; + } + static void Pack(std::span data, const ThingA& value) { + data[0] = value.x; + } +}; + +template <> +struct wpi::Struct { + 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 data, const Info1&) { + return ThingB{.x = data[0]}; + } + static void Pack(std::span data, const ThingB& value, const Info1&) { + data[0] = value.x; + } +}; + namespace nt { class StructTest : public ::testing::Test { @@ -293,4 +335,111 @@ TEST_F(StructTest, InnerArrayNonconstexpr) { ASSERT_EQ(vals[0].value[0].b, 2); } +TEST_F(StructTest, StructA) { + nt::StructTopic topic = inst.GetStructTopic("a"); + nt::StructPublisher pub = topic.Publish(); + nt::StructPublisher pub2 = topic.PublishEx({{}}); + nt::StructSubscriber sub = topic.Subscribe({}); + nt::StructEntry 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 topic = inst.GetStructArrayTopic("a"); + nt::StructArrayPublisher pub = topic.Publish(); + nt::StructArrayPublisher pub2 = topic.PublishEx({{}}); + nt::StructArraySubscriber sub = topic.Subscribe({}); + nt::StructArrayEntry 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> topic = inst.GetStructTopic>("a"); + nt::StructPublisher> pub = topic.Publish(); + nt::StructPublisher> pub2 = topic.PublishEx({{}}); + nt::StructSubscriber> sub = topic.Subscribe({}); + nt::StructEntry> entry = topic.GetEntry({}); + std::array 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 topic = inst.GetStructTopic("a"); + nt::StructPublisher pub = topic.Publish(info); + nt::StructPublisher pub2 = topic.PublishEx({{}}, info); + nt::StructSubscriber sub = topic.Subscribe({}, info); + nt::StructEntry entry = topic.GetEntry({}, info); + pub.SetDefault({}, info); + pub.Set({}, info, 5); + sub.Get(info); + sub.Get({}, info); + sub.GetAtomic(info); + sub.GetAtomic({}, info); + entry.SetDefault({}, info); + entry.Set({}, info, 6); + entry.Get({}, info); +} + +TEST_F(StructTest, StructArrayB) { + Info1 info; + nt::StructArrayTopic topic = inst.GetStructArrayTopic("a"); + nt::StructArrayPublisher pub = topic.Publish(info); + nt::StructArrayPublisher pub2 = topic.PublishEx({{}}, info); + nt::StructArraySubscriber sub = topic.Subscribe({}, info); + nt::StructArrayEntry entry = topic.GetEntry({}, info); + pub.SetDefault({{ThingB{}, ThingB{}}}, info); + pub.Set({{ThingB{}, ThingB{}}}, info, 5); + sub.Get(info); + sub.Get({}, info); + sub.GetAtomic(info); + sub.GetAtomic({}, info); + entry.SetDefault({{ThingB{}, ThingB{}}}, info); + entry.Set({{ThingB{}, ThingB{}}}, info, 6); + entry.Get({}, info); +} + +TEST_F(StructTest, StructFixedArrayB) { + Info1 info; + nt::StructTopic, Info1> topic = inst.GetStructTopic, Info1>("a"); + nt::StructPublisher, Info1> pub = topic.Publish(info); + nt::StructPublisher, Info1> pub2 = topic.PublishEx({{}}, info); + nt::StructSubscriber, Info1> sub = topic.Subscribe({}, info); + nt::StructEntry, Info1> entry = topic.GetEntry({}, info); + std::array arr; + pub.SetDefault(arr, info); + pub.Set(arr, info, 5); + sub.Get(info); + sub.Get(arr, info); + sub.GetAtomic(info); + sub.GetAtomic(arr, info); + entry.SetDefault(arr, info); + entry.Set(arr, info, 6); + entry.Get(arr, info); +} + } // namespace nt diff --git a/wpiutil/src/main/native/include/wpi/DataLog.h b/wpiutil/src/main/native/include/wpi/DataLog.h index c78deafce16..125e77fcd92 100644 --- a/wpiutil/src/main/native/include/wpi/DataLog.h +++ b/wpiutil/src/main/native/include/wpi/DataLog.h @@ -263,16 +263,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 - void AddStructSchema(int64_t timestamp = 0) { + template + requires StructSerializable + void AddStructSchema(const I&... info, int64_t timestamp = 0) { if (timestamp == 0) { timestamp = Now(); } - ForEachStructSchema([this, timestamp](auto typeString, auto schema) { - AddSchema(typeString, "structschema", schema, timestamp); - }); + ForEachStructSchema( + [this, timestamp](auto typeString, auto schema) { + this->AddSchema(typeString, "structschema", schema, timestamp); + }, + info...); } /** @@ -946,36 +950,40 @@ class StringArrayLogEntry : public DataLogEntry { /** * Log raw struct serializable objects. */ -template +template + requires StructSerializable class StructLogEntry : public DataLogEntry { - using S = Struct; + using S = Struct; 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, const I&... info, + int64_t timestamp = 0) + : StructLogEntry{log, name, {}, info..., timestamp} {} StructLogEntry(DataLog& log, std::string_view name, std::string_view metadata, - int64_t timestamp = 0) { + const I&... info, int64_t timestamp = 0) { m_log = &log; - log.AddStructSchema(timestamp); - m_entry = log.Start(name, S::kTypeString, metadata, timestamp); + log.AddStructSchema(info..., timestamp); + m_entry = log.Start(name, S::GetTypeString(info...), metadata, timestamp); } /** * Appends a record to the log. * * @param data Data to record + * @param info optional struct type info * @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()]; + void Append(const T& data, const I&... info, int64_t timestamp = 0) { + if constexpr (sizeof...(I) == 0 && + wpi::is_constexpr([&] { S::GetSize(info...); })) { + uint8_t buf[S::GetSize(info...)]; S::Pack(buf, data); m_log->AppendRaw(m_entry, buf, timestamp); } else { wpi::SmallVector buf; - buf.resize_for_overwrite(S::GetSize()); - S::Pack(buf, data); + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, data, info...); m_log->AppendRaw(m_entry, buf, timestamp); } } @@ -984,28 +992,31 @@ class StructLogEntry : public DataLogEntry { /** * Log raw struct serializable array of objects. */ -template +template + requires StructSerializable class StructArrayLogEntry : public DataLogEntry { - using S = Struct; + using S = Struct; public: StructArrayLogEntry() = default; - StructArrayLogEntry(DataLog& log, std::string_view name, + StructArrayLogEntry(DataLog& log, std::string_view name, const I&... info, int64_t timestamp = 0) - : StructArrayLogEntry{log, name, {}, timestamp} {} + : StructArrayLogEntry{log, name, {}, info..., timestamp} {} StructArrayLogEntry(DataLog& log, std::string_view name, - std::string_view metadata, int64_t timestamp = 0) { + std::string_view metadata, const I&... info, + int64_t timestamp = 0) { m_log = &log; - log.AddStructSchema(timestamp); - m_entry = - log.Start(name, MakeStructArrayTypeString(), - metadata, timestamp); + log.AddStructSchema(info..., timestamp); + m_entry = log.Start( + name, MakeStructArrayTypeString(info...), + metadata, timestamp); } /** * Appends a record to the log. * * @param data Data to record + * @param info optional struct type info * @param timestamp Time stamp (may be 0 to indicate now) */ template @@ -1013,25 +1024,29 @@ class StructArrayLogEntry : public DataLogEntry { requires std::ranges::range && std::convertible_to, T> #endif - void Append(U&& data, int64_t timestamp = 0) { - m_buf.Write(std::forward(data), [&](auto bytes) { - m_log->AppendRaw(m_entry, bytes, timestamp); - }); + void Append(U&& data, const I&... info, int64_t timestamp = 0) { + m_buf.Write( + std::forward(data), + [&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); }, + info...); } /** * Appends a record to the log. * * @param data Data to record + * @param info optional struct type info * @param timestamp Time stamp (may be 0 to indicate now) */ - void Append(std::span data, int64_t timestamp = 0) { + void Append(std::span data, const I&... info, + int64_t timestamp = 0) { m_buf.Write( - data, [&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); }); + data, [&](auto bytes) { m_log->AppendRaw(m_entry, bytes, timestamp); }, + info...); } private: - StructArrayBuffer m_buf; + StructArrayBuffer m_buf; }; /** diff --git a/wpiutil/src/main/native/include/wpi/struct/Struct.h b/wpiutil/src/main/native/include/wpi/struct/Struct.h index 58aca475d9e..9c87f0cdc1b 100644 --- a/wpiutil/src/main/native/include/wpi/struct/Struct.h +++ b/wpiutil/src/main/native/include/wpi/struct/Struct.h @@ -32,8 +32,9 @@ namespace wpi { * StructSerializable concept. * * @tparam T type to serialize/deserialize + * @tparam I optional struct type info */ -template +template struct Struct {}; /** @@ -63,23 +64,31 @@ struct Struct {}; * If the struct has nested structs, implementations should also meet the * requirements of HasNestedStruct. */ -template +template concept StructSerializable = requires(std::span in, - std::span out, T&& value) { - typename Struct>; + std::span out, T&& value, + const I&... info) { + typename Struct, + typename std::remove_cvref_t...>; { - Struct>::GetTypeString() + Struct, + typename std::remove_cvref_t...>::GetTypeString(info...) } -> std::convertible_to; { - Struct>::GetSize() + Struct, + typename std::remove_cvref_t...>::GetSize(info...) } -> std::convertible_to; { - Struct>::GetSchema() + Struct, + typename std::remove_cvref_t...>::GetSchema(info...) } -> std::convertible_to; { - Struct>::Unpack(in) + Struct, + typename std::remove_cvref_t...>::Unpack(in, info...) } -> std::same_as>; - Struct>::Pack(out, std::forward(value)); + Struct, + typename std::remove_cvref_t...>::Pack(out, std::forward(value), + info...); }; /** @@ -89,10 +98,12 @@ concept StructSerializable = requires(std::span in, * wpi::Struct static member `void UnpackInto(T*, std::span)` * to update the pointed-to T with the contents of the span. */ -template +template concept MutableStructSerializable = - StructSerializable && requires(T* out, std::span in) { - Struct>::UnpackInto(out, in); + StructSerializable && + requires(T* out, std::span in, const I&... info) { + Struct, + typename std::remove_cvref_t...>::UnpackInto(out, in, info...); }; /** @@ -104,11 +115,13 @@ concept MutableStructSerializable = * fn)` (or equivalent) and call ForEachNestedStruct on each nested struct * type. */ -template +template concept HasNestedStruct = - StructSerializable && - requires(function_ref fn) { - Struct>::ForEachNested(fn); + StructSerializable && + requires(function_ref fn, + const I&... info) { + Struct, + typename std::remove_cvref_t...>::ForEachNested(fn, info...); }; /** @@ -116,11 +129,13 @@ concept HasNestedStruct = * * @tparam T object type * @param data raw struct data + * @param info optional struct type info * @return Deserialized object */ -template -inline T UnpackStruct(std::span data) { - return Struct::Unpack(data); +template +inline T UnpackStruct(std::span data, const I&... info) { + using S = Struct...>; + return S::Unpack(data, info...); } /** @@ -130,11 +145,14 @@ inline T UnpackStruct(std::span data) { * @tparam T object type * @tparam Offset starting offset * @param data raw struct data + * @param info optional struct type info * @return Deserialized object */ -template -inline T UnpackStruct(std::span data) { - return Struct::Unpack(data.subspan(Offset)); +template + requires StructSerializable +inline T UnpackStruct(std::span data, const I&... info) { + using S = Struct...>; + return S::Unpack(data.subspan(Offset), info...); } /** @@ -142,10 +160,14 @@ inline T UnpackStruct(std::span data) { * * @param data struct storage (mutable, output) * @param value object + * @param info optional struct type info */ -template -inline void PackStruct(std::span data, T&& value) { - Struct>::Pack(data, std::forward(value)); +template + requires StructSerializable +inline void PackStruct(std::span data, T&& value, const I&... info) { + using S = Struct, + typename std::remove_cvref_t...>; + S::Pack(data, std::forward(value), info...); } /** @@ -155,11 +177,14 @@ inline void PackStruct(std::span data, T&& value) { * @tparam Offset starting offset * @param data struct storage (mutable, output) * @param value object + * @param info optional struct type info */ -template -inline void PackStruct(std::span data, T&& value) { - Struct>::Pack(data.subspan(Offset), - std::forward(value)); +template + requires StructSerializable +inline void PackStruct(std::span data, T&& value, const I&... info) { + using S = Struct, + typename std::remove_cvref_t...>; + S::Pack(data.subspan(Offset), std::forward(value), info...); } /** @@ -167,13 +192,17 @@ inline void PackStruct(std::span data, T&& value) { * * @param out object (output) * @param data raw struct data + * @param info optional struct type info */ -template -inline void UnpackStructInto(T* out, std::span data) { - if constexpr (MutableStructSerializable) { - Struct::UnpackInto(out, data); +template + requires StructSerializable +inline void UnpackStructInto(T* out, std::span data, + const I&... info) { + using S = Struct...>; + if constexpr (MutableStructSerializable) { + S::UnpackInto(out, data, info...); } else { - *out = UnpackStruct(data); + *out = UnpackStruct(data, info...); } } @@ -185,13 +214,17 @@ inline void UnpackStructInto(T* out, std::span data) { * @tparam Offset starting offset * @param out object (output) * @param data raw struct data + * @param info optional struct type info */ -template -inline void UnpackStructInto(T* out, std::span data) { - if constexpr (MutableStructSerializable) { - Struct::UnpackInto(out, data.subspan(Offset)); +template + requires StructSerializable +inline void UnpackStructInto(T* out, std::span data, + const I&... info) { + using S = Struct...>; + if constexpr (MutableStructSerializable) { + S::UnpackInto(out, data.subspan(Offset), info...); } else { - *out = UnpackStruct(data); + *out = UnpackStruct(data, info...); } } @@ -199,28 +232,37 @@ inline void UnpackStructInto(T* out, std::span 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 -constexpr auto GetStructTypeString() { - return Struct::GetTypeString(); +template + requires StructSerializable +constexpr auto GetStructTypeString(const I&... info) { + using S = Struct...>; + 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 -constexpr size_t GetStructSize() { - return Struct::GetSize(); +template + requires StructSerializable +constexpr size_t GetStructSize(const I&... info) { + using S = Struct...>; + return S::GetSize(info...); } -template -constexpr auto MakeStructArrayTypeString() { - if constexpr (is_constexpr([] { Struct::GetTypeString(); })) { - constexpr auto typeString = Struct::GetTypeString(); +template + requires StructSerializable +constexpr auto MakeStructArrayTypeString(const I&... info) { + using S = Struct...>; + 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 +277,20 @@ constexpr auto MakeStructArrayTypeString() { } } else { if constexpr (N == std::dynamic_extent) { - return fmt::format("{}[]", Struct::GetTypeString()); + return fmt::format("{}[]", S::GetTypeString(info...)); } else { - return fmt::format("{}[{}]", Struct::GetTypeString(), N); + return fmt::format("{}[{}]", S::GetTypeString(info...), N); } } } -template -consteval auto MakeStructArraySchema() { - if constexpr (is_constexpr([] { Struct::GetSchema(); })) { - constexpr auto schema = Struct::GetSchema(); +template + requires StructSerializable +constexpr auto MakeStructArraySchema(const I&... info) { + using S = Struct...>; + 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 +303,45 @@ consteval auto MakeStructArraySchema() { } } else { if constexpr (N == std::dynamic_extent) { - return fmt::format("{}[]", Struct::GetSchema()); + return fmt::format("{}[]", S::GetSchema(info...)); } else { - return fmt::format("{}[{}]", Struct::GetSchema(), N); + return fmt::format("{}[{}]", S::GetSchema(info...), N); } } } -template -constexpr std::string_view GetStructSchema() { - return Struct::GetSchema(); +template + requires StructSerializable +constexpr std::string_view GetStructSchema(const I&... info) { + using S = Struct...>; + return S::GetSchema(info...); } -template -constexpr std::span GetStructSchemaBytes() { - auto schema = Struct::GetSchema(); +template + requires StructSerializable +constexpr std::span GetStructSchemaBytes(const I&... info) { + using S = Struct...>; + auto schema = S::GetSchema(info...); return {reinterpret_cast(schema.data()), schema.size()}; } -template +template + requires StructSerializable void ForEachStructSchema( - std::invocable auto fn) { - if constexpr (HasNestedStruct) { - Struct>::ForEachNested(fn); + std::invocable auto fn, + const I&... info) { + using S = Struct, + typename std::remove_cvref_t...>; + if constexpr (HasNestedStruct) { + S::ForEachNested(fn, info...); } - fn(Struct::GetTypeString(), Struct::GetSchema()); + fn(S::GetTypeString(info...), S::GetSchema(info...)); } -template +template + requires StructSerializable class StructArrayBuffer { - using S = Struct; + using S = Struct; public: StructArrayBuffer() = default; @@ -306,15 +360,15 @@ class StructArrayBuffer { std::convertible_to, T> && #endif std::invocable> - 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{std::to_address(out), size}, - std::forward(val)); + std::forward(val), info...); out += size; } func(std::span{buf, out}); @@ -324,7 +378,7 @@ class StructArrayBuffer { auto out = m_buf.begin(); for (auto&& val : data) { S::Pack(std::span{std::to_address(out), size}, - std::forward(val)); + std::forward(val), info...); out += size; } func(m_buf); @@ -339,39 +393,48 @@ class StructArrayBuffer { /** * Raw struct support for fixed-size arrays of other structs. */ -template -struct Struct> { - static constexpr auto GetTypeString() { - return MakeStructArrayTypeString(); - } - static constexpr size_t GetSize() { return N * GetStructSize(); } - static constexpr auto GetSchema() { return MakeStructArraySchema(); } - static std::array Unpack(std::span data) { - auto size = GetStructSize(); +template + requires StructSerializable +struct Struct, I...> { + static constexpr auto GetTypeString(const I&... info) { + return MakeStructArrayTypeString(info...); + } + static constexpr size_t GetSize(const I&... info) { + return N * GetStructSize(info...); + } + static constexpr auto GetSchema(const I&... info) { + return MakeStructArraySchema(info...); + } + static std::array Unpack(std::span data, + const I&... info) { + auto size = GetStructSize(info...); std::array result; for (size_t i = 0; i < N; ++i) { - result[i] = UnpackStruct(data); + result[i] = UnpackStruct(data, info...); data = data.subspan(size); } return result; } - static void Pack(std::span data, std::span values) { - auto size = GetStructSize(); + static void Pack(std::span data, std::span values, + const I&... info) { + auto size = GetStructSize(info...); std::span unsizedData = data; for (auto&& val : values) { - PackStruct(unsizedData, val); + PackStruct(unsizedData, val, info...); unsizedData = unsizedData.subspan(size); } } - static void UnpackInto(std::array* out, std::span data) { - UnpackInto(std::span{*out}, data); + static void UnpackInto(std::array* out, std::span data, + const I&... info) { + UnpackInto(std::span{*out}, data, info...); } // alternate span-based function - static void UnpackInto(std::span out, std::span data) { - auto size = GetStructSize(); + static void UnpackInto(std::span out, std::span data, + const I&... info) { + auto size = GetStructSize(info...); std::span unsizedData = data; for (size_t i = 0; i < N; ++i) { - UnpackStructInto(&out[i], unsizedData); + UnpackStructInto(&out[i], unsizedData, info...); unsizedData = unsizedData.subspan(size); } } diff --git a/wpiutil/src/test/native/cpp/DataLogTest.cpp b/wpiutil/src/test/native/cpp/DataLogTest.cpp new file mode 100644 index 00000000000..997039e1f89 --- /dev/null +++ b/wpiutil/src/test/native/cpp/DataLogTest.cpp @@ -0,0 +1,233 @@ +// 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 + +#include + +#include "wpi/DataLog.h" + +namespace { +struct ThingA { + int x; +}; + +struct ThingB { + int x; +}; + +struct ThingC { + int x; +}; + +struct Info1 { + int info; +}; + +struct Info2 { + int info; +}; +} // namespace + +template <> +struct wpi::Struct { + 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 data) { + return ThingA{.x = data[0]}; + } + static void Pack(std::span data, const ThingA& value) { + data[0] = value.x; + } +}; + +template <> +struct wpi::Struct { + 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 data, const Info1&) { + return ThingB{.x = data[0]}; + } + static void Pack(std::span data, const ThingB& value, const Info1&) { + data[0] = value.x; + } +}; + +template <> +struct wpi::Struct { + 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 data) { + return ThingC{.x = data[0]}; + } + static void Pack(std::span data, const ThingC& value) { + data[0] = value.x; + } +}; + +template <> +struct wpi::Struct { + 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 data, const Info1&) { + return ThingC{.x = data[0]}; + } + static void Pack(std::span data, const ThingC& value, const Info1&) { + data[0] = value.x; + } +}; + +template <> +struct wpi::Struct { + 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 data, const Info2&) { + return ThingC{.x = data[0]}; + } + static void Pack(std::span data, const ThingC& value, const Info2&) { + data[0] = value.x; + } +}; + +static_assert(wpi::StructSerializable); +static_assert(!wpi::StructSerializable); + +static_assert(!wpi::StructSerializable); +static_assert(wpi::StructSerializable); +static_assert(!wpi::StructSerializable); + +static_assert(wpi::StructSerializable); +static_assert(wpi::StructSerializable); +static_assert(wpi::StructSerializable); + +TEST(DataLogTest, StructA) { + wpi::log::DataLog log{[](auto) {}}; + wpi::log::StructLogEntry entry{log, "a", 5}; + entry.Append(ThingA{}); + entry.Append(ThingA{}, 7); +} + +TEST(DataLogTest, StructArrayA) { + wpi::log::DataLog log{[](auto) {}}; + wpi::log::StructArrayLogEntry entry{log, "a", 5}; + entry.Append({{ThingA{}, ThingA{}}}); + entry.Append({{ThingA{}, ThingA{}}}, 7); +} + +TEST(DataLogTest, StructFixedArrayA) { + wpi::log::DataLog log{[](auto) {}}; + wpi::log::StructLogEntry> entry{log, "a", 5}; + std::array arr; + entry.Append(arr); + entry.Append(arr, 7); +} + +TEST(DataLogTest, StructB) { + wpi::log::DataLog log{[](auto) {}}; + Info1 info; + wpi::log::StructLogEntry entry{log, "b", info, 5}; + entry.Append(ThingB{}, info); + entry.Append(ThingB{}, info, 7); +} + +TEST(DataLogTest, StructArrayB) { + wpi::log::DataLog log{[](auto) {}}; + Info1 info; + wpi::log::StructArrayLogEntry entry{log, "a", info, 5}; + entry.Append({{ThingB{}, ThingB{}}}, info); + entry.Append({{ThingB{}, ThingB{}}}, info, 7); +} + +TEST(DataLogTest, StructFixedArrayB) { + wpi::log::DataLog log{[](auto) {}}; + Info1 info; + wpi::log::StructLogEntry, Info1> entry{log, "a", info, + 5}; + std::array arr; + entry.Append(arr, info); + entry.Append(arr, info, 7); +} + +TEST(DataLogTest, StructC) { + wpi::log::DataLog log{[](auto) {}}; + { + wpi::log::StructLogEntry entry{log, "c", 5}; + entry.Append(ThingC{}); + entry.Append(ThingC{}, 7); + } + { + Info1 info; + wpi::log::StructLogEntry entry{log, "c1", info, 5}; + entry.Append(ThingC{}, info); + entry.Append(ThingC{}, info, 7); + } + { + Info2 info; + wpi::log::StructLogEntry entry{log, "c2", info, 5}; + entry.Append(ThingC{}, info); + entry.Append(ThingC{}, info, 7); + } +} + +TEST(DataLogTest, StructArrayC) { + wpi::log::DataLog log{[](auto) {}}; + { + wpi::log::StructArrayLogEntry entry{log, "c", 5}; + entry.Append({{ThingC{}, ThingC{}}}); + entry.Append({{ThingC{}, ThingC{}}}, 7); + } + { + Info1 info; + wpi::log::StructArrayLogEntry entry{log, "c1", info, 5}; + entry.Append({{ThingC{}, ThingC{}}}, info); + entry.Append({{ThingC{}, ThingC{}}}, info, 7); + } + { + Info2 info; + wpi::log::StructArrayLogEntry entry{log, "c2", info, 5}; + entry.Append({{ThingC{}, ThingC{}}}, info); + entry.Append({{ThingC{}, ThingC{}}}, info, 7); + } +} + +TEST(DataLogTest, StructFixedArrayC) { + wpi::log::DataLog log{[](auto) {}}; + std::array arr; + { + wpi::log::StructLogEntry> entry{log, "c", 5}; + entry.Append(arr); + entry.Append(arr, 7); + } + { + Info1 info; + wpi::log::StructLogEntry, Info1> entry{log, "c1", + info, 5}; + entry.Append(arr, info); + entry.Append(arr, info, 7); + } + { + Info2 info; + wpi::log::StructLogEntry, Info2> entry{log, "c2", + info, 5}; + entry.Append(arr, info); + entry.Append(arr, info, 7); + } +}