add some time logging

This commit is contained in:
ChaosExAnima 2025-08-04 10:36:00 +02:00
parent 966e9889ee
commit 8befbb2191
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117

View File

@ -1,7 +1,6 @@
import { autoPlayGif } from '@/mastodon/initial_state';
import { createLimitedCache } from '@/mastodon/utils/cache';
import { assetHost } from '@/mastodon/utils/config';
import * as perf from '@/mastodon/utils/performance';
import {
EMOJI_MODE_NATIVE,
@ -42,16 +41,17 @@ export function emojifyElement<Element extends HTMLElement>(
appState: EmojiAppState,
extraEmojis: ExtraCustomEmojiMap = {},
): Element | null {
const finish = timingMeasurementHelper('emojifyElement');
// Check the cache and return it if we get a hit.
const cacheKey = createTextCacheKey(element, appState, extraEmojis);
const cached = textCache.get(cacheKey);
if (cached !== undefined) {
log('Cache hit on %s', element.outerHTML);
if (cached === null) {
return null;
// return null;
}
element.innerHTML = cached;
return element;
// element.innerHTML = cached;
// return element;
}
// Exit if there are no emoji in the string.
@ -60,7 +60,6 @@ export function emojifyElement<Element extends HTMLElement>(
return null;
}
perf.start('emojifyElement()');
const queue: (HTMLElement | Text)[] = [element];
while (queue.length > 0) {
const current = queue.shift();
@ -76,8 +75,9 @@ export function emojifyElement<Element extends HTMLElement>(
current.textContent &&
(current instanceof Text || !current.hasChildNodes())
) {
const renderedContent = textToElementArray(
current.textContent,
const tokens = tokenizeText(current.textContent, appState.mode);
const renderedContent = tokensToElementArray(
tokens,
appState,
extraEmojis,
);
@ -97,7 +97,7 @@ export function emojifyElement<Element extends HTMLElement>(
}
}
textCache.set(cacheKey, element.innerHTML);
perf.stop('emojifyElement()');
finish();
return element;
}
@ -116,7 +116,8 @@ export function emojifyText(
textCache.set(cacheKey, null);
return text;
}
const eleArray = textToElementArray(text, appState, extraEmojis);
const tokens = tokenizeText(text, appState.mode);
const eleArray = tokensToElementArray(tokens, appState, extraEmojis);
if (!eleArray) {
textCache.set(cacheKey, null);
return text;
@ -158,51 +159,14 @@ function cacheForType(type: EmojiType) {
return type === EMOJI_TYPE_UNICODE ? unicodeEmojiCache : customEmojiCache;
}
type EmojifiedTextArray = (string | HTMLImageElement)[];
function textToElementArray(
text: string,
appState: EmojiAppState,
extraEmojis: ExtraCustomEmojiMap = {},
): EmojifiedTextArray | null {
// Exit if no text to convert.
if (!text.trim()) {
return null;
}
const tokens = tokenizeText(text, appState.mode);
// If only one token and it's a string, exit early.
if (tokens.length === 1 && typeof tokens[0] === 'string') {
return null;
}
const renderedFragments: EmojifiedTextArray = [];
for (const token of tokens) {
// Plain text does not need to be converted.
if (typeof token === 'string') {
renderedFragments.push(token);
continue;
}
// Check if this is a provided custom emoji and use that if so.
if (token.type === EMOJI_TYPE_CUSTOM) {
const extraEmojiData = extraEmojis[token.code];
if (extraEmojiData) {
token.data = extraEmojiData;
}
}
// Create an image element from the token and add it to the the fragments.
const image = stateToImage(token, appState);
renderedFragments.push(image);
}
return renderedFragments;
}
// Tokenization. This takes the HTML string and converts it into an array of emoji types that are cached.
type TokenizedText = (string | Exclude<EmojiState, EmojiStateMissing>)[];
const tokenCache = createLimitedCache<TokenizedText>({
log: log.extend('tokens'),
});
/**
* Accepts incoming text strings and breaks them into an array of state tokens.
*/
@ -211,6 +175,13 @@ export function tokenizeText(text: string, mode: EmojiMode): TokenizedText {
return [];
}
const finish = timingMeasurementHelper('tokenizeText');
const cached = tokenCache.get(text);
if (cached) {
// return cached;
}
const tokens = [];
let lastIndex = 0;
for (const match of text.matchAll(anyEmojiRegex())) {
@ -238,10 +209,9 @@ export function tokenizeText(text: string, mode: EmojiMode): TokenizedText {
if (cachedData === EMOJI_STATE_MISSING) {
continue; // Exit if we know this is missing.
} else if (cachedData) {
tokens.push(cachedData); // We already cached this token, so just use that.
} else {
// This is possibly an emoji!
// We are not saving the data in here to keep cache sizes small.
tokens.push({
type,
code,
@ -256,6 +226,7 @@ export function tokenizeText(text: string, mode: EmojiMode): TokenizedText {
if (lastIndex < text.length) {
tokens.push(text.slice(lastIndex));
}
finish();
return tokens;
}
@ -273,9 +244,57 @@ function shouldRenderUnicodeImage(code: string, mode: EmojiMode): boolean {
return true;
}
type EmojifiedTextArray = (string | HTMLImageElement)[];
function tokensToElementArray(
tokens: TokenizedText,
appState: EmojiAppState,
extraEmojis: ExtraCustomEmojiMap = {},
): EmojifiedTextArray | null {
// If only one token and it's a string, exit early.
if (tokens.length === 1 && typeof tokens[0] === 'string') {
return null;
}
const finish = timingMeasurementHelper('tokensToElementArray');
const renderedFragments: EmojifiedTextArray = [];
for (const token of tokens) {
// Plain text does not need to be converted.
if (typeof token === 'string') {
renderedFragments.push(token);
continue;
}
// Check if this is a provided custom emoji and use that if so.
if (token.type === EMOJI_TYPE_CUSTOM) {
const extraEmojiData = extraEmojis[token.code];
if (extraEmojiData) {
token.data = extraEmojiData;
}
}
// Otherwise, load the data from the cache if it exists.
if (!token.data) {
const cache = cacheForType(token.type);
const cached = cache.get(token.code);
if (cached !== EMOJI_STATE_MISSING && cached?.data) {
token.data = cached.data;
}
}
// Create an image element from the token and add it to the the fragments.
const image = stateToImage(token, appState);
renderedFragments.push(image);
}
finish();
return renderedFragments;
}
const EMOJI_SIZE = 16;
function stateToImage(state: EmojiStateToken, appState: EmojiAppState) {
const finish = timingMeasurementHelper('stateToImage');
const image = document.createElement('img');
image.draggable = false;
image.classList.add('emojione');
@ -295,6 +314,7 @@ function stateToImage(state: EmojiStateToken, appState: EmojiAppState) {
imageAttributesFromState(image, state, appState.darkTheme);
}
finish();
return image;
}
@ -394,6 +414,14 @@ function renderedToHTML(
return fragment;
}
function timingMeasurementHelper(name: string) {
const start = performance.now();
return () => {
const duration = performance.now() - start;
log.extend('timing')('timing for %s: %d', name, duration);
};
}
// Testing helpers
export const testCacheClear = () => {
textCache.clear();