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