use std::io::{self, Read, Write};
use swarmkit_sailing::spherical::Wind;
use crate::wind_map::BakedWindMap;
const MAGIC: [u8; 8] = *b"WBAKED01";
const VERSION: u32 = 1;
const ZSTD_LEVEL: i32 = 11;
const HEADER_BYTES: usize = 76;
const BYTES_PER_CELL: usize = 16;
#[derive(Debug)]
#[non_exhaustive]
pub enum EncodeError {
Io(io::Error),
}
impl std::fmt::Display for EncodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for EncodeError {}
impl From<io::Error> for EncodeError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DecodeError {
BadMagic,
UnsupportedVersion(u32),
BadDimensions { nx: u64, ny: u64, nt: u64 },
Io(io::Error),
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadMagic => f.write_str("not a bywind baked-cache file (bad magic)"),
Self::UnsupportedVersion(v) => write!(f, "unsupported baked-cache version: {v}"),
Self::BadDimensions { nx, ny, nt } => {
write!(f, "invalid grid dimensions: nx={nx}, ny={ny}, nt={nt}")
}
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for DecodeError {}
impl From<io::Error> for DecodeError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub fn encode<W: Write>(map: &BakedWindMap, writer: W) -> Result<(), EncodeError> {
let mut writer = writer;
write_header(&mut writer, map)?;
let mut zenc = zstd::stream::write::Encoder::new(writer, ZSTD_LEVEL)?;
let workers: u32 = std::thread::available_parallelism()
.ok()
.and_then(|n| u32::try_from(n.get()).ok())
.unwrap_or(1);
if let Err(e) = zenc.multithread(workers) {
log::warn!("zstd multithread setup failed (continuing single-threaded): {e}");
}
let mut buf = [0u8; BYTES_PER_CELL];
for v in &map.grid {
buf[..8].copy_from_slice(&v.east_mps.to_le_bytes());
buf[8..].copy_from_slice(&v.north_mps.to_le_bytes());
zenc.write_all(&buf)?;
}
zenc.finish()?;
Ok(())
}
pub fn decode<R: Read>(reader: R) -> Result<BakedWindMap, DecodeError> {
let mut reader = reader;
let header = read_header(&mut reader)?;
let total_cells = (header.nx as usize)
.checked_mul(header.ny as usize)
.and_then(|p| p.checked_mul(header.nt as usize))
.ok_or(DecodeError::BadDimensions {
nx: header.nx,
ny: header.ny,
nt: header.nt,
})?;
let payload_bytes =
total_cells
.checked_mul(BYTES_PER_CELL)
.ok_or(DecodeError::BadDimensions {
nx: header.nx,
ny: header.ny,
nt: header.nt,
})?;
let mut zdec = zstd::stream::read::Decoder::new(reader)?;
let mut bytes = vec![0u8; payload_bytes];
zdec.read_exact(&mut bytes)?;
let mut grid: Vec<Wind> = Vec::with_capacity(total_cells);
for chunk in bytes.chunks_exact(BYTES_PER_CELL) {
grid.push(Wind::new(
read_f64_le(&chunk[..8]),
read_f64_le(&chunk[8..]),
));
}
Ok(BakedWindMap {
grid,
nx: header.nx as usize,
ny: header.ny as usize,
nt: header.nt as usize,
x_min: header.x_min,
y_min: header.y_min,
step: header.step,
t_step_seconds: header.t_step_seconds,
crossfade_seconds: 5.0 * header.t_step_seconds,
coord_scale: header.coord_scale,
})
}
struct Header {
nx: u64,
ny: u64,
nt: u64,
x_min: f64,
y_min: f64,
step: f64,
t_step_seconds: f64,
coord_scale: f64,
}
fn write_header<W: Write>(writer: &mut W, map: &BakedWindMap) -> io::Result<()> {
let mut buf = [0u8; HEADER_BYTES];
buf[0..8].copy_from_slice(&MAGIC);
buf[8..12].copy_from_slice(&VERSION.to_le_bytes());
buf[12..20].copy_from_slice(&(map.nx as u64).to_le_bytes());
buf[20..28].copy_from_slice(&(map.ny as u64).to_le_bytes());
buf[28..36].copy_from_slice(&(map.nt as u64).to_le_bytes());
buf[36..44].copy_from_slice(&map.x_min.to_le_bytes());
buf[44..52].copy_from_slice(&map.y_min.to_le_bytes());
buf[52..60].copy_from_slice(&map.step.to_le_bytes());
buf[60..68].copy_from_slice(&map.t_step_seconds.to_le_bytes());
buf[68..76].copy_from_slice(&map.coord_scale.to_le_bytes());
writer.write_all(&buf)
}
fn read_header<R: Read>(reader: &mut R) -> Result<Header, DecodeError> {
let mut buf = [0u8; HEADER_BYTES];
reader.read_exact(&mut buf)?;
if buf[0..8] != MAGIC {
return Err(DecodeError::BadMagic);
}
let version = read_u32_le(&buf[8..12]);
if version != VERSION {
return Err(DecodeError::UnsupportedVersion(version));
}
Ok(Header {
nx: read_u64_le(&buf[12..20]),
ny: read_u64_le(&buf[20..28]),
nt: read_u64_le(&buf[28..36]),
x_min: read_f64_le(&buf[36..44]),
y_min: read_f64_le(&buf[44..52]),
step: read_f64_le(&buf[52..60]),
t_step_seconds: read_f64_le(&buf[60..68]),
coord_scale: read_f64_le(&buf[68..76]),
})
}
fn read_u32_le(slice: &[u8]) -> u32 {
let bytes: [u8; 4] = slice.try_into().expect("4-byte slice required");
u32::from_le_bytes(bytes)
}
fn read_u64_le(slice: &[u8]) -> u64 {
let bytes: [u8; 8] = slice.try_into().expect("8-byte slice required");
u64::from_le_bytes(bytes)
}
fn read_f64_le(slice: &[u8]) -> f64 {
let bytes: [u8; 8] = slice.try_into().expect("8-byte slice required");
f64::from_le_bytes(bytes)
}