Creating HistoryBlock only before adding an item to it.

Now invariants always should be kept true, including
indexInBlock, indexInHistory and !block->items.isEmpty()

Need to backport UniquePointer from other branch.
This commit is contained in:
John Preston 2016-03-31 14:37:58 +04:00
parent 8f06244895
commit fed715c1f4
2 changed files with 204 additions and 132 deletions

View file

@ -577,14 +577,8 @@ void ChannelHistory::addNewGroup(const MTPMessageGroup &group) {
if (onlyImportant()) { if (onlyImportant()) {
if (newLoaded) { if (newLoaded) {
HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back(); t_assert(!isBuildingFrontBlock());
HistoryItem *prev = block->items.isEmpty() ? nullptr : block->items.back(); addMessageGroup(d);
prev = addMessageGroupAfterPrevToBlock(d, prev, block);
if (block->items.isEmpty()) {
blocks.pop_back();
delete block;
}
} }
} else { } else {
setNotLoadedAtBottom(); setNotLoadedAtBottom();
@ -641,14 +635,12 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
} }
} }
// adding new item to new block startBuildingFrontBlock();
HistoryBlock *block = pushFrontNewBlock();
_joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags);
addItemToBlock(_joinedMessage, block); addItemToBlock(_joinedMessage);
t_assert(blocks.size() > 1); finishBuildingFrontBlock();
blocks.at(1)->items.front()->previousItemChanged();
return _joinedMessage; return _joinedMessage;
} }
@ -757,15 +749,15 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp
} }
if (!isImportant && onlyImportant()) { 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<HistoryGroup*>(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; return item;
} }
@ -821,22 +813,15 @@ void ChannelHistory::switchMode() {
clear(true); clear(true);
t_assert(!isBuildingFrontBlock());
newLoaded = _otherNewLoaded; newLoaded = _otherNewLoaded;
oldLoaded = _otherOldLoaded; oldLoaded = _otherOldLoaded;
if (int count = _otherList.size()) { if (int count = _otherList.size()) {
blocks.reserve(qCeil(count / float64(MessagesPerPage))); blocks.reserve((count / MessagesPerPage) + 1);
for (int i = 0; i < count;) { for (int i = 0; i < count;) {
HistoryBlock *block = pushBackNewBlock(); t_assert(_otherList.at(i)->detached());
addItemToBlock(_otherList.at(i));
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());
} }
} }
@ -1550,16 +1535,10 @@ void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) {
} }
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) { HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
t_assert(adding != nullptr); t_assert(!isBuildingFrontBlock());
t_assert(adding->detached()); addItemToBlock(adding);
HistoryBlock *block = blocks.isEmpty() ? pushBackNewBlock() : blocks.back();
adding->attachToBlock(block, block->items.size());
block->items.push_back(adding);
adding->previousItemChanged();
setLastMessage(adding); setLastMessage(adding);
if (newMsg) { if (newMsg) {
newItemAdded(adding); 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()); item->attachToBlock(block, block->items.size());
block->items.push_back(item); block->items.push_back(item);
item->previousItemChanged(); item->previousItemChanged();
return item;
}
HistoryItem *History::addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block) { if (isBuildingFrontBlock() && _frontBlock->expectedItemsCount > 0) {
if (prev && prev->type() == HistoryItemGroup) { --_frontBlock->expectedItemsCount;
static_cast<HistoryGroup*>(prev)->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v);
return prev;
} }
return addItemToBlock(HistoryGroup::create(this, group, prev ? prev->date : date(group.vdate)), block);
} }
void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPMessageGroup> *collapsed) { void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPMessageGroup> *collapsed) {
@ -1700,10 +1710,8 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
const MTPMessageGroup *groupsBegin = (isChannel() && collapsed) ? collapsed->constData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; const MTPMessageGroup *groupsBegin = (isChannel() && collapsed) ? collapsed->constData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0;
HistoryItem *prev = nullptr; startBuildingFrontBlock(slice.size() + (collapsed ? collapsed->size() : 0));
HistoryBlock *block = pushFrontNewBlock();
block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0));
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i; --i;
HistoryItem *adding = createItem(*i, false, true); HistoryItem *adding = createItem(*i, false, true);
@ -1714,23 +1722,21 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
const MTPDmessageGroup &group(groupsIt->c_messageGroup()); const MTPDmessageGroup &group(groupsIt->c_messageGroup());
if (group.vmin_id.v >= adding->id) break; 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) { for (; groupsIt != groupsEnd; ++groupsIt) {
if (groupsIt->type() != mtpc_messageGroup) continue; if (groupsIt->type() != mtpc_messageGroup) continue;
const MTPDmessageGroup &group(groupsIt->c_messageGroup()); const MTPDmessageGroup &group(groupsIt->c_messageGroup());
prev = addMessageGroupAfterPrevToBlock(group, prev, block); addMessageGroup(group);
} }
if (block->items.isEmpty()) { HistoryBlock *block = finishBuildingFrontBlock();
blocks.pop_front(); if (!block) {
delete block; // If no items were added it means we've loaded everything old.
block = nullptr;
oldLoaded = true; oldLoaded = true;
} else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants } else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors / lastParticipants
bool channel = isChannel(); bool channel = isChannel();
@ -1808,28 +1814,6 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
} }
} }
// some checks if there was some message history already
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<HistoryGroup*>(first)->uniteWith(static_cast<HistoryGroup*>(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()) { if (isChannel()) {
asChannelHistory()->checkJoinedMessage(); asChannelHistory()->checkJoinedMessage();
asChannelHistory()->checkMaxReadMessageDate(); asChannelHistory()->checkMaxReadMessageDate();
@ -1845,14 +1829,11 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
if (!lastMsg) setLastMessage(lastImportantMessage()); if (!lastMsg) setLastMessage(lastImportantMessage());
} }
t_assert(!isBuildingFrontBlock());
if (!slice.isEmpty() || (isChannel() && collapsed && !collapsed->isEmpty())) { if (!slice.isEmpty() || (isChannel() && collapsed && !collapsed->isEmpty())) {
const MTPMessageGroup *groupsBegin = (isChannel() && collapsed) ? collapsed->constData() : 0, *groupsIt = groupsBegin, *groupsEnd = (isChannel() && collapsed) ? (groupsBegin + collapsed->size()) : 0; 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(); bool atLeastOneAdded = false;
HistoryBlock *block = pushBackNewBlock();
block->items.reserve(slice.size() + (collapsed ? collapsed->size() : 0));
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) { for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i; --i;
HistoryItem *adding = createItem(*i, false, true); HistoryItem *adding = createItem(*i, false, true);
@ -1863,25 +1844,22 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice, const QVector<MTPM
const MTPDmessageGroup &group(groupsIt->c_messageGroup()); const MTPDmessageGroup &group(groupsIt->c_messageGroup());
if (group.vmin_id.v >= adding->id) break; 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) { for (; groupsIt != groupsEnd; ++groupsIt) {
if (groupsIt->type() != mtpc_messageGroup) continue; if (groupsIt->type() != mtpc_messageGroup) continue;
const MTPDmessageGroup &group(groupsIt->c_messageGroup()); const MTPDmessageGroup &group(groupsIt->c_messageGroup());
prev = addMessageGroupAfterPrevToBlock(group, prev, block); addMessageGroup(group);
} }
if (block->items.isEmpty()) { if (!atLeastOneAdded) {
newLoaded = true; newLoaded = true;
setLastMessage(lastImportantMessage()); setLastMessage(lastImportantMessage());
blocks.pop_back();
delete block;
block = nullptr;
} }
} }
@ -2181,21 +2159,76 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
return newItem; return newItem;
} }
HistoryBlock *History::pushBackNewBlock() { template <typename CreateGroup, typename UniteGroup>
HistoryBlock *result = new HistoryBlock(this); void History::addMessageGroup(CreateGroup create, UniteGroup unite) {
result->setIndexInHistory(blocks.size()); HistoryItem *previous = nullptr;
blocks.push_back(result); if (isBuildingFrontBlock()) {
return result; 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<HistoryGroup*>(previous));
return;
}
HistoryGroup *result = create(previous);
if (isBuildingFrontBlock()) {
addItemToBuildingFrontBlock(result);
} else {
addItemToBackBlock(result);
}
} }
HistoryBlock *History::pushFrontNewBlock() { void History::addMessageGroup(const MTPDmessageGroup &group) {
HistoryBlock *result = new HistoryBlock(this); addMessageGroup([&group, this](HistoryItem *previous) -> HistoryItem* { // create(..)
result->setIndexInHistory(0); return HistoryGroup::create(this, group, previous ? previous->date : date(group.vdate));
blocks.push_front(result); }, [&group](HistoryGroup *existing) { // unite(..)
for (int i = 1, l = blocks.size(); i < l; ++i) { existing->uniteWith(group.vmin_id.v, group.vmax_id.v, group.vcount.v);
blocks.at(i)->setIndexInHistory(i); });
}
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<HistoryGroup*>(first)->uniteWith(static_cast<HistoryGroup*>(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() { void History::clearNotifications() {
@ -2572,6 +2605,10 @@ void History::changeMsgId(MsgId oldId, MsgId newId) {
void History::removeBlock(HistoryBlock *block) { void History::removeBlock(HistoryBlock *block) {
t_assert(block->items.isEmpty()); t_assert(block->items.isEmpty());
if (_frontBlock && block == _frontBlock->block) {
_frontBlock->block = nullptr;
}
int index = block->indexInHistory(); int index = block->indexInHistory();
blocks.removeAt(index); blocks.removeAt(index);
for (int i = index, l = blocks.size(); i < l; ++i) { for (int i = index, l = blocks.size(); i < l; ++i) {

View file

@ -470,19 +470,64 @@ protected:
void clearOnDestroy(); void clearOnDestroy();
HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); 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 <typename CreateGroup, typename UniteGroup>
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: private:
enum Flag { enum class Flag {
f_has_pending_resized_items = (1 << 0), f_has_pending_resized_items = (1 << 0),
f_pending_resize = (1 << 1), f_pending_resize = (1 << 1),
}; };
Q_DECLARE_FLAGS(Flags, Flag); Q_DECLARE_FLAGS(Flags, Flag);
Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, Flags::enum_type f2) Q_DECL_NOTHROW { Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, Flags::enum_type f2) noexcept {
return QFlags<Flags::enum_type>(f1) | f2; return QFlags<Flags::enum_type>(f1) | f2;
} }
Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, QFlags<Flags::enum_type> f2) Q_DECL_NOTHROW { Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, QFlags<Flags::enum_type> f2) noexcept {
return f2 | f1; return f2 | f1;
} }
Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator~(Flags::enum_type f) noexcept {
return ~QFlags<Flags::enum_type>(f);
}
Flags _flags; Flags _flags;
ChatListLinksMap _chatListLinks; ChatListLinksMap _chatListLinks;
@ -497,28 +542,18 @@ private:
MediaOverviewIds overviewIds[OverviewCount]; MediaOverviewIds overviewIds[OverviewCount];
int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded
friend class HistoryBlock; // A pointer to the block that is currently being built.
friend class ChannelHistory; // 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<BuildingBlock> _buildingFrontBlock;
// this method just removes a block from the blocks list // Creates if necessary a new block for adding item.
// when the last item from this block was detached and // Depending on isBuildingFrontBlock() gets front or back block.
// calls the required previousItemChanged() HistoryBlock *prepareBlockForAddingItem();
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();
}; };