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.
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import { Fuse } from '../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; |
| 4 | 4 | import { 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'; |
| 5 | 5 | import { extension_settings, getContext } from './extensions.js'; |
| 6 | 6 | import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; |
| 7 | 7 | import { isMobile } from './RossAscends-mods.js'; |
| @@ -1754,12 +1754,12 @@ function registerWorldInfoSlashCommands() { | ||
| 1754 | 1754 | */ |
| 1755 | 1755 | export async function showWorldEditor(name) { |
| 1756 | 1756 | if (!name) { |
| 1757 | 1757 | await hideWorldEditor(); |
| 1758 | 1758 | return; |
| 1759 | 1759 | } |
| 1760 | 1760 | |
| 1761 | 1761 | const wiData = await loadWorldInfo(name); |
| 1762 | 1762 | await displayWorldEntries(name, wiData); |
| 1763 | 1763 | } |
| 1764 | 1764 | |
| 1765 | 1765 | /** |
| @@ -1815,8 +1815,8 @@ export async function updateWorldInfoList() { | ||
| 1815 | 1815 | } |
| 1816 | 1816 | } |
| 1817 | 1817 | |
| 1818 | 1818 | async function hideWorldEditor() { |
| 1819 | 1819 | await displayWorldEntries(null, null); |
| 1820 | 1820 | } |
| 1821 | 1821 | |
| 1822 | 1822 | function getWIElement(name) { |
| @@ -1937,15 +1937,102 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f | ||
| 1937 | 1937 | worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text)); |
| 1938 | 1938 | } |
| 1939 | 1939 | |
| 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); | |
| 1942 | 2026 | |
| 1943 | 2027 | const worldEntriesList = $('#world_popup_entries_list'); |
| 1944 | 2028 | |
| 1945 | 2029 | // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements |
| 1946 | 2030 | // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it |
| 1947 | 2031 | //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(); | |
| 1949 | 2036 | |
| 1950 | 2037 | if (!data || !('entries' in data)) { |
| 1951 | 2038 | $('#world_popup_new').off('click').on('click', nullWorldInfo); |
| @@ -2040,8 +2127,9 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | ||
| 2040 | 2127 | callback: async function (/** @type {object[]} */ page) { |
| 2041 | 2128 | // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements |
| 2042 | 2129 | // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it |
| 2043 | 2130 | //worldEntriesList.find('*').off(); |
| 2044 | 2131 | worldEntriesList.emptyclearEntryList(); |
| 2132 | + //worldEntriesList.empty(); | |
| 2045 | 2133 | |
| 2046 | 2134 | const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders'); |
| 2047 | 2135 | 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 | ||
| 2173 | 2261 | if (selectedIndex !== -1) { |
| 2174 | 2262 | $('#world_editor_select').val(selectedIndex).trigger('change'); |
| 2175 | 2263 | } else { |
| 2176 | 2264 | await hideWorldEditor(); |
| 2177 | 2265 | } |
| 2178 | 2266 | } |
| 2179 | 2267 | }); |
| @@ -2211,6 +2299,12 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | ||
| 2211 | 2299 | await saveWorldInfo(name, data); |
| 2212 | 2300 | }, |
| 2213 | 2301 | }); |
| 2302 | + | |
| 2303 | + worldEntriesList.css('opacity', '1'); | |
| 2304 | + //remove inline CSS on the list | |
| 2305 | + worldEntriesList.removeAttr('style'); | |
| 2306 | + | |
| 2307 | + | |
| 2214 | 2308 | //$("#world_popup_entries_list").disableSelection(); |
| 2215 | 2309 | } |
| 2216 | 2310 | |
| @@ -3819,7 +3913,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {} | ||
| 3819 | 3913 | if (selectedIndex !== -1) { |
| 3820 | 3914 | $('#world_editor_select').val(selectedIndex).trigger('change'); |
| 3821 | 3915 | } else { |
| 3822 | 3916 | await hideWorldEditor(); |
| 3823 | 3917 | } |
| 3824 | 3918 | |
| 3825 | 3919 | return true; |
| @@ -5385,7 +5479,7 @@ export function initWorldInfo() { | ||
| 5385 | 5479 | const selectedIndex = String($('#world_editor_select').find(':selected').val()); |
| 5386 | 5480 | |
| 5387 | 5481 | if (selectedIndex === '') { |
| 5388 | 5482 | await hideWorldEditor(); |
| 5389 | 5483 | } else { |
| 5390 | 5484 | const worldName = world_names[selectedIndex]; |
| 5391 | 5485 | showWorldEditor(worldName); |