Skip to content

Commit

Permalink
Ensure live status requests are always batched (#4713)
Browse files Browse the repository at this point in the history
  • Loading branch information
pajlada authored Jul 2, 2023
1 parent f915eab commit 7652707
Show file tree
Hide file tree
Showing 20 changed files with 582 additions and 282 deletions.
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ CheckOptions:
value: CamelCase
- key: readability-identifier-naming.GlobalConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.GlobalVariableCase
value: UPPER_CASE
- key: readability-identifier-naming.VariableCase
value: camelBack
- key: readability-implicit-bool-conversion.AllowPointerConditions
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Minor: Added option to subscribe to and unsubscribe from reply threads. (#4680)
- Minor: Added a message for when Chatterino joins a channel (#4616)
- Minor: Add pin action to usercards and reply threads. (#4692)
- Minor: Stream status requests are now batched. (#4713)
- Bugfix: Fixed generation of crashdumps by the browser-extension process when the browser was closed. (#4667)
- Bugfix: Fix spacing issue with mentions inside RTL text. (#4677)
- Bugfix: Fixed a crash when opening and closing a reply thread and switching the user. (#4675)
Expand Down
5 changes: 5 additions & 0 deletions mocks/include/mocks/EmptyApplication.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class EmptyApplication : public IApplication
{
return nullptr;
}

ITwitchLiveController *getTwitchLiveController() override
{
return nullptr;
}
};

} // namespace chatterino::mock
6 changes: 6 additions & 0 deletions mocks/include/mocks/Helix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ class Helix : public IHelix
std::function<void()> finallyCallback),
(override));

MOCK_METHOD(void, fetchChannels,
(QStringList userIDs,
ResultCallback<std::vector<HelixChannel>> successCallback,
HelixFailureCallback failureCallback),
(override));

MOCK_METHOD(void, getChannel,
(QString broadcasterId,
ResultCallback<HelixChannel> successCallback,
Expand Down
7 changes: 7 additions & 0 deletions src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# include "controllers/plugins/PluginController.hpp"
#endif
#include "controllers/sound/SoundController.hpp"
#include "controllers/twitch/LiveController.hpp"
#include "controllers/userdata/UserDataController.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "messages/Message.hpp"
Expand Down Expand Up @@ -88,6 +89,7 @@ Application::Application(Settings &_settings, Paths &_paths)
, seventvBadges(&this->emplace<SeventvBadges>())
, userData(&this->emplace<UserDataController>())
, sound(&this->emplace<SoundController>())
, twitchLiveController(&this->emplace<TwitchLiveController>())
#ifdef CHATTERINO_HAVE_PLUGINS
, plugins(&this->emplace<PluginController>())
#endif
Expand Down Expand Up @@ -245,6 +247,11 @@ IUserDataController *Application::getUserData()
return this->userData;
}

ITwitchLiveController *Application::getTwitchLiveController()
{
return this->twitchLiveController;
}

ITwitchIrcServer *Application::getTwitch()
{
return this->twitch;
Expand Down
8 changes: 8 additions & 0 deletions src/Application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class HotkeyController;
class IUserDataController;
class UserDataController;
class SoundController;
class ITwitchLiveController;
class TwitchLiveController;
#ifdef CHATTERINO_HAVE_PLUGINS
class PluginController;
#endif
Expand Down Expand Up @@ -62,6 +64,7 @@ class IApplication
virtual ChatterinoBadges *getChatterinoBadges() = 0;
virtual FfzBadges *getFfzBadges() = 0;
virtual IUserDataController *getUserData() = 0;
virtual ITwitchLiveController *getTwitchLiveController() = 0;
};

class Application : public IApplication
Expand Down Expand Up @@ -101,6 +104,10 @@ class Application : public IApplication
UserDataController *const userData{};
SoundController *const sound{};

private:
TwitchLiveController *const twitchLiveController{};

public:
#ifdef CHATTERINO_HAVE_PLUGINS
PluginController *const plugins{};
#endif
Expand Down Expand Up @@ -154,6 +161,7 @@ class Application : public IApplication
return this->ffzBadges;
}
IUserDataController *getUserData() override;
ITwitchLiveController *getTwitchLiveController() override;

pajlada::Signals::NoArgSignal streamerModeChanged;

Expand Down
9 changes: 6 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,16 @@ set(SOURCE_FILES
controllers/plugins/LuaUtilities.cpp
controllers/plugins/LuaUtilities.hpp

controllers/sound/SoundController.cpp
controllers/sound/SoundController.hpp

controllers/twitch/LiveController.cpp
controllers/twitch/LiveController.hpp

controllers/userdata/UserDataController.cpp
controllers/userdata/UserDataController.hpp
controllers/userdata/UserData.hpp

controllers/sound/SoundController.cpp
controllers/sound/SoundController.hpp

debug/Benchmark.cpp
debug/Benchmark.hpp

Expand Down
2 changes: 2 additions & 0 deletions src/common/QLogging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Q_LOGGING_CATEGORY(chatterinoStreamlink, "chatterino.streamlink", logThreshold);
Q_LOGGING_CATEGORY(chatterinoTheme, "chatterino.theme", logThreshold);
Q_LOGGING_CATEGORY(chatterinoTokenizer, "chatterino.tokenizer", logThreshold);
Q_LOGGING_CATEGORY(chatterinoTwitch, "chatterino.twitch", logThreshold);
Q_LOGGING_CATEGORY(chatterinoTwitchLiveController,
"chatterino.twitch.livecontroller", logThreshold);
Q_LOGGING_CATEGORY(chatterinoUpdate, "chatterino.update", logThreshold);
Q_LOGGING_CATEGORY(chatterinoWebsocket, "chatterino.websocket", logThreshold);
Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold);
Expand Down
1 change: 1 addition & 0 deletions src/common/QLogging.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamlink);
Q_DECLARE_LOGGING_CATEGORY(chatterinoTheme);
Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer);
Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitch);
Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitchLiveController);
Q_DECLARE_LOGGING_CATEGORY(chatterinoUpdate);
Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket);
Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget);
Expand Down
190 changes: 190 additions & 0 deletions src/controllers/twitch/LiveController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#include "controllers/twitch/LiveController.hpp"

