From ecf835175ce26cbc0b22bc1916d8c51177c7bfe7 Mon Sep 17 00:00:00 2001 From: Christian Seiler Date: Fri, 29 May 2026 20:21:55 +0200 Subject: [PATCH] C++: serialization: add version-dependent serialization support The code always tries to serialize with the lowest possible version, but will use the minimum version required to satisfy all settings used in the file description. --- cpp/src/monoformat_structured.cpp | 92 +++++++++++++++++++++++++------ cpp/src/monoformat_structured.hpp | 39 ++++++++----- 2 files changed, 100 insertions(+), 31 deletions(-) diff --git a/cpp/src/monoformat_structured.cpp b/cpp/src/monoformat_structured.cpp index 5d28259..585252c 100644 --- a/cpp/src/monoformat_structured.cpp +++ b/cpp/src/monoformat_structured.cpp @@ -61,7 +61,11 @@ ElementType ImageElement::elementType() const { return ElementType::Image; } -std::size_t ImageElement::serializeTo(std::span target) const { +std::uint32_t ImageElement::minimumFormatVersion() const { + return 1; +} + +std::size_t ImageElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::Image)); pos = writeU16LE(target, pos, m_x); @@ -211,7 +215,11 @@ ElementType AnimationElement::elementType() const { return ElementType::Animation; } -std::size_t AnimationElement::serializeTo(std::span target) const { +std::uint32_t AnimationElement::minimumFormatVersion() const { + return 1; +} + +std::size_t AnimationElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::Animation)); pos = writeU16LE(target, pos, m_x); @@ -367,7 +375,11 @@ ElementType HScrollImageElement::elementType() const { return ElementType::HScrollImage; } -std::size_t HScrollImageElement::serializeTo(std::span target) const { +std::uint32_t HScrollImageElement::minimumFormatVersion() const { + return 1; +} + +std::size_t HScrollImageElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::HScrollImage)); pos = writeU16LE(target, pos, m_x); @@ -613,7 +625,11 @@ ElementType VScrollImageElement::elementType() const { return ElementType::VScrollImage; } -std::size_t VScrollImageElement::serializeTo(std::span target) const { +std::uint32_t VScrollImageElement::minimumFormatVersion() const { + return 1; +} + +std::size_t VScrollImageElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::VScrollImage)); pos = writeU16LE(target, pos, m_x); @@ -831,7 +847,11 @@ ElementType LineElement::elementType() const { return ElementType::Line; } -std::size_t LineElement::serializeTo(std::span target) const { +std::uint32_t LineElement::minimumFormatVersion() const { + return 1; +} + +std::size_t LineElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::Line)); pos = writeU16LE(target, pos, m_originX); @@ -951,7 +971,11 @@ ElementType ClippedTextElement::elementType() const { return ElementType::ClippedText; } -std::size_t ClippedTextElement::serializeTo(std::span target) const { +std::uint32_t ClippedTextElement::minimumFormatVersion() const { + return 1; +} + +std::size_t ClippedTextElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::ClippedText)); pos = writeU16LE(target, pos, m_x); @@ -1083,7 +1107,11 @@ ElementType HScrollTextElement::elementType() const { return ElementType::HScrollText; } -std::size_t HScrollTextElement::serializeTo(std::span target) const { +std::uint32_t HScrollTextElement::minimumFormatVersion() const { + return 1; +} + +std::size_t HScrollTextElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::HScrollText)); pos = writeU16LE(target, pos, m_x); @@ -1300,7 +1328,11 @@ ElementType CurrentTimeElement::elementType() const { return ElementType::CurrentTime; } -std::size_t CurrentTimeElement::serializeTo(std::span target) const { +std::uint32_t CurrentTimeElement::minimumFormatVersion() const { + return 1; +} + +std::size_t CurrentTimeElement::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU16LE(target, pos, static_cast(ElementType::CurrentTime)); pos = writeU16LE(target, pos, m_x); @@ -1485,7 +1517,15 @@ SectionType AlwaysDrawnSection::sectionType() const { return SectionType::AlwaysDrawn; } -std::size_t AlwaysDrawnSection::serializeTo(std::span target) const { +std::uint32_t AlwaysDrawnSection::minimumFormatVersion() const { + std::uint32_t result = 1; + for (auto const& element : m_elements) { + result = std::max(result, element->minimumFormatVersion()); + } + return result; +} + +std::size_t AlwaysDrawnSection::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU8LE(target, pos, static_cast(SectionType::AlwaysDrawn)); // Will be replaced later @@ -1498,9 +1538,9 @@ std::size_t AlwaysDrawnSection::serializeTo(std::span target) const { pos = writeU16LE(target, pos, m_elements.size()); for (auto const& element : m_elements) { if (pos < target.size()) { - pos += element->serializeTo(target.subspan(pos)); + pos += element->serializeTo(target.subspan(pos), formatVersion); } else { - pos += element->serializeTo({}); + pos += element->serializeTo({}, formatVersion); } } pos = alignNextWrite(target, pos, 4); @@ -1655,7 +1695,15 @@ SectionType TimeBasedDrawnSection::sectionType() const { return SectionType::TimeBasedDrawn; } -std::size_t TimeBasedDrawnSection::serializeTo(std::span target) const { +std::uint32_t TimeBasedDrawnSection::minimumFormatVersion() const { + std::uint32_t result = 1; + for (auto const& element : m_elements) { + result = std::max(result, element->minimumFormatVersion()); + } + return result; +} + +std::size_t TimeBasedDrawnSection::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU8LE(target, pos, static_cast(SectionType::AlwaysDrawn)); // Will be replaced later @@ -1670,9 +1718,9 @@ std::size_t TimeBasedDrawnSection::serializeTo(std::span target) cons pos = writeU64LE(target, pos, std::bit_cast(m_endTimestamp)); for (auto const& element : m_elements) { if (pos < target.size()) { - pos += element->serializeTo(target.subspan(pos)); + pos += element->serializeTo(target.subspan(pos), formatVersion); } else { - pos += element->serializeTo({}); + pos += element->serializeTo({}, formatVersion); } } pos = alignNextWrite(target, pos, 4); @@ -1764,7 +1812,11 @@ SectionType CustomFontSection::sectionType() const { return SectionType::CustomFont; } -std::size_t CustomFontSection::serializeTo(std::span target) const { +std::uint32_t CustomFontSection::minimumFormatVersion() const { + return 1; +} + +std::size_t CustomFontSection::serializeTo(std::span target, std::uint32_t formatVersion) const { std::size_t pos = 0; pos = writeU8LE(target, pos, static_cast(SectionType::AlwaysDrawn)); // Will be replaced later @@ -1817,19 +1869,23 @@ std::expected, ParseError> CustomFontSection: std::size_t serializeFile(std::span& buffer, File const& file) { std::size_t pos = 0; + std::uint32_t formatVersion = 1; + for (auto const& section : file.sections) { + formatVersion = std::max(formatVersion, section->minimumFormatVersion()); + } pos = writeU8LE(buffer, pos, 0xAF); pos = writeU8LE(buffer, pos, 0x7E); pos = writeU8LE(buffer, pos, 0x2B); pos = writeU8LE(buffer, pos, 0x63); - pos = writeU32LE(buffer, pos, 1); + pos = writeU32LE(buffer, pos, formatVersion); pos = writeU16LE(buffer, pos, file.sections.size()); pos = writeU16LE(buffer, pos, 0); pos = alignNextWrite(buffer, pos, 4); for (auto const& section : file.sections) { if (pos < buffer.size()) { - pos += section->serializeTo(buffer.subspan(pos)); + pos += section->serializeTo(buffer.subspan(pos), formatVersion); } else { - pos += section->serializeTo({}); + pos += section->serializeTo({}, formatVersion); } } return pos; diff --git a/cpp/src/monoformat_structured.hpp b/cpp/src/monoformat_structured.hpp index 56ffbae..ff30d72 100644 --- a/cpp/src/monoformat_structured.hpp +++ b/cpp/src/monoformat_structured.hpp @@ -15,7 +15,8 @@ namespace monoformat { struct Section { virtual ~Section() = default; virtual SectionType sectionType() const = 0; - virtual std::size_t serializeTo(std::span target) const = 0; + virtual std::uint32_t minimumFormatVersion() const = 0; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const = 0; protected: Section() = default; @@ -26,7 +27,8 @@ class CustomFontSection; struct Element { virtual ~Element() = default; virtual ElementType elementType() const = 0; - virtual std::size_t serializeTo(std::span target) const = 0; + virtual std::uint32_t minimumFormatVersion() const = 0; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const = 0; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) = 0; protected: @@ -48,7 +50,8 @@ struct ImageElement : public Element { virtual ~ImageElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -80,7 +83,8 @@ struct AnimationElement : public Element { virtual ~AnimationElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -113,7 +117,8 @@ struct HScrollImageElement : public Element { virtual ~HScrollImageElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -147,7 +152,8 @@ struct VScrollImageElement : public Element { virtual ~VScrollImageElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -175,7 +181,8 @@ struct LineElement : public Element { virtual ~LineElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -201,7 +208,8 @@ struct ClippedTextElement : public Element { virtual ~ClippedTextElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -229,7 +237,8 @@ struct HScrollTextElement : public Element { virtual ~HScrollTextElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -258,7 +267,8 @@ struct CurrentTimeElement : public Element { virtual ~CurrentTimeElement() override; virtual ElementType elementType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; virtual void drawTo(OneBitBufferInterface* imageBuffer, std::size_t animationTick, std::int64_t currentTimestamp, std::span customFonts) override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -294,7 +304,8 @@ struct AlwaysDrawnSection : public Section { std::unique_ptr eraseElement(std::size_t index); virtual SectionType sectionType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -331,7 +342,8 @@ struct TimeBasedDrawnSection : public Section { void setEndTimestamp(std::int64_t value); virtual SectionType sectionType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion); @@ -352,7 +364,8 @@ struct CustomFontSection : public Section { void setFontData(std::span fontData); virtual SectionType sectionType() const override; - virtual std::size_t serializeTo(std::span target) const override; + virtual std::uint32_t minimumFormatVersion() const override; + virtual std::size_t serializeTo(std::span target, std::uint32_t formatVersion) const override; static std::expected, ParseError> parse(std::span& buffer, std::uint32_t formatVersion);