rust rhai ui components wip
This commit is contained in:
11
devtools/reloadd/Cargo.toml
Normal file
11
devtools/reloadd/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "reloadd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.21"
|
||||
notify = "6"
|
||||
futures-util = "0.3"
|
97
devtools/reloadd/bin/reloadd.rs
Normal file
97
devtools/reloadd/bin/reloadd.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use clap::{Parser};
|
||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct Args {
|
||||
/// Paths to watch (like src/, templates/, scripts/)
|
||||
#[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
|
||||
watch: Vec<String>,
|
||||
|
||||
/// Command to run on change (like: -- run --example server)
|
||||
#[arg(last = true)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let (tx, _) = broadcast::channel::<()>(10);
|
||||
let tx_ws = tx.clone();
|
||||
let server_process = Arc::new(Mutex::new(None::<Child>));
|
||||
|
||||
// Start WebSocket reload server
|
||||
tokio::spawn(start_websocket_server(tx_ws.clone()));
|
||||
|
||||
// Start watching files and restarting command
|
||||
let server_process_clone = Arc::clone(&server_process);
|
||||
let watch_paths = args.watch.clone();
|
||||
let cmd_args = args.command.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut watcher = recommended_watcher(move |res| {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
|
||||
println!("📦 Change detected, restarting...");
|
||||
|
||||
// Kill previous process
|
||||
if let Some(mut child) = server_process_clone.lock().unwrap().take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
|
||||
// Run new process
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(&cmd_args);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
let child = cmd.spawn().expect("Failed to spawn");
|
||||
|
||||
*server_process_clone.lock().unwrap() = Some(child);
|
||||
|
||||
// Notify browser
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}).expect("watcher failed");
|
||||
|
||||
// Add watches
|
||||
for path in &watch_paths {
|
||||
watcher.watch(path, RecursiveMode::Recursive).unwrap();
|
||||
}
|
||||
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
|
||||
println!("🔁 Watching paths: {:?}", args.watch);
|
||||
println!("🌐 Connect browser to ws://localhost:35729");
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_websocket_server(tx: broadcast::Sender<()>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:35729").await.unwrap();
|
||||
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
let tx = tx.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let ws_stream = accept_async(stream).await.unwrap();
|
||||
let (mut write, _) = ws_stream.split();
|
||||
|
||||
while rx.recv().await.is_ok() {
|
||||
let _ = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
240
devtools/reloadd/src/main.rs
Normal file
240
devtools/reloadd/src/main.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use clap::{Parser};
|
||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct Args {
|
||||
/// Paths to watch (like src/, templates/, scripts/)
|
||||
#[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
|
||||
watch: Vec<String>,
|
||||
|
||||
/// Command to run on change (like: -- run --example server)
|
||||
#[arg(last = true)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
async fn wait_for_server(port: u16) -> bool {
|
||||
let address = format!("localhost:{}", port);
|
||||
let mut retries = 0;
|
||||
|
||||
while retries < 10 {
|
||||
if let Ok(mut stream) = TcpStream::connect(&address).await {
|
||||
let _ = stream.write_all(b"GET / HTTP/1.1\r\n\r\n").await; // A dummy GET request
|
||||
println!("✅ Server is ready on {}!", address);
|
||||
return true;
|
||||
} else {
|
||||
retries += 1;
|
||||
println!("⏳ Waiting for server to be ready (Attempt {}/10)...", retries);
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("❌ Server not ready after 10 attempts.");
|
||||
false
|
||||
}
|
||||
|
||||
// Check if a port is already in use
|
||||
fn is_port_in_use(port: u16) -> bool {
|
||||
StdTcpListener::bind(format!("127.0.0.1:{}", port)).is_err()
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
println!("Command: {:?}", args.command);
|
||||
|
||||
// Check if server port is already in use
|
||||
let server_port = 8080; // Adjust as needed
|
||||
if is_port_in_use(server_port) {
|
||||
eprintln!("❌ Error: Port {} is already in use. Stop any running instances before starting a new one.", server_port);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Check if WebSocket port is already in use
|
||||
let ws_port = 35729;
|
||||
if is_port_in_use(ws_port) {
|
||||
eprintln!("❌ Error: WebSocket port {} is already in use. Stop any running instances before starting a new one.", ws_port);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let (tx, _) = broadcast::channel::<()>(10);
|
||||
let tx_ws = tx.clone();
|
||||
let server_process = Arc::new(Mutex::new(None::<Child>));
|
||||
|
||||
// Validate paths before starting the watcher
|
||||
let mut invalid_paths = Vec::new();
|
||||
for path in &args.watch {
|
||||
if !std::path::Path::new(path).exists() {
|
||||
invalid_paths.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_paths.is_empty() {
|
||||
eprintln!("❌ Error: The following watch paths do not exist:");
|
||||
for path in invalid_paths {
|
||||
eprintln!(" - {}", path);
|
||||
}
|
||||
eprintln!("Please provide valid paths to watch.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Start WebSocket reload server
|
||||
match start_websocket_server(tx_ws.clone()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to start WebSocket server: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 🚀 Run the server immediately
|
||||
{
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(&args.command);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
*server_process.lock().unwrap() = Some(child);
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to start initial process: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the server to be ready before triggering reloads
|
||||
if !wait_for_server(server_port).await {
|
||||
eprintln!("❌ Server failed to start properly.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("🔁 Watching paths: {:?}", args.watch);
|
||||
println!("🌐 Connect browser to ws://localhost:{}", ws_port);
|
||||
|
||||
// Create a runtime handle for the watcher to use
|
||||
let rt_handle = tokio::runtime::Handle::current();
|
||||
|
||||
// Clone necessary values before moving into the watcher thread
|
||||
let watch_paths = args.watch.clone();
|
||||
let cmd_args = args.command.clone();
|
||||
let server_process_clone = Arc::clone(&server_process);
|
||||
let tx_clone = tx.clone();
|
||||
|
||||
// Create a dedicated thread for the file watcher
|
||||
let watcher_thread = std::thread::spawn(move || {
|
||||
// Create a new watcher
|
||||
let mut watcher = match recommended_watcher(move |res: notify::Result<notify::Event>| {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
|
||||
println!("📦 Change detected, restarting...");
|
||||
|
||||
// Kill previous process
|
||||
if let Some(mut child) = server_process_clone.lock().unwrap().take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
|
||||
// Run new process
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(&cmd_args);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
*server_process_clone.lock().unwrap() = Some(child);
|
||||
|
||||
// Use the runtime handle to spawn a task
|
||||
let tx = tx_clone.clone();
|
||||
rt_handle.spawn(async move {
|
||||
if wait_for_server(server_port).await {
|
||||
// Notify browser to reload
|
||||
let _ = tx.send(());
|
||||
}
|
||||
});
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to spawn process: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Err(e) = res {
|
||||
eprintln!("❌ Watch error: {}", e);
|
||||
}
|
||||
}) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to create watcher: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Add watches
|
||||
for path in &watch_paths {
|
||||
match watcher.watch(path.as_ref(), RecursiveMode::Recursive) {
|
||||
Ok(_) => println!("👁️ Watching path: {}", path),
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to watch path '{}': {}", path, e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the thread alive
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the watcher thread to finish (it shouldn't unless there's an error)
|
||||
match watcher_thread.join() {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Watcher thread panicked: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_websocket_server(tx: broadcast::Sender<()>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:35729").await?;
|
||||
println!("WebSocket server started on ws://localhost:35729");
|
||||
|
||||
// Spawn a task to handle WebSocket connections
|
||||
tokio::spawn(async move {
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
println!("New WebSocket connection from: {}", addr);
|
||||
let tx = tx.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match accept_async(stream).await {
|
||||
Ok(ws_stream) => {
|
||||
let (mut write, _) = ws_stream.split();
|
||||
|
||||
while rx.recv().await.is_ok() {
|
||||
if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await {
|
||||
eprintln!("❌ Error sending reload message to client {}: {}", addr, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to accept WebSocket connection from {}: {}", addr, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user