From f7b4994887612b25bdcad8df7766af479a2603c3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 3 Jun 2020 15:27:18 +0400 Subject: [PATCH] Optimize Ui::Text::String blocks vector. --- ui/text/text.cpp | 114 ++++---------- ui/text/text.h | 40 ++--- ui/text/text_block.cpp | 32 ++-- ui/text/text_block.h | 341 +++++++++++++++++++++++++++++++++++------ 4 files changed, 360 insertions(+), 167 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 60a8a52..8b9b17a 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -461,13 +461,13 @@ void Parser::createBlock(int32 skipBack) { } _lastSkipped = false; if (_emoji) { - _t->_blocks.push_back(std::make_unique(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji)); + _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji)); _emoji = nullptr; _lastSkipped = true; } else if (newline) { - _t->_blocks.push_back(std::make_unique(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex)); + _t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex)); } else { - _t->_blocks.push_back(std::make_unique(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex)); + _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex)); } _blockStart += len; blockCreated(); @@ -477,7 +477,7 @@ void Parser::createBlock(int32 skipBack) { void Parser::createSkipBlock(int32 w, int32 h) { createBlock(); _t->_text.push_back('_'); - _t->_blocks.push_back(std::make_unique(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex)); + _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex)); blockCreated(); } @@ -913,15 +913,14 @@ void Parser::checkForElidedSkipBlock() { void Parser::finalize(const TextParseOptions &options) { _t->_links.resize(_maxLnkIndex); - for (const auto &block : _t->_blocks) { - const auto b = block.get(); - const auto shiftedIndex = b->lnkIndex(); + for (auto &block : _t->_blocks) { + const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { continue; } const auto realIndex = (shiftedIndex - kStringLinkIndexShift); const auto index = _maxLnkIndex + realIndex; - b->setLnkIndex(index); + block->setLnkIndex(index); if (_t->_links.size() >= index) { continue; } @@ -1165,7 +1164,7 @@ public: _wLeft -= _elideRemoveFromEnd; } - _parDirection = static_cast(b)->nextDirection(); + _parDirection = static_cast(b)->nextDirection(); if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection(); initNextParagraph(i + 1); @@ -1187,7 +1186,7 @@ public: } if (_btype == TextBlockTText) { - auto t = static_cast(b); + auto t = static_cast(b); if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line _last_rPadding += b->f_rpadding(); @@ -1678,7 +1677,7 @@ private: } Emoji::Draw( *_p, - static_cast(currentBlock)->emoji, + static_cast(currentBlock)->_emoji, Emoji::GetSizeNormal(), (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY); @@ -1847,7 +1846,7 @@ private: _p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg); } - void elideSaveBlock(int32 blockIndex, AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) { + void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) { if (_elideSavedBlock) { restoreAfterElided(); } @@ -1855,7 +1854,7 @@ private: _elideSavedIndex = blockIndex; auto mutableText = const_cast(_t); _elideSavedBlock = std::move(mutableText->_blocks[blockIndex]); - mutableText->_blocks[blockIndex] = std::make_unique(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, _elideSavedBlock->flags(), _elideSavedBlock->lnkIndex()); + mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex()); _blocksSize = blockIndex + 1; _endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr); } @@ -1870,7 +1869,7 @@ private: } } - void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, AbstractBlock *&_endBlock, int repeat = 0) { + void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat = 0) { static const auto _Elide = QString::fromLatin1("..."); _f = _t->_st->font; @@ -1980,7 +1979,7 @@ private: void restoreAfterElided() { if (_elideSavedBlock) { - const_cast(_t)->_blocks[_elideSavedIndex] = std::move(_elideSavedBlock); + const_cast(_t)->_blocks[_elideSavedIndex] = std::move(*_elideSavedBlock); } } @@ -2034,7 +2033,7 @@ private: return result; } - void eSetFont(AbstractBlock *block) { + void eSetFont(const AbstractBlock *block) { const auto flags = block->flags(); const auto usedFont = [&] { if (const auto index = block->lnkIndex()) { @@ -2609,7 +2608,7 @@ private: } private: - void applyBlockProperties(AbstractBlock *block) { + void applyBlockProperties(const AbstractBlock *block) { eSetFont(block); if (_p) { if (block->lnkIndex()) { @@ -2660,7 +2659,7 @@ private: // elided hack support int _blocksSize = 0; int _elideSavedIndex = 0; - std::unique_ptr _elideSavedBlock; + std::optional _elideSavedBlock; int _lineStart = 0; int _localFrom = 0; @@ -2679,7 +2678,8 @@ private: String::String(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) { } -String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) : _minResizeWidth(minResizeWidth) { +String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) +: _minResizeWidth(minResizeWidth) { if (richText) { setRichText(st, text, options); } else { @@ -2687,60 +2687,6 @@ String::String(const style::TextStyle &st, const QString &text, const TextParseO } } -String::String(const String &other) -: _minResizeWidth(other._minResizeWidth) -, _maxWidth(other._maxWidth) -, _minHeight(other._minHeight) -, _text(other._text) -, _st(other._st) -, _links(other._links) -, _startDir(other._startDir) { - _blocks.reserve(other._blocks.size()); - for (auto &block : other._blocks) { - _blocks.push_back(block->clone()); - } -} - -String::String(String &&other) -: _minResizeWidth(other._minResizeWidth) -, _maxWidth(other._maxWidth) -, _minHeight(other._minHeight) -, _text(other._text) -, _st(other._st) -, _blocks(std::move(other._blocks)) -, _links(other._links) -, _startDir(other._startDir) { - other.clearFields(); -} - -String &String::operator=(const String &other) { - _minResizeWidth = other._minResizeWidth; - _maxWidth = other._maxWidth; - _minHeight = other._minHeight; - _text = other._text; - _st = other._st; - _blocks = TextBlocks(other._blocks.size()); - _links = other._links; - _startDir = other._startDir; - for (int32 i = 0, l = _blocks.size(); i < l; ++i) { - _blocks[i] = other._blocks.at(i)->clone(); - } - return *this; -} - -String &String::operator=(String &&other) { - _minResizeWidth = other._minResizeWidth; - _maxWidth = other._maxWidth; - _minHeight = other._minHeight; - _text = other._text; - _st = other._st; - _blocks = std::move(other._blocks); - _links = other._links; - _startDir = other._startDir; - other.clearFields(); - return *this; -} - void String::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) { _st = &st; clear(); @@ -2757,8 +2703,8 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { int32 lineHeight = 0; int32 lastNewlineStart = 0; QFixed _width = 0, last_rBearing = 0, last_rPadding = 0; - for (auto i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) { - auto b = i->get(); + for (auto &block : _blocks) { + auto b = block.get(); auto _btype = b->type(); auto blockHeight = countBlockHeight(b, _st); if (_btype == TextBlockTNewline) { @@ -2775,7 +2721,7 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { } } lastNewlineStart = b->from(); - lastNewline = static_cast(b); + lastNewline = &block.unsafe(); _minHeight += lineHeight; lineHeight = 0; @@ -2824,19 +2770,19 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { } int String::countMaxMonospaceWidth() const { - NewlineBlock *lastNewline = 0; + const NewlineBlock *lastNewline = nullptr; auto result = QFixed(); auto paragraphWidth = QFixed(); auto lastNewlineStart = 0; auto fullMonospace = true; QFixed _width = 0, last_rBearing = 0, last_rPadding = 0; - for (auto i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) { - auto b = i->get(); + for (auto &block : _blocks) { + auto b = block.get(); auto _btype = b->type(); if (_btype == TextBlockTNewline) { lastNewlineStart = b->from(); - lastNewline = static_cast(b); + lastNewline = &block.unsafe(); last_rBearing = b->f_rbearing(); last_rPadding = b->f_rpadding(); @@ -2943,7 +2889,7 @@ bool String::updateSkipBlock(int width, int height) { _blocks.pop_back(); } _text.push_back('_'); - _blocks.push_back(std::make_unique( + _blocks.push_back(Block::Skip( _st->font, _text, _text.size() - 1, @@ -3036,7 +2982,7 @@ void String::enumerateLines( } if (_btype == TextBlockTText) { - auto t = static_cast(b.get()); + const auto t = &b.unsafe(); if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line last_rPadding += b->f_rpadding(); @@ -3389,7 +3335,7 @@ IsolatedEmoji String::toIsolatedEmoji() const { if (block->lnkIndex()) { return IsolatedEmoji(); } else if (type == TextBlockTEmoji) { - result.items[index++] = static_cast(block.get())->emoji; + result.items[index++] = block.unsafe()._emoji; } else if (type != TextBlockTSkip) { return IsolatedEmoji(); } @@ -3409,7 +3355,5 @@ void String::clearFields() { _startDir = Qt::LayoutDirectionAuto; } -String::~String() = default; - } // namespace Text } // namespace Ui diff --git a/ui/text/text.h b/ui/text/text.h index 709f22a..1768d34 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -7,6 +7,7 @@ #pragma once #include "ui/text/text_entity.h" +#include "ui/text/text_block.h" #include "ui/painter.h" #include "ui/click_handler.h" #include "base/flags.h" @@ -69,7 +70,6 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF }; namespace Ui { namespace Text { -class AbstractBlock; struct IsolatedEmoji; struct StateRequest { @@ -108,11 +108,17 @@ struct StateRequestElided : public StateRequest { class String { public: String(int32 minResizeWidth = QFIXED_MAX); - String(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false); - String(const String &other); - String(String &&other); - String &operator=(const String &other); - String &operator=(String &&other); + String( + const style::TextStyle &st, + const QString &text, + const TextParseOptions &options = _defaultOptions, + int32 minResizeWidth = QFIXED_MAX, + bool richText = false); + String(const String &other) = default; + String(String &&other) = default; + String &operator=(const String &other) = default; + String &operator=(String &&other) = default; + ~String() = default; int countWidth(int width, bool breakEverywhere = false) const; int countHeight(int width, bool breakEverywhere = false) const; @@ -168,34 +174,14 @@ public: TextSelection selection = AllTextSelection) const; IsolatedEmoji toIsolatedEmoji() const; - bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation - if (_text.size() < maxdots) return false; - - int32 nowDots = 0, from = _text.size() - maxdots, to = _text.size(); - for (int32 i = from; i < to; ++i) { - if (_text.at(i) == QChar('.')) { - ++nowDots; - } - } - if (nowDots == dots) return false; - for (int32 j = from; j < from + dots; ++j) { - _text[j] = QChar('.'); - } - for (int32 j = from + dots; j < to; ++j) { - _text[j] = QChar(' '); - } - return true; - } - const style::TextStyle *style() const { return _st; } void clear(); - ~String(); private: - using TextBlocks = std::vector>; + using TextBlocks = QVector; using TextLinks = QVector; uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index c2a3f11..ace85d1 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -310,10 +310,20 @@ private: }; QFixed AbstractBlock::f_rbearing() const { - return (type() == TextBlockTText) ? static_cast(this)->real_f_rbearing() : 0; + return (type() == TextBlockTText) + ? static_cast(this)->real_f_rbearing() + : 0; } -TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex) { +TextBlock::TextBlock( + const style::font &font, + const QString &str, + QFixed minResizeWidth, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) +: AbstractBlock(font, str, from, length, flags, lnkIndex) { _flags |= ((TextBlockTText & 0x0F) << 8); if (length) { style::font blockFont = font; @@ -347,11 +357,18 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi } } -EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji) : AbstractBlock(font, str, from, length, flags, lnkIndex) -, emoji(emoji) { +EmojiBlock::EmojiBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + EmojiPtr emoji) +: AbstractBlock(font, str, from, length, flags, lnkIndex) +, _emoji(emoji) { _flags |= ((TextBlockTEmoji & 0x0F) << 8); _width = int(st::emojiSize + 2 * st::emojiPadding); - _rpadding = 0; for (auto i = length; i != 0;) { auto ch = str[_from + (--i)]; @@ -363,10 +380,5 @@ EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, } } -SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : AbstractBlock(font, str, from, 1, 0, lnkIndex), _height(h) { - _flags |= ((TextBlockTSkip & 0x0F) << 8); - _width = w; -} - } // namespace Text } // namespace Ui diff --git a/ui/text/text_block.h b/ui/text/text_block.h index 0bd5953..10769f6 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -34,16 +34,13 @@ enum TextBlockFlags { class AbstractBlock { public: - AbstractBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { - } - uint16 from() const { return _from; } - int32 width() const { + int width() const { return _width.toInt(); } - int32 rpadding() const { + int rpadding() const { return _rpadding.toInt(); } QFixed f_width() const { @@ -70,11 +67,18 @@ public: return (_flags & 0xFF); } - virtual std::unique_ptr clone() const = 0; - virtual ~AbstractBlock() { +protected: + AbstractBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) + : _from(from) + , _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { } -protected: uint16 _from = 0; uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags @@ -90,9 +94,16 @@ protected: }; -class NewlineBlock : public AbstractBlock { +class NewlineBlock final : public AbstractBlock { public: - NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex), _nextDir(Qt::LayoutDirectionAuto) { + NewlineBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) + : AbstractBlock(font, str, from, length, flags, lnkIndex) { _flags |= ((TextBlockTNewline & 0x0F) << 8); } @@ -100,12 +111,8 @@ public: return _nextDir; } - std::unique_ptr clone() const override { - return std::make_unique(*this); - } - private: - Qt::LayoutDirection _nextDir; + Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto; friend class String; friend class Parser; @@ -113,14 +120,16 @@ private: }; -class TextWord { +class TextWord final { public: TextWord() = default; TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) - : _from(from) - , _width(width) - , _rpadding(rpadding) - , _rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) { + : _from(from) + , _rbearing((rbearing.value() > 0x7FFF) + ? 0x7FFF + : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) + , _width(width) + , _rpadding(rpadding) { } uint16 from() const { return _from; @@ -140,45 +149,50 @@ public: private: uint16 _from = 0; - QFixed _width, _rpadding; int16 _rbearing = 0; + QFixed _width, _rpadding; }; -class TextBlock : public AbstractBlock { +class TextBlock final : public AbstractBlock { public: - TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex); - - std::unique_ptr clone() const override { - return std::make_unique(*this); - } + TextBlock( + const style::font &font, + const QString &str, + QFixed minResizeWidth, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex); private: - friend class AbstractBlock; QFixed real_f_rbearing() const { return _words.isEmpty() ? 0 : _words.back().f_rbearing(); } - using TextWords = QVector; - TextWords _words; + QVector _words; friend class String; friend class Parser; friend class Renderer; friend class BlockParser; + friend class AbstractBlock; }; -class EmojiBlock : public AbstractBlock { +class EmojiBlock final : public AbstractBlock { public: - EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji); - - std::unique_ptr clone() const override { - return std::make_unique(*this); - } + EmojiBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + EmojiPtr emoji); private: - EmojiPtr emoji = nullptr; + EmojiPtr _emoji = nullptr; friend class String; friend class Parser; @@ -186,20 +200,27 @@ private: }; -class SkipBlock : public AbstractBlock { +class SkipBlock final : public AbstractBlock { public: - SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex); + SkipBlock( + const style::font &font, + const QString &str, + uint16 from, + int32 w, + int32 h, + uint16 lnkIndex) + : AbstractBlock(font, str, from, 1, 0, lnkIndex) + , _height(h) { + _flags |= ((TextBlockTSkip & 0x0F) << 8); + _width = w; + } - int32 height() const { + int height() const { return _height; } - std::unique_ptr clone() const override { - return std::make_unique(*this); - } - private: - int32 _height; + int _height = 0; friend class String; friend class Parser; @@ -207,5 +228,235 @@ private: }; +class Block final { +public: + Block() { + Unexpected("Should not be called."); + } + Block(const Block &other) { + switch (other->type()) { + case TextBlockTNewline: + emplace(other.unsafe()); + break; + case TextBlockTText: + emplace(other.unsafe()); + break; + case TextBlockTEmoji: + emplace(other.unsafe()); + break; + case TextBlockTSkip: + emplace(other.unsafe()); + break; + default: + Unexpected("Bad text block type in Block(const Block&)."); + } + } + Block(Block &&other) { + switch (other->type()) { + case TextBlockTNewline: + emplace(std::move(other.unsafe())); + break; + case TextBlockTText: + emplace(std::move(other.unsafe())); + break; + case TextBlockTEmoji: + emplace(std::move(other.unsafe())); + break; + case TextBlockTSkip: + emplace(std::move(other.unsafe())); + break; + default: + Unexpected("Bad text block type in Block(Block&&)."); + } + } + Block &operator=(const Block &other) { + if (&other == this) { + return *this; + } + destroy(); + switch (other->type()) { + case TextBlockTNewline: + emplace(other.unsafe()); + break; + case TextBlockTText: + emplace(other.unsafe()); + break; + case TextBlockTEmoji: + emplace(other.unsafe()); + break; + case TextBlockTSkip: + emplace(other.unsafe()); + break; + default: + Unexpected("Bad text block type in operator=(const Block&)."); + } + return *this; + } + Block &operator=(Block &&other) { + if (&other == this) { + return *this; + } + destroy(); + switch (other->type()) { + case TextBlockTNewline: + emplace(std::move(other.unsafe())); + break; + case TextBlockTText: + emplace(std::move(other.unsafe())); + break; + case TextBlockTEmoji: + emplace(std::move(other.unsafe())); + break; + case TextBlockTSkip: + emplace(std::move(other.unsafe())); + break; + default: + Unexpected("Bad text block type in operator=(Block&&)."); + } + return *this; + } + ~Block() { + destroy(); + } + + [[nodiscard]] static Block Newline( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) { + return New(font, str, from, length, flags, lnkIndex); + } + + [[nodiscard]] static Block Text( + const style::font &font, + const QString &str, + QFixed minResizeWidth, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) { + return New( + font, + str, + minResizeWidth, + from, + length, + flags, + lnkIndex); + } + + [[nodiscard]] static Block Emoji( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + EmojiPtr emoji) { + return New( + font, + str, + from, + length, + flags, + lnkIndex, + emoji); + } + + [[nodiscard]] static Block Skip( + const style::font &font, + const QString &str, + uint16 from, + int32 w, + int32 h, + uint16 lnkIndex) { + return New(font, str, from, w, h, lnkIndex); + } + + template + [[nodiscard]] FinalBlock &unsafe() { + return *reinterpret_cast(&_data); + } + + template + [[nodiscard]] const FinalBlock &unsafe() const { + return *reinterpret_cast(&_data); + } + + [[nodiscard]] AbstractBlock *get() { + return &unsafe(); + } + + [[nodiscard]] const AbstractBlock *get() const { + return &unsafe(); + } + + [[nodiscard]] AbstractBlock *operator->() { + return get(); + } + + [[nodiscard]] const AbstractBlock *operator->() const { + return get(); + } + + [[nodiscard]] AbstractBlock &operator*() { + return *get(); + } + + [[nodiscard]] const AbstractBlock &operator*() const { + return *get(); + } + +private: + struct Tag { + }; + + explicit Block(const Tag &) { + } + + template + [[nodiscard]] static Block New(Args &&...args) { + auto result = Block(Tag{}); + result.emplace(std::forward(args)...); + return result; + } + + template + void emplace(Args &&...args) { + new (&_data) FinalType(std::forward(args)...); + } + + void destroy() { + switch (get()->type()) { + case TextBlockTNewline: + unsafe().~NewlineBlock(); + break; + case TextBlockTText: + unsafe().~TextBlock(); + break; + case TextBlockTEmoji: + unsafe().~EmojiBlock(); + break; + case TextBlockTSkip: + unsafe().~SkipBlock(); + break; + default: + Unexpected("Bad text block type in Block(Block&&)."); + } + } + + static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock)); + static_assert(alignof(NewlineBlock) <= alignof(AbstractBlock)); + static_assert(sizeof(EmojiBlock) <= sizeof(TextBlock)); + static_assert(alignof(EmojiBlock) <= alignof(AbstractBlock)); + static_assert(sizeof(SkipBlock) <= sizeof(TextBlock)); + static_assert(alignof(SkipBlock) <= alignof(AbstractBlock)); + + std::aligned_storage_t _data; + +}; + } // namespace Text } // namespace Ui