use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt}; use crate::newtypes; use crate::file::BlockId; use strafesnet_common::physics::Time; const VERSION:u32=0; type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction; #[derive(Debug)] pub enum Error{ InvalidHeader(binrw::Error), InvalidSegment(binrw::Error), SegmentConvert(newtypes::integer::RatioError), InstructionConvert(newtypes::physics::InstructionConvert), InstructionWrite(binrw::Error), InvalidSegmentId(SegmentId), InvalidData(binrw::Error), IO(std::io::Error), File(crate::file::Error), } // Bot files are simply the sequence of instructions that the physics received during the run. // The instructions are partitioned into timestamped blocks for ease of streaming. // // Keyframe information required for efficient seeking // is part of a different file, and is generated from this file. /* block types BLOCK_BOT_HEADER: // Segments are laid out in chronological order, // but block_id is not necessarily in ascending order. // // This is to place the final segment close to the start of the file, // which allows the duration of the bot to be conveniently calculated // from the first and last instruction timestamps. // // Use exact physics version for replay playback // Use highest compatible physics version for verification u32 physics_version u32 num_segments for _ in 0..num_segments{ i64 time u32 instruction_count u32 block_id } BLOCK_BOT_SEGMENT: // segments can potentially be losslessly compressed! for _ in 0..instruction_count{ // TODO: delta encode as much as possible (time,mousepos) i64 time physics::Instruction instruction } */ #[binrw] #[brw(little)] struct SegmentHeader{ time:i64, instruction_count:u32, block_id:BlockId, } #[binrw] #[brw(little)] struct Header{ physics_version:u32, num_segments:u32, #[br(count=num_segments)] segments:Vec, } #[binrw] #[brw(little)] #[derive(Clone,Copy,Debug,id::Id)] pub struct SegmentId(u32); pub struct Segment{ pub instructions:Vec } #[derive(Clone,Copy,Debug)] pub struct SegmentInfo{ /// time of the first instruction in this segment. time:Time, instruction_count:u32, /// How many total instructions in segments up to and including this segment /// Alternatively, the id of the first instruction be in the _next_ segment instructions_subtotal:u64, block_id:BlockId, } pub struct StreamableBot{ file:crate::file::File, segment_map:Vec, } impl StreamableBot{ pub(crate) fn new(mut file:crate::file::File)->Result{ //assume the file seek is in the right place to start reading header let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?; let mut instructions_subtotal=0; let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{ instructions_subtotal+=instruction_count as u64; SegmentInfo{ time:Time::raw(time), instruction_count, instructions_subtotal, block_id, } }).collect(); Ok(Self{ file, segment_map, }) } fn get_segment_info(&self,segment_id:SegmentId)->Result{ Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?) } pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{ let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal&[SegmentInfo]{ // // TODO: This is off by one, both should be one less // let start=self.segment_map.partition_point(|segment_info|segment_info.timeResult<(),Error>{ let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?; for _ in 0..segment_info.instruction_count{ let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?; segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?); } Ok(()) } pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result{ let mut segment=Segment{ instructions:Vec::with_capacity(segment_info.instruction_count as usize), }; self.append_to_segment(segment_info,&mut segment)?; Ok(segment) } pub fn read_all(&mut self)->Result{ let mut segment=Segment{ instructions:Vec::new(), }; for i in 0..self.segment_map.len(){ let segment_info=self.segment_map[i]; self.append_to_segment(segment_info,&mut segment)?; } Ok(segment) } } const MAX_BLOCK_SIZE:usize=64*1024;//64 kB pub fn write_bot(mut writer:W,physics_version:u32,instructions:impl IntoIterator)->Result<(),Error>{ // decide which instructions to put in which segment // write segment 1 to block 1 // write segment N to block 2 // write rest of segments // 1 2 3 4 5 // becomes // [1 5] 2 3 4 struct SegmentHeaderInfo{ time:Time, instruction_count:u32, range:core::ops::Range } let mut segment_header_infos=Vec::new(); let mut raw_segments=std::io::Cursor::new(Vec::new()); // block info let mut start_time=Time::ZERO; let mut start_position=raw_segments.position() as usize; let mut instruction_count=0; let mut last_position=start_position; let mut iter=instructions.into_iter(); macro_rules! collect_instruction{ ($instruction:expr)=>{ let time=$instruction.time; let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?; instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?; instruction_count+=1; let position=raw_segments.position() as usize; // exceeds max block size if MAX_BLOCK_SIZE