Skip to main content

Beat Loading & Caching System Overview

Introduction

The beat loading system in Pajama provides smooth, responsive story reading experiences through a sophisticated multi-layered caching architecture. The system features a streamlined coordination pattern with clear separation of concerns, actor-based thread safety, and intelligent preloading strategies.

System Architecture

┌─────────────────────────────────────────────┐
│ UI/Experience Layer │
├─────────────────────────────────────────────┤
│ StoryReader │ CoReading │ ← UI Components │
└─────────────────────┬───────────────────────┘

|

┌─────────────────────▼─────────────────────┐
│ StoryContentManager │ ← Single content orchestrator
│ "Manage story content experience" │ (@MainActor, events, coordination)
│ • getBeat() / loadBeat() │ • Story-wide loading strategy
│ • beginLoadingContent() │ • Beat + asset coordination
│ • UI event publishing │ • Priority management
└─────────┬─────────────┬─────────┬─────────┘
│ │ │
│ │ │ immediate asset needs
│ │ └───────────────────────────────────────────────────────────┐
│ ┌─────────▼─────────┐ ┌─────────────────┐ |
│ │ BeatPreloader │ ─── invokes for assets ──▶ │ AssetPreloader │ |
│ │ "Preload beats + │ (sliding window, choice- │ "Extract & load │ |
│ │ their assets" │ aware preloading) │ beat assets" │ |
│ │ │ │ │ |
│ └─────────┬─────────┘ └─────────┬───────┘ |
│ │ │ |
│ │ uses for beat loading │ delegates |
│ │ │ |
│ delegates │ ▼ |
│ beat | ┌───────────────────┬────────────────┐ |
│ loading │ │ ImageLoading │ AudioManager │ |
│ │ │ Service │ │ |
┌─────────▼─────────────▼──────────────────┐ │ │ │◀--┘
│ BeatStorageCoordinator │ │ • preloadImages │ • preloadSounds│
│ "How to get beats efficiently" │ │ • cancelLoading │ • cancelSounds │
│ • getBeat() / loadBeat() │ └───────────────────┴────────────────┘
│ • storeBeat() │
│ • clearCache() │
└─────────┬────────────────────────────────┘

│ orchestrates hierarchy access


┌─────────▼───────────────────────────────────────────────────────┐
│ Storage Vehicle Layer │
├─────────────────┬─────────────────┬─────────────────────────────┤
│ InMemoryBeat │ BeatDiskCache │ BeatRepository │
│ Cache │ Adapter │ (Network) │
│ (Actor) │ (Actor) │ (Firestore) │
│ │ │ │
│ • get() │ • get() │ • fetchBeat() │
│ • store() │ • store() │ • fetchInitialBeats() │
│ • clear() │ • clear() │ • Firestore integration │
│ • evict() │ • cleanup() │ │
└─────────────────┴─────────────────┴─────────────────────────────┘

Core Design Principles

Actor-Based Concurrency

All storage components use Swift actors for thread safety, eliminating data races and ensuring reliable concurrent access patterns.

Layered Caching Strategy

Three-tier hierarchy optimizes for different access patterns:

  • Memory Cache: Instant access (0ms latency) via InMemoryBeatCache
  • Disk Cache: Fast persistent storage (1-5ms latency) via BeatDiskCacheAdapter
  • Network: Authoritative source (50-500ms latency) via PajamaScriptBeatRepository

Intelligent Preloading

Sliding window algorithm preloads content based on reading position and story structure, including choice-aware prediction for branching narratives.

Clean Architecture

Clear separation between orchestration (StoryContentManager), coordination (BeatStorageCoordinator), specialized caching (InMemoryBeatCache, BeatDiskCacheAdapter), and preloading intelligence (BeatPreloader, AssetPreloader).

Component Responsibilities

StoryContentManager

Location: Pajama/Features/StoryReader/StoryContentManager.swift

Central orchestrator implementing the StoryBeatManaging protocol. Runs on @MainActor for UI integration and manages the complete beat lifecycle.

