server test is working :)

This commit is contained in:
Rusty Striker 2024-09-30 18:44:36 +03:00
parent 9189d9cd88
commit dd34317d14
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
9 changed files with 249 additions and 166 deletions

View file

@ -6,7 +6,7 @@ pub struct LoginRequest {
pub password: String,
}
#[derive(Serialize)]
#[derive(Serialize, Clone)]
pub struct LoginResult {
pub success: bool,
// TODO: Figure out what the user needs on successful login to reduce traffic

View file

@ -4,21 +4,30 @@ pub mod map_actions;
use serde::{Deserialize, Serialize};
use crate::game::chat_message::ChatMessage;
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Request {
#[default]
Error,
Login(login::LoginRequest)
Login(login::LoginRequest),
Message(ChatMessage),
Quit,
Shutdown
}
#[derive(Serialize)]
#[derive(Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum Response {
Error(RequestError),
Login(login::LoginResult)
Login(login::LoginResult),
Message(ChatMessage),
Quit { id: String },
Shutdown,
}
#[derive(Serialize, Debug)]
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum RequestError {
InvalidRequest,

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ChatMessage {
/// message text, `{item}` can be used to refer to items and such, where item is of the path such as `items/sword` or `spells/fireball`
pub text: String,
@ -17,7 +17,7 @@ pub struct ChatMessage {
pub targets: Option<Vec<String>>
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RollDialogOption {
/// Field name
pub name: String,

View file

@ -1,5 +1,34 @@
use game::{Game, GameImpl};
use tokio::sync::{broadcast, mpsc};
pub mod user;
pub mod table;
pub mod api;
pub mod game;
pub mod pathfinder2r_impl;
pub mod pathfinder2r_impl;
pub struct GameServer {
game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::PEntry>
}
impl GameServer {
pub fn new() -> Self {
Self {
game: Game::new(),
}
}
pub async fn server_loop(mut self, mut msgs: mpsc::Receiver<(String, api::Request)>, mut broadcast: broadcast::Sender<api::Response>) {
while let Some(req) = msgs.recv().await {
// TODO: do stuff yo!
let (id, req) = req;
match req {
api::Request::Error => {},
api::Request::Login(_) => {},
api::Request::Message(msg) => { _ = broadcast.send(api::Response::Message(msg)); },
api::Request::Quit => { _ = broadcast.send(api::Response::Quit { id })},
api::Request::Shutdown => todo!(),
}
}
_ = broadcast.send(api::Response::Shutdown);
}
}

View file

@ -1,61 +1,110 @@
use axum::{
extract::ws::{self,Message}, response, routing, Router
};
use futures_util::{stream::{SplitSink, SplitStream}, SinkExt, StreamExt};
use open_tavern::api::{Request, RequestError, Response};
use tokio::sync::{broadcast, mpsc};
#[tokio::main]
async fn main() {
let (bsend, _) = broadcast::channel(10);
let (msend, mrecv) = mpsc::channel(50);
let bsend2 = bsend.clone();
let app = Router::new()
.route("/", routing::get(root))
.route("/socket.js", routing::get(socket))
.route("/ws", routing::get(ws_handler))
;
.route("/ws", routing::get(move |w| ws_handler(w, msend, bsend2.clone().subscribe())));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap();
axum::serve(listener, app).await.unwrap();
let game = open_tavern::GameServer::new();
tokio::spawn(game.server_loop(mrecv, bsend));
axum::serve(listener, app).await.expect("axum server crashed, yaaaaay (unless i crashed him that yay)");
}
async fn ws_handler(ws: ws::WebSocketUpgrade) -> impl axum::response::IntoResponse {
ws.on_upgrade(handle_socket)
async fn ws_handler(ws: ws::WebSocketUpgrade, msend: mpsc::Sender<(String, Request)>, brecv: broadcast::Receiver<Response>) -> impl axum::response::IntoResponse {
ws.on_upgrade(|w| handle_socket(w, msend, brecv))
}
async fn handle_socket(mut socket: ws::WebSocket) {
let mut logged_in = false;
println!("Got a new socket");
async fn socket_receiver(mut recv: SplitStream<ws::WebSocket>, msend: mpsc::Sender<(String, Request)>, id: String) {
while let Some(msg) = recv.next().await {
if let Ok(msg) = msg {
match msg {
Message::Text(t) => {
let req = serde_json::from_str::<Request>(&t).unwrap_or_default();
println!("Got message: {:?}", t);
let erred = msend.send((id.clone(), req)).await.is_err();
if erred {
break;
}
}
ws::Message::Binary(_) => todo!(),
ws::Message::Ping(_) => todo!(),
ws::Message::Pong(_) => todo!(),
ws::Message::Close(_) => {
// dont care if we fail the send as we are quitting regardless
_ = msend.send((id.clone(), open_tavern::api::Request::Quit)).await;
break;
},
}
}
}
}
async fn socket_sender(mut send: SplitSink<ws::WebSocket, ws::Message>, mut brecv: broadcast::Receiver<Response>) {
while let Ok(msg) = brecv.recv().await {
let err = send.send(ws::Message::Text(serde_json::to_string(&msg).unwrap())).await.is_err();
if err {
break;
}
}
}
async fn handle_socket(mut socket: ws::WebSocket, msend: mpsc::Sender<(String, Request)>, brecv: broadcast::Receiver<Response>) {
let mut id: Option<String> = None;
loop {
if let Some(msg) = socket.recv().await {
if let Ok(msg) = msg {
let response: Message = match msg {
match msg {
Message::Text(t) => {
let req = serde_json::from_str::<Request>(&t).unwrap_or_default();
println!("Got message: {:?}", t);
match req {
Request::Error => Message::Text(serde_json::to_string(&Response::Error(RequestError::InvalidRequest)).unwrap()),
Request::Login(r) => if !logged_in {
if r.username == "rusty" {
logged_in = true;
Message::Text(serde_json::to_string(&Response::Login(open_tavern::api::login::LoginResult { success: true })).unwrap())
}
else {
Message::Text(serde_json::to_string(&Response::Login(open_tavern::api::login::LoginResult { success: false })).unwrap())
}
} else {
Message::Text(serde_json::to_string(&Response::Error(open_tavern::api::RequestError::AlreadyLoggedIn)).unwrap())
// TODO: Actual signing in mechanism with multiple ids :)
Request::Login(r) => if r.username == "rusty" {
_ = socket.send(Message::Text(
serde_json::to_string(&Response::Login(open_tavern::api::login::LoginResult { success: true })).unwrap()
)).await;
id = Some(String::from("rusty"));
break;
}
else {
_ = socket.send(Message::Text(
serde_json::to_string(&Response::Login(open_tavern::api::login::LoginResult { success: false })).unwrap()
)).await;
},
}
_ => {
_ = socket.send(Message::Text(serde_json::to_string(&Response::Error(RequestError::InvalidRequest)).unwrap())).await;
},
}
}
ws::Message::Binary(_) => todo!(),
ws::Message::Ping(_) => todo!(),
ws::Message::Pong(_) => todo!(),
ws::Message::Close(_) => break,
};
socket.send(response).await.expect("failed sending to socket");
}
}
else {
break;
}
}
if let Some(id) = id {
println!("Got id for socket: {}", &id);
let (send, recv) = socket.split();
tokio::spawn(socket_receiver(recv, msend, id));
tokio::spawn(socket_sender(send, brecv));
}
println!("Done with so-cat");
}

View file

@ -1,10 +1,11 @@
use crate::game::{action::{ActionDefinition, ActionResult, GameEntry}, character_sheet::*, chat_message::{ChatMessage, RollDialogOption}};
use serde::Serialize;
use tavern_macros::CharacterSheet;
pub mod entry;
use entry::{PEntry, Weapon};
#[derive(Default, CharacterSheet)]
#[derive(Default, CharacterSheet, Serialize)]
pub struct Pathfinder2rCharacterSheet {
// Genral stuff
#[Input("Name")]