First iteration that should work correctly but seems to have some memory issues with entities

This commit is contained in:
Lol3rrr
2024-10-17 21:15:03 +02:00
parent 840ac071b1
commit 14c422983e
15 changed files with 735 additions and 18 deletions

View File

@@ -43,3 +43,34 @@ mod eager {
}
}
}
mod lazy {
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
fn no_entities_mirage() {
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
let demo = csdemo::lazyparser::LazyParser::new(container);
for event in demo.events() {
divan::black_box(event);
}
}
#[divan::bench(max_time = std::time::Duration::from_secs(30))]
fn entities_mirage() {
let raw_bytes = include_bytes!("../testfiles/mirage.dem");
let container = csdemo::Container::parse(divan::black_box(raw_bytes.as_slice())).unwrap();
let demo = csdemo::lazyparser::LazyParser::new(container);
for event in demo.events() {
divan::black_box(event);
}
for entity in demo.entities() {
divan::black_box(entity);
}
}
}

View File

@@ -0,0 +1,25 @@
const DATA: &[u8] = include_bytes!("../testfiles/de_ancient.dem");
fn main() {
let container = csdemo::Container::parse(DATA).unwrap();
let demo = csdemo::lazyparser::LazyParser::new(container);
for entity in demo.entities() {
core::hint::black_box(entity);
}
/*
for tick in output.entity_states.ticks.iter() {
for state in tick.states.iter() {
if state.class.as_str() != "CCSPlayerPawn" {
continue;
}
for prop in state.props.iter() {
println!("{:?} = {:?}", prop.prop_info.prop_name, prop.value);
}
}
}
*/
}

