some event parsing

This commit is contained in:
Robin Appelman 2021-08-07 22:07:59 +02:00
commit e83f7928aa
7 changed files with 282 additions and 61 deletions

View file

@ -12,3 +12,10 @@ thiserror = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
main_error = "0.1" main_error = "0.1"
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "bench"
harness = false

11
benches/bench.rs Normal file
View file

@ -0,0 +1,11 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::fs::read_to_string;
use tf_log_parser::parse;
pub fn parse_benchmark(c: &mut Criterion) {
let input = read_to_string("test_data/log_2892242.log").unwrap();
c.bench_function("parse 2892242", |b| b.iter(|| parse(black_box(&input))));
}
criterion_group!(benches, parse_benchmark);
criterion_main!(benches);

View file

@ -1,7 +1,5 @@
use crate::event::{param_parse_with, quoted, u_int};
use crate::raw_event::{subject_parser, RawSubject}; use crate::raw_event::{subject_parser, RawSubject};
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::error::ErrorKind;
use nom::IResult; use nom::IResult;
#[derive(Debug)] #[derive(Debug)]
@ -11,12 +9,7 @@ pub struct HealedEvent<'a> {
} }
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> { pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
let (input, _) = tag("against ")(input)?; let (input, subject) = param_parse_with("against", subject_parser)(input)?;
let (input, subject) = subject_parser(input)?; let (input, amount) = param_parse_with("healing", quoted(u_int))(input)?;
let (input, _) = tag(r#" (healing ""#)(input)?;
let (input, raw_amount) = digit1(input)?;
let amount = raw_amount
.parse()
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw_amount, ErrorKind::Digit)))?;
Ok((input, HealedEvent { subject, amount })) Ok((input, HealedEvent { subject, amount }))
} }

View file

@ -1,3 +1,104 @@
mod medic; mod medic;
mod player;
pub use medic::{healed_event_parser, HealedEvent}; pub use medic::*;
use nom::bytes::complete::{tag, take_while};
use nom::character::complete::{alpha1, digit1};
use nom::combinator::opt;
use nom::error::ErrorKind;
use nom::IResult;
pub use player::*;
struct ParamIter<'a> {
input: &'a str,
}
impl<'a> ParamIter<'a> {
pub fn new(input: &'a str) -> Self {
ParamIter { input }
}
}
impl<'a> Iterator for ParamIter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let (input, res) = param_pair_parse(self.input).ok()?;
self.input = input;
Some(res)
}
}
fn param_pair_parse(input: &str) -> IResult<&str, (&str, &str)> {
let (input, open_tag) = opt(tag("("))(input)?;
let (input, key) = alpha1(input)?;
let (input, _) = tag(r#" ""#)(input)?;
let (input, value) = take_while(|c| c != '"')(input)?;
let (input, _) = tag(r#"""#)(input)?;
if open_tag.is_some() {
let (_input, _) = tag("(")(input)?;
}
Ok((input, (key, value)))
}
fn quoted<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
parser: P,
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
move |input| {
let (input, _) = tag(r#"""#)(input)?;
let (input, res) = parser(input)?;
let (input, _) = tag(r#"""#)(input)?;
Ok((input, res))
}
}
fn param_parse<'a>(key: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> {
param_parse_with(key, quoted(take_while(|c| c != '"')))
}
fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
key: &'a str,
parser: P,
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
move |input: &str| {
let (input, open_tag) = opt(tag("("))(input)?;
let (input, _) = tag(key)(input)?;
let (input, _) = tag(r#" "#)(input)?;
let (input, value) = parser(input)?;
if open_tag.is_some() {
let (_input, _) = tag(")")(input)?;
}
Ok((input.trim_start(), value))
}
}
fn int(input: &str) -> IResult<&str, i32> {
let (input, sign) = opt(tag("-"))(input)?;
let (input, raw) = digit1(input)?;
let val: i32 = raw
.parse()
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw, ErrorKind::Digit)))?;
Ok((input, if sign.is_some() { -val } else { val }))
}
fn u_int(input: &str) -> IResult<&str, u32> {
let (input, raw) = digit1(input)?;
let val = raw
.parse()
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw, ErrorKind::Digit)))?;
Ok((input, val))
}
fn position(input: &str) -> IResult<&str, (i32, i32, i32)> {
let (input, x) = int(input)?;
let (input, _) = tag(" ")(input)?;
let (input, y) = int(input)?;
let (input, _) = tag(" ")(input)?;
let (input, z) = int(input)?;
Ok((input, (x, y, z)))
}

103
src/event/player.rs Normal file
View file

