[Protocol] Implement generic response with dyn (#56)

* Implement generic response as enum

* First draft of implementing into_common()

* Make common response type generic

* Use macros and generics to reduce repetition

* [Games] Add dynamically dispatched CommonResponse trait

This adds two traits: "CommonResponse", and "CommonPlayer", when the
generic game query function returns a response it returns a pointer to
its original response type that implements "CommonResponse".

Both common traits require that "as_original()" be implemented, this
returns an enum containing a pointer to the original type.

Both traits have a concrete method "as_json()" that returns a struct
containing data fetched from all of its methods as. This struct
implements serde and can hence be serialized as required.

The traits require a few other methods be implemented, those being the
fields that are common across all types. All other methods have a
default None implementation so that each response type only needs to
implement methods for fields that it has.

* [Game] Implement common traits for JCMP2 response

* [Fmt] Run cargo fmt

* Fix doctest failing

* Run cargo fmt
This commit is contained in:
Tom 2023-06-25 13:31:23 +00:00 committed by GitHub
parent bf14ecb4a4
commit b368877031
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 405 additions and 430 deletions

View file

@ -1,12 +1,14 @@
use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient};
use crate::protocols::quake::Response;
use crate::protocols::types::TimeoutSettings;
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
use crate::{GDError, GDResult};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::slice::Iter;
use super::QuakePlayerType;
/// Quake 1 player data.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -22,6 +24,17 @@ pub struct Player {
pub color_secondary: u8,
}
impl QuakePlayerType for Player {
fn version(response: &Response<Self>) -> super::VersionedResponse { super::VersionedResponse::One(response) }
}
impl CommonPlayer for Player {
fn as_original(&self) -> GenericPlayer { GenericPlayer::QuakeOne(self) }
fn name(&self) -> &str { &self.name }
fn score(&self) -> Option<u32> { Some(self.score.into()) }
}
pub(crate) struct QuakeOne;
impl QuakeClient for QuakeOne {
type Player = Player;

View file

@ -1,13 +1,15 @@
use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient};
use crate::protocols::quake::one::QuakeOne;
use crate::protocols::quake::Response;
use crate::protocols::types::TimeoutSettings;
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
use crate::{GDError, GDResult};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::slice::Iter;
use super::QuakePlayerType;
/// Quake 2 player data.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -17,6 +19,19 @@ pub struct Player {
pub name: String,
}
impl QuakePlayerType for Player {
fn version(response: &Response<Self>) -> super::VersionedResponse {
super::VersionedResponse::TwoAndThree(response)
}
}
impl CommonPlayer for Player {
fn as_original(&self) -> GenericPlayer { GenericPlayer::QuakeTwo(self) }
fn name(&self) -> &str { &self.name }
// TODO: Maybe frags is score?
}
pub(crate) struct QuakeTwo;
impl QuakeClient for QuakeTwo {
type Player = Player;

View file

@ -2,7 +2,10 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::protocols::{types::SpecificResponse, GenericResponse};
use crate::protocols::{
types::{CommonPlayer, CommonResponse},
GenericResponse,
};
/// General server information's.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -24,30 +27,32 @@ pub struct Response<P> {
pub unused_entries: HashMap<String, String>,
}
/// Non-generic quake response
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtraResponse {
/// Other server entries that weren't used.
pub unused_entries: HashMap<String, String>,
pub trait QuakePlayerType: Sized + CommonPlayer {
fn version(response: &Response<Self>) -> VersionedResponse;
}
impl<T> From<Response<T>> for GenericResponse {
fn from(r: Response<T>) -> Self {
Self {
name: Some(r.name),
description: None,
game: None,
game_version: Some(r.version),
map: Some(r.map),
players_maximum: r.players_maximum.into(),
players_online: r.players_online.into(),
players_bots: None,
has_password: None,
inner: SpecificResponse::Quake(ExtraResponse {
// TODO: Players
unused_entries: r.unused_entries,
}),
}
impl<P: QuakePlayerType> CommonResponse for Response<P> {
fn as_original(&self) -> GenericResponse { GenericResponse::Quake(P::version(self)) }
fn name(&self) -> Option<&str> { Some(&self.name) }
fn map(&self) -> Option<&str> { Some(&self.map) }
fn players_maximum(&self) -> u64 { self.players_maximum.into() }
fn players_online(&self) -> u64 { self.players_online.into() }
fn game_version(&self) -> Option<&str> { Some(&self.version) }
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
Some(
self.players
.iter()
.map(|p| p as &dyn CommonPlayer)
.collect(),
)
}
}
/// Versioned response type
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionedResponse<'a> {
One(&'a Response<crate::protocols::quake::one::Player>),
TwoAndThree(&'a Response<crate::protocols::quake::two::Player>),
}