roplat 0.2.0

roplat: just a robot operation system
Documentation
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Instant;

use serde::{Deserialize, Serialize};

use super::frame::{FrameRecord, RhythmMeta};

// ============================================================================
// RLOG 文件头
// ============================================================================

const RLOG_MAGIC: &[u8; 4] = b"RLOG";
const RLOG_VERSION: u16 = 2;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RlogHeader {
    magic: [u8; 4],
    version: u16,
    rhythm_count: u16,
    rhythms: Vec<RhythmMeta>,
}

// ============================================================================
// FrameWriter — 帧写入器(线程安全,Arc 共享)
// ============================================================================

/// 帧写入器,在 RecordingRhythm 之间共享。
///
/// 每帧序列化为一行 JSON 追加到文件。
/// 崩溃安全:丢失最后未 flush 的帧,不损坏已有数据。
pub struct FrameWriter {
    writer: Mutex<BufWriter<File>>,
    start: Instant,
}

impl FrameWriter {
    /// 打开或创建 .rlog 文件,写入头部
    pub fn create(path: impl AsRef<Path>, rhythms: &[RhythmMeta]) -> io::Result<Arc<Self>> {
        let file = File::create(path)?;
        let mut writer = BufWriter::new(file);

        let header = RlogHeader {
            magic: *RLOG_MAGIC,
            version: RLOG_VERSION,
            rhythm_count: rhythms.len() as u16,
            rhythms: rhythms.to_vec(),
        };

        let header_json = serde_json::to_string(&header)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
        writeln!(writer, "{}", header_json)?;
        writer.flush()?;

        Ok(Arc::new(Self {
            writer: Mutex::new(writer),
            start: Instant::now(),
        }))
    }

    /// 距录制开始经过的纳秒数
    pub fn elapsed_ns(&self) -> u64 {
        self.start.elapsed().as_nanos() as u64
    }

    /// 写入一帧记录(线程安全)
    pub fn write_frame(&self, frame: &FrameRecord) {
        let json = serde_json::to_string(frame).expect("FrameRecord serialize failed");
        let mut w = self.writer.lock().expect("FrameWriter lock poisoned");
        let _ = writeln!(w, "{}", json);
        // 不在每帧 flush —— 由 OS 页缓存异步写入
    }

    /// 显式 flush,用于录制结束
    pub fn flush(&self) {
        let mut w = self.writer.lock().expect("FrameWriter lock poisoned");
        let _ = w.flush();
    }
}

impl Drop for FrameWriter {
    fn drop(&mut self) {
        self.flush();
    }
}

// ============================================================================
// RlogWriter — 高层录制会话管理
// ============================================================================

/// 录制会话:创建 FrameWriter 并管理节律元信息
pub struct RlogWriter {
    writer: Arc<FrameWriter>,
}

impl RlogWriter {
    /// 创建录制会话并写入头部。
    pub fn create(path: impl AsRef<Path>, rhythms: &[RhythmMeta]) -> io::Result<Self> {
        let writer = FrameWriter::create(path, rhythms)?;
        Ok(Self { writer })
    }

    /// 获取底层帧写入器共享句柄。
    pub fn writer(&self) -> Arc<FrameWriter> {
        self.writer.clone()
    }

    /// 立即 flush 到磁盘。
    pub fn flush(&self) {
        self.writer.flush();
    }
}

// ============================================================================
// RlogReader — 读取 .rlog 文件
// ============================================================================

/// .rlog 文件读取器
pub struct RlogReader {
    /// 日志头。
    pub header: RlogHeader,
    /// 全部帧记录。
    pub frames: Vec<FrameRecord>,
}

impl RlogReader {
    /// 读取并解析整个 .rlog 文件
    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
        let file = File::open(path)?;
        let reader = BufReader::new(file);
        let mut lines = reader.lines();

        // 第一行是 header
        let header_line = lines
            .next()
            .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "empty rlog file"))??;
        let header: RlogHeader = serde_json::from_str(&header_line)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

        // 校验 magic
        if header.magic != *RLOG_MAGIC {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "invalid rlog magic bytes",
            ));
        }
        if header.version != RLOG_VERSION {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "unsupported rlog version: expected {}, got {}",
                    RLOG_VERSION, header.version
                ),
            ));
        }

        // 读取所有帧
        let mut frames = Vec::new();
        for line in lines {
            let line = line?;
            if line.is_empty() {
                continue;
            }
            let frame: FrameRecord = serde_json::from_str(&line)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            frames.push(frame);
        }

        Ok(Self { header, frames })
    }

    /// 获取指定节律域的帧序列
    pub fn frames_for(&self, rhythm_id: u16) -> Vec<FrameRecord> {
        self.frames
            .iter()
            .filter(|f| f.rhythm_id == rhythm_id)
            .cloned()
            .collect()
    }

    /// 获取节律域元信息
    pub fn rhythm_meta(&self, rhythm_id: u16) -> Option<&RhythmMeta> {
        self.header
            .rhythms
            .iter()
            .find(|m| m.rhythm_id == rhythm_id)
    }

    /// 所有帧按全局序号排序
    pub fn frames_sorted(&self) -> Vec<&FrameRecord> {
        let mut refs: Vec<&FrameRecord> = self.frames.iter().collect();
        refs.sort_by_key(|f| f.global_seq);
        refs
    }

    /// 帧总数
    pub fn frame_count(&self) -> usize {
        self.frames.len()
    }
}