1
profile.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@ use crate::{csgo_proto, RawValue, UserId};
macro_rules! define_event {
($name:ident, $target:path $(, ($field:ident, $field_ty:ty))*) => {
#[derive(Debug)]
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
pub struct $name {
$(pub $field: Option<$field_ty>,)*
@@ -231,7 +231,7 @@ type ParseFn = fn(
event: csgo_proto::CMsgSource1LegacyGameEvent,
) -> Result<GameEvent, ParseGameEventError>;
#[derive(Debug)]
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
pub enum GameEvent {
HltvVersionInfo(HltvVersionInfo),

53
src/lazyparser.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::{Container, FrameIterator};
use std::collections::VecDeque;
mod events;
pub use events::LazyEventIterator;
mod entities;
pub use entities::LazyEntityIterator;
pub struct LazyParser<'b> {
container: Container<'b>,
}
impl<'b> LazyParser<'b> {
pub fn new(container: Container<'b>) -> Self {
Self { container }
}
pub fn file_header(&self) -> Option<crate::csgo_proto::CDemoFileHeader> {
let mut buffer = Vec::new();
for frame in FrameIterator::parse(self.container.inner) {
if let crate::DemoCommand::FileHeader = frame.cmd {
let data = frame.decompress_with_buf(&mut buffer).ok()?;
let raw: crate::csgo_proto::CDemoFileHeader = prost::Message::decode(data).ok()?;
return Some(raw);
}
}
None
}
pub fn file_info(&self) -> Option<crate::csgo_proto::CDemoFileInfo> {
let mut buffer = Vec::new();
for frame in FrameIterator::parse(self.container.inner) {
if let crate::DemoCommand::FileInfo = frame.cmd {
let data = frame.decompress_with_buf(&mut buffer).ok()?;
let raw: crate::csgo_proto::CDemoFileInfo = prost::Message::decode(data).ok()?;
return Some(raw);
}
}
None
}
pub fn events(&self) -> LazyEventIterator<'b> {
LazyEventIterator::new(self)
}
pub fn entities(&self) -> LazyEntityIterator<'b> {
LazyEntityIterator::new(self)
}
}

342
src/lazyparser/entities.rs Normal file
View File

@@ -0,0 +1,342 @@
use crate::{
parser::{
decoder, entities, propcontroller, sendtables, update_entity, Class, EntityTickList,
FirstPassError, Paths,
},
DemoCommand, FrameIterator,
};
use std::collections::VecDeque;
pub struct LazyEntityIterator<'b> {
buffer: Vec<u8>,
frames: FrameIterator<'b>,
current_tick: u32,
pending_entities: VecDeque<(u32, entities::EntityState)>,
paths: Paths,
baselines: std::collections::HashMap<u32, Vec<u8>>,
serializers: std::collections::HashMap<String, sendtables::Serializer>,
qf_mapper: decoder::QfMapper,
prop_controller: propcontroller::PropController,
entity_ctx: entities::EntityContext,
}
impl<'b> LazyEntityIterator<'b> {
pub(crate) fn new(parser: &super::LazyParser<'b>) -> Self {
Self {
buffer: Vec::new(),
frames: FrameIterator::parse(parser.container.inner),
current_tick: 0,
pending_entities: VecDeque::with_capacity(64),
paths: Paths::new(),
baselines: std::collections::HashMap::new(),
serializers: std::collections::HashMap::new(),
qf_mapper: decoder::QfMapper {
idx: 0,
map: std::collections::HashMap::new(),
},
prop_controller: propcontroller::PropController::new(),
entity_ctx: entities::EntityContext {
entities: std::collections::HashMap::new(),
cls_to_class: std::collections::HashMap::new(),
filter: entities::EntityFilter::all(),
},
}
}
}
impl<'b> LazyEntityIterator<'b> {
fn inner_parse_packet(
raw: &crate::csgo_proto::CDemoPacket,
entity_ctx: &mut entities::EntityContext,
paths: &mut Paths,
qf_mapper: &mut decoder::QfMapper,
baselines: &mut std::collections::HashMap<u32, Vec<u8>>,
prop_controller: &propcontroller::PropController,
entity_states: &mut VecDeque<(u32, entities::EntityState)>,
current_tick: &mut u32,
) -> Result<(), FirstPassError> {
let mut bitreader = crate::bitreader::Bitreader::new(raw.data());
while bitreader.bits_remaining().unwrap_or(0) > 8 {
let msg_type = bitreader.read_u_bit_var()?;
let size = bitreader.read_varint()?;
let msg_bytes = bitreader.read_n_bytes(size as usize)?;
assert_eq!(msg_bytes.len(), size as usize);
let net_msg_type =
match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) {
Ok(v) => v,
Err(e) => {
dbg!(e);
continue;
}
};
match net_msg_type {
crate::netmessagetypes::NetmessageType::net_Tick => {
let raw: crate::csgo_proto::CnetMsgTick =
prost::Message::decode(msg_bytes.as_slice())?;
assert!(
*current_tick <= raw.tick(),
"Current Tick {} <= Tick Packet {}",
*current_tick,
raw.tick()
);
if raw.tick() > *current_tick {
*current_tick = raw.tick();
}
}
// TODO
// How to handle these things?
crate::netmessagetypes::NetmessageType::svc_ClearAllStringTables => {}
crate::netmessagetypes::NetmessageType::svc_CreateStringTable => {}
crate::netmessagetypes::NetmessageType::svc_UpdateStringTable => {}
crate::netmessagetypes::NetmessageType::svc_PacketEntities => {
let raw: crate::csgo_proto::CsvcMsgPacketEntities =
prost::Message::decode(msg_bytes.as_slice())?;
if entity_ctx.filter.enabled {
let mut bitreader = crate::bitreader::Bitreader::new(raw.entity_data());
let mut entity_id: i32 = -1;
for _ in 0..raw.updated_entries() {
entity_id += 1 + (bitreader.read_u_bit_var()? as i32);
match bitreader.read_nbits(2)? {
0b01 | 0b11 => {
entity_ctx.entities.remove(&entity_id);
}
0b10 => {
let cls =
entity_ctx.create_entity(entity_id, &mut bitreader)?;
if let Some(baseline_bytes) = baselines.get(&cls) {
let mut br =
crate::bitreader::Bitreader::new(baseline_bytes);
// TODO
// How should we handle is this?
let _state = update_entity(
entity_id,
&mut br,
entity_ctx,
paths,
qf_mapper,
prop_controller,
)?;
}
let state = update_entity(
entity_id,
&mut bitreader,
entity_ctx,
paths,
qf_mapper,
prop_controller,
)?;
if let Some(state) = state {
entity_states.push_back((*current_tick, state));
}
}
0b00 => {
if raw.has_pvs_vis_bits() > 0
&& bitreader.read_nbits(2)? & 0x01 == 1
{
continue;
}
let state = update_entity(
entity_id,
&mut bitreader,
entity_ctx,
paths,
qf_mapper,
prop_controller,
)?;
if let Some(state) = state {
entity_states.push_back((*current_tick, state));
}
}
unknown => {
panic!("{:?}", unknown);
}
};
}
}
}
_ => {
// dbg!(unknown);
}
};
}
Ok(())
}
}
impl<'b> Iterator for LazyEntityIterator<'b> {
type Item = Result<(u32, crate::parser::entities::EntityState), ()>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(tmp) = self.pending_entities.pop_front() {
return Some(Ok(tmp));
}
while let Some(frame) = self.frames.next() {
match frame.cmd {
DemoCommand::SignonPacket | DemoCommand::Packet => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoPacket = match prost::Message::decode(data) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
if let Err(e) = Self::inner_parse_packet(
&raw,
&mut self.entity_ctx,
&mut self.paths,
&mut self.qf_mapper,
&mut self.baselines,
&mut self.prop_controller,
&mut self.pending_entities,
&mut self.current_tick,
) {
return Some(Err(()));
}
}
DemoCommand::FullPacket => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoFullPacket = match prost::Message::decode(data)
{
Ok(d) => d,
Err(e) => return Some(Err(())),
};
if let Some(packet) = raw.packet {
if let Err(e) = Self::inner_parse_packet(
&packet,
&mut self.entity_ctx,
&mut self.paths,
&mut self.qf_mapper,
&mut self.baselines,
&mut self.prop_controller,
&mut self.pending_entities,
&mut self.current_tick,
) {
return Some(Err(()));
}
}
}
// Handling all the "meta" related stuff
DemoCommand::StringTables => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoStringTables =
match prost::Message::decode(data) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
for table in raw.tables.iter() {
if table.table_name() == "instancebaseline" {
for item in table.items.iter() {
let k = item.str().parse::<u32>().unwrap_or(u32::MAX);
self.baselines.insert(k, item.data().to_vec());
}
}
}
}
DemoCommand::SendTables => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let tables: crate::csgo_proto::CDemoSendTables =
match prost::Message::decode(data) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let mut bitreader = crate::bitreader::Bitreader::new(tables.data());
let n_bytes = match bitreader.read_varint() {
Ok(b) => b,
Err(e) => return Some(Err(())),
};
let bytes = match bitreader.read_n_bytes(n_bytes as usize) {
Ok(b) => b,
Err(e) => return Some(Err(())),
};
let serializer_msg: crate::csgo_proto::CsvcMsgFlattenedSerializer =
match prost::Message::decode(bytes.as_slice()) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
// std::fs::write("send_table.b", bytes.as_slice());
assert!(self.serializers.is_empty());
self.serializers = match sendtables::get_serializers(
&serializer_msg,
&mut self.qf_mapper,
&mut self.prop_controller,
) {
Ok(s) => s,
Err(e) => return Some(Err(())),
};
}
DemoCommand::ClassInfo => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoClassInfo = match prost::Message::decode(data)
{
Ok(d) => d,
Err(e) => return Some(Err(())),
};
self.entity_ctx.cls_to_class.clear();
for class_t in raw.classes {
let cls_id = class_t.class_id();
let network_name = class_t.network_name();
if let Some(ser) = self.serializers.remove(network_name) {
self.entity_ctx.cls_to_class.insert(
cls_id as u32,
Class {
name: network_name.into(),
serializer: ser,
},
);
}
}
}
_ => continue,
};
if let Some(tmp) = self.pending_entities.pop_front() {
return Some(Ok(tmp));
}
}
None
}
}

