diff --git a/relay/src/build.rs b/relay/src/build.rs index a2798c5..dab2b3e 100644 --- a/relay/src/build.rs +++ b/relay/src/build.rs @@ -6,6 +6,7 @@ use tokio::process::{Child, Command}; use crate::HResult; const EMBEDDED_VHDL_UI_LIB: &[u8] = include_bytes!(env!("EMBEDDED_VHDL_CONN_LIB_PATH")); +const EMBEDDED_TB_VHDL: &str = include_str!("../../rtl/tb.vhdl"); async fn ensure_ok(child: Child) -> Result<(), Box> { let result = child.wait_with_output().await?; @@ -73,10 +74,12 @@ pub async fn copy_and_build( } -pub async fn build(path: &Path, src: &Path) -> HResult<()>{ - std::fs::create_dir_all(path)?; - let embedded_lib_path = path.join("libvhdl_conn.a"); +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"); cmd.kill_on_drop(true); @@ -84,32 +87,28 @@ pub async fn build(path: &Path, src: &Path) -> HResult<()>{ 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(file.path().canonicalize()?); } } - - cmd.arg(std::fs::canonicalize("../rtl/tb.vhdl")?); + cmd.arg(&embedded_tb_path.canonicalize()?); cmd.stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()); - cmd.current_dir(path); + 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"]); cmd.arg(format!( "-Wl,{}", - embedded_lib_path.display() + embedded_lib_path.canonicalize()?.display() )); cmd.arg("tb"); - cmd.current_dir(path); + cmd.current_dir(build); cmd.stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()); diff --git a/relay/src/main.rs b/relay/src/main.rs index 7ccb5ec..d16a557 100644 --- a/relay/src/main.rs +++ b/relay/src/main.rs @@ -13,17 +13,13 @@ use std::{ pub mod build; pub mod run; -pub mod local; -pub mod remote; +pub mod uploaded; +pub mod workspace; const UI_INDEX_HTML: &str = include_str!("../ui/index.html"); const UI_STYLES_CSS: &str = include_str!("../ui/styles.css"); const UI_APP_JS: &str = include_str!("../ui/app.js"); -async fn serve_index() -> impl IntoResponse { - Html(UI_INDEX_HTML) -} - async fn serve_styles() -> impl IntoResponse { ( [(header::CONTENT_TYPE, "text/css; charset=utf-8")], @@ -38,6 +34,10 @@ async fn serve_app_js() -> impl IntoResponse { ) } +async fn serve_index() -> impl IntoResponse { + Html(UI_INDEX_HTML) +} + async fn not_found() -> impl IntoResponse { (StatusCode::NOT_FOUND, "not found") } @@ -47,6 +47,7 @@ struct Config { ip: IpAddr, port: u16, update_ms: u64, + workspace_ws: bool, } impl Default for Config { @@ -55,6 +56,7 @@ impl Default for Config { ip: IpAddr::from([127, 0, 0, 1]), port: 8080, update_ms: 30, + workspace_ws: true, } } } @@ -84,12 +86,17 @@ fn parse_config_from_args() -> Result { .parse::() .map_err(|err| format!("invalid --update-ms `{value}`: {err}"))?; } + "--workspace" => { + cfg.workspace_ws = true; + } "--help" | "-h" => { - return Err("usage: relay [--ip ] [--port ] [--update-ms ]".into()); + return Err( + "usage: relay [--ip ] [--port ] [--update-ms ] [--workspace]".into(), + ); } _ => { return Err(format!( - "unknown argument `{arg}`\nusage: relay [--ip ] [--port ] [--update-ms ]" + "unknown argument `{arg}`\nusage: relay [--ip ] [--port ] [--update-ms ] [--workspace]" )); } } @@ -109,39 +116,44 @@ async fn main() { }; let update_interval = Duration::from_millis(cfg.update_ms); - let app = Router::new() - .route("/", get(serve_index)) - .route("/index.html", get(serve_index)) + 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/remote", + "/ws/uploaded", get(move |ws: WebSocketUpgrade| { let update_interval = update_interval; async move { - ws.on_upgrade(move |socket| remote::ws_handler(socket, update_interval)) + ws.on_upgrade(move |socket| uploaded::ws_handler(socket, update_interval)) } }), - ) - .route( - "/ws/local", + ); + + if cfg.workspace_ws { + app = app.route( + "/ws/workspace", get(move |ws: WebSocketUpgrade| { let update_interval = update_interval; async move { - ws.on_upgrade(move |socket| local::ws_handler(socket, update_interval)) + ws.on_upgrade(move |socket| workspace::ws_handler(socket, update_interval)) } }), - ) - .route( - "/ws", - get(move |ws: WebSocketUpgrade| { - let update_interval = update_interval; - async move { - ws.on_upgrade(move |socket| remote::ws_handler(socket, update_interval)) - } - }), - ) - .fallback(get(not_found)); + ); + } + + app = app.fallback(get(not_found)); let addr = SocketAddr::new(cfg.ip, cfg.port); println!("Open UI: http://{}/", addr); diff --git a/relay/src/remote.rs b/relay/src/uploaded.rs similarity index 100% rename from relay/src/remote.rs rename to relay/src/uploaded.rs diff --git a/relay/src/local.rs b/relay/src/workspace.rs similarity index 97% rename from relay/src/local.rs rename to relay/src/workspace.rs index f5950cd..0ed0179 100644 --- a/relay/src/local.rs +++ b/relay/src/workspace.rs @@ -37,7 +37,7 @@ struct Handler { impl Handler { - fn local(socket: WebSocket, build: PathBuf, src: PathBuf, refresh_time: Duration) -> Self { + fn workspace(socket: WebSocket, build: PathBuf, src: PathBuf, refresh_time: Duration) -> Self { let (sender, receiver) = socket.split(); Self { sender, @@ -208,5 +208,5 @@ impl Handler { } pub async fn ws_handler(socket: WebSocket, refresh_time: Duration) { - Handler::local(socket, "../target".into(), "../src".into(), refresh_time).run().await; + Handler::workspace(socket, "./target".into(), "./src".into(), refresh_time).run().await; } diff --git a/relay/ui/app.js b/relay/ui/app.js index 75fa044..99fcbf7 100644 --- a/relay/ui/app.js +++ b/relay/ui/app.js @@ -648,7 +648,7 @@ class CircuitUiApp { this.editor = new EditorController({ ...this.dom, - enabled: config.initialMode === "local", + enabled: config.initialMode === "uploaded", externalFiles: config.externalFiles, }); @@ -670,7 +670,7 @@ class CircuitUiApp { this.outputs.resetVisuals(); }, onOpen: () => { - if (this.mode === "local") { + if (this.mode === "uploaded") { this.connection.send(this.editor.getFilesPayload()); } this.connection.send({ input: this.inputs.getInputPayload() }); @@ -705,7 +705,7 @@ class CircuitUiApp { this.setRunButtonEnabled(false); this.setRunning(false); this.updateStatusIndicator(); - if (this.mode === "remote") { + if (this.mode === "workspace") { this.scheduleReconnect(); } }, @@ -741,13 +741,14 @@ class CircuitUiApp { wireModeControls() { this.dom.modeToggle.addEventListener("change", () => { - const nextMode = this.dom.modeToggle.checked ? "remote" : "local"; + const nextMode = this.dom.modeToggle.checked ? "uploaded" : "workspace"; this.applyMode(nextMode); }); } applyMode(nextMode, fromInit = false) { - const mode = nextMode === "remote" ? "remote" : "local"; + const mode = + nextMode === "workspace" && this.config.workspaceEnabled ? "workspace" : "uploaded"; const changed = this.mode !== mode; if (!fromInit && changed && this.connection.isConnected()) { @@ -759,13 +760,14 @@ class CircuitUiApp { localStorage.setItem(LS_KEY_MODE, mode); } catch {} - const isRemote = mode === "remote"; - this.dom.modeToggle.checked = isRemote; - this.editor.setEnabled(!isRemote); - this.dom.connectToggleBtn.classList.toggle("is-hidden", isRemote); - this.dom.runToggleBtn.classList.toggle("is-hidden", !isRemote); + const isUploaded = mode === "uploaded"; + this.dom.modeToggle.checked = isUploaded; + this.dom.modeToggle.disabled = !this.config.workspaceEnabled; + this.editor.setEnabled(isUploaded); + this.dom.connectToggleBtn.classList.toggle("is-hidden", !isUploaded); + this.dom.runToggleBtn.classList.toggle("is-hidden", isUploaded); - if (isRemote) { + if (!isUploaded) { this.scheduleReconnect(0); } else { this.cancelReconnect(); @@ -831,27 +833,29 @@ function resolveConfig() { const config = window.VHDL_UI_CONFIG ?? {}; const query = new URLSearchParams(location.search); const queryMode = (query.get("mode") ?? "").toLowerCase(); + const workspaceEnabled = config.workspaceEnabled !== false; let storedMode = ""; try { storedMode = (localStorage.getItem(LS_KEY_MODE) ?? "").toLowerCase(); } catch {} - let initialMode = "local"; - if (queryMode === "local" || queryMode === "remote") { + let initialMode = workspaceEnabled ? "workspace" : "uploaded"; + if (queryMode === "workspace" || queryMode === "uploaded") { initialMode = queryMode; - } else if (storedMode === "local" || storedMode === "remote") { + } else if (storedMode === "workspace" || storedMode === "uploaded") { initialMode = storedMode; - } else if (config.mode === "local" || config.mode === "remote") { + } else if (config.mode === "workspace" || config.mode === "uploaded") { initialMode = config.mode; } else if (query.has("externalEditor")) { - initialMode = parseBoolean(query.get("externalEditor")) ? "remote" : "local"; + initialMode = parseBoolean(query.get("externalEditor")) ? "uploaded" : "workspace"; } else if (parseBoolean(config.externalEditor)) { - initialMode = "remote"; + initialMode = "uploaded"; } return { - initialMode, + initialMode: initialMode === "workspace" && !workspaceEnabled ? "uploaded" : initialMode, + workspaceEnabled, externalFiles: config.externalFiles ?? null, }; } diff --git a/relay/ui/index.html b/relay/ui/index.html index a1d5095..13537d1 100644 --- a/relay/ui/index.html +++ b/relay/ui/index.html @@ -29,10 +29,10 @@
DISABLED @@ -796,13 +796,6 @@ -