Update source files

This commit is contained in:
2025-10-20 01:09:08 -05:00
commit f7b7faa60f
194 changed files with 29945 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
[package]
name = "borders-server"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
anyhow = "1.0"
borders-core = { path = "../borders-core" }
flume = "0.11"
rkyv = { version = "0.8", features = ["hashbrown-0_15"] }
rustls-pemfile = "2.2.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
tracing = "0.1"
tracing-log = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
web-transport = "0.9"

View File

@@ -0,0 +1,233 @@
// Platform-specific imports for native server
use crate::registry::ServerRegistry;
use anyhow::Result;
use borders_core::networking::{NetMessage, SourcedIntent};
use flume::Sender;
use rkyv::{Archived, api::deserialize_using, de::Pool};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{Instrument, error, info, instrument, warn};
use web_transport::quinn::{RecvStream, SendStream};
type ArchivedNetMessage = Archived<NetMessage>;
/// Handle a single client connection over WebTransport
#[instrument(skip_all)]
pub async fn handle_client_connection(mut send_stream: SendStream, mut recv_stream: RecvStream, intent_tx: Sender<SourcedIntent>, registry: Arc<RwLock<ServerRegistry>>) -> Result<()> {
info!("New client connected, starting message handling");
// Create a per-client channel for receiving broadcast messages
let (client_tx, client_rx) = flume::unbounded::<NetMessage>();
// Register this client with the server registry and get assigned player ID
let player_id = { registry.write().await.add_client(client_tx) };
info!(player_id = %player_id, "Client registered");
// Send initial server config
let server_config = NetMessage::ServerConfig { player_id };
let config_bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&server_config)?;
let len_bytes = (config_bytes.len() as u64).to_be_bytes();
// Send length prefix
let mut written = 0;
while written < len_bytes.len() {
let bytes_written = send_stream.write(&len_bytes[written..]).await?;
written += bytes_written;
}
// Send config bytes
let mut written = 0;
while written < config_bytes.len() {
let bytes_written = send_stream.write(&config_bytes[written..]).await?;
written += bytes_written;
}
// Spawn task to handle incoming intents from this client
let intent_tx_clone = intent_tx.clone();
tokio::spawn(
async move {
loop {
// Read length prefix (8 bytes)
let mut len_bytes = [0u8; 8];
let mut read_so_far = 0;
while read_so_far < 8 {
let buf = &mut len_bytes[read_so_far..];
if let Ok(maybe_size) = recv_stream.read(buf).await {
if let Some(size) = maybe_size {
read_so_far += size;
} else {
error!("Stream closed before reading length prefix");
break;
}
} else {
error!("Stream error reading length prefix");
break;
}
}
if read_so_far < 8 {
break;
}
let len = u64::from_be_bytes(len_bytes) as usize;
// Read message data
let mut message_bytes = vec![0u8; len];
let mut read_so_far = 0;
while read_so_far < len {
let buf = &mut message_bytes[read_so_far..];
if let Ok(maybe_size) = recv_stream.read(buf).await {
if let Some(size) = maybe_size {
read_so_far += size;
} else {
error!("Stream closed before reading full message");
break;
}
} else {
error!("Stream error reading message data");
break;
}
}
if read_so_far < len {
break;
}
// Decode message
let archived = unsafe { rkyv::access_unchecked::<ArchivedNetMessage>(&message_bytes) };
match deserialize_using::<_, _, rkyv::rancor::Error>(archived, &mut Pool::new()) {
Ok(net_message) => match net_message {
NetMessage::Intent { id, intent } => {
// Wrap intent with authenticated source player_id
let sourced_intent = SourcedIntent { source: player_id, intent_id: id, intent };
if let Err(e) = intent_tx_clone.send(sourced_intent) {
error!(error = %e, "Failed to forward intent");
break;
}
}
_ => warn!("Received unexpected message type from client"),
},
Err(e) => {
error!(error = %e, "Failed to deserialize message");
break;
}
}
}
info!("Client intent receiver task ended");
}
.instrument(tracing::trace_span!(
"client_recv_loop",
player_id = %player_id
)),
);
// Handle outgoing messages to this client
let registry_clone = registry.clone();
tokio::spawn(
async move {
while let Ok(message) = client_rx.recv_async().await {
match rkyv::to_bytes::<rkyv::rancor::Error>(&message) {
Ok(message_bytes) => {
let len_bytes = (message_bytes.len() as u64).to_be_bytes();
// Send length prefix
let mut written = 0;
while written < len_bytes.len() {
match send_stream.write(&len_bytes[written..]).await {
Ok(bytes_written) => written += bytes_written,
Err(e) => {
error!(
player_id = %player_id,
error = %e,
"Failed to send length prefix"
);
break;
}
}
}
// Send message bytes
let mut written = 0;
while written < message_bytes.len() {
match send_stream.write(&message_bytes[written..]).await {
Ok(bytes_written) => written += bytes_written,
Err(e) => {
error!(player_id = %player_id, error = %e, "Failed to send message");
break;
}
}
}
}
Err(e) => {
error!(player_id = %player_id, error = %e, "Failed to encode message");
break;
}
}
}
// Remove client from registry when sender task ends
info!(
player_id = %player_id,
"Client message sender task ended, removing from registry"
);
registry_clone.write().await.remove_client(player_id);
}
.instrument(tracing::trace_span!("client_send_loop", player_id = %player_id)),
);
info!(
player_id = %player_id,
"Client connection handler setup complete"
);
Ok(())
}
/// Start the WebTransport server and accept connections
#[instrument(skip_all, fields(bind_address = %bind_address))]
pub async fn start_server(bind_address: &str, intent_tx: Sender<SourcedIntent>, registry: Arc<RwLock<ServerRegistry>>) -> Result<()> {
use web_transport::quinn::ServerBuilder;
info!("Starting WebTransport server");
// Load development certificate and key
let cert_path = "dev-cert.pem";
let key_path = "dev-key.pem";
let cert_data = std::fs::read(cert_path).map_err(|e| anyhow::anyhow!("Failed to read certificate file {}: {}", cert_path, e))?;
let key_data = std::fs::read(key_path).map_err(|e| anyhow::anyhow!("Failed to read key file {}: {}", key_path, e))?;
// Parse certificate and key
let certs = rustls_pemfile::certs(&mut &cert_data[..]).collect::<Result<Vec<_>, _>>().map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e))?;
let key = rustls_pemfile::private_key(&mut &key_data[..]).map_err(|e| anyhow::anyhow!("Failed to parse private key: {}", e))?.ok_or_else(|| anyhow::anyhow!("No private key found"))?;
let mut server = ServerBuilder::new().with_addr(bind_address.parse()?).with_certificate(certs, key)?;
info!("WebTransport server listening for connections");
loop {
match server.accept().await {
Some(connection) => {
info!("New client connected");
let intent_tx_clone = intent_tx.clone();
let registry_clone = registry.clone();
let session = connection.ok().await?;
tokio::spawn(async move {
// Accept bidirectional stream from client
match session.accept_bi().await {
Ok((send_stream, recv_stream)) => {
if let Err(e) = handle_client_connection(send_stream, recv_stream, intent_tx_clone, registry_clone).await {
error!(error = %e, "Error handling client connection");
}
}
Err(e) => {
error!(error = %e, "Failed to accept bidirectional stream from client");
}
}
});
}
None => {
error!("Failed to accept connection");
}
}
}
}