213
src/lazyparser/events.rs Normal file
View File

@@ -0,0 +1,213 @@
use crate::{parser::GameEventMapping, DemoEvent, FrameIterator};
use std::collections::VecDeque;
pub struct LazyEventIterator<'b> {
pub(super) buffer: Vec<u8>,
pub(super) frames: FrameIterator<'b>,
pub(super) pending_events: VecDeque<crate::DemoEvent>,
pub(super) event_mapper: GameEventMapping,
}
impl<'b> LazyEventIterator<'b> {
pub(crate) fn new(parser: &super::LazyParser<'b>) -> Self {
Self {
buffer: Vec::new(),
frames: FrameIterator::parse(parser.container.inner),
pending_events: VecDeque::with_capacity(64),
event_mapper: crate::parser::GameEventMapping {
mapping: std::collections::HashMap::new(),
},
}
}
}
impl<'b> LazyEventIterator<'b> {
fn inner_parse_packet(
raw: &crate::csgo_proto::CDemoPacket,
events: &mut VecDeque<DemoEvent>,
event_mapper: &mut GameEventMapping,
) -> Result<(), ()> {
let mut bitreader = crate::bitreader::Bitreader::new(raw.data());
while bitreader.bits_remaining().unwrap_or(0) > 8 {
let msg_type = bitreader.read_u_bit_var().map_err(|e| ())?;
let size = bitreader.read_varint().map_err(|e| ())?;
let msg_bytes = bitreader.read_n_bytes(size as usize).map_err(|e| ())?;
assert_eq!(msg_bytes.len(), size as usize);
let net_msg_type =
match crate::netmessagetypes::NetmessageType::try_from(msg_type as i32) {
Ok(v) => v,
Err(e) => {
dbg!(e);
continue;
}
};
match net_msg_type {
crate::netmessagetypes::NetmessageType::GE_Source1LegacyGameEventList => {
let event_list: crate::csgo_proto::CsvcMsgGameEventList =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
event_mapper.mapping.clear();
for event in event_list.descriptors {
event_mapper
.mapping
.insert(event.eventid(), (event.name().to_owned(), event.keys));
}
}
crate::netmessagetypes::NetmessageType::svc_ServerInfo => {
let raw: crate::csgo_proto::CsvcMsgServerInfo =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
events.push_back(DemoEvent::ServerInfo(Box::new(raw)));
}
crate::netmessagetypes::NetmessageType::net_Tick => {
let raw: crate::csgo_proto::CnetMsgTick =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
events.push_back(DemoEvent::Tick(Box::new(raw)));
}
crate::netmessagetypes::NetmessageType::GE_Source1LegacyGameEvent => {
let raw: crate::csgo_proto::CMsgSource1LegacyGameEvent =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
match event_mapper.mapping.get(&raw.eventid()) {
Some((name, keys)) => {
match crate::game_event::EVENT_PARSERS.get(name) {
Some(parser) => {
let parsed = parser
.parse(keys.as_slice(), raw.clone())
.map_err(|e| ())?;
events.push_back(DemoEvent::GameEvent(Box::new(parsed)));
}
None => {
println!("No parser for {:?}", name);
}
};
}
None => {
println!("Unknown Event - ID: {}", raw.eventid());
}
};
}
crate::netmessagetypes::NetmessageType::CS_UM_ServerRankUpdate => {
let raw: crate::csgo_proto::CcsUsrMsgServerRankUpdate =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
events.push_back(DemoEvent::RankUpdate(Box::new(raw)));
}
crate::netmessagetypes::NetmessageType::CS_UM_ServerRankRevealAll => {
let raw: crate::csgo_proto::CcsUsrMsgServerRankRevealAll =
prost::Message::decode(msg_bytes.as_slice()).map_err(|e| ())?;
events.push_back(DemoEvent::RankReveal(Box::new(raw)));
}
crate::netmessagetypes::NetmessageType::net_SignonState
| crate::netmessagetypes::NetmessageType::svc_ClearAllStringTables
| crate::netmessagetypes::NetmessageType::svc_CreateStringTable
| crate::netmessagetypes::NetmessageType::svc_UpdateStringTable
| crate::netmessagetypes::NetmessageType::net_SetConVar
| crate::netmessagetypes::NetmessageType::svc_ClassInfo
| crate::netmessagetypes::NetmessageType::svc_VoiceInit
| crate::netmessagetypes::NetmessageType::svc_PacketEntities
| crate::netmessagetypes::NetmessageType::svc_UserCmds
| crate::netmessagetypes::NetmessageType::GE_SosStartSoundEvent
| crate::netmessagetypes::NetmessageType::GE_SosStopSoundEvent
| crate::netmessagetypes::NetmessageType::CS_GE_PlayerAnimationEvent
| crate::netmessagetypes::NetmessageType::CS_GE_RadioIconEvent
| crate::netmessagetypes::NetmessageType::CS_GE_FireBullets
| crate::netmessagetypes::NetmessageType::UM_SayText2
| crate::netmessagetypes::NetmessageType::CS_UM_XpUpdate
| crate::netmessagetypes::NetmessageType::CS_UM_WeaponSound
| crate::netmessagetypes::NetmessageType::CS_UM_RadioText
| crate::netmessagetypes::NetmessageType::TE_WorldDecal
| crate::netmessagetypes::NetmessageType::TE_EffectDispatch
| crate::netmessagetypes::NetmessageType::CS_UM_EndOfMatchAllPlayersData
| crate::netmessagetypes::NetmessageType::TE_PhysicsProp
| crate::netmessagetypes::NetmessageType::UM_TextMsg
| crate::netmessagetypes::NetmessageType::CS_UM_VoteFailed
| crate::netmessagetypes::NetmessageType::net_SpawnGroup_Load
| crate::netmessagetypes::NetmessageType::CS_UM_MatchEndConditions
| crate::netmessagetypes::NetmessageType::TE_Explosion => {}
_unknown => {
// dbg!(unknown);
}
};
}
Ok(())
}
}
impl<'b> Iterator for LazyEventIterator<'b> {
type Item = Result<crate::DemoEvent, ()>;
fn next(&mut self) -> Option<Self::Item> {
use crate::DemoCommand;
if let Some(event) = self.pending_events.pop_front() {
return Some(Ok(event));
}
while let Some(frame) = self.frames.next() {
match frame.cmd {
DemoCommand::SignonPacket | DemoCommand::Packet => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoPacket = match prost::Message::decode(data) {
Ok(p) => p,
Err(e) => return Some(Err(())),
};
if let Err(e) = Self::inner_parse_packet(
&raw,
&mut self.pending_events,
&mut self.event_mapper,
) {
return Some(Err(()));
}
}
DemoCommand::FullPacket => {
let data = match frame.decompress_with_buf(&mut self.buffer) {
Ok(d) => d,
Err(e) => return Some(Err(())),
};
let raw: crate::csgo_proto::CDemoFullPacket = match prost::Message::decode(data)
{
Ok(p) => p,
Err(e) => return Some(Err(())),
};
// TODO
if let Some(packet) = raw.packet {
if let Err(e) = Self::inner_parse_packet(
&packet,
&mut self.pending_events,
&mut self.event_mapper,
) {
return Some(Err(()));
}
}
}
_ => {}
};
if let Some(event) = self.pending_events.pop_front() {
return Some(Ok(event));
}
}
None
}
}

