|
|
|
|
@@ -2,29 +2,28 @@ use std::io::{SeekFrom,Error as IoError};
|
|
|
|
|
use binrw::binrw;
|
|
|
|
|
use binrw::io::{TakeSeek,TakeSeekExt};
|
|
|
|
|
use binrw::BinReaderExt;
|
|
|
|
|
use crate::BinrwError;
|
|
|
|
|
|
|
|
|
|
// the bit chunks are deposited in reverse
|
|
|
|
|
const fn read_trey_float(bits:u32)->f32{
|
|
|
|
|
fn read_trey_float(bits:u32)->f32{
|
|
|
|
|
let s=bits&1;
|
|
|
|
|
let e=(bits>>1)&((1<<8)-1);
|
|
|
|
|
let m=(bits>>(1+8))&((1<<23)-1);
|
|
|
|
|
f32::from_bits(m|(e<<23)|(s<<31))
|
|
|
|
|
}
|
|
|
|
|
const fn write_trey_float(value:&f32)->u32{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
const fn read_trey_double(bits:u64)->f64{
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
const fn write_trey_double(value:&f64)->u64{
|
|
|
|
|
fn write_trey_double(value:&f64)->u64{
|
|
|
|
|
let bits=value.to_bits();
|
|
|
|
|
let s=(bits>>63)&1;
|
|
|
|
|
let e=(bits>>52)&((1<<11)-1);
|
|
|
|
|
@@ -34,7 +33,7 @@ const fn write_trey_double(value:&f64)->u64{
|
|
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone,Copy)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct Vector2{
|
|
|
|
|
#[br(map=read_trey_float)]
|
|
|
|
|
#[bw(map=write_trey_float)]
|
|
|
|
|
@@ -45,7 +44,7 @@ pub struct Vector2{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone,Copy)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct Vector3{
|
|
|
|
|
#[br(map=read_trey_float)]
|
|
|
|
|
#[bw(map=write_trey_float)]
|
|
|
|
|
@@ -135,7 +134,7 @@ impl<A,B> PartialOrd<Timed<B>> for Timed<A>
|
|
|
|
|
// input
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct InputEvent{
|
|
|
|
|
#[br(try_map=GameControls::try_from_bits)]
|
|
|
|
|
#[bw(map=GameControls::bits)]
|
|
|
|
|
@@ -168,7 +167,7 @@ impl TickInfo{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct OutputEvent{
|
|
|
|
|
#[br(try_map=TickInfo::try_from_bits)]
|
|
|
|
|
#[bw(map=TickInfo::bits)]
|
|
|
|
|
@@ -203,7 +202,7 @@ pub enum SoundType{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct SoundEvent{
|
|
|
|
|
pub sound_type:SoundType,
|
|
|
|
|
/// Roblox enum
|
|
|
|
|
@@ -213,13 +212,13 @@ pub struct SoundEvent{
|
|
|
|
|
// world
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct WorldEventReset{
|
|
|
|
|
pub position:Vector3,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct WorldEventButton{
|
|
|
|
|
pub button_id:u32,
|
|
|
|
|
// This field does not exist in the final struct and
|
|
|
|
|
@@ -231,7 +230,7 @@ pub struct WorldEventButton{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct WorldEventSetTime{
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
@@ -243,7 +242,7 @@ pub struct WorldEventSetTime{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct WorldEventSetPaused{
|
|
|
|
|
#[br(map=|paused:u32|paused!=0)]
|
|
|
|
|
#[bw(map=|&paused:&bool|paused as u32)]
|
|
|
|
|
@@ -255,7 +254,7 @@ pub struct WorldEventSetPaused{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub enum WorldEvent{
|
|
|
|
|
#[brw(magic=0u32)]
|
|
|
|
|
Reset(WorldEventReset),
|
|
|
|
|
@@ -270,7 +269,7 @@ pub enum WorldEvent{
|
|
|
|
|
// gravity
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct GravityEvent{
|
|
|
|
|
pub gravity:Vector3,
|
|
|
|
|
}
|
|
|
|
|
@@ -350,17 +349,16 @@ pub enum FlagReason{
|
|
|
|
|
#[brw(magic=9u32)]
|
|
|
|
|
Practice,
|
|
|
|
|
}
|
|
|
|
|
/// Creates a new run when the player enters a start zone.
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct RunEventPrepare{
|
|
|
|
|
pub mode:ModeID,
|
|
|
|
|
pub style:Style,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct RunEventZone{
|
|
|
|
|
pub mode:ModeID,
|
|
|
|
|
#[br(temp)]
|
|
|
|
|
@@ -370,7 +368,7 @@ pub struct RunEventZone{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct RunEventClear{
|
|
|
|
|
pub mode:ModeSpec,
|
|
|
|
|
#[br(temp)]
|
|
|
|
|
@@ -380,21 +378,21 @@ pub struct RunEventClear{
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct RunEventFlag{
|
|
|
|
|
pub mode:ModeSpec,
|
|
|
|
|
pub flag_reason:FlagReason,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct RunEventPractice{
|
|
|
|
|
pub mode:ModeSpec,
|
|
|
|
|
pub state_id:u32,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub enum RunEvent{
|
|
|
|
|
#[brw(magic=0u32)]
|
|
|
|
|
Prepare(RunEventPrepare),
|
|
|
|
|
@@ -413,96 +411,51 @@ pub enum RunEvent{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// camera
|
|
|
|
|
/// Punches the camera when the player has an intense collision.
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct CameraEventCameraPunch{
|
|
|
|
|
pub rot_velocity:Vector3,
|
|
|
|
|
}
|
|
|
|
|
/// Rotates the camera when the player goes through a wormhole.
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct CameraEventTransform{
|
|
|
|
|
pub axis_angle:Vector3,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub enum CameraEvent{
|
|
|
|
|
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
|
|
|
|
pub enum CameraEventType{
|
|
|
|
|
#[brw(magic=0u32)]
|
|
|
|
|
CameraPunch(CameraEventCameraPunch),
|
|
|
|
|
CameraPunch,
|
|
|
|
|
#[brw(magic=1u32)]
|
|
|
|
|
Transform(CameraEventTransform),
|
|
|
|
|
Transform,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct CameraEvent{
|
|
|
|
|
pub camera_event_type:CameraEventType,
|
|
|
|
|
pub value:Vector3,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setting
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct SettingEventFieldOfView{
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
pub fov:f64,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct SettingEventSensitivity{
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
pub sensitivity:f64,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct SettingEventVerticalSensitivityMultiplier{
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
pub multiplier:f64,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct SettingEventAbsoluteSensitivity{
|
|
|
|
|
#[br(map=|v:u64|read_trey_double(v)==1.0)]
|
|
|
|
|
#[bw(map=|&enabled:&bool|
|
|
|
|
|
if enabled{
|
|
|
|
|
write_trey_double(&1.0)
|
|
|
|
|
}else{
|
|
|
|
|
write_trey_double(&0.0)
|
|
|
|
|
}
|
|
|
|
|
)]
|
|
|
|
|
pub enabled:bool,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub struct SettingEventTurnSpeed{
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
pub turn_speed:f64,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone)]
|
|
|
|
|
pub enum SettingEvent{
|
|
|
|
|
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
|
|
|
|
pub enum SettingType{
|
|
|
|
|
#[brw(magic=0u32)]
|
|
|
|
|
FieldOfView(SettingEventFieldOfView),
|
|
|
|
|
FieldOfView,
|
|
|
|
|
#[brw(magic=1u32)]
|
|
|
|
|
Sensitivity(SettingEventSensitivity),
|
|
|
|
|
Sensitivity,
|
|
|
|
|
#[brw(magic=2u32)]
|
|
|
|
|
VerticalSensitivityMultiplier(SettingEventVerticalSensitivityMultiplier),
|
|
|
|
|
VerticalSensitivityMultiplier,
|
|
|
|
|
#[brw(magic=3u32)]
|
|
|
|
|
AbsoluteSensitivity(SettingEventAbsoluteSensitivity),
|
|
|
|
|
AbsoluteSensitivity,
|
|
|
|
|
#[brw(magic=4u32)]
|
|
|
|
|
TurnSpeed(SettingEventTurnSpeed),
|
|
|
|
|
TurnSpeed,
|
|
|
|
|
}
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Debug,Clone,PartialEq)]
|
|
|
|
|
pub struct SettingEvent{
|
|
|
|
|
pub setting_type:SettingType,
|
|
|
|
|
#[br(map=read_trey_double)]
|
|
|
|
|
#[bw(map=write_trey_double)]
|
|
|
|
|
pub value:f64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A segment of event timelines.
|
|
|
|
|
/// Timelines are always be sorted.
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
#[derive(Debug,Default,PartialEq)]
|
|
|
|
|
pub struct Block{
|
|
|
|
|
pub input_events:Vec<Timed<InputEvent>>,
|
|
|
|
|
pub output_events:Vec<Timed<OutputEvent>>,
|
|
|
|
|
@@ -517,7 +470,7 @@ pub struct Block{
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
#[derive(Clone,Copy)]
|
|
|
|
|
pub enum EventType{
|
|
|
|
|
enum EventType{
|
|
|
|
|
#[brw(magic=1u32)]
|
|
|
|
|
Input,
|
|
|
|
|
#[brw(magic=2u32)]
|
|
|
|
|
@@ -535,6 +488,22 @@ pub 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{
|
|
|
|
|
@@ -543,15 +512,15 @@ struct EventChunkHeader{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// binread args tech has been further refined
|
|
|
|
|
fn read_data_into_events<R,T,F>(
|
|
|
|
|
fn read_data_into_events<'a,R,T,F>(
|
|
|
|
|
data:&mut R,
|
|
|
|
|
events:&mut Vec<T>,
|
|
|
|
|
num_events:usize,
|
|
|
|
|
reserve_fn:F,
|
|
|
|
|
)->Result<(),BinrwError>
|
|
|
|
|
)->binrw::BinResult<()>
|
|
|
|
|
where
|
|
|
|
|
R:BinReaderExt,
|
|
|
|
|
T:for<'a> binrw::BinRead<Args<'a>=()>,
|
|
|
|
|
T:binrw::BinRead<Args<'a>=()>,
|
|
|
|
|
F:Fn(&mut Vec<T>,usize),
|
|
|
|
|
{
|
|
|
|
|
reserve_fn(events,num_events);
|
|
|
|
|
@@ -562,7 +531,7 @@ fn read_data_into_events<R,T,F>(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Block{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(data:R)->Result<Block,BinrwError>{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{
|
|
|
|
|
let mut block=Block::default();
|
|
|
|
|
// there is only supposed to be at most one of each type
|
|
|
|
|
// of event chunk per block, so allocate the size exactly.
|
|
|
|
|
@@ -571,7 +540,7 @@ impl Block{
|
|
|
|
|
}
|
|
|
|
|
/// Read a complete data block and append the elements to the timelines in this block.
|
|
|
|
|
/// Reserves exactly enough information for the new data.
|
|
|
|
|
pub fn extend_from_reader_exact<R:BinReaderExt>(&mut self,mut data:R)->Result<(),BinrwError>{
|
|
|
|
|
pub fn extend_from_reader_exact<R:BinReaderExt>(&mut self,mut data:R)->binrw::BinResult<()>{
|
|
|
|
|
// well... this looks error prone
|
|
|
|
|
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
|
|
|
|
|
match event_chunk_header.event_type{
|
|
|
|
|
@@ -588,7 +557,7 @@ impl Block{
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
/// Read a complete data block and append the elements to the timelines in this block.
|
|
|
|
|
pub fn extend_from_reader<R:BinReaderExt>(&mut self,mut data:R)->Result<(),BinrwError>{
|
|
|
|
|
pub fn extend_from_reader<R:BinReaderExt>(&mut self,mut data:R)->binrw::BinResult<()>{
|
|
|
|
|
// sad code duplication
|
|
|
|
|
while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
|
|
|
|
|
match event_chunk_header.event_type{
|
|
|
|
|
@@ -604,23 +573,13 @@ impl Block{
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
fn extend_from_block_id_iter<'a,R:BinReaderExt>(&mut self,mut data:R,block_timelines:&BlockTimelines,blocks:impl IntoIterator<Item=&'a Timed<BlockId>>)->Result<(),Error>{
|
|
|
|
|
for timed in blocks{
|
|
|
|
|
let take_seek=block_timelines
|
|
|
|
|
.block_info(timed.event)?
|
|
|
|
|
.take_seek(&mut data)
|
|
|
|
|
.map_err(Error::Seek)?;
|
|
|
|
|
self.extend_from_reader(take_seek).map_err(Error::InvalidData)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum Error{
|
|
|
|
|
InvalidBlockId(InvalidBlockId),
|
|
|
|
|
InvalidBlockId(BlockId),
|
|
|
|
|
Seek(IoError),
|
|
|
|
|
InvalidData(BinrwError),
|
|
|
|
|
InvalidData(binrw::Error),
|
|
|
|
|
}
|
|
|
|
|
impl std::fmt::Display for Error{
|
|
|
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
|
|
|
@@ -646,20 +605,6 @@ struct BlockPosition(
|
|
|
|
|
u32
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct InvalidBlockId(pub BlockId);
|
|
|
|
|
impl std::fmt::Display for InvalidBlockId{
|
|
|
|
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
|
|
|
write!(f,"{self:?}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl std::error::Error for InvalidBlockId{}
|
|
|
|
|
impl From<InvalidBlockId> for Error{
|
|
|
|
|
fn from(value:InvalidBlockId)->Self{
|
|
|
|
|
Self::InvalidBlockId(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The first 16 bytes of the file.
|
|
|
|
|
#[binrw]
|
|
|
|
|
#[brw(little)]
|
|
|
|
|
@@ -671,7 +616,7 @@ pub struct FileHeader{
|
|
|
|
|
num_realtime_blocks:u32,
|
|
|
|
|
}
|
|
|
|
|
impl FileHeader{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(mut data:R)->Result<Self,BinrwError>{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(mut data:R)->binrw::BinResult<Self>{
|
|
|
|
|
data.read_le()
|
|
|
|
|
}
|
|
|
|
|
fn block_position_count(&self)->u32{
|
|
|
|
|
@@ -705,7 +650,7 @@ pub struct BlockTimelines{
|
|
|
|
|
realtime_blocks_timeline:Vec<Timed<BlockId>>,
|
|
|
|
|
}
|
|
|
|
|
impl BlockTimelines{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->Result<Self,BinrwError>{
|
|
|
|
|
pub fn from_reader<R:BinReaderExt>(header:&FileHeader,mut data:R)->binrw::BinResult<Self>{
|
|
|
|
|
data.read_le_args(header)
|
|
|
|
|
}
|
|
|
|
|
/// "Offline" blocks (containing World, Gravity, Run, Camera, and Setting events) in chronological order.
|
|
|
|
|
@@ -717,13 +662,12 @@ impl BlockTimelines{
|
|
|
|
|
&self.realtime_blocks_timeline
|
|
|
|
|
}
|
|
|
|
|
/// Get BlockInfo for a specfic BlockId.
|
|
|
|
|
pub fn block_info(&self,block_id:BlockId)->Result<BlockInfo,InvalidBlockId>{
|
|
|
|
|
let BlockId(id)=block_id;
|
|
|
|
|
if self.block_positions.len() as u32<=id{
|
|
|
|
|
return Err(InvalidBlockId(block_id));
|
|
|
|
|
pub fn block_info(&self,BlockId(block_id):BlockId)->Result<BlockInfo,Error>{
|
|
|
|
|
if self.block_positions.len() as u32<=block_id{
|
|
|
|
|
return Err(Error::InvalidBlockId(BlockId(block_id)));
|
|
|
|
|
}
|
|
|
|
|
let BlockPosition(start)=self.block_positions[id as usize];
|
|
|
|
|
let BlockPosition(end)=self.block_positions[id as usize+1];
|
|
|
|
|
let BlockPosition(start)=self.block_positions[block_id as usize];
|
|
|
|
|
let BlockPosition(end)=self.block_positions[block_id as usize+1];
|
|
|
|
|
Ok(BlockInfo(start..end))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -744,13 +688,24 @@ impl core::ops::Deref for BlockInfo{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_to_block<'a,R:BinReaderExt>(mut data:R,block_timelines:&BlockTimelines,blocks:impl IntoIterator<Item=&'a Timed<BlockId>>)->Result<Block,Error>{
|
|
|
|
|
let mut block=Block::default();
|
|
|
|
|
for timed in blocks{
|
|
|
|
|
let take_seek=block_timelines
|
|
|
|
|
.block_info(timed.event)?
|
|
|
|
|
.take_seek(&mut data)
|
|
|
|
|
.map_err(Error::Seek)?;
|
|
|
|
|
block.extend_from_reader(take_seek).map_err(Error::InvalidData)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read offline blocks and combine the timelines into a single Block.
|
|
|
|
|
/// Note that this reads the blocks in chronological order, not the order they appear in the file, so there is some seeking involved.
|
|
|
|
|
pub fn read_offline_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
|
|
|
|
let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let mut block=Block::default();
|
|
|
|
|
block.extend_from_block_id_iter(data,&block_timelines,block_timelines.offline_blocks())?;
|
|
|
|
|
let block=read_to_block(data,&block_timelines,block_timelines.offline_blocks())?;
|
|
|
|
|
Ok(block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -759,122 +714,26 @@ pub fn read_offline_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
|
|
|
|
pub fn read_realtime_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
|
|
|
|
let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let mut block=Block::default();
|
|
|
|
|
block.extend_from_block_id_iter(data,&block_timelines,block_timelines.realtime_blocks())?;
|
|
|
|
|
let block=read_to_block(data,&block_timelines,block_timelines.realtime_blocks())?;
|
|
|
|
|
Ok(block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read the entire file and combine the timelines into a single Block.
|
|
|
|
|
/// Note that this reads the blocks in chronological order, not the order they appear in the file, so there is some seeking involved.
|
|
|
|
|
#[cfg(feature="itertools")]
|
|
|
|
|
pub fn read_all_to_block<R:BinReaderExt>(mut data:R)->Result<Block,Error>{
|
|
|
|
|
let header=FileHeader::from_reader(&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let block_timelines=BlockTimelines::from_reader(&header,&mut data).map_err(Error::InvalidData)?;
|
|
|
|
|
let mut block=Block::default();
|
|
|
|
|
block.extend_from_block_id_iter(&mut data,&block_timelines,block_timelines.offline_blocks())?;
|
|
|
|
|
block.extend_from_block_id_iter(&mut data,&block_timelines,block_timelines.realtime_blocks())?;
|
|
|
|
|
let block=read_to_block(data,&block_timelines,itertools::merge(block_timelines.offline_blocks(),block_timelines.realtime_blocks()))?;
|
|
|
|
|
Ok(block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NUM_EVENT_TYPES:usize=8;
|
|
|
|
|
#[derive(Clone,Copy)]
|
|
|
|
|
pub struct Head([usize;NUM_EVENT_TYPES]);
|
|
|
|
|
impl Head{
|
|
|
|
|
pub const fn new()->Self{
|
|
|
|
|
Self([0;NUM_EVENT_TYPES])
|
|
|
|
|
}
|
|
|
|
|
/// Use `Head::partition_point` instead.
|
|
|
|
|
#[deprecated]
|
|
|
|
|
pub fn after_time(block:&Block,time:f64)->Self{
|
|
|
|
|
Self([
|
|
|
|
|
block.input_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.output_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.sound_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.world_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.gravity_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.run_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.camera_events.partition_point(|event|event.time<=time),
|
|
|
|
|
block.setting_events.partition_point(|event|event.time<=time),
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
/// Uses a binary search to initialize the head positions according to a predicate.
|
|
|
|
|
/// You probably want `|event_time|event_time<=time`
|
|
|
|
|
pub fn partition_point(block:&Block,pred:impl Fn(f64)->bool)->Self{
|
|
|
|
|
Self([
|
|
|
|
|
block.input_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.output_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.sound_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.world_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.gravity_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.run_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.camera_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
block.setting_events.partition_point(|event|pred(event.time)),
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
// compare an event at the head of the plan to the best event collected so far.
|
|
|
|
|
fn collect_event<E>(
|
|
|
|
|
&self,
|
|
|
|
|
event_type:EventType,
|
|
|
|
|
list:&[Timed<E>],
|
|
|
|
|
best:&mut Option<Timed<EventType>>,
|
|
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
E:for<'a>binrw::BinRead<Args<'a>=()>,
|
|
|
|
|
E:for<'a>binrw::BinWrite<Args<'a>=()>,
|
|
|
|
|
{
|
|
|
|
|
if let Some(event)=list.get(self.get_event_index(event_type))
|
|
|
|
|
&&best.as_ref().is_none_or(|b|event.time<b.time)
|
|
|
|
|
{
|
|
|
|
|
*best=Some(Timed{time:event.time,event:event_type});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn collect_offline(&self,block:&Block,next_event:&mut Option<Timed<EventType>>){
|
|
|
|
|
self.collect_event(EventType::World,&block.world_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Gravity,&block.gravity_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Run,&block.run_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Camera,&block.camera_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Setting,&block.setting_events,next_event);
|
|
|
|
|
}
|
|
|
|
|
fn collect_realtime(&self,block:&Block,next_event:&mut Option<Timed<EventType>>){
|
|
|
|
|
self.collect_event(EventType::Input,&block.input_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Output,&block.output_events,next_event);
|
|
|
|
|
self.collect_event(EventType::Sound,&block.sound_events,next_event);
|
|
|
|
|
}
|
|
|
|
|
pub fn next_event(&self,block:&Block)->Option<Timed<EventType>>{
|
|
|
|
|
let mut next_event=None;
|
|
|
|
|
// This order is particular.
|
|
|
|
|
// Setting must appear before Input for strafe client resimulation to work.
|
|
|
|
|
self.collect_offline(block,&mut next_event);
|
|
|
|
|
self.collect_realtime(block,&mut next_event);
|
|
|
|
|
next_event
|
|
|
|
|
}
|
|
|
|
|
pub const fn get_event_index(&self,event_type:EventType)->usize{
|
|
|
|
|
self.0[event_type as usize]
|
|
|
|
|
}
|
|
|
|
|
pub const fn set_event_index(&mut self,event_type:EventType,index:usize){
|
|
|
|
|
self.0[event_type as usize]=index;
|
|
|
|
|
}
|
|
|
|
|
/// Add the new event.
|
|
|
|
|
pub const fn push(&mut self,event_type:EventType){
|
|
|
|
|
self.0[event_type as usize]+=1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature="itertools")]
|
|
|
|
|
pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),BinrwError>{
|
|
|
|
|
pub fn serialize<W:binrw::BinWriterExt>(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_TYPES:[EventType;NUM_EVENT_TYPES]=[
|
|
|
|
|
EventType::Input,
|
|
|
|
|
EventType::Output,
|
|
|
|
|
EventType::Sound,
|
|
|
|
|
EventType::World,
|
|
|
|
|
EventType::Gravity,
|
|
|
|
|
EventType::Run,
|
|
|
|
|
EventType::Camera,
|
|
|
|
|
EventType::Setting,
|
|
|
|
|
];
|
|
|
|
|
const EVENT_SIZE:[usize;NUM_EVENT_TYPES]=[
|
|
|
|
|
const EVENT_SIZE:[usize;8]=[
|
|
|
|
|
8+4+2*4, // Input
|
|
|
|
|
8+4+4*3*4, // Output
|
|
|
|
|
8+4+4, // Sound
|
|
|
|
|
@@ -884,12 +743,26 @@ pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),B
|
|
|
|
|
8+4+3*4, // Camera
|
|
|
|
|
8+4+8, // Setting
|
|
|
|
|
];
|
|
|
|
|
// A plan of what range of events to include in a data block.
|
|
|
|
|
struct Plan([Range<usize>;NUM_EVENT_TYPES]);
|
|
|
|
|
impl Plan{
|
|
|
|
|
fn new(start:&Head,end:&Head)->Self{
|
|
|
|
|
Plan(core::array::from_fn(|i|start.0[i]..end.0[i]))
|
|
|
|
|
#[derive(Clone,Default)]
|
|
|
|
|
struct Plan<T>([T;8]);
|
|
|
|
|
// A plan of how many events of each type to include in a data block.
|
|
|
|
|
impl Plan<usize>{
|
|
|
|
|
/// 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;
|
|
|
|
|
let mask=(-(new_chunk_header as isize)) as usize;
|
|
|
|
|
EVENT_SIZE[event_type as usize]+(mask&size_of::<EventChunkHeader>())
|
|
|
|
|
}
|
|
|
|
|
/// Add the new event.
|
|
|
|
|
fn accumulate(&mut self,event_type:EventType){
|
|
|
|
|
self.0[event_type as usize]+=1;
|
|
|
|
|
}
|
|
|
|
|
fn range(&self,end:&Plan<usize>)->Plan<Range<usize>>{
|
|
|
|
|
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<Range<usize>>{
|
|
|
|
|
/// Calculate the predicted size of the planned block.
|
|
|
|
|
fn size(&self)->usize{
|
|
|
|
|
self.0.iter()
|
|
|
|
|
@@ -901,42 +774,43 @@ pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),B
|
|
|
|
|
.sum()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl IntoIterator for Plan{
|
|
|
|
|
type IntoIter=core::iter::Zip<
|
|
|
|
|
core::array::IntoIter<EventType,NUM_EVENT_TYPES>,
|
|
|
|
|
core::array::IntoIter<Range<usize>,NUM_EVENT_TYPES>,
|
|
|
|
|
>;
|
|
|
|
|
type Item=(EventType,Range<usize>);
|
|
|
|
|
fn into_iter(self)->Self::IntoIter{
|
|
|
|
|
EVENT_TYPES.into_iter().zip(self.0)
|
|
|
|
|
// compare an event at the head of the plan to the best event collected so far.
|
|
|
|
|
fn collect_event<E>(
|
|
|
|
|
best:&mut Option<(f64,EventType)>,
|
|
|
|
|
list:&[Timed<E>],
|
|
|
|
|
plan:&Plan<usize>,
|
|
|
|
|
event_type:EventType,
|
|
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
E:for<'a>binrw::BinRead<Args<'a>=()>,
|
|
|
|
|
E:for<'a>binrw::BinWrite<Args<'a>=()>,
|
|
|
|
|
{
|
|
|
|
|
if let Some(event)=list.get(plan.0[event_type as usize])
|
|
|
|
|
&&best.is_none_or(|(time,_)|event.time<time)
|
|
|
|
|
{
|
|
|
|
|
*best=Some((event.time,event_type));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// Predict the size increment from adding a new event.
|
|
|
|
|
fn predict_size_increment(head:&Head,event_type:EventType)->usize{
|
|
|
|
|
let new_chunk_header=head.get_event_index(event_type)==0;
|
|
|
|
|
let mask=(-(new_chunk_header as isize)) as usize;
|
|
|
|
|
EVENT_SIZE[event_type as usize]+(mask&size_of::<EventChunkHeader>())
|
|
|
|
|
}
|
|
|
|
|
// plan a single block: collect events until the block is full
|
|
|
|
|
fn plan_block(head:&mut Head,next_event:impl Fn(&Head)->Option<Timed<EventType>>)->Option<f64>{
|
|
|
|
|
fn plan_block(plan:&mut Plan<usize>,next_event:impl Fn(&Plan<usize>)->Option<(f64,EventType)>)->Option<f64>{
|
|
|
|
|
let mut size=0;
|
|
|
|
|
let first=next_event(head)?;
|
|
|
|
|
let (start_time,first_event)=next_event(plan)?;
|
|
|
|
|
|
|
|
|
|
size+=predict_size_increment(head,first.event);
|
|
|
|
|
size+=plan.size_increase(first_event);
|
|
|
|
|
if MAX_BLOCK_SIZE<size{
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
head.push(first.event);
|
|
|
|
|
plan.accumulate(first_event);
|
|
|
|
|
|
|
|
|
|
while let Some(event)=next_event(head){
|
|
|
|
|
size+=predict_size_increment(head,event.event);
|
|
|
|
|
while let Some((_,event_type))=next_event(plan){
|
|
|
|
|
size+=plan.size_increase(event_type);
|
|
|
|
|
if MAX_BLOCK_SIZE<size{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
head.push(event.event);
|
|
|
|
|
plan.accumulate(event_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(first.time)
|
|
|
|
|
Some(start_time)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct PlannedBlock{
|
|
|
|
|
@@ -944,24 +818,24 @@ pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),B
|
|
|
|
|
// It is list-local for both plan_offline and plan_realtime.
|
|
|
|
|
index:usize,
|
|
|
|
|
time:f64,
|
|
|
|
|
plan:Plan,
|
|
|
|
|
plan:Plan<Range<usize>>,
|
|
|
|
|
}
|
|
|
|
|
fn plan_timeline<F>(next_event:F)->std::collections::VecDeque<PlannedBlock>
|
|
|
|
|
where
|
|
|
|
|
F:Copy,
|
|
|
|
|
F:Fn(&Head)->Option<Timed<EventType>>
|
|
|
|
|
F:Fn(&Plan<usize>)->Option<(f64,EventType)>
|
|
|
|
|
{
|
|
|
|
|
let mut timeline=std::collections::VecDeque::new();
|
|
|
|
|
let mut head=Head::new();
|
|
|
|
|
let mut last_head=head.clone();
|
|
|
|
|
let mut plan=Plan::default();
|
|
|
|
|
let mut last_plan=plan.clone();
|
|
|
|
|
let mut index=0;
|
|
|
|
|
while let Some(time)=plan_block(&mut head,next_event){
|
|
|
|
|
while let Some(time)=plan_block(&mut plan,next_event){
|
|
|
|
|
timeline.push_back(PlannedBlock{
|
|
|
|
|
index,
|
|
|
|
|
time,
|
|
|
|
|
plan:Plan::new(&last_head,&head),
|
|
|
|
|
plan:last_plan.range(&plan),
|
|
|
|
|
});
|
|
|
|
|
last_head=head.clone();
|
|
|
|
|
last_plan=plan.clone();
|
|
|
|
|
index+=1;
|
|
|
|
|
}
|
|
|
|
|
timeline
|
|
|
|
|
@@ -970,12 +844,18 @@ pub fn serialize<W:binrw::BinWriterExt>(block:&Block,writer:&mut W)->Result<(),B
|
|
|
|
|
// each plan describes the range of events included in the block.
|
|
|
|
|
let mut plan_offline=plan_timeline(|plan|{
|
|
|
|
|
let mut next_event=None;
|
|
|
|
|
plan.collect_offline(block,&mut next_event);
|
|
|
|
|
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(|plan|{
|
|
|
|
|
let mut next_event=None;
|
|
|
|
|
plan.collect_realtime(block,&mut next_event);
|
|
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -1050,11 +930,12 @@ pub fn serialize<W:binrw::BinWriterExt>(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,range) in plan{
|
|
|
|
|
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:num_events as u32,
|
|
|
|
|
|