use std::io::Read;
use log::debug;
use arrow2::{
array::StructArray,
io::ipc::read::{read_stream_metadata, StreamReader, StreamState},
};
use crate::{
frame::{immutable::Frame, mutable::Frame as MutableFrame},
game::{self, immutable::Game, port_occupancy},
io::{expect_bytes, peppi, slippi, Result},
};
type JsMap = serde_json::Map<String, serde_json::Value>;
#[derive(Clone, Debug, Default)]
pub struct Opts {
pub skip_frames: bool,
}
fn read_arrow_frames<R: Read>(mut r: R, version: slippi::Version) -> Result<Frame> {
expect_bytes(&mut r, &[65, 82, 82, 79, 87, 49, 0, 0])?;
let metadata = read_stream_metadata(&mut r)?;
let reader = StreamReader::new(r, metadata, None);
let mut frame: Option<Frame> = None;
for result in reader {
match result? {
StreamState::Some(chunk) => match frame {
None => {
let f = chunk.arrays()[0]
.as_any()
.downcast_ref::<StructArray>()
.expect("expected a `StructArray`");
frame = Some(Frame::from_struct_array(f.clone(), version))
}
Some(_) => return Err(err!("multiple batches")),
},
StreamState::Waiting => std::thread::sleep(std::time::Duration::from_millis(1000)),
}
}
match frame {
Some(f) => Ok(f),
_ => Err(err!("no batches")),
}
}
fn read_peppi_start<R: Read>(mut r: R) -> Result<game::Start> {
let mut buf = Vec::new();
r.read_to_end(&mut buf)?;
slippi::de::game_start(&mut &buf[..])
}
fn read_peppi_end<R: Read>(mut r: R) -> Result<game::End> {
let mut buf = Vec::new();
r.read_to_end(&mut buf)?;
slippi::de::game_end(&mut &buf[..])
}
fn read_peppi_metadata<R: Read>(r: R) -> Result<JsMap> {
let json_object: serde_json::Value = serde_json::from_reader(r)?;
match json_object {
serde_json::Value::Object(map) => Ok(map),
obj => Err(err!("expected map, got: {:?}", obj)),
}
}
fn read_peppi_gecko_codes<R: Read>(mut r: R) -> Result<game::GeckoCodes> {
let mut actual_size = [0; 4];
r.read_exact(&mut actual_size)?;
let mut bytes = Vec::new();
r.read_to_end(&mut bytes)?;
Ok(game::GeckoCodes {
actual_size: u32::from_le_bytes(actual_size),
bytes: bytes,
})
}
pub fn read<R: Read>(r: R, opts: Option<&Opts>) -> Result<Game> {
let mut start: Option<game::Start> = None;
let mut end: Option<game::End> = None;
let mut metadata: Option<JsMap> = None;
let mut gecko_codes: Option<game::GeckoCodes> = None;
let mut frames: Option<Frame> = None;
let mut peppi: Option<peppi::Peppi> = None;
for entry in tar::Archive::new(r).entries()? {
let file = entry?;
let path = file.path()?;
debug!("processing file: {}", path.display());
match path.file_name().and_then(|n| n.to_str()) {
Some("peppi.json") => {
let p: peppi::Peppi = serde_json::from_reader::<_, peppi::Peppi>(file)?;
super::assert_current_version(p.version)?;
peppi = Some(p);
}
Some("start.raw") => start = Some(read_peppi_start(file)?),
Some("end.raw") => end = Some(read_peppi_end(file)?),
Some("metadata.json") => metadata = Some(read_peppi_metadata(file)?),
Some("gecko_codes.raw") => gecko_codes = Some(read_peppi_gecko_codes(file)?),
Some("frames.arrow") => {
let version = start
.as_ref()
.map(|s| s.slippi.version)
.ok_or(err!("no start"))?;
frames = Some(match opts.map_or(false, |o| o.skip_frames) {
true => {
let start = start.as_ref().ok_or(err!("missing start"))?;
MutableFrame::with_capacity(0, start.slippi.version, &port_occupancy(start))
.into()
}
_ => read_arrow_frames(file, version)?,
});
break;
}
_ => debug!("=> skipping"),
};
}
let peppi = peppi.ok_or(err!("missing peppi"))?;
Ok(Game {
metadata: metadata,
start: start.ok_or(err!("missing start"))?,
end: end,
gecko_codes: gecko_codes,
frames: frames.ok_or(err!("missing frames"))?,
hash: peppi.slp_hash,
quirks: peppi.quirks,
})
}