View File

@@ -19,6 +19,7 @@ pub mod game_event;
mod values;
pub use values::*;
pub mod lazyparser;
pub mod parser;
pub mod csgo_proto {

View File

@@ -1,6 +1,6 @@
use crate::csgo_proto;
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum DemoEvent {
GameEvent(Box<crate::game_event::GameEvent>),
ServerInfo(Box<csgo_proto::CsvcMsgServerInfo>),

View File

@@ -3,11 +3,11 @@ use crate::{packet::DemoEvent, DemoCommand, Frame, FrameDecompressError, UserId}
mod fieldpath;
pub use fieldpath::{FieldPath, Paths};
mod decoder;
pub(crate) mod decoder;
pub mod entities;
mod propcontroller;
mod sendtables;
mod variant;
pub(crate) mod propcontroller;
pub(crate) mod sendtables;
pub(crate) mod variant;
pub use entities::EntityFilter;
pub use variant::Variant;
@@ -95,8 +95,8 @@ pub struct FirstPassOutput {
}
#[derive(Debug)]
struct GameEventMapping {
mapping: std::collections::HashMap<
pub(crate) struct GameEventMapping {
pub mapping: std::collections::HashMap<
i32,
(
String,
@@ -107,8 +107,8 @@ struct GameEventMapping {
#[derive(Debug)]
pub struct Class {
name: std::sync::Arc<str>,
serializer: sendtables::Serializer,
pub(crate) name: std::sync::Arc<str>,
pub(crate) serializer: sendtables::Serializer,
}
pub fn parse<'b, FI>(frames: FI, filter: EntityFilter) -> Result<FirstPassOutput, FirstPassError>
@@ -248,7 +248,7 @@ where
}
}
}
_other => {
_ => {
// dbg!(other);
}
}
@@ -501,7 +501,7 @@ fn inner_parse_packet(
Ok(())
}
fn update_entity(
pub(crate) fn update_entity(
entity_id: i32,
bitreader: &mut crate::bitreader::Bitreader,
entity_ctx: &mut entities::EntityContext,

View File

@@ -8,7 +8,7 @@ pub struct EntityContext {
pub filter: EntityFilter,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct EntityState {
pub id: i32,
pub class: Arc<str>,
@@ -16,7 +16,7 @@ pub struct EntityState {
pub props: Vec<EntityProp>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct EntityProp {
pub field_info: super::sendtables::FieldInfo,
pub prop_info: super::propcontroller::PropInfo,

View File

@@ -89,7 +89,7 @@ pub struct PropController {
#[derive(Debug, Clone)]
pub struct SpecialIDs {}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct PropInfo {
pub id: u32,
// pub prop_type: PropType,

View File

@@ -6,7 +6,7 @@ pub struct Serializer {
pub fields: Vec<Field>,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FieldInfo {
pub decoder: decoder::Decoder,
pub should_parse: bool,

View File

@@ -1,4 +1,4 @@
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum RawValue {
String(String),
F32(f32),

51
tests/lazy.rs Normal file
View File

@@ -0,0 +1,51 @@
#[test]
fn cmp_lazy_nonlazy_events() {
let content = std::fs::read("testfiles/mirage.dem").unwrap();
let container = csdemo::Container::parse(&content).unwrap();
let demo = csdemo::parser::parse(
csdemo::FrameIterator::parse(container.inner),
csdemo::parser::EntityFilter::disabled(),
)
.unwrap();
let lazy_demo =
csdemo::lazyparser::LazyParser::new(csdemo::Container::parse(&content).unwrap());
for (normal, lazied) in demo
.events
.into_iter()
.zip(lazy_demo.events().filter_map(|e| e.ok()))
{
assert_eq!(normal, lazied);
}
}
#[test]
fn cmp_lazy_nonlazy_entities() {
let content = std::fs::read("testfiles/mirage.dem").unwrap();
let container = csdemo::Container::parse(&content).unwrap();
let demo = csdemo::parser::parse(
csdemo::FrameIterator::parse(container.inner),
csdemo::parser::EntityFilter::all(),
)
.unwrap();
let lazy_demo =
csdemo::lazyparser::LazyParser::new(csdemo::Container::parse(&content).unwrap());
let mut normal_iter = demo
.entity_states
.ticks
.into_iter()
.flat_map(|t| t.states.into_iter().map(move |s| (t.tick, s)));
let mut lazy_iter = lazy_demo.entities().filter_map(|e| e.ok());
while let Some(normal) = normal_iter.next() {
let lazy = lazy_iter.next().unwrap();
assert_eq!(normal, lazy);
}
assert_eq!(None, lazy_iter.next());
}