Sidecar: shard link (Rust)
First cut of the Rust sidecar, in sidecar/. It is the TCP listener the shard
dials out to; that asymmetry is what keeps the game unreachable from the website.
shard.rs: serve() binds 127.0.0.1:7788 and accepts shard connections in a loop,
re-accepting on disconnect. Each connection splits read/write: the reader parses
newline-JSON into ShardEvent { kind, value } and forwards over an mpsc; the writer
drains a command mpsc. ShardHandle::send posts to whichever shard is connected and
drops with a warning if none is -- a website query during an outage should fail
fast, not queue; live events that must survive an outage are buffered by the shard.
main.rs wires it up, logs events by kind, and runs a 15s heartbeat ping to
exercise the command path. Later phases fan events out to a WebSocket broadcaster
and SQLite, and turn REST calls into shard commands.
Verified against the live shard: the sidecar received the shard's server.hello
(parsed, fields intact), round-tripped its heartbeat ping -> pong, and after a
sidecar restart the shard reconnected on its own and re-sent hello. Tokio + serde;
axum/sqlx/tungstenite come with the WS and REST phases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
67
sidecar/src/main.rs
Normal file
67
sidecar/src/main.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
//! uo-link sidecar.
|
||||
//!
|
||||
//! Terminates the loopback link to the ServUO shard and (in later phases) exposes WebSocket + REST
|
||||
//! to the website. This first cut proves the shard link: it receives real events and can send
|
||||
//! commands back.
|
||||
|
||||
mod shard;
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
const SHARD_ADDR: &str = "127.0.0.1:7788";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
init_tracing();
|
||||
|
||||
info!("uo-link sidecar starting");
|
||||
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<shard::ShardEvent>();
|
||||
let handle = shard::serve(SHARD_ADDR, event_tx).await?;
|
||||
|
||||
// Phase 1: log every event and tally by kind. Later phases fan this out to WS + SQLite.
|
||||
let mut total: u64 = 0;
|
||||
tokio::spawn(async move {
|
||||
while let Some(ev) = event_rx.recv().await {
|
||||
total += 1;
|
||||
match ev.kind.as_str() {
|
||||
// These are the anchors worth surfacing at info; the rest are debug.
|
||||
"server.hello" | "mob.login" | "mob.logout" | "player.death"
|
||||
| "vendor.sale" | "house.decay" | "link.request" => {
|
||||
info!(kind = %ev.kind, n = total, "{}", ev.value);
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!(kind = %ev.kind, n = total, "{}", ev.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// A tiny demonstration that the command path works: ping the shard once it connects.
|
||||
let ping_handle = handle.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
|
||||
if ping_handle.is_connected().await {
|
||||
let _ = ping_handle
|
||||
.send(r#"{"kind":"ping","id":"sidecar-heartbeat"}"#.to_string())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Run until Ctrl-C.
|
||||
tokio::signal::ctrl_c().await?;
|
||||
info!("shutting down");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
|
||||
)
|
||||
.init();
|
||||
}
|
||||
Reference in New Issue
Block a user