23 changed files with 1422 additions and 1732 deletions
@ -0,0 +1 @@ |
|||
DATABASE_URL="sqlite:db.sqlite" |
|||
@ -1,2 +1,4 @@ |
|||
/target |
|||
Cargo.lock |
|||
db.sqlite* |
|||
/games |
|||
@ -1,14 +1,17 @@ |
|||
use std::fs::read_dir; |
|||
|
|||
fn main() { |
|||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
|||
let mut files = Vec::new(); |
|||
for f in read_dir("../protobuf").unwrap() { |
|||
if let Ok(f) =f { |
|||
if let Ok(f) = f { |
|||
if f.path().is_file() && f.path().extension().map(|x| x == "proto").unwrap_or(false) { |
|||
if let Err(e) = protoc_rust_grpc::Codegen::new().include("../protobuf").input(f.path()).out_dir("src/grpc").rust_protobuf(true).run() { |
|||
eprintln!("PROTOC Error:\n{}", e); |
|||
std::process::exit(1) |
|||
} |
|||
files.push(f.path()); |
|||
} |
|||
} |
|||
} |
|||
tonic_build::configure() |
|||
.build_client(false) |
|||
.out_dir("src/grpc") |
|||
.compile(&files, &["../protobuf".into()])?; |
|||
Ok(()) |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
{ |
|||
"$schema": "http://json-schema.org/draft-07/schema#", |
|||
"title": "Config", |
|||
"type": "object", |
|||
"required": [ |
|||
"available_cards", |
|||
"name", |
|||
"piles", |
|||
"player_piles", |
|||
"script" |
|||
], |
|||
"properties": { |
|||
"authors": { |
|||
"default": [], |
|||
"type": "array", |
|||
"items": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"available_cards": { |
|||
"type": "object", |
|||
"additionalProperties": { |
|||
"$ref": "#/definitions/Card" |
|||
} |
|||
}, |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"piles": { |
|||
"type": "object", |
|||
"additionalProperties": { |
|||
"$ref": "#/definitions/Pile" |
|||
} |
|||
}, |
|||
"player_piles": { |
|||
"type": "object", |
|||
"additionalProperties": { |
|||
"$ref": "#/definitions/Pile" |
|||
} |
|||
}, |
|||
"script": { |
|||
"type": "string" |
|||
}, |
|||
"version": { |
|||
"default": "0.0.0", |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"definitions": { |
|||
"Card": { |
|||
"type": "object", |
|||
"required": [ |
|||
"image" |
|||
], |
|||
"properties": { |
|||
"image": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"additionalProperties": true |
|||
}, |
|||
"Pile": { |
|||
"type": "object", |
|||
"properties": { |
|||
"cards": { |
|||
"default": [], |
|||
"type": "array", |
|||
"items": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"additionalProperties": true |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
use sqlx::{pool::PoolConnection, prelude::*, query, query_as, query_file, SqliteConnection, SqlitePool}; |
|||
use tokio::stream::StreamExt; |
|||
|
|||
pub mod types; |
|||
|
|||
pub struct DbPool { |
|||
pool: SqlitePool, |
|||
} |
|||
|
|||
impl DbPool { |
|||
pub async fn new() -> Self { |
|||
// std::env::set_var("DATABASE_URL", );
|
|||
let pool = SqlitePool::new("sqlite:db.sqlite").await.unwrap(); |
|||
|
|||
let mut conn = pool.acquire().await.unwrap(); |
|||
|
|||
query_file!("src/setup.sql") |
|||
.execute(&mut conn) |
|||
.await |
|||
.unwrap(); |
|||
|
|||
Self { pool } |
|||
} |
|||
|
|||
pub async fn acquire(&self) -> DbConnection { |
|||
DbConnection::new(self.pool.acquire().await.unwrap()) |
|||
} |
|||
} |
|||
|
|||
// impl Drop for DbPool {
|
|||
// fn drop(&mut self) {
|
|||
// tokio::runtime::Handle::current().block_on(future)
|
|||
// }
|
|||
// }
|
|||
|
|||
pub struct DbConnection { |
|||
conn: PoolConnection<SqliteConnection>, |
|||
} |
|||
|
|||
use uuid::Uuid; |
|||
|
|||
|
|||
// TODO: return Results intead of crashing
|
|||
impl DbConnection { |
|||
fn new(conn: PoolConnection<SqliteConnection>) -> Self { |
|||
Self { conn } |
|||
} |
|||
|
|||
pub async fn add_user<T: ToString>(&mut self, name: T) -> Uuid { |
|||
let uuid = Uuid::new_v4(); |
|||
// println!("{:?}", uuid.as_bytes().to_vec());
|
|||
self.conn |
|||
.execute(query!( |
|||
"INSERT INTO Users(uuid, name) VALUES(?, ?)", |
|||
uuid.as_bytes().to_vec(), |
|||
name.to_string() |
|||
)) |
|||
.await |
|||
.unwrap(); // Server crashes if uuids collide
|
|||
uuid |
|||
} |
|||
|
|||
pub async fn users(&mut self) -> Vec<types::User> { |
|||
query_as::<_, types::User>("SELECT UUID, Name FROM Users") |
|||
.fetch_all(&mut self.conn) |
|||
.await |
|||
.unwrap() |
|||
} |
|||
|
|||
pub async fn create_lobby(&mut self) -> u32 { |
|||
let id = rand::random(); |
|||
self.conn |
|||
.execute(query!( |
|||
"INSERT INTO Lobbies(id) VALUES(?)", |
|||
id as i32 |
|||
)) |
|||
.await |
|||
.unwrap(); // Server crashes if ids collide
|
|||
id |
|||
} |
|||
|
|||
pub async fn join_lobby(&mut self, user: Uuid, lobby: u32) { |
|||
self.conn |
|||
.execute(query!( |
|||
"INSERT INTO UsersInLobbies(UserId, LobbyId) VALUES(?, ?)", |
|||
user.as_bytes().to_vec(), |
|||
lobby as i32 |
|||
)) |
|||
.await |
|||
.unwrap(); // Server crashes if ids collide
|
|||
} |
|||
|
|||
pub async fn close(self) { |
|||
self.conn.close().await.unwrap(); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,27 @@ |
|||
use uuid::Uuid; |
|||
use sqlx::{prelude::*, sqlite::SqliteRow}; |
|||
|
|||
pub struct User{ |
|||
pub name: String, |
|||
pub uuid: Uuid |
|||
} |
|||
|
|||
impl<'a> FromRow<'a, SqliteRow<'a>> for User { |
|||
fn from_row(row: &SqliteRow<'a>) -> sqlx::Result<Self> { |
|||
let uuid: String = row.try_get("UUID")?; |
|||
// println!("{:?}", uuid.as_bytes());
|
|||
Ok(Self {uuid: Uuid::from_slice(uuid.as_bytes()).map_err(|x| sqlx::Error::Decode(Box::new(x)))?, name: row.try_get("Name")?}) |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Display for User { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "{}[{}]", self.name, self.uuid) |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Debug for User { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "{}", self) |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
use schemars::JsonSchema; |
|||
use serde::{Deserialize, Serialize}; |
|||
use std::collections::HashMap; |
|||
use std::path::PathBuf; |
|||
|
|||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] |
|||
pub struct Config { |
|||
pub name: String, |
|||
#[serde(default = "default_version")] |
|||
pub version: String, |
|||
#[serde(default)] |
|||
pub authors: Vec<String>, |
|||
pub script: String, |
|||
pub available_cards: HashMap<String, Card>, |
|||
pub piles: HashMap<String, Pile>, |
|||
pub player_piles: HashMap<String, Pile>, |
|||
} |
|||
|
|||
fn default_version() -> String { |
|||
"0.0.0".into() |
|||
} |
|||
|
|||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] |
|||
pub struct Card { |
|||
pub image: PathBuf, |
|||
#[serde(flatten)] |
|||
pub other: HashMap<String, serde_json::Value>, |
|||
} |
|||
|
|||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] |
|||
pub struct Pile { |
|||
#[serde(default)] |
|||
pub cards: Vec<String>, |
|||
#[serde(flatten)] |
|||
pub other: HashMap<String, serde_json::Value>, |
|||
} |
|||
|
|||
impl Pile { |
|||
pub fn from_rhai_map(map: rhai::Map) -> Result<Self, Box<rhai::EvalAltResult>> { |
|||
// println!("{}", map.get("cards")
|
|||
// .ok_or("Pile doesn't have property cards")?.type_name());
|
|||
let cards: Vec<String> = |
|||
rhai::serde::from_dynamic(map.get("cards").ok_or("Pile doesn't have property cards")?)?; |
|||
|
|||
let other_fallible: Vec<Result<(String, serde_json::Value), Box<rhai::EvalAltResult>>> = |
|||
map.into_iter() |
|||
.map(|(x, v)| (x.to_string(), v)) |
|||
.filter(|(s, _)| s != &"cards".to_string()) |
|||
.map(|(k, v)| Ok((k, rhai::serde::from_dynamic::<serde_json::Value>(&v)?))) |
|||
.collect(); |
|||
|
|||
let mut other = HashMap::new(); |
|||
|
|||
for x in other_fallible { |
|||
let (k, v) = x?; |
|||
other.insert(k, v); |
|||
} |
|||
|
|||
Ok(Self { cards, other }) |
|||
} |
|||
} |
|||
|
|||
impl Config { |
|||
pub fn load<P: AsRef<std::path::Path> + std::fmt::Debug>(file: P) -> Self { |
|||
serde_json::from_reader(std::fs::File::open(&file).unwrap()) |
|||
.map_err(|e| { |
|||
eprintln!( |
|||
"Malformed game defintion file @ {}", |
|||
file.as_ref().display() |
|||
); |
|||
eprintln!("JSON Error: {}", e); |
|||
panic!() |
|||
}) |
|||
.unwrap() |
|||
} |
|||
} |
|||
|
|||
pub fn setup() { |
|||
if cfg!(debug_assertions) { |
|||
if let Ok(e) = std::env::var("CARGO_MANIFEST_DIR") { |
|||
std::fs::write( |
|||
AsRef::<std::path::Path>::as_ref(&e) |
|||
.join("schema") |
|||
.join("game-config.json"), |
|||
serde_json::to_string_pretty(&schemars::schema_for!(Config)).unwrap(), |
|||
) |
|||
.unwrap() |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
use rhai::AST; |
|||
|
|||
use std::fs::read_dir; |
|||
mod config; |
|||
mod run; |
|||
|
|||
#[derive(Clone)] |
|||
pub struct Game { |
|||
name: String, |
|||
version: String, |
|||
authors: Vec<String>, |
|||
ast: AST, |
|||
conf: config::Config, |
|||
} |
|||
|
|||
|
|||
impl Game { |
|||
pub fn load<P: AsRef<std::path::Path> + std::fmt::Debug>(folder: P) -> Self { |
|||
// config::setup();
|
|||
|
|||
let conf = config::Config::load(folder.as_ref().join("game.json")); |
|||
|
|||
let ast = rhai::Engine::new().compile_file(folder.as_ref().join(&conf.script)).unwrap(); |
|||
println!("AST: {:?}", ast); |
|||
Self { conf: conf.clone(), name: conf.name, version: conf.version, authors: conf.authors, ast } |
|||
} |
|||
|
|||
// pub fn name(&self) -> String {
|
|||
// self.name.clone()
|
|||
// }
|
|||
// pub fn version(&self) -> String {
|
|||
// self.version.clone()
|
|||
// }
|
|||
// pub fn authors(&self) -> Vec<String> {
|
|||
// self.authors.clone()
|
|||
// }
|
|||
|
|||
pub fn run(&self, players: u32) -> run::RunningGame { |
|||
run::RunningGame::new(self.ast.clone(), &self.conf, players) |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Display for Game { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "{} [{}]{}", self.name, self.version, if self.authors.is_empty() {String::new()} else {format!(" by {}", self.authors.join(", "))}) |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Debug for Game { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "{}", self) |
|||
} |
|||
} |
|||
|
|||
pub fn load_games() -> Vec<Game> { |
|||
config::setup(); |
|||
let mut games = Vec::new(); |
|||
for file in read_dir("games").unwrap() { |
|||
if let Ok(folder) = file { |
|||
if folder.path().is_dir() { |
|||
for file in read_dir(folder.path()).unwrap() { |
|||
if let Ok(file) = file { |
|||
if file.file_name().to_str().unwrap() == "game.json" { |
|||
games.push(Game::load(folder.path())) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
games |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
use rand::seq::SliceRandom; |
|||
use rhai::{ |
|||
serde::{from_dynamic, to_dynamic}, |
|||
Dynamic, Engine, Func, Map, RegisterResultFn, AST, |
|||
}; |
|||
use serde::{Deserialize, Serialize}; |
|||
|
|||
use std::collections::HashMap; |
|||
|
|||
use super::config::{Config, Pile}; |
|||
|
|||
fn shuffle_pile(pile: Map) -> Result<Dynamic, Box<rhai::EvalAltResult>> { |
|||
let mut pile = Pile::from_rhai_map(pile)?; |
|||
let mut rng = rand::thread_rng(); |
|||
pile.cards.shuffle(&mut rng); |
|||
to_dynamic(pile) |
|||
} |
|||
|
|||
|
|||
#[derive(Debug, Serialize, Deserialize, Clone)] |
|||
struct Setup { |
|||
piles: HashMap<String, Pile>, |
|||
player_piles: Vec<HashMap<String, Pile>>, |
|||
players: u32, |
|||
} |
|||
|
|||
pub struct RunningGame { |
|||
piles: HashMap<String, Pile>, |
|||
player_piles: Vec<HashMap<String, Pile>>, |
|||
} |
|||
|
|||
impl RunningGame { |
|||
pub fn new(ast: AST, conf: &Config, players: u32) -> Self { |
|||
let mut engine = Engine::new(); |
|||
engine.register_result_fn("shuffle", shuffle_pile); |
|||
let setup = Func::<(Dynamic,), Dynamic>::create_from_ast(engine, ast, "setup"); |
|||
|
|||
let piles = conf.piles.clone(); |
|||
let player_piles = vec![conf.player_piles.clone(); players as usize]; |
|||
let Setup { |
|||
piles, |
|||
player_piles, |
|||
players: _, |
|||
} = from_dynamic( |
|||
&setup( |
|||
to_dynamic(Setup { |
|||
piles, |
|||
player_piles, |
|||
players, |
|||
}) |
|||
.unwrap(), |
|||
) |
|||
.unwrap(), |
|||
) |
|||
.unwrap(); |
|||
Self { |
|||
piles, |
|||
player_piles, |
|||
} |
|||
// Self {setup: Box::new(Func::<(Vec<Pile>, Vec<Vec<Pile>>), ()>::create_from_ast(engine, ast, "setup"))}
|
|||
} |
|||
|
|||
// pub fn setup(&self) {
|
|||
// (self.setup)().unwrap()
|
|||
// }
|
|||
} |
|||
@ -1,10 +1,88 @@ |
|||
pub mod game; |
|||
pub mod game_grpc; |
|||
use tonic::{transport::Server, Request, Response, Status}; |
|||
|
|||
// use grpc::ServerBuilder;
|
|||
use std::sync::Arc; |
|||
|
|||
// pub fn start() {
|
|||
// // let mut s = ServerBuilder::new();
|
|||
// // s.add_service(game_grpc::ConnectionServer::new_service_def(handler))
|
|||
// // let server = s.build().unwrap();
|
|||
// }
|
|||
mod game; |
|||
|
|||
use game::connection_server::{Connection, ConnectionServer}; |
|||
use game::{LobbyCode, Null, UserId, Username}; |
|||
|
|||
use crate::db; |
|||
|
|||
pub struct ConnectionService { |
|||
conn: Arc<db::DbPool>, |
|||
} |
|||
|
|||
#[tonic::async_trait] |
|||
impl Connection for ConnectionService { |
|||
async fn connect(&self, request: Request<Username>) -> Result<Response<UserId>, Status> { |
|||
let name = request.into_inner().name; |
|||
let mut conn = self.conn.acquire().await; |
|||
let uuid = conn.add_user(&name).await; |
|||
println!("Connected {}[{}]", name, uuid); |
|||
conn.close().await; |
|||
Ok(Response::new(UserId { |
|||
id: uuid.to_hyphenated().to_string(), |
|||
})) |
|||
} |
|||
|
|||
async fn join_lobby_with_code( |
|||
&self, |
|||
request: Request<LobbyCode>, |
|||
) -> Result<Response<Null>, Status> { |
|||
let uuid = client_id::get(request.metadata()).map_err(|x| match x { |
|||
client_id::Error::NotSet => Status::failed_precondition("client_id must be set"), |
|||
client_id::Error::MalformedUuid => Status::failed_precondition("malformed client_id") |
|||
})?; |
|||
let lobby = request.get_ref().code; |
|||
let mut conn = self.conn.acquire().await; |
|||
conn.join_lobby(uuid, lobby).await; |
|||
conn.close().await; |
|||
Ok(Response::new(Null{})) |
|||
} |
|||
|
|||
async fn join_lobby_without_code( |
|||
&self, |
|||
request: Request<Null>, |
|||
) -> Result<Response<LobbyCode>, Status> { |
|||
let uuid = client_id::get(request.metadata()).map_err(|x| match x { |
|||
client_id::Error::NotSet => Status::failed_precondition("client_id must be set"), |
|||
client_id::Error::MalformedUuid => Status::failed_precondition("malformed client_id") |
|||
})?; |
|||
let mut conn = self.conn.acquire().await; |
|||
let lobby = conn.create_lobby().await; |
|||
conn.join_lobby(uuid, lobby).await; |
|||
conn.close().await; |
|||
Ok(Response::new(LobbyCode{code: lobby})) |
|||
} |
|||
} |
|||
|
|||
pub async fn start(pool: db::DbPool) { |
|||
let arc = Arc::new(pool); |
|||
let connection = ConnectionService { conn: arc.clone() }; |
|||
|
|||
Server::builder() |
|||
.add_service(ConnectionServer::new(connection)) |
|||
.serve("0.0.0.0:50052".parse().unwrap()) |
|||
.await |
|||
.unwrap(); |
|||
} |
|||
|
|||
mod client_id { |
|||
pub fn get( |
|||
metadata: &tonic::metadata::MetadataMap, |
|||
) -> Result<uuid::Uuid, Error> { |
|||
metadata |
|||
.get("client_id") |
|||
.ok_or(Error::NotSet) |
|||
.and_then(|x| { |
|||
uuid::Uuid::parse_str(x.to_str().map_err(|_| Error::MalformedUuid)?) |
|||
.map_err(|_| Error::MalformedUuid) |
|||
}) |
|||
} |
|||
|
|||
pub enum Error { |
|||
MalformedUuid, |
|||
NotSet, |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -1,234 +0,0 @@ |
|||
// This file is generated. Do not edit
|
|||
// @generated
|
|||
|
|||
// https://github.com/Manishearth/rust-clippy/issues/702
|
|||
#![allow(unknown_lints)] |
|||
#![allow(clippy::all)] |
|||
|
|||
#![cfg_attr(rustfmt, rustfmt_skip)] |
|||
|
|||
#![allow(box_pointers)] |
|||
#![allow(dead_code)] |
|||
#![allow(missing_docs)] |
|||
#![allow(non_camel_case_types)] |
|||
#![allow(non_snake_case)] |
|||
#![allow(non_upper_case_globals)] |
|||
#![allow(trivial_casts)] |
|||
#![allow(unsafe_code)] |
|||
#![allow(unused_imports)] |
|||
#![allow(unused_results)] |
|||
|
|||
|
|||
// server interface
|
|||
|
|||
pub trait Connection { |
|||
fn join_lobby_with_code(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::LobbyCode>, resp: ::grpc::ServerResponseUnarySink<super::game::Result>) -> ::grpc::Result<()>; |
|||
|
|||
fn join_lobby_without_code(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::Null>, resp: ::grpc::ServerResponseUnarySink<super::game::LobbyCode>) -> ::grpc::Result<()>; |
|||
} |
|||
|
|||
// client
|
|||
|
|||
pub struct ConnectionClient { |
|||
grpc_client: ::std::sync::Arc<::grpc::Client>, |
|||
} |
|||
|
|||
impl ::grpc::ClientStub for ConnectionClient { |
|||
fn with_client(grpc_client: ::std::sync::Arc<::grpc::Client>) -> Self { |
|||
ConnectionClient { |
|||
grpc_client: grpc_client, |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl ConnectionClient { |
|||
pub fn join_lobby_with_code(&self, o: ::grpc::RequestOptions, req: super::game::LobbyCode) -> ::grpc::SingleResponse<super::game::Result> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Connection/joinLobbyWithCode"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_unary(o, req, descriptor) |
|||
} |
|||
|
|||
pub fn join_lobby_without_code(&self, o: ::grpc::RequestOptions, req: super::game::Null) -> ::grpc::SingleResponse<super::game::LobbyCode> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Connection/joinLobbyWithoutCode"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_unary(o, req, descriptor) |
|||
} |
|||
} |
|||
|
|||
// server
|
|||
|
|||
pub struct ConnectionServer; |
|||
|
|||
|
|||
impl ConnectionServer { |
|||
pub fn new_service_def<H : Connection + 'static + Sync + Send + 'static>(handler: H) -> ::grpc::rt::ServerServiceDefinition { |
|||
let handler_arc = ::std::sync::Arc::new(handler); |
|||
::grpc::rt::ServerServiceDefinition::new("/game.Connection", |
|||
vec![ |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Connection/joinLobbyWithCode"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerUnary::new(move |ctx, req, resp| (*handler_copy).join_lobby_with_code(ctx, req, resp)) |
|||
}, |
|||
), |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Connection/joinLobbyWithoutCode"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerUnary::new(move |ctx, req, resp| (*handler_copy).join_lobby_without_code(ctx, req, resp)) |
|||
}, |
|||
), |
|||
], |
|||
) |
|||
} |
|||
} |
|||
|
|||
// server interface
|
|||
|
|||
pub trait Lobby { |
|||
fn get_games(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::Null>, resp: ::grpc::ServerResponseSink<super::game::Game>) -> ::grpc::Result<()>; |
|||
|
|||
fn vote(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::Vote>, resp: ::grpc::ServerResponseUnarySink<super::game::Result>) -> ::grpc::Result<()>; |
|||
|
|||
fn ready(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::Null>, resp: ::grpc::ServerResponseUnarySink<super::game::Result>) -> ::grpc::Result<()>; |
|||
|
|||
fn status(&self, o: ::grpc::ServerHandlerContext, req: ::grpc::ServerRequestSingle<super::game::Null>, resp: ::grpc::ServerResponseUnarySink<super::game::LobbyStatus>) -> ::grpc::Result<()>; |
|||
} |
|||
|
|||
// client
|
|||
|
|||
pub struct LobbyClient { |
|||
grpc_client: ::std::sync::Arc<::grpc::Client>, |
|||
} |
|||
|
|||
impl ::grpc::ClientStub for LobbyClient { |
|||
fn with_client(grpc_client: ::std::sync::Arc<::grpc::Client>) -> Self { |
|||
LobbyClient { |
|||
grpc_client: grpc_client, |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl LobbyClient { |
|||
pub fn get_games(&self, o: ::grpc::RequestOptions, req: super::game::Null) -> ::grpc::StreamingResponse<super::game::Game> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/getGames"), |
|||
streaming: ::grpc::rt::GrpcStreaming::ServerStreaming, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_server_streaming(o, req, descriptor) |
|||
} |
|||
|
|||
pub fn vote(&self, o: ::grpc::RequestOptions, req: super::game::Vote) -> ::grpc::SingleResponse<super::game::Result> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/vote"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_unary(o, req, descriptor) |
|||
} |
|||
|
|||
pub fn ready(&self, o: ::grpc::RequestOptions, req: super::game::Null) -> ::grpc::SingleResponse<super::game::Result> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/ready"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_unary(o, req, descriptor) |
|||
} |
|||
|
|||
pub fn status(&self, o: ::grpc::RequestOptions, req: super::game::Null) -> ::grpc::SingleResponse<super::game::LobbyStatus> { |
|||
let descriptor = ::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/status"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}); |
|||
self.grpc_client.call_unary(o, req, descriptor) |
|||
} |
|||
} |
|||
|
|||
// server
|
|||
|
|||
pub struct LobbyServer; |
|||
|
|||
|
|||
impl LobbyServer { |
|||
pub fn new_service_def<H : Lobby + 'static + Sync + Send + 'static>(handler: H) -> ::grpc::rt::ServerServiceDefinition { |
|||
let handler_arc = ::std::sync::Arc::new(handler); |
|||
::grpc::rt::ServerServiceDefinition::new("/game.Lobby", |
|||
vec![ |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/getGames"), |
|||
streaming: ::grpc::rt::GrpcStreaming::ServerStreaming, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerServerStreaming::new(move |ctx, req, resp| (*handler_copy).get_games(ctx, req, resp)) |
|||
}, |
|||
), |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/vote"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerUnary::new(move |ctx, req, resp| (*handler_copy).vote(ctx, req, resp)) |
|||
}, |
|||
), |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/ready"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerUnary::new(move |ctx, req, resp| (*handler_copy).ready(ctx, req, resp)) |
|||
}, |
|||
), |
|||
::grpc::rt::ServerMethod::new( |
|||
::grpc::rt::ArcOrStatic::Static(&::grpc::rt::MethodDescriptor { |
|||
name: ::grpc::rt::StringOrStatic::Static("/game.Lobby/status"), |
|||
streaming: ::grpc::rt::GrpcStreaming::Unary, |
|||
req_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
resp_marshaller: ::grpc::rt::ArcOrStatic::Static(&::grpc_protobuf::MarshallerProtobuf), |
|||
}), |
|||
{ |
|||
let handler_copy = handler_arc.clone(); |
|||
::grpc::rt::MethodHandlerUnary::new(move |ctx, req, resp| (*handler_copy).status(ctx, req, resp)) |
|||
}, |
|||
), |
|||
], |
|||
) |
|||
} |
|||
} |
|||
@ -1,6 +1,17 @@ |
|||
mod grpc; |
|||
mod db; |
|||
mod games; |
|||
|
|||
fn main() { |
|||
#[tokio::main] |
|||
async fn main() { |
|||
// protobuf::route_guide_grpc::RouteGuid
|
|||
println!("Hello, world!"); |
|||
let games = games::load_games(); |
|||
println!("{:?}", games); |
|||
games[0].run(4); |
|||
// let pool = db::DbPool::new().await;
|
|||
// let mut conn = pool.acquire().await;
|
|||
// println!("{}", conn.add_user("Hi").await);
|
|||
// println!("{:?}", conn.users().await);
|
|||
// conn.close().await;
|
|||
// grpc::start(pool).await;
|
|||
} |
|||
|
|||
@ -0,0 +1,34 @@ |
|||
-- Add migration script here |
|||
|
|||
DROP TABLE IF EXISTS `UsersInLobbies`; |
|||
DROP TABLE IF EXISTS `Users`; |
|||
DROP TABLE IF EXISTS `Lobbies`; |
|||
|
|||
|
|||
CREATE TABLE Users ( |
|||
UUID CHAR(16) NOT NULL UNIQUE PRIMARY KEY, |
|||
Name VARCHAR(255) NOT NULL |
|||
-- LobbyID INT NULL, |
|||
-- FOREIGN KEY (LobbyID) REFERENCES Lobbies(ID) |
|||
); |
|||
|
|||
CREATE TABLE Lobbies ( |
|||
ID INT NOT NULL UNIQUE PRIMARY KEY |
|||
); |
|||
|
|||
CREATE TABLE UsersInLobbies ( |
|||
LobbyID INT NOT NULL, |
|||
UserID CHAR(16) NOT NULL UNIQUE, |
|||
FOREIGN KEY (LobbyID) REFERENCES Lobbies(ID), |
|||
FOREIGN KEY (UserID) REFERENCES Users(UUID) |
|||
); |
|||
|
|||
-- INSERT INTO Users(UUID, Name) VALUES("0123456789abcdef", "Hi"); |
|||
-- INSERT INTO Lobbies(ID) VALUES(0); |
|||
-- INSERT INTO Users(UUID, Name) VALUES("0123456789abcdff", "Hi2"); |
|||
|
|||
-- INSERT INTO UsersInLobbies(UserID,LobbyID) VALUES("0123456789abcdff", 0); |
|||
|
|||
-- INSERT INTO Users(UUID, Name) VALUES("0123456789abcdfe", "Hi3"); |
|||
|
|||
-- INSERT INTO UsersInLobbies(UserID,LobbyID) VALUES("0123456789abcdfe", 1); |
|||
@ -0,0 +1,51 @@ |
|||
// using UnityEngine;
|
|||
|
|||
public static class Base32 |
|||
{ |
|||
private const string CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; |
|||
public static string ToString(uint number) |
|||
{ |
|||
uint quotient = number / 32; |
|||
uint remainder = number % 32; |
|||
string res = CHARS[(int)remainder].ToString(); |
|||
// Debug.Log(res);
|
|||
// Debug.Log("Q " + quotient);
|
|||
// Debug.Log("R " + remainder + " > " + res);
|
|||
if (quotient > 0) |
|||
{ |
|||
res = ToString(quotient) + res; |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
public static uint FromString(string s) |
|||
{ |
|||
|
|||
uint res = 0; |
|||
if (s.Length > 0) |
|||
{ |
|||
for (int i = 0; i < s.Length; i++) |
|||
{ |
|||
uint pow = UIntPow(32, (uint)i); |
|||
// Debug.Log();
|
|||
res += ((uint)CHARS.IndexOf(s[s.Length - 1 - i])) * pow; |
|||
// Debug.Log(i + ":" + pow + " | " + s[i] + " " + CHARS.IndexOf(s[s.Length - 1 - i]) + " > " + res);
|
|||
} |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
private static uint UIntPow(uint x, uint pow) |
|||
{ |
|||
uint ret = 1; |
|||
while (pow != 0) |
|||
{ |
|||
if ((pow & 1) == 1) |
|||
ret *= x; |
|||
x *= x; |
|||
pow >>= 1; |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
fileFormatVersion: 2 |
|||
guid: 35e876ff614b5e8f5b1cc702b9502b5c |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|||
@ -1,13 +1,68 @@ |
|||
using UnityEngine; |
|||
using Grpc.Core; |
|||
using Game; |
|||
|
|||
public class Client: MonoBehaviour |
|||
// using UnityEngine;
|
|||
public static class Client |
|||
{ |
|||
public void Run() { |
|||
Channel channel = new Channel("127.0.0.1:50052", ChannelCredentials.Insecure); |
|||
var client = new Connection.ConnectionClient(channel); |
|||
// Client code goes here
|
|||
private static Connection reference; |
|||
|
|||
public static void Connect(string name) |
|||
{ |
|||
reference = new Connection(name); |
|||
} |
|||
|
|||
public static ref Connection GetConnection() |
|||
{ |
|||
return ref reference; |
|||
} |
|||
|
|||
public static void CloseConnection() |
|||
{ |
|||
if (reference != null) |
|||
{ |
|||
reference.Close(); |
|||
reference = null; |
|||
} |
|||
} |
|||
|
|||
public class Connection // : MonoBehaviour
|
|||
{ |
|||
private Game.Connection.ConnectionClient connection; |
|||
private Channel channel; |
|||
private string connId; |
|||
private uint? lobby = null; |
|||
public Connection(string user) |
|||
{ |
|||
channel = new Channel("127.0.0.1:50052", ChannelCredentials.Insecure); |
|||
connection = new Game.Connection.ConnectionClient(channel); |
|||
connId = connection.connect(new Game.Username { Name = user }).Id; |
|||
} |
|||
|
|||
public void JoinLobby(string code) |
|||
{ |
|||
lobby = Base32.FromString(code); |
|||
connection.joinLobbyWithCode(new Game.LobbyCode { Code = (uint)lobby }, new Metadata { new Metadata.Entry("client_id", connId) }); |
|||
} |
|||
|
|||
public string CreateLobby() |
|||
{ |
|||
lobby = connection.joinLobbyWithoutCode(new Game.Null(), new Metadata { new Metadata.Entry("client_id", connId) }).Code; |
|||
return Base32.ToString((uint)lobby); |
|||
} |
|||
|
|||
public string GetLobby() |
|||
{ |
|||
if (lobby != null) |
|||
{ |
|||
return Base32.ToString((uint)lobby); |
|||
} |
|||
else |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public void Close() |
|||
{ |
|||
channel.ShutdownAsync().Wait(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue