mirror of
https://github.com/ParkerTenBroeck/hdl_sim.git
synced 2026-06-07 05:28:45 -04:00
added support for local editing with different editor
This commit is contained in:
parent
3cce2983a5
commit
0289d1171f
11 changed files with 2252 additions and 1096 deletions
|
|
@ -1,10 +1,10 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
collections::HashMap, ffi::OsStr, ops::Deref, path::{Path, PathBuf}
|
||||
};
|
||||
use tokio::process::{Child, Command};
|
||||
|
||||
use crate::HResult;
|
||||
|
||||
async fn ensure_ok(child: Child) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let result = child.wait_with_output().await?;
|
||||
if !result.status.success() {
|
||||
|
|
@ -43,9 +43,9 @@ impl AsRef<Path> for TempDir {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn build(
|
||||
pub async fn copy_and_build(
|
||||
files: HashMap<String, String>,
|
||||
) -> Result<TempDir, Box<dyn std::error::Error + Send + Sync>> {
|
||||
) -> HResult<TempDir> {
|
||||
use std::hash::*;
|
||||
let mut hasher = std::hash::DefaultHasher::default();
|
||||
for (key, value) in &files {
|
||||
|
|
@ -56,7 +56,7 @@ pub async fn build(
|
|||
|
||||
let mut work_dir = std::env::temp_dir();
|
||||
work_dir.push(format!("ghdl-relay-{hash:x?}"));
|
||||
_ = std::fs::create_dir(&work_dir);
|
||||
std::fs::create_dir_all(&work_dir)?;
|
||||
let work_dir = TempDir(work_dir);
|
||||
|
||||
for (name, contents) in &files {
|
||||
|
|
@ -65,36 +65,51 @@ pub async fn build(
|
|||
std::fs::write(path, contents)?;
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
build(&work_dir, &work_dir).await?;
|
||||
|
||||
Ok(work_dir)
|
||||
}
|
||||
|
||||
|
||||
pub async fn build(path: &Path, src: &Path) -> HResult<()>{
|
||||
std::fs::create_dir_all(path)?;
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-a", "-g", "--std=08"]);
|
||||
for name in files.keys() {
|
||||
let mut path = work_dir.clone();
|
||||
path.push(name);
|
||||
cmd.arg(path);
|
||||
cmd.args(["-i", "-g", "--std=08"]);
|
||||
|
||||
for file in src.read_dir().unwrap().flatten(){
|
||||
if Path::new(&file.file_name()).extension() == Some(OsStr::new("vhdl")) {
|
||||
cmd.arg(file.path());
|
||||
}
|
||||
}
|
||||
|
||||
cmd.arg(std::fs::canonicalize("../rtl/tb.vhdl")?);
|
||||
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
|
||||
cmd.current_dir(&work_dir);
|
||||
cmd.current_dir(path);
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let mut cmd = Command::new("ghdl");
|
||||
cmd.kill_on_drop(true);
|
||||
cmd.args(["-e", "--std=08"]);
|
||||
cmd.args(["-m", "--std=08"]);
|
||||
cmd.arg(format!(
|
||||
"-Wl,{}",
|
||||
std::fs::canonicalize("../conn/target/release/libvhdl_ui.a")?.display()
|
||||
std::fs::canonicalize("../target/release/libvhdl_ui.a")?.display()
|
||||
));
|
||||
cmd.arg("tb");
|
||||
cmd.current_dir(&work_dir);
|
||||
cmd.current_dir(path);
|
||||
cmd.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
ensure_ok(cmd.spawn()?).await?;
|
||||
|
||||
Ok(work_dir)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -5,43 +5,15 @@ use futures_util::{
|
|||
SinkExt, StreamExt,
|
||||
stream::{SplitSink, SplitStream},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader, Lines},
|
||||
process::{Child, ChildStderr, ChildStdin, ChildStdout},
|
||||
process::{Child, ChildStderr, ChildStdin, ChildStdout}, time::Instant,
|
||||
};
|
||||
|
||||
use crate::{build, run};
|
||||
use crate::{ClientMsg, ServerMsg, build, run};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum ClientMsg {
|
||||
Compile(Option<HashMap<String, String>>),
|
||||
Start,
|
||||
Stop,
|
||||
Input {
|
||||
/// bitfield of 32 switches
|
||||
switch: u32,
|
||||
/// bitfield of 32 buttons
|
||||
buttons: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum ServerMsg<'a> {
|
||||
Log { stream: &'a str, line: &'a str },
|
||||
Start,
|
||||
Stop,
|
||||
Led(u32),
|
||||
Seg0(u32),
|
||||
Seg1(u32),
|
||||
Seg2(u32),
|
||||
Seg3(u32),
|
||||
}
|
||||
|
||||
struct Process {
|
||||
process: Child,
|
||||
|
||||
|
|
@ -50,10 +22,6 @@ struct Process {
|
|||
stdin: ChildStdin,
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
SingleLocal,
|
||||
Remote,
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
sender: SplitSink<WebSocket, Message>,
|
||||
|
|
@ -68,11 +36,9 @@ struct Handler {
|
|||
refresh_time: Duration,
|
||||
}
|
||||
|
||||
type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
||||
|
||||
impl Handler {
|
||||
|
||||
async fn local(socket: WebSocket, build: PathBuf, src: PathBuf) -> Self {
|
||||
fn local(socket: WebSocket, build: PathBuf, src: PathBuf) -> Self {
|
||||
let (sender, receiver) = socket.split();
|
||||
Self {
|
||||
sender,
|
||||
|
|
@ -108,20 +74,18 @@ impl Handler {
|
|||
_ = 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) -> HResult<bool> {
|
||||
async fn handle_websocket_msg(&mut self, msg: ClientMsg) {
|
||||
match msg{
|
||||
ClientMsg::Compile(_) => todo!(),
|
||||
ClientMsg::Start => self.run_program().await,
|
||||
ClientMsg::Stop => self.stop_process().await,
|
||||
ClientMsg::Input { switch, buttons } => {
|
||||
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;
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn handle_websocket_receive(
|
||||
|
|
@ -150,13 +114,16 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Sync + Send>> {
|
||||
async fn run(&mut self) {
|
||||
loop {
|
||||
if let Some(process) = &mut self.process {
|
||||
if let Ok(Some(_)) = process.process.try_wait(){
|
||||
self.stop_process().await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut print_deadline = Instant::now();
|
||||
|
||||
tokio::select! {
|
||||
receive = self.receiver.next() => {
|
||||
if self.handle_websocket_receive(receive).await{
|
||||
|
|
@ -190,7 +157,7 @@ impl Handler {
|
|||
self.eprint(line).await;
|
||||
continue;
|
||||
};
|
||||
self.sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?;
|
||||
_ = self.sender.send(Message::Text(serde_json::to_string(&msg).unwrap_or_default().into())).await;
|
||||
},
|
||||
Ok(None) => self.stop_process().await,
|
||||
Err(err) => {
|
||||
|
|
@ -199,8 +166,9 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(self.refresh_time) => {
|
||||
_ = tokio::time::sleep_until(print_deadline) => {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
print_deadline += self.refresh_time;
|
||||
_ = process.stdin.write_all("\n".as_bytes()).await;
|
||||
}
|
||||
}
|
||||
|
|
@ -211,20 +179,12 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn build_program(&mut self) {
|
||||
let files = if let Some(Ok(Message::Text(msg))) = self.receiver.next().await
|
||||
&& let Ok(files) = serde_json::from_str::<'_, HashMap<String, String>>(&msg)
|
||||
{
|
||||
files
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
async fn run_program(&mut self) {
|
||||
|
||||
let artifact_dir = match build::build(files).await {
|
||||
Ok(dir) => dir,
|
||||
match build::build(&self.build_dir, &self.src_dir).await {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
_ = self
|
||||
.sender
|
||||
|
|
@ -233,9 +193,7 @@ impl Handler {
|
|||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn run_program(&mut self) {
|
||||
let process = match run::run(&self.build_dir).await {
|
||||
Ok(process) => process,
|
||||
Err(err) => {
|
||||
|
|
@ -247,6 +205,7 @@ 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 }
|
||||
)
|
||||
|
|
@ -254,5 +213,5 @@ impl Handler {
|
|||
}
|
||||
|
||||
pub async fn ws_handler(socket: WebSocket) {
|
||||
Handler::local(socket, "target".into(), "src".into());
|
||||
Handler::local(socket, "../target".into(), "../src".into()).run().await;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,12 @@
|
|||
use axum::{
|
||||
Router,
|
||||
extract::ws::{Message, WebSocket, WebSocketUpgrade},
|
||||
extract::ws::WebSocketUpgrade,
|
||||
routing::get,
|
||||
};
|
||||
use futures_util::{
|
||||
stream::{SplitSink, SplitStream},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
use tokio::{
|
||||
io::{BufReader, Lines},
|
||||
process::{Child, ChildStderr, ChildStdin, ChildStdout},
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
use crate::build::TempDir;
|
||||
|
||||
pub mod build;
|
||||
pub mod run;
|
||||
pub mod local;
|
||||
|
|
@ -25,9 +16,13 @@ pub mod remote;
|
|||
async fn main() {
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/ws",
|
||||
"/ws/local",
|
||||
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(remote::ws_handler) }),
|
||||
)
|
||||
.route(
|
||||
"/ws/remote",
|
||||
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(local::ws_handler) }),
|
||||
)
|
||||
.fallback_service(ServeDir::new("ui"));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
|
|
@ -39,9 +34,8 @@ async fn main() {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ClientMsg {
|
||||
Compile(Option<HashMap<String, String>>),
|
||||
Start,
|
||||
Stop,
|
||||
Input {
|
||||
|
|
@ -65,25 +59,4 @@ pub enum ServerMsg<'a> {
|
|||
Seg3(u32),
|
||||
}
|
||||
|
||||
struct Process {
|
||||
process: Child,
|
||||
|
||||
stderr: Lines<BufReader<ChildStderr>>,
|
||||
stdout: Lines<BufReader<ChildStdout>>,
|
||||
stdin: ChildStdin,
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
sender: SplitSink<WebSocket, Message>,
|
||||
receiver: SplitStream<WebSocket>,
|
||||
|
||||
build_dir: TempDir,
|
||||
src_dir: PathBuf,
|
||||
|
||||
program: Option<PathBuf>,
|
||||
process: Option<Process>,
|
||||
|
||||
refresh_time: Duration,
|
||||
}
|
||||
|
||||
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
||||
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use axum::{
|
|||
use futures_util::{
|
||||
SinkExt, StreamExt,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
io::{AsyncBufReadExt, BufReader}, time::Instant,
|
||||
};
|
||||
|
||||
use crate::{ClientMsg, HResult, ServerMsg, build, run};
|
||||
|
|
@ -23,7 +23,7 @@ pub async fn ws_handler(socket: WebSocket) {
|
|||
};
|
||||
|
||||
|
||||
let artifact_dir = match build::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;
|
||||
|
|
@ -42,6 +42,8 @@ pub async fn ws_handler(socket: WebSocket) {
|
|||
let mut serr = BufReader::new(process.stderr).lines();
|
||||
|
||||
let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE");
|
||||
|
||||
let mut print_deadline = Instant::now();
|
||||
|
||||
let result: HResult<()> = async {
|
||||
loop{
|
||||
|
|
@ -52,7 +54,6 @@ pub async fn ws_handler(socket: WebSocket) {
|
|||
Some(Ok(Message::Text(msg))) => {
|
||||
let input = serde_json::from_str::<'_, ClientMsg>(&msg)?;
|
||||
match input{
|
||||
ClientMsg::Compile(_) => {},
|
||||
ClientMsg::Start => {},
|
||||
ClientMsg::Stop => break,
|
||||
ClientMsg::Input { switch, buttons } => {
|
||||
|
|
@ -110,8 +111,9 @@ pub async fn ws_handler(socket: WebSocket) {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(30)) => {
|
||||
_ = tokio::time::sleep_until(print_deadline) => {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
print_deadline += Duration::from_millis(30);
|
||||
process.stdin.write_all("\n".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue