fix: take indices to messages as a hint

This commit is contained in:
Nerixyz 2024-10-28 22:08:59 +01:00
parent 90211cca55
commit e4d23fae6c
No known key found for this signature in database
GPG key ID: 946BA188C5609CCC
6 changed files with 244 additions and 27 deletions

View file

@ -253,21 +253,33 @@ void Channel::fillInMissingMessages(const std::vector<MessagePtr> &messages)
} }
} }
void Channel::replaceMessage(MessagePtr message, MessagePtr replacement) void Channel::replaceMessage(const MessagePtr &message,
const MessagePtr &replacement)
{ {
int index = this->messages_.replaceItem(message, replacement); int index = this->messages_.replaceItem(message, replacement);
if (index >= 0) if (index >= 0)
{ {
this->messageReplaced.invoke((size_t)index, replacement); this->messageReplaced.invoke((size_t)index, message, replacement);
} }
} }
void Channel::replaceMessage(size_t index, MessagePtr replacement) void Channel::replaceMessage(size_t index, const MessagePtr &replacement)
{ {
if (this->messages_.replaceItem(index, replacement)) MessagePtr prev;
if (this->messages_.replaceItem(index, replacement, &prev))
{ {
this->messageReplaced.invoke(index, replacement); this->messageReplaced.invoke(index, prev, replacement);
}
}
void Channel::replaceMessage(size_t hint, const MessagePtr &message,
const MessagePtr &replacement)
{
auto index = this->messages_.replaceItem(hint, message, replacement);
if (index >= 0)
{
this->messageReplaced.invoke(hint, message, replacement);
} }
} }

View file

@ -66,7 +66,9 @@ public:
pajlada::Signals::Signal<MessagePtr &, std::optional<MessageFlags>> pajlada::Signals::Signal<MessagePtr &, std::optional<MessageFlags>>
messageAppended; messageAppended;
pajlada::Signals::Signal<std::vector<MessagePtr> &> messagesAddedAtStart; pajlada::Signals::Signal<std::vector<MessagePtr> &> messagesAddedAtStart;
pajlada::Signals::Signal<size_t, MessagePtr &> messageReplaced; /// (index, prev-message, replacement)
pajlada::Signals::Signal<size_t, const MessagePtr &, const MessagePtr &>
messageReplaced;
/// Invoked when some number of messages were filled in using time received /// Invoked when some number of messages were filled in using time received
pajlada::Signals::Signal<const std::vector<MessagePtr> &> filledInMessages; pajlada::Signals::Signal<const std::vector<MessagePtr> &> filledInMessages;
pajlada::Signals::NoArgSignal destroyed; pajlada::Signals::NoArgSignal destroyed;
@ -96,8 +98,11 @@ public:
void addOrReplaceTimeout(MessagePtr message); void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages(); void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement); void replaceMessage(const MessagePtr &message,
void replaceMessage(size_t index, MessagePtr replacement); const MessagePtr &replacement);
void replaceMessage(size_t index, const MessagePtr &replacement);
void replaceMessage(size_t hint, const MessagePtr &message,
const MessagePtr &replacement);
void deleteMessage(QString messageID); void deleteMessage(QString messageID);
/// Removes all messages from this channel and invokes #messagesCleared /// Removes all messages from this channel and invokes #messagesCleared

View file

