From 0d3b64e75a4a5b3734916c3141a70436c8bda891 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 18:19:19 -0800 Subject: [PATCH 01/31] v0 serializer --- Cargo.toml | 2 +- src/v0.rs | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6299713..ea48728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "strafesnet_roblox_bot_file" version = "0.7.0" -edition = "2021" +edition = "2024" [dependencies] binrw = "0.15.0" diff --git a/src/v0.rs b/src/v0.rs index e5a5507..5398df5 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,7 +1,8 @@ use std::io::{SeekFrom,Error as IoError}; +use std::ops::Range; use binrw::binrw; use binrw::io::{TakeSeek,TakeSeekExt}; -use binrw::BinReaderExt; +use binrw::{BinReaderExt,BinWriterExt}; use crate::BinrwError; // the bit chunks are deposited in reverse @@ -449,6 +450,7 @@ pub struct Block{ #[binrw] #[brw(little)] +#[derive(Clone,Copy)] enum EventType{ #[brw(magic=1u32)] Input, @@ -467,6 +469,22 @@ enum EventType{ #[brw(magic=8u32)] Setting, } +impl EventType{ + // internal function meant for array indexing + fn from_usize(value:usize)->Self{ + match value{ + 0=>Self::Input, + 1=>Self::Output, + 2=>Self::Sound, + 3=>Self::World, + 4=>Self::Gravity, + 5=>Self::Run, + 6=>Self::Camera, + 7=>Self::Setting, + _=>panic!(), + } + } +} #[binrw] #[brw(little)] struct EventChunkHeader{ @@ -698,3 +716,203 @@ pub fn read_all_to_block(mut data:R)->Result{ block.extend_from_block_id_iter(&mut data,&block_timelines,block_timelines.realtime_blocks())?; Ok(block) } + +#[cfg(feature="itertools")] +pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::Error>{ + const MAX_BLOCK_SIZE:usize=1<<14; + const FILE_VERSION:u32=0; + const EVENT_SIZE:[usize;8]=[ + 8+4+2*4, // Input + 8+4+4*3*4, // Output + 8+4+4, // Sound + 8+4+12, // World + 8+3*4, // Gravity + 8+4+4+4, // Run + 8+4+3*4, // Camera + 8+4+8, // Setting + ]; + #[derive(Clone,Default)] + struct Plan([T;8]); + impl Plan{ + fn accumulate(&mut self,event_type:EventType){ + self.0[event_type as usize]+=1; + } + fn range(&self,end:&Plan)->Plan>{ + Plan(core::array::from_fn(|i|self.0[i]..end.0[i])) + } + } + impl Plan>{ + fn size(&self)->usize{ + self.0 + .iter() + .enumerate() + .map(|(i,range)| + range.len()*EVENT_SIZE[i] + ) + .sum() + } + } + macro_rules! collect_event{ + ($best:ident,$list:expr,$plan:ident,$event_type:expr)=>{ + if let Some(event)=$list.get($plan.0[$event_type as usize]) + &&$best.map_or(true,|(time,_)|event.time)->Option<(f64,EventType)>{ + let mut next_event=None; + collect_event!(next_event,block.world_events,plan,EventType::World); + collect_event!(next_event,block.gravity_events,plan,EventType::Gravity); + collect_event!(next_event,block.run_events,plan,EventType::Run); + collect_event!(next_event,block.camera_events,plan,EventType::Camera); + collect_event!(next_event,block.setting_events,plan,EventType::Setting); + next_event + } + fn next_realtime_event(block:&Block,plan:&Plan)->Option<(f64,EventType)>{ + let mut next_event=None; + collect_event!(next_event,block.input_events,plan,EventType::Input); + collect_event!(next_event,block.output_events,plan,EventType::Output); + collect_event!(next_event,block.sound_events,plan,EventType::Sound); + next_event + } + fn plan_block(block:&Block,plan:&mut Plan,next_event:impl Fn(&Block,&Plan)->Option<(f64,EventType)>)->Option{ + let mut size=0; + if let Some((start_time,first_event))=next_event(block,plan){ + size+=EVENT_SIZE[first_event as usize]; + if MAX_BLOCK_SIZE{ + block_positions.push(BlockPosition(position)); + position+=$plan.size() as u32; + + offline_blocks_timeline.push(Timed{ + time:$time, + event:BlockId(block_id), + }); + block_id+=1; + + chrono_plan.push($plan); + } + } + macro_rules! push_realtime{ + ($time:expr,$plan:expr)=>{ + block_positions.push(BlockPosition(position)); + position+=$plan.size() as u32; + + realtime_blocks_timeline.push(Timed{ + time:$time, + event:BlockId(block_id), + }); + block_id+=1; + + chrono_plan.push($plan); + } + } + // push three important blocks + if let Some((time,plan))=plan_offline.pop_front(){ + push_offline!(time,plan); + } + if let Some((time,plan))=plan_realtime.pop_front(){ + push_realtime!(time,plan); + } + if let Some((time,plan))=plan_realtime.pop_back(){ + push_realtime!(time,plan); + } + // push the remaining blocks in chronological order + for block_plan in itertools::merge_join_by( + plan_offline, + plan_realtime, + |&(offline,_),&(realtime,_)|offline<=realtime, + ){ + match block_plan{ + itertools::Either::Left((time,plan))=>{push_offline!(time,plan);}, + itertools::Either::Right((time,plan))=>{push_realtime!(time,plan);}, + } + } + // final position + block_positions.push(BlockPosition(position)); + + let block_timelines=BlockTimelines{ + block_positions, + offline_blocks_timeline, + realtime_blocks_timeline, + }; + + file_header.write_le(writer)?; + block_timelines.write_le(writer)?; + for plan in chrono_plan{ + for (event_type_id,range) in plan.0.iter().enumerate(){ + let event_type=EventType::from_usize(event_type_id); + let event_chunk_header=EventChunkHeader{ + event_type, + num_events:range.len() as u32, + }; + event_chunk_header.write_le(writer)?; + match event_type{ + EventType::Input=>block.input_events.write_le(writer)?, + EventType::Output=>block.output_events.write_le(writer)?, + EventType::Sound=>block.sound_events.write_le(writer)?, + EventType::World=>block.world_events.write_le(writer)?, + EventType::Gravity=>block.gravity_events.write_le(writer)?, + EventType::Run=>block.run_events.write_le(writer)?, + EventType::Camera=>block.camera_events.write_le(writer)?, + EventType::Setting=>block.setting_events.write_le(writer)?, + } + } + } + + Ok(()) +} -- 2.49.1 From fd9b6a0d70c05fed82c7c2c9b8a12994c822204f Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 18:55:04 -0800 Subject: [PATCH 02/31] rename variable --- src/v0.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 5398df5..4d6c99c 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -872,12 +872,12 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E push_realtime!(time,plan); } // push the remaining blocks in chronological order - for block_plan in itertools::merge_join_by( + for either_plan in itertools::merge_join_by( plan_offline, plan_realtime, |&(offline,_),&(realtime,_)|offline<=realtime, ){ - match block_plan{ + match either_plan{ itertools::Either::Left((time,plan))=>{push_offline!(time,plan);}, itertools::Either::Right((time,plan))=>{push_realtime!(time,plan);}, } -- 2.49.1 From 73a497909f73a4e3c41cf7b2dab8d04f3d003334 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 18:37:20 -0800 Subject: [PATCH 03/31] test serializer --- src/tests.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index cdbd2da..87cce07 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -27,4 +27,19 @@ fn deserialize_all()->Result<(),v0::Error>{ Ok(()) } -// TODO: file serialization test +#[test] +#[cfg(feature="itertools")] +fn serialize_round_trip()->Result<(),binrw::Error>{ + use crate::v0::serialize; + + let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); + let block=read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap(); + + let mut data=Vec::with_capacity(file.len()); + serialize(&block,&mut std::io::Cursor::new(&mut data))?; + + assert_eq!(data.len(),file.len()); + assert_eq!(data,file); + + Ok(()) +} -- 2.49.1 From e9abefccde11015b3eef76adb2a689cf63e38e62 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 18:57:27 -0800 Subject: [PATCH 04/31] fix macro --- src/v0.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 4d6c99c..e52a3e0 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -757,7 +757,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E if let Some(event)=$list.get($plan.0[$event_type as usize]) &&$best.map_or(true,|(time,_)|event.time Date: Thu, 6 Nov 2025 18:58:04 -0800 Subject: [PATCH 05/31] fix event range serialization --- src/v0.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index e52a3e0..f9c6c50 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -894,7 +894,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E file_header.write_le(writer)?; block_timelines.write_le(writer)?; for plan in chrono_plan{ - for (event_type_id,range) in plan.0.iter().enumerate(){ + for (event_type_id,range) in plan.0.into_iter().enumerate(){ let event_type=EventType::from_usize(event_type_id); let event_chunk_header=EventChunkHeader{ event_type, @@ -902,14 +902,14 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E }; event_chunk_header.write_le(writer)?; match event_type{ - EventType::Input=>block.input_events.write_le(writer)?, - EventType::Output=>block.output_events.write_le(writer)?, - EventType::Sound=>block.sound_events.write_le(writer)?, - EventType::World=>block.world_events.write_le(writer)?, - EventType::Gravity=>block.gravity_events.write_le(writer)?, - EventType::Run=>block.run_events.write_le(writer)?, - EventType::Camera=>block.camera_events.write_le(writer)?, - EventType::Setting=>block.setting_events.write_le(writer)?, + EventType::Input=>block.input_events[range].write_le(writer)?, + EventType::Output=>block.output_events[range].write_le(writer)?, + EventType::Sound=>block.sound_events[range].write_le(writer)?, + EventType::World=>block.world_events[range].write_le(writer)?, + EventType::Gravity=>block.gravity_events[range].write_le(writer)?, + EventType::Run=>block.run_events[range].write_le(writer)?, + EventType::Camera=>block.camera_events[range].write_le(writer)?, + EventType::Setting=>block.setting_events[range].write_le(writer)?, } } } -- 2.49.1 From 088bcba37512977ffcbfaaeeddf1e8f798de76b4 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 18:52:43 -0800 Subject: [PATCH 06/31] fix VecDeque mistake --- src/v0.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index f9c6c50..b993ea4 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -805,7 +805,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E let mut plan=Plan::default(); let mut last_plan=plan.clone(); while let Some(time)=plan_block(block,&mut plan,next_offline_event){ - plan_offline.push_front((time,last_plan.range(&plan))); + plan_offline.push_back((time,last_plan.range(&plan))); last_plan=plan.clone(); } @@ -813,7 +813,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E let mut plan=Plan::default(); let mut last_plan=plan.clone(); while let Some(time)=plan_block(block,&mut plan,next_realtime_event){ - plan_realtime.push_front((time,last_plan.range(&plan))); + plan_realtime.push_back((time,last_plan.range(&plan))); last_plan=plan.clone(); } -- 2.49.1 From ff1d0c851e5258f8bbb7b6a648da4330ddfb8c7d Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 19:14:53 -0800 Subject: [PATCH 07/31] fix float serialization --- src/v0.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/v0.rs b/src/v0.rs index b993ea4..133b337 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -12,20 +12,36 @@ fn read_trey_float(bits:u32)->f32{ let m=(bits>>(1+8))&((1<<23)-1); f32::from_bits(m|(e<<23)|(s<<31)) } +fn write_trey_float(value:&f32)->u32{ + let bits=value.to_bits(); + let s=(bits>>31)&1; + let e=(bits>>23)&((1<<8)-1); + let m=bits&((1<<23)-1); + m<<(1+8)|(e<<1)|s +} fn read_trey_double(bits:u64)->f64{ let s=bits&1; let e=(bits>>1)&((1<<11)-1); let m=(bits>>(1+11))&((1<<52)-1); f64::from_bits(m|(e<<52)|(s<<63)) } +fn write_trey_double(value:&f64)->u64{ + let bits=value.to_bits(); + let s=(bits>>63)&1; + let e=(bits>>52)&((1<<11)-1); + let m=bits&((1<<52)-1); + m<<(1+11)|(e<<1)|s +} #[binrw] #[brw(little)] #[derive(Debug,Clone)] pub struct Vector2{ #[br(map=read_trey_float)] + #[bw(map=write_trey_float)] pub x:f32, #[br(map=read_trey_float)] + #[bw(map=write_trey_float)] pub y:f32, } #[binrw] @@ -33,10 +49,13 @@ pub struct Vector2{ #[derive(Debug,Clone)] pub struct Vector3{ #[br(map=read_trey_float)] + #[bw(map=write_trey_float)] pub x:f32, #[br(map=read_trey_float)] + #[bw(map=write_trey_float)] pub y:f32, #[br(map=read_trey_float)] + #[bw(map=write_trey_float)] pub z:f32, } @@ -86,6 +105,7 @@ pub struct Timed E:for<'a>binrw::BinWrite=()>, { #[br(map=read_trey_double)] + #[bw(map=write_trey_double)] pub time:f64, pub event:E, } @@ -215,6 +235,7 @@ pub struct WorldEventButton{ #[derive(Debug,Clone)] pub struct WorldEventSetTime{ #[br(map=read_trey_double)] + #[bw(map=write_trey_double)] pub time:f64, #[br(temp)] #[bw(ignore)] @@ -431,6 +452,7 @@ pub enum SettingType{ pub struct SettingEvent{ pub setting_type:SettingType, #[br(map=read_trey_double)] + #[bw(map=write_trey_double)] pub value:f64, } -- 2.49.1 From 584e6087e5df7b21b9813cf1960f0ade16ba5d99 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 19:15:00 -0800 Subject: [PATCH 08/31] change variable name --- src/v0.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 133b337..220d281 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -847,7 +847,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E num_realtime_blocks:plan_realtime.len() as u32, }; - let mut chrono_plan=Vec::with_capacity(plan_offline.len()+plan_realtime.len()); + let mut plan_order=Vec::with_capacity(plan_offline.len()+plan_realtime.len()); let mut block_positions=Vec::with_capacity(file_header.block_position_count() as usize); let mut offline_blocks_timeline=Vec::with_capacity(plan_offline.len()); let mut realtime_blocks_timeline=Vec::with_capacity(plan_realtime.len()); @@ -866,7 +866,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E }); block_id+=1; - chrono_plan.push($plan); + plan_order.push($plan); } } macro_rules! push_realtime{ @@ -880,7 +880,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E }); block_id+=1; - chrono_plan.push($plan); + plan_order.push($plan); } } // push three important blocks @@ -915,7 +915,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E file_header.write_le(writer)?; block_timelines.write_le(writer)?; - for plan in chrono_plan{ + for plan in plan_order{ for (event_type_id,range) in plan.0.into_iter().enumerate(){ let event_type=EventType::from_usize(event_type_id); let event_chunk_header=EventChunkHeader{ -- 2.49.1 From 6fc827a6ea4e58d05d5f1b58caf688311b9510c0 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Thu, 6 Nov 2025 19:21:16 -0800 Subject: [PATCH 09/31] fix BlockId & BlockPosition write --- src/v0.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 220d281..8ff9e18 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -604,11 +604,19 @@ impl std::error::Error for Error{} #[binrw] #[brw(little)] #[derive(Debug,Clone,Copy)] -pub struct BlockId(#[br(map=|i:u32|i-1)]u32); +pub struct BlockId( + #[br(map=|i:u32|i-1)] + #[bw(map=|&i:&u32|i+1)] + u32 +); #[binrw] #[brw(little)] #[derive(Debug,Clone)] -struct BlockPosition(#[br(map=|i:u32|i-1)]u32); +struct BlockPosition( + #[br(map=|i:u32|i-1)] + #[bw(map=|&i:&u32|i+1)] + u32 +); #[derive(Debug)] pub struct InvalidBlockId(pub BlockId); -- 2.49.1 From bae07301002ee46687ceeb2892508b18774d98b3 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 16:56:03 -0800 Subject: [PATCH 10/31] fix timelines --- src/v0.rs | 71 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 8ff9e18..96589e2 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -831,19 +831,36 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E // plan events into segments without spilling over max size threshold // each index describes the non-inclusive end index of the range of events included in the block. + struct PlannedBlock{ + // index is not the same as BlockId. + // It is list-local for both plan_offline and plan_realtime. + index:usize, + time:f64, + plan:Plan>, + } let mut plan_offline=std::collections::VecDeque::new(); let mut plan=Plan::default(); let mut last_plan=plan.clone(); + let mut index=0; while let Some(time)=plan_block(block,&mut plan,next_offline_event){ - plan_offline.push_back((time,last_plan.range(&plan))); + plan_offline.push_back(PlannedBlock{ + index, + time, + plan:last_plan.range(&plan), + }); last_plan=plan.clone(); + index+=1; } let mut plan_realtime=std::collections::VecDeque::new(); let mut plan=Plan::default(); let mut last_plan=plan.clone(); while let Some(time)=plan_block(block,&mut plan,next_realtime_event){ - plan_realtime.push_back((time,last_plan.range(&plan))); + plan_realtime.push_back(PlannedBlock{ + index, + time, + plan:last_plan.range(&plan), + }); last_plan=plan.clone(); } @@ -857,59 +874,63 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E let mut plan_order=Vec::with_capacity(plan_offline.len()+plan_realtime.len()); let mut block_positions=Vec::with_capacity(file_header.block_position_count() as usize); - let mut offline_blocks_timeline=Vec::with_capacity(plan_offline.len()); - let mut realtime_blocks_timeline=Vec::with_capacity(plan_realtime.len()); + // Fill the timelines with dummy values, we don't know the block ids yet. + // This can be done with Vec::spare_capacity_mut and unsafe, but whatever. + const DUMMY_BLOCK:Timed=Timed{time:0.0,event:BlockId(0)}; + let mut offline_blocks_timeline=vec![DUMMY_BLOCK;plan_offline.len()]; + let mut realtime_blocks_timeline=vec![DUMMY_BLOCK;plan_realtime.len()]; // position starts after the *predicted* end of the BlockTimelines let mut position=file_header.block_timelines_info().end; let mut block_id=0; macro_rules! push_offline{ - ($time:expr,$plan:expr)=>{ + ($planned:expr)=>{ block_positions.push(BlockPosition(position)); - position+=$plan.size() as u32; + position+=$planned.plan.size() as u32; - offline_blocks_timeline.push(Timed{ - time:$time, + // write the block id to the correct index + offline_blocks_timeline[$planned.index]=Timed{ + time:$planned.time, event:BlockId(block_id), - }); + }; block_id+=1; - plan_order.push($plan); + plan_order.push($planned.plan); } } macro_rules! push_realtime{ - ($time:expr,$plan:expr)=>{ + ($planned:expr)=>{ block_positions.push(BlockPosition(position)); - position+=$plan.size() as u32; + position+=$planned.plan.size() as u32; - realtime_blocks_timeline.push(Timed{ - time:$time, + realtime_blocks_timeline[$planned.index]=Timed{ + time:$planned.time, event:BlockId(block_id), - }); + }; block_id+=1; - plan_order.push($plan); + plan_order.push($planned.plan); } } // push three important blocks - if let Some((time,plan))=plan_offline.pop_front(){ - push_offline!(time,plan); + if let Some(plan)=plan_offline.pop_front(){ + push_offline!(plan); } - if let Some((time,plan))=plan_realtime.pop_front(){ - push_realtime!(time,plan); + if let Some(plan)=plan_realtime.pop_front(){ + push_realtime!(plan); } - if let Some((time,plan))=plan_realtime.pop_back(){ - push_realtime!(time,plan); + if let Some(plan)=plan_realtime.pop_back(){ + push_realtime!(plan); } // push the remaining blocks in chronological order for either_plan in itertools::merge_join_by( plan_offline, plan_realtime, - |&(offline,_),&(realtime,_)|offline<=realtime, + |offline,realtime|offline.time<=realtime.time, ){ match either_plan{ - itertools::Either::Left((time,plan))=>{push_offline!(time,plan);}, - itertools::Either::Right((time,plan))=>{push_realtime!(time,plan);}, + itertools::Either::Left(offline)=>{push_offline!(offline);}, + itertools::Either::Right(realtime)=>{push_realtime!(realtime);}, } } // final position -- 2.49.1 From c9a3b8ddde68e89cb45c8ff20bb1288625fdf0c5 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 16:57:24 -0800 Subject: [PATCH 11/31] scary shadowing --- src/v0.rs | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 96589e2..9cdb1d1 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -839,29 +839,34 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E plan:Plan>, } let mut plan_offline=std::collections::VecDeque::new(); - let mut plan=Plan::default(); - let mut last_plan=plan.clone(); - let mut index=0; - while let Some(time)=plan_block(block,&mut plan,next_offline_event){ - plan_offline.push_back(PlannedBlock{ - index, - time, - plan:last_plan.range(&plan), - }); - last_plan=plan.clone(); - index+=1; + { + let mut plan=Plan::default(); + let mut last_plan=plan.clone(); + let mut index=0; + while let Some(time)=plan_block(block,&mut plan,next_offline_event){ + plan_offline.push_back(PlannedBlock{ + index, + time, + plan:last_plan.range(&plan), + }); + last_plan=plan.clone(); + index+=1; + } } - let mut plan_realtime=std::collections::VecDeque::new(); - let mut plan=Plan::default(); - let mut last_plan=plan.clone(); - while let Some(time)=plan_block(block,&mut plan,next_realtime_event){ - plan_realtime.push_back(PlannedBlock{ - index, - time, - plan:last_plan.range(&plan), - }); - last_plan=plan.clone(); + { + let mut plan=Plan::default(); + let mut last_plan=plan.clone(); + let mut index=0; + while let Some(time)=plan_block(block,&mut plan,next_realtime_event){ + plan_realtime.push_back(PlannedBlock{ + index, + time, + plan:last_plan.range(&plan), + }); + last_plan=plan.clone(); + index+=1; + } } use binrw::BinWrite; -- 2.49.1 From c2a0990a89386ae783704b1e02020e879bdede5c Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 17:20:05 -0800 Subject: [PATCH 12/31] simplify timeline construction --- src/v0.rs | 48 ++++++++++++++++-------------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 9cdb1d1..cc8b9f6 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -888,44 +888,28 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E // position starts after the *predicted* end of the BlockTimelines let mut position=file_header.block_timelines_info().end; let mut block_id=0; - macro_rules! push_offline{ - ($planned:expr)=>{ - block_positions.push(BlockPosition(position)); - position+=$planned.plan.size() as u32; + let mut push_block=|timeline:&mut Vec>,planned:PlannedBlock|{ + block_positions.push(BlockPosition(position)); + position+=planned.plan.size() as u32; - // write the block id to the correct index - offline_blocks_timeline[$planned.index]=Timed{ - time:$planned.time, - event:BlockId(block_id), - }; - block_id+=1; + // write the block id to the correct index + timeline[planned.index]=Timed{ + time:planned.time, + event:BlockId(block_id), + }; + block_id+=1; - plan_order.push($planned.plan); - } - } - macro_rules! push_realtime{ - ($planned:expr)=>{ - block_positions.push(BlockPosition(position)); - position+=$planned.plan.size() as u32; - - realtime_blocks_timeline[$planned.index]=Timed{ - time:$planned.time, - event:BlockId(block_id), - }; - block_id+=1; - - plan_order.push($planned.plan); - } - } + plan_order.push(planned.plan); + }; // push three important blocks if let Some(plan)=plan_offline.pop_front(){ - push_offline!(plan); + push_block(&mut offline_blocks_timeline,plan); } if let Some(plan)=plan_realtime.pop_front(){ - push_realtime!(plan); + push_block(&mut realtime_blocks_timeline,plan); } if let Some(plan)=plan_realtime.pop_back(){ - push_realtime!(plan); + push_block(&mut realtime_blocks_timeline,plan); } // push the remaining blocks in chronological order for either_plan in itertools::merge_join_by( @@ -934,8 +918,8 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E |offline,realtime|offline.time<=realtime.time, ){ match either_plan{ - itertools::Either::Left(offline)=>{push_offline!(offline);}, - itertools::Either::Right(realtime)=>{push_realtime!(realtime);}, + itertools::Either::Left(offline)=>push_block(&mut offline_blocks_timeline,offline), + itertools::Either::Right(realtime)=>push_block(&mut realtime_blocks_timeline,realtime), } } // final position -- 2.49.1 From f0b085be48796605e13a98b672d3a06300e8b316 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 17:28:27 -0800 Subject: [PATCH 13/31] remove macro --- src/v0.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index cc8b9f6..ff39fd9 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -782,29 +782,37 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E .sum() } } - macro_rules! collect_event{ - ($best:ident,$list:expr,$plan:ident,$event_type:expr)=>{ - if let Some(event)=$list.get($plan.0[$event_type as usize]) - &&$best.map_or(true,|(time,_)|event.time( + best:&mut Option<(f64,EventType)>, + list:&[Timed], + plan:&Plan, + event_type:EventType, + ) + where + E:for<'a>binrw::BinRead=()>, + E:for<'a>binrw::BinWrite=()>, + { + if let Some(event)=list.get(plan.0[event_type as usize]) + &&best.map_or(true,|(time,_)|event.time)->Option<(f64,EventType)>{ let mut next_event=None; - collect_event!(next_event,block.world_events,plan,EventType::World); - collect_event!(next_event,block.gravity_events,plan,EventType::Gravity); - collect_event!(next_event,block.run_events,plan,EventType::Run); - collect_event!(next_event,block.camera_events,plan,EventType::Camera); - collect_event!(next_event,block.setting_events,plan,EventType::Setting); + collect_event(&mut next_event,&block.world_events,plan,EventType::World); + collect_event(&mut next_event,&block.gravity_events,plan,EventType::Gravity); + collect_event(&mut next_event,&block.run_events,plan,EventType::Run); + collect_event(&mut next_event,&block.camera_events,plan,EventType::Camera); + collect_event(&mut next_event,&block.setting_events,plan,EventType::Setting); next_event } fn next_realtime_event(block:&Block,plan:&Plan)->Option<(f64,EventType)>{ let mut next_event=None; - collect_event!(next_event,block.input_events,plan,EventType::Input); - collect_event!(next_event,block.output_events,plan,EventType::Output); - collect_event!(next_event,block.sound_events,plan,EventType::Sound); + collect_event(&mut next_event,&block.input_events,plan,EventType::Input); + collect_event(&mut next_event,&block.output_events,plan,EventType::Output); + collect_event(&mut next_event,&block.sound_events,plan,EventType::Sound); next_event } fn plan_block(block:&Block,plan:&mut Plan,next_event:impl Fn(&Block,&Plan)->Option<(f64,EventType)>)->Option{ -- 2.49.1 From bbb3eedb3dfcdfa63a05dc91a1309902062ff330 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 17:49:19 -0800 Subject: [PATCH 14/31] comments --- src/v0.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index ff39fd9..997158e 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -763,6 +763,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E ]; #[derive(Clone,Default)] struct Plan([T;8]); + // A plan of how many events of each type to include in a data block. impl Plan{ fn accumulate(&mut self,event_type:EventType){ self.0[event_type as usize]+=1; @@ -771,6 +772,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E Plan(core::array::from_fn(|i|self.0[i]..end.0[i])) } } + // A plan of what range of events to include in a data block. impl Plan>{ fn size(&self)->usize{ self.0 @@ -909,13 +911,18 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E plan_order.push(planned.plan); }; - // push three important blocks + // the first block in the file is an offline block to + // initialize the state of things like the current style if let Some(plan)=plan_offline.pop_front(){ push_block(&mut offline_blocks_timeline,plan); } + // the second block is the first realtime block which + // includes the starting position of the replay if let Some(plan)=plan_realtime.pop_front(){ push_block(&mut realtime_blocks_timeline,plan); } + // the third block is the last realtime block which + // is used by the game client to determine the duration if let Some(plan)=plan_realtime.pop_back(){ push_block(&mut realtime_blocks_timeline,plan); } -- 2.49.1 From b5a5a5d6ac199d841fa20b2607202fa8ac47dba3 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 17:49:30 -0800 Subject: [PATCH 15/31] skip empty event chunks --- src/v0.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 997158e..89ff3e2 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -950,10 +950,14 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E block_timelines.write_le(writer)?; for plan in plan_order{ for (event_type_id,range) in plan.0.into_iter().enumerate(){ + let num_events=range.len(); + if num_events==0{ + continue; + } let event_type=EventType::from_usize(event_type_id); let event_chunk_header=EventChunkHeader{ event_type, - num_events:range.len() as u32, + num_events:num_events as u32, }; event_chunk_header.write_le(writer)?; match event_type{ -- 2.49.1 From f36a4b3c22e5d7f0ab440c729961af0f0bc74ac4 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Tue, 9 Dec 2025 18:23:39 -0800 Subject: [PATCH 16/31] move and tweak comment --- src/v0.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 89ff3e2..cc2e5ec 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -839,8 +839,6 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E None } - // plan events into segments without spilling over max size threshold - // each index describes the non-inclusive end index of the range of events included in the block. struct PlannedBlock{ // index is not the same as BlockId. // It is list-local for both plan_offline and plan_realtime. @@ -848,6 +846,8 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E time:f64, plan:Plan>, } + // plan events into segments without spilling over max size threshold + // each plan describes the range of events included in the block. let mut plan_offline=std::collections::VecDeque::new(); { let mut plan=Plan::default(); -- 2.49.1 From b86248250c94f859664043932c3e29014b33be7d Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Wed, 10 Dec 2025 14:28:13 -0800 Subject: [PATCH 17/31] tab group --- src/v0.rs | 82 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index cc2e5ec..6917d72 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -895,50 +895,52 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::E let mut offline_blocks_timeline=vec![DUMMY_BLOCK;plan_offline.len()]; let mut realtime_blocks_timeline=vec![DUMMY_BLOCK;plan_realtime.len()]; - // position starts after the *predicted* end of the BlockTimelines - let mut position=file_header.block_timelines_info().end; - let mut block_id=0; - let mut push_block=|timeline:&mut Vec>,planned:PlannedBlock|{ - block_positions.push(BlockPosition(position)); - position+=planned.plan.size() as u32; + { + // position starts after the *predicted* end of the BlockTimelines + let mut position=file_header.block_timelines_info().end; + let mut block_id=0; + let mut push_block=|timeline:&mut Vec>,planned:PlannedBlock|{ + block_positions.push(BlockPosition(position)); + position+=planned.plan.size() as u32; - // write the block id to the correct index - timeline[planned.index]=Timed{ - time:planned.time, - event:BlockId(block_id), + // write the block id to the correct index + timeline[planned.index]=Timed{ + time:planned.time, + event:BlockId(block_id), + }; + block_id+=1; + + plan_order.push(planned.plan); }; - block_id+=1; - - plan_order.push(planned.plan); - }; - // the first block in the file is an offline block to - // initialize the state of things like the current style - if let Some(plan)=plan_offline.pop_front(){ - push_block(&mut offline_blocks_timeline,plan); - } - // the second block is the first realtime block which - // includes the starting position of the replay - if let Some(plan)=plan_realtime.pop_front(){ - push_block(&mut realtime_blocks_timeline,plan); - } - // the third block is the last realtime block which - // is used by the game client to determine the duration - if let Some(plan)=plan_realtime.pop_back(){ - push_block(&mut realtime_blocks_timeline,plan); - } - // push the remaining blocks in chronological order - for either_plan in itertools::merge_join_by( - plan_offline, - plan_realtime, - |offline,realtime|offline.time<=realtime.time, - ){ - match either_plan{ - itertools::Either::Left(offline)=>push_block(&mut offline_blocks_timeline,offline), - itertools::Either::Right(realtime)=>push_block(&mut realtime_blocks_timeline,realtime), + // the first block in the file is an offline block to + // initialize the state of things like the current style + if let Some(plan)=plan_offline.pop_front(){ + push_block(&mut offline_blocks_timeline,plan); } + // the second block is the first realtime block which + // includes the starting position of the replay + if let Some(plan)=plan_realtime.pop_front(){ + push_block(&mut realtime_blocks_timeline,plan); + } + // the third block is the last realtime block which + // is used by the game client to determine the duration + if let Some(plan)=plan_realtime.pop_back(){ + push_block(&mut realtime_blocks_timeline,plan); + } + // push the remaining blocks in chronological order + for either_plan in itertools::merge_join_by( + plan_offline, + plan_realtime, + |offline,realtime|offline.time<=realtime.time, + ){ + match either_plan{ + itertools::Either::Left(offline)=>push_block(&mut offline_blocks_timeline,offline), + itertools::Either::Right(realtime)=>push_block(&mut realtime_blocks_timeline,realtime), + } + } + // final position + block_positions.push(BlockPosition(position)); } - // final position - block_positions.push(BlockPosition(position)); let block_timelines=BlockTimelines{ block_positions, -- 2.49.1 From 8c03073a35a0940427a154b433ebd312a9d69700 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sat, 13 Dec 2025 13:13:55 -0800 Subject: [PATCH 18/31] fix tests without itertools --- src/tests.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 87cce07..455c822 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -30,13 +30,11 @@ fn deserialize_all()->Result<(),v0::Error>{ #[test] #[cfg(feature="itertools")] fn serialize_round_trip()->Result<(),binrw::Error>{ - use crate::v0::serialize; - let file=std::fs::read("files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap(); - let block=read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap(); + let block=v0::read_all_to_block(std::io::Cursor::new(file.as_slice())).unwrap(); let mut data=Vec::with_capacity(file.len()); - serialize(&block,&mut std::io::Cursor::new(&mut data))?; + v0::serialize(&block,&mut std::io::Cursor::new(&mut data))?; assert_eq!(data.len(),file.len()); assert_eq!(data,file); -- 2.49.1 From 77ce2f50b2761245046f2cc4c500ffe0a4c4bce4 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sat, 13 Dec 2025 13:16:59 -0800 Subject: [PATCH 19/31] fix lints without itertools --- src/v0.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 6917d72..27ed7dc 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,8 +1,7 @@ use std::io::{SeekFrom,Error as IoError}; -use std::ops::Range; use binrw::binrw; use binrw::io::{TakeSeek,TakeSeekExt}; -use binrw::{BinReaderExt,BinWriterExt}; +use binrw::BinReaderExt; use crate::BinrwError; // the bit chunks are deposited in reverse @@ -748,7 +747,8 @@ pub fn read_all_to_block(mut data:R)->Result{ } #[cfg(feature="itertools")] -pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::Error>{ +pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::Error>{ + use std::ops::Range; const MAX_BLOCK_SIZE:usize=1<<14; const FILE_VERSION:u32=0; const EVENT_SIZE:[usize;8]=[ -- 2.49.1 From d9d3406817570668d35e2099c256e7f49c60d06e Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:06:56 -0800 Subject: [PATCH 20/31] fix chunk header size prediction --- src/v0.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 27ed7dc..6cf7ae4 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -778,9 +778,10 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b self.0 .iter() .enumerate() - .map(|(i,range)| - range.len()*EVENT_SIZE[i] - ) + .filter_map(|(i,range)|match range.len(){ + 0=>None, + other=>Some(other*EVENT_SIZE[i]+size_of::()), + }) .sum() } } -- 2.49.1 From 1bd7d2c11ce995411eae7521c2e03af04c09325e Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:13:19 -0800 Subject: [PATCH 21/31] no test exact data --- src/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index 455c822..a366654 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -37,7 +37,6 @@ fn serialize_round_trip()->Result<(),binrw::Error>{ v0::serialize(&block,&mut std::io::Cursor::new(&mut data))?; assert_eq!(data.len(),file.len()); - assert_eq!(data,file); Ok(()) } -- 2.49.1 From 5d075b8ee2376665db331ff85f3e2314dfe088c1 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:18:45 -0800 Subject: [PATCH 22/31] use zip for funsies --- src/v0.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 6cf7ae4..765e657 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -774,13 +774,13 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b } // A plan of what range of events to include in a data block. impl Plan>{ + /// Calculate the predicted size of the planned block. fn size(&self)->usize{ - self.0 - .iter() - .enumerate() - .filter_map(|(i,range)|match range.len(){ + self.0.iter() + .zip(EVENT_SIZE) + .filter_map(|(range,event_size)|match range.len(){ 0=>None, - other=>Some(other*EVENT_SIZE[i]+size_of::()), + other=>Some(other*event_size+size_of::()), }) .sum() } -- 2.49.1 From 841d976ade68da58ce7f4c324096f479b33a3761 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:34:01 -0800 Subject: [PATCH 23/31] fix block size prediction --- src/v0.rs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 765e657..03a9d9e 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -765,6 +765,12 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b struct Plan([T;8]); // A plan of how many events of each type to include in a data block. impl Plan{ + /// Predict the size increment from adding a new event. + fn size_increase(&self,event_type:EventType)->usize{ + let new_chunk_header=self.0[event_type as usize]==0; + EVENT_SIZE[event_type as usize]+(new_chunk_header as usize)*size_of::() + } + /// Add the new event. fn accumulate(&mut self,event_type:EventType){ self.0[event_type as usize]+=1; } @@ -820,24 +826,25 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b } fn plan_block(block:&Block,plan:&mut Plan,next_event:impl Fn(&Block,&Plan)->Option<(f64,EventType)>)->Option{ let mut size=0; - if let Some((start_time,first_event))=next_event(block,plan){ - size+=EVENT_SIZE[first_event as usize]; - if MAX_BLOCK_SIZE Date: Sun, 14 Dec 2025 12:45:42 -0800 Subject: [PATCH 24/31] use mask --- src/v0.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 03a9d9e..d1d5836 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -768,7 +768,8 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b /// Predict the size increment from adding a new event. fn size_increase(&self,event_type:EventType)->usize{ let new_chunk_header=self.0[event_type as usize]==0; - EVENT_SIZE[event_type as usize]+(new_chunk_header as usize)*size_of::() + let mask=(-(new_chunk_header as isize)) as usize; + EVENT_SIZE[event_type as usize]+(mask&size_of::()) } /// Add the new event. fn accumulate(&mut self,event_type:EventType){ -- 2.49.1 From 6b7aa795a92a0868c196c17e9989e66bfade692d Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:56:03 -0800 Subject: [PATCH 25/31] generic timeline fn --- src/v0.rs | 82 ++++++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index d1d5836..9cac116 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -809,22 +809,6 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b *best=Some((event.time,event_type)); } } - fn next_offline_event(block:&Block,plan:&Plan)->Option<(f64,EventType)>{ - let mut next_event=None; - collect_event(&mut next_event,&block.world_events,plan,EventType::World); - collect_event(&mut next_event,&block.gravity_events,plan,EventType::Gravity); - collect_event(&mut next_event,&block.run_events,plan,EventType::Run); - collect_event(&mut next_event,&block.camera_events,plan,EventType::Camera); - collect_event(&mut next_event,&block.setting_events,plan,EventType::Setting); - next_event - } - fn next_realtime_event(block:&Block,plan:&Plan)->Option<(f64,EventType)>{ - let mut next_event=None; - collect_event(&mut next_event,&block.input_events,plan,EventType::Input); - collect_event(&mut next_event,&block.output_events,plan,EventType::Output); - collect_event(&mut next_event,&block.sound_events,plan,EventType::Sound); - next_event - } fn plan_block(block:&Block,plan:&mut Plan,next_event:impl Fn(&Block,&Plan)->Option<(f64,EventType)>)->Option{ let mut size=0; let Some((start_time,first_event))=next_event(block,plan) else{ @@ -855,38 +839,44 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b time:f64, plan:Plan>, } + fn plan_timeline(block:&Block,next_event:F)->std::collections::VecDeque + where + F:Copy, + F:Fn(&Block,&Plan)->Option<(f64,EventType)> + { + let mut timeline=std::collections::VecDeque::new(); + let mut plan=Plan::default(); + let mut last_plan=plan.clone(); + let mut index=0; + while let Some(time)=plan_block(block,&mut plan,next_event){ + timeline.push_back(PlannedBlock{ + index, + time, + plan:last_plan.range(&plan), + }); + last_plan=plan.clone(); + index+=1; + } + timeline + } // plan events into segments without spilling over max size threshold // each plan describes the range of events included in the block. - let mut plan_offline=std::collections::VecDeque::new(); - { - let mut plan=Plan::default(); - let mut last_plan=plan.clone(); - let mut index=0; - while let Some(time)=plan_block(block,&mut plan,next_offline_event){ - plan_offline.push_back(PlannedBlock{ - index, - time, - plan:last_plan.range(&plan), - }); - last_plan=plan.clone(); - index+=1; - } - } - let mut plan_realtime=std::collections::VecDeque::new(); - { - let mut plan=Plan::default(); - let mut last_plan=plan.clone(); - let mut index=0; - while let Some(time)=plan_block(block,&mut plan,next_realtime_event){ - plan_realtime.push_back(PlannedBlock{ - index, - time, - plan:last_plan.range(&plan), - }); - last_plan=plan.clone(); - index+=1; - } - } + let mut plan_offline=plan_timeline(block,|block,plan|{ + let mut next_event=None; + collect_event(&mut next_event,&block.world_events,plan,EventType::World); + collect_event(&mut next_event,&block.gravity_events,plan,EventType::Gravity); + collect_event(&mut next_event,&block.run_events,plan,EventType::Run); + collect_event(&mut next_event,&block.camera_events,plan,EventType::Camera); + collect_event(&mut next_event,&block.setting_events,plan,EventType::Setting); + next_event + }); + let mut plan_realtime=plan_timeline(block,|block,plan|{ + let mut next_event=None; + collect_event(&mut next_event,&block.input_events,plan,EventType::Input); + collect_event(&mut next_event,&block.output_events,plan,EventType::Output); + collect_event(&mut next_event,&block.sound_events,plan,EventType::Sound); + next_event + }); use binrw::BinWrite; -- 2.49.1 From 87ce47f7071a3a255d77fb8c0841b8a694cabbbc Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 12:59:07 -0800 Subject: [PATCH 26/31] unplumb block and use closure capture --- src/v0.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 9cac116..b3033d9 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -809,9 +809,10 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b *best=Some((event.time,event_type)); } } - fn plan_block(block:&Block,plan:&mut Plan,next_event:impl Fn(&Block,&Plan)->Option<(f64,EventType)>)->Option{ + // plan a single block: collect events until the block is full + fn plan_block(plan:&mut Plan,next_event:impl Fn(&Plan)->Option<(f64,EventType)>)->Option{ let mut size=0; - let Some((start_time,first_event))=next_event(block,plan) else{ + let Some((start_time,first_event))=next_event(plan) else{ return None; }; @@ -821,7 +822,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b } plan.accumulate(first_event); - while let Some((_,event_type))=next_event(block,plan){ + while let Some((_,event_type))=next_event(plan){ size+=plan.size_increase(event_type); if MAX_BLOCK_SIZE(block:&Block,writer:&mut W)->Result<(),b time:f64, plan:Plan>, } - fn plan_timeline(block:&Block,next_event:F)->std::collections::VecDeque + fn plan_timeline(next_event:F)->std::collections::VecDeque where F:Copy, - F:Fn(&Block,&Plan)->Option<(f64,EventType)> + F:Fn(&Plan)->Option<(f64,EventType)> { let mut timeline=std::collections::VecDeque::new(); let mut plan=Plan::default(); let mut last_plan=plan.clone(); let mut index=0; - while let Some(time)=plan_block(block,&mut plan,next_event){ + while let Some(time)=plan_block(&mut plan,next_event){ timeline.push_back(PlannedBlock{ index, time, @@ -861,7 +862,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b } // plan events into segments without spilling over max size threshold // each plan describes the range of events included in the block. - let mut plan_offline=plan_timeline(block,|block,plan|{ + let mut plan_offline=plan_timeline(|plan|{ let mut next_event=None; collect_event(&mut next_event,&block.world_events,plan,EventType::World); collect_event(&mut next_event,&block.gravity_events,plan,EventType::Gravity); @@ -870,7 +871,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b collect_event(&mut next_event,&block.setting_events,plan,EventType::Setting); next_event }); - let mut plan_realtime=plan_timeline(block,|block,plan|{ + let mut plan_realtime=plan_timeline(|plan|{ let mut next_event=None; collect_event(&mut next_event,&block.input_events,plan,EventType::Input); collect_event(&mut next_event,&block.output_events,plan,EventType::Output); -- 2.49.1 From 9bdd298b76906a333744d9df12e0f1ff728fb4b1 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 13:01:26 -0800 Subject: [PATCH 27/31] move import to relevant section --- src/v0.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index b3033d9..972155e 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -879,8 +879,6 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b next_event }); - use binrw::BinWrite; - let file_header=FileHeader{ file_version:FILE_VERSION, num_offline_blocks:plan_offline.len() as u32, @@ -948,6 +946,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b realtime_blocks_timeline, }; + use binrw::BinWrite; file_header.write_le(writer)?; block_timelines.write_le(writer)?; for plan in plan_order{ -- 2.49.1 From 4447d40cf01f4f2afcdb2e4615a9009f0bda642b Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 13:03:01 -0800 Subject: [PATCH 28/31] clippy fixes --- src/v0.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 972155e..c52c707 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -804,7 +804,7 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),b E:for<'a>binrw::BinWrite=()>, { if let Some(event)=list.get(plan.0[event_type as usize]) - &&best.map_or(true,|(time,_)|event.time(block:&Block,writer:&mut W)->Result<(),b // plan a single block: collect events until the block is full fn plan_block(plan:&mut Plan,next_event:impl Fn(&Plan)->Option<(f64,EventType)>)->Option{ let mut size=0; - let Some((start_time,first_event))=next_event(plan) else{ - return None; - }; + let (start_time,first_event)=next_event(plan)?; size+=plan.size_increase(first_event); if MAX_BLOCK_SIZE(block:&Block,writer:&mut W)->Result<(),b plan.accumulate(event_type); } - return Some(start_time); + Some(start_time) } struct PlannedBlock{ -- 2.49.1 From 3690c272dd271f08229fc46d533eb2dc69cc661b Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sun, 14 Dec 2025 19:18:01 -0800 Subject: [PATCH 29/31] not real --- src/tests.rs | 3 +-- src/v0.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index a366654..9274458 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -36,7 +36,6 @@ fn serialize_round_trip()->Result<(),binrw::Error>{ let mut data=Vec::with_capacity(file.len()); v0::serialize(&block,&mut std::io::Cursor::new(&mut data))?; - assert_eq!(data.len(),file.len()); - + // TODO: It encodes, but is it equal? Test something! PartialEq? Ok(()) } diff --git a/src/v0.rs b/src/v0.rs index c52c707..7232565 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -747,7 +747,7 @@ pub fn read_all_to_block(mut data:R)->Result{ } #[cfg(feature="itertools")] -pub fn serialize(block:&Block,writer:&mut W)->Result<(),binrw::Error>{ +pub fn serialize(block:&Block,writer:&mut W)->Result<(),BinrwError>{ use std::ops::Range; const MAX_BLOCK_SIZE:usize=1<<14; const FILE_VERSION:u32=0; -- 2.49.1 From 79cd4bc4467bdd205f8aeffbcc299b3265003e70 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Mon, 15 Dec 2025 14:57:52 -0800 Subject: [PATCH 30/31] dep --- Cargo.lock | 10 ++++++++++ Cargo.toml | 3 +++ 2 files changed, 13 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 526d9be..489184c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "owo-colors" version = "4.2.3" @@ -80,6 +89,7 @@ version = "0.7.0" dependencies = [ "binrw", "bitflags", + "itertools", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ea48728..1f08e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,8 @@ edition = "2024" [dependencies] binrw = "0.15.0" bitflags = "2.6.0" +itertools = { version = "0.14.0", optional = true } [features] +default = ["itertools"] +itertools = ["dep:itertools"] -- 2.49.1 From 50dc39138b88983dafc5b90b0bbc640b0b048d85 Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Mon, 15 Dec 2025 15:02:23 -0800 Subject: [PATCH 31/31] get rid of EventType::from_usize --- src/v0.rs | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 7232565..9d3c4e1 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -490,22 +490,6 @@ enum EventType{ #[brw(magic=8u32)] Setting, } -impl EventType{ - // internal function meant for array indexing - fn from_usize(value:usize)->Self{ - match value{ - 0=>Self::Input, - 1=>Self::Output, - 2=>Self::Sound, - 3=>Self::World, - 4=>Self::Gravity, - 5=>Self::Run, - 6=>Self::Camera, - 7=>Self::Setting, - _=>panic!(), - } - } -} #[binrw] #[brw(little)] struct EventChunkHeader{ @@ -751,6 +735,16 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),B use std::ops::Range; const MAX_BLOCK_SIZE:usize=1<<14; const FILE_VERSION:u32=0; + const EVENT_TYPES:[EventType;8]=[ + EventType::Input, + EventType::Output, + EventType::Sound, + EventType::World, + EventType::Gravity, + EventType::Run, + EventType::Camera, + EventType::Setting, + ]; const EVENT_SIZE:[usize;8]=[ 8+4+2*4, // Input 8+4+4*3*4, // Output @@ -948,12 +942,11 @@ pub fn serialize(block:&Block,writer:&mut W)->Result<(),B file_header.write_le(writer)?; block_timelines.write_le(writer)?; for plan in plan_order{ - for (event_type_id,range) in plan.0.into_iter().enumerate(){ + for (range,event_type) in plan.0.into_iter().zip(EVENT_TYPES){ let num_events=range.len(); if num_events==0{ continue; } - let event_type=EventType::from_usize(event_type_id); let event_chunk_header=EventChunkHeader{ event_type, num_events:num_events as u32, -- 2.49.1