// 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 #include #include "wpi/Endian.h" #include "wpi/bit.h" #include "wpi/ct_string.h" #include "wpi/function_ref.h" #include "wpi/mutex.h" #include "wpi/type_traits.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 GetTypeString(): function that returns the type string * - size_t GetSize(): function that returns the structure size in bytes * - std::string_view GetSchema(): function that returns the struct schema * - T Unpack(std::span): function for deserialization * - void Pack(std::span, T&& value): function for * serialization * * If possible, the GetTypeString(), GetSize(), and GetSchema() functions should * be marked constexpr. GetTypeString() and GetSchema() may return types other * than std::string_view, as long as the return value is convertible to * std::string_view. * * 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>::GetTypeString() } -> std::convertible_to; { Struct>::GetSize() } -> std::convertible_to; { Struct>::GetSchema() } -> std::convertible_to; { Struct>::Unpack(in) } -> std::same_as>; Struct>::Pack(out, 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); }; /** * 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 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.subspan(Offset)); } /** * Pack a serialized struct. * * @param data struct storage (mutable, output) * @param value object */ template inline void PackStruct(std::span 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.subspan(Offset), 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 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.subspan(Offset)); } 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::GetTypeString(); } /** * Get the size for a raw struct serializable type * * @tparam T serializable type * @return size */ template constexpr size_t GetStructSize() { return Struct::GetSize(); } template constexpr auto MakeStructArrayTypeString() { if constexpr (is_constexpr([] { Struct::GetTypeString(); })) { constexpr auto typeString = Struct::GetTypeString(); using namespace literals; if constexpr (N == std::dynamic_extent) { return Concat( ct_string, typeString.size()>{ typeString}, "[]"_ct_string); } else { return Concat( ct_string, typeString.size()>{ typeString}, "["_ct_string, NumToCtString(), "]"_ct_string); } } else { if constexpr (N == std::dynamic_extent) { return fmt::format("{}[]", Struct::GetTypeString()); } else { return fmt::format("{}[{}]", Struct::GetTypeString(), N); } } } template consteval auto MakeStructArraySchema() { if constexpr (is_constexpr([] { Struct::GetSchema(); })) { constexpr auto schema = Struct::GetSchema(); using namespace literals; if constexpr (N == std::dynamic_extent) { return Concat( ct_string, schema.size()>{schema}, "[]"_ct_string); } else { return Concat( ct_string, schema.size()>{schema}, "["_ct_string, NumToCtString(), "]"_ct_string); } } else { if constexpr (N == std::dynamic_extent) { return fmt::format("{}[]", Struct::GetSchema()); } else { return fmt::format("{}[{}]", Struct::GetSchema(), N); } } } template constexpr std::string_view GetStructSchema() { return Struct::GetSchema(); } template constexpr std::span GetStructSchemaBytes() { auto schema = Struct::GetSchema(); return {reinterpret_cast(schema.data()), schema.size()}; } template void ForEachStructSchema( std::invocable auto fn) { if constexpr (HasNestedStruct) { Struct>::ForEachNested(fn); } fn(Struct::GetTypeString(), Struct::GetSchema()); } 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) { auto size = S::GetSize(); 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)); out += size; } func(std::span{buf, out}); } else { std::scoped_lock lock{m_mutex}; m_buf.resize(std::size(data) * size); auto out = m_buf.begin(); for (auto&& val : data) { S::Pack(std::span{std::to_address(out), size}, std::forward(val)); out += size; } 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 GetTypeString() { return MakeStructArrayTypeString(); } static constexpr size_t GetSize() { return N * GetStructSize(); } static constexpr auto GetSchema() { return MakeStructArraySchema(); } static std::array Unpack(std::span data) { auto size = GetStructSize(); std::array result; for (size_t i = 0; i < N; ++i) { result[i] = UnpackStruct(data); data = data.subspan(size); } return result; } static void Pack(std::span data, std::span values) { auto size = GetStructSize(); std::span unsizedData = data; for (auto&& val : values) { PackStruct(unsizedData, val); unsizedData = unsizedData.subspan(size); } } 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) { auto size = GetStructSize(); std::span unsizedData = data; for (size_t i = 0; i < N; ++i) { UnpackStructInto(&out[i], unsizedData); unsizedData = unsizedData.subspan(size); } } }; /** * Raw struct support for boolean values. * Primarily useful for higher level struct implementations. */ template <> struct Struct { static constexpr std::string_view GetTypeString() { return "struct:bool"; } static constexpr size_t GetSize() { return 1; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:uint8"; } static constexpr size_t GetSize() { return 1; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:int8"; } static constexpr size_t GetSize() { return 1; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:uint16"; } static constexpr size_t GetSize() { return 2; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:int16"; } static constexpr size_t GetSize() { return 2; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:uint32"; } static constexpr size_t GetSize() { return 4; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:int32"; } static constexpr size_t GetSize() { return 4; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:uint64"; } static constexpr size_t GetSize() { return 8; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:int64"; } static constexpr size_t GetSize() { return 8; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:float"; } static constexpr size_t GetSize() { return 4; } static constexpr std::string_view GetSchema() { return "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 GetTypeString() { return "struct:double"; } static constexpr size_t GetSize() { return 8; } static constexpr std::string_view GetSchema() { return "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