2022-03-18 10:29:38 +00:00
use std ::sync ::mpsc ::{ Receiver , SyncSender } ;
use std ::thread ;
use rand ::{ thread_rng , Rng } ;
2022-03-29 08:02:57 +00:00
use rusqlite ::{ params , Connection } ;
2022-03-18 10:29:38 +00:00
2022-03-29 06:02:20 +00:00
pub type GetIDSenderType = ( u32 , Option < bool > ) ;
2022-03-29 08:02:57 +00:00
/// first bool is player exists,
/// second bool is if paired,
/// third bool is if cyan player
pub type CheckPairingType = ( bool , bool , bool ) ;
2022-03-29 06:02:20 +00:00
2022-03-28 07:31:53 +00:00
#[ derive(Clone, Debug) ]
pub enum DBHandlerRequest {
2022-03-29 06:02:20 +00:00
GetID ( SyncSender < GetIDSenderType > ) ,
2022-03-29 08:02:57 +00:00
CheckPairing {
id : u32 ,
response_sender : SyncSender < CheckPairingType > ,
} ,
2022-03-28 07:31:53 +00:00
}
2022-03-18 10:29:38 +00:00
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
enum DBFirstRun {
FirstRun ,
NotFirstRun ,
}
2022-03-29 06:02:20 +00:00
struct DBHandler {
rx : Receiver < DBHandlerRequest > ,
sqlite_path : String ,
shutdown_tx : SyncSender < ( ) > ,
}
impl DBHandler {
/// Returns true if should break out of outer loop
fn handle_request ( & mut self ) -> bool {
let rx_recv_result = self . rx . recv ( ) ;
if let Err ( e ) = rx_recv_result {
println! ( " Failed to get DBHandlerRequest: {:?} " , e ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return false ;
}
let db_request = rx_recv_result . unwrap ( ) ;
match db_request {
DBHandlerRequest ::GetID ( player_tx ) = > {
// got request to create new player, create new player
let mut player_id : u32 = thread_rng ( ) . gen ( ) ;
2022-03-29 06:05:23 +00:00
let conn_result = self . get_conn ( DBFirstRun ::NotFirstRun ) ;
2022-03-29 06:02:20 +00:00
if let Err ( e ) = conn_result {
println! ( " Failed to get sqlite db connection: {:?} " , e ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return false ;
}
let conn = conn_result . unwrap ( ) ;
loop {
2022-03-29 08:10:12 +00:00
let exists_result = self . check_if_player_exists ( Some ( & conn ) , player_id ) ;
if let Ok ( exists ) = exists_result {
if exists {
2022-03-29 06:02:20 +00:00
player_id = thread_rng ( ) . gen ( ) ;
2022-03-29 08:10:12 +00:00
} else {
break ;
2022-03-29 06:02:20 +00:00
}
2022-03-29 08:10:12 +00:00
} else {
let error = exists_result . unwrap_err ( ) ;
println! ( " Failed to check if player exists in db: {:?} " , error ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return true ;
2022-03-29 06:02:20 +00:00
}
}
let insert_result = conn . execute (
" INSERT INTO players (id, date_added) VALUES (?, datetime()); " ,
[ player_id ] ,
) ;
if let Err ( e ) = insert_result {
println! ( " Failed to insert into sqlite db: {:?} " , e ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return true ;
}
2022-03-29 08:02:57 +00:00
let pair_up_result = self . pair_up_players ( Some ( & conn ) ) ;
if let Err ( e ) = pair_up_result {
println! ( " Failed to pair up players: {} " , e ) ;
return true ;
}
// Check if current player has been paired
let mut is_cyan_player_opt : Option < bool > = None ;
let check_player_row = conn . query_row ( " SELECT games.cyan_player FROM players JOIN games WHERE games.id = players.game_id AND players.id = ?; " , [ player_id ] , | row | row . get ::< usize , u32 > ( 0 ) ) ;
if let Ok ( cyan_player ) = check_player_row {
if cyan_player = = player_id {
// is paired, is cyan_player
is_cyan_player_opt = Some ( true ) ;
} else {
// is paired, not cyan_player
is_cyan_player_opt = Some ( false ) ;
}
} else if check_player_row . is_err ( ) {
// not paired, can do nothing here
}
let send_result = player_tx . send ( ( player_id , is_cyan_player_opt ) ) ;
2022-03-29 06:02:20 +00:00
if let Err ( e ) = send_result {
println! ( " Failed to send back player id: {:?} " , e ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return true ;
}
send_result . unwrap ( ) ;
2022-03-29 08:02:57 +00:00
}
DBHandlerRequest ::CheckPairing {
id ,
response_sender ,
} = > {
let check_result = self . check_if_player_is_paired ( id ) ;
if let Ok ( ( exists , is_paired , is_cyan ) ) = check_result {
let send_result = response_sender . send ( ( exists , is_paired , is_cyan ) ) ;
if let Err ( e ) = send_result {
println! ( " Failed to send back check pairing status: {:?} " , e ) ;
self . shutdown_tx . send ( ( ) ) . ok ( ) ;
return true ;
}
send_result . unwrap ( ) ;
} else {
}
2022-03-29 06:02:20 +00:00
} // DBHandlerRequest::GetID(player_tx)
} // match db_request
false
}
2022-03-29 06:05:23 +00:00
fn get_conn ( & self , first_run : DBFirstRun ) -> Result < Connection , String > {
if let Ok ( conn ) = Connection ::open ( & self . sqlite_path ) {
conn . execute ( " PRAGMA foreign_keys = ON; " , [ ] )
. map_err ( | e | format! ( " Should be able to handle \" foreign_keys \" : {:?} " , e ) ) ? ;
let result = conn . execute (
"
CREATE TABLE players ( id INTEGER PRIMARY KEY NOT NULL ,
date_added TEXT NOT NULL ,
game_id INTEGER ,
FOREIGN KEY ( game_id ) REFERENCES games ( id ) ON DELETE CASCADE ) ;
" ,
[ ] ,
) ;
if result . is_ok ( ) {
if first_run = = DBFirstRun ::FirstRun {
println! ( " Created \" players \" table " ) ;
}
} else if first_run = = DBFirstRun ::FirstRun {
println! ( " \" players \" table exists " ) ;
2022-03-18 10:29:38 +00:00
}
2022-03-29 06:05:23 +00:00
let result = conn . execute (
"
CREATE TABLE games ( id INTEGER PRIMARY KEY NOT NULL ,
cyan_player INTEGER UNIQUE ,
magenta_player INTEGER UNIQUE ,
date_added TEXT NOT NULL ,
board TEXT NOT NULL ,
status INTEGER NOT NULL ,
FOREIGN KEY ( cyan_player ) REFERENCES players ( id ) ,
FOREIGN KEY ( magenta_player ) REFERENCES players ( id ) ) ;
" ,
[ ] ,
) ;
if result . is_ok ( ) {
if first_run = = DBFirstRun ::FirstRun {
println! ( " Created \" games \" table " ) ;
}
} else if first_run = = DBFirstRun ::FirstRun {
println! ( " \" games \" table exists " ) ;
2022-03-18 10:29:38 +00:00
}
2022-03-29 06:05:23 +00:00
Ok ( conn )
} else {
Err ( String ::from ( " Failed to open connection " ) )
2022-03-18 10:29:38 +00:00
}
}
2022-03-29 08:02:57 +00:00
fn pair_up_players ( & self , conn : Option < & Connection > ) -> Result < ( ) , String > {
if let Some ( conn ) = conn {
let mut to_pair : Option < u32 > = None ;
let mut unpaired_players_stmt = conn
. prepare ( " SELECT id FROM players WHERE game_id ISNULL ORDER BY date_added; " )
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
let mut unpaired_players_rows = unpaired_players_stmt
. query ( [ ] )
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
while let Some ( row ) = unpaired_players_rows
. next ( )
. map_err ( | e | format! ( " {:?} " , e ) ) ?
{
if to_pair . is_none ( ) {
to_pair = Some ( row . get ( 0 ) . map_err ( | e | format! ( " {:?} " , e ) ) ? ) ;
} else {
let players : [ u32 ; 2 ] = [
to_pair . take ( ) . unwrap ( ) ,
row . get ( 0 ) . map_err ( | e | format! ( " {:?} " , e ) ) ? ,
] ;
self . create_game ( Some ( conn ) , & players ) ? ;
}
}
Ok ( ( ) )
} else {
let conn = self . get_conn ( DBFirstRun ::NotFirstRun ) ? ;
self . pair_up_players ( Some ( & conn ) )
}
}
fn create_game ( & self , conn : Option < & Connection > , players : & [ u32 ; 2 ] ) -> Result < u32 , String > {
if let Some ( conn ) = conn {
let mut game_id : u32 = thread_rng ( ) . gen ( ) ;
{
let mut get_game_stmt = conn
. prepare ( " SELECT id FROM games WHERE id = ?; " )
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
while get_game_stmt . query_row ( [ game_id ] , | _row | Ok ( ( ) ) ) . is_ok ( ) {
game_id = thread_rng ( ) . gen ( ) ;
}
}
// TODO randomize players (or first-come-first-serve ok to do?)
conn . execute (
" INSERT INTO games (id, cyan_player, magenta_player, date_added, board, status) VALUES (?, ?, ?, datetime(), ?, 0); " ,
params! [ game_id , players [ 0 ] , players [ 1 ] , new_board ( ) ]
)
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
conn . execute (
" UPDATE players SET game_id = ? WHERE id = ? " ,
[ game_id , players [ 0 ] ] ,
)
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
conn . execute (
" UPDATE players SET game_id = ? WHERE id = ? " ,
[ game_id , players [ 1 ] ] ,
)
. map_err ( | e | format! ( " {:?} " , e ) ) ? ;
Ok ( game_id )
} else {
let conn = self . get_conn ( DBFirstRun ::NotFirstRun ) ? ;
self . create_game ( Some ( & conn ) , players )
}
}
fn check_if_player_is_paired ( & self , player_id : u32 ) -> Result < CheckPairingType , String > {
{
2022-03-29 08:10:12 +00:00
let player_exists_result = self . check_if_player_exists ( None , player_id ) ;
2022-03-29 08:02:57 +00:00
if player_exists_result . is_err ( ) | | ! player_exists_result . unwrap ( ) {
// player doesn't exist
return Ok ( ( false , false , true ) ) ;
}
}
let conn = self . get_conn ( DBFirstRun ::NotFirstRun ) ? ;
let check_player_row = conn . query_row ( " SELECT games.cyan_player FROM players JOIN games where games.id = players.game_id AND players.id = ?; " , [ player_id ] , | row | row . get ::< usize , u32 > ( 0 ) ) ;
if let Ok ( cyan_player ) = check_player_row {
if cyan_player = = player_id {
// is cyan player
Ok ( ( true , true , true ) )
} else {
// is magenta player
Ok ( ( true , true , false ) )
}
} else if let Err ( rusqlite ::Error ::QueryReturnedNoRows ) = check_player_row {
// is not paired
Ok ( ( true , false , true ) )
} else if let Err ( e ) = check_player_row {
Err ( format! ( " check_if_player_is_paired: {:?} " , e ) )
} else {
unreachable! ( ) ;
}
}
2022-03-29 08:10:12 +00:00
fn check_if_player_exists (
& self ,
conn : Option < & Connection > ,
player_id : u32 ,
) -> Result < bool , String > {
if let Some ( conn ) = conn {
let check_player_row =
conn . query_row ( " SELECT id FROM players WHERE id = ?; " , [ player_id ] , | row | {
row . get ::< usize , u32 > ( 0 )
} ) ;
if let Ok ( _id ) = check_player_row {
Ok ( true )
} else {
Ok ( false )
}
2022-03-29 08:02:57 +00:00
} else {
2022-03-29 08:10:12 +00:00
let conn = self . get_conn ( DBFirstRun ::NotFirstRun ) ? ;
self . check_if_player_exists ( Some ( & conn ) , player_id )
2022-03-29 08:02:57 +00:00
}
}
2022-03-18 10:29:38 +00:00
}
2022-03-18 14:43:15 +00:00
pub fn start_db_handler_thread (
2022-03-28 07:31:53 +00:00
rx : Receiver < DBHandlerRequest > ,
2022-03-18 14:43:15 +00:00
sqlite_path : String ,
shutdown_tx : SyncSender < ( ) > ,
) {
2022-03-29 06:02:20 +00:00
let mut handler = DBHandler {
rx ,
sqlite_path ,
shutdown_tx ,
} ;
2022-03-18 10:29:38 +00:00
thread ::spawn ( move | | {
// temporarily get conn which should initialize on first setup of db
2022-03-29 06:05:23 +00:00
if let Ok ( _conn ) = handler . get_conn ( DBFirstRun ::FirstRun ) {
2022-03-18 10:29:38 +00:00
} else {
println! ( " ERROR: Failed init sqlite db connection " ) ;
2022-03-29 06:02:20 +00:00
handler . shutdown_tx . send ( ( ) ) . ok ( ) ;
2022-03-18 10:29:38 +00:00
return ;
}
2022-03-18 14:43:15 +00:00
' outer : loop {
2022-03-29 06:02:20 +00:00
if handler . handle_request ( ) {
handler . shutdown_tx . send ( ( ) ) . ok ( ) ;
break 'outer ;
2022-03-18 10:29:38 +00:00
}
2022-03-29 06:02:20 +00:00
}
2022-03-18 10:29:38 +00:00
} ) ;
}
2022-03-29 08:02:57 +00:00
fn new_board ( ) -> String {
let mut board = String ::with_capacity ( 56 ) ;
for _i in 0 .. 56 {
board . push ( 'a' ) ;
}
board
}