Key Interfaces:

  • getBeat(_:) - Synchronous cache-only access
  • loadBeat(_:) - Async loading with full hierarchy check
  • beginLoadingContent() - Initiates content loading for specific beats
  • beginLoadingAllStoryContent() - Bulk loading for story initialization
  • Event publishing via storyBeatLoadedPublisher

Integration Points:

  • Coordinates with BeatStorageCoordinator for beat data
  • Manages BeatPreloader for intelligent preloading
  • Coordinates AssetPreloader for asset management
  • Publishes loading events for UI reactivity

BeatStorageCoordinator

Location: Pajama/Features/StoryReader/Storage/Coordination/BeatStorageCoordinator.swift

Actor-based storage coordinator that implements the complete Memory→Disk→Network hierarchy. Serves as the single source of truth for beat data access.

Storage Hierarchy:

  • First: InMemoryBeatCache - 50MB/100 beats with LRU eviction
  • Second: BeatDiskCacheAdapter - Persistent storage with corruption detection
  • Third: PajamaScriptBeatRepository - Firestore network access

Key Capabilities:

  • Task deduplication to prevent redundant network calls
  • Detailed performance metrics and cache hit tracking
  • Runtime cache configuration (enable/disable layers)
  • Automatic promotion from disk to memory
  • Comprehensive error handling and recovery

BeatPreloader

Location: Pajama/Features/StoryReader/Storage/Coordination/BeatPreloader.swift

Intelligent preloading system using sliding window strategy to optimize reading experience.

Preloading Strategy:

  • Configurable window size (default: 2 before, 5 after current position)
  • Choice-aware preloading for story branching via choiceTargets
  • Concurrent loading limits (max 3) to avoid resource exhaustion
  • Task deduplication with automatic cancellation

Window Management:

  • Updates automatically as user progresses through story
  • Analyzes story structure for predictive loading
  • Coordinates with AssetPreloader for holistic preloading

AssetPreloader

Location: Pajama/Features/StoryReader/Storage/Coordination/AssetPreloader.swift

Asset coordination system that extracts and preloads images, audio, and other assets from story beats.

Coordination Responsibilities:

  • Extracts asset URLs from beat content using regex patterns
  • Coordinates image preloading via ImageLoadingServiceProtocol
  • Manages audio asset coordination with AudioManager
  • Priority-based loading for current story position
  • Cancellation support for memory management

Storage Vehicle Layer

InMemoryBeatCache

Location: Pajama/Features/StoryReader/Storage/Cache/InMemoryBeatCache.swift

Actor-based in-memory cache with sophisticated eviction and memory management.

Features:

  • LRU eviction policy with 50MB size limit and 100 beat count limit
  • Memory pressure handling with automatic cleanup
  • Size estimation for beats including metadata
  • Thread-safe operations through actor isolation

BeatDiskCacheAdapter

Location: Pajama/Features/StoryReader/Storage/Cache/BeatDiskCacheAdapter.swift

Actor-based adapter wrapping TerryKit's DiskCache for persistent storage.

Features:

  • Async operations with proper error handling
  • Metadata validation and corruption detection
  • Prefix-based cache clearing for story-specific cleanup
  • Integration with unified cache management

PajamaScriptBeatRepository

Location: Pajama/Core/Storage/Firestore/Repositories/PajamaScriptBeatRepository.swift

Firestore integration layer handling all network operations for story beats.

Network Operations:

  • Single beat fetching with direct document access
  • Concurrent batch loading using TaskGroup
  • Intelligent initial story loading with configurable depth
  • Choice target prefetching for story branching

File Organization

The storage system is organized under Features/StoryReader/Storage/ with logical subdirectories:

Cache/

Memory and disk cache implementations:

  • InMemoryBeatCache.swift - Actor-based memory storage with LRU eviction
  • BeatDiskCacheAdapter.swift - Persistent storage adapter

Coordination/

Orchestration and preloading logic:

  • BeatStorageCoordinator.swift - Main storage coordination (Memory→Disk→Network)
  • BeatPreloader.swift - Intelligent sliding window preloading
  • AssetPreloader.swift - Asset coordination for beats

Root Storage/

Core interfaces:

  • StoryBeatManagingProtocols.swift - Protocol definitions
  • BeatStorageError.swift - Error types and handling

Data Flow Patterns

