This commit is contained in:
Robin Appelman 2021-08-23 22:49:26 +02:00
commit 8a88e452ea
13 changed files with 291 additions and 284 deletions

View file

@ -5,7 +5,7 @@ use tf_log_parser::{parse, RawEvent};
pub fn parse_benchmark(c: &mut Criterion) { pub fn parse_benchmark(c: &mut Criterion) {
let input = read_to_string("test_data/log_2892242.log").unwrap(); let input = read_to_string("test_data/log_2892242.log").unwrap();
c.bench_function("parse 2892242", |b| b.iter(|| parse(black_box(&input)))); c.bench_function("parse log 2892242", |b| b.iter(|| parse(black_box(&input))));
} }
pub fn parse_raw(c: &mut Criterion) { pub fn parse_raw(c: &mut Criterion) {

View file

@ -3,11 +3,11 @@
use main_error::MainError; use main_error::MainError;
use std::env::args; use std::env::args;
use std::fs; use std::fs;
use tf_log_parser::module::{ChatHandler, LobbySettingsHandler}; use tf_log_parser::module::{ChatMessages, LobbySettingsHandler};
use tf_log_parser::{handler, parse_with_handler}; use tf_log_parser::{handler, parse_with_handler};
handler!(Handler { handler!(Handler {
chat: ChatHandler, chat: ChatMessages,
lobby_settings: LobbySettingsHandler lobby_settings: LobbySettingsHandler
}); });
@ -15,12 +15,15 @@ fn main() -> Result<(), MainError> {
let path = args().skip(1).next().expect("No path provided"); let path = args().skip(1).next().expect("No path provided");
let content = fs::read_to_string(path)?; let content = fs::read_to_string(path)?;
let HandlerOutput { let (
HandlerGlobalOutput {
chat, chat,
lobby_settings, lobby_settings,
} = parse_with_handler::<Handler>(&content)?; },
_,
) = parse_with_handler::<Handler>(&content)?;
if let Ok(Some(settings)) = lobby_settings { if let Some(Ok(settings)) = lobby_settings {
println!("Lobby settings: {:#?}", settings); println!("Lobby settings: {:#?}", settings);
println!(); println!();
} }

View file

@ -4,8 +4,9 @@ use main_error::MainError;
use std::env::args; use std::env::args;
use std::fs; use std::fs;
use tf_log_parser::event::DamageEvent; use tf_log_parser::event::DamageEvent;
use tf_log_parser::module::GlobalData;
use tf_log_parser::{ use tf_log_parser::{
parse_with_handler, EventHandler, GameEvent, RawEventType, SubjectData, SubjectId, SubjectMap, parse_with_handler, EventMeta, GameEvent, RawEventType, SubjectData, SubjectId, SubjectMap,
}; };
struct HighestDamage { struct HighestDamage {
@ -18,14 +19,14 @@ struct HighestDamageHandler {
current: Option<(SubjectId, u32)>, current: Option<(SubjectId, u32)>,
} }
impl EventHandler for HighestDamageHandler { impl GlobalData for HighestDamageHandler {
type GlobalOutput = Option<HighestDamage>; type Output = Option<HighestDamage>;
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
matches!(ty, RawEventType::Damage) matches!(ty, RawEventType::Damage)
} }
fn handle(&mut self, _time: u32, subject: SubjectId, event: &GameEvent) { fn handle_event(&mut self, _meta: &EventMeta, subject: SubjectId, event: &GameEvent) {
if let GameEvent::Damage(DamageEvent { if let GameEvent::Damage(DamageEvent {
damage: Some(damage), damage: Some(damage),
.. ..
@ -42,9 +43,9 @@ impl EventHandler for HighestDamageHandler {
} }
} }
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput { fn finish(self, subjects: &SubjectMap) -> Self::Output {
self.current.map(|(subject, damage)| { self.current.map(|(subject, damage)| {
let user = match &subjects[subject] { let user = match subjects.subject(subject) {
SubjectData::Player { name, .. } => name.clone(), SubjectData::Player { name, .. } => name.clone(),
_ => { _ => {
panic!("A non player did the most damage?") panic!("A non player did the most damage?")
@ -59,8 +60,9 @@ fn main() -> Result<(), MainError> {
let path = args().skip(1).next().expect("No path provided"); let path = args().skip(1).next().expect("No path provided");
let content = fs::read_to_string(path)?; let content = fs::read_to_string(path)?;
let HighestDamage { user, damage } = let HighestDamage { user, damage } = parse_with_handler::<HighestDamageHandler>(&content)?
parse_with_handler::<HighestDamageHandler>(&content)?.expect("nobody did any damage?"); .0
.expect("nobody did any damage?");
println!("highest damage was {} done by {}", user, damage); println!("highest damage was {} done by {}", user, damage);
Ok(()) Ok(())

View file

@ -4,7 +4,7 @@ use serde::ser::SerializeMap;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::str::FromStr; use std::str::FromStr;
use steamid_ng::{AccountType, Instance, SteamID, Universe}; use steamid_ng::{AccountType, Instance, SteamID, Universe};
@ -27,7 +27,7 @@ impl Team {
impl Display for Team { impl Display for Team {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f) <str as Debug>::fmt(self.as_str(), f)
} }
} }
@ -153,6 +153,18 @@ impl<T: Default> Default for ClassMap<T> {
} }
} }
impl<T: Debug> Debug for ClassMap<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
<[T; 9] as Debug>::fmt(&self.0, f)
}
}
impl<T: PartialEq> PartialEq for ClassMap<T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
/// Optimized subject id /// Optimized subject id
#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)]
pub enum SubjectId { pub enum SubjectId {
@ -222,6 +234,18 @@ pub enum SubjectData {
World, World,
} }
impl SubjectData {
pub fn id(&self) -> SubjectId {
match self {
SubjectData::Player { steam_id, .. } => SubjectId::Player(steam_id.account_id()),
SubjectData::Team(team) => SubjectId::Team(*team),
SubjectData::System(_) => SubjectId::System,
SubjectData::Console => SubjectId::Console,
SubjectData::World => SubjectId::World,
}
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum SubjectError { pub enum SubjectError {
#[error("Invalid user id")] #[error("Invalid user id")]

View file

@ -3,7 +3,7 @@ mod medic;
mod player; mod player;
use crate::event::game::{RoundLengthEvent, RoundWinEvent}; use crate::event::game::{RoundLengthEvent, RoundWinEvent};
use crate::{RawEvent, RawEventType}; use crate::{RawEvent, RawEventType, SubjectId};
pub use game::*; pub use game::*;
pub use medic::*; pub use medic::*;
use nom::bytes::complete::{tag, take_while}; use nom::bytes::complete::{tag, take_while};
@ -46,6 +46,12 @@ impl<'a, T> GameEventErrTrait<T> for IResult<&str, T> {
} }
} }
#[derive(Debug)]
pub struct EventMeta {
pub time: u32,
pub subject: SubjectId,
}
#[derive(Debug)] #[derive(Debug)]
pub enum GameEvent<'a> { pub enum GameEvent<'a> {
ShotFired(ShotFiredEvent<'a>), ShotFired(ShotFiredEvent<'a>),

View file

@ -1,10 +1,10 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId}; pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::event::GameEventError; use crate::event::GameEventError;
pub use crate::module::EventHandler; pub use crate::module::EventHandler;
use crate::module::{ChatHandler, ClassStatsHandler, HealSpreadHandler, MedicStatsHandler}; use crate::module::{ChatMessages, ClassStatsHandler, HealSpread, PlayerHandler};
use crate::subjectmap::SubjectMap; pub use crate::subjectmap::SubjectMap;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use event::GameEvent; pub use event::{EventMeta, GameEvent};
pub use raw_event::{RawEvent, RawEventType}; pub use raw_event::{RawEvent, RawEventType};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryInto; use std::convert::TryInto;
@ -72,7 +72,7 @@ pub fn parse_with_handler<Handler: EventHandler>(
for event_res in events { for event_res in events {
let raw_event = event_res?; let raw_event = event_res?;
let should_handle = handler.does_handle(raw_event.ty); let should_handle = Handler::does_handle(raw_event.ty);
if should_handle || start_time.is_none() { if should_handle || start_time.is_none() {
let event_time: DateTime<Utc> = (&raw_event.date).try_into().unwrap(); let event_time: DateTime<Utc> = (&raw_event.date).try_into().unwrap();
let match_time = match start_time { let match_time = match start_time {
@ -85,7 +85,11 @@ pub fn parse_with_handler<Handler: EventHandler>(
if should_handle { if should_handle {
let event = GameEvent::parse(&raw_event)?; let event = GameEvent::parse(&raw_event)?;
let (subject, data) = subjects.insert(&raw_event.subject)?; let (subject, data) = subjects.insert(&raw_event.subject)?;
handler.handle(match_time, subject, data, &event); let meta = EventMeta {
time: match_time,
subject,
};
handler.handle(&meta, subject, data, &event);
} }
} }
} }
@ -107,8 +111,8 @@ pub fn parse_with_handler<Handler: EventHandler>(
} }
handler!(LogHandler { handler!(LogHandler {
chat: ChatHandler, chat: ChatMessages,
heal_spread: HealSpreadHandler, heal_spread: PlayerHandler::<HealSpread>,
medic_stats: MedicStatsHandler, // medic_stats: MedicStatsHandler,
class_stats: ClassStatsHandler, class_stats: ClassStatsHandler,
}); });

View file

@ -1,8 +1,8 @@
use crate::common::{SubjectData, SubjectId}; use crate::common::{SubjectData, SubjectId};
use crate::event::GameEvent; use crate::event::GameEvent;
use crate::module::EventHandler; use crate::module::GlobalData;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::SubjectMap; use crate::{EventMeta, SubjectMap};
use serde::Serialize; use serde::Serialize;
use steamid_ng::SteamID; use steamid_ng::SteamID;
@ -13,7 +13,7 @@ struct BareChatMessage {
pub chat_type: ChatType, pub chat_type: ChatType,
} }
#[derive(Serialize)] #[derive(Serialize, PartialEq)]
pub struct ChatMessage { pub struct ChatMessage {
pub time: u32, pub time: u32,
pub name: String, pub name: String,
@ -40,31 +40,24 @@ impl ChatMessage {
} }
} }
#[derive(Serialize)] #[derive(Serialize, PartialEq)]
pub enum ChatType { pub enum ChatType {
All, All,
Team, Team,
} }
#[derive(Default)] #[derive(Default)]
pub struct ChatHandler(Vec<BareChatMessage>); pub struct ChatMessages(Vec<BareChatMessage>);
impl EventHandler for ChatHandler { impl GlobalData for ChatMessages {
type GlobalOutput = Vec<ChatMessage>; type Output = Vec<ChatMessage>;
type PerSubjectData = ();
type PerSubjectOutput = ();
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
matches!(ty, RawEventType::SayTeam | RawEventType::Say) matches!(ty, RawEventType::SayTeam | RawEventType::Say)
} }
fn handle( fn handle_event(&mut self, meta: &EventMeta, subject: SubjectId, event: &GameEvent) {
&mut self, let time = meta.time;
time: u32,
subject: SubjectId,
_subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
if !matches!(subject, SubjectId::Player(_)) { if !matches!(subject, SubjectId::Player(_)) {
return; return;
} }
@ -85,18 +78,10 @@ impl EventHandler for ChatHandler {
} }
} }
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput { fn finish(self, subjects: &SubjectMap) -> Self::Output {
self.0 self.0
.into_iter() .into_iter()
.map(|bare| ChatMessage::from_bare(bare, subjects)) .map(|bare| ChatMessage::from_bare(bare, subjects))
.collect() .collect()
} }
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
}
} }

View file

@ -1,75 +1,42 @@
use crate::common::{Class, ClassMap, SteamId3, SubjectId}; use crate::common::{Class, ClassMap, SubjectId};
use crate::event::{DamageEvent, GameEvent, RoleChangeEvent, SpawnEvent}; use crate::event::{DamageEvent, GameEvent, RoleChangeEvent, SpawnEvent};
use crate::module::EventHandler; use crate::module::EventHandler;
use crate::raw_event::{RawEventType, RawSubject}; use crate::raw_event::{RawEventType, RawSubject};
use crate::{SubjectData, SubjectMap}; use crate::{EventMeta, SubjectData, SubjectMap};
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::{Add, AddAssign};
#[derive(Debug, Serialize, Default, PartialEq)] #[derive(Debug, Serialize, Default, PartialEq)]
pub struct ClassStat { pub struct ClassStats {
kills: u8, kills: ClassMap<u8>,
deaths: u8, deaths: ClassMap<u8>,
assists: u8, assists: ClassMap<u8>,
damage: u16, damage: ClassMap<u16>,
}
impl Add for ClassStat {
type Output = ClassStat;
fn add(self, rhs: Self) -> Self::Output {
ClassStat {
kills: self.kills + rhs.kills,
deaths: self.deaths + rhs.deaths,
assists: self.assists + rhs.assists,
damage: self.damage + rhs.damage,
}
}
}
impl AddAssign for ClassStat {
fn add_assign(&mut self, rhs: Self) {
self.kills += rhs.kills;
self.deaths += rhs.deaths;
self.assists += rhs.assists;
self.damage += rhs.damage;
}
} }
#[derive(Default)] #[derive(Default)]
pub struct ClassStatsHandler { pub struct ClassStatsHandler {
active: bool, active: bool,
classes: BTreeMap<SubjectId, Class>, classes: BTreeMap<SubjectId, Class>,
stats: BTreeMap<SteamId3, ClassMap<ClassStat>>, deaths: BTreeMap<SubjectId, ClassMap<u8>>,
} }
impl ClassStatsHandler { impl ClassStatsHandler {
fn handle_stats(&mut self, subject: SubjectId, target: &RawSubject, stats: ClassStat) { fn get_class(&self, subject: &RawSubject) -> Option<Class> {
if let Ok(target) = target.id() { subject
self.handle_stats_id(subject, target, stats) .id()
} .ok()
} .and_then(|id| self.classes.get(&id))
.copied()
fn handle_stats_id(&mut self, subject: SubjectId, target: SubjectId, stats: ClassStat) {
let subject = if let Some(steam_id) = subject.steam_id() {
steam_id
} else {
return;
};
if let Some(target_class) = self.classes.get(&target) {
self.stats.entry(SteamId3(subject)).or_default()[*target_class] += stats;
}
} }
} }
impl EventHandler for ClassStatsHandler { impl EventHandler for ClassStatsHandler {
type GlobalOutput = (); type GlobalOutput = ();
type PerSubjectData = ClassMap<ClassStat>; type PerSubjectData = ClassStats;
type PerSubjectOutput = ClassMap<ClassStat>; type PerSubjectOutput = ClassStats;
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
matches!( matches!(
ty, ty,
RawEventType::Killed RawEventType::Killed
@ -84,9 +51,9 @@ impl EventHandler for ClassStatsHandler {
fn handle( fn handle(
&mut self, &mut self,
_time: u32, _meta: &EventMeta,
subject: SubjectId, subject: SubjectId,
_subject_data: &mut Self::PerSubjectData, subject_data: &mut Self::PerSubjectData,
event: &GameEvent, event: &GameEvent,
) { ) {
match event { match event {
@ -101,48 +68,28 @@ impl EventHandler for ClassStatsHandler {
self.active = false; self.active = false;
} }
GameEvent::Kill(kill) if self.active => { GameEvent::Kill(kill) if self.active => {
self.handle_stats( if let Some(target_class) = self.get_class(&kill.target) {
subject, subject_data.kills[target_class] += 1;
&kill.target, }
ClassStat {
kills: 1,
..Default::default()
},
);
if let Ok(target) = kill.target.id() { if let Ok(target) = kill.target.id() {
self.handle_stats_id( if let Some(subject_class) = self.classes.get(&subject) {
target, self.deaths.entry(target).or_default()[*subject_class] += 1;
subject,
ClassStat {
deaths: 1,
..Default::default()
},
);
} }
} }
GameEvent::KillAssist(kill) if self.active => { }
self.handle_stats( GameEvent::KillAssist(assist) if self.active => {
subject, if let Some(target_class) = self.get_class(&assist.target) {
&kill.target, subject_data.assists[target_class] += 1;
ClassStat { }
assists: 1,
..Default::default()
},
);
} }
GameEvent::Damage(DamageEvent { GameEvent::Damage(DamageEvent {
damage: Some(damage), damage: Some(damage),
target, target,
.. ..
}) if self.active => { }) if self.active => {
self.handle_stats( if let Some(target_class) = self.get_class(target) {
subject, subject_data.damage[target_class] += damage.get() as u16;
target, }
ClassStat {
damage: damage.get() as u16,
..Default::default()
},
);
} }
_ => {} _ => {}
} }
@ -153,10 +100,11 @@ impl EventHandler for ClassStatsHandler {
} }
fn finish_per_subject( fn finish_per_subject(
&self, &mut self,
_subject: &SubjectData, subject: &SubjectData,
data: Self::PerSubjectData, mut data: Self::PerSubjectData,
) -> Self::PerSubjectOutput { ) -> Self::PerSubjectOutput {
data.deaths = self.deaths.remove(&subject.id()).unwrap_or_default();
data data
} }
} }

View file

@ -1,49 +1,34 @@
use crate::common::{SteamId3, SubjectId}; use crate::common::{SteamId3, SubjectId};
use crate::event::GameEvent; use crate::event::GameEvent;
use crate::module::EventHandler; use crate::module::PlayerSpecificData;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::{SubjectData, SubjectMap}; use crate::EventMeta;
use serde::Serialize;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryFrom; use std::convert::TryFrom;
#[derive(Default)] #[derive(Default, Serialize, PartialEq)]
pub struct HealSpreadHandler; pub struct HealSpread(BTreeMap<SteamId3, u32>);
impl EventHandler for HealSpreadHandler { impl PlayerSpecificData for HealSpread {
type GlobalOutput = (); type Output = HealSpread;
type PerSubjectData = BTreeMap<SteamId3, u32>;
type PerSubjectOutput = BTreeMap<SteamId3, u32>;
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
matches!(ty, RawEventType::Healed) matches!(ty, RawEventType::Healed)
} }
fn handle( fn handle_event(&mut self, _meta: &EventMeta, _subject: SubjectId, event: &GameEvent) {
&mut self,
_time: u32,
_subject: SubjectId,
subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
if let GameEvent::Healed(heal_event) = event { if let GameEvent::Healed(heal_event) = event {
if let Ok(target_subject) = SubjectId::try_from(&heal_event.target) { if let Ok(target_subject) = SubjectId::try_from(&heal_event.target) {
if let Some(target_steam_id) = target_subject.steam_id() { if let Some(target_steam_id) = target_subject.steam_id() {
let healed = subject_data.entry(SteamId3(target_steam_id)).or_default(); let healed = self.0.entry(SteamId3(target_steam_id)).or_default();
*healed += heal_event.amount *healed += heal_event.amount
} }
} }
} }
} }
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput { fn finish(self) -> Self::Output {
() self
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
} }
} }

View file

@ -1,8 +1,8 @@
use crate::common::SubjectId; use crate::common::SubjectId;
use crate::event::GameEvent; use crate::event::GameEvent;
use crate::module::EventHandler; use crate::module::GlobalData;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::{SubjectData, SubjectMap}; use crate::{EventMeta, SubjectMap};
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc}; use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use std::num::ParseIntError; use std::num::ParseIntError;
@ -10,7 +10,7 @@ use std::str::{FromStr, ParseBoolError};
use steamid_ng::SteamID; use steamid_ng::SteamID;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq)]
pub enum GameType { pub enum GameType {
Sixes, Sixes,
Highlander, Highlander,
@ -28,7 +28,7 @@ impl FromStr for GameType {
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq)]
pub enum Location { pub enum Location {
Europe, Europe,
NorthAmerica, NorthAmerica,
@ -46,7 +46,7 @@ impl FromStr for Location {
} }
} }
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize, PartialEq)]
pub struct LobbyLeader { pub struct LobbyLeader {
name: String, name: String,
steam_id: SteamID, steam_id: SteamID,
@ -71,7 +71,7 @@ impl FromStr for LobbyLeader {
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq)]
pub struct Settings { pub struct Settings {
id: u32, id: u32,
leader: LobbyLeader, leader: LobbyLeader,
@ -108,7 +108,7 @@ impl Default for Settings {
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error, PartialEq)]
pub enum LobbySettingsError { pub enum LobbySettingsError {
#[error("Malformed lobby id: {0}")] #[error("Malformed lobby id: {0}")]
InvalidLobbyId(String), InvalidLobbyId(String),
@ -196,22 +196,14 @@ impl LobbySettingsHandler {
} }
} }
impl EventHandler for LobbySettingsHandler { impl GlobalData for LobbySettingsHandler {
type GlobalOutput = Result<Option<Settings>, LobbySettingsError>; type Output = Option<Result<Settings, LobbySettingsError>>;
type PerSubjectData = ();
type PerSubjectOutput = ();
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
matches!(ty, RawEventType::Say) matches!(ty, RawEventType::Say)
} }
fn handle( fn handle_event(&mut self, _meta: &EventMeta, subject: SubjectId, event: &GameEvent) {
&mut self,
_time: u32,
subject: SubjectId,
_subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
if !matches!(subject, SubjectId::Console) { if !matches!(subject, SubjectId::Console) {
return; return;
} }
@ -222,21 +214,13 @@ impl EventHandler for LobbySettingsHandler {
} }
} }
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput { fn finish(self, _subjects: &SubjectMap) -> Self::Output {
match self { match self {
LobbySettingsHandler::NotAvailable => Ok(None), LobbySettingsHandler::NotAvailable => None,
LobbySettingsHandler::Active(settings) => Ok(Some(settings)), LobbySettingsHandler::Active(settings) => Some(Ok(settings)),
LobbySettingsHandler::Err(e) => Err(e), LobbySettingsHandler::Err(e) => Some(Err(e)),
} }
} }
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data
}
} }
fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> { fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> {

View file

@ -1,10 +1,9 @@
use crate::common::SubjectId; use crate::common::SubjectId;
use crate::event::GameEvent; use crate::event::GameEvent;
use crate::module::EventHandler; use crate::module::PlayerSpecificData;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::{SubjectData, SubjectMap}; use crate::EventMeta;
use serde::Serialize; use serde::Serialize;
use thiserror::Error;
#[derive(Default)] #[derive(Default)]
pub struct MedicStatsBuilder { pub struct MedicStatsBuilder {
@ -24,7 +23,7 @@ pub struct MedicStatsBuilder {
drops: u32, drops: u32,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, Default, PartialEq)]
pub struct MedicStats { pub struct MedicStats {
advantages_lost: u32, advantages_lost: u32,
biggest_advantage_lost: f32, biggest_advantage_lost: f32,
@ -40,6 +39,9 @@ pub struct MedicStats {
impl From<MedicStatsBuilder> for MedicStats { impl From<MedicStatsBuilder> for MedicStats {
fn from(builder: MedicStatsBuilder) -> Self { fn from(builder: MedicStatsBuilder) -> Self {
if builder.start_healing_count == 0 {
return Self::default();
}
MedicStats { MedicStats {
advantages_lost: builder.advantages_lost, advantages_lost: builder.advantages_lost,
biggest_advantage_lost: builder.biggest_advantage_lost, biggest_advantage_lost: builder.biggest_advantage_lost,
@ -56,19 +58,10 @@ impl From<MedicStatsBuilder> for MedicStats {
} }
} }
#[derive(Error, Debug)] impl PlayerSpecificData for MedicStatsBuilder {
#[error("Invalid charge event: {0}")] type Output = MedicStats;
pub struct InvalidMedicEvent(String);
#[derive(Default)] fn does_handle(ty: RawEventType) -> bool {
pub struct MedicStatsHandler;
impl EventHandler for MedicStatsHandler {
type GlobalOutput = ();
type PerSubjectData = MedicStatsBuilder;
type PerSubjectOutput = MedicStats;
fn does_handle(&self, ty: RawEventType) -> bool {
matches!( matches!(
ty, ty,
RawEventType::ChargeDeployed RawEventType::ChargeDeployed
@ -80,65 +73,51 @@ impl EventHandler for MedicStatsHandler {
) )
} }
fn handle( fn handle_event(&mut self, meta: &EventMeta, _subject: SubjectId, event: &GameEvent) {
&mut self,
time: u32,
_subject: SubjectId,
subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
match event { match event {
GameEvent::ChargeEnded(end) => { GameEvent::ChargeEnded(end) => {
subject_data.total_uber_length += end.duration.unwrap_or_default(); self.total_uber_length += end.duration.unwrap_or_default();
subject_data.last_uber_end = time; self.last_uber_end = meta.time;
} }
GameEvent::ChargeDeployed(_deployed) => { GameEvent::ChargeDeployed(_deployed) => {
subject_data.charge_count += 1; self.charge_count += 1;
} }
GameEvent::AdvantageLost(lost) => { GameEvent::AdvantageLost(lost) => {
subject_data.advantages_lost += 1; self.advantages_lost += 1;
let time = lost.time.unwrap_or_default(); let time = lost.time.unwrap_or_default();
if time > subject_data.biggest_advantage_lost { if time > self.biggest_advantage_lost {
subject_data.biggest_advantage_lost = time; self.biggest_advantage_lost = time;
} }
} }
GameEvent::FirstHeal(first) => { GameEvent::FirstHeal(first) => {
subject_data.total_time_before_healing += first.time.unwrap_or_default(); self.total_time_before_healing += first.time.unwrap_or_default();
subject_data.start_healing_count += 1; self.start_healing_count += 1;
subject_data.last_build_start = time; self.last_build_start = meta.time;
} }
GameEvent::ChargeReady => { GameEvent::ChargeReady => {
if subject_data.last_build_start > 0 { if self.last_build_start > 0 {
let build_time = time - subject_data.last_build_start; let build_time = meta.time - self.last_build_start;
subject_data.last_build_start = 0; self.last_build_start = 0;
subject_data.total_time_to_build += build_time; self.total_time_to_build += build_time;
subject_data.uber_build_count += 1; self.uber_build_count += 1;
} }
} }
GameEvent::MedicDeath(death) => { GameEvent::MedicDeath(death) => {
let charge = death.charge.unwrap_or_default(); let charge = death.charge.unwrap_or_default();
if charge >= 95 && charge < 100 { if charge >= 95 && charge < 100 {
subject_data.near_full_charge_death += 1; self.near_full_charge_death += 1;
} else if charge >= 100 { } else if charge >= 100 {
subject_data.drops += 1; self.drops += 1;
} }
if time - subject_data.last_uber_end <= 10 { if meta.time - self.last_uber_end <= 10 {
subject_data.deaths_after_uber += 1; self.deaths_after_uber += 1;
} }
} }
_ => {} _ => {}
} }
} }
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput { fn finish(self) -> Self::Output {
() self.into()
}
fn finish_per_subject(
&self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data.into()
} }
} }

View file

@ -1,14 +1,16 @@
use crate::common::SubjectId; use crate::common::SubjectId;
use crate::event::GameEvent; use crate::event::{EventMeta, GameEvent};
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::{SubjectData, SubjectMap}; use crate::{SubjectData, SubjectMap};
pub use chat::{ChatHandler, ChatMessage, ChatType}; pub use chat::{ChatMessage, ChatMessages, ChatType};
pub use classstats::{ClassStat, ClassStatsHandler}; pub use classstats::{ClassStats, ClassStatsHandler};
pub use healspread::HealSpreadHandler; pub use healspread::HealSpread;
pub use lobbysettings::{ pub use lobbysettings::{
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings, LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
}; };
pub use medicstats::{MedicStats, MedicStatsHandler}; pub use medicstats::MedicStats;
use serde::Serialize;
use std::marker::PhantomData;
mod chat; mod chat;
mod classstats; mod classstats;
@ -21,11 +23,11 @@ pub trait EventHandler: Default {
type PerSubjectData: Default; type PerSubjectData: Default;
type PerSubjectOutput; type PerSubjectOutput;
fn does_handle(&self, ty: RawEventType) -> bool; fn does_handle(ty: RawEventType) -> bool;
fn handle( fn handle(
&mut self, &mut self,
time: u32, meta: &EventMeta,
subject: SubjectId, subject: SubjectId,
subject_data: &mut Self::PerSubjectData, subject_data: &mut Self::PerSubjectData,
event: &GameEvent, event: &GameEvent,
@ -34,7 +36,7 @@ pub trait EventHandler: Default {
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput; fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput;
fn finish_per_subject( fn finish_per_subject(
&self, &mut self,
subject: &SubjectData, subject: &SubjectData,
data: Self::PerSubjectData, data: Self::PerSubjectData,
) -> Self::PerSubjectOutput; ) -> Self::PerSubjectOutput;
@ -51,19 +53,19 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
type PerSubjectData = (Head::PerSubjectData, Tail::PerSubjectData); type PerSubjectData = (Head::PerSubjectData, Tail::PerSubjectData);
type PerSubjectOutput = (Head::PerSubjectOutput, Tail::PerSubjectOutput); type PerSubjectOutput = (Head::PerSubjectOutput, Tail::PerSubjectOutput);
fn does_handle(&self, ty: RawEventType) -> bool { fn does_handle(ty: RawEventType) -> bool {
self.head.does_handle(ty) || self.tail.does_handle(ty) Head::does_handle(ty) || Tail::does_handle(ty)
} }
fn handle( fn handle(
&mut self, &mut self,
time: u32, meta: &EventMeta,
subject: SubjectId, subject: SubjectId,
subject_data: &mut Self::PerSubjectData, subject_data: &mut Self::PerSubjectData,
event: &GameEvent, event: &GameEvent,
) { ) {
self.head.handle(time, subject, &mut subject_data.0, event); self.head.handle(meta, subject, &mut subject_data.0, event);
self.tail.handle(time, subject, &mut subject_data.1, event); self.tail.handle(meta, subject, &mut subject_data.1, event);
} }
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput { fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
@ -74,7 +76,7 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
} }
fn finish_per_subject( fn finish_per_subject(
&self, &mut self,
subject: &SubjectData, subject: &SubjectData,
data: Self::PerSubjectData, data: Self::PerSubjectData,
) -> Self::PerSubjectOutput { ) -> Self::PerSubjectOutput {
@ -85,16 +87,6 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
} }
} }
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
macro_rules! count_tts {
($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
}
#[macro_export] #[macro_export]
macro_rules! handler { macro_rules! handler {
($name:ident {$($child:ident: $ty:path),*}) => { ($name:ident {$($child:ident: $ty:path),*}) => {
@ -117,8 +109,12 @@ macro_rules! handler {
S: serde::Serializer, S: serde::Serializer,
{ {
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), count_tts!($($child)*))?; let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), 0)?;
$(state.serialize_field(stringify!($child), &self.$child)?;)* $(
if self.$child != <<$ty as $crate::EventHandler>::GlobalOutput>::default() {
state.serialize_field(stringify!($child), &self.$child)?;
}
)*
state.end() state.end()
} }
} }
@ -145,8 +141,12 @@ macro_rules! handler {
S: serde::Serializer, S: serde::Serializer,
{ {
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), count_tts!($($child)*))?; let mut state = serializer.serialize_struct(concat!(stringify!($name), "output"), 0)?;
$(state.serialize_field(stringify!($child), &self.$child)?;)* $(
if self.$child != <<$ty as $crate::EventHandler>::PerSubjectOutput>::default() {
state.serialize_field(stringify!($child), &self.$child)?;
}
)*
state.end() state.end()
} }
} }
@ -157,16 +157,16 @@ macro_rules! handler {
type PerSubjectData = [<$name PerSubjectData>]; type PerSubjectData = [<$name PerSubjectData>];
type PerSubjectOutput = [<$name PerSubjectOutput>]; type PerSubjectOutput = [<$name PerSubjectOutput>];
fn does_handle(&self, ty: $crate::RawEventType) -> bool { fn does_handle(ty: $crate::RawEventType) -> bool {
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::EventHandler; use $crate::EventHandler;
$(self.$child.does_handle(ty))||* $($ty::does_handle(ty))||*
} }
fn handle(&mut self, time: u32, subject: $crate::SubjectId,subject_data:&mut Self::PerSubjectData, event: &$crate::GameEvent) { fn handle(&mut self, meta: &$crate::EventMeta, subject: $crate::SubjectId,subject_data:&mut Self::PerSubjectData, event: &$crate::GameEvent) {
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::EventHandler; use $crate::EventHandler;
$(self.$child.handle(time, subject, &mut subject_data.$child, event);)* $(self.$child.handle(meta, subject, &mut subject_data.$child, event);)*
} }
fn finish_global(self, subjects: &$crate::SubjectMap) -> Self::GlobalOutput { fn finish_global(self, subjects: &$crate::SubjectMap) -> Self::GlobalOutput {
@ -177,7 +177,7 @@ macro_rules! handler {
} }
} }
fn finish_per_subject(&self, subject: &SubjectData, data: Self::PerSubjectData) -> Self::PerSubjectOutput { fn finish_per_subject(&mut self, subject: &$crate::SubjectData, data: Self::PerSubjectData) -> Self::PerSubjectOutput {
Self::PerSubjectOutput { Self::PerSubjectOutput {
$($child: self.$child.finish_per_subject(subject, data.$child),)* $($child: self.$child.finish_per_subject(subject, data.$child),)*
} }
@ -186,3 +186,86 @@ macro_rules! handler {
} }
}; };
} }
pub trait GlobalData: Default {
type Output;
fn does_handle(ty: RawEventType) -> bool;
fn handle_event(&mut self, meta: &EventMeta, subject: SubjectId, event: &GameEvent);
fn finish(self, subjects: &SubjectMap) -> Self::Output;
}
impl<T: GlobalData> EventHandler for T {
type GlobalOutput = T::Output;
type PerSubjectData = ();
type PerSubjectOutput = ();
fn does_handle(ty: RawEventType) -> bool {
T::does_handle(ty)
}
fn handle(
&mut self,
meta: &EventMeta,
subject: SubjectId,
_subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
self.handle_event(meta, subject, event)
}
fn finish_global(self, subjects: &SubjectMap) -> Self::GlobalOutput {
self.finish(subjects)
}
fn finish_per_subject(
&mut self,
_subject: &SubjectData,
_data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
()
}
}
pub trait PlayerSpecificData: Default {
type Output: Serialize;
fn does_handle(ty: RawEventType) -> bool;
fn handle_event(&mut self, meta: &EventMeta, subject: SubjectId, event: &GameEvent);
fn finish(self) -> Self::Output;
}
#[derive(Default)]
pub struct PlayerHandler<T: PlayerSpecificData>(PhantomData<T>);
impl<T: PlayerSpecificData + Default> EventHandler for PlayerHandler<T> {
type GlobalOutput = ();
type PerSubjectData = T;
type PerSubjectOutput = T::Output;
fn does_handle(ty: RawEventType) -> bool {
T::does_handle(ty)
}
fn handle(
&mut self,
meta: &EventMeta,
subject: SubjectId,
subject_data: &mut Self::PerSubjectData,
event: &GameEvent,
) {
subject_data.handle_event(meta, subject, event)
}
fn finish_global(self, _subjects: &SubjectMap) -> Self::GlobalOutput {
()
}
fn finish_per_subject(
&mut self,
_subject: &SubjectData,
data: Self::PerSubjectData,
) -> Self::PerSubjectOutput {
data.finish()
}
}

View file

@ -37,6 +37,10 @@ impl<T: Default> SubjectMap<T> {
} }
impl<T> SubjectMap<T> { impl<T> SubjectMap<T> {
pub fn subject(&self, id: SubjectId) -> &SubjectData {
&self[id].0
}
pub fn to_just_subjects(&self) -> SubjectMap<()> { pub fn to_just_subjects(&self) -> SubjectMap<()> {
SubjectMap( SubjectMap(
self.0 self.0