diff --git a/chatterino.pro b/chatterino.pro index 328a10b1c..2a3bfd6bf 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -99,7 +99,8 @@ SOURCES += \ src/twitch/twitchchannel.cpp \ src/widgets/rippleeffectlabel.cpp \ src/widgets/rippleeffectbutton.cpp \ - src/messages/messagecolor.cpp + src/messages/messagecolor.cpp \ + src/messages/imageloader.cpp HEADERS += \ src/asyncexec.hpp \ @@ -165,7 +166,8 @@ HEADERS += \ src/widgets/emotepopup.hpp \ src/messages/messagecolor.hpp \ src/util/nativeeventhelper.hpp \ - src/debug/log.hpp + src/debug/log.hpp \ + src/messages/imageloader.hpp PRECOMPILED_HEADER = diff --git a/src/messages/imageloader.cpp b/src/messages/imageloader.cpp new file mode 100644 index 000000000..22ce1c5b0 --- /dev/null +++ b/src/messages/imageloader.cpp @@ -0,0 +1,116 @@ +#include "messages/imageloader.hpp" +#include "emotemanager.hpp" +#include "messages/lazyloadedimage.hpp" +#include "windowmanager.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace chatterino { +namespace messages { + +ImageLoader::ImageLoader() +{ + this->worker = std::thread(&ImageLoader::run, this); +} + +ImageLoader::~ImageLoader() +{ + { + std::lock_guard lk(this->workerM); + this->exit = true; + this->ready = true; + } + this->cv.notify_all(); + qDebug() << "notified waiting"; + this->worker.join(); + qDebug() << "destruct"; +} + +void ImageLoader::run() +{ + this->eventLoop.reset(new QEventLoop()); + while (true) { + std::unique_lock lk(this->workerM); + if (!this->ready) { + this->cv.wait(lk, [this]() { return this->ready; }); + } + if (this->exit) { + return; + } + std::vector current(std::move(this->toProcess)); + // (hemirt) + // after move toProcess is guaranteed to be empty() + // and we start processing while more items can be added into toProcess + this->ready = false; + lk.unlock(); + for (auto &lli : current) { + if (this->exit) + return; + QNetworkRequest request; + request.setUrl(QUrl(lli->url)); + QNetworkAccessManager NaM; + QNetworkReply *reply = NaM.get(request); + QObject::connect(reply, &QNetworkReply::finished, this->eventLoop.get(), + &QEventLoop::quit); + qDebug() << "eve1"; + this->eventLoop->exec(); // Wait until response is read. + qDebug() << "eve2"; + qDebug() << "Received emote " << lli->url; + QByteArray array = reply->readAll(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + QImage image; + QImageReader reader(&buffer); + + bool first = true; + + for (int index = 0; index < reader.imageCount(); ++index) { + if (reader.read(&image)) { + auto pixmap = new QPixmap(QPixmap::fromImage(image)); + + if (first) { + first = false; + lli->currentPixmap = pixmap; + } + + LazyLoadedImage::FrameData data; + data.duration = std::max(20, reader.nextImageDelay()); + data.image = pixmap; + + lli->allFrames.push_back(data); + } + } + + if (lli->allFrames.size() > 1) { + lli->animated = true; + } + + lli->emoteManager.incGeneration(); + lli->windowManager.layoutVisibleChatWidgets(); + + delete reply; + } + } +} + +void ImageLoader::push_back(chatterino::messages::LazyLoadedImage *lli) +{ + { + std::lock_guard lk(this->workerM); + this->toProcess.push_back(lli); + this->ready = true; + } + this->cv.notify_all(); +} + +} // namespace messages +} // namespace chatterino diff --git a/src/messages/imageloader.hpp b/src/messages/imageloader.hpp new file mode 100644 index 000000000..83b776b7b --- /dev/null +++ b/src/messages/imageloader.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace chatterino { +namespace messages { + +class LazyLoadedImage; + +class ImageLoader +{ +public: + ImageLoader(); + ~ImageLoader(); + void push_back(chatterino::messages::LazyLoadedImage *lli); + +private: + void run(); + std::thread worker; + std::mutex workerM; + std::vector toProcess; + bool ready = false; + std::condition_variable cv; + bool exit = false; + std::unique_ptr eventLoop; +}; + +} // namespace messages +} // namespace chatterino diff --git a/src/messages/lazyloadedimage.cpp b/src/messages/lazyloadedimage.cpp index 1d84d0263..b01b01708 100644 --- a/src/messages/lazyloadedimage.cpp +++ b/src/messages/lazyloadedimage.cpp @@ -2,22 +2,25 @@ #include "asyncexec.hpp" #include "emotemanager.hpp" #include "ircmanager.hpp" +#include "messages/imageloader.hpp" #include "util/urlfetch.hpp" #include "windowmanager.hpp" -#include #include #include #include #include #include #include +#include #include namespace chatterino { namespace messages { +ImageLoader LazyLoadedImage::imageLoader; + LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_windowManager, const QString &url, qreal scale, const QString &name, const QString &tooltip, const QMargins &margin, bool isHat) @@ -51,70 +54,30 @@ LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_wi void LazyLoadedImage::loadImage() { - std::thread([=] () { - QNetworkRequest request; - request.setUrl(QUrl(this->url)); - QNetworkAccessManager NaM; - QEventLoop eventLoop; - QNetworkReply *reply = NaM.get(request); - QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); // Wait until response is read. + LazyLoadedImage::imageLoader.push_back(this); - qDebug() << "Received emote " << this->url; - QByteArray array = reply->readAll(); - QBuffer buffer(&array); - buffer.open(QIODevice::ReadOnly); - - QImage image; - QImageReader reader(&buffer); - - bool first = true; - - for (int index = 0; index < reader.imageCount(); ++index) { - if (reader.read(&image)) { - auto pixmap = new QPixmap(QPixmap::fromImage(image)); - - if (first) { - first = false; - this->currentPixmap = pixmap; - } - - FrameData data; - data.duration = std::max(20, reader.nextImageDelay()); - data.image = pixmap; - - this->allFrames.push_back(data); - } - } - - if (this->allFrames.size() > 1) { - this->animated = true; - } - - this->emoteManager.incGeneration(); - this->windowManager.layoutVisibleChatWidgets(); - - delete reply; - }).detach(); - this->emoteManager.getGifUpdateSignal().connect([=] () { this->gifUpdateTimout(); }); // For some reason when Boost signal is in thread scope and thread deletes the signal doesn't work, so this is the fix. + this->emoteManager.getGifUpdateSignal().connect([=]() { + this->gifUpdateTimout(); + }); // For some reason when Boost signal is in thread scope and thread deletes the signal + // doesn't work, so this is the fix. } void LazyLoadedImage::gifUpdateTimout() { - if (animated) { - this->currentFrameOffset += GIF_FRAME_LENGTH; + if (animated) { + this->currentFrameOffset += GIF_FRAME_LENGTH; - while (true) { - if (this->currentFrameOffset > this->allFrames.at(this->currentFrame).duration) { - this->currentFrameOffset -= this->allFrames.at(this->currentFrame).duration; - this->currentFrame = (this->currentFrame + 1) % this->allFrames.size(); - } else { - break; - } - } + while (true) { + if (this->currentFrameOffset > this->allFrames.at(this->currentFrame).duration) { + this->currentFrameOffset -= this->allFrames.at(this->currentFrame).duration; + this->currentFrame = (this->currentFrame + 1) % this->allFrames.size(); + } else { + break; + } + } - this->currentPixmap = this->allFrames[this->currentFrame].image; - } + this->currentPixmap = this->allFrames[this->currentFrame].image; + } } const QPixmap *LazyLoadedImage::getPixmap() diff --git a/src/messages/lazyloadedimage.hpp b/src/messages/lazyloadedimage.hpp index c6c06125e..9a0818205 100644 --- a/src/messages/lazyloadedimage.hpp +++ b/src/messages/lazyloadedimage.hpp @@ -10,6 +10,8 @@ class WindowManager; namespace messages { +class ImageLoader; + class LazyLoadedImage : QObject { public: @@ -64,6 +66,9 @@ private: void loadImage(); void gifUpdateTimout(); + + static ImageLoader imageLoader; + friend class ImageLoader; }; } // namespace messages