Primary User Flow

User Tap/Swipe → StoryPageController → StoryContentManager → 
BeatStorageCoordinator → [Memory → Disk → Network] → Content Display

Background Intelligence Flow

Current Position Update → BeatPreloader → Sliding Window Calculation → 
Concurrent Preloading Tasks → BeatStorageCoordinator → Storage Population

Asset Loading Flow

The system uses different patterns for beat vs. asset loading due to their different access characteristics:

Immediate Asset Loading (UI-driven)

UI Component (CachedAsyncImage) → ImageLoadingService → 
Check Cache → Load if Needed → Display

Predictive Asset Preloading

BeatPreloader → AssetPreloader → Asset Extraction → 
[ImageLoadingService + AudioManager] → Bulk Preload

Why Different Patterns for Beats vs. Assets?

Beats require explicit availability checking:

  • Navigation depends on beat availability
  • UI needs synchronous getBeat() to decide whether to show content or loading state
  • One beat at a time access pattern

Assets work well with async loading:

  • Images are visual enhancements that can load progressively
  • CachedAsyncImage handles the check-and-load pattern internally
  • Many images per page access pattern
  • Standard iOS async image loading patterns

Performance Characteristics

Loading Performance

  • Critical Path: Beat loading through BeatStorageCoordinator blocks navigation
  • Background: Asset loading via AssetPreloader is non-blocking
  • Preloading: BeatPreloader reduces network requests by 60-80%

Memory Management

  • Automatic cache eviction under memory pressure in InMemoryBeatCache
  • Task deduplication prevents redundant network calls in BeatStorageCoordinator
  • Concurrent loading limits (max 3) in BeatPreloader

Integration Architecture

App-Level Integration

The system integrates at the AppServices level with shared cache instances used across all story reading experiences.

Story Reader Integration

StoryReaderViewModel creates StoryContentManager instances that coordinate beat loading with asset preloading, ensuring smooth user experience.

Co-Reading Integration

Shared storage ensures consistent state across solo and collaborative reading modes, with the same caching benefits for all participants.

Error Handling Strategy

Error Classification

Location: Pajama/Features/StoryReader/Storage/BeatStorageError.swift

Comprehensive error types covering disk failures, network issues, corruption detection, and memory pressure scenarios.

Recovery Strategies

  • Retry from Network: For transient failures
  • Cache Layer Fallback: Memory→Disk→Network graceful degradation
  • Corrupted Data Cleanup: Automatic bad data removal
  • Task Deduplication: Prevents error amplification

Configuration & Monitoring

Runtime Configuration

  • Memory/disk cache enable/disable controls in BeatStorageCoordinator
  • Cache size limits and eviction policies in InMemoryBeatCache
  • Preloading window size adjustment in BeatPreloader

Performance Monitoring

  • Real-time cache hit rate tracking in BeatStorageCoordinator
  • Detailed loading metrics and timing information
  • Task deduplication statistics and concurrent operation tracking

Best Practices

Memory Management

  • Always use weak references in async Task closures
  • Leverage actor isolation for thread safety
  • Implement proper cleanup in deinit methods
  • Respect system memory pressure warnings

Error Handling

  • Provide comprehensive error classification
  • Implement meaningful recovery strategies
  • Log errors with sufficient context for debugging
  • Design for graceful degradation scenarios

Performance Optimization

  • Use TaskGroup for concurrent network operations
  • Implement task deduplication to avoid redundant work
  • Monitor cache hit rates for optimization opportunities
  • Balance preloading aggressiveness with resource usage

Conclusion

The beat loading system provides a robust, performant foundation for story content delivery in Pajama. The architecture features a clean separation of concerns with:

  • Single orchestrator (StoryContentManager) for UI interaction
  • Dedicated coordination (BeatStorageCoordinator) for storage hierarchy management
  • Intelligent preloading (BeatPreloader, AssetPreloader) for performance optimization
  • Actor-based thread safety throughout the storage layer
  • Clear file organization under the StoryReader feature module

This architecture balances performance, reliability, and resource efficiency while maintaining flexibility for various reading scenarios and providing comprehensive monitoring and configuration capabilities for both development and production needs.