perf: eliminate mock capture allocation churn, add idle bypass, and deduplicate filenames

This commit is contained in:
katR 2026-09-10 22:46:19 +05:30
parent b7a0257b03
commit 22e3dbda4c
4 changed files with 94 additions and 16 deletions

View file

@ -25,9 +25,22 @@ ln -sf "$BIN_DIR/scythe-daemon" "$BIN_DIR/vrec-daemon"
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec-ui"
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec"
if pgrep -x scythe-daemon > /dev/null; then
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
if [ -f "$SYSTEMD_USER_DIR/vrec-daemon.service" ] || systemctl --user is-active --quiet vrec-daemon.service 2>/dev/null; then
echo "Migrating legacy vrec-daemon systemd service to scythe-daemon.service..."
systemctl --user stop vrec-daemon.service 2>/dev/null || true
systemctl --user disable vrec-daemon.service 2>/dev/null || true
rm -f "$SYSTEMD_USER_DIR/vrec-daemon.service"
rm -f "$SYSTEMD_USER_DIR/default.target.wants/vrec-daemon.service"
fi
if systemctl --user is-active --quiet scythe-daemon.service 2>/dev/null; then
echo "Restarting active scythe-daemon systemd service..."
systemctl --user restart scythe-daemon.service
elif pgrep -x scythe-daemon > /dev/null || pgrep -x vrec-daemon > /dev/null; then
echo "Restarting active scythe-daemon engine..."
pkill -x scythe-daemon || true
pkill -x scythe-daemon 2>/dev/null || true
pkill -x vrec-daemon 2>/dev/null || true
sleep 0.5
nohup "$BIN_DIR/scythe-daemon" > /dev/null 2>&1 &
disown

View file

@ -488,6 +488,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut ticker = crossbeam_channel::tick(std::time::Duration::from_nanos(1_000_000_000 / config.fps.max(1) as u64));
let mut latest_frame: Option<Frame> = None;
let mut has_new_frame = false;
let mut last_encoded_raw_ptr: usize = 0;
loop {
crossbeam_channel::select! {
@ -671,24 +672,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
recv(ticker) -> _ => {
while ticker.try_recv().is_ok() {}
if !normal_recording && !config.replay_enabled {
has_new_frame = false;
continue;
}
let now = std::time::Instant::now();
let elapsed = now.duration_since(stream_start);
let raw_pts = (elapsed.as_secs_f64() * config.fps as f64).round() as i64;
let pts = if raw_pts > last_video_pts { raw_pts } else { last_video_pts + 1 };
last_video_pts = pts;
let packets_res = if has_new_frame {
let is_same_raw_frame = match &latest_frame {
Some(Frame::Raw { data, .. }) => {
let ptr = Arc::as_ptr(data) as usize;
ptr != 0 && ptr == last_encoded_raw_ptr
}
_ => false,
};
let packets_res = if has_new_frame && !is_same_raw_frame {
has_new_frame = false;
if let Some(ref f) = latest_frame {
if let Frame::Raw { data, .. } = f {
last_encoded_raw_ptr = Arc::as_ptr(data) as usize;
}
encoder.encode_frame(f, pts)
} else {
Ok(Vec::new())
}
} else {
has_new_frame = false;
match encoder.encode_cached_frame(pts) {
Ok(pkts) => Ok(pkts),
Err(_) => {
if let Some(ref f) = latest_frame {
if let Frame::Raw { data, .. } = f {
last_encoded_raw_ptr = Arc::as_ptr(data) as usize;
}
encoder.encode_frame(f, pts)
} else {
Ok(Vec::new())

View file

@ -1,15 +1,32 @@
use super::{Frame, FrameSource};
use std::time::Duration;
use std::sync::Arc;
pub struct MockCapture {
width: u32,
height: u32,
frame_count: u64,
static_frame: Arc<Vec<u8>>,
}
impl MockCapture {
pub fn new() -> Self {
Self { width: 1920, height: 1080, frame_count: 0 }
let width = 1920;
let height = 1080;
let mut data = vec![0u8; (width * height * 4) as usize];
for chunk in data.chunks_exact_mut(4) {
chunk[0] = 64; // B
chunk[1] = 64; // G
chunk[2] = 64; // R
chunk[3] = 255; // A
}
Self {
width,
height,
frame_count: 0,
static_frame: Arc::new(data),
}
}
}
@ -22,24 +39,13 @@ impl Default for MockCapture {
impl FrameSource for MockCapture {
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>> {
std::thread::sleep(Duration::from_millis(16)); // ~60fps
let mut data = vec![0u8; (self.width * self.height * 4) as usize];
// Fill with a changing color
let color = (self.frame_count % 255) as u8;
for chunk in data.chunks_exact_mut(4) {
chunk[0] = color; // B
chunk[1] = color; // G
chunk[2] = 255; // R
chunk[3] = 255; // A
}
self.frame_count += 1;
Ok(Frame::Raw {
width: self.width,
height: self.height,
stride: self.width * 4,
data: std::sync::Arc::new(data),
data: Arc::clone(&self.static_frame),
})
}
}

View file

@ -147,6 +147,22 @@ impl ScytheConfig {
let mut p = Self::expand_tilde(&self.output_directory);
let _ = fs::create_dir_all(&p);
p.push(filename);
if !p.exists() {
return p.to_string_lossy().to_string();
}
let stem = p.file_stem().and_then(|s| s.to_str()).unwrap_or("recording");
let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("mp4");
let parent = p.parent().unwrap_or_else(|| std::path::Path::new("."));
for counter in 1..10000 {
let candidate = parent.join(format!("{}_{}.{}", stem, counter, ext));
if !candidate.exists() {
return candidate.to_string_lossy().to_string();
}
}
p.to_string_lossy().to_string()
}
@ -361,4 +377,26 @@ mod tests {
assert!(part.chars().all(|c| c.is_ascii_digit()));
}
}
#[test]
fn test_resolve_save_path_collision() {
let temp_dir = std::env::temp_dir().join(format!("scythe_test_collision_{}", std::process::id()));
let cfg = ScytheConfig {
output_directory: temp_dir.to_string_lossy().to_string(),
..Default::default()
};
let path1 = cfg.resolve_save_path("test_clip.mp4");
assert!(path1.ends_with("test_clip.mp4"));
fs::write(&path1, b"dummy").unwrap();
let path2 = cfg.resolve_save_path("test_clip.mp4");
assert!(path2.ends_with("test_clip_1.mp4"), "Expected collision to append _1: {}", path2);
fs::write(&path2, b"dummy2").unwrap();
let path3 = cfg.resolve_save_path("test_clip.mp4");
assert!(path3.ends_with("test_clip_2.mp4"), "Expected collision to append _2: {}", path3);
let _ = fs::remove_dir_all(temp_dir);
}
}