feat: Website (#15)

Add a basic website that shows the current flutgrid state
Co-authored-by: Noa Aarts <noa@voorwaarts.nl>
This commit is contained in:
peppidesu 2024-10-22 21:58:25 +02:00 committed by GitHub
parent 6c9de45c6a
commit 7f04b39a15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1095 additions and 75 deletions

View file

@ -1,8 +1,8 @@
use std::{
convert::Infallible,
fs::{create_dir_all, File},
io::{self, Write as _},
io::Write as _,
path::Path,
process::exit,
sync::Arc,
time::Duration,
};
@ -12,21 +12,20 @@ use flurry::{
config::{GRID_LENGTH, HOST, IMAGE_SAVE_INTERVAL, JPEG_UPDATE_INTERVAL},
flutclient::FlutClient,
grid::{self, Flut},
COUNTER,
webapi::WebApiContext,
AsyncResult, COUNTER,
};
use tokio::{
net::TcpListener,
time::interval
};
type Never = Infallible;
use futures::never::Never;
use tokio::{net::TcpListener, time::interval, try_join};
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
/// This function logs the current amount of changed pixels to stdout every second
async fn pixel_change_stdout_log() -> io::Result<Never> {
async fn pixel_change_stdout_log() -> AsyncResult<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");
tracing::info!("{cnt} pixels changed");
}
}
@ -36,7 +35,10 @@ async fn pixel_change_stdout_log() -> io::Result<Never> {
/// # 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> {
async fn save_image_frames(
grids: Arc<[grid::Flut<u32>; GRID_LENGTH]>,
duration: Duration,
) -> AsyncResult<Never> {
let base_dir = Path::new("./recordings");
let mut timer = interval(duration);
create_dir_all(base_dir)?;
@ -46,7 +48,7 @@ async fn save_image_frames(grids: Arc<[grid::Flut<u32>]>, duration: Duration) ->
let p = base_dir.join(format!("{}", Local::now().format("%Y-%m-%d_%H-%M-%S.jpg")));
let mut file_writer = File::create(p)?;
file_writer.write_all(&grid.read_jpg_buffer().await)?;
file_writer.write_all(&grid.read_jpg_buffer())?;
}
}
}
@ -54,7 +56,10 @@ async fn save_image_frames(grids: Arc<[grid::Flut<u32>]>, duration: Duration) ->
/// 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> {
async fn handle_flut(
flut_listener: TcpListener,
grids: Arc<[grid::Flut<u32>]>,
) -> AsyncResult<Never> {
let mut handles = Vec::new();
loop {
let (mut socket, _) = flut_listener.accept().await?;
@ -71,12 +76,12 @@ async fn handle_flut(flut_listener: TcpListener, grids: Arc<[grid::Flut<u32>]>)
}
}
async fn jpeg_update_loop(grids: Arc<[Flut<u32>]>) -> io::Result<Never> {
async fn jpeg_update_loop(grids: Arc<[Flut<u32>]>) -> AsyncResult<Never> {
let mut interval = interval(JPEG_UPDATE_INTERVAL);
loop {
interval.tick().await;
for grid in grids.as_ref() {
grid.update_jpg_buffer().await;
grid.update_jpg_buffer();
}
}
}
@ -84,23 +89,41 @@ async fn jpeg_update_loop(grids: Arc<[Flut<u32>]>) -> io::Result<Never> {
#[tokio::main]
#[allow(clippy::needless_return)]
async fn main() {
// diagnostics
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
let grids: Arc<[Flut<u32>; GRID_LENGTH]> = [grid::Flut::init(800, 600, 0xff_00_ff_ff)].into();
println!("created grids");
tracing::trace!("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;
tracing::error!(
"Was unable to bind to {HOST}, please check if a different process is bound"
);
exit(1);
};
println!("bound flut listener");
tracing::info!("Started TCP listener on {HOST}");
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())))
];
let pixel_logger = tokio::spawn(pixel_change_stdout_log());
let snapshots = tokio::spawn(save_image_frames(grids.clone(), IMAGE_SAVE_INTERVAL));
let pixelflut_server = tokio::spawn(handle_flut(flut_listener, grids.clone()));
let jpeg_update_loop = tokio::spawn(jpeg_update_loop(grids.clone()));
let website = tokio::spawn(flurry::webapi::serve(WebApiContext {
grids: grids.clone(),
}));
for handle in handles {
println!("joined handle had result {:?}", handle.await);
}
let res = try_join! {
pixel_logger,
snapshots,
pixelflut_server,
jpeg_update_loop,
website,
};
tracing::error!("something went wrong {:?}", res);
}