From 64f2c27bfc2807e8aaa8010a42ffd65ce3b38f30 Mon Sep 17 00:00:00 2001 From: sparshg <43041139+sparshg@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:34:35 +0530 Subject: [PATCH] improvements --- app/src/lib/state.svelte.ts | 7 +- app/src/routes/+page.svelte | 2 +- .../{0001_battleship.sql => 0001_schema.sql} | 29 ++------- migrations/0002_triggers.sql | 39 +++++++++++ src/game.rs | 64 +++++++++++-------- src/main.rs | 38 ++++++----- 6 files changed, 104 insertions(+), 75 deletions(-) rename migrations/{0001_battleship.sql => 0001_schema.sql} (56%) create mode 100644 migrations/0002_triggers.sql diff --git a/app/src/lib/state.svelte.ts b/app/src/lib/state.svelte.ts index d3f0f7c..e68ed5e 100644 --- a/app/src/lib/state.svelte.ts +++ b/app/src/lib/state.svelte.ts @@ -12,15 +12,16 @@ export class State { turn = $state(-1); // -1 not my turn, 0 might be, 1 is socket: Socket; - constructor(hostname: string) { - let session = sessionStorage.getItem('session'); + constructor(hostname: string, newSession: boolean = true) { + // let session = sessionStorage.getItem('session'); this.socket = io(`ws://${hostname}:3000/`, { transports: ['websocket'], - auth: { session } + auth: { session: !newSession ? sessionStorage.getItem('session') : null } }); this.socket.on('connect', () => { + console.log(this.socket.id); sessionStorage.setItem('session', this.socket.id!); }); diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index e8ab788..026446a 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -43,7 +43,7 @@ class="btn btn-error text-xl" onclick={() => { gameState.socket.emit('leave'); - gameState = new State(window.location.hostname); + gameState = new State(window.location.hostname, true); }}>Leave diff --git a/migrations/0001_battleship.sql b/migrations/0001_schema.sql similarity index 56% rename from migrations/0001_battleship.sql rename to migrations/0001_schema.sql index fcfa698..ebb0ec4 100644 --- a/migrations/0001_battleship.sql +++ b/migrations/0001_schema.sql @@ -4,7 +4,9 @@ CREATE TYPE STAT AS ENUM ('waiting', 'p1turn', 'p2turn'); CREATE TABLE IF NOT EXISTS players ( id CHAR(16) PRIMARY KEY, board CHAR(10) [10], - room_code CHAR(4) NOT NULL + room_code CHAR(4) NOT NULL, + abandoned BOOLEAN DEFAULT FALSE NOT NULL, + time TIMESTAMP DEFAULT NOW() NOT NULL ); CREATE TABLE IF NOT EXISTS rooms ( @@ -24,12 +26,6 @@ CREATE TABLE IF NOT EXISTS rooms ( ) ); -CREATE TABLE IF NOT EXISTS abandoned_players ( - time TIMESTAMP PRIMARY KEY, - id CHAR(16) NOT NULL, - CONSTRAINT fk_player_id FOREIGN KEY (id) REFERENCES players (id) ON DELETE CASCADE ON UPDATE CASCADE -); - ALTER TABLE players ADD CONSTRAINT fk_room_code FOREIGN KEY (room_code) REFERENCES rooms (code) ON DELETE SET NULL; @@ -40,22 +36,5 @@ SET NULL ON UPDATE CASCADE, ADD CONSTRAINT fk_player2 FOREIGN KEY (player2_id) REFERENCES players (id) ON DELETE SET NULL ON UPDATE CASCADE; --- delete room if both players are null -CREATE OR REPLACE FUNCTION delete_room() RETURNS TRIGGER AS $$ BEGIN IF ( - SELECT player1_id IS NULL - AND player2_id IS NULL - FROM rooms - WHERE code = OLD.room_code - ) THEN -DELETE FROM rooms -WHERE code = OLD.room_code; -END IF; -RETURN OLD; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER delete_room_trigger -AFTER DELETE ON players FOR EACH ROW EXECUTE FUNCTION delete_room(); - CREATE INDEX idx_player_room_code ON players (room_code); -CREATE INDEX idx_room_status ON rooms (stat); \ No newline at end of file +CREATE INDEX idx_room_status ON players (abandoned); \ No newline at end of file diff --git a/migrations/0002_triggers.sql b/migrations/0002_triggers.sql new file mode 100644 index 0000000..d8ebdf7 --- /dev/null +++ b/migrations/0002_triggers.sql @@ -0,0 +1,39 @@ +-- delete room if both players are null +CREATE OR REPLACE FUNCTION delete_room() RETURNS TRIGGER AS $$ BEGIN IF ( + SELECT player1_id IS NULL + AND player2_id IS NULL + FROM rooms + WHERE code = OLD.room_code + ) THEN +DELETE FROM rooms +WHERE code = OLD.room_code; +END IF; +RETURN OLD; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER delete_room_trigger +AFTER DELETE ON players FOR EACH ROW EXECUTE FUNCTION delete_room(); + + +-- retain only 1000 recent abandoned players according to timestamp +CREATE OR REPLACE FUNCTION delete_player() RETURNS TRIGGER AS $$ BEGIN IF ( + SELECT COUNT(*) + FROM players + WHERE abandoned = TRUE + ) > 10000 THEN +DELETE FROM players +WHERE id IN ( + SELECT id + FROM players + WHERE abandoned = TRUE + ORDER BY time DESC OFFSET 10000 + ); +END IF; +RETURN OLD; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER delete_player_trigger +AFTER +INSERT ON players FOR EACH ROW EXECUTE FUNCTION delete_player(); \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index 2048c96..d56ff68 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,4 @@ +use rand::Rng; use serde::Serialize; use socketioxide::socket::Sid; use thiserror::Error; @@ -20,6 +21,8 @@ pub enum Error { NotInRoom, #[error("Invalid Move")] InvalidMove, + #[error("Code Generation Limit Reached")] + CodeGenerationLimitReached, #[error("SQL Error\n{0:?}")] Sqlx(#[from] sqlx::Error), } @@ -41,8 +44,28 @@ pub async fn room_if_player_exists(sid: &str, pool: &sqlx::PgPool) -> Result Result<()> { +async fn generate_code(pool: &sqlx::PgPool) -> Result { + for _ in 0..50 { + let code: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(ROOM_CODE_LENGTH) + .map(|x| char::to_ascii_uppercase(&(x as char))) + .collect(); + if sqlx::query!(r"SELECT code FROM rooms WHERE code = $1", code) + .fetch_optional(pool) + .await? + .is_none() + { + return Ok(code); + } + } + Err(Error::CodeGenerationLimitReached) +} + +pub async fn add_room(sid: Sid, pool: &sqlx::PgPool) -> Result { delete_sid(sid.as_str(), pool).await?; + let code = generate_code(&pool).await?; + sqlx::query!( r"WITH new_user AS (INSERT INTO players (id, room_code) VALUES ($1, $2) RETURNING id) INSERT INTO rooms (player1_id, code) SELECT $1, $2 FROM new_user", sid.as_str(), @@ -50,7 +73,7 @@ pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> ) .execute(pool) .await?; - Ok(()) + Ok(code) } pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> { @@ -286,9 +309,13 @@ pub async fn attack( } pub async fn update_sid(oldsid: &str, newsid: &str, pool: &sqlx::PgPool) -> Result<()> { - sqlx::query!(r"UPDATE players SET id = $1 WHERE id = $2", newsid, oldsid) - .execute(pool) - .await?; + sqlx::query!( + r"UPDATE players SET id = $1, abandoned = FALSE WHERE id = $2", + newsid, + oldsid + ) + .execute(pool) + .await?; Ok(()) } @@ -300,32 +327,17 @@ pub async fn delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> { } pub async fn to_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> { - sqlx::query!( - r"INSERT INTO abandoned_players (time, id) VALUES (NOW(), $1)", - sid - ) - .execute(pool) - .await?; + sqlx::query!(r"UPDATE players SET abandoned = TRUE WHERE id = $1", sid) + .execute(pool) + .await?; Ok(()) } pub async fn in_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result { Ok( - sqlx::query!(r"SELECT id FROM abandoned_players WHERE id = $1", sid) - .fetch_optional(pool) + sqlx::query!(r"SELECT abandoned FROM players WHERE id = $1", sid) + .fetch_one(pool) .await? - .is_some(), + .abandoned, ) } - -pub async fn delete_abandoned(pool: &sqlx::PgPool) -> Result<()> { - sqlx::query!( - r"DELETE FROM players - WHERE id IN (SELECT id FROM abandoned_players - ORDER BY time DESC - OFFSET 1000)" - ) - .execute(pool) - .await?; - Ok(()) // TODO: REMOVE duliplcates id from abandoned -} diff --git a/src/main.rs b/src/main.rs index 5b4e45f..ba2d60e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,10 +32,6 @@ async fn main() -> Result<(), Box> { let pool = sqlx::postgres::PgPool::connect(&url).await?; sqlx::migrate!("./migrations").run(&pool).await?; sqlx::query("DELETE FROM players").execute(&pool).await?; - sqlx::query("DELETE FROM abandoned_players") - .execute(&pool) - .await?; - sqlx::query("DELETE FROM rooms").execute(&pool).await?; let (layer, io) = SocketIo::builder().with_state(pool).build_layer(); io.ns("/", on_connect); @@ -90,18 +86,15 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat return; } - let room: String = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(ROOM_CODE_LENGTH) - .map(|x| char::to_ascii_uppercase(&(x as char))) - .collect(); - tracing::info!("Creating room: {:?}", room); - // TODO: Handle duplicates + let room = match add_room(socket.id, &pool).await { + Err(e) => { + tracing::error!("{:?}", e); + return; + } + Ok(c) => c, + }; - if let Err(e) = add_room(socket.id, room.clone(), &pool).await { - tracing::error!("{:?}", e); - return; - } + tracing::info!("Creating room: {:?}", room); socket.leave_all().unwrap(); socket.join(room.clone()).unwrap(); emit_update_room( @@ -119,7 +112,7 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat return; } tracing::info!("Joining room: {:?}", room); - let room_error = join_room(socket.id, room.clone(), &pool).await; + let room_error = join_room(socket.id, room.clone(), &pool).await; if let Err(e) = &room_error { if let Error::RoomFull(Some(player)) = &e { tracing::warn!("{:?}", e); @@ -202,17 +195,17 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat "leave", |socket: SocketRef, pool: State| async move { tracing::info!("Leaving Rooms: {:?}", socket.id); - leave_and_inform(&socket, &pool).await; + leave_and_inform(&socket, &pool, true).await; }, ); socket.on_disconnect(|socket: SocketRef, pool: State| async move { tracing::info!("Disconnecting: {:?}", socket.id); - leave_and_inform(&socket, &pool).await; + leave_and_inform(&socket, &pool, false).await; }); } -async fn leave_and_inform(socket: &SocketRef, pool: &PgPool) { +async fn leave_and_inform(socket: &SocketRef, pool: &PgPool, delete: bool) { let room = socket .rooms() .unwrap() @@ -225,7 +218,12 @@ async fn leave_and_inform(socket: &SocketRef, pool: &PgPool) { let ops = socket.within(room.clone()); socket.leave_all().unwrap(); emit_update_room(socket, &room.to_string(), ops.sockets().unwrap().len()); - if let Err(e) = to_delete_sid(socket.id.as_str(), pool).await { + let sid = socket.id.as_str(); + if let Err(e) = if delete { + delete_sid(sid, &pool).await + } else { + to_delete_sid(sid, &pool).await + } { tracing::error!("{:?}", e); } }