diff --git a/ntcore/src/main/native/include/networktables/NetworkTable.h b/ntcore/src/main/native/include/networktables/NetworkTable.h index e03ea9e99a..e2d14212ba 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 06e2cd6483..cf8f8e116d 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; @@ -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 - StructTopic GetStructTopic(std::string_view name) const; + template + requires wpi::StructSerializable + StructTopic 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 - StructArrayTopic GetStructArrayTopic(std::string_view name) const; + template + requires wpi::StructSerializable + StructArrayTopic 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 - 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 b4bbee16c0..e583e59702 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc +++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc @@ -44,16 +44,18 @@ inline ProtobufTopic NetworkTableInstance::GetProtobufTopic( return ProtobufTopic{GetTopic(name)}; } -template -inline StructTopic NetworkTableInstance::GetStructTopic( - std::string_view name) const { - return StructTopic{GetTopic(name)}; +template + requires wpi::StructSerializable +inline StructTopic NetworkTableInstance::GetStructTopic( + std::string_view name, I... info) const { + return StructTopic{GetTopic(name), std::move(info)...}; } -template -inline StructArrayTopic NetworkTableInstance::GetStructArrayTopic( - std::string_view name) const { - return StructArrayTopic{GetTopic(name)}; +template + requires wpi::StructSerializable +inline StructArrayTopic NetworkTableInstance::GetStructArrayTopic( + std::string_view name, I... info) const { + return StructArrayTopic{GetTopic(name), std::move(info)...}; } inline std::vector NetworkTableInstance::GetTopics() { @@ -272,11 +274,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 f3529eafd2..667570bd7e 100644 --- a/ntcore/src/main/native/include/networktables/StructArrayTopic.h +++ b/ntcore/src/main/native/include/networktables/StructArrayTopic.h @@ -7,9 +7,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -24,18 +26,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; @@ -48,15 +52,17 @@ class StructArraySubscriber : public Subscriber { * * @param handle Native handle * @param defaultValue Default value + * @param info optional struct type info */ template #if __cpp_lib_ranges >= 201911L requires std::ranges::range && std::convertible_to, 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 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(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{std::to_address(in), size})); + std::apply( + [&](const I&... info) { + rv.value.emplace_back(S::Unpack( + std::span{std::to_address(in), size}, info...)); + }, + m_info); } return rv; } @@ -146,7 +156,7 @@ class StructArraySubscriber : public Subscriber { */ TimestampedValueType GetAtomic(std::span defaultValue) const { wpi::SmallVector 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{std::to_address(in), size})); + std::apply( + [&](const I&... info) { + rv.value.emplace_back(S::Unpack( + std::span{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 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{std::to_address(in), size})); + std::apply( + [&](const I&... info) { + values.emplace_back( + S::Unpack(std::span{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{::nt::GetTopicFromHandle(m_subHandle)}; + return std::apply( + [&](const I&... info) { + return StructArrayTopic{ + ::nt::GetTopicFromHandle(m_subHandle), info...}; + }, + m_info); } private: ValueType m_defaultValue; + [[no_unique_address]] std::tuple m_info; }; /** * 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; @@ -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, T> #endif void Set(U&& value, int64_t time = 0) { - if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); - } - m_buf.Write(std::forward(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(info...); + } + m_buf.Write( + std::forward(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 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, T> #endif void SetDefault(U&& value) { - if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) { - GetTopic().GetInstance().template AddStructSchema(); - } - m_buf.Write(std::forward(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(info...); + } + m_buf.Write( + std::forward(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 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{::nt::GetTopicFromHandle(m_pubHandle)}; + return std::apply( + [&](const I&... info) { + return StructArrayTopic{ + ::nt::GetTopicFromHandle(m_pubHandle), info...}; + }, + m_info); } private: - wpi::StructArrayBuffer m_buf; + wpi::StructArrayBuffer m_buf; std::atomic_bool m_schemaPublished{false}; + [[no_unique_address]] std::tuple m_info; }; /** @@ -328,13 +389,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; @@ -348,15 +410,16 @@ class StructArrayEntry final : public StructArraySubscriber, * * @param handle Native handle * @param defaultValue Default value + * @param info optional struct type info */ template #if __cpp_lib_ranges >= 201911L requires std::ranges::range && std::convertible_to, T> #endif - StructArrayEntry(NT_Entry handle, U&& defaultValue) - : StructArraySubscriber{handle, defaultValue}, - StructArrayPublisher{handle} { + StructArrayEntry(NT_Entry handle, U&& defaultValue, const I&... info) + : StructArraySubscriber{handle, defaultValue, info...}, + StructArrayPublisher{handle, info...} { } /** @@ -379,7 +442,7 @@ class StructArrayEntry final : public StructArraySubscriber, * @return Topic */ TopicType GetTopic() const { - return StructArrayTopic{::nt::GetTopicFromHandle(this->m_subHandle)}; + return StructArraySubscriber::GetTopic(); } /** @@ -391,12 +454,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; @@ -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{ - ::nt::Subscribe( - m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options), - defaultValue}; + return std::apply( + [&](const I&... info) { + return StructArraySubscriber{ + ::nt::Subscribe( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + info...), + options), + defaultValue, info...}; + }, + m_info); } /** @@ -467,11 +541,17 @@ class StructArrayTopic final : public Topic { SubscriberType Subscribe( std::span defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArraySubscriber{ - ::nt::Subscribe( - m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options), - defaultValue}; + return std::apply( + [&](const I&... info) { + return StructArraySubscriber{ + ::nt::Subscribe( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + info...), + options), + defaultValue, info...}; + }, + m_info); } /** @@ -491,9 +571,17 @@ class StructArrayTopic final : public Topic { */ [[nodiscard]] PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayPublisher{::nt::Publish( - m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), options)}; + return std::apply( + [&](const I&... info) { + return StructArrayPublisher{ + ::nt::Publish( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + 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{::nt::PublishEx( - m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), properties, - options)}; + return std::apply( + [&](const I&... info) { + return StructArrayPublisher{ + ::nt::PublishEx( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + 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{ - ::nt::GetEntry(m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), - options), - defaultValue}; + return std::apply( + [&](const I&... info) { + return StructArrayEntry{ + ::nt::GetEntry( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + info...), + options), + defaultValue, info...}; + }, + m_info); } /** @@ -581,12 +682,21 @@ class StructArrayTopic final : public Topic { [[nodiscard]] EntryType GetEntry(std::span defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) { - return StructArrayEntry{ - ::nt::GetEntry(m_handle, NT_RAW, - wpi::MakeStructArrayTypeString(), - options), - defaultValue}; + return std::apply( + [&](const I&... info) { + return StructArrayEntry{ + ::nt::GetEntry( + m_handle, NT_RAW, + wpi::MakeStructArrayTypeString( + info...), + options), + defaultValue, info...}; + }, + m_info); } + + private: + [[no_unique_address]] std::tuple m_info; }; } // namespace nt diff --git a/ntcore/src/main/native/include/networktables/StructTopic.h b/ntcore/src/main/native/include/networktables/StructTopic.h index 08d7a770b0..b69d0af62b 100644 --- a/ntcore/src/main/native/include/networktables/StructTopic.h +++ b/ntcore/src/main/native/include/networktables/StructTopic.h @@ -8,8 +8,10 @@ #include #include +#include #include #include +#include #include #include @@ -23,25 +25,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; @@ -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 buf; + wpi::SmallVector 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 buf; + wpi::SmallVector 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 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(r.value))); + std::apply( + [&](const I&... info) { + rv.emplace_back( + r.time, r.serverTime, + S::Unpack(std::span(r.value), info...)); + }, + m_info); } } return rv; @@ -157,22 +170,29 @@ class StructSubscriber : public Subscriber { * @return Topic */ TopicType GetTopic() const { - return StructTopic{::nt::GetTopicFromHandle(m_subHandle)}; + return std::apply( + [&](const I&... info) { + return StructTopic{::nt::GetTopicFromHandle(m_subHandle), + info...}; + }, + m_info); } private: ValueType m_defaultValue; + [[no_unique_address]] std::tuple m_info; }; /** * 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&; @@ -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(); + std::apply( + [&](const I&... info) { + GetTopic().GetInstance().template AddStructSchema(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 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 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(); + std::apply( + [&](const I&... info) { + GetTopic().GetInstance().template AddStructSchema(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 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 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{::nt::GetTopicFromHandle(m_pubHandle)}; + return std::apply( + [&](const I&... info) { + return StructTopic{::nt::GetTopicFromHandle(m_pubHandle), + info...}; + }, + m_info); } private: std::atomic_bool m_schemaPublished{false}; + [[no_unique_address]] std::tuple m_info; }; /** @@ -265,13 +309,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&; @@ -285,10 +330,11 @@ class StructEntry final : public StructSubscriber, * * @param handle Native handle * @param defaultValue Default value + * @param info optional struct type info */ - StructEntry(NT_Entry handle, T defaultValue) - : StructSubscriber{handle, std::move(defaultValue)}, - StructPublisher{handle} {} + StructEntry(NT_Entry handle, T defaultValue, const I&... info) + : StructSubscriber{handle, std::move(defaultValue), info...}, + StructPublisher{handle, info...} {} /** * Determines if the native handle is valid. @@ -309,9 +355,7 @@ class StructEntry final : public StructSubscriber, * * @return Topic */ - TopicType GetTopic() const { - return StructTopic{::nt::GetTopicFromHandle(this->m_subHandle)}; - } + TopicType GetTopic() const { return StructSubscriber::GetTopic(); } /** * Stops publishing the entry if it's published. @@ -322,12 +366,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; @@ -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{ - ::nt::Subscribe(m_handle, NT_RAW, wpi::GetStructTypeString(), - options), - std::move(defaultValue)}; + return std::apply( + [&](const I&... info) { + return StructSubscriber{ + ::nt::Subscribe(m_handle, NT_RAW, + wpi::GetStructTypeString(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{::nt::Publish( - m_handle, NT_RAW, wpi::GetStructTypeString(), options)}; + return std::apply( + [&](const I&... info) { + return StructPublisher{ + ::nt::Publish(m_handle, NT_RAW, + wpi::GetStructTypeString(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{::nt::PublishEx( - m_handle, NT_RAW, wpi::GetStructTypeString(), properties, options)}; + return std::apply( + [&](const I&... info) { + return StructPublisher{ + ::nt::PublishEx(m_handle, NT_RAW, + wpi::GetStructTypeString(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{ - ::nt::GetEntry(m_handle, NT_RAW, wpi::GetStructTypeString(), - options), - std::move(defaultValue)}; + return std::apply( + [&](const I&... info) { + return StructEntry{ + ::nt::GetEntry(m_handle, NT_RAW, + wpi::GetStructTypeString(info...), + options), + std::move(defaultValue), info...}; + }, + m_info); } + + private: + [[no_unique_address]] std::tuple m_info; }; } // namespace nt diff --git a/ntcore/src/test/native/cpp/StructTest.cpp b/ntcore/src/test/native/cpp/StructTest.cpp index 9cf0bc55f9..55b6a906c3 100644 --- a/ntcore/src/test/native/cpp/StructTest.cpp +++ b/ntcore/src/test/native/cpp/StructTest.cpp @@ -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 { } }; +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,116 @@ 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("b", info); + 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, StructArrayB) { + Info1 info; + nt::StructArrayTopic topic = + inst.GetStructArrayTopic("b", info); + nt::StructArrayPublisher pub = topic.Publish(); + nt::StructArrayPublisher pub2 = topic.PublishEx({{}}); + nt::StructArraySubscriber sub = topic.Subscribe({}); + nt::StructArrayEntry 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, Info1> topic = + inst.GetStructTopic, Info1>("b", info); + nt::StructPublisher, Info1> pub = topic.Publish(); + nt::StructPublisher, Info1> pub2 = + topic.PublishEx({{}}); + nt::StructSubscriber, Info1> sub = topic.Subscribe({}); + nt::StructEntry, Info1> 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); +} + } // namespace nt diff --git a/wpiutil/src/main/native/include/wpi/DataLog.h b/wpiutil/src/main/native/include/wpi/DataLog.h index 8663369949..2f79a3a17b 100644 --- a/wpiutil/src/main/native/include/wpi/DataLog.h +++ b/wpiutil/src/main/native/include/wpi/DataLog.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -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 - 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,19 +951,22 @@ 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, 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(timestamp); - m_entry = log.Start(name, S::kTypeString, metadata, timestamp); + log.AddStructSchema(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 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 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 m_info; }; /** * 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, 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(timestamp); - m_entry = - log.Start(name, MakeStructArrayTypeString(), - metadata, timestamp); + log.AddStructSchema(info..., timestamp); + m_entry = log.Start( + name, MakeStructArrayTypeString(info...), + metadata, timestamp); } /** @@ -1014,9 +1030,14 @@ class StructArrayLogEntry : public DataLogEntry { 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); - }); + std::apply( + [&](const I&... info) { + m_buf.Write( + std::forward(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 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 m_buf; + StructArrayBuffer m_buf; + [[no_unique_address]] std::tuple m_info; }; /** diff --git a/wpiutil/src/main/native/include/wpi/struct/Struct.h b/wpiutil/src/main/native/include/wpi/struct/Struct.h index 58aca475d9..085b50bb34 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,14 @@ 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 + requires StructSerializable +inline T UnpackStruct(std::span data, const I&... info) { + using S = Struct...>; + return S::Unpack(data, info...); } /** @@ -130,11 +146,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 +161,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 +178,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 +193,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 +215,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 +233,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 +278,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 +304,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 +361,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 +379,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 +394,48 @@ class StructArrayBuffer { /** * Raw struct support for fixed-size arrays of other structs. */ -template -struct Struct> { - static constexpr auto GetTypeString() { - return MakeStructArrayTypeString(); +template + requires StructSerializable +struct Struct, I...> { + static constexpr auto GetTypeString(const I&... info) { + return MakeStructArrayTypeString(info...); } - static constexpr size_t GetSize() { return N * GetStructSize(); } - static constexpr auto GetSchema() { return MakeStructArraySchema(); } - static std::array Unpack(std::span data) { - auto size = GetStructSize(); + 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 index 486210115c..d06ad339a3 100644 --- a/wpiutil/src/test/native/cpp/DataLogTest.cpp +++ b/wpiutil/src/test/native/cpp/DataLogTest.cpp @@ -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 + #include #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 { + 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, SimpleInt) { std::vector 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 entry0; + wpi::log::StructLogEntry entry{log, "a", 5}; + entry.Append(ThingA{}); + entry.Append(ThingA{}, 7); +} + +TEST(DataLogTest, StructArrayA) { + wpi::log::DataLog log{[](auto) {}}; + [[maybe_unused]] wpi::log::StructArrayLogEntry entry0; + wpi::log::StructArrayLogEntry 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> entry0; + 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; + [[maybe_unused]] wpi::log::StructLogEntry entry0; + wpi::log::StructLogEntry 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 entry0; + wpi::log::StructArrayLogEntry 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, Info1> entry{log, "a", info, + 5}; + std::array arr; + entry.Append(arr); + entry.Append(arr, 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{}); + entry.Append(ThingC{}, 7); + } + { + Info2 info; + wpi::log::StructLogEntry entry{log, "c2", info, 5}; + entry.Append(ThingC{}); + entry.Append(ThingC{}, 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{}}}); + entry.Append({{ThingC{}, ThingC{}}}, 7); + } + { + Info2 info; + wpi::log::StructArrayLogEntry entry{log, "c2", info, 5}; + entry.Append({{ThingC{}, ThingC{}}}); + entry.Append({{ThingC{}, ThingC{}}}, 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); + entry.Append(arr, 7); + } + { + Info2 info; + wpi::log::StructLogEntry, Info2> entry{log, "c2", + info, 5}; + entry.Append(arr); + entry.Append(arr, 7); + } +}