From 9caad1f6c9ce43e0cc284b69a4ac5e797ed56503 Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Thu, 6 Jan 2022 21:27:40 -0500 Subject: [PATCH] Improve chat future performance w/ parallelization --- .../channel/chat/chat_assets_store.dart | 82 +++++++++---------- .../channel/chat/chat_assets_store.g.dart | 40 +++++---- lib/screens/channel/chat/chat_store.dart | 22 +++-- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/lib/screens/channel/chat/chat_assets_store.dart b/lib/screens/channel/chat/chat_assets_store.dart index b08d4fa9..51064b38 100644 --- a/lib/screens/channel/chat/chat_assets_store.dart +++ b/lib/screens/channel/chat/chat_assets_store.dart @@ -4,6 +4,7 @@ import 'package:frosty/api/seventv_api.dart'; import 'package:frosty/api/twitch_api.dart'; import 'package:frosty/models/badges.dart'; import 'package:frosty/models/emotes.dart'; +import 'package:frosty/models/user.dart'; import 'package:mobx/mobx.dart'; part 'chat_assets_store.g.dart'; @@ -23,6 +24,9 @@ abstract class _ChatAssetsStoreBase with Store { @computed List get sevenTvEmotes => _emoteToObject.values.where((emote) => is7TV(emote)).toList(); + /// The map of badges ids to their object representation. + final twitchBadgesToObject = ObservableMap(); + /// The map of emote words to their image or GIF URL. May be used by anyone in the chat. @readonly var _emoteToObject = {}; @@ -31,10 +35,6 @@ abstract class _ChatAssetsStoreBase with Store { @readonly var _userEmoteToObject = {}; - /// The map of badges ids to their object representation. - @readonly - var _twitchBadgesToObject = {}; - /// The map of usernames to their FFZ badges. @readonly var _userToFFZBadges = >{}; @@ -76,49 +76,47 @@ abstract class _ChatAssetsStoreBase with Store { if (channelInfo != null) { // Fetch the global and channel's assets (emotes & badges). // Async awaits are placed in a list so they are performed in parallel. - final assets = [ - ...await FFZ.getEmotesGlobal(), - ...await BTTV.getEmotesGlobal(), - ...await BTTV.getEmotesChannel(id: channelInfo.id), - ...await Twitch.getEmotesGlobal(headers: headers), - ...await Twitch.getEmotesChannel(id: channelInfo.id, headers: headers), - ...await SevenTV.getEmotesGlobal(), - ...await SevenTV.getEmotesChannel(user: channelInfo.login) - ]; - final ffzRoom = await FFZ.getRoomInfo(name: channelInfo.login); - if (ffzRoom != null) { - assets.addAll(ffzRoom.item2); - ffzRoomInfo = ffzRoom.item1; - } - - _emoteToObject = {for (final emote in assets) emote.name: emote}; - - _twitchBadgesToObject = { - ...await Twitch.getBadgesGlobal(), - ...await Twitch.getBadgesChannel(id: channelInfo.id), - }; - - final ffzBadges = await FFZ.getBadges(); - if (ffzBadges != null) _userToFFZBadges = ffzBadges; - - final sevenTVBadges = await SevenTV.getBadges(); - if (sevenTVBadges != null) _userTo7TVBadges = sevenTVBadges; - - final bttvBadges = await BTTV.getBadges(); - if (bttvBadges != null) _userToBTTVBadges = bttvBadges; + await Future.wait([ + getEmotes(channelInfo: channelInfo, headers: headers), + getBadges(channelInfo: channelInfo, headers: headers), + ]); } } @action - Future getUserEmotes({List? emoteSets, required Map headers}) async { - if (emoteSets != null) { - final userEmotes = []; - for (final setId in emoteSets) { - userEmotes.addAll(await Twitch.getEmotesSets(setId: setId, headers: headers)); - } + Future getEmotes({required UserTwitch channelInfo, required Map headers}) async { + final assets = await Future.wait([ + FFZ.getEmotesGlobal(), + BTTV.getEmotesGlobal(), + BTTV.getEmotesChannel(id: channelInfo.id), + Twitch.getEmotesGlobal(headers: headers), + Twitch.getEmotesChannel(id: channelInfo.id, headers: headers), + SevenTV.getEmotesGlobal(), + SevenTV.getEmotesChannel(user: channelInfo.login), + FFZ.getRoomInfo(name: channelInfo.login).then((ffzRoom) { + ffzRoomInfo = ffzRoom?.item1; + return ffzRoom?.item2 ?? []; + }), + ]); - _userEmoteToObject = {for (final emote in userEmotes) emote.name: emote}.asObservable(); - } + final emotes = assets.expand((list) => list); + + _emoteToObject = {for (final emote in emotes) emote.name: emote}; } + + @action + Future getBadges({required UserTwitch channelInfo, required Map headers}) async => await Future.wait([ + Twitch.getBadgesGlobal().then((badges) => twitchBadgesToObject.addAll(badges)), + Twitch.getBadgesChannel(id: channelInfo.id).then((badges) => twitchBadgesToObject.addAll(badges)), + FFZ.getBadges().then((badges) => _userToFFZBadges = badges ?? {}), + SevenTV.getBadges().then((badges) => _userTo7TVBadges = badges ?? {}), + BTTV.getBadges().then((badges) => _userToBTTVBadges = badges ?? {}), + ]); + + @action + Future getUserEmotes({required List emoteSets, required Map headers}) async => + await Future.wait(emoteSets.map((setId) => Twitch.getEmotesSets(setId: setId, headers: headers))) + .then((emotes) => emotes.expand((list) => list).toList()) + .then((userEmotes) => _userEmoteToObject = {for (final emote in userEmotes) emote.name: emote}.asObservable()); } diff --git a/lib/screens/channel/chat/chat_assets_store.g.dart b/lib/screens/channel/chat/chat_assets_store.g.dart index d041a297..eaa98769 100644 --- a/lib/screens/channel/chat/chat_assets_store.g.dart +++ b/lib/screens/channel/chat/chat_assets_store.g.dart @@ -67,26 +67,6 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store { }); } - final _$_twitchBadgesToObjectAtom = - Atom(name: '_ChatAssetsStoreBase._twitchBadgesToObject'); - - Map get twitchBadgesToObject { - _$_twitchBadgesToObjectAtom.reportRead(); - return super._twitchBadgesToObject; - } - - @override - Map get _twitchBadgesToObject => - twitchBadgesToObject; - - @override - set _twitchBadgesToObject(Map value) { - _$_twitchBadgesToObjectAtom.reportWrite(value, super._twitchBadgesToObject, - () { - super._twitchBadgesToObject = value; - }); - } - final _$_userToFFZBadgesAtom = Atom(name: '_ChatAssetsStoreBase._userToFFZBadges'); @@ -181,12 +161,30 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store { .run(() => super.getAssets(channelName: channelName, headers: headers)); } + final _$getEmotesAsyncAction = AsyncAction('_ChatAssetsStoreBase.getEmotes'); + + @override + Future getEmotes( + {required UserTwitch channelInfo, required Map headers}) { + return _$getEmotesAsyncAction + .run(() => super.getEmotes(channelInfo: channelInfo, headers: headers)); + } + + final _$getBadgesAsyncAction = AsyncAction('_ChatAssetsStoreBase.getBadges'); + + @override + Future getBadges( + {required UserTwitch channelInfo, required Map headers}) { + return _$getBadgesAsyncAction + .run(() => super.getBadges(channelInfo: channelInfo, headers: headers)); + } + final _$getUserEmotesAsyncAction = AsyncAction('_ChatAssetsStoreBase.getUserEmotes'); @override Future getUserEmotes( - {List? emoteSets, required Map headers}) { + {required List emoteSets, required Map headers}) { return _$getUserEmotesAsyncAction .run(() => super.getUserEmotes(emoteSets: emoteSets, headers: headers)); } diff --git a/lib/screens/channel/chat/chat_store.dart b/lib/screens/channel/chat/chat_store.dart index 595e71ed..aa400c3b 100644 --- a/lib/screens/channel/chat/chat_store.dart +++ b/lib/screens/channel/chat/chat_store.dart @@ -139,11 +139,13 @@ abstract class _ChatStoreBase with Store { break; case Command.globalUserState: final setIds = parsedIRCMessage.tags['emote-sets']?.split(','); - _messages.add(IRCMessage.createNotice(message: 'Fetching user emotes...')); - assetsStore - .getUserEmotes(emoteSets: setIds, headers: auth.headersTwitch) - .then((_) => _messages.add(IRCMessage.createNotice(message: 'User emotes fetched!'))) - .onError((error, stackTrace) => _messages.add(IRCMessage.createNotice(message: 'Failed to fetch user emotes: ${error.toString()}'))); + if (setIds != null) { + _messages.add(IRCMessage.createNotice(message: 'Fetching user emotes...')); + assetsStore + .getUserEmotes(emoteSets: setIds, headers: auth.headersTwitch) + .then((_) => _messages.add(IRCMessage.createNotice(message: 'User emotes fetched!'))) + .onError((error, stackTrace) => _messages.add(IRCMessage.createNotice(message: 'Failed to fetch user emotes: ${error.toString()}'))); + } continue; case Command.none: debugPrint('Unknown command: ${parsedIRCMessage.command}'); @@ -213,8 +215,12 @@ abstract class _ChatStoreBase with Store { onDone: () async { if (_channel == null) return; - // Add notice that chat was disconnected and then wait the backoff time before reconnecting. - _messages.add(IRCMessage.createNotice(message: 'Disconnected from chat, waiting $_backoffTime seconds before reconnecting...')); + if (_backoffTime > 0) { + // Add notice that chat was disconnected and then wait the backoff time before reconnecting. + final notice = 'Disconnected from chat, waiting ${_backoffTime == 1 ? 'second' : 'seconds'} before reconnecting...'; + _messages.add(IRCMessage.createNotice(message: notice)); + } + await Future.delayed(Duration(seconds: _backoffTime)); // Increase the backoff time for the next retry. @@ -222,7 +228,7 @@ abstract class _ChatStoreBase with Store { // Increment the retry count and attempt the reconnect. _retries++; - _messages.add(IRCMessage.createNotice(message: 'Reconnecting to chat (attempt $_retries).')); + _messages.add(IRCMessage.createNotice(message: 'Reconnecting to chat (attempt $_retries)...')); reconnect(); }, );