#include "common/QLogging.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "util/Helpers.hpp"

#include <QDebug>

namespace {

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
const auto &LOG = chatterinoTwitchLiveController;

} // namespace

namespace chatterino {

TwitchLiveController::TwitchLiveController()
{
QObject::connect(&this->refreshTimer, &QTimer::timeout, [this] {
this->request();
});
this->refreshTimer.start(TwitchLiveController::REFRESH_INTERVAL);

QObject::connect(&this->immediateRequestTimer, &QTimer::timeout, [this] {
QStringList channelIDs;

{
std::unique_lock immediateRequestsLock(
this->immediateRequestsMutex);
for (const auto &channelID : this->immediateRequests)
{
channelIDs.append(channelID);
}
this->immediateRequests.clear();
}

if (channelIDs.isEmpty())
{
return;
}

this->request(channelIDs);
});
this->immediateRequestTimer.start(
TwitchLiveController::IMMEDIATE_REQUEST_INTERVAL);
}

void TwitchLiveController::add(const std::shared_ptr<TwitchChannel> &newChannel)
{
assert(newChannel != nullptr);

const auto channelID = newChannel->roomId();
assert(!channelID.isEmpty());

{
std::unique_lock lock(this->channelsMutex);
this->channels[channelID] = newChannel;
}

{
std::unique_lock immediateRequestsLock(this->immediateRequestsMutex);
this->immediateRequests.emplace(channelID);
}
}

void TwitchLiveController::request(std::optional<QStringList> optChannelIDs)
{
QStringList channelIDs;

if (optChannelIDs)
{
channelIDs = *optChannelIDs;
}
else
{
std::shared_lock lock(this->channelsMutex);

for (const auto &channelList : this->channels)
{
channelIDs.append(channelList.first);
}
}

if (channelIDs.isEmpty())
{
return;
}

auto batches =
splitListIntoBatches(channelIDs, TwitchLiveController::BATCH_SIZE);

qCDebug(LOG) << "Make" << batches.size() << "requests";

for (const auto &batch : batches)
{
// TODO: Explore making this concurrent
getHelix()->fetchStreams(
batch, {},
[this, batch{batch}](const auto &streams) {
std::unordered_map<QString, std::optional<HelixStream>> results;

for (const auto &channelID : batch)
{
results[channelID] = std::nullopt;
}

for (const auto &stream : streams)
{
results[stream.userId] = stream;
}

QStringList deadChannels;

{
std::shared_lock lock(this->channelsMutex);
for (const auto &result : results)
{
auto it = this->channels.find(result.first);
if (it != channels.end())
{
if (auto channel = it->second.lock(); channel)
{
channel->updateStreamStatus(result.second);
}
else
{
deadChannels.append(result.first);
}
}
}
}

if (!deadChannels.isEmpty())
{
std::unique_lock lock(this->channelsMutex);
for (const auto &deadChannel : deadChannels)
{
this->channels.erase(deadChannel);
}
}
},
[] {
qCWarning(LOG) << "Failed stream check request";
},
[] {});

// TODO: Explore making this concurrent
getHelix()->fetchChannels(
batch,
[this, batch{batch}](const auto &helixChannels) {
QStringList deadChannels;

{
std::shared_lock lock(this->channelsMutex);
for (const auto &helixChannel : helixChannels)
{
auto it = this->channels.find(helixChannel.userId);
if (it != this->channels.end())
{
if (auto channel = it->second.lock(); channel)
{
channel->updateStreamTitle(helixChannel.title);
channel->updateDisplayName(helixChannel.name);
}
else
{
deadChannels.append(helixChannel.userId);
}
}
}
}

if (!deadChannels.isEmpty())
{
std::unique_lock lock(this->channelsMutex);
for (const auto &deadChannel : deadChannels)
{
this->channels.erase(deadChannel);
}
}
},
[] {
qCWarning(LOG) << "Failed stream check request";
});
}
}

} // namespace chatterino
Loading

0 comments on commit 7652707

Please sign in to comment.