diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 7ea40b30d..f53c91689 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -577,14 +577,8 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) { if (onlyImportant()) { if (newLoaded) { - HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back(); - HistoryItem *prev = block->items.isEmpty() ? nullptr : block->items.back(); - - prev = addMessageGroupAfterPrevToBlock(d, prev, block); - if (block->items.isEmpty()) { - blocks.pop_back(); - delete block; - } + t_assert(!isBuildingFrontBlock()); + addMessageGroup(d); } } else { setNotLoadedAtBottom(); @@ -641,14 +635,12 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { } } - // adding new item to new block - HistoryBlock *block = pushFrontNewBlock(); + startBuildingFrontBlock(); _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); - addItemToBlock(_joinedMessage, block); + addItemToBlock(_joinedMessage); - t_assert(blocks.size() > 1); - blocks.at(1)->items.front()->previousItemChanged(); + finishBuildingFrontBlock(); return _joinedMessage; } @@ -757,15 +749,15 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp } if (!isImportant && onlyImportant()) { - HistoryItem *item = addToHistory(msg), *prev = isEmpty() ? nullptr : blocks.back()->items.back(); + HistoryItem *item = addToHistory(msg); + + t_assert(!isBuildingFrontBlock()); + addMessageGroup([item, this](HistoryItem *previous) -> HistoryItem* { // create(..) + return HistoryGroup::create(this, item, previous ? previous->date : item->date); + }, [item](HistoryGroup *existing) { // unite(..) + existing->uniteWith(item); + }); - if (prev && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(item); - } else { - QDateTime date = prev ? prev->date : item->date; - HistoryBlock *block = prev ? prev->block() : pushBackNewBlock(); - addItemToBlock(HistoryGroup::create(this, item, date), block); - } return item; } @@ -821,22 +813,15 @@ void ChannelHistory::switchMode() { clear(true); + t_assert(!isBuildingFrontBlock()); + newLoaded = _otherNewLoaded; oldLoaded = _otherOldLoaded; if (int count = _otherList.size()) { - blocks.reserve(qCeil(count / float64(MessagesPerPage))); - + blocks.reserve((count / MessagesPerPage) + 1); for (int i = 0; i < count;) { - HistoryBlock *block = pushBackNewBlock(); - - int willAddToBlock = qMin(int(MessagesPerPage), count - i); - block->items.reserve(willAddToBlock); - for (int till = i + willAddToBlock; i < till; ++i) { - t_assert(_otherList.at(i)->detached()); - addItemToBlock(_otherList.at(i), block); - } - - t_assert(!block->items.isEmpty()); + t_assert(_otherList.at(i)->detached()); + addItemToBlock(_otherList.at(i)); } } @@ -1550,16 +1535,10 @@ void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) { } HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { - t_assert(adding != nullptr); - t_assert(adding->detached()); + t_assert(!isBuildingFrontBlock()); + addItemToBlock(adding); - HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back(); - - adding->attachToBlock(block, block->items.size()); - block->items.push_back(adding); - adding->previousItemChanged(); setLastMessage(adding); - if (newMsg) { newItemAdded(adding); } @@ -1671,19 +1650,50 @@ void History::newItemAdded(HistoryItem *item) { } } -HistoryItem *History::addItemToBlock(HistoryItem *item, HistoryBlock *block) { +HistoryBlock *History::prepareBlockForAddingItem() { + if (isBuildingFrontBlock()) { + if (_frontBlock->block) { + return _frontBlock->block; + } + + HistoryBlock *result = _frontBlock->block = new HistoryBlock(this); + if (_frontBlock->expectedItemsCount > 0) { + result->items.reserve(_frontBlock->expectedItemsCount + 1); + } + result->setIndexInHistory(0); + blocks.push_front(result); + for (int i = 1, l = blocks.size(); i < l; ++i) { + blocks.at(i)->setIndexInHistory(i); + } + return result; + } + + bool addNewBlock = blocks.isEmpty() || (blocks.back()->items.size() >= MessagesPerPage); + if (!addNewBlock) { + return blocks.back(); + } + + HistoryBlock *result = new HistoryBlock(this); + result->setIndexInHistory(blocks.size()); + blocks.push_back(result); + + result->items.reserve(MessagesPerPage); + return result; +}; + +void History::addItemToBlock(HistoryItem *item) { + t_assert(item != nullptr); + t_assert(item->detached()); + + HistoryBlock *block = prepareBlockForAddingItem(); + item->attachToBlock(block, block->items.size()); block->items.push_back(item); item->previousItemChanged(); - return item; -} -HistoryItem *History::addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block) { - if (prev && prev->type() == HistoryItemGroup) { - static_cast(prev)->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v); - return prev; + if (isBuildingFrontBlock() && _frontBlock->expectedItemsCount > 0) { + --_frontBlock->expectedItemsCount; } - return addItemToBlock(HistoryGroup::create(this, group, prev ? prev->date : date(group.vdate)), block); } void History::addOlderSlice(const QVector &slice, const QVector *collapsed) { @@ -1700,10 +1710,8 @@ void History::addOlderSlice(const QVector &slice, const QVectorconstData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - HistoryItem *prev = nullptr; - HistoryBlock *block = pushFrontNewBlock(); + startBuildingFrontBlock(slice.size() + (collapsed ? collapsed->size() : 0)); - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; HistoryItem *adding = createItem(*i, false, true); @@ -1714,23 +1722,21 @@ void History::addOlderSlice(const QVector &slice, const QVectorc_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - prev = addItemToBlock(adding, block); + addItemToBlock(adding); } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; const MTPDmessageGroup &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - if (block->items.isEmpty()) { - blocks.pop_front(); - delete block; - block = nullptr; - + HistoryBlock *block = finishBuildingFrontBlock(); + if (!block) { + // If no items were added it means we've loaded everything old. oldLoaded = true; } else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants bool channel = isChannel(); @@ -1808,28 +1814,6 @@ void History::addOlderSlice(const QVector &slice, const QVector 1) { - HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... - HistoryItem *first = blocks.at(1)->items.front(); - - // we've added a new front block, so previous item for - // the old first item of a first block was changed - first->previousItemChanged(); - - // we've added a new front block, now we check if both - // last message of the first block and first message of - // the second block are groups, if they are - unite them - if (first->type() == HistoryItemGroup && last->type() == HistoryItemGroup) { - static_cast(first)->uniteWith(static_cast(last)); - last->destroy(); - - // last->destroy() could've destroyed this new block - // so we can't rely on this pointer any more - block = nullptr; - } - } - if (isChannel()) { asChannelHistory()->checkJoinedMessage(); asChannelHistory()->checkMaxReadMessageDate(); @@ -1845,14 +1829,11 @@ void History::addNewerSlice(const QVector &slice, const QVectorisEmpty())) { const MTPMessageGroup *groupsBegin = (isChannel() && collapsed) ? collapsed->constData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; - HistoryItem *prev = blocks.isEmpty() ? nullptr : blocks.back()->items.back(); - - HistoryBlock *block = pushBackNewBlock(); - - block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0)); + bool atLeastOneAdded = false; for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { --i; HistoryItem *adding = createItem(*i, false, true); @@ -1863,25 +1844,22 @@ void History::addNewerSlice(const QVector &slice, const QVectorc_messageGroup()); if (group.vmin_id.v >= adding->id) break; - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - prev = addItemToBlock(adding, block); + addItemToBlock(adding); + atLeastOneAdded = true; } for (; groupsIt != groupsEnd; ++groupsIt) { if (groupsIt->type() != mtpc_messageGroup) continue; const MTPDmessageGroup &group(groupsIt->c_messageGroup()); - prev = addMessageGroupAfterPrevToBlock(group, prev, block); + addMessageGroup(group); } - if (block->items.isEmpty()) { + if (!atLeastOneAdded) { newLoaded = true; setLastMessage(lastImportantMessage()); - - blocks.pop_back(); - delete block; - block = nullptr; } } @@ -2181,21 +2159,76 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, return newItem; } -HistoryBlock *History::pushBackNewBlock() { - HistoryBlock *result = new HistoryBlock(this); - result->setIndexInHistory(blocks.size()); - blocks.push_back(result); - return result; +template +void History::addMessageGroup(CreateGroup create, UniteGroup unite) { + HistoryItem *previous = nullptr; + if (isBuildingFrontBlock()) { + if (_frontBlock->block) { + previous = _frontBlock->block->items.back(); + } + } else { + if (!blocks.isEmpty()) { + previous = blocks.back()->items.back(); + } + } + + if (previous && previous->type() == HistoryItemGroup) { + unite(static_cast(previous)); + return; + } + + HistoryGroup *result = create(previous); + if (isBuildingFrontBlock()) { + addItemToBuildingFrontBlock(result); + } else { + addItemToBackBlock(result); + } } -HistoryBlock *History::pushFrontNewBlock() { - HistoryBlock *result = new HistoryBlock(this); - result->setIndexInHistory(0); - blocks.push_front(result); - for (int i = 1, l = blocks.size(); i < l; ++i) { - blocks.at(i)->setIndexInHistory(i); +void History::addMessageGroup(const MTPDmessageGroup &group) { + addMessageGroup([&group, this](HistoryItem *previous) -> HistoryItem* { // create(..) + return HistoryGroup::create(this, group, previous ? previous->date : date(group.vdate)); + }, [&group](HistoryGroup *existing) { // unite(..) + existing->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v); + }); +} + +void History::startBuildingFrontBlock(int expectedItemsCount) { + t_assert(!isBuildingFrontBlock()); + t_assert(expectedItemsCount > 0); + + _frontBlock.reset(new BuildingBlock()); + _frontBlock->expectedItemsCount = expectedItemsCount; +} + +HistoryBlock *History::finishBuildingFrontBlock() { + t_assert(isBuildingFrontBlock()); + + // Some checks if there was some message history already + HistoryBlock *block = _frontBlock->block; + if (block && blocks.size() > 1) { + HistoryItem *last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... + HistoryItem *first = blocks.at(1)->items.front(); + + // we've added a new front block, so previous item for + // the old first item of a first block was changed + first->previousItemChanged(); + + // we've added a new front block, now we check if both + // last message of the first block and first message of + // the second block are groups, if they are - unite them + if (first->type() == HistoryItemGroup && last->type() == HistoryItemGroup) { + static_cast(first)->uniteWith(static_cast(last)); + last->destroy(); + + // last->destroy() could've destroyed this new block + // so we can't rely on this pointer any more + block = _frontBlock->block; + } } - return result; + + _frontBlock.clear(); + return block; } void History::clearNotifications() { @@ -2572,6 +2605,10 @@ void History::changeMsgId(MsgId oldId, MsgId newId) { void History::removeBlock(HistoryBlock *block) { t_assert(block->items.isEmpty()); + if (_frontBlock && block == _frontBlock->block) { + _frontBlock->block = nullptr; + } + int index = block->indexInHistory(); blocks.removeAt(index); for (int i = index, l = blocks.size(); i < l; ++i) { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 497dd1060..b42e7e8b0 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -470,19 +470,64 @@ protected: void clearOnDestroy(); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); + friend class HistoryBlock; + + // this method just removes a block from the blocks list + // when the last item from this block was detached and + // calls the required previousItemChanged() + void removeBlock(HistoryBlock *block); + + void clearBlocks(bool leaveItems); + + HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem); + HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg); + HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); + HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); + + HistoryItem *addNewItem(HistoryItem *adding, bool newMsg); + HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); + + // All this methods add a new item to the first or last block + // depending on if we are in isBuildingFronBlock() state. + // The last block is created on the go if it is needed. + + // If the previous item is a message group the new group is + // not created but is just united with the previous one. + // create(HistoryItem *previous) should return a new HistoryGroup* + // unite(HistoryGroup *existing) should unite a new group with an existing + template + void addMessageGroup(CreateGroup create, UniteGroup unite); + void addMessageGroup(const MTPDmessageGroup &group); + + // Adds the item to the back or front block, depending on + // isBuildingFrontBlock(), creating the block if necessary. + void addItemToBlock(HistoryItem *item); + + // Usually all new items are added to the last block. + // Only when we scroll up and add a new slice to the + // front we want to create a new front block. + void startBuildingFrontBlock(int expectedItemsCount = 1); + HistoryBlock *finishBuildingFrontBlock(); // Returns the built block or nullptr if nothing was added. + bool isBuildingFrontBlock() const { + return !_buildingFrontBlock.isNull(); + } + private: - enum Flag { + enum class Flag { f_has_pending_resized_items = (1 << 0), - f_pending_resize = (1 << 1), + f_pending_resize = (1 << 1), }; Q_DECLARE_FLAGS(Flags, Flag); - Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, Flags::enum_type f2) Q_DECL_NOTHROW { + Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, Flags::enum_type f2) noexcept { return QFlags(f1) | f2; } - Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, QFlags f2) Q_DECL_NOTHROW { + Q_DECL_CONSTEXPR friend inline QFlags operator|(Flags::enum_type f1, QFlags f2) noexcept { return f2 | f1; } + Q_DECL_CONSTEXPR friend inline QFlags operator~(Flags::enum_type f) noexcept { + return ~QFlags(f); + } Flags _flags; ChatListLinksMap _chatListLinks; @@ -497,28 +542,18 @@ private: MediaOverviewIds overviewIds[OverviewCount]; int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded - friend class HistoryBlock; - friend class ChannelHistory; + // A pointer to the block that is currently being built. + // We hold this pointer so we can destroy it while building + // and then create a new one if it is necessary. + struct BuildingBlock { + int expectedItemsCount = 0; // optimization for block->items.reserve() call + HistoryBlock *block = nullptr; + }; + UniquePointer _buildingFrontBlock; - // this method just removes a block from the blocks list - // when the last item from this block was detached and - // calls the required previousItemChanged() - void removeBlock(HistoryBlock *block); - - void clearBlocks(bool leaveItems); - - HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem); - HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg); - HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption); - HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption); - - HistoryItem *addItemToBlock(HistoryItem *item, HistoryBlock *block); - HistoryItem *addNewItem(HistoryItem *adding, bool newMsg); - HistoryItem *addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block); - HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); - - HistoryBlock *pushBackNewBlock(); - HistoryBlock *pushFrontNewBlock(); + // Creates if necessary a new block for adding item. + // Depending on isBuildingFrontBlock() gets front or back block. + HistoryBlock *prepareBlockForAddingItem(); };