Regex cache (#4858) feat: optimize regex extension performance - Implemented caching mechanism for regex scripts to improve message rendering speed. - Added automatic cache invalidation on script updates. - Optimized regex compilation to reduce CPU usage. --------- Co-authored-by: GhostXia <33112711+GhostXia@users.noreply.github.com>

0225562f4ef27d52bbfc7d4e1a6e6c961074875b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +59 -2Ignore whitespace
public/scripts/extensions/regex/engine.js+56 -1
@@ -35,6 +35,61 @@ export const SCRIPT_TYPE_UNKNOWN = -1;
35const DEFAULT_GET_REGEX_SCRIPTS_OPTIONS = Object.freeze({ allowedOnly: false });35const DEFAULT_GET_REGEX_SCRIPTS_OPTIONS = Object.freeze({ allowedOnly: false });
3636
37/**37/**
38 * Manages the compiled regex cache with LRU eviction.
39 */
40export class RegexProvider {
41 /** @type {Map<string, RegExp>} */
42 #cache = new Map();
43 /** @type {number} */
44 #maxSize = 1000;
45
46 static instance = new RegexProvider();
47
48 /**
49 * Gets a regex instance by its string representation.
50 * @param {string} regexString The regex string to retrieve
51 * @returns {RegExp?} Compiled regex or null if invalid
52 */
53 get(regexString) {
54 const isCached = this.#cache.has(regexString);
55 const regex = isCached
56 ? this.#cache.get(regexString)
57 : regexFromString(regexString);
58
59 if (!regex) {
60 return null;
61 }
62
63 if (isCached) {
64 // LRU: Move to end by re-inserting
65 this.#cache.delete(regexString);
66 this.#cache.set(regexString, regex);
67 } else {
68 // Evict oldest if at capacity
69 if (this.#cache.size >= this.#maxSize) {
70 const firstKey = this.#cache.keys().next().value;
71 this.#cache.delete(firstKey);
72 }
73 this.#cache.set(regexString, regex);
74 }
75
76 // Reset lastIndex for global/sticky regexes
77 if (regex.global || regex.sticky) {
78 regex.lastIndex = 0;
79 }
80
81 return regex;
82 }
83
84 /**
85 * Clears the entire cache.
86 */
87 clear() {
88 this.#cache.clear();
89 }
90}
91
92/**
38 * Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data93 * Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data
39 *94 *
40 * @param {GetRegexScriptsOptions} options Options for retrieving the regex scripts95 * @param {GetRegexScriptsOptions} options Options for retrieving the regex scripts
@@ -353,7 +408,7 @@ export function runRegexScript(regexScript, rawString, { characterOverride } = {
353 }408 }
354 };409 };
355 const regexString = getRegexString();410 const regexString = getRegexString();
356 const findRegex = regexFromString(regexString);411 const findRegex = RegexProvider.instance.get(regexString);
357412
358 // The user skill issued. Return with nothing.413 // The user skill issued. Return with nothing.
359 if (!findRegex) {414 if (!findRegex) {
public/scripts/extensions/regex/index.js+3 -1
@@ -8,7 +8,7 @@ import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashComman
8import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';8import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
10import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js';10import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js';
11import { allowPresetScripts, allowScopedScripts, disallowPresetScripts, disallowScopedScripts, getCurrentPresetAPI, getCurrentPresetName, getRegexScripts, getScriptsByType, isPresetScriptsAllowed, isScopedScriptsAllowed, regex_placement, runRegexScript, saveScriptsByType, SCRIPT_TYPE_UNKNOWN, SCRIPT_TYPES, substitute_find_regex } from './engine.js';11import { allowPresetScripts, allowScopedScripts, disallowPresetScripts, disallowScopedScripts, getCurrentPresetAPI, getCurrentPresetName, getRegexScripts, getScriptsByType, isPresetScriptsAllowed, isScopedScriptsAllowed, regex_placement, RegexProvider, runRegexScript, saveScriptsByType, SCRIPT_TYPE_UNKNOWN, SCRIPT_TYPES, substitute_find_regex } from './engine.js';
12import { t } from '../../i18n.js';12import { t } from '../../i18n.js';
13import { accountStorage } from '../../util/AccountStorage.js';13import { accountStorage } from '../../util/AccountStorage.js';
14import { getPresetManager } from '../../preset-manager.js';14import { getPresetManager } from '../../preset-manager.js';
@@ -1628,6 +1628,8 @@ async function checkCharEmbeddedRegexScripts() {
1628 }1628 }
1629 }1629 }
16301630
1631 // Clear cache and reload scripts
1632 RegexProvider.instance.clear();
1631 await loadRegexScripts();1633 await loadRegexScripts();
1632}1634}
16331635