Removes memory heap bloat by improving DOM and JQUI datacache clearing when swapping WI in the editor, and all other 'reloadEditor' calls via slashcommands etc. Results in a slight delay depending on entries visible via pagination (about 2s for 50 visible entries), but I added a pleasant fade transition to make it feel organic.

b55de85243bdae4e1202f3642b077712ba957f33

RossAscends <124905043+RossAscends@users.noreply.github.com>

1 files changed, +108 -14Showing whitespace changes
public/scripts/world-info.js+108 -14
@@ -1,7 +1,7 @@
11import { Fuse } from '../lib.js';
22
33import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
44import { download, debounce, delay, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
55import { extension_settings, getContext } from './extensions.js';
66import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
77import { isMobile } from './RossAscends-mods.js';
@@ -1754,12 +1754,12 @@ function registerWorldInfoSlashCommands() {
17541754 */
17551755export async function showWorldEditor(name) {
17561756 if (!name) {
17571757 await hideWorldEditor();
17581758 return;
17591759 }
17601760
17611761 const wiData = await loadWorldInfo(name);
17621762 await displayWorldEntries(name, wiData);
17631763}
17641764
17651765/**
@@ -1815,8 +1815,8 @@ export async function updateWorldInfoList() {
18151815 }
18161816}
18171817
18181818async function hideWorldEditor() {
18191819 await displayWorldEntries(null, null);
18201820}
18211821
18221822function getWIElement(name) {
@@ -1937,15 +1937,102 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
19371937 worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
19381938}
19391939
1940-function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
1940+function clearEntryList() {
1941- updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
1941+ console.time('clearEntryList');
1942+ const $list = $('#world_popup_entries_list');
1943+
1944+
1945+ if (!$list.children().length) {
1946+ console.warn('List already empty, skipping cleanup.');
1947+ console.timeEnd('clearEntryList');
1948+ return;
1949+ }
1950+
1951+ // Step 1: Clean all <option> elements within <select>
1952+ console.warn(`Before cleaning: ${$list.find('option').length} options in list`);
1953+ $list.find('option').each(function () {
1954+ const $option = $(this);
1955+ $option.off();
1956+ $.cleanData([$option[0]]);
1957+ $option.remove();
1958+ });
1959+ console.warn(`Post-clean: ${$list.find('option').length} options in list`);
1960+
1961+ // Step 2: Clean all <select> elements
1962+ console.warn(`Before cleaning: ${$list.find('select').length} selects in list`);
1963+ $list.find('select').each(function () {
1964+ const $select = $(this);
1965+ // Remove Select2-related data and container if present
1966+ if ($select.hasClass('select2-hidden-accessible')) {
1967+ try {
1968+ $select.select2('destroy');
1969+ } catch (e) {
1970+ console.warn('Select2 destroy failed:', e);
1971+ }
1972+ }
1973+ const $container = $select.parent();
1974+ if ($container.length) {
1975+ $container.find('*').off();
1976+ $.cleanData($container.find('*').get());
1977+ $container.remove();
1978+ } else {
1979+ console.warn('container not found');
1980+ console.warn($select.parent().html());
1981+ }
1982+ // Destroy Select2 if applicable
1983+
1984+ $select.off();
1985+ $.cleanData([$select[0]]);
1986+
1987+ });
1988+ console.warn(`Post-clean: ${$list.find('select').length} selects in list`);
1989+
1990+ // Step 3: Clean <div>, <span>, <input>
1991+ console.warn(`Before cleaning: ${$list.find('div, span, input').length} divs, spans, inputs in list`);
1992+ $list.find('div, span, input').each(function () {
1993+ const $elem = $(this);
1994+ $elem.off();
1995+ $.cleanData([$elem[0]]);
1996+ $elem.remove();
1997+ });
1998+ console.warn(`Post-clean: ${$list.find('div, span, input').length} divs, spans, inputs in list`);
1999+
2000+ // Destroy Sortable
2001+ console.warn(`Before cleaning: ${$list.sortable('instance') ? 1 : 0} sortable instance in list`);
2002+ if ($list.sortable('instance')) {
2003+ $list.sortable('destroy');
2004+ }
2005+ console.warn(`Post-cleaning: ${$list.sortable('instance') ? 1 : 0} sortable instance in list`);
2006+
2007+ let totalElementsOfanyKindLeftInList = $list.children().length;
2008+
2009+ // Final cleanup
2010+ if (totalElementsOfanyKindLeftInList !== 0) {
2011+ console.time('empty');
2012+ console.warn(`Before .empty(): ${totalElementsOfanyKindLeftInList} elements left in list`);
2013+ $list.empty();
2014+ totalElementsOfanyKindLeftInList = $list.children().length;
2015+ console.warn(`After .empty(): ${totalElementsOfanyKindLeftInList} elements left in list`);
2016+ console.timeEnd('empty');
2017+ } else {
2018+ console.warn('Entry List is already totally empty, no need to call .empty()');
2019+ }
2020+
2021+ console.timeEnd('clearEntryList');
2022+}
2023+
2024+async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
2025+ updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
19422026
19432027 const worldEntriesList = $('#world_popup_entries_list');
19442028
19452029 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
19462030 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
19472031 //worldEntriesList.find('*').off();
1948- worldEntriesList.empty().show();
2032+ worldEntriesList.css({ 'opacity': 0, 'transition': 'opacity 250ms ease-in-out' });
2033+ await delay(250);
2034+ clearEntryList(); // Use enhanced cleanup
2035+ worldEntriesList.show();
19492036
19502037 if (!data || !('entries' in data)) {
19512038 $('#world_popup_new').off('click').on('click', nullWorldInfo);
@@ -2040,8 +2127,9 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
20402127 callback: async function (/** @type {object[]} */ page) {
20412128 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
20422129 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
20432130 //worldEntriesList.find('*').off();
20442131 worldEntriesList.emptyclearEntryList();
2132+ //worldEntriesList.empty();
20452133
20462134 const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
20472135 const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
@@ -2173,7 +2261,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
21732261 if (selectedIndex !== -1) {
21742262 $('#world_editor_select').val(selectedIndex).trigger('change');
21752263 } else {
21762264 await hideWorldEditor();
21772265 }
21782266 }
21792267 });
@@ -2211,6 +2299,12 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
22112299 await saveWorldInfo(name, data);
22122300 },
22132301 });
2302+
2303+ worldEntriesList.css('opacity', '1');
2304+ //remove inline CSS on the list
2305+ worldEntriesList.removeAttr('style');
2306+
2307+
22142308 //$("#world_popup_entries_list").disableSelection();
22152309}
22162310
@@ -3819,7 +3913,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
38193913 if (selectedIndex !== -1) {
38203914 $('#world_editor_select').val(selectedIndex).trigger('change');
38213915 } else {
38223916 await hideWorldEditor();
38233917 }
38243918
38253919 return true;
@@ -5385,7 +5479,7 @@ export function initWorldInfo() {
53855479 const selectedIndex = String($('#world_editor_select').find(':selected').val());
53865480
53875481 if (selectedIndex === '') {
53885482 await hideWorldEditor();
53895483 } else {
53905484 const worldName = world_names[selectedIndex];
53915485 showWorldEditor(worldName);