5.5 KiB
5.5 KiB
MUD Terminal Performance Optimization Summary
Problem Identified
The MUD terminal component had severe performance issues when the message history grew large due to expensive text processing operations being performed in the Svelte template on every render cycle.
Key Issues:
- Expensive re-computation on every render:
processAnsi(),applyHighlights(), andsplitIntoLines()were called for every message on every component re-render - Complex text processing in template: ANSI color processing, regex highlighting, and line splitting happened in the template using
{@const}blocks - Inefficient reactive statements: Triggered unnecessary DOM work on every history change
- No caching: The same text processing was repeated multiple times for the same content
Optimizations Implemented
1. Pre-processing in Store Layer
- Created
textProcessing.tsutility with all text processing logic - Moved expensive operations to happen once when messages are added to store
- Added
processedOutputHistorystore that contains pre-processed messages
2. Intelligent Caching System
- Implemented
ProcessedMessageinterface with built-in cache - Cache keyed by UI settings that affect rendering (e.g., ANSI color enabled/disabled)
- Messages are re-processed only when relevant settings change
- Uses
Map<string, ProcessedContent>for efficient cache lookups
3. Flattened Renderable Lines Store
- Created
activeRenderableLinesderived store that provides a flat array of all renderable lines - Eliminates complex nested logic in template
- Each line has pre-computed properties (content, styling, metadata)
- Single
{#each}loop instead of nested processing
4. Optimized Template Rendering
<!-- Before: Complex nested processing -->
{#each safeOutputHistory as item (item.id)}
{#if item.isInput}
<!-- ... -->
{:else}
{@const processedContent = applyHighlights(processAnsi(item.text), item.highlights || [])}
{@const lines = splitIntoLines(processedContent)}
{#if lines.length <= 1}
<!-- ... -->
{:else}
{#each lines as line, lineIndex}
<!-- ... -->
{/each}
{/if}
{/if}
{/each}
<!-- After: Simple flat rendering -->
{#each safeRenderableLines as line (line.id)}
<div class="mud-terminal-line"
class:mud-input-line={line.isInput}
class:mud-terminal-subline={line.isSubline}>
{#if $uiSettings.showTimestamps && line.lineIndex === 0}
<span class="mud-timestamp">[{formatTimestamp(line.timestamp)}]</span>
{/if}
<div class="mud-terminal-content">
{@html line.content}
</div>
</div>
{/each}
5. Optimized Reactive Statements
- Replaced
setTimeout()withPromise.resolve().then()for better microtask scheduling - More targeted reactive updates that only trigger when necessary
- Removed redundant reactive blocks
6. Store Architecture Improvements
- Separated raw message storage from processed message storage
- Made
addToOutputHistory()handle both raw and processed storage - Ensured cache consistency when clearing history
Performance Benefits
Before Optimization:
- O(n) text processing operations on every render for n messages
- Multiple expensive regex operations per message per render
- ANSI-to-HTML conversion happening repeatedly
- Complex DOM operations during each reactive update
After Optimization:
- O(1) amortized cost per message (processing happens once)
- O(1) cache lookups for repeated operations
- Text processing only when messages are added or settings change
- Simple, flat DOM structure with minimal reactive overhead
Technical Implementation Details
New Files Created:
src/lib/utils/textProcessing.ts- Centralized text processing utilities
Modified Files:
src/lib/stores/mudStore.ts- Added processed stores and cachingsrc/lib/components/MudTerminal.svelte- Simplified template and removed processing functions
Key Data Structures:
interface ProcessedMessage {
id: string;
originalText: string;
timestamp: number;
isInput: boolean;
highlights: HighlightRule[];
processedContent: string;
lines: ProcessedLine[];
processedCache: Map<string, ProcessedContent>; // Cache by UI settings
}
interface ProcessedLine {
id: string;
content: string; // Pre-processed HTML content
isSubline: boolean; // For indentation
parentId: string; // Reference to parent message
lineIndex: number; // Position in parent message
}
Leveraging Svelte's Strengths
The optimization takes full advantage of Svelte's reactive system:
- Derived Stores: Used for computed values that automatically update when dependencies change
- Keyed Each Blocks: Ensures efficient DOM updates with
{#each items as item (item.id)} - Conditional Classes: Uses
class:name={condition}for efficient class toggling - Reactive Declarations: Optimized
$:statements that only run when necessary - Store Composition: Layered stores that build upon each other efficiently
Expected Performance Gains
For a terminal with 1000+ messages:
- Before: ~1000 × (ANSI processing + regex highlighting + line splitting) per render
- After: ~0 processing per render (cached results)
- Memory: Slightly higher due to caching, but with configurable limits
- Responsiveness: Should feel instant even with large message histories
The optimization maintains all existing functionality while dramatically improving performance, especially as message history grows.