# Record Daemon Architecture ## Overview The record-daemon is a high-performance Rust daemon that automatically records League of Legends matches using libobs, captures game events via the League Client API (LQP), and exposes configuration via a Unix socket IPC for the Tauri app. ## High-Level Architecture ```mermaid flowchart TB subgraph Daemon[Record Daemon] direction TB Main[Main Event Loop] State[State Machine] subgraph Core[Core Modules] Config[Configuration Manager] LQP[LQP Client] OBS[libobs Recording Engine] IPC[IPC Server] Timeline[Event Timeline Store] end end subgraph External[External Systems] LC[League Client] Game[League of Legends Game] Tauri[Tauri App] FS[File System] end LQP -->|WebSocket| LC OBS -->|Game Capture| Game IPC -->|Unix Socket| Tauri Config -->|JSON/TOML| FS Timeline -->|SQLite/JSON| FS OBS -->|Video Files| FS Main --> State State --> Config State --> LQP State --> OBS State --> IPC State --> Timeline ``` ## Module Structure ``` record-daemon/ ├── Cargo.toml ├── src/ │ ├── main.rs # Entry point, daemon setup │ ├── lib.rs # Library exports │ ├── config/ │ │ ├── mod.rs # Config module exports │ │ ├── settings.rs # User-configurable settings │ │ ├── presets.rs # Encoding presets (quality/performance) │ │ └── persistence.rs # Config file I/O │ ├── lqp/ │ │ ├── mod.rs # LQP module exports │ │ ├── client.rs # LQP WebSocket client │ │ ├── events.rs # Game event types and parsing │ │ └── auth.rs # LQP authentication (port/password from lockfile) │ ├── recording/ │ │ ├── mod.rs # Recording module exports │ │ ├── obs_context.rs # libobs initialization and context │ │ ├── capture.rs # Game capture source setup │ │ ├── encoder.rs # Video encoder configuration │ │ └── output.rs # File output configuration │ ├── ipc/ │ │ ├── mod.rs # IPC module exports │ │ ├── server.rs # Unix socket server │ │ ├── protocol.rs # Request/response protocol │ │ └── handlers.rs # Command handlers │ ├── timeline/ │ │ ├── mod.rs # Timeline module exports │ │ ├── store.rs # Event storage backend │ │ └── mapper.rs # Event-to-video timestamp mapping │ ├── state/ │ │ ├── mod.rs # State module exports │ │ ├── machine.rs # Daemon state machine │ │ └── transitions.rs # State transition logic │ └── error.rs # Error types and handling └── tests/ └── ... ``` ## State Machine The daemon operates as a finite state machine with the following states: ```mermaid stateDiagram-v2 [*] --> Idle: Daemon Start Idle --> Monitoring: League Client Detected Monitoring --> Idle: League Client Closed Monitoring --> Recording: Match Started Recording --> Monitoring: Match Ended Recording --> Recording: Event Received Idle --> Error: Fatal Error Monitoring --> Error: Fatal Error Recording --> Error: Fatal Error Error --> Idle: Recovery ``` ### State Descriptions | State | Description | |-------|-------------| | **Idle** | Daemon is running but League Client is not detected | | **Monitoring** | League Client is running, waiting for match to start | | **Recording** | Active recording in progress, capturing events | | **Error** | Recoverable error state, attempting recovery | ## Data Flow ### Match Recording Flow ```mermaid sequenceDiagram participant D as Daemon participant LC as League Client participant OBS as libobs participant TL as Timeline participant FS as FileSystem Note over D: State: Monitoring LC->>D: MatchFound event via LQP D->>OBS: Initialize recording context D->>FS: Create output file Note over D: State: Recording LC->>D: GameStart event D->>OBS: Start recording D->>TL: Record start timestamp loop Game Events LC->>D: Kill/Death/Objective events D->>TL: Store event with timestamp end LC->>D: GameEnd event D->>OBS: Stop recording D->>TL: Finalize timeline D->>FS: Save timeline metadata Note over D: State: Monitoring ``` ### IPC Communication Flow ```mermaid sequenceDiagram participant T as Tauri App participant IPC as IPC Server participant D as Daemon Core participant C as Config Manager T->>IPC: Connect via Unix Socket T->>IPC: GetSettings request IPC->>D: Handle request D->>C: Read current settings C-->>D: Settings data D-->>IPC: Settings response IPC-->>T: JSON response T->>IPC: UpdateSettings request IPC->>D: Handle request D->>C: Validate and save C-->>D: Success D-->>IPC: Confirmation IPC-->>T: Success response ``` ## Key Components ### 1. Configuration Module Handles user-configurable settings with hot-reload support. ```rust // Key configuration structures struct Settings { output_path: PathBuf, encoder_preset: EncoderPreset, frame_rate: u32, quality: QualityLevel, audio_capture: AudioSettings, } enum EncoderPreset { Nvenc { bitrate: u32, cq_level: u32 }, Amf { bitrate: u32 }, X264 { preset: String, bitrate: u32 }, } enum QualityLevel { Low, // 720p30, lower bitrate Medium, // 1080p30, moderate bitrate High, // 1080p60, high bitrate Ultra, // 1440p60, max quality } ``` ### 2. LQP Client Connects to the League Client via WebSocket using credentials from the lockfile. ```rust struct LqpClient { port: u16, password: String, websocket: Option, } // Key events to capture enum GameEvent { MatchFound(MatchInfo), GameStart(GameStartInfo), Kill(KillEvent), Death(DeathEvent), Objective(ObjectiveEvent), GameEnd(GameEndInfo), } ``` ### 3. libobs Recording Engine Manages OBS context, capture sources, and encoding. ```rust struct ObsRecordingEngine { context: ObsContext, video_encoder: Box, audio_encoder: Box, output: Box, active: bool, } impl ObsRecordingEngine { fn initialize(config: &Settings) -> Result; fn start_recording(&mut self, output_path: &Path) -> Result<()>; fn stop_recording(&mut self) -> Result; fn get_current_timestamp(&self) -> Duration; } ``` ### 4. IPC Server Unix socket server for communication with Tauri app. ```rust struct IpcServer { socket_path: PathBuf, listener: UnixListener, handlers: HashMap, } enum IpcCommand { GetSettings, UpdateSettings, GetStatus, GetRecordings, GetTimeline { recording_id: Uuid }, StartRecording, StopRecording, } ``` ### 5. Event Timeline Store Stores game events mapped to video timestamps. ```rust struct TimelineStore { backend: StorageBackend, } struct RecordingMetadata { id: Uuid, game_id: u64, start_time: DateTime, end_time: Option>, output_file: PathBuf, events: Vec, } struct TimestampedEvent { video_timestamp: Duration, // Offset from recording start game_timestamp: Duration, // Game time event: GameEvent, } ``` ## Performance Considerations ### Memory Management - Use `Arc>` for shared state between modules - Pre-allocate buffers for video encoding - Use `crossbeam` channels for inter-thread communication - Implement backpressure on event channels ### CPU Efficiency - Run libobs on dedicated thread - Use async/await with `tokio` for I/O operations - Minimize allocations in hot paths - Use `parking_lot` locks for better performance ### GPU Acceleration - Prioritize NVENC/AMF hardware encoding - Fall back to software encoding only if unavailable - Configure GPU-based video capture ### Disk I/O - Use buffered writes for video output - Store timeline data in memory during recording - Flush to disk only on recording end - Consider SSD for output directory ## Dependencies ```toml [dependencies] # Async runtime tokio = { version = "1", features = ["full"] } # Serialization serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.8" # libobs bindings libobs-rs = "0.1" # or appropriate version # WebSocket for LQP tokio-tungstenite = "0.21" # HTTP client for LQP REST API reqwest = { version = "0.11", features = ["json", "rustls-tls"] } # Async utilities futures = "0.3" async-trait = "0.1" # Error handling thiserror = "1" anyhow = "1" # Logging tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } # UUID for recording IDs uuid = { version = "1", features = ["v4", "serde"] } # Date/time chrono = { version = "0.4", features = ["serde"] } # Locking primitives parking_lot = "0.12" # Crossbeam channels crossbeam = "0.8" # Unix socket IPC tokio-util = { version = "0.7", features = ["codec"] } # Configuration directories directories = "5" # CLI (optional, for debugging) clap = { version = "4", features = ["derive"] } ``` ## IPC Protocol Specification ### Message Format All messages are JSON-encoded with the following structure: ```json { "type": "request|response|notification", "id": "uuid-v4", "command": "CommandName", "payload": { ... } } ``` ### Commands | Command | Direction | Payload | Description | |---------|-----------|---------|-------------| | `GetSettings` | Request → | `{}` | Get current settings | | `GetSettings` | ← Response | `Settings` | Current settings object | | `UpdateSettings` | Request → | `Settings` | Update settings | | `UpdateSettings` | ← Response | `{ "success": true }` | Confirmation | | `GetStatus` | Request → | `{}` | Get daemon status | | `GetStatus` | ← Response | `DaemonStatus` | Current state and info | | `GetRecordings` | Request → | `{}` | List all recordings | | `GetRecordings` | ← Response | `[RecordingMetadata]` | Recording list | | `GetTimeline` | Request → | `{ "recording_id": "uuid" }` | Get specific timeline | | `GetTimeline` | ← Response | `Timeline` | Event timeline | | `RecordingStarted` | ← Notification | `RecordingInfo` | Recording started | | `RecordingStopped` | ← Notification | `RecordingResult` | Recording ended | | `GameEvent` | ← Notification | `GameEvent` | Live game event | ## Error Handling Strategy ```rust #[derive(Debug, thiserror::Error)] pub enum DaemonError { #[error("LQP connection failed: {0}")] LqpConnection(#[from] LqpError), #[error("Recording error: {0}")] Recording(#[from] RecordingError), #[error("Configuration error: {0}")] Config(#[from] ConfigError), #[error("IPC error: {0}")] Ipc(#[from] IpcError), #[error("IO error: {0}")] Io(#[from] std::io::Error), } ``` ## Next Steps 1. Set up `Cargo.toml` with all required dependencies 2. Implement configuration module first (needed by all other modules) 3. Implement LQP client for game detection and event capture 4. Implement libobs recording engine 5. Implement IPC server 6. Implement timeline storage 7. Wire everything together in the main daemon loop 8. Add comprehensive logging and error handling 9. Write tests and documentation