memory optimization
This commit is contained in:
parent
5ae2a2d4e2
commit
a6a4d68651
6 changed files with 204 additions and 147 deletions
76
src/game.rs
76
src/game.rs
|
@ -1,11 +1,14 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use qrcode::{render::svg, QrCode};
|
||||
use rand::{distributions, Rng};
|
||||
use sailfish::TemplateOnce;
|
||||
use serde::Deserialize;
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{broadcast, RwLock},
|
||||
|
@ -16,6 +19,65 @@ use crate::{
|
|||
ViewerState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
pub struct GameId(Arc<str>);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
pub struct PlayerId(Arc<str>);
|
||||
|
||||
impl Deref for GameId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for GameId {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl GameId {
|
||||
pub fn random() -> Self {
|
||||
Self(
|
||||
rand::thread_rng()
|
||||
.sample_iter(distributions::Alphanumeric)
|
||||
.take(8)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PlayerId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for PlayerId {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerId {
|
||||
pub fn random() -> Self {
|
||||
Self(
|
||||
rand::thread_rng()
|
||||
.sample_iter(distributions::Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GameState {
|
||||
NotStarted,
|
||||
|
@ -25,18 +87,18 @@ pub enum GameState {
|
|||
}
|
||||
|
||||
pub struct Game {
|
||||
pub id: String,
|
||||
pub id: GameId,
|
||||
pub owner: String,
|
||||
pub state: Arc<RwLock<GameState>>,
|
||||
pub quiz: Quiz,
|
||||
pub players: HashSet<String>,
|
||||
pub players: HashSet<PlayerId>,
|
||||
pub on_state_update: broadcast::Sender<()>,
|
||||
pub on_submission: broadcast::Sender<()>,
|
||||
pub questions: Vec<Box<dyn Question>>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(id: String, owner: String, quiz: Quiz) -> Self {
|
||||
pub fn new(id: GameId, owner: String, quiz: Quiz) -> Self {
|
||||
Self {
|
||||
id,
|
||||
owner,
|
||||
|
@ -87,7 +149,7 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn player_view(&self, player_id: &str, htmx: bool) -> HandlerResult<String> {
|
||||
pub async fn player_view(&self, player_id: &PlayerId, htmx: bool) -> HandlerResult<String> {
|
||||
if !self.players.contains(player_id) {
|
||||
return Err(Error::PlayerNotFound);
|
||||
}
|
||||
|
@ -134,7 +196,7 @@ impl Game {
|
|||
|
||||
pub async fn handle_answer(
|
||||
&mut self,
|
||||
player_id: &str,
|
||||
player_id: &PlayerId,
|
||||
values: &HashMap<String, String>,
|
||||
) -> HandlerResult<()> {
|
||||
if !self.players.contains(player_id) {
|
||||
|
@ -145,7 +207,7 @@ impl Game {
|
|||
|
||||
if let GameState::Answering(i) = *state {
|
||||
self.questions[i as usize]
|
||||
.handle_answer(player_id, values)
|
||||
.handle_answer(player_id.clone(), values)
|
||||
.await?;
|
||||
|
||||
self.on_submission.send(());
|
||||
|
@ -164,7 +226,7 @@ impl Game {
|
|||
|
||||
let viewer_state = match *state {
|
||||
GameState::NotStarted => {
|
||||
let url = format!("{}/{}", base_url, &self.id);
|
||||
let url = format!("{}/{}", base_url, &self.id.deref());
|
||||
let img = QrCode::new(&url).expect("");
|
||||
let img = img.render::<svg::Color>().build();
|
||||
ViewerState::NotStarted((self.players.len() as u32, img, url))
|
||||
|
|
|
@ -6,11 +6,11 @@ use std::{
|
|||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::game::Game;
|
||||
use crate::game::{Game, GameId};
|
||||
|
||||
pub fn start_gc(
|
||||
game_expiry: Arc<RwLock<BinaryHeap<GarbageCollectorItem>>>,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
) {
|
||||
let games = games.clone();
|
||||
let game_expiry = game_expiry.clone();
|
||||
|
@ -36,12 +36,12 @@ pub fn start_gc(
|
|||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct GarbageCollectorItem {
|
||||
id: String,
|
||||
id: GameId,
|
||||
expires_at: u64,
|
||||
}
|
||||
|
||||
impl GarbageCollectorItem {
|
||||
pub fn new_in(id: String, time: u64) -> Self {
|
||||
pub fn new_in(id: GameId, time: u64) -> Self {
|
||||
Self {
|
||||
id,
|
||||
expires_at: SystemTime::now()
|
||||
|
|
100
src/main.rs
100
src/main.rs
|
@ -3,15 +3,14 @@
|
|||
use std::{
|
||||
collections::{BinaryHeap, HashMap},
|
||||
env,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use axum::{
|
||||
async_trait,
|
||||
body::HttpBody,
|
||||
error_handling::HandleErrorLayer,
|
||||
extract::{FromRef, Multipart, Path, Query, State},
|
||||
http::{Request, StatusCode, Uri},
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{StatusCode, Uri},
|
||||
response::{
|
||||
sse::{Event, KeepAlive},
|
||||
Html, IntoResponse, Redirect, Sse,
|
||||
|
@ -21,21 +20,19 @@ use axum::{
|
|||
};
|
||||
use axum_htmx::{HxRedirect, HxRequest};
|
||||
use axum_oidc::{
|
||||
error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient,
|
||||
OidcLoginLayer,
|
||||
error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
||||
};
|
||||
use futures_util::Stream;
|
||||
use game::Game;
|
||||
use game::{Game, GameId, PlayerId};
|
||||
use garbage_collector::{start_gc, GarbageCollectorItem};
|
||||
use question::single_choice::SingleChoiceQuestion;
|
||||
use rand::{distributions, Rng};
|
||||
use question::{single_choice::SingleChoiceQuestion, Question};
|
||||
use sailfish::TemplateOnce;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use stream::{PlayerBroadcastStream, ViewerBroadcastStream};
|
||||
use tokio::sync::RwLock;
|
||||
use tower::{Layer, ServiceBuilder};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_sessions::{cookie::SameSite, Expiry, MemoryStore, SessionManagerLayer};
|
||||
use tower_sessions::{cookie::SameSite, MemoryStore, SessionManagerLayer};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
|
@ -50,9 +47,9 @@ mod question;
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
game_expiry: Arc<RwLock<BinaryHeap<GarbageCollectorItem>>>,
|
||||
application_base: String,
|
||||
application_base: &'static str,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -108,7 +105,7 @@ pub async fn main() {
|
|||
let app_state = AppState {
|
||||
games,
|
||||
game_expiry,
|
||||
application_base,
|
||||
application_base: Box::leak(application_base.into()),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
|
@ -147,11 +144,7 @@ pub async fn handle_create(
|
|||
|
||||
let quiz = quiz.ok_or(Error::QuizFileNotFound)?;
|
||||
|
||||
let game_id: String = rand::thread_rng()
|
||||
.sample_iter(distributions::Alphanumeric)
|
||||
.take(8)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
let game_id = GameId::random();
|
||||
|
||||
let game = Game::new(game_id.clone(), claims.subject().to_string(), quiz);
|
||||
|
||||
|
@ -159,7 +152,7 @@ pub async fn handle_create(
|
|||
|
||||
games.insert(game_id.clone(), game);
|
||||
|
||||
let url = format!("{}/{}/view", state.application_base, &game_id);
|
||||
let url = format!("{}/{}/view", state.application_base, &game_id.deref());
|
||||
|
||||
let mut game_expiry = state.game_expiry.write().await;
|
||||
game_expiry.push(GarbageCollectorItem::new_in(game_id, 24 * 3600));
|
||||
|
@ -168,7 +161,7 @@ pub async fn handle_create(
|
|||
}
|
||||
|
||||
pub async fn handle_view(
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
HxRequest(htmx): HxRequest,
|
||||
OidcClaims(claims): OidcClaims<EmptyAdditionalClaims>,
|
||||
|
@ -180,13 +173,12 @@ pub async fn handle_view(
|
|||
return Err(Error::Forbidden);
|
||||
}
|
||||
|
||||
Ok(Html(game.viewer_view(htmx, &state.application_base).await?))
|
||||
Ok(Html(game.viewer_view(htmx, state.application_base).await?))
|
||||
}
|
||||
|
||||
pub async fn handle_view_next(
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
HxRequest(htmx): HxRequest,
|
||||
OidcClaims(claims): OidcClaims<EmptyAdditionalClaims>,
|
||||
) -> HandlerResult<impl IntoResponse> {
|
||||
let mut games = state.games.write().await;
|
||||
|
@ -202,7 +194,7 @@ pub async fn handle_view_next(
|
|||
}
|
||||
|
||||
pub async fn sse_view(
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
OidcClaims(claims): OidcClaims<EmptyAdditionalClaims>,
|
||||
) -> HandlerResult<Sse<impl Stream<Item = Result<Event, Error>>>> {
|
||||
|
@ -216,13 +208,8 @@ pub async fn sse_view(
|
|||
let rx1 = game.on_state_update.subscribe();
|
||||
let rx2 = game.on_submission.subscribe();
|
||||
|
||||
let stream = ViewerBroadcastStream::new(
|
||||
rx1,
|
||||
rx2,
|
||||
state.games.clone(),
|
||||
id,
|
||||
state.application_base.clone(),
|
||||
);
|
||||
let stream =
|
||||
ViewerBroadcastStream::new(rx1, rx2, state.games.clone(), id, state.application_base);
|
||||
|
||||
Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
@ -234,27 +221,25 @@ pub struct PlayerQuery {
|
|||
|
||||
pub async fn handle_player(
|
||||
Query(query): Query<PlayerQuery>,
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
HxRequest(htmx): HxRequest,
|
||||
) -> HandlerResult<impl IntoResponse> {
|
||||
let mut games = state.games.write().await;
|
||||
let game = games.get_mut(&id).ok_or(Error::NotFound)?;
|
||||
|
||||
if let Some(player_id) = query.player {
|
||||
if let Some(player_id) = query.player.map(PlayerId::from) {
|
||||
Ok(Html(game.player_view(&player_id, htmx).await?).into_response())
|
||||
} else {
|
||||
let player_id: String = rand::thread_rng()
|
||||
.sample_iter(distributions::Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
game.players.insert(player_id.to_string());
|
||||
let player_id = PlayerId::random();
|
||||
game.players.insert(player_id.clone());
|
||||
game.on_submission.send(());
|
||||
|
||||
Ok(Redirect::temporary(&format!(
|
||||
"{}/{}?player={}",
|
||||
state.application_base, id, player_id
|
||||
state.application_base,
|
||||
id.deref(),
|
||||
player_id.deref()
|
||||
))
|
||||
.into_response())
|
||||
}
|
||||
|
@ -262,13 +247,13 @@ pub async fn handle_player(
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubmissionPayload {
|
||||
player_id: String,
|
||||
player_id: PlayerId,
|
||||
#[serde(flatten)]
|
||||
values: HashMap<String, String>,
|
||||
}
|
||||
|
||||
pub async fn handle_player_answer(
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
Form(form): Form<SubmissionPayload>,
|
||||
) -> HandlerResult<impl IntoResponse> {
|
||||
|
@ -282,12 +267,12 @@ pub async fn handle_player_answer(
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SsePlayerQuery {
|
||||
player: String,
|
||||
player: PlayerId,
|
||||
}
|
||||
|
||||
pub async fn sse_player(
|
||||
Query(query): Query<SsePlayerQuery>,
|
||||
Path(id): Path<String>,
|
||||
Path(id): Path<GameId>,
|
||||
State(state): State<AppState>,
|
||||
) -> HandlerResult<Sse<impl Stream<Item = Result<Event, Error>>>> {
|
||||
let games = state.games.read().await;
|
||||
|
@ -312,7 +297,6 @@ struct PlayTemplate<'a> {
|
|||
state: PlayerState,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PlayerState {
|
||||
NotStarted,
|
||||
Answering { inner_body: String },
|
||||
|
@ -329,7 +313,6 @@ struct ViewTemplate<'a> {
|
|||
state: ViewerState,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ViewerState {
|
||||
NotStarted((u32, String, String)),
|
||||
Answering {
|
||||
|
@ -357,30 +340,11 @@ pub enum QuizQuestion {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SingleChoice {
|
||||
name: String,
|
||||
answers: Vec<String>,
|
||||
name: Box<str>,
|
||||
answers: Box<[Box<str>]>,
|
||||
correct: u32,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Question: Send + Sync {
|
||||
async fn render_player(&self, player_id: &str, show_result: bool) -> Result<String, Error>;
|
||||
|
||||
async fn handle_answer(
|
||||
&mut self,
|
||||
player_id: &str,
|
||||
values: &HashMap<String, String>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
async fn has_answered(&self, player_id: &str) -> Result<bool, Error>;
|
||||
|
||||
async fn answered_correctly(&self, player_id: &str) -> Result<bool, Error>;
|
||||
|
||||
async fn answer_count(&self) -> Result<u32, Error>;
|
||||
|
||||
async fn render_viewer(&self, show_result: bool) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
impl From<QuizQuestion> for Box<dyn Question> {
|
||||
fn from(value: QuizQuestion) -> Self {
|
||||
match value {
|
||||
|
|
|
@ -1 +1,27 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use axum::async_trait;
|
||||
|
||||
use crate::{error::Error, game::PlayerId};
|
||||
|
||||
pub mod single_choice;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Question: Send + Sync {
|
||||
async fn render_player(&self, player_id: &PlayerId, show_result: bool)
|
||||
-> Result<String, Error>;
|
||||
|
||||
async fn handle_answer(
|
||||
&mut self,
|
||||
player_id: PlayerId,
|
||||
values: &HashMap<String, String>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
async fn has_answered(&self, player_id: &PlayerId) -> Result<bool, Error>;
|
||||
|
||||
async fn answered_correctly(&self, player_id: &PlayerId) -> Result<bool, Error>;
|
||||
|
||||
async fn answer_count(&self) -> Result<u32, Error>;
|
||||
|
||||
async fn render_viewer(&self, show_result: bool) -> Result<String, Error>;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ use std::collections::HashMap;
|
|||
use axum::async_trait;
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::{error::Error, Question, SingleChoice};
|
||||
use crate::{error::Error, game::PlayerId, Question, SingleChoice};
|
||||
|
||||
pub struct SingleChoiceQuestion {
|
||||
inner: SingleChoice,
|
||||
submissions: HashMap<String, u32>,
|
||||
submissions: HashMap<PlayerId, u32>,
|
||||
}
|
||||
|
||||
impl SingleChoiceQuestion {
|
||||
|
@ -21,12 +21,15 @@ impl SingleChoiceQuestion {
|
|||
|
||||
#[async_trait]
|
||||
impl Question for SingleChoiceQuestion {
|
||||
async fn render_player(&self, player_id: &str, show_result: bool) -> Result<String, Error> {
|
||||
async fn render_player(
|
||||
&self,
|
||||
player_id: &PlayerId,
|
||||
show_result: bool,
|
||||
) -> Result<String, Error> {
|
||||
if show_result {
|
||||
let player_sub_index = self.submissions.get(player_id);
|
||||
|
||||
let player_sub_value =
|
||||
player_sub_index.map(|x| self.inner.answers[*x as usize].as_str());
|
||||
let player_sub_value = player_sub_index.map(|x| &*self.inner.answers[*x as usize]);
|
||||
|
||||
Ok(ResultTemplate {
|
||||
is_correct: Some(&self.inner.correct) == player_sub_index,
|
||||
|
@ -46,7 +49,7 @@ impl Question for SingleChoiceQuestion {
|
|||
|
||||
async fn handle_answer(
|
||||
&mut self,
|
||||
player_id: &str,
|
||||
player_id: PlayerId,
|
||||
values: &HashMap<String, String>,
|
||||
) -> Result<(), Error> {
|
||||
let value = values
|
||||
|
@ -59,20 +62,20 @@ impl Question for SingleChoiceQuestion {
|
|||
return Err(Error::InvalidInput);
|
||||
}
|
||||
|
||||
if self.submissions.contains_key(player_id) {
|
||||
if self.submissions.contains_key(&player_id) {
|
||||
return Err(Error::QuestionAlreadySubmitted);
|
||||
}
|
||||
|
||||
self.submissions.insert(player_id.to_string(), value);
|
||||
self.submissions.insert(player_id, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn has_answered(&self, player_id: &str) -> Result<bool, Error> {
|
||||
async fn has_answered(&self, player_id: &PlayerId) -> Result<bool, Error> {
|
||||
Ok(self.submissions.get(player_id).is_some())
|
||||
}
|
||||
|
||||
async fn answered_correctly(&self, player_id: &str) -> Result<bool, Error> {
|
||||
async fn answered_correctly(&self, player_id: &PlayerId) -> Result<bool, Error> {
|
||||
Ok(self
|
||||
.submissions
|
||||
.get(player_id)
|
||||
|
@ -90,30 +93,34 @@ impl Question for SingleChoiceQuestion {
|
|||
name: &self.inner.name,
|
||||
total_submissions: self.submissions.len() as u32,
|
||||
submissions: &self.submissions.iter().fold(
|
||||
vec![0; self.inner.answers.len()],
|
||||
vec![0; self.inner.answers.len()].into_boxed_slice(),
|
||||
|mut acc, (_, v)| {
|
||||
acc[*v as usize] += 1;
|
||||
acc
|
||||
},
|
||||
),
|
||||
submissions_correct: &self.submissions.iter().fold(
|
||||
vec![0; self.inner.answers.len()],
|
||||
|mut acc, (_, v)| {
|
||||
if *v == self.inner.correct {
|
||||
submissions_correct: &self
|
||||
.submissions
|
||||
.iter()
|
||||
.filter(|(_, v)| **v == self.inner.correct)
|
||||
.fold(
|
||||
vec![0; self.inner.answers.len()].into_boxed_slice(),
|
||||
|mut acc, (_, v)| {
|
||||
acc[*v as usize] += 1;
|
||||
}
|
||||
acc
|
||||
},
|
||||
),
|
||||
submissions_wrong: &self.submissions.iter().fold(
|
||||
vec![0; self.inner.answers.len()],
|
||||
|mut acc, (_, v)| {
|
||||
if *v != self.inner.correct {
|
||||
acc
|
||||
},
|
||||
),
|
||||
submissions_wrong: &self
|
||||
.submissions
|
||||
.iter()
|
||||
.filter(|(_, v)| **v != self.inner.correct)
|
||||
.fold(
|
||||
vec![0; self.inner.answers.len()].into_boxed_slice(),
|
||||
|mut acc, (_, v)| {
|
||||
acc[*v as usize] += 1;
|
||||
}
|
||||
acc
|
||||
},
|
||||
),
|
||||
acc
|
||||
},
|
||||
),
|
||||
correct_answer: &self.inner.answers[self.inner.correct as usize],
|
||||
answers: &self.inner.answers,
|
||||
}
|
||||
|
@ -126,7 +133,7 @@ impl Question for SingleChoiceQuestion {
|
|||
struct PlayerTemplate<'a> {
|
||||
name: &'a str,
|
||||
player_id: &'a str,
|
||||
answers: &'a [String],
|
||||
answers: &'a [Box<str>],
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
|
@ -147,5 +154,5 @@ struct ViewerTemplate<'a> {
|
|||
submissions_correct: &'a [u32],
|
||||
submissions_wrong: &'a [u32],
|
||||
correct_answer: &'a str,
|
||||
answers: &'a [String],
|
||||
answers: &'a [Box<str>],
|
||||
}
|
||||
|
|
|
@ -15,43 +15,46 @@ use tokio::{
|
|||
};
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use crate::{error::Error, game::Game};
|
||||
use crate::{
|
||||
error::Error,
|
||||
game::{Game, GameId, PlayerId},
|
||||
};
|
||||
|
||||
pub struct PlayerBroadcastStream {
|
||||
id: String,
|
||||
player_id: String,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: GameId,
|
||||
player_id: PlayerId,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
inner: ReusableBoxFuture<'static, (Result<Result<Event, Error>, RecvError>, Receiver<()>)>,
|
||||
}
|
||||
|
||||
impl PlayerBroadcastStream {
|
||||
async fn make_future(
|
||||
mut rx: Receiver<()>,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
player_id: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: GameId,
|
||||
player_id: PlayerId,
|
||||
) -> (Result<Result<Event, Error>, RecvError>, Receiver<()>) {
|
||||
let result = match rx.recv().await {
|
||||
Ok(_) => Ok(Self::build_template(games, id, player_id).await),
|
||||
Ok(_) => Ok(Self::build_template(games, &id, &player_id).await),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
(result, rx)
|
||||
}
|
||||
async fn build_template(
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
player_id: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: &GameId,
|
||||
player_id: &PlayerId,
|
||||
) -> Result<Event, Error> {
|
||||
let games = games.read().await;
|
||||
let game = games.get(&id).ok_or(Error::NotFound)?;
|
||||
let game = games.get(id).ok_or(Error::NotFound)?;
|
||||
|
||||
Ok(Event::default().data(game.player_view(&player_id, true).await?))
|
||||
Ok(Event::default().data(game.player_view(player_id, true).await?))
|
||||
}
|
||||
pub fn new(
|
||||
recv: Receiver<()>,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
player_id: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: GameId,
|
||||
player_id: PlayerId,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(Self::make_future(
|
||||
|
@ -91,8 +94,8 @@ impl Stream for PlayerBroadcastStream {
|
|||
}
|
||||
|
||||
pub struct ViewerBroadcastStream {
|
||||
id: String,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: GameId,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
inner: ReusableBoxFuture<
|
||||
'static,
|
||||
(
|
||||
|
@ -102,16 +105,16 @@ pub struct ViewerBroadcastStream {
|
|||
),
|
||||
>,
|
||||
|
||||
base_url: String,
|
||||
base_url: &'static str,
|
||||
}
|
||||
|
||||
impl ViewerBroadcastStream {
|
||||
async fn make_future(
|
||||
mut rx1: Receiver<()>,
|
||||
mut rx2: Receiver<()>,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
base_url: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: GameId,
|
||||
base_url: &'static str,
|
||||
) -> (
|
||||
Result<Result<Event, Error>, RecvError>,
|
||||
Receiver<()>,
|
||||
|
@ -121,27 +124,27 @@ impl ViewerBroadcastStream {
|
|||
a = rx1.recv() => a,
|
||||
b = rx2.recv() => b
|
||||
} {
|
||||
Ok(_) => Ok(Self::build_template(games, id, base_url).await),
|
||||
Ok(_) => Ok(Self::build_template(games, &id, base_url).await),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
(result, rx1, rx2)
|
||||
}
|
||||
async fn build_template(
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
base_url: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: &GameId,
|
||||
base_url: &'static str,
|
||||
) -> Result<Event, Error> {
|
||||
let games = games.read().await;
|
||||
let game = games.get(&id).ok_or(Error::NotFound)?;
|
||||
let game = games.get(id).ok_or(Error::NotFound)?;
|
||||
|
||||
Ok(Event::default().data(game.viewer_view(true, &base_url).await?))
|
||||
Ok(Event::default().data(game.viewer_view(true, base_url).await?))
|
||||
}
|
||||
pub fn new(
|
||||
rx1: Receiver<()>,
|
||||
rx2: Receiver<()>,
|
||||
games: Arc<RwLock<HashMap<String, Game>>>,
|
||||
id: String,
|
||||
base_url: String,
|
||||
games: Arc<RwLock<HashMap<GameId, Game>>>,
|
||||
id: GameId,
|
||||
base_url: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(Self::make_future(
|
||||
|
@ -149,7 +152,7 @@ impl ViewerBroadcastStream {
|
|||
rx2,
|
||||
games.clone(),
|
||||
id.clone(),
|
||||
base_url.clone(),
|
||||
base_url,
|
||||
)),
|
||||
games,
|
||||
id,
|
||||
|
@ -166,13 +169,8 @@ impl Stream for ViewerBroadcastStream {
|
|||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let (result, rx1, rx2) = ready!(self.inner.poll(cx));
|
||||
let future = Self::make_future(
|
||||
rx1,
|
||||
rx2,
|
||||
self.games.clone(),
|
||||
self.id.clone(),
|
||||
self.base_url.clone(),
|
||||
);
|
||||
let future =
|
||||
Self::make_future(rx1, rx2, self.games.clone(), self.id.clone(), self.base_url);
|
||||
self.inner.set(future);
|
||||
match result {
|
||||
Ok(item) => Poll::Ready(Some(item)),
|
||||
|
|
Loading…
Reference in a new issue