// 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 #include #include #include #include #include #include #include #include "wpi/Endian.h" #include "wpi/MathExtras.h" #include "wpi/bit.h" #include "wpi/ct_string.h" #include "wpi/function_ref.h" #include "wpi/mutex.h" namespace wpi { /** * Struct serialization template. Unspecialized class has no members; only * specializations of this class are useful, and only if they meet the * StructSerializable concept. * * @tparam T type to serialize/deserialize */ template struct Struct {}; /** * Specifies that a type is capable of raw struct serialization and * deserialization. * * This is designed for serializing small fixed-size data structures in the * fastest and most compact means possible. Serialization consists of writing * values into a mutable std::span and deserialization consists of reading * values from an immutable std::span. * * Implementations must define a template specialization for wpi::Struct with * T being the type that is being serialized/deserialized, with the following * static members (as enforced by this concept): * - std::string_view kTypeString: the type string * - size_t kSize: the structure size in bytes * - std::string_view kSchema: the struct schema * - T Unpack(std::span): function for deserialization * - void Pack(std::span, T&& value): function for * serialization * * If the struct has nested structs, implementations should also meet the * requirements of HasNestedStruct. */ template concept StructSerializable = requires(std::span in, std::span out, T&& value) { typename Struct>; { Struct>::kTypeString } -> std::convertible_to; { Struct>::kSize } -> std::convertible_to; { Struct>::kSchema } -> std::convertible_to; { Struct>::Unpack( in.subspan<0, Struct>::kSize>()) } -> std::same_as>; Struct>::Pack( out.subspan<0, Struct>::kSize>(), std::forward(value)); }; /** * Specifies that a type is capable of in-place raw struct deserialization. * * In addition to meeting StructSerializable, implementations must define a * wpi::Struct static member `void UnpackInto(T*, std::span)` * to update the pointed-to T with the contents of the span. */ template concept MutableStructSerializable = StructSerializable && requires(T* out, std::span in) { Struct>::UnpackInto( out, in.subspan<0, Struct>::kSize>()); }; /** * Specifies that a struct type has nested struct declarations. * * In addition to meeting StructSerializable, implementations must define a * wpi::Struct static member * `void ForEachNested(std::invocable on each nested struct * type. */ template concept HasNestedStruct = StructSerializable && requires(function_ref fn) { Struct>::ForEachNested(fn); }; /** * Unpack a serialized struct. * * @tparam T object type * @param data raw struct data * @return Deserialized object */ template inline T UnpackStruct(std::span::kSize> data) { return Struct::Unpack(data); } /** * Unpack a serialized struct starting at a given offset within the data. * This is primarily useful in unpack implementations to unpack nested structs. * * @tparam T object type * @tparam Offset starting offset * @param data raw struct data * @return Deserialized object */ template inline T UnpackStruct(std::span data) { return Struct::Unpack(data.template subspan::kSize>()); } /** * Pack a serialized struct. * * @param data struct storage (mutable, output) * @param value object */ template inline void PackStruct( std::span>::kSize> data, T&& value) { Struct>::Pack(data, std::forward(value)); } /** * Pack a serialized struct starting at a given offset within the data. This is * primarily useful in pack implementations to pack nested structs. * * @tparam Offset starting offset * @param data struct storage (mutable, output) * @param value object */ template inline void PackStruct(std::span data, T&& value) { Struct>::Pack( data.template subspan>::kSize>(), std::forward(value)); } /** * Unpack a serialized struct into an existing object, overwriting its contents. * * @param out object (output) * @param data raw struct data */ template inline void UnpackStructInto(T* out, std::span::kSize> data) { if constexpr (MutableStructSerializable) { Struct::UnpackInto(out, data); } else { *out = UnpackStruct(data); } } /** * Unpack a serialized struct into an existing object, overwriting its contents, * and starting at a given offset within the data. * This is primarily useful in unpack implementations to unpack nested structs. * * @tparam Offset starting offset * @param out object (output) * @param data raw struct data */ template inline void UnpackStructInto(T* out, std::span data) { if constexpr (MutableStructSerializable) { Struct::UnpackInto(out, data.template subspan::kSize>()); } else { *out = UnpackStruct(data); } } /** * Get the type string for a raw struct serializable type * * @tparam T serializable type * @return type string */ template constexpr auto GetStructTypeString() { return Struct::kTypeString; } template consteval auto MakeStructArrayTypeString() { using namespace literals; if constexpr (N == std::dynamic_extent) { return Concat( ct_string, Struct::kTypeString.size()>{ Struct::kTypeString}, "[]"_ct_string); } else { return Concat( ct_string, Struct::kTypeString.size()>{ Struct::kTypeString}, "["_ct_string, NumToCtString(), "]"_ct_string); } } template consteval auto MakeStructArraySchema() { using namespace literals; if constexpr (N == std::dynamic_extent) { return Concat( ct_string, Struct::kSchema.size()>{ Struct::kSchema}, "[]"_ct_string); } else { return Concat( ct_string, Struct::kSchema.size()>{ Struct::kSchema}, "["_ct_string, NumToCtString(), "]"_ct_string); } } template constexpr std::string_view GetStructSchema() { return Struct::kSchema; } template constexpr std::span GetStructSchemaBytes() { return {reinterpret_cast(Struct::kSchema.data()), Struct::kSchema.size()}; } template void ForEachStructSchema( std::invocable auto fn) { if constexpr (HasNestedStruct) { Struct::ForEachNested(fn); } fn(Struct::kTypeString, Struct::kSchema); } template class StructArrayBuffer { using S = Struct; public: StructArrayBuffer() = default; StructArrayBuffer(const StructArrayBuffer&) = delete; StructArrayBuffer& operator=(const StructArrayBuffer&) = delete; StructArrayBuffer(StructArrayBuffer&& rhs) : m_buf{std::move(rhs.m_buf)} {} StructArrayBuffer& operator=(StructArrayBuffer&& rhs) { m_buf = std::move(rhs.m_buf); return *this; } template requires #if __cpp_lib_ranges >= 201911L std::ranges::range && std::convertible_to, T> && #endif std::invocable> void Write(U&& data, F&& func) { if ((std::size(data) * S::kSize) < 256) { // use the stack uint8_t buf[256]; auto out = buf; for (auto&& val : data) { S::Pack(std::span{out, out + S::kSize}, std::forward(val)); out += S::kSize; } func(std::span{buf, out}); } else { std::scoped_lock lock{m_mutex}; m_buf.resize(std::size(data) * S::kSize); auto out = m_buf.begin(); for (auto&& val : data) { S::Pack(std::span{out, out + S::kSize}, std::forward(val)); out += S::kSize; } func(m_buf); } } private: wpi::mutex m_mutex; std::vector m_buf; }; /** * Raw struct support for fixed-size arrays of other structs. */ template struct Struct> { static constexpr auto kTypeString = MakeStructArrayTypeString(); static constexpr size_t kSize = N * Struct::kSize; static constexpr auto kSchema = MakeStructArraySchema(); static std::array Unpack(std::span data) { std::array result; for (size_t i = 0; i < N; ++i) { result[i] = UnpackStruct(data); data = data.subspan(Struct::kSize); } return result; } static void Pack(std::span data, std::span values) { std::span unsizedData = data; for (auto&& val : values) { PackStruct<0>(unsizedData, val); unsizedData = unsizedData.subspan(Struct::kSize); } } static void UnpackInto(std::array* out, std::span data) { UnpackInto(std::span{*out}, data); } // alternate span-based function static void UnpackInto(std::span out, std::span data) { std::span unsizedData = data; for (size_t i = 0; i < N; ++i) { UnpackStructInto<0, T>(&out[i], unsizedData); unsizedData = unsizedData.subspan(Struct::kSize); } } }; /** * Raw struct support for boolean values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:bool"; static constexpr size_t kSize = 1; static constexpr std::string_view kSchema = "bool value"; static bool Unpack(std::span data) { return data[0]; } static void Pack(std::span data, bool value) { data[0] = static_cast(value ? 1 : 0); } }; /** * Raw struct support for uint8_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:uint8"; static constexpr size_t kSize = 1; static constexpr std::string_view kSchema = "uint8 value"; static uint8_t Unpack(std::span data) { return data[0]; } static void Pack(std::span data, uint8_t value) { data[0] = value; } }; /** * Raw struct support for int8_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:int8"; static constexpr size_t kSize = 1; static constexpr std::string_view kSchema = "int8 value"; static int8_t Unpack(std::span data) { return data[0]; } static void Pack(std::span data, int8_t value) { data[0] = value; } }; /** * Raw struct support for uint16_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:uint16"; static constexpr size_t kSize = 2; static constexpr std::string_view kSchema = "uint16 value"; static uint16_t Unpack(std::span data) { return support::endian::read16le(data.data()); } static void Pack(std::span data, uint16_t value) { support::endian::write16le(data.data(), value); } }; /** * Raw struct support for int16_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:int16"; static constexpr size_t kSize = 2; static constexpr std::string_view kSchema = "int16 value"; static int16_t Unpack(std::span data) { return support::endian::read16le(data.data()); } static void Pack(std::span data, int16_t value) { support::endian::write16le(data.data(), value); } }; /** * Raw struct support for uint32_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:uint32"; static constexpr size_t kSize = 4; static constexpr std::string_view kSchema = "uint32 value"; static uint32_t Unpack(std::span data) { return support::endian::read32le(data.data()); } static void Pack(std::span data, uint32_t value) { support::endian::write32le(data.data(), value); } }; /** * Raw struct support for int32_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:int32"; static constexpr size_t kSize = 4; static constexpr std::string_view kSchema = "int32 value"; static int32_t Unpack(std::span data) { return support::endian::read32le(data.data()); } static void Pack(std::span data, int32_t value) { support::endian::write32le(data.data(), value); } }; /** * Raw struct support for uint64_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:uint64"; static constexpr size_t kSize = 8; static constexpr std::string_view kSchema = "uint64 value"; static uint64_t Unpack(std::span data) { return support::endian::read64le(data.data()); } static void Pack(std::span data, uint64_t value) { support::endian::write64le(data.data(), value); } }; /** * Raw struct support for int64_t values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:int64"; static constexpr size_t kSize = 8; static constexpr std::string_view kSchema = "int64 value"; static int64_t Unpack(std::span data) { return support::endian::read64le(data.data()); } static void Pack(std::span data, int64_t value) { support::endian::write64le(data.data(), value); } }; /** * Raw struct support for float values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:float"; static constexpr size_t kSize = 4; static constexpr std::string_view kSchema = "float value"; static float Unpack(std::span data) { return bit_cast(support::endian::read32le(data.data())); } static void Pack(std::span data, float value) { support::endian::write32le(data.data(), bit_cast(value)); } }; /** * Raw struct support for double values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view kTypeString = "struct:double"; static constexpr size_t kSize = 8; static constexpr std::string_view kSchema = "double value"; static double Unpack(std::span data) { return bit_cast(support::endian::read64le(data.data())); } static void Pack(std::span data, double value) { support::endian::write64le(data.data(), bit_cast(value)); } }; } // namespace wpi