mirror of
https://github.com/mastodon/mastodon.git
synced 2025-10-05 08:33:00 +00:00
add some time logging
This commit is contained in:
parent
966e9889ee
commit
8befbb2191
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue
Block a user