140 lines
5.5 KiB
Markdown
140 lines
5.5 KiB
Markdown
|
|
# 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:
|
|||
|
|
1. **Expensive re-computation on every render**: `processAnsi()`, `applyHighlights()`, and `splitIntoLines()` were called for every message on every component re-render
|
|||
|
|
2. **Complex text processing in template**: ANSI color processing, regex highlighting, and line splitting happened in the template using `{@const}` blocks
|
|||
|
|
3. **Inefficient reactive statements**: Triggered unnecessary DOM work on every history change
|
|||
|
|
4. **No caching**: The same text processing was repeated multiple times for the same content
|
|||
|
|
|
|||
|
|
## Optimizations Implemented
|
|||
|
|
|
|||
|
|
### 1. **Pre-processing in Store Layer**
|
|||
|
|
- Created `textProcessing.ts` utility with all text processing logic
|
|||
|
|
- Moved expensive operations to happen once when messages are added to store
|
|||
|
|
- Added `processedOutputHistory` store that contains pre-processed messages
|
|||
|
|
|
|||
|
|
### 2. **Intelligent Caching System**
|
|||
|
|
- Implemented `ProcessedMessage` interface 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 `activeRenderableLines` derived 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**
|
|||
|
|
```svelte
|
|||
|
|
<!-- 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()` with `Promise.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 caching
|
|||
|
|
- `src/lib/components/MudTerminal.svelte` - Simplified template and removed processing functions
|
|||
|
|
|
|||
|
|
### Key Data Structures:
|
|||
|
|
```typescript
|
|||
|
|
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:
|
|||
|
|
|
|||
|
|
1. **Derived Stores**: Used for computed values that automatically update when dependencies change
|
|||
|
|
2. **Keyed Each Blocks**: Ensures efficient DOM updates with `{#each items as item (item.id)}`
|
|||
|
|
3. **Conditional Classes**: Uses `class:name={condition}` for efficient class toggling
|
|||
|
|
4. **Reactive Declarations**: Optimized `$:` statements that only run when necessary
|
|||
|
|
5. **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.
|