serialization: Support for multiple parameters

This commit makes a minimal change to the ParamsStream class to let it retrieve
multiple parameters. Followup commits after this commit clean up code using
ParamsStream and make it easier to set multiple parameters.

Currently it is only possible to attach one serialization parameter to a stream
at a time. For example, it is not possible to set a parameter controlling the
transaction format and a parameter controlling the address format at the same
time because one parameter will override the other.

This limitation is inconvenient for multiprocess code since it is not possible
to create just one type of stream and serialize any object to it. Instead it is
necessary to create different streams for different object types, which
requires extra boilerplate and makes using the new parameter fields a lot more
awkward than the older version and type fields.

Fix this problem by allowing an unlimited number of serialization stream
parameters to be set, and allowing them to be requested by type. Later
parameters will still override earlier parameters, but only if they have the
same type.

This change requires replacing the stream.GetParams() method with a
stream.GetParams<T>() method in order for serialization code to retrieve the
desired parameters. This change is more verbose, but probably a good thing for
readability because previously it could be difficult to know what type the
GetParams() method would return, and now it is more obvious.
This commit is contained in:
Ryan Ofsky
2023-11-22 14:47:00 -05:00
parent 4ae5171d42
commit f3a2b52376
5 changed files with 28 additions and 31 deletions

View File

