Files
svelte-mud/PERFORMANCE_OPTIMIZATION.md

5.5 KiB
Raw Permalink Blame History

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

<!-- 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:

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.