@ -8,6 +8,7 @@
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <shared_mutex> #include <shared_mutex>
#include <utility>
#include <vector> #include <vector>
namespace chatterino { namespace chatterino {
@ -212,9 +213,10 @@ public:
* *
* @param[in] index the index of the item to replace * @param[in] index the index of the item to replace
* @param[in] replacement the item to put in place of the item at index * @param[in] replacement the item to put in place of the item at index
* @param[out] prev (optional) the item located at @a index before replacing
* @return true if a replacement took place * @return true if a replacement took place
*/ */
bool replaceItem(size_t index, const T &replacement) bool replaceItem(size_t index, const T &replacement, T *prev = nullptr)
{ {
std::unique_lock lock(this->mutex_); std::unique_lock lock(this->mutex_);
@ -223,10 +225,46 @@ public:
return false; return false;
} }
this->buffer_[index] = replacement; if (prev)
{
*prev = std::exchange(this->buffer_[index], replacement);
}
else
{
this->buffer_[index] = replacement;
}
return true; return true;
} }
/**
* @brief Replace the needle with the given item
*
* @param hint A hint on where the needle _might_ be
* @param[in] needle the item to search for
* @param[in] replacement the item to replace needle with
* @return the index of the replaced item, or -1 if no replacement took place
*/
int replaceItem(size_t hint, const T &needle, const T &replacement)
{
std::unique_lock lock(this->mutex_);
if (hint < this->buffer_.size() && this->buffer_[hint] == needle)
{
this->buffer_[hint] = replacement;
return static_cast<int>(hint);
}
for (size_t i = 0; i < this->buffer_.size(); ++i)
{
if (this->buffer_[i] == needle)
{
this->buffer_[i] = replacement;
return static_cast<int>(i);
}
}
return -1;
}
/** /**
* @brief Inserts the given item before another item * @brief Inserts the given item before another item
* *
@ -315,6 +353,32 @@ public:
return std::nullopt; return std::nullopt;
} }
/**
* @brief Find an item with a hint
*
* @param hint A hint on where the needle _might_ be
* @param needle The item to search for
* @return the item and its index or none if it's not found
*/
std::optional<std::pair<size_t, T>> find(size_t hint, auto &&predicate)
{
std::unique_lock lock(this->mutex_);
if (hint < this->buffer_.size() && predicate(this->buffer_[hint]))
{
return std::pair{hint, this->buffer_[hint]};
};
for (size_t i = 0; i < this->buffer_.size(); i++)
{
if (predicate(this->buffer_[i]))
{
return std::pair{i, this->buffer_[i]};
}
}
return std::nullopt;
}
/** /**
* @brief Returns the first item matching a predicate, checking in reverse * @brief Returns the first item matching a predicate, checking in reverse
* *

View file

@ -967,10 +967,10 @@ void ChannelView::setChannel(const ChannelPtr &underlyingChannel)
this->channelConnections_.managedConnect( this->channelConnections_.managedConnect(
underlyingChannel->messageReplaced, underlyingChannel->messageReplaced,
[this](auto index, const auto &replacement) { [this](auto index, const auto &prev, const auto &replacement) {
if (this->shouldIncludeMessage(replacement)) if (this->shouldIncludeMessage(replacement))
{ {
this->channel_->replaceMessage(index, replacement); this->channel_->replaceMessage(index, prev, replacement);
} }
}); });
@ -1051,8 +1051,9 @@ void ChannelView::setChannel(const ChannelPtr &underlyingChannel)
// on message replaced // on message replaced
this->channelConnections_.managedConnect( this->channelConnections_.managedConnect(
this->channel_->messageReplaced, this->channel_->messageReplaced,
[this](size_t index, MessagePtr replacement) { [this](size_t index, const MessagePtr &prev,
this->messageReplaced(index, replacement); const MessagePtr &replacement) {
this->messageReplaced(index, prev, replacement);
}); });
// on messages filled in // on messages filled in
@ -1258,19 +1259,21 @@ void ChannelView::messageAddedAtStart(std::vector<MessagePtr> &messages)
this->queueLayout(); this->queueLayout();
} }
void ChannelView::messageReplaced(size_t index, MessagePtr &replacement) void ChannelView::messageReplaced(size_t hint, const MessagePtr &prev,
const MessagePtr &replacement)
{ {
auto oMessage = this->messages_.get(index); auto optItem = this->messages_.find(hint, [&](const auto &it) {
if (!oMessage) return it->getMessagePtr() == prev;
});
if (!optItem)
{ {
return; return;
} }
const auto &[index, oldItem] = *optItem;
auto message = *oMessage;
auto newItem = std::make_shared<MessageLayout>(replacement); auto newItem = std::make_shared<MessageLayout>(replacement);
if (message->flags.has(MessageLayoutFlag::AlternateBackground)) if (oldItem->flags.has(MessageLayoutFlag::AlternateBackground))
{ {
newItem->flags.set(MessageLayoutFlag::AlternateBackground); newItem->flags.set(MessageLayoutFlag::AlternateBackground);
} }
@ -1278,7 +1281,7 @@ void ChannelView::messageReplaced(size_t index, MessagePtr &replacement)
this->scrollBar_->replaceHighlight(index, this->scrollBar_->replaceHighlight(index,
replacement->getScrollBarHighlight()); replacement->getScrollBarHighlight());
this->messages_.replaceItem(message, newItem); this->messages_.replaceItem(index, newItem);
this->queueLayout(); this->queueLayout();
} }

View file

@ -268,7 +268,8 @@ private:
std::optional<MessageFlags> overridingFlags); std::optional<MessageFlags> overridingFlags);
void messageAddedAtStart(std::vector<MessagePtr> &messages); void messageAddedAtStart(std::vector<MessagePtr> &messages);
void messageRemoveFromStart(MessagePtr &message); void messageRemoveFromStart(MessagePtr &message);
void messageReplaced(size_t index, MessagePtr &replacement); void messageReplaced(size_t hint, const MessagePtr &prev,
const MessagePtr &replacement);
void messagesUpdated(); void messagesUpdated();
void performLayout(bool causedByScrollbar = false, void performLayout(bool causedByScrollbar = false,

View file

@ -114,20 +114,152 @@ TEST(LimitedQueue, PushFront)
TEST(LimitedQueue, ReplaceItem) TEST(LimitedQueue, ReplaceItem)
{ {
LimitedQueue<int> queue(5); LimitedQueue<int> queue(10);
queue.pushBack(1); queue.pushBack(1);
queue.pushBack(2); queue.pushBack(2);
queue.pushBack(3); queue.pushBack(3);
queue.pushBack(4);
queue.pushBack(5);
queue.pushBack(6);
int idex = queue.replaceItem(2, 10); int idex = queue.replaceItem(2, 10);
EXPECT_EQ(idex, 1); EXPECT_EQ(idex, 1);
idex = queue.replaceItem(5, 11); idex = queue.replaceItem(7, 11);
EXPECT_EQ(idex, -1); EXPECT_EQ(idex, -1);
bool res = queue.replaceItem(std::size_t(0), 9); int prev = -1;
bool res = queue.replaceItem(std::size_t(0), 9, &prev);
EXPECT_TRUE(res); EXPECT_TRUE(res);
res = queue.replaceItem(std::size_t(5), 4); EXPECT_EQ(prev, 1);
res = queue.replaceItem(std::size_t(6), 4);
EXPECT_FALSE(res); EXPECT_FALSE(res);
SNAPSHOT_EQUALS(queue.getSnapshot(), {9, 10, 3}, "first snapshot"); // correct hint
EXPECT_EQ(queue.replaceItem(3ULL, 4, 11), 3);
// incorrect hints
EXPECT_EQ(queue.replaceItem(5ULL, 11, 12), 3);
EXPECT_EQ(queue.replaceItem(0ULL, 12, 13), 3);
// oob hint
EXPECT_EQ(queue.replaceItem(42ULL, 13, 14), 3);
// bad needle
EXPECT_EQ(queue.replaceItem(0ULL, 15, 16), -1);
SNAPSHOT_EQUALS(queue.getSnapshot(), {9, 10, 3, 14, 5, 6},
"first snapshot");
}
TEST(LimitedQueue, Find)
{
LimitedQueue<int> queue(10);
queue.pushBack(1);
queue.pushBack(2);
queue.pushBack(3);
queue.pushBack(4);
queue.pushBack(5);
queue.pushBack(6);
// without hint
EXPECT_FALSE(queue
.find([](int i) {
return i == 0;
})
.has_value());
EXPECT_EQ(queue
.find([](int i) {
return i == 1;
})
.value(),
1);
EXPECT_EQ(queue
.find([](int i) {
return i == 2;
})
.value(),
2);
EXPECT_EQ(queue
.find([](int i) {
return i == 6;
})
.value(),
6);
EXPECT_FALSE(queue
.find([](int i) {
return i == 7;
})
.has_value());
EXPECT_FALSE(queue
.find([](int i) {
return i > 6;
})
.has_value());
EXPECT_FALSE(queue
.find([](int i) {
return i <= 0;
})
.has_value());
// with hint
EXPECT_FALSE(queue
.find(0,
[](int i) {
return i == 0;
})
.has_value());
// correct hint
EXPECT_EQ(queue
.find(0,
[](int i) {
return i == 1;
})
.value(),
(std::pair{0ULL, 1}));
EXPECT_EQ(queue
.find(1,
[](int i) {
return i == 2;
})
.value(),
(std::pair{1ULL, 2}));
// incorrect hint
EXPECT_EQ(queue
.find(1,
[](int i) {
return i == 1;
})
.value(),
(std::pair{0ULL, 1}));
EXPECT_EQ(queue
.find(5,
[](int i) {
return i == 6;
})
.value(),
(std::pair{5ULL, 6}));
// oob hint
EXPECT_EQ(queue
.find(6,
[](int i) {
return i == 3;
})
.value(),
(std::pair{2ULL, 3}));
// non-existent items
EXPECT_FALSE(queue
.find(42,
[](int i) {
return i == 7;
})
.has_value());
EXPECT_FALSE(queue
.find(0,
[](int i) {
return i > 6;
})
.has_value());
EXPECT_FALSE(queue
.find(0,
[](int i) {
return i <= 0;
})
.has_value());
} }