112 lines
3.9 KiB
Rust
112 lines
3.9 KiB
Rust
use std::{
|
|
collections::VecDeque, convert::Infallible, fs::{create_dir_all, File}, io::{self, Write}, path::Path, ptr::write_bytes, sync::Arc, time::Duration
|
|
};
|
|
|
|
use chrono::Local;
|
|
use debug_print::{debug_eprintln, debug_println};
|
|
use flurry::{
|
|
config::{GRID_LENGTH, HOST, IMAGE_SAVE_INTERVAL},
|
|
flutclient::FlutClient,
|
|
grid::{self, Flut},
|
|
COUNTER,
|
|
};
|
|
use image::{codecs::jpeg::JpegEncoder, GenericImageView, SubImage};
|
|
use tokio::{
|
|
net::TcpListener,
|
|
time::interval
|
|
};
|
|
type Never = Infallible;
|
|
|
|
/// This function logs the current amount of changed pixels to stdout every second
|
|
async fn pixel_change_stdout_log() -> io::Result<Never> {
|
|
let mut interval = tokio::time::interval(Duration::from_millis(1000));
|
|
loop {
|
|
interval.tick().await;
|
|
let cnt = COUNTER.load(std::sync::atomic::Ordering::Relaxed);
|
|
println!("{cnt} pixels were changed");
|
|
}
|
|
}
|
|
|
|
/// This function starts a timer that saves the current grid state every `duration`.
|
|
/// These images may then be used for moderation or timelapses
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function will return an error if it is unable to create or write to the file for the image
|
|
async fn save_image_frames(grids: Arc<[grid::Flut<u32>]>, duration: Duration) -> io::Result<Never> {
|
|
let base_dir = Path::new("./recordings");
|
|
let mut timer = interval(duration);
|
|
create_dir_all(base_dir)?;
|
|
loop {
|
|
timer.tick().await;
|
|
for grid in grids.as_ref() {
|
|
let p = base_dir.join(format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S")));
|
|
debug_println!("timer ticked, grid writing to {:?}", p);
|
|
let mut file_writer = File::create(p)?;
|
|
|
|
let encoder = JpegEncoder::new_with_quality(&mut file_writer, 50);
|
|
grid.view(0, 0, grid.width(), grid.height()).to_image();
|
|
|
|
let sub_image = SubImage::new(grid, 0, 0, grid.width(), grid.height());
|
|
let image = sub_image.to_image();
|
|
match image.write_with_encoder(encoder) {
|
|
Ok(_) => {}
|
|
Err(err) => eprintln!("{}", err),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle connections made to the socket, keeps a vec of the currently active connections,
|
|
/// uses timeout to loop through them and clean them up to stop a memory leak while not throwing
|
|
/// everything away
|
|
async fn handle_flut(flut_listener: TcpListener, grids: Arc<[grid::Flut<u32>]>) -> io::Result<Never> {
|
|
let mut handles = Vec::new();
|
|
loop {
|
|
let (mut socket, _) = flut_listener.accept().await?;
|
|
let grids = grids.clone();
|
|
handles.push(tokio::spawn(async move {
|
|
let (reader, writer) = socket.split();
|
|
let mut connection = FlutClient::new(reader, writer, grids);
|
|
let resp = connection.process_socket().await;
|
|
match resp {
|
|
Ok(()) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
|
|
async fn jpeg_update_loop(grids: Arc<[Flut<u32>]>) -> io::Result<Never> {
|
|
let mut interval = interval(Duration::from_millis(20));
|
|
loop {
|
|
interval.tick().await;
|
|
for grid in grids.as_ref() {
|
|
grid.update_jpg_buffer().await;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
#[allow(clippy::needless_return)]
|
|
async fn main() {
|
|
let grids: Arc<[Flut<u32>; GRID_LENGTH]> = [grid::Flut::init(800, 600, 0xff_00_ff_ff)].into();
|
|
println!("created grids");
|
|
|
|
let Ok(flut_listener) = TcpListener::bind(HOST).await else {
|
|
eprintln!("Was unable to bind to {HOST}, please check if a different process is bound");
|
|
return;
|
|
};
|
|
println!("bound flut listener");
|
|
|
|
let handles = vec![
|
|
(tokio::spawn(pixel_change_stdout_log())),
|
|
(tokio::spawn(save_image_frames(grids.clone(), IMAGE_SAVE_INTERVAL))),
|
|
(tokio::spawn(handle_flut(flut_listener, grids.clone()))),
|
|
(tokio::spawn(jpeg_update_loop(grids.clone())))
|
|
];
|
|
|
|
for handle in handles {
|
|
println!("joined handle had result {:?}", handle.await);
|
|
}
|
|
}
|