use std::error::Error;
use std::net::SocketAddr;
use std::sync::Arc;
use async_std::io::WriteExt;
use async_std::net::TcpStream;
use async_std::sync::RwLock;
use log::{error, info, warn};
use starkingdoms_protocol::PROTOCOL_VERSION;
use crate::entity::{Entity, EntityHandler};
use crate::manager::{ClientHandler, ClientManager, PhysicsData};
use crate::{CMGR, ServerPingResponse, ServerPingResponseVersion};
use crate::handler::handle_client;
use futures::StreamExt;
pub async fn handle_request(
conn: TcpStream,
remote_addr: SocketAddr,
mgr: ClientManager,
entities: Arc<RwLock<EntityHandler>>,
physics_data: Arc<RwLock<PhysicsData>>,
) {
match _handle_request(conn, remote_addr, mgr, entities, physics_data).await {
Ok(_) => (),
Err(e) => {
error!("[{}] error in handler thread: {}", remote_addr, e);
}
}
}
async fn _handle_request(
mut conn: TcpStream,
remote_addr: SocketAddr,
mgr: ClientManager,
entities: Arc<RwLock<EntityHandler>>,
physics_data: Arc<RwLock<PhysicsData>>,
) -> Result<(), Box<dyn Error + Sync + Send>> {
let mut peek_buf = [0u8; 9];
loop {
let read = conn.peek(&mut peek_buf).await?;
if read == 9 {
break;
}
}
if peek_buf == *b"GET /ping" {
info!("[{}] incoming http connection", remote_addr);
let ping_resp = serde_json::to_string(&ServerPingResponse {
version: ServerPingResponseVersion {
name: env!("STK_VERSION_NAME").to_string(), // Set by build.rs
number: env!("STK_VERSION").to_string(), // Set by build.rs
protocol: PROTOCOL_VERSION,
channel: env!("STK_CHANNEL").to_string(),
build: env!("STK_BUILD").to_string(),
},
players: u32::try_from(CMGR.usernames.read().await.len())?,
description: env!("STK_SLP_DESCRIPTION").to_string(),
})?;
let resp_str = format!(
"HTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\nContent-Length: {}\n\n{}",
ping_resp.len(),
ping_resp
);
let http_resp = resp_str.as_bytes();
conn.write_all(http_resp).await?;
info!(
"[{}] sent ping response (200 OK {} bytes)",
remote_addr,
ping_resp.len()
);
return Ok(());
}
info!("[{}] incoming websocket connection", remote_addr);
// if its not GET /ping, assume its websocket and attempt to handshake with them
let ws_stream = async_tungstenite::accept_async(conn).await?;
let (ws_write, ws_read) = ws_stream.split();
let (tx, rx) = async_std::channel::unbounded();
let client = ClientHandler { tx };
// Acquire the write lock in a small scope, so it's dropped as quickly as possible
{
mgr.handlers.write().await.insert(remote_addr, client);
}
info!("[{}] passing to client handler", remote_addr);
//forward the stream to the sink to achieve echo
match handle_client(
mgr.clone(),
entities.clone(),
physics_data.clone(),
remote_addr,
rx,
ws_write,
ws_read,
)
.await
{
Ok(_) => (),
Err(e) if e.is::<async_tungstenite::tungstenite::error::Error>() => {
#[allow(clippy::expect_used)]
let e = {
e.downcast::<async_tungstenite::tungstenite::error::Error>()
.expect("unable to convert between types safely")
};
if matches!(*e, async_tungstenite::tungstenite::Error::ConnectionClosed) {
info!("[{}] connection closed normally", remote_addr);
} else {
error!("[{}] error in client thread: {}", remote_addr, e);
}
}
Err(e) => {
error!("[{}] error in client thread: {}", remote_addr, e);
}
}
// clean up values left over
{
mgr.handlers.write().await.remove(&remote_addr);
mgr.usernames.write().await.remove(&remote_addr);
// remove player physics body
let mut entity_write = entities.write().await;
let mut data = physics_data.write().await;
let mut rigid_body_set = data.rigid_body_set.clone();
let mut island_manager = data.island_manager.clone();
let mut collider_set = data.collider_set.clone();
let mut impulse_joint_set = data.impulse_joint_set.clone();
let mut multibody_joint_set = data.multibody_joint_set.clone();
let Some(player_id) = entity_write.get_player_id(remote_addr) else {
warn!("[{}] player missing from entities.players", remote_addr);
return Err("Player missing from entities.players".into());
};
if let Some(Entity::Player(player)) = entity_write.entities.get(&player_id) {
rigid_body_set.remove(
player.handle,
&mut island_manager,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
true,
);
for module in player.search_modules(&entity_write) {
rigid_body_set.remove(
module.handle,
&mut island_manager,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
true,
);
let module_id = entity_write
.get_id_from_attached(&module)
.ok_or("Tried to remove nonexistent module")?;
entity_write.entities.remove(&module_id);
}
} else {
warn!("tried to remove player that doesnt exist: #{}", player_id);
}
data.rigid_body_set = rigid_body_set;
data.collider_set = collider_set;
data.island_manager = island_manager;
data.impulse_joint_set = impulse_joint_set;
data.multibody_joint_set = multibody_joint_set;
entity_write.entities.remove(&player_id);
}
Ok(())
}