Improve chat future performance w/ parallelization

This commit is contained in:
Tommy Chow 2022-01-06 21:27:40 -05:00
parent 8b24356399
commit 9caad1f6c9
3 changed files with 73 additions and 71 deletions

View file

@ -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<Emote> get sevenTvEmotes => _emoteToObject.values.where((emote) => is7TV(emote)).toList();
/// The map of badges ids to their object representation.
final twitchBadgesToObject = ObservableMap<String, BadgeInfoTwitch>();
/// The map of emote words to their image or GIF URL. May be used by anyone in the chat.
@readonly
var _emoteToObject = <String, Emote>{};
@ -31,10 +35,6 @@ abstract class _ChatAssetsStoreBase with Store {
@readonly
var _userEmoteToObject = <String, Emote>{};
/// The map of badges ids to their object representation.
@readonly
var _twitchBadgesToObject = <String, BadgeInfoTwitch>{};
/// The map of usernames to their FFZ badges.
@readonly
var _userToFFZBadges = <String, List<BadgeInfoFFZ>>{};
@ -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<void> getUserEmotes({List<String>? emoteSets, required Map<String, String> headers}) async {
if (emoteSets != null) {
final userEmotes = <Emote>[];
for (final setId in emoteSets) {
userEmotes.addAll(await Twitch.getEmotesSets(setId: setId, headers: headers));
}
Future<void> getEmotes({required UserTwitch channelInfo, required Map<String, String> 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 ?? <Emote>[];
}),
]);
_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<void> getBadges({required UserTwitch channelInfo, required Map<String, String> 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<void> getUserEmotes({required List<String> emoteSets, required Map<String, String> 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());
}

View file

@ -67,26 +67,6 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store {
});
}
final _$_twitchBadgesToObjectAtom =
Atom(name: '_ChatAssetsStoreBase._twitchBadgesToObject');
Map<String, BadgeInfoTwitch> get twitchBadgesToObject {
_$_twitchBadgesToObjectAtom.reportRead();
return super._twitchBadgesToObject;
}
@override
Map<String, BadgeInfoTwitch> get _twitchBadgesToObject =>
twitchBadgesToObject;
@override
set _twitchBadgesToObject(Map<String, BadgeInfoTwitch> 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<void> getEmotes(
{required UserTwitch channelInfo, required Map<String, String> headers}) {
return _$getEmotesAsyncAction
.run(() => super.getEmotes(channelInfo: channelInfo, headers: headers));
}
final _$getBadgesAsyncAction = AsyncAction('_ChatAssetsStoreBase.getBadges');
@override
Future<void> getBadges(
{required UserTwitch channelInfo, required Map<String, String> headers}) {
return _$getBadgesAsyncAction
.run(() => super.getBadges(channelInfo: channelInfo, headers: headers));
}
final _$getUserEmotesAsyncAction =
AsyncAction('_ChatAssetsStoreBase.getUserEmotes');
@override
Future<void> getUserEmotes(
{List<String>? emoteSets, required Map<String, String> headers}) {
{required List<String> emoteSets, required Map<String, String> headers}) {
return _$getUserEmotesAsyncAction
.run(() => super.getUserEmotes(emoteSets: emoteSets, headers: headers));
}

View file

@ -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();
},
);