Skip to content

Commit

Permalink
Use binary search for emotes/badges/cheermotes and reduce GetKeyName …
Browse files Browse the repository at this point in the history
…allocations
  • Loading branch information
ScrubN committed Jan 19, 2024
1 parent f3cab4a commit b4696f7
Showing 1 changed file with 94 additions and 38 deletions.
132 changes: 94 additions & 38 deletions TwitchDownloaderCore/ChatRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -652,17 +652,9 @@ private SKBitmap CombineImages(List<(SKImageInfo info, SKBitmap bitmap)> section

private static string GetKeyName(IEnumerable<Codepoint> codepoints)
{
List<string> codepointList = new List<string>();
foreach (Codepoint codepoint in codepoints)
{
if (codepoint.Value != 0xFE0F)
{
codepointList.Add(codepoint.Value.ToString("X"));
}
}
var codepointList = from codepoint in codepoints where codepoint.Value != 0xFE0F select codepoint.Value.ToString("X");

string emojiKey = string.Join(' ', codepointList);
return emojiKey;
return string.Join(' ', codepointList);
}

private void DrawNonAccentedMessage(Comment comment, List<(SKImageInfo info, SKBitmap bitmap)> sectionImages, List<(Point, TwitchEmote)> emotePositionList, bool highlightWords, ref Point drawPos, ref Point defaultPos)
Expand Down Expand Up @@ -917,15 +909,28 @@ private void DrawFragmentPart(List<(SKImageInfo info, SKBitmap bitmap)> sectionI

static bool TryGetTwitchEmote(List<TwitchEmote> twitchEmoteList, ReadOnlySpan<char> emoteName, [NotNullWhen(true)] out TwitchEmote twitchEmote)
{
// Enumerating over a span is faster than a list
var emoteListSpan = CollectionsMarshal.AsSpan(twitchEmoteList);
foreach (var emote1 in emoteListSpan)
var lo = 0;
var hi = emoteListSpan.Length - 1;
while (lo <= hi)
{
if (emote1.Name.AsSpan().SequenceEqual(emoteName))
var i = lo + ((hi - lo) >> 1);
var order = emoteListSpan[i].Name.AsSpan().CompareTo(emoteName, StringComparison.Ordinal);

if (order == 0)
{
twitchEmote = emote1;
twitchEmote = emoteListSpan[i];
return true;
}

if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}

twitchEmote = null;
Expand Down Expand Up @@ -1179,15 +1184,28 @@ private void DrawRegularMessage(List<(SKImageInfo info, SKBitmap bitmap)> sectio

static bool TryGetCheerEmote(List<CheerEmote> cheerEmoteList, ReadOnlySpan<char> prefix, [NotNullWhen(true)] out CheerEmote cheerEmote)
{
// Enumerating over a span is faster than a list
var cheerEmoteListSpan = CollectionsMarshal.AsSpan(cheerEmoteList);
foreach (var emote1 in cheerEmoteListSpan)
var emoteListSpan = CollectionsMarshal.AsSpan(cheerEmoteList);
var lo = 0;
var hi = emoteListSpan.Length - 1;
while (lo <= hi)
{
if (emote1.prefix.AsSpan().Equals(prefix, StringComparison.OrdinalIgnoreCase))
var i = lo + ((hi - lo) >> 1);
var order = emoteListSpan[i].prefix.AsSpan().CompareTo(prefix, StringComparison.Ordinal);

if (order == 0)
{
cheerEmote = emote1;
cheerEmote = emoteListSpan[i];
return true;
}

if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}

cheerEmote = null;
Expand Down Expand Up @@ -1228,15 +1246,28 @@ private void DrawFirstPartyEmote(List<(SKImageInfo info, SKBitmap bitmap)> secti

static bool TryGetTwitchEmote(List<TwitchEmote> twitchEmoteList, ReadOnlySpan<char> emoteId, [NotNullWhen(true)] out TwitchEmote twitchEmote)
{
// Enumerating over a span is faster than a list
var emoteListSpan = CollectionsMarshal.AsSpan(twitchEmoteList);
foreach (var emote1 in emoteListSpan)
var lo = 0;
var hi = emoteListSpan.Length - 1;
while (lo <= hi)
{
if (emote1.Id.AsSpan().SequenceEqual(emoteId))
var i = lo + ((hi - lo) >> 1);
var order = emoteListSpan[i].Id.AsSpan().CompareTo(emoteId, StringComparison.Ordinal);

if (order == 0)
{
twitchEmote = emote1;
twitchEmote = emoteListSpan[i];
return true;
}

if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}

twitchEmote = null;
Expand Down Expand Up @@ -1460,29 +1491,49 @@ private void DrawBadges(Comment comment, List<(SKImageInfo info, SKBitmap bitmap

foreach (var badge in comment.message.user_badges)
{
string id = badge._id;
string version = badge.version;
var id = badge._id;
var version = badge.version;

foreach (var cachedBadge in badgeList)
if (!TryGetBadge(badgeList, id, out var cachedBadge))
continue;

if (!cachedBadge.Versions.TryGetValue(version, out var badgeBitmap))
continue;

returnList.Add((badgeBitmap, cachedBadge.Type));
}

return returnList;

static bool TryGetBadge(List<ChatBadge> badgeList, ReadOnlySpan<char> badgeName, [NotNullWhen(true)] out ChatBadge badge)
{
var badgeSpan = CollectionsMarshal.AsSpan(badgeList);
var lo = 0;
var hi = badgeSpan.Length - 1;
while (lo <= hi)
{
if (cachedBadge.Name != id)
continue;
var i = lo + ((hi - lo) >> 1);
var order = badgeSpan[i].Name.AsSpan().CompareTo(badgeName, StringComparison.Ordinal);

foreach (var cachedVersion in cachedBadge.Versions)
if (order == 0)
{
if (cachedVersion.Key == version)
{
returnList.Add((cachedVersion.Value, cachedBadge.Type));
goto NextUserBadge;
}
badge = badgeSpan[i];
return true;
}

if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}

// goto is cheaper and more readable than using a boolean + branch check after each operation
NextUserBadge: ;
badge = null;
return false;
}

return returnList;
}

private void DrawTimestamp(Comment comment, List<(SKImageInfo info, SKBitmap bitmap)> sectionImages, ref Point drawPos, ref Point defaultPos)
Expand Down Expand Up @@ -1557,6 +1608,11 @@ private async Task FetchScaledImages(CancellationToken cancellationToken)
emoteThirdList = emoteThirdTask.Result;
cheermotesList = cheerTask.Result;
emojiCache = emojiTask.Result;

badgeList.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
emoteList.Sort((a, b) => string.Compare(a.Id, b.Id, StringComparison.Ordinal));
emoteThirdList.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
cheermotesList.Sort((a, b) => string.Compare(a.prefix, b.prefix, StringComparison.Ordinal));
}

private async Task<List<ChatBadge>> GetScaledBadges(CancellationToken cancellationToken)
Expand Down

0 comments on commit b4696f7

Please sign in to comment.