SCRIPT Move cc files

This commit is contained in:
PJ Reiniger
2025-11-07 19:55:39 -05:00
committed by Peter Johnson
parent 10b4a0c971
commit 7ca1be9bae
1197 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <concepts>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "pb.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include "wpi/array.h"
#include "wpi/function_ref.h"
namespace wpi {
template <typename T>
class SmallVectorImpl;
/**
* Protobuf serialization template. Unspecialized class has no members; only
* specializations of this class are useful, and only if they meet the
* ProtobufSerializable concept.
*
* @tparam T type to serialize/deserialize
*/
template <typename T>
struct Protobuf {};
namespace detail {
using SmallVectorType = wpi::SmallVectorImpl<uint8_t>;
using StdVectorType = std::vector<uint8_t>;
bool WriteFromSmallVector(pb_ostream_t* stream, const pb_byte_t* buf,
size_t count);
bool WriteFromStdVector(pb_ostream_t* stream, const pb_byte_t* buf,
size_t count);
bool WriteSubmessage(pb_ostream_t* stream, const pb_msgdesc_t* desc,
const void* msg);
} // namespace detail
/**
* Class for wrapping a nanopb istream.
*/
template <typename T>
class ProtoInputStream {
public:
/**
* Constructs a nanopb istream from an existing istream object.
* Generally used internally for decoding submessages
*
* @param[in] stream the nanopb istream
*/
explicit ProtoInputStream(pb_istream_t* stream)
: m_streamMsg{stream},
m_msgDesc{
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
}
/**
* Constructs a nanopb istream from a buffer.
*
* @param[in] stream the stream buffer
*/
explicit ProtoInputStream(std::span<const uint8_t> stream)
: m_streamLocal{pb_istream_from_buffer(
reinterpret_cast<const pb_byte_t*>(stream.data()), stream.size())},
m_msgDesc{
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
}
/**
* Gets the backing nanopb stream object.
*
* @return nanopb stream
*/
pb_istream_t* Stream() noexcept {
return m_streamMsg ? m_streamMsg : &m_streamLocal;
}
/**
* Gets the nanopb message descriptor
*
* @return the nanopb message descriptor
*/
const pb_msgdesc_t* MsgDesc() const noexcept { return m_msgDesc; }
/**
* Decodes a protobuf. Flags are the same flags passed to pb_decode_ex.
*
* @param[in] msg The message to decode into
* @param[in] flags Flags to pass
* @return true if decoding was successful, false otherwise
*/
bool Decode(typename Protobuf<std::remove_cvref_t<T>>::MessageStruct& msg,
unsigned int flags = 0) {
return pb_decode_ex(Stream(), m_msgDesc, &msg, flags);
}
private:
pb_istream_t m_streamLocal;
pb_istream_t* m_streamMsg{nullptr};
const pb_msgdesc_t* m_msgDesc;
};
/**
* Class for wrapping a nanopb ostream
*/
template <typename T>
class ProtoOutputStream {
public:
/**
* Constructs a nanopb ostream from an existing ostream object
* Generally used internally for encoding messages.
*
* This constructor will cause `Encode` to call pb_encode_submessage
* instead of `pb_encode_ex`
*
* @param[in] stream the nanopb ostream
*/
explicit ProtoOutputStream(pb_ostream_t* stream)
: m_streamMsg{stream},
m_msgDesc{
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
}
/**
* Constructs a nanopb ostream from a buffer.
*
* This constructor will cause `Encode` to call `pb_encode_ex`
*
* @param[in] out the stream buffer
*/
explicit ProtoOutputStream(detail::SmallVectorType& out)
: m_msgDesc{
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
m_streamLocal.callback = detail::WriteFromSmallVector;
m_streamLocal.state = &out;
m_streamLocal.max_size = SIZE_MAX;
m_streamLocal.bytes_written = 0;
m_streamLocal.errmsg = nullptr;
}
/**
* Constructs a nanopb ostream from a buffer.
*
* This constructor will cause `Encode` to call `pb_encode_ex`
*
* @param[in] out the stream buffer
*/
explicit ProtoOutputStream(detail::StdVectorType& out)
: m_msgDesc{
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {
m_streamLocal.callback = detail::WriteFromStdVector;
m_streamLocal.state = &out;
m_streamLocal.max_size = SIZE_MAX;
m_streamLocal.bytes_written = 0;
m_streamLocal.errmsg = nullptr;
}
/**
* Constructs a empty nanopb stream. You must fill out the stream
* returned from `Stream` before calling Encode.
*
* This constructor exists to cause `Encode` to call `pb_encode_ex`,
* but allow manipulating the stream manually.
*/
ProtoOutputStream()
: m_msgDesc{Protobuf<
std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()} {}
/**
* Gets the backing nanopb stream object.
*
* @return nanopb stream
*/
pb_ostream_t* Stream() noexcept {
return m_streamMsg ? m_streamMsg : &m_streamLocal;
}
/**
* Gets if this stream points to a submessage, and will call
* pb_encode_submessage instead of pb_encode
*
* @return true if submessage, false otherwise
*/
bool IsSubmessage() const noexcept { return m_streamMsg; }
/**
* Gets the nanopb message descriptor
*
* @return the nanopb message descriptor
*/
const pb_msgdesc_t* MsgDesc() const noexcept { return m_msgDesc; }
/**
* Decodes a protobuf. Flags are the same flags passed to pb_decode_ex.
*
* @param[in] msg The message to encode from
* @return true if encoding was successful, false otherwise
*/
bool Encode(
const typename Protobuf<std::remove_cvref_t<T>>::MessageStruct& msg) {
if (m_streamMsg) {
return detail::WriteSubmessage(m_streamMsg, m_msgDesc, &msg);
// return pb_encode_submessage(m_streamMsg, m_msgDesc, &msg);
}
return pb_encode(&m_streamLocal, m_msgDesc, &msg);
}
private:
pb_ostream_t m_streamLocal;
pb_ostream_t* m_streamMsg{nullptr};
const pb_msgdesc_t* m_msgDesc;
};
/**
* Specifies that a type is capable of protobuf serialization and
* deserialization.
*
* This is designed for serializing complex flexible data structures using
* code generated from a .proto file. Serialization consists of writing
* values into a nanopb Stream and deserialization consists of
* reading values from nanopb Stream.
*
* Implementations must define a template specialization for wpi::Protobuf with
* T being the type that is being serialized/deserialized, with the following
* static members (as enforced by this concept):
* - using MessageStruct = nanopb_message_struct_here: typedef to the wpilib
* modified nanopb message struct
* - std::optional<T> Unpack(wpi::ProtoInputStream<T>&): function
* for deserialization
* - bool Pack(wpi::ProtoOutputStream<T>&, T&& value): function
* for serialization
*
* As a suggestion, 2 extra type usings can be added to simplify the stream
* definitions, however these are not required.
* - using InputStream = wpi::ProtoInputStream<T>;
* - using OutputStream = wpi::ProtoOutputStream<T>;
*/
template <typename T>
concept ProtobufSerializable = requires(
wpi::ProtoOutputStream<std::remove_cvref_t<T>>& ostream,
wpi::ProtoInputStream<std::remove_cvref_t<T>>& istream, const T& value) {
typename Protobuf<typename std::remove_cvref_t<T>>;
{
Protobuf<typename std::remove_cvref_t<T>>::Unpack(istream)
} -> std::same_as<std::optional<typename std::remove_cvref_t<T>>>;
{
Protobuf<typename std::remove_cvref_t<T>>::Pack(ostream, value)
} -> std::same_as<bool>;
typename Protobuf<typename std::remove_cvref_t<T>>::MessageStruct;
{
Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::msg_descriptor()
} -> std::same_as<const pb_msgdesc_t*>;
{
Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::msg_name()
} -> std::same_as<std::string_view>;
{
Protobuf<typename std::remove_cvref_t<T>>::MessageStruct::file_descriptor()
} -> std::same_as<pb_filedesc_t>;
};
/**
* Specifies that a type is capable of in-place protobuf deserialization.
*
* In addition to meeting ProtobufSerializable, implementations must define a
* wpi::Protobuf<T> static member -
* `bool UnpackInto(T*, wpi::ProtoInputStream<T>&)` to update the pointed-to T
* with the contents of the message.
*/
template <typename T>
concept MutableProtobufSerializable =
ProtobufSerializable<T> &&
requires(T* out, wpi::ProtoInputStream<T>& istream) {
{
Protobuf<typename std::remove_cvref_t<T>>::UnpackInto(out, istream)
} -> std::same_as<bool>;
};
namespace detail {
std::string GetTypeString(const pb_msgdesc_t* msg);
void ForEachProtobufDescriptor(
const pb_msgdesc_t* msg,
function_ref<bool(std::string_view filename)> wants,
function_ref<void(std::string_view filename,
std::span<const uint8_t> descriptor)>
fn);
} // namespace detail
/**
* Ease of use wrapper to make nanopb streams more opaque to the user.
* This class is stateless and thread safe.
*
* @tparam T serialized object type
*/
template <ProtobufSerializable T>
class ProtobufMessage {
public:
/**
* Unpacks from a byte array.
*
* @param data byte array
* @return Optional; empty if parsing failed
*/
std::optional<std::remove_cvref_t<T>> Unpack(std::span<const uint8_t> data) {
ProtoInputStream<std::remove_cvref_t<T>> stream{data};
return Protobuf<std::remove_cvref_t<T>>::Unpack(stream);
}
/**
* Unpacks from a byte array into an existing object.
*
* @param[out] out output object
* @param[in] data byte array
* @return true if successful
*/
bool UnpackInto(T* out, std::span<const uint8_t> data) {
if constexpr (MutableProtobufSerializable<T>) {
ProtoInputStream<std::remove_cvref_t<T>> stream{data};
return Protobuf<std::remove_cvref_t<T>>::UnpackInto(out, stream);
} else {
auto unpacked = Unpack(data);
if (!unpacked) {
return false;
}
*out = std::move(unpacked.value());
return true;
}
}
/**
* Packs object into a SmallVector.
*
* @param[out] out output bytes
* @param[in] value value
* @return true if successful
*/
bool Pack(wpi::SmallVectorImpl<uint8_t>& out, const T& value) {
ProtoOutputStream<std::remove_cvref_t<T>> stream{out};
return Protobuf<std::remove_cvref_t<T>>::Pack(stream, value);
}
/**
* Packs object into a std::vector.
*
* @param[out] out output bytes
* @param[in] value value
* @return true if successful
*/
bool Pack(std::vector<uint8_t>& out, const T& value) {
ProtoOutputStream<std::remove_cvref_t<T>> stream{out};
return Protobuf<std::remove_cvref_t<T>>::Pack(stream, value);
}
/**
* Gets the type string for the message.
*
* @return type string
*/
std::string GetTypeString() const {
return detail::GetTypeString(
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor());
}
/**
* Loops over all protobuf descriptors including nested/referenced
* descriptors.
*
* @param exists function that returns false if fn should be called for the
* given type string
* @param fn function to call for each descriptor
*/
void ForEachProtobufDescriptor(
function_ref<bool(std::string_view filename)> exists,
function_ref<void(std::string_view filename,
std::span<const uint8_t> descriptor)>
fn) {
detail::ForEachProtobufDescriptor(
Protobuf<std::remove_cvref_t<T>>::MessageStruct::msg_descriptor(),
exists, fn);
}
};
} // namespace wpi

View File

@@ -0,0 +1,670 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <span>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include "pb.h"
#include "wpi/SmallVector.h"
#include "wpi/array.h"
#include "wpi/protobuf/Protobuf.h"
namespace wpi {
/**
* The behavior to use when more elements are in the message then expected when
* decoding.
*/
enum class DecodeLimits {
// Ignore any extra elements
Ignore,
// Add any extra elements to the backing vector
Add,
// Cause decoding to fail if extra elements exist
Fail,
};
template <class T>
concept StringLike = std::is_convertible_v<T, std::string_view>;
template <class T>
concept ConstVectorLike = std::is_convertible_v<T, std::span<const uint8_t>>;
template <class T>
concept MutableVectorLike = std::is_convertible_v<T, std::span<uint8_t>>;
template <typename T>
concept PackBytes = StringLike<T> || ConstVectorLike<T>;
template <typename T>
concept UnpackBytes = requires(T& t) {
{ t.resize(size_t()) }; // NOLINT
{ t.size() } -> std::same_as<size_t>;
{ t.data() } -> std::convertible_to<void*>;
} && (PackBytes<T> || MutableVectorLike<T>);
template <typename T>
concept ProtoEnumeration = std::is_enum_v<T>;
template <typename T>
concept ProtoPackable =
ProtoEnumeration<T> || std::integral<T> || std::floating_point<T>;
template <typename T>
concept ProtoCallbackPackable =
ProtobufSerializable<T> || PackBytes<T> || ProtoPackable<T>;
template <typename T>
concept ProtoCallbackUnpackable =
ProtobufSerializable<T> || UnpackBytes<T> || ProtoPackable<T>;
namespace detail {
template <typename T>
concept Validatable = ProtoCallbackPackable<T> || ProtoCallbackUnpackable<T>;
template <Validatable T>
constexpr bool ValidateType(pb_type_t type) {
switch (type) {
case PB_LTYPE_BOOL:
return std::integral<T>;
case PB_LTYPE_VARINT:
return std::signed_integral<T> || ProtoEnumeration<T>;
case PB_LTYPE_UVARINT:
return std::unsigned_integral<T>;
case PB_LTYPE_SVARINT:
return std::signed_integral<T>;
case PB_LTYPE_FIXED32:
return std::integral<T> || std::floating_point<T>;
case PB_LTYPE_FIXED64:
return std::integral<T> || std::floating_point<T>;
case PB_LTYPE_BYTES:
case PB_LTYPE_STRING:
return PackBytes<T> || UnpackBytes<T>;
case PB_LTYPE_SUBMESSAGE:
return ProtobufSerializable<T>;
default:
return false;
}
}
} // namespace detail
/**
* A callback method that will directly unpack elements into
* the specified vector like data structure. The size passed
* is the expected number of elements.
*
* By default, any elements in the packed buffer past N will
* still be added to the vector.
*
* @tparam T object type
* @tparam U vector type to pack into
* @tparam N number of elements
*/
template <ProtoCallbackUnpackable T, typename U, size_t N = 1>
class DirectUnpackCallback {
public:
/**
* Constructs a callback from a vector like type.
*
* @param storage the vector to store into
*/
explicit DirectUnpackCallback(U& storage) : m_storage{storage} {
m_callback.funcs.decode = CallbackFunc;
m_callback.arg = this;
}
DirectUnpackCallback(const DirectUnpackCallback&) = delete;
DirectUnpackCallback(DirectUnpackCallback&&) = delete;
DirectUnpackCallback& operator=(const DirectUnpackCallback&) = delete;
DirectUnpackCallback& operator=(DirectUnpackCallback&&) = delete;
/**
* Set the limits on what happens if more elements exist in the buffer then
* expected.
*
* @param limit the limit to set
*/
void SetLimits(DecodeLimits limit) noexcept { m_limits = limit; }
/**
* Gets the nanopb callback pointing to this object.
*
* @return nanopb callback
*/
pb_callback_t Callback() const { return m_callback; }
private:
bool SizeCheck(bool* retVal) const {
if (m_storage.size() >= N) {
switch (m_limits) {
case DecodeLimits::Ignore:
*retVal = true;
return false;
case DecodeLimits::Add:
break;
default:
*retVal = false;
return false;
}
}
return true;
}
bool Decode(pb_istream_t* stream, pb_type_t fieldType) {
if constexpr (ProtoPackable<T>) {
switch (fieldType) {
case PB_LTYPE_BOOL:
if constexpr (std::integral<T>) {
bool val = false;
if (!pb_decode_bool(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
case PB_LTYPE_VARINT:
if constexpr (std::signed_integral<T> || ProtoEnumeration<T>) {
int64_t val = 0;
if (!pb_decode_varint(stream, reinterpret_cast<uint64_t*>(&val))) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
case PB_LTYPE_UVARINT:
if constexpr (std::unsigned_integral<T>) {
uint64_t val = 0;
if (!pb_decode_varint(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
case PB_LTYPE_SVARINT:
if constexpr (std::signed_integral<T>) {
int64_t val = 0;
if (!pb_decode_svarint(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
case PB_LTYPE_FIXED32:
if constexpr (std::signed_integral<T>) {
int32_t val = 0;
if (!pb_decode_fixed32(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else if constexpr (std::unsigned_integral<T>) {
uint32_t val = 0;
if (!pb_decode_fixed32(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
}
if constexpr (std::floating_point<T>) {
float val = 0;
if (!pb_decode_fixed32(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
case PB_LTYPE_FIXED64:
if constexpr (std::signed_integral<T>) {
int64_t val = 0;
if (!pb_decode_fixed64(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else if constexpr (std::unsigned_integral<T>) {
uint64_t val = 0;
if (!pb_decode_fixed64(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
}
if constexpr (std::floating_point<T>) {
double val = 0;
if (!pb_decode_fixed64(stream, &val)) {
return false;
}
m_storage.emplace_back(static_cast<T>(val));
return true;
} else {
return false;
}
default:
return false;
}
} else if constexpr (UnpackBytes<T>) {
T& space = m_storage.emplace_back(T{});
space.resize(stream->bytes_left);
return pb_read(stream, reinterpret_cast<pb_byte_t*>(space.data()),
space.size());
} else if constexpr (ProtobufSerializable<T>) {
ProtoInputStream<T> istream{stream};
auto decoded = wpi::Protobuf<T>::Unpack(istream);
if (decoded.has_value()) {
m_storage.emplace_back(std::move(decoded.value()));
return true;
}
return false;
}
}
bool CallbackFunc(pb_istream_t* stream, const pb_field_t* field) {
pb_type_t fieldType = PB_LTYPE(field->type);
if (!detail::ValidateType<T>(fieldType)) {
return false;
}
// Validate our types
if constexpr (ProtoPackable<T>) {
// Handle decode loop
while (stream->bytes_left > 0) {
bool sizeRetVal = 0;
if (!SizeCheck(&sizeRetVal)) {
return sizeRetVal;
}
if (!Decode(stream, fieldType)) {
return false;
}
}
return true;
} else {
// At this point, do the size check
bool sizeRetVal = 0;
if (!SizeCheck(&sizeRetVal)) {
return sizeRetVal;
}
// At this point, we're good to decode
return Decode(stream, fieldType);
}
}
static bool CallbackFunc(pb_istream_t* stream, const pb_field_t* field,
void** arg) {
return reinterpret_cast<DirectUnpackCallback*>(*arg)->CallbackFunc(stream,
field);
}
U& m_storage;
pb_callback_t m_callback;
DecodeLimits m_limits{DecodeLimits::Add};
};
/**
* A DirectUnpackCallback backed by a SmallVector<T, N>.
*
* By default, any elements in the packed buffer past N will
* be ignored, but decoding will still succeed
*
* @tparam T object type
* @tparam N small vector small size/number of expected elements
*/
template <ProtoCallbackUnpackable T, size_t N = 1>
class UnpackCallback
: public DirectUnpackCallback<T, wpi::SmallVector<T, N>, N> {
public:
/**
* Constructs an UnpackCallback.
*/
UnpackCallback()
: DirectUnpackCallback<T, wpi::SmallVector<T, N>, N>{m_storedBuffer} {
this->SetLimits(DecodeLimits::Ignore);
}
/**
* Gets a span pointing to the storage buffer.
*
* @return storage buffer span
*/
std::span<T> Items() noexcept { return m_storedBuffer; }
/**
* Gets a const span pointing to the storage buffer.
*
* @return storage buffer span
*/
std::span<const T> Items() const noexcept { return m_storedBuffer; }
/**
* Gets a reference to the backing small vector.
*
* @return small vector reference
*/
wpi::SmallVector<T, N>& Vec() noexcept { return m_storedBuffer; }
private:
wpi::SmallVector<T, N> m_storedBuffer;
};
/**
* A DirectUnpackCallback backed by a std::vector.
*
* By default, any elements in the packed buffer past N will
* be ignored, but decoding will still succeed
*
* @tparam T object type
* @tparam N number of expected elements
*/
template <ProtoCallbackUnpackable T, size_t N = 1>
class StdVectorUnpackCallback
: public DirectUnpackCallback<T, std::vector<T>, N> {
public:
/**
* Constructs a StdVectorUnpackCallback.
*/
StdVectorUnpackCallback()
: DirectUnpackCallback<T, std::vector<T>, N>{m_storedBuffer} {
this->SetLimits(DecodeLimits::Ignore);
}
/**
* Gets a span pointing to the storage buffer.
*
* @return storage buffer span
*/
std::span<T> Items() noexcept { return m_storedBuffer; }
/**
* Gets a const span pointing to the storage buffer.
*
* @return storage buffer span
*/
std::span<const T> Items() const noexcept { return m_storedBuffer; }
/**
* Gets a reference to the backing vector.
*
* @return vector reference
*/
std::vector<T>& Vec() noexcept { return m_storedBuffer; }
private:
std::vector<T> m_storedBuffer;
};
/**
* A wrapper around a wpi::array that lets us
* treat it as a limited sized vector.
*/
template <ProtoCallbackUnpackable T, size_t N>
struct WpiArrayEmplaceWrapper {
wpi::array<T, N> m_array{wpi::empty_array_t{}};
size_t m_currentIndex = 0;
size_t size() const { return m_currentIndex; }
template <typename... ArgTypes>
T& emplace_back(ArgTypes&&... Args) {
m_array[m_currentIndex] = T(std::forward<ArgTypes>(Args)...);
m_currentIndex++;
return m_array[m_currentIndex - 1];
}
};
/**
* A DirectUnpackCallback backed by a wpi::array<T, N>.
*
* Any elements in the packed buffer past N will
* be cause decoding to fail.
*
* @tparam T object type
* @tparam N small vector small size/number of expected elements
*/
template <ProtoCallbackUnpackable T, size_t N>
struct WpiArrayUnpackCallback
: public DirectUnpackCallback<T, WpiArrayEmplaceWrapper<T, N>, N> {
/**
* Constructs a WpiArrayUnpackCallback.
*/
WpiArrayUnpackCallback()
: DirectUnpackCallback<T, WpiArrayEmplaceWrapper<T, N>, N>{m_array} {
this->SetLimits(DecodeLimits::Fail);
}
/**
* Returns if the buffer is completely filled up.
*
* @return true if buffer is full
*/
bool IsFull() const noexcept { return m_array.m_currentIndex == N; }
/**
* Returns the number of elements in the buffer.
*
* @return number of elements
*/
size_t Size() const noexcept { return m_array.m_currentIndex; }
/**
* Returns a reference to the backing array.
*
* @return array reference
*/
wpi::array<T, N>& Array() noexcept { return m_array.m_array; }
private:
WpiArrayEmplaceWrapper<T, N> m_array;
};
/**
* A callback method that will pack elements when called.
*
* @tparam T object type
*/
template <ProtoCallbackPackable T>
class PackCallback {
public:
/**
* Constructs a pack callback from a span of elements. The elements in the
* buffer _MUST_ stay alive throughout the entire encode call.
*/
explicit PackCallback(std::span<const T> buffer) : m_buffer{buffer} {
m_callback.funcs.encode = CallbackFunc;
m_callback.arg = this;
}
/**
* Constructs a pack callback from a pointer to a single element.
* This element _MUST_ stay alive throughout the entire encode call.
* Do not pass a temporary here (This is why its a pointer and not a
* reference)
*/
explicit PackCallback(const T* element)
: m_buffer{std::span<const T>{element, 1}} {
m_callback.funcs.encode = CallbackFunc;
m_callback.arg = this;
}
PackCallback(const PackCallback&) = delete;
PackCallback(PackCallback&&) = delete;
PackCallback& operator=(const PackCallback&) = delete;
PackCallback& operator=(PackCallback&&) = delete;
/**
* Gets the nanopb callback pointing to this object.
*
* @return nanopb callback
*/
pb_callback_t Callback() const { return m_callback; }
/**
* Gets a span pointing to the items
*
* @return span
*/
std::span<const T> Bufs() const { return m_buffer; }
private:
static auto EncodeStreamTypeFinder() {
if constexpr (ProtobufSerializable<T>) {
return ProtoOutputStream<T>(nullptr);
} else {
return pb_ostream_t{};
}
}
using EncodeStreamType = decltype(EncodeStreamTypeFinder());
bool EncodeItem(EncodeStreamType& stream, const pb_field_t* field,
const T& value) const {
if constexpr (std::floating_point<T>) {
pb_type_t fieldType = PB_LTYPE(field->type);
switch (fieldType) {
case PB_LTYPE_FIXED32: {
float flt = static_cast<float>(value);
return pb_encode_fixed32(&stream, &flt);
}
case PB_LTYPE_FIXED64: {
double dbl = static_cast<double>(value);
return pb_encode_fixed64(&stream, &dbl);
}
default:
return false;
}
} else if constexpr (std::integral<T> || ProtoEnumeration<T>) {
pb_type_t fieldType = PB_LTYPE(field->type);
switch (fieldType) {
case PB_LTYPE_BOOL:
case PB_LTYPE_VARINT:
case PB_LTYPE_UVARINT:
return pb_encode_varint(&stream, value);
case PB_LTYPE_SVARINT:
return pb_encode_svarint(&stream, value);
case PB_LTYPE_FIXED32: {
uint32_t f = value;
return pb_encode_fixed32(&stream, &f);
}
case PB_LTYPE_FIXED64: {
uint64_t f = value;
return pb_encode_fixed64(&stream, &f);
}
default:
return false;
}
} else if constexpr (StringLike<T>) {
std::string_view view{value};
return pb_encode_string(&stream,
reinterpret_cast<const pb_byte_t*>(view.data()),
view.size());
} else if constexpr (ConstVectorLike<T>) {
std::span<const uint8_t> view{value};
return pb_encode_string(&stream,
reinterpret_cast<const pb_byte_t*>(view.data()),
view.size());
} else if constexpr (ProtobufSerializable<T>) {
return wpi::Protobuf<T>::Pack(stream, value);
}
}
bool EncodeLoop(pb_ostream_t* stream, const pb_field_t* field,
bool writeTag) const {
if constexpr (ProtobufSerializable<T>) {
ProtoOutputStream<T> ostream{stream};
for (auto&& i : m_buffer) {
if (writeTag) {
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
}
if (!EncodeItem(ostream, field, i)) {
return false;
}
}
} else {
for (auto&& i : m_buffer) {
if (writeTag) {
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
}
if (!EncodeItem(*stream, field, i)) {
return false;
}
}
}
return true;
}
bool PackedEncode(pb_ostream_t* stream, const pb_field_t* field) const {
// We're always going to used packed encoding.
// So first we need to get the packed size.
pb_ostream_t substream = PB_OSTREAM_SIZING;
if (!EncodeLoop(&substream, field, false)) {
return false;
}
// Encode as a string tag
if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) {
return false;
}
// Write length as varint
size_t size = substream.bytes_written;
if (!pb_encode_varint(stream, static_cast<uint64_t>(size))) {
return false;
}
return EncodeLoop(stream, field, false);
}
bool CallbackFunc(pb_ostream_t* stream, const pb_field_t* field) const {
// First off, if we're empty, do nothing, but say we were successful
if (m_buffer.empty()) {
return true;
}
pb_type_t fieldType = PB_LTYPE(field->type);
if (!detail::ValidateType<T>(fieldType)) {
return false;
}
if constexpr (ProtoPackable<T>) {
return PackedEncode(stream, field);
} else {
return EncodeLoop(stream, field, true);
}
}
static bool CallbackFunc(pb_ostream_t* stream, const pb_field_t* field,
void* const* arg) {
return reinterpret_cast<const PackCallback*>(*arg)->CallbackFunc(stream,
field);
}
std::span<const T> m_buffer;
pb_callback_t m_callback;
};
} // namespace wpi