@ -0,0 +1,103 @@
use crate::event::{param_parse, param_parse_with, position, quoted, u_int, ParamIter};
use crate::raw_event::{subject_parser, RawSubject};
use nom::combinator::opt;
use nom::IResult;
use std::num::NonZeroU32;
#[derive(Debug)]
pub struct ShotFiredEvent<'a> {
pub weapon: Option<&'a str>,
}
pub fn shot_fired_event_parser(input: &str) -> IResult<&str, ShotFiredEvent> {
let (input, weapon) = opt(param_parse("weapon"))(input)?;
Ok((input, ShotFiredEvent { weapon }))
}
#[derive(Debug)]
pub struct ShotHitEvent<'a> {
pub weapon: Option<&'a str>,
}
pub fn shot_hit_event_parser(input: &str) -> IResult<&str, ShotFiredEvent> {
let (input, weapon) = opt(param_parse("weapon"))(input)?;
Ok((input, ShotFiredEvent { weapon }))
}
#[derive(Debug)]
pub struct DamageEvent<'a> {
pub target: RawSubject<'a>,
pub damage: Option<NonZeroU32>,
pub real_damage: Option<NonZeroU32>,
pub weapon: Option<&'a str>,
}
pub fn damage_event_parser(input: &str) -> IResult<&str, DamageEvent> {
let (input, target) = param_parse_with("against", subject_parser)(input)?;
let mut event = DamageEvent {
target,
damage: None,
real_damage: None,
weapon: None,
};
for (key, value) in ParamIter::new(input) {
match key {
"damage" => event.damage = NonZeroU32::new(quoted(u_int)(value)?.1),
"realdamage" => event.real_damage = NonZeroU32::new(quoted(u_int)(value)?.1),
"weapon" => event.weapon = Some(value.trim_matches('"')),
_ => {}
}
}
Ok(("", event))
}
#[derive(Debug)]
pub struct KillEvent<'a> {
pub target: RawSubject<'a>,
pub weapon: &'a str,
pub attacker_position: Option<(i32, i32, i32)>,
pub victim_position: Option<(i32, i32, i32)>,
}
pub fn kill_event_parser(input: &str) -> IResult<&str, KillEvent> {
let (input, target) = param_parse_with("against", subject_parser)(input)?;
let (input, weapon) = param_parse("with")(input)?;
let mut event = KillEvent {
target,
weapon,
attacker_position: None,
victim_position: None,
};
for (key, value) in ParamIter::new(input) {
match key {
"attacker_position" => event.attacker_position = Some(position(value)?.1),
"victim_position" => event.victim_position = Some(position(value)?.1),
_ => {}
}
}
Ok(("", event))
}
#[derive(Debug)]
pub struct KillAssistEvent<'a> {
pub target: RawSubject<'a>,
pub attacker_position: Option<(i32, i32, i32)>,
pub victim_position: Option<(i32, i32, i32)>,
}
pub fn kill_assist_event_parser(input: &str) -> IResult<&str, KillAssistEvent> {
let (input, target) = param_parse_with("against", subject_parser)(input)?;
let mut event = KillAssistEvent {
target,
attacker_position: None,
victim_position: None,
};
for (key, value) in ParamIter::new(input) {
match key {
"attacker_position" => event.attacker_position = Some(position(value)?.1),
"victim_position" => event.victim_position = Some(position(value)?.1),
_ => {}
}
}
Ok(("", event))
}

View file

@ -1,9 +1,10 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId}; pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::module::EventHandler; use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent};
use crate::raw_event::RawSubject; use crate::raw_event::RawSubject;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use raw_event::{RawEvent, RawEventType}; pub use raw_event::{RawEvent, RawEventType};
use std::collections::BTreeMap; use serde::Serialize;
use std::collections::{BTreeMap, HashMap};
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::ops::Index; use std::ops::Index;
@ -66,6 +67,10 @@ impl SubjectMap {
} }
} }
pub fn parse(log: &str) -> Result<LogOutput, Error<LogHandler>> {
parse_with_handler::<LogHandler>(log)
}
pub fn parse_with_handler<Handler: EventHandler>( pub fn parse_with_handler<Handler: EventHandler>(
log: &str, log: &str,
) -> Result<Handler::Output, Error<Handler>> { ) -> Result<Handler::Output, Error<Handler>> {
@ -98,3 +103,48 @@ pub fn parse_with_handler<Handler: EventHandler>(
Ok(handler.finish(&subjects)) Ok(handler.finish(&subjects))
} }
#[derive(Default)]
pub struct LogHandler {
chat: ChatHandler,
heal_spread: HealSpreadHandler,
}
#[derive(Default, Serialize)]
pub struct LogOutput {
chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
}
#[derive(Error, Debug)]
pub enum LogError {
#[error("{0}")]
HealSpread(#[from] InvalidHealEvent),
}
impl EventHandler for LogHandler {
type Output = LogOutput;
type Error = LogError;
fn does_handle(&self, ty: RawEventType) -> bool {
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty)
}
fn handle(
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event)?;
Ok(())
}
fn finish(self, subjects: &SubjectMap) -> Self::Output {
LogOutput {
chat: self.chat.finish(subjects),
heal_spread: self.heal_spread.finish(subjects),
}
}
}

View file

@ -7,59 +7,15 @@ use std::io::stdout;
use tf_log_parser::module::{ use tf_log_parser::module::{
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent, ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent,
}; };
use tf_log_parser::{parse_with_handler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap}; use tf_log_parser::{
use thiserror::Error; parse, parse_with_handler, LogHandler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap,
};
#[derive(Default)]
struct LogHandler {
chat: ChatHandler,
heal_spread: HealSpreadHandler,
}
#[derive(Default, Serialize)]
struct LogOutput {
chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
}
#[derive(Error, Debug)]
enum LogError {
#[error("{0}")]
HealSpread(#[from] InvalidHealEvent),
}
impl EventHandler for LogHandler {
type Output = LogOutput;
type Error = LogError;
fn does_handle(&self, ty: RawEventType) -> bool {
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty)
}
fn handle(
&mut self,
time: u32,
subject: SubjectId,
event: &RawEvent,
) -> Result<(), Self::Error> {
self.chat.handle(time, subject, event).unwrap();
self.heal_spread.handle(time, subject, event)?;
Ok(())
}
fn finish(self, subjects: &SubjectMap) -> Self::Output {
LogOutput {
chat: self.chat.finish(subjects),
heal_spread: self.heal_spread.finish(subjects),
}
}
}
fn main() -> Result<(), MainError> { 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 log = parse_with_handler::<LogHandler>(&content)?; let log = parse(&content)?;
serde_json::to_writer_pretty(stdout().lock(), &log).unwrap(); serde_json::to_writer_pretty(stdout().lock(), &log).unwrap();