Fixes #11: Memory management enhancements
- Add clearDomCache() for jQuery object cache invalidation on panel rebuild - Track all event handlers in on()/once() for complete cleanup by unregisterAllEvents() - Migrate direct eventSource.on() calls to tracked onEvent() in index.js, injector.js, sillyTavernExpressions.js - Add clearDebugLogs() called on CHAT_CHANGED to prevent memory accumulation - Add trackJQueryHandler()/cleanupJQueryEvents() infrastructure for jQuery event cleanup - Add comprehensive memory management tests (14 new tests, all passing)
This commit is contained in:
@@ -12,6 +12,12 @@ import { eventSource, event_types } from '../../../../../../script.js';
|
||||
*/
|
||||
export function on(eventType, handler) {
|
||||
eventSource.on(eventType, handler);
|
||||
|
||||
// Track for cleanup
|
||||
if (!registeredHandlers.has(eventType)) {
|
||||
registeredHandlers.set(eventType, []);
|
||||
}
|
||||
registeredHandlers.get(eventType).push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,6 +27,12 @@ export function on(eventType, handler) {
|
||||
*/
|
||||
export function once(eventType, handler) {
|
||||
eventSource.once(eventType, handler);
|
||||
|
||||
// Track for cleanup (one-time handlers should also be tracked)
|
||||
if (!registeredHandlers.has(eventType)) {
|
||||
registeredHandlers.set(eventType, []);
|
||||
}
|
||||
registeredHandlers.get(eventType).push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -594,3 +594,60 @@ export function setMusicPlayerContainer($element) {
|
||||
export function setEquipmentContainer($element) {
|
||||
$equipmentContainer = $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cached DOM element references.
|
||||
* Call this when the panel is destroyed or rebuilt to prevent stale jQuery references.
|
||||
*/
|
||||
export function clearDomCache() {
|
||||
$panelContainer = null;
|
||||
$userStatsContainer = null;
|
||||
$infoBoxContainer = null;
|
||||
$thoughtsContainer = null;
|
||||
$inventoryContainer = null;
|
||||
$questsContainer = null;
|
||||
$musicPlayerContainer = null;
|
||||
$equipmentContainer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the debug logs array.
|
||||
* Call this on chat change to prevent memory accumulation across sessions.
|
||||
*/
|
||||
export function clearDebugLogs() {
|
||||
debugLogs.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks jQuery delegated event handlers for cleanup.
|
||||
* Each handler is stored with its namespace for targeted unbinding.
|
||||
*/
|
||||
const jqueryEventHandlers = [];
|
||||
|
||||
/**
|
||||
* Register a jQuery delegated event handler for tracking.
|
||||
* @param {string} event - Event type with optional namespace (e.g., 'click.rpgCompanion')
|
||||
* @param {string} selector - Delegated selector
|
||||
* @param {Function} handler - Event handler function
|
||||
* @returns {Function} The same handler for chaining
|
||||
*/
|
||||
export function trackJQueryHandler(event, selector, handler) {
|
||||
jqueryEventHandlers.push({ event, selector, handler });
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds all tracked jQuery delegated event handlers.
|
||||
* Call this when the extension is disabled or the panel is destroyed.
|
||||
* @param {Function} $ - jQuery function (passed from caller)
|
||||
*/
|
||||
export function cleanupJQueryEvents($) {
|
||||
for (const { event, selector, handler } of jqueryEventHandlers) {
|
||||
if (handler) {
|
||||
$(document).off(event, selector, handler);
|
||||
} else {
|
||||
$(document).off(event, selector);
|
||||
}
|
||||
}
|
||||
jqueryEventHandlers.length = 0;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
|
||||
import { getContext } from '../../../../../../extensions.js';
|
||||
import { extension_prompt_types, extension_prompt_roles, setExtensionPrompt, eventSource, event_types } from '../../../../../../../script.js';
|
||||
import { extension_prompt_types, extension_prompt_roles, setExtensionPrompt, event_types } from '../../../../../../../script.js';
|
||||
import { on as onEvent } from '../../core/events.js';
|
||||
import {
|
||||
extensionSettings,
|
||||
committedTrackerData,
|
||||
@@ -892,15 +893,16 @@ ${contextInstructionsText}
|
||||
export function initHistoryInjectionListeners() {
|
||||
// Register persistent listeners for prompt injection
|
||||
// These check pendingContextMap and only inject if there's data
|
||||
// Use tracked registration (onEvent) so unregisterAllEvents() cleans them up
|
||||
|
||||
// Primary: BEFORE_COMBINE for text completion (more reliable - modifies message objects)
|
||||
eventSource.on(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, onGenerateBeforeCombinePrompts);
|
||||
onEvent(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, onGenerateBeforeCombinePrompts);
|
||||
|
||||
// Fallback: AFTER_COMBINE for text completion (string-based injection)
|
||||
eventSource.on(event_types.GENERATE_AFTER_COMBINE_PROMPTS, onGenerateAfterCombinePrompts);
|
||||
onEvent(event_types.GENERATE_AFTER_COMBINE_PROMPTS, onGenerateAfterCombinePrompts);
|
||||
|
||||
// Chat completion (OpenAI, etc.)
|
||||
eventSource.on(event_types.CHAT_COMPLETION_PROMPT_READY, onChatCompletionPromptReady);
|
||||
onEvent(event_types.CHAT_COMPLETION_PROMPT_READY, onChatCompletionPromptReady);
|
||||
|
||||
console.log('[RPG Companion] History injection listeners initialized');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Fuse } from '../../../../../../lib.js';
|
||||
import {
|
||||
characters,
|
||||
eventSource,
|
||||
event_types,
|
||||
generateQuietPrompt,
|
||||
generateRaw,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
substituteParamsExtended,
|
||||
this_chid
|
||||
} from '../../../../../../script.js';
|
||||
import { once as onceEvent } from '../core/events.js';
|
||||
import {
|
||||
doExtrasFetch,
|
||||
extension_settings as stExtensionSettings,
|
||||
@@ -562,7 +562,7 @@ export async function classifyExpressionText(text, { characterName = '' } = {})
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onReady);
|
||||
onceEvent(event_types.TEXT_COMPLETION_SETTINGS_READY, onReady);
|
||||
|
||||
const responseText = settings.promptType === PROMPT_TYPE.full
|
||||
? await generateQuietPrompt({ quietPrompt: prompt })
|
||||
|
||||
Reference in New Issue
Block a user