View File

@@ -0,0 +1,107 @@
use borders_core::networking::server::{SharedTurnGenerator, TurnOutput};
use borders_core::networking::{NetMessage, SourcedIntent};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::time::{Duration, interval};
use tracing::{error, info};
use crate::{connections::start_server, registry::ServerRegistry};
mod connections;
mod registry;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize tracing
#[cfg(debug_assertions)]
let default_filter = "borders_core=debug,borders_server=debug,borders_protocol=debug,info";
#[cfg(not(debug_assertions))]
let default_filter = "borders_core=warn,borders_server=warn,borders_protocol=warn,error";
tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(default_filter))).init();
// Initialize log-to-tracing bridge for dependencies using log crate
tracing_log::LogTracer::init().expect("Failed to set logger");
// Parse command line arguments
let args: Vec<String> = std::env::args().collect();
let bind_address = if args.len() > 1 { args[1].clone() } else { "127.0.0.1:4433".to_string() };
info!(bind_address = %bind_address, "Starting borders relay server");
// Create channels for communication
let (intent_tx, intent_rx) = flume::unbounded::<SourcedIntent>();
// Create server registry
let registry = Arc::new(RwLock::new(ServerRegistry::new()));
// Spawn network listener task
let intent_tx_clone = intent_tx.clone();
let registry_clone = registry.clone();
let bind_addr = bind_address.clone();
info!("Spawning server listener task...");
tokio::spawn(async move {
info!("Server listener task started");
if let Err(e) = start_server(&bind_addr, intent_tx_clone, registry_clone).await {
error!(error = %e, "Server listener failed");
}
});
// Create shared turn generator
let mut turn_generator = SharedTurnGenerator::new();
let mut tick_interval = interval(Duration::from_millis(100)); // 100ms tick rate
info!("Server running - spawn phase active");
loop {
let _guard = tracing::debug_span!("server_loop").entered();
tick_interval.tick().await;
// Process all pending sourced intents
let mut sourced_intents = Vec::new();
while let Ok(sourced_intent) = intent_rx.try_recv() {
let output = turn_generator.process_intent(sourced_intent.clone());
// Broadcast spawn updates to all clients
if let TurnOutput::SpawnUpdate(spawn_config) = output {
let spawn_message = NetMessage::SpawnConfiguration { spawns: spawn_config };
registry.write().await.broadcast(spawn_message);
}
// Collect sourced intents for turn generation
sourced_intents.push(sourced_intent);
}
// Tick the generator with 100ms delta
let output = turn_generator.tick(100.0, sourced_intents);
// Handle turn output
match output {
TurnOutput::GameStart(turn) => {
info!("Broadcasting Turn(0) to start game (spawns already configured)");
let turn_message = NetMessage::Turn { turn: turn.turn_number, intents: turn.intents };
registry.write().await.broadcast(turn_message);
info!("Spawn phase complete - game started");
}
TurnOutput::Turn(turn) => {
let turn_number = turn.turn_number;
let intent_count = turn.intents.len();
let client_count = { registry.read().await.client_count() };
let turn_message = NetMessage::Turn { turn: turn.turn_number, intents: turn.intents.clone() };
if !turn.intents.is_empty() || turn_number.is_multiple_of(100) {
let _guard = tracing::trace_span!("turn_broadcast", turn_number = turn_number, intent_count = intent_count, client_count = client_count).entered();
info!(turn_number = turn_number, intent_count = intent_count, client_count = client_count, "Broadcasting turn");
}
registry.write().await.broadcast(turn_message);
}
TurnOutput::None | TurnOutput::SpawnUpdate(_) => {
// No turn to broadcast this tick
}
}
}
}

