updated segment interface

This commit is contained in:
Parker TenBroeck 2026-03-12 21:49:21 -04:00
parent 4773ab5e9e
commit 5746846896
9 changed files with 183 additions and 195 deletions

View file

@ -1,5 +1,8 @@
use std::{
collections::HashMap, ffi::OsStr, ops::Deref, path::{Path, PathBuf}
collections::HashMap,
ffi::OsStr,
ops::Deref,
path::{Path, PathBuf},
};
use tokio::process::{Child, Command};
@ -46,9 +49,7 @@ impl AsRef<Path> for TempDir {
}
}
pub async fn copy_and_build(
files: HashMap<String, String>,
) -> HResult<TempDir> {
pub async fn copy_and_build(files: HashMap<String, String>) -> HResult<TempDir> {
use std::hash::*;
let mut hasher = std::hash::DefaultHasher::default();
for (key, value) in &files {
@ -73,19 +74,18 @@ pub async fn copy_and_build(
Ok(work_dir)
}
pub async fn build(build: &Path, src: &Path) -> HResult<()>{
pub async fn build(build: &Path, src: &Path) -> HResult<()> {
std::fs::create_dir_all(build)?;
let embedded_lib_path = build.join("libvhdl_conn.a");
let embedded_tb_path = build.join("tb.vhdl");
std::fs::write(&embedded_lib_path, EMBEDDED_VHDL_UI_LIB)?;
std::fs::write(&embedded_tb_path, EMBEDDED_TB_VHDL)?;
let mut cmd = Command::new("ghdl");
let mut cmd = Command::new("ghdl");
cmd.kill_on_drop(true);
cmd.args(["-i", "-g", "--std=08"]);
for file in src.read_dir().unwrap().flatten(){
for file in src.read_dir().unwrap().flatten() {
if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) {
cmd.arg(file.path().canonicalize()?);
}
@ -99,7 +99,6 @@ pub async fn build(build: &Path, src: &Path) -> HResult<()>{
cmd.current_dir(build);
ensure_ok(cmd.spawn()?).await?;
let mut cmd = Command::new("ghdl");
cmd.kill_on_drop(true);
cmd.args(["-m", "--std=08"]);
@ -113,6 +112,6 @@ pub async fn build(build: &Path, src: &Path) -> HResult<()>{
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
ensure_ok(cmd.spawn()?).await?;
Ok(())
}

View file

@ -29,7 +29,10 @@ async fn serve_styles() -> impl IntoResponse {
async fn serve_app_js() -> impl IntoResponse {
(
[(header::CONTENT_TYPE, "application/javascript; charset=utf-8")],
[(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)],
UI_APP_JS,
)
}
@ -91,7 +94,8 @@ fn parse_config_from_args() -> Result<Config, String> {
}
"--help" | "-h" => {
return Err(
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]".into(),
"usage: relay [--ip <ip>] [--port <port>] [--update-ms <ms>] [--workspace]"
.into(),
);
}
_ => {
@ -116,30 +120,24 @@ async fn main() {
};
let update_interval = Duration::from_millis(cfg.update_ms);
let mut app = Router::new()
.route(
"/",
get(move || {
async move { serve_index().await }
}),
)
.route(
"/index.html",
get(move || {
async move { serve_index().await }
}),
)
.route("/styles.css", get(serve_styles))
.route("/app.js", get(serve_app_js))
.route(
"/ws/uploaded",
get(move |ws: WebSocketUpgrade| {
let update_interval = update_interval;
async move {
ws.on_upgrade(move |socket| uploaded::ws_handler(socket, update_interval))
}
}),
);
let mut app =
Router::new()
.route("/", get(move || async move { serve_index().await }))
.route(
"/index.html",
get(move || async move { serve_index().await }),
)
.route("/styles.css", get(serve_styles))
.route("/app.js", get(serve_app_js))
.route(
"/ws/uploaded",
get(move |ws: WebSocketUpgrade| {
let update_interval = update_interval;
async move {
ws.on_upgrade(move |socket| uploaded::ws_handler(socket, update_interval))
}
}),
);
if cfg.workspace_ws {
app = app.route(
@ -183,10 +181,7 @@ pub enum ServerMsg<'a> {
Start,
Stop,
Led(u32),
Seg0(u32),
Seg1(u32),
Seg2(u32),
Seg3(u32),
Seg { value: u32, index: u32 },
}
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;

View file

@ -1,12 +1,9 @@
use axum::{
extract::ws::{Message, WebSocket},
};
use futures_util::{
SinkExt, StreamExt,
};
use axum::extract::ws::{Message, WebSocket};
use futures_util::{SinkExt, StreamExt};
use std::{collections::HashMap, time::Duration};
use tokio::{
io::{AsyncBufReadExt, BufReader}, time::Instant,
io::{AsyncBufReadExt, BufReader},
time::Instant,
};
use crate::{ClientMsg, HResult, ServerMsg, build, run};
@ -16,27 +13,30 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
let files = if let Some(Ok(Message::Text(msg))) = receiver.next().await
&& let Ok(files) = serde_json::from_str::<'_, HashMap<String, String>>(&msg)
{
{
files
} else {
return;
};
let artifact_dir = match build::copy_and_build(files).await{
let artifact_dir = match build::copy_and_build(files).await {
Ok(dir) => dir,
Err(err) => {
_ = sender.send(Message::Text(format!("Failed to build: {err}").into())).await;
_ = sender
.send(Message::Text(format!("Failed to build: {err}").into()))
.await;
return;
},
}
};
let mut process = match run::run(&artifact_dir).await{
let mut process = match run::run(&artifact_dir).await {
Ok(process) => process,
Err(err) => {
_ = sender.send(Message::Text(format!("Failed to run: {err}").into())).await;
_ = sender
.send(Message::Text(format!("Failed to run: {err}").into()))
.await;
return;
},
}
};
let mut sout = BufReader::new(process.stdout).lines();
let mut serr = BufReader::new(process.stderr).lines();
@ -44,7 +44,7 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE");
let mut print_deadline = Instant::now();
let result: HResult<()> = async {
loop{
tokio::select! {
@ -89,14 +89,12 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg0="){
ServerMsg::Seg0(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg1="){
ServerMsg::Seg1(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg2="){
ServerMsg::Seg2(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg=")
&& let Some((val, idx)) = repr.split_once(";") {
ServerMsg::Seg{
value: val.parse().unwrap_or(0),
index: idx.parse().unwrap_or(0)
}
}else{
ServerMsg::Log {
stream: "stderr",
@ -121,10 +119,10 @@ pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Ok(())
}.await;
match result{
Ok(_) => {},
match result {
Ok(_) => {}
Err(err) => {
_ = sender.send(Message::Text(format!("{err}").into())).await;
},
}
}
}

View file

@ -1,5 +1,6 @@
use axum::{
Error, extract::ws::{Message, WebSocket}
Error,
extract::ws::{Message, WebSocket},
};
use futures_util::{
SinkExt, StreamExt,
@ -8,12 +9,12 @@ use futures_util::{
use std::{path::PathBuf, time::Duration};
use tokio::{
io::{AsyncBufReadExt, BufReader, Lines},
process::{Child, ChildStderr, ChildStdin, ChildStdout}, time::Instant,
process::{Child, ChildStderr, ChildStdin, ChildStdout},
time::Instant,
};
use crate::{ClientMsg, ServerMsg, build, run};
struct Process {
process: Child,
@ -22,7 +23,6 @@ struct Process {
stdin: ChildStdin,
}
struct Handler {
sender: SplitSink<WebSocket, Message>,
receiver: SplitStream<WebSocket>,
@ -36,7 +36,6 @@ struct Handler {
}
impl Handler {
fn workspace(socket: WebSocket, build: PathBuf, src: PathBuf, refresh_time: Duration) -> Self {
let (sender, receiver) = socket.split();
Self {
@ -55,7 +54,12 @@ impl Handler {
stream: "stdout",
line: msg.as_ref(),
};
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await;
_ = self
.sender
.send(Message::Text(
serde_json::to_string(&msg).unwrap_or_default().into(),
))
.await;
}
pub async fn eprint(&mut self, msg: impl AsRef<str>) {
@ -64,36 +68,51 @@ impl Handler {
stream: "stderr",
line: msg.as_ref(),
};
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await;
_ = self
.sender
.send(Message::Text(
serde_json::to_string(&msg).unwrap_or_default().into(),
))
.await;
}
async fn stop_process(&mut self) {
self.process = None;
_ = self.sender.send(Message::Text(serde_json::to_string(&ServerMsg::Stop).unwrap_or_default().into())).await;
_ = self
.sender
.send(Message::Text(
serde_json::to_string(&ServerMsg::Stop)
.unwrap_or_default()
.into(),
))
.await;
}
async fn handle_websocket_msg(&mut self, msg: ClientMsg) {
match msg{
match msg {
ClientMsg::Start => self.run_program().await,
ClientMsg::Stop => self.stop_process().await,
ClientMsg::Input { switch, buttons } => {
if let Some(process) = &mut self.process{
if let Some(process) = &mut self.process {
use tokio::io::AsyncWriteExt;
_ = process.stdin.write_all(format!("btn={}\n", buttons).as_bytes()).await;
_ = process.stdin.write_all(format!("sw={}\n", switch).as_bytes()).await;
_ = process
.stdin
.write_all(format!("btn={}\n", buttons).as_bytes())
.await;
_ = process
.stdin
.write_all(format!("sw={}\n", switch).as_bytes())
.await;
}
},
}
}
}
async fn handle_websocket_receive(
&mut self,
msg: Option<Result<Message, Error>>,
) -> bool {
async fn handle_websocket_receive(&mut self, msg: Option<Result<Message, Error>>) -> bool {
match msg {
Some(Ok(Message::Close(_))) => true,
Some(Ok(Message::Text(msg))) => {
let msg = match serde_json::from_str(msg.as_str()){
let msg = match serde_json::from_str(msg.as_str()) {
Ok(msg) => msg,
Err(err) => {
self.eprint(format!("Client message error {err}")).await;
@ -107,7 +126,7 @@ impl Handler {
Some(Err(err)) => {
self.eprint(format!("Client websocket error {err}")).await;
true
},
}
None => true,
}
}
@ -115,7 +134,7 @@ impl Handler {
async fn run(&mut self) {
loop {
if let Some(process) = &mut self.process {
if let Ok(Some(_)) = process.process.try_wait(){
if let Ok(Some(_)) = process.process.try_wait() {
self.stop_process().await;
continue;
}
@ -143,14 +162,12 @@ impl Handler {
Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg0="){
ServerMsg::Seg0(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg1="){
ServerMsg::Seg1(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg2="){
ServerMsg::Seg2(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg=")
&& let Some((val, idx)) = repr.split_once(";") {
ServerMsg::Seg{
value: val.parse().unwrap_or(0),
index: idx.parse().unwrap_or(0)
}
}else{
self.eprint(line).await;
continue;
@ -170,19 +187,18 @@ impl Handler {
_ = process.stdin.write_all("\n".as_bytes()).await;
}
}
}else{
} else {
let res = self.receiver.next().await;
if self.handle_websocket_receive(res).await{
if self.handle_websocket_receive(res).await {
break;
}
}
}
}
async fn run_program(&mut self) {
async fn run_program(&mut self) {
match build::build(&self.build_dir, &self.src_dir).await {
Ok(_) => {},
Ok(_) => {}
Err(err) => {
_ = self.eprint(format!("Failed to build: {err}")).await;
return;
@ -200,13 +216,25 @@ impl Handler {
let stderr = BufReader::new(process.stderr).lines();
let stdin = process.stdin;
_ = self.sender.send(Message::Text(serde_json::to_string(&ServerMsg::Start).unwrap_or_default().into())).await;
self.process = Some(
Process { process: process.child, stderr, stdout, stdin }
)
_ = self
.sender
.send(Message::Text(
serde_json::to_string(&ServerMsg::Start)
.unwrap_or_default()
.into(),
))
.await;
self.process = Some(Process {
process: process.child,
stderr,
stdout,
stdin,
})
}
}
pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) {
Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time).run().await;
Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time)
.run()
.await;
}