@@ -241,7 +241,7 @@ public:
template <typename Stream> template <typename Stream>
void Serialize(Stream& s) const void Serialize(Stream& s) const
{ {
if (s.GetParams().enc == Encoding::V2) { if (s.template GetParams<SerParams>().enc == Encoding::V2) {
SerializeV2Stream(s); SerializeV2Stream(s);
} else { } else {
SerializeV1Stream(s); SerializeV1Stream(s);
@@ -254,7 +254,7 @@ public:
template <typename Stream> template <typename Stream>
void Unserialize(Stream& s) void Unserialize(Stream& s)
{ {
if (s.GetParams().enc == Encoding::V2) { if (s.template GetParams<SerParams>().enc == Encoding::V2) {
UnserializeV2Stream(s); UnserializeV2Stream(s);
} else { } else {
UnserializeV1Stream(s); UnserializeV1Stream(s);

View File

@@ -326,7 +326,7 @@ public:
template <typename Stream> template <typename Stream>
inline void Serialize(Stream& s) const { inline void Serialize(Stream& s) const {
SerializeTransaction(*this, s, s.GetParams()); SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
} }
/** This deserializing constructor is provided instead of an Unserialize method. /** This deserializing constructor is provided instead of an Unserialize method.
@@ -386,12 +386,12 @@ struct CMutableTransaction
template <typename Stream> template <typename Stream>
inline void Serialize(Stream& s) const { inline void Serialize(Stream& s) const {
SerializeTransaction(*this, s, s.GetParams()); SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
} }
template <typename Stream> template <typename Stream>
inline void Unserialize(Stream& s) { inline void Unserialize(Stream& s) {
UnserializeTransaction(*this, s, s.GetParams()); UnserializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
} }
template <typename Stream> template <typename Stream>

View File

@@ -406,9 +406,10 @@ public:
static constexpr SerParams V1_DISK{{CNetAddr::Encoding::V1}, Format::Disk}; static constexpr SerParams V1_DISK{{CNetAddr::Encoding::V1}, Format::Disk};
static constexpr SerParams V2_DISK{{CNetAddr::Encoding::V2}, Format::Disk}; static constexpr SerParams V2_DISK{{CNetAddr::Encoding::V2}, Format::Disk};
SERIALIZE_METHODS_PARAMS(CAddress, obj, SerParams, params) SERIALIZE_METHODS(CAddress, obj)
{ {
bool use_v2; bool use_v2;
auto& params = SER_PARAMS(SerParams);
if (params.fmt == Format::Disk) { if (params.fmt == Format::Disk) {
// In the disk serialization format, the encoding (v1 or v2) is determined by a flag version // In the disk serialization format, the encoding (v1 or v2) is determined by a flag version
// that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines // that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines

View File

@@ -181,9 +181,8 @@ const Out& AsBase(const In& x)
static void SerializationOps(Type& obj, Stream& s, Operation ser_action) static void SerializationOps(Type& obj, Stream& s, Operation ser_action)
/** /**
* Variant of FORMATTER_METHODS that supports a declared parameter type. * Formatter methods can retrieve parameters attached to a stream using the
* * SER_PARAMS(type) macro as long as the stream is created directly or
* If a formatter has a declared parameter type, it must be invoked directly or
* indirectly with a parameter of that type. This permits making serialization * indirectly with a parameter of that type. This permits making serialization
* depend on run-time context in a type-safe way. * depend on run-time context in a type-safe way.
* *
@@ -191,7 +190,8 @@ const Out& AsBase(const In& x)
* struct BarParameter { bool fancy; ... }; * struct BarParameter { bool fancy; ... };
* struct Bar { ... }; * struct Bar { ... };
* struct FooFormatter { * struct FooFormatter {
* FORMATTER_METHODS(Bar, obj, BarParameter, param) { * FORMATTER_METHODS(Bar, obj) {
* auto& param = SER_PARAMS(BarParameter);
* if (param.fancy) { * if (param.fancy) {
* READWRITE(VARINT(obj.value)); * READWRITE(VARINT(obj.value));
* } else { * } else {
@@ -213,13 +213,7 @@ const Out& AsBase(const In& x)
* Compilation will fail in any context where serialization is invoked but * Compilation will fail in any context where serialization is invoked but
* no parameter of a type convertible to BarParameter is provided. * no parameter of a type convertible to BarParameter is provided.
*/ */
#define FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) \ #define SER_PARAMS(type) (s.template GetParams<type>())
template <typename Stream> \
static void Ser(Stream& s, const cls& obj) { SerializationOps(obj, s, ActionSerialize{}, s.GetParams()); } \
template <typename Stream> \
static void Unser(Stream& s, cls& obj) { SerializationOps(obj, s, ActionUnserialize{}, s.GetParams()); } \
template <typename Stream, typename Type, typename Operation> \
static void SerializationOps(Type& obj, Stream& s, Operation ser_action, const paramcls& paramobj)
#define BASE_SERIALIZE_METHODS(cls) \ #define BASE_SERIALIZE_METHODS(cls) \
template <typename Stream> \ template <typename Stream> \
@@ -246,15 +240,6 @@ const Out& AsBase(const In& x)
BASE_SERIALIZE_METHODS(cls) \ BASE_SERIALIZE_METHODS(cls) \
FORMATTER_METHODS(cls, obj) FORMATTER_METHODS(cls, obj)
/**
* Variant of SERIALIZE_METHODS that supports a declared parameter type.
*
* See FORMATTER_METHODS_PARAMS for more information on parameters.
*/
#define SERIALIZE_METHODS_PARAMS(cls, obj, paramcls, paramobj) \
BASE_SERIALIZE_METHODS(cls) \
FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj)
// Templates for serializing to anything that looks like a stream, // Templates for serializing to anything that looks like a stream,
// i.e. anything that supports .read(Span<std::byte>) and .write(Span<const std::byte>) // i.e. anything that supports .read(Span<std::byte>) and .write(Span<const std::byte>)
// //
@@ -1134,9 +1119,19 @@ public:
void ignore(size_t num) { m_substream.ignore(num); } void ignore(size_t num) { m_substream.ignore(num); }
bool eof() const { return m_substream.eof(); } bool eof() const { return m_substream.eof(); }
size_t size() const { return m_substream.size(); } size_t size() const { return m_substream.size(); }
const Params& GetParams() const { return m_params; }
int GetVersion() = delete; // Deprecated with Params usage int GetVersion() = delete; // Deprecated with Params usage
int GetType() = delete; // Deprecated with Params usage int GetType() = delete; // Deprecated with Params usage
//! Get reference to stream parameters.
template <typename P>
const auto& GetParams() const
{
if constexpr (std::is_convertible_v<Params, P>) {
return m_params;
} else {
return m_substream.template GetParams<P>();
}
}
}; };
/** Wrapper that serializes objects with the specified parameters. */ /** Wrapper that serializes objects with the specified parameters. */
@@ -1176,7 +1171,7 @@ public:
/** \ /** \
* Return a wrapper around t that (de)serializes it with specified parameter params. \ * Return a wrapper around t that (de)serializes it with specified parameter params. \
* \ * \
* See FORMATTER_METHODS_PARAMS for more information on serialization parameters. \ * See SER_PARAMS for more information on serialization parameters. \
*/ \ */ \
template <typename T> \ template <typename T> \
auto operator()(T&& t) const \ auto operator()(T&& t) const \

View File

@@ -289,7 +289,7 @@ public:
template <typename Stream> template <typename Stream>
void Serialize(Stream& s) const void Serialize(Stream& s) const
{ {
if (s.GetParams().m_base_format == BaseFormat::RAW) { if (s.template GetParams<BaseFormat>().m_base_format == BaseFormat::RAW) {
s << m_base_data; s << m_base_data;
} else { } else {
s << Span{HexStr(Span{&m_base_data, 1})}; s << Span{HexStr(Span{&m_base_data, 1})};
@@ -299,7 +299,7 @@ public:
template <typename Stream> template <typename Stream>
void Unserialize(Stream& s) void Unserialize(Stream& s)
{ {
if (s.GetParams().m_base_format == BaseFormat::RAW) { if (s.template GetParams<BaseFormat>().m_base_format == BaseFormat::RAW) {
s >> m_base_data; s >> m_base_data;
} else { } else {
std::string hex{"aa"}; std::string hex{"aa"};
@@ -327,8 +327,9 @@ class Derived : public Base
public: public:
std::string m_derived_data; std::string m_derived_data;
SERIALIZE_METHODS_PARAMS(Derived, obj, DerivedAndBaseFormat, fmt) SERIALIZE_METHODS(Derived, obj)
{ {
auto& fmt = SER_PARAMS(DerivedAndBaseFormat);
READWRITE(fmt.m_base_format(AsBase<Base>(obj))); READWRITE(fmt.m_base_format(AsBase<Base>(obj)));
if (ser_action.ForRead()) { if (ser_action.ForRead()) {