View File

@@ -0,0 +1,66 @@
use borders_core::game::NationId;
use borders_core::networking::NetMessage;
use flume::Sender;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tracing::error;
/// Connection information for a client
#[derive(Debug, Clone)]
pub struct ClientConnection {
pub id: NationId,
pub tx: Sender<NetMessage>,
}
/// Registry for managing client connections and broadcasting messages
pub struct ServerRegistry {
connections: Arc<RwLock<HashMap<NationId, ClientConnection>>>,
next_player_id: Arc<RwLock<u16>>,
}
impl Default for ServerRegistry {
fn default() -> Self {
Self::new()
}
}
impl ServerRegistry {
pub fn new() -> Self {
Self {
connections: Arc::new(RwLock::new(HashMap::new())),
next_player_id: Arc::new(RwLock::new(1)), // Start from 1, 0 reserved
}
}
/// Add a new client connection and return assigned player ID
pub fn add_client(&self, tx: Sender<NetMessage>) -> NationId {
let mut next_id = self.next_player_id.write().unwrap();
let player_id = NationId::new_unchecked(*next_id);
*next_id += 1;
let connection = ClientConnection { id: player_id, tx };
self.connections.write().unwrap().insert(player_id, connection);
player_id
}
/// Remove a client connection
pub fn remove_client(&self, player_id: NationId) {
self.connections.write().unwrap().remove(&player_id);
}
/// Broadcast a message to all connected clients
pub fn broadcast(&self, message: NetMessage) {
let connections = self.connections.read().unwrap();
for connection in connections.values() {
if let Err(e) = connection.tx.send(message.clone()) {
error!("Failed to send message to client {}: {}", connection.id, e);
}
}
}
/// Get the number of connected clients
pub fn client_count(&self) -> usize {
self.connections.read().unwrap().len()
}
}