Skip to content

Commit

Permalink
Refactor game_state into directory to remove game_id
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanRJohnston committed Aug 30, 2024
1 parent 19f05a6 commit f356a36
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 271 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@
"CARGO_TARGET_DIR": "target/rust-analyzer"
},
"rust-analyzer.check.extraArgs": ["--target-dir=target/rust-analyzer"]
// "rust-analyzer.cargo.target": "wasm32-unknown-unknown"
// "rust-analyzer.cargo.noDefaultFeatures": true,
// "rust-analyzer.cargo.features": ["app/ssr", "app/wasm"]
}
23 changes: 14 additions & 9 deletions app/src/adapters/game_state/durable_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use worker::{durable_object, Env, State, Storage, WebSocketPair};

use crate::adapters::event_log::durable_object::DurableObjectKeyValue;
use crate::ports::event_log::EventLog;
use crate::ports::game_state::GameState;
use crate::ports::game_state::{GameDirectory, GameState};
use crate::router::into_game_router;

#[derive(Clone)]
Expand Down Expand Up @@ -44,19 +44,17 @@ impl DurableObject for Game {

pub async fn alarm(&mut self) -> worker::Result<worker::Response> {
let events = self.events.vector().await.map_err(|err| err.to_string())?;
// TODO: Remove this, GameState should be produced by a factory that takes GameID instead;
let _game_id = GameID::random();

let (events, alarm) = run_processors(&events).map_err(|err| err.to_string())?;

for event in events {
self.push_event(_game_id, event)
self.push_event(event)
.await
.map_err(|err| err.to_string())?;
}

if let Some(Alarm(duration)) = alarm {
self.set_alarm(_game_id, duration)
self.set_alarm(duration)
.await
.map_err(|err| err.to_string())?;
}
Expand All @@ -67,11 +65,11 @@ impl DurableObject for Game {

// DurableObject Game State Can Ignore the GameID parameter as there is one per game
impl GameState for Game {
async fn events(&self, _: GameID) -> anyhow::Result<im::Vector<Event>> {
async fn events(&self) -> anyhow::Result<im::Vector<Event>> {
SendFuture::new(async { Ok(self.events.vector().await?) }).await
}

async fn push_event(&self, _: GameID, event: Event) -> anyhow::Result<()> {
async fn push_event(&self, event: Event) -> anyhow::Result<()> {
SendFuture::new(async {
self.events.push(event.clone()).await;

Expand All @@ -84,7 +82,7 @@ impl GameState for Game {
.await
}

fn accept_web_socket(&self, _: GameID) -> anyhow::Result<WebSocketPair> {
fn accept_web_socket(&self) -> anyhow::Result<WebSocketPair> {
let pair = WebSocketPair::new()?;
self.state.accept_web_socket(&pair.server);

Expand All @@ -93,7 +91,6 @@ impl GameState for Game {

fn set_alarm(
&self,
_: GameID,
duration: std::time::Duration,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>> + Send + '_>> {
Box::pin(async move {
Expand All @@ -112,3 +109,11 @@ impl GameState for Game {
})
}
}

impl GameDirectory for Game {
type GameState = Game;

async fn get(&self, _: GameID) -> Self::GameState {
self.clone()
}
}
76 changes: 36 additions & 40 deletions app/src/adapters/game_state/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ use tracing::instrument;

use crate::{
adapters::event_log::file::FileEventLog,
ports::{event_log::EventLog, game_state::GameState},
ports::{
event_log::EventLog,
game_state::{GameDirectory, GameState},
},
};

struct Game {
struct InnerGame {
events: FileEventLog,
sockets: Vec<WebSocket>,
alarm: Option<JoinHandle<()>>,
}

impl Game {
#[derive(Clone)]
pub struct Game {
inner: Arc<Mutex<InnerGame>>,
}

impl InnerGame {
async fn push_event(&mut self, event: Event) -> Result<()> {
self.events.push(event.clone()).await?;

Expand All @@ -43,65 +51,59 @@ impl Game {
impl Game {
fn from_game_id(game_id: GameID) -> Self {
Self {
events: FileEventLog::from_game_id(game_id),
sockets: vec![],
alarm: None,
inner: Arc::new(Mutex::new(InnerGame {
events: FileEventLog::from_game_id(game_id),
sockets: vec![],
alarm: None,
})),
}
}
}

#[derive(Clone, Default)]
pub struct FileGameState {
pub struct FileGameDirectory {
inner: Arc<Mutex<HashMap<GameID, Game>>>,
}

impl GameState for FileGameState {
async fn events(&self, game_id: GameID) -> Result<im::Vector<Event>> {
impl GameDirectory for FileGameDirectory {
type GameState = Game;

async fn get(&self, game_id: GameID) -> Self::GameState {
self.inner
.lock()
.await
.entry(game_id)
.or_insert_with(|| Game::from_game_id(game_id))
.events
.vector()
.await
.clone()
}
}

impl GameState for Game {
async fn events(&self) -> Result<im::Vector<Event>> {
self.inner.lock().await.events.vector().await
}

async fn push_event(&self, game_id: GameID, event: Event) -> Result<()> {
async fn push_event(&self, event: Event) -> Result<()> {
let mut lock_guard = self.inner.lock().await;
let game = lock_guard
.entry(game_id)
.or_insert_with(|| Game::from_game_id(game_id));

game.push_event(event).await?;
lock_guard.push_event(event).await?;

Ok(())
}

async fn accept_web_socket(&self, game_id: GameID, ws: WebSocket) -> Result<()> {
self.inner
.lock()
.await
.entry(game_id)
.or_insert_with(|| Game::from_game_id(game_id))
.sockets
.push(ws);
async fn accept_web_socket(&self, ws: WebSocket) -> Result<()> {
self.inner.lock().await.sockets.push(ws);

Ok(())
}

#[instrument(skip(self))]
fn set_alarm(
&self,
game_id: GameID,
duration: Duration,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
Box::pin(async move {
let mut lock_guard = self.inner.lock().await;

let game = lock_guard
.entry(game_id)
.or_insert_with(|| Game::from_game_id(game_id));
let mut game = self.inner.lock().await;

if let Some(handle) = game.alarm.take() {
handle.abort();
Expand All @@ -121,11 +123,7 @@ impl GameState for FileGameState {
return;
};

let mut lock_guard = this.lock().await;

let game = lock_guard
.entry(game_id)
.or_insert_with(|| Game::from_game_id(game_id));
let mut game = this.lock().await;

let (new_events, alarm) = run_processors(&game.events.vector().await?)?;

Expand All @@ -136,12 +134,10 @@ impl GameState for FileGameState {
}

game.alarm = None;
drop(lock_guard);
drop(game);

if let Some(alarm) = alarm {
FileGameState { inner: this }
.set_alarm(game_id, alarm.0)
.await?;
Game { inner: this }.set_alarm(alarm.0).await?;
}
};

Expand Down
70 changes: 0 additions & 70 deletions app/src/adapters/game_state/in_memory.rs

This file was deleted.

2 changes: 0 additions & 2 deletions app/src/adapters/game_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@
pub mod durable_object;
#[cfg(not(target_arch = "wasm32"))]
pub mod file;
#[cfg(not(target_arch = "wasm32"))]
pub mod in_memory;
4 changes: 2 additions & 2 deletions app/src/bin/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::net::SocketAddr;

use app::{
adapters::game_state::file::FileGameState,
adapters::game_state::file::FileGameDirectory,
router::{into_game_router, into_outer_router},
service::axum_router::AxumGameService,
};
Expand All @@ -13,7 +13,7 @@ pub async fn main() {
tracing_subscriber::fmt().pretty().init();

let app = into_outer_router(AxumGameService {
router: into_game_router(FileGameState::default()),
router: into_game_router(FileGameDirectory::default()),
})
.layer(
TraceLayer::new_for_http().make_span_with(DefaultMakeSpan::default().include_headers(true)),
Expand Down
20 changes: 19 additions & 1 deletion app/src/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use serde::Deserialize;
use tracing::instrument;
use uuid::Uuid;

use crate::service::InternalServerError;
use crate::{
ports::game_state::{GameDirectory, GameState},
service::InternalServerError,
};

#[derive(Debug, Copy, Clone)]
pub struct SessionID(pub Uuid);
Expand Down Expand Up @@ -54,3 +57,18 @@ impl<S: Send + Sync> FromRequestParts<S> for GameCode {
Ok(GameCode { code: game_id })
}
}

#[derive(Clone)]
pub struct Game<G: GameDirectory>(pub <G as GameDirectory>::GameState);

#[async_trait]
impl<G: GameDirectory> FromRequestParts<G> for Game<G> {
type Rejection = InternalServerError;

#[instrument(skip_all, err, fields(uri = ?parts.uri))]
async fn from_request_parts(parts: &mut Parts, state: &G) -> Result<Self, Self::Rejection> {
let GameCode { code } = parts.extract::<GameCode>().await?;

Ok(Game(state.get(code).await))
}
}
Loading

0 comments on commit f356a36

Please sign in to comment.