Deserialize chunks off the main thread.

This commit is contained in:
Joshua Yanovski 2022-07-07 21:06:56 -07:00
parent 6eb26ba6d8
commit 3ba53b3e4c
11 changed files with 240 additions and 124 deletions

1
Cargo.lock generated
View File

@ -6470,6 +6470,7 @@ dependencies = [
"bincode", "bincode",
"byteorder", "byteorder",
"clap 3.1.8", "clap 3.1.8",
"crossbeam-channel",
"hashbrown 0.12.0", "hashbrown 0.12.0",
"image", "image",
"num 0.4.0", "num 0.4.0",

View File

@ -23,6 +23,7 @@ network = { package = "veloren-network", path = "../network", features = ["compr
bincode = "1.3.2" bincode = "1.3.2"
byteorder = "1.3.2" byteorder = "1.3.2"
crossbeam-channel = "0.5"
tokio = { version = "1.14", default-features = false, features = ["rt-multi-thread"] } tokio = { version = "1.14", default-features = false, features = ["rt-multi-thread"] }
quinn = "0.8" quinn = "0.8"
image = { version = "0.24", default-features = false, features = ["png"] } image = { version = "0.24", default-features = false, features = ["png"] }

View File

@ -1,4 +1,5 @@
use authc::AuthClientError; use authc::AuthClientError;
use common_net::msg::DecodeError;
pub use network::{InitProtocolError, NetworkConnectError, NetworkError}; pub use network::{InitProtocolError, NetworkConnectError, NetworkError};
use network::{ParticipantError, StreamError}; use network::{ParticipantError, StreamError};
use specs::error::Error as SpecsError; use specs::error::Error as SpecsError;
@ -24,6 +25,7 @@ pub enum Error {
//TODO: InvalidAlias, //TODO: InvalidAlias,
Other(String), Other(String),
SpecsErr(SpecsError), SpecsErr(SpecsError),
CompressionError(DecodeError),
} }
impl From<SpecsError> for Error { impl From<SpecsError> for Error {
@ -46,6 +48,15 @@ impl From<bincode::Error> for Error {
fn from(err: bincode::Error) -> Self { Self::StreamErr(StreamError::Deserialize(err)) } fn from(err: bincode::Error) -> Self { Self::StreamErr(StreamError::Deserialize(err)) }
} }
impl From<DecodeError> for Error {
fn from(err: DecodeError) -> Self {
match err {
DecodeError::Deserialize(err) => Self::StreamErr(StreamError::Deserialize(err)),
_ => Self::CompressionError(err),
}
}
}
impl From<AuthClientError> for Error { impl From<AuthClientError> for Error {
fn from(err: AuthClientError) -> Self { Self::AuthClientError(err) } fn from(err: AuthClientError) -> Self { Self::AuthClientError(err) }
} }

View File

@ -40,6 +40,7 @@ use common::{
outcome::Outcome, outcome::Outcome,
recipe::{ComponentRecipeBook, RecipeBook}, recipe::{ComponentRecipeBook, RecipeBook},
resources::{PlayerEntity, TimeOfDay}, resources::{PlayerEntity, TimeOfDay},
slowjob::SlowJobPool,
spiral::Spiral2d, spiral::Spiral2d,
terrain::{ terrain::{
block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk, block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk,
@ -66,6 +67,7 @@ use common_net::{
use common_state::State; use common_state::State;
use common_systems::add_local_systems; use common_systems::add_local_systems;
use comp::BuffKind; use comp::BuffKind;
use crossbeam_channel as mpsc;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use image::DynamicImage; use image::DynamicImage;
use network::{ConnectAddr, Network, Participant, Pid, Stream}; use network::{ConnectAddr, Network, Participant, Pid, Stream};
@ -199,6 +201,19 @@ impl Default for WeatherLerp {
} }
} }
/// Deserialized and decompressed terrain updates.
enum TerrainUpdate {
Chunk {
key: Vec2<i32>,
chunk: Option<Arc<TerrainChunk>>,
},
LodZone {
key: Vec2<i32>,
zone: lod::Zone,
},
Block(HashMap<Vec3<i32>, Block>),
}
pub struct Client { pub struct Client {
registered: bool, registered: bool,
presence: Option<PresenceKind>, presence: Option<PresenceKind>,
@ -251,6 +266,8 @@ pub struct Client {
// TODO: move into voxygen // TODO: move into voxygen
loaded_distance: f32, loaded_distance: f32,
terrain_tx: mpsc::Sender<Result<TerrainUpdate, Error>>,
terrain_rx: mpsc::Receiver<Result<TerrainUpdate, Error>>,
pending_chunks: HashMap<Vec2<i32>, Instant>, pending_chunks: HashMap<Vec2<i32>, Instant>,
target_time_of_day: Option<TimeOfDay>, target_time_of_day: Option<TimeOfDay>,
} }
@ -263,6 +280,8 @@ pub struct CharacterList {
pub loading: bool, pub loading: bool,
} }
const TOTAL_PENDING_CHUNKS_LIMIT: usize = 1024;
impl Client { impl Client {
pub async fn new( pub async fn new(
addr: ConnectionArgs, addr: ConnectionArgs,
@ -355,6 +374,8 @@ impl Client {
let mut state = State::client(); let mut state = State::client();
// Client-only components // Client-only components
state.ecs_mut().register::<comp::Last<CharacterState>>(); state.ecs_mut().register::<comp::Last<CharacterState>>();
state.ecs_mut().write_resource::<SlowJobPool>()
.configure("TERRAIN_DESERIALIZING", |n| n / 2);
let entity = state.ecs_mut().apply_entity_package(entity_package); let entity = state.ecs_mut().apply_entity_package(entity_package);
*state.ecs_mut().write_resource() = time_of_day; *state.ecs_mut().write_resource() = time_of_day;
@ -646,6 +667,8 @@ impl Client {
debug!("Initial sync done"); debug!("Initial sync done");
let (terrain_tx, terrain_rx) = mpsc::bounded(TOTAL_PENDING_CHUNKS_LIMIT);
Ok(Self { Ok(Self {
registered: false, registered: false,
presence: None, presence: None,
@ -707,6 +730,8 @@ impl Client {
lod_distance: 4.0, lod_distance: 4.0,
loaded_distance: 0.0, loaded_distance: 0.0,
terrain_tx,
terrain_rx,
pending_chunks: HashMap::new(), pending_chunks: HashMap::new(),
target_time_of_day: None, target_time_of_day: None,
}) })
@ -1827,7 +1852,6 @@ impl Client {
for key in keys.iter() { for key in keys.iter() {
if self.state.terrain().get_key(*key).is_none() { if self.state.terrain().get_key(*key).is_none() {
if !skip_mode && !self.pending_chunks.contains_key(key) { if !skip_mode && !self.pending_chunks.contains_key(key) {
const TOTAL_PENDING_CHUNKS_LIMIT: usize = 1024;
const CURRENT_TICK_PENDING_CHUNKS_LIMIT: usize = 8 * 4; const CURRENT_TICK_PENDING_CHUNKS_LIMIT: usize = 8 * 4;
if self.pending_chunks.len() < TOTAL_PENDING_CHUNKS_LIMIT if self.pending_chunks.len() < TOTAL_PENDING_CHUNKS_LIMIT
&& current_tick_send_chunk_requests && current_tick_send_chunk_requests
@ -2261,29 +2285,47 @@ impl Client {
Ok(()) Ok(())
} }
fn handle_server_terrain_msg(&mut self, msg: ServerGeneral) -> Result<(), Error> { fn handle_server_terrain_msg(msg: ServerGeneral) -> Result<TerrainUpdate, Error> {
prof_span!("handle_server_terrain_mgs"); prof_span!("handle_server_terrain_mgs");
match msg { match msg {
ServerGeneral::TerrainChunkUpdate { key, chunk } => { ServerGeneral::TerrainChunkUpdate { key, chunk } => {
if let Some(chunk) = chunk.ok().and_then(|c| c.to_chunk()) { let chunk = chunk
self.state.insert_chunk(key, Arc::new(chunk)); .ok()
.map(|c| c.to_chunk())
.transpose()?
.map(Arc::new);
return Ok(TerrainUpdate::Chunk { key, chunk });
},
ServerGeneral::LodZoneUpdate { key, zone } => {
return Ok(TerrainUpdate::LodZone { key, zone });
},
ServerGeneral::TerrainBlockUpdates(blocks) => {
let blocks = blocks.decompress()?;
return Ok(TerrainUpdate::Block(blocks));
},
_ => {},
}
return Err(Error::Other("Invalid terrain message".into()));
}
fn handle_terrain_msg(&mut self, msg: TerrainUpdate) {
match msg {
TerrainUpdate::Chunk { key, chunk } => {
if let Some(chunk) = chunk {
self.state.insert_chunk(key, chunk);
} }
self.pending_chunks.remove(&key); self.pending_chunks.remove(&key);
}, },
ServerGeneral::LodZoneUpdate { key, zone } => { TerrainUpdate::LodZone { key, zone } => {
self.lod_zones.insert(key, zone); self.lod_zones.insert(key, zone);
self.lod_last_requested = None; self.lod_last_requested = None;
}, },
ServerGeneral::TerrainBlockUpdates(blocks) => { TerrainUpdate::Block(blocks) => {
if let Some(mut blocks) = blocks.decompress() { blocks.into_iter().for_each(|(pos, block)| {
blocks.drain().for_each(|(pos, block)| { self.state.set_block(pos, block);
self.state.set_block(pos, block); });
}); }
}
},
_ => unreachable!("Not a terrain message"),
} }
Ok(())
} }
fn handle_server_character_screen_msg( fn handle_server_character_screen_msg(
@ -2375,15 +2417,28 @@ impl Client {
} }
while let Some(msg) = self.terrain_stream.try_recv_raw()? { while let Some(msg) = self.terrain_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
let msg = msg.decompress()?; let terrain_tx = self.terrain_tx.clone();
let msg = bincode::deserialize(&msg)?; self
.state
.slow_job_pool()
.spawn("TERRAIN_DESERIALIZING", move || {
let handle_msg = || {
let msg = msg.decompress()?;
let msg = bincode::deserialize(&msg)?;
Self::handle_server_terrain_msg(msg)
};
terrain_tx.send(handle_msg());
});
}
while let Ok(msg) = self.terrain_rx.try_recv() {
let msg = msg?;
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
{ {
if let ServerGeneral::TerrainChunkUpdate { chunk, .. } = &msg { if let TerrainUpdate::Chunk { chunk, .. } = &msg {
terrain_cnt += chunk.as_ref().map(|x| x.approx_len()).unwrap_or(0); terrain_cnt += chunk.as_ref().map(|x| x.approx_len()).unwrap_or(0);
} }
} }
self.handle_server_terrain_msg(msg)?; self.handle_terrain_msg(msg);
} }
if cnt_start == cnt { if cnt_start == cnt {

View File

@ -4,19 +4,55 @@ use common::{
volumes::vol_grid_2d::VolGrid2d, volumes::vol_grid_2d::VolGrid2d,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use image::{ImageBuffer, ImageDecoder, ImageEncoder, Pixel}; use image::{ImageBuffer, ImageDecoder, ImageEncoder, ImageError, Pixel};
use num_traits::cast::FromPrimitive; use num_traits::cast::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
borrow::Cow, borrow::Cow,
fmt::Debug, fmt::Debug,
io::{Read, Write}, io::{Error as IoError, Read, Write},
marker::PhantomData, marker::PhantomData,
}; };
use serde_with::{serde_as, Bytes}; use serde_with::{serde_as, Bytes};
use tracing::warn; use tracing::warn;
use vek::*; use vek::*;
/// Errors that can occur during decoding.
#[derive(Debug)]
pub enum DecodeError {
Deserialize(bincode::Error),
Image(ImageError),
Io(IoError)
}
impl ToString for DecodeError {
fn to_string(&self) -> String {
match self {
Self::Deserialize(error) => error.to_string(),
Self::Image(error) => error.to_string(),
Self::Io(error) => error.to_string(),
}
}
}
impl From<ImageError> for DecodeError {
fn from(error: ImageError) -> Self {
Self::Image(error)
}
}
impl From<IoError> for DecodeError {
fn from(error: IoError) -> Self {
Self::Io(error)
}
}
impl From<bincode::Error> for DecodeError {
fn from(error: bincode::Error) -> Self {
Self::Deserialize(error)
}
}
/// Wrapper for compressed, serialized data (for stuff that doesn't use the /// Wrapper for compressed, serialized data (for stuff that doesn't use the
/// default lz4 compression) /// default lz4 compression)
#[serde_as] #[serde_as]
@ -59,15 +95,14 @@ impl<'a, T: Serialize> CompressedData<'a, T> {
} }
impl<T: for<'a> Deserialize<'a>> CompressedData<'_, T> { impl<T: for<'a> Deserialize<'a>> CompressedData<'_, T> {
pub fn decompress(&self) -> Option<T> { pub fn decompress(&self) -> Result<T, DecodeError> {
if self.compressed { if self.compressed {
let mut uncompressed = Vec::with_capacity(self.data.len()); let mut uncompressed = Vec::with_capacity(self.data.len());
flate2::read::DeflateDecoder::new(&*self.data) flate2::read::DeflateDecoder::new(&*self.data)
.read_to_end(&mut uncompressed) .read_to_end(&mut uncompressed)?;
.ok()?; Ok(bincode::deserialize(&*uncompressed)?)
bincode::deserialize(&*uncompressed).ok()
} else { } else {
bincode::deserialize(&*self.data).ok() Ok(bincode::deserialize(&*self.data)?)
} }
} }
} }
@ -129,6 +164,8 @@ impl PackingFormula for GridLtrPacking {
pub trait VoxelImageEncoding { pub trait VoxelImageEncoding {
type Workspace; type Workspace;
type Output; type Output;
type EncodeError;
fn create(width: u32, height: u32) -> Self::Workspace; fn create(width: u32, height: u32) -> Self::Workspace;
fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>); fn put_solid(&self, ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>);
fn put_sprite( fn put_sprite(
@ -140,25 +177,31 @@ pub trait VoxelImageEncoding {
sprite: SpriteKind, sprite: SpriteKind,
ori: Option<u8>, ori: Option<u8>,
); );
fn finish(ws: &Self::Workspace) -> Option<Self::Output>; fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError>;
} }
pub trait VoxelImageDecoding: VoxelImageEncoding { pub trait VoxelImageDecoding: VoxelImageEncoding {
fn start(ws: &Self::Output) -> Option<Self::Workspace>; fn start(ws: &Self::Output) -> Result<Self::Workspace, DecodeError>;
fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block; fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block;
} }
pub fn image_from_bytes<'a, I: ImageDecoder<'a>, P: 'static + Pixel<Subpixel = u8>>( pub fn image_from_bytes<'a, I: ImageDecoder<'a>, P: 'static + Pixel<Subpixel = u8>>(
decoder: I, decoder: I,
) -> Option<ImageBuffer<P, Vec<u8>>> { ) -> Result<ImageBuffer<P, Vec<u8>>, ImageError> {
let (w, h) = decoder.dimensions(); let (w, h) = decoder.dimensions();
let mut buf = vec![0; decoder.total_bytes() as usize]; let mut buf = vec![0; decoder.total_bytes() as usize];
decoder.read_image(&mut buf).ok()?; decoder.read_image(&mut buf)?;
ImageBuffer::from_raw(w, h, buf) Ok(ImageBuffer::from_raw(w, h, buf).ok_or_else(|| {
IoError::new(
std::io::ErrorKind::WriteZero,
"Could not write full image to buffer; maybe out of memory?"
)
})?)
} }
impl<'a, VIE: VoxelImageEncoding> VoxelImageEncoding for &'a VIE { impl<'a, VIE: VoxelImageEncoding> VoxelImageEncoding for &'a VIE {
type Output = VIE::Output; type Output = VIE::Output;
type EncodeError = VIE::EncodeError;
type Workspace = VIE::Workspace; type Workspace = VIE::Workspace;
fn create(width: u32, height: u32) -> Self::Workspace { VIE::create(width, height) } fn create(width: u32, height: u32) -> Self::Workspace { VIE::create(width, height) }
@ -179,11 +222,11 @@ impl<'a, VIE: VoxelImageEncoding> VoxelImageEncoding for &'a VIE {
(*self).put_sprite(ws, x, y, kind, sprite, ori) (*self).put_sprite(ws, x, y, kind, sprite, ori)
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { VIE::finish(ws) } fn finish(ws: &Self::Workspace) -> Result<Self::Output, VIE::EncodeError> { VIE::finish(ws) }
} }
impl<'a, VIE: VoxelImageDecoding> VoxelImageDecoding for &'a VIE { impl<'a, VIE: VoxelImageDecoding> VoxelImageDecoding for &'a VIE {
fn start(ws: &Self::Output) -> Option<Self::Workspace> { VIE::start(ws) } fn start(ws: &Self::Output) -> Result<Self::Workspace, DecodeError> { VIE::start(ws) }
fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block { fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block {
VIE::get_block(ws, x, y, is_border) VIE::get_block(ws, x, y, is_border)
@ -195,6 +238,7 @@ pub struct QuadPngEncoding<'a, const RESOLUTION_DIVIDER: u32>(pub PhantomData<&'
impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> { impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> {
type Output = CompressedData<'a, (Vec<u8>, [usize; 3])>; type Output = CompressedData<'a, (Vec<u8>, [usize; 3])>;
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
@ -232,20 +276,19 @@ impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> {
ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)])); ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3]; let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| -> Result<_, Self::EncodeError> {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Rle, CompressionType::Rle,
FilterType::Up, FilterType::Up,
); );
png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8) png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)?;
.ok()?;
indices[i] = buf.len(); indices[i] = buf.len();
Some(()) Ok(())
}; };
f(&ws.0, 0)?; f(&ws.0, 0)?;
f(&ws.1, 1)?; f(&ws.1, 1)?;
@ -262,11 +305,10 @@ impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> {
ws.3.width(), ws.3.width(),
ws.3.height(), ws.3.height(),
image::ColorType::Rgb8, image::ColorType::Rgb8,
) )?;
.ok()?;
} }
Some(CompressedData::compress(&(buf, indices), 4)) Ok(CompressedData::compress(&(buf, indices), 4))
} }
} }
@ -323,7 +365,7 @@ const fn gen_lanczos_lookup<const N: u32, const R: u32>(
} }
impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<'_, N> { impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<'_, N> {
fn start(data: &Self::Output) -> Option<Self::Workspace> { fn start(data: &Self::Output) -> Result<Self::Workspace, DecodeError> {
use image::codecs::png::PngDecoder; use image::codecs::png::PngDecoder;
let (quad, indices) = data.decompress()?; let (quad, indices) = data.decompress()?;
let ranges: [_; 4] = [ let ranges: [_; 4] = [
@ -332,11 +374,11 @@ impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<'_, N> {
indices[1]..indices[2], indices[1]..indices[2],
indices[2]..quad.len(), indices[2]..quad.len(),
]; ];
let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()]).ok()?)?; let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()])?)?;
let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()]).ok()?)?; let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()])?)?;
let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()]).ok()?)?; let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()])?)?;
let d = image_from_bytes(PngDecoder::new(&quad[ranges[3].clone()]).ok()?)?; let d = image_from_bytes(PngDecoder::new(&quad[ranges[3].clone()])?)?;
Some((a, b, c, d)) Ok((a, b, c, d))
} }
fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block { fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block {
@ -469,6 +511,7 @@ pub struct TriPngEncoding<'a, const AVERAGE_PALETTE: bool>(pub PhantomData<&'a (
impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a, AVERAGE_PALETTE> { impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a, AVERAGE_PALETTE> {
type Output = CompressedData<'a, (Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>; type Output = CompressedData<'a, (Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>;
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
@ -508,20 +551,19 @@ impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a,
ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)])); ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3]; let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| -> Result<_, Self::EncodeError> {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Rle, CompressionType::Rle,
FilterType::Up, FilterType::Up,
); );
png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8) png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)?;
.ok()?;
indices[i] = buf.len(); indices[i] = buf.len();
Some(()) Ok(())
}; };
f(&ws.0, 0)?; f(&ws.0, 0)?;
f(&ws.1, 1)?; f(&ws.1, 1)?;
@ -550,12 +592,12 @@ impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a,
Vec::new() Vec::new()
}; };
Some(CompressedData::compress(&(buf, palette, indices), 4)) Ok(CompressedData::compress(&(buf, palette, indices), 4))
} }
} }
impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<'_, AVERAGE_PALETTE> { impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<'_, AVERAGE_PALETTE> {
fn start(data: &Self::Output) -> Option<Self::Workspace> { fn start(data: &Self::Output) -> Result<Self::Workspace, DecodeError> {
use image::codecs::png::PngDecoder; use image::codecs::png::PngDecoder;
let (quad, palette, indices) = data.decompress()?; let (quad, palette, indices) = data.decompress()?;
let ranges: [_; 3] = [ let ranges: [_; 3] = [
@ -563,9 +605,9 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<'_, AVER
indices[0]..indices[1], indices[0]..indices[1],
indices[1]..indices[2], indices[1]..indices[2],
]; ];
let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()]).ok()?)?; let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()])?)?;
let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()]).ok()?)?; let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()])?)?;
let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()]).ok()?)?; let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()])?)?;
let mut d: HashMap<_, HashMap<_, _>> = HashMap::new(); let mut d: HashMap<_, HashMap<_, _>> = HashMap::new();
if AVERAGE_PALETTE { if AVERAGE_PALETTE {
for i in 0..=255 { for i in 0..=255 {
@ -578,7 +620,7 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<'_, AVER
} }
} }
Some((a, b, c, d)) Ok((a, b, c, d))
} }
fn get_block(ws: &Self::Workspace, x: u32, y: u32, _: bool) -> Block { fn get_block(ws: &Self::Workspace, x: u32, y: u32, _: bool) -> Block {
@ -681,7 +723,7 @@ pub fn image_terrain_chonk<S: RectVolSize, Storage: core::ops::DerefMut<Target=V
vie: &VIE, vie: &VIE,
packing: P, packing: P,
chonk: &Chonk<Block, Storage, S, M>, chonk: &Chonk<Block, Storage, S, M>,
) -> Option<VIE::Output> { ) -> Result<VIE::Output, VIE::EncodeError> {
image_terrain( image_terrain(
vie, vie,
packing, packing,
@ -701,7 +743,7 @@ pub fn image_terrain_volgrid<
vie: &VIE, vie: &VIE,
packing: P, packing: P,
volgrid: &VolGrid2d<Chonk<Block, Storage, S, M>>, volgrid: &VolGrid2d<Chonk<Block, Storage, S, M>>,
) -> Option<VIE::Output> { ) -> Result<VIE::Output, VIE::EncodeError> {
let mut lo = Vec3::broadcast(i32::MAX); let mut lo = Vec3::broadcast(i32::MAX);
let mut hi = Vec3::broadcast(i32::MIN); let mut hi = Vec3::broadcast(i32::MIN);
for (pos, chonk) in volgrid.iter() { for (pos, chonk) in volgrid.iter() {
@ -727,7 +769,7 @@ pub fn image_terrain<
vol: &V, vol: &V,
lo: Vec3<u32>, lo: Vec3<u32>,
hi: Vec3<u32>, hi: Vec3<u32>,
) -> Option<VIE::Output> { ) -> Result<VIE::Output, VIE::EncodeError> {
let dims = Vec3::new( let dims = Vec3::new(
hi.x.wrapping_sub(lo.x), hi.x.wrapping_sub(lo.x),
hi.y.wrapping_sub(lo.y), hi.y.wrapping_sub(lo.y),
@ -782,7 +824,7 @@ pub fn write_image_terrain<
data: &VIE::Output, data: &VIE::Output,
lo: Vec3<u32>, lo: Vec3<u32>,
hi: Vec3<u32>, hi: Vec3<u32>,
) -> Option<()> { ) -> Result<(), DecodeError> {
let ws = VIE::start(data)?; let ws = VIE::start(data)?;
let dims = Vec3::new( let dims = Vec3::new(
hi.x.wrapping_sub(lo.x), hi.x.wrapping_sub(lo.x),
@ -805,7 +847,7 @@ pub fn write_image_terrain<
} }
} }
} }
Some(()) Ok(())
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -824,16 +866,14 @@ pub struct WireChonk<VIE: VoxelImageEncoding, P: PackingFormula, M: Clone, S: Re
impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone, S: RectVolSize> impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone, S: RectVolSize>
WireChonk<VIE, P, M, S> WireChonk<VIE, P, M, S>
{ {
pub fn from_chonk<Storage: core::ops::DerefMut<Target=Vec<Block>>>(vie: VIE, packing: P, chonk: &Chonk<Block, Storage, S, M>) -> Option<Self> { pub fn from_chonk<Storage: core::ops::DerefMut<Target=Vec<Block>>>(vie: VIE, packing: P, chonk: &Chonk<Block, Storage, S, M>) -> Result<Self, VIE::EncodeError> {
let data = image_terrain_chonk(&vie, packing, chonk)?; let data = image_terrain_chonk(&vie, packing, chonk)?;
Some(Self { Ok(Self {
zmin: chonk.get_min_z(), zmin: chonk.get_min_z(),
zmax: chonk.get_max_z(), zmax: chonk.get_max_z(),
data, data,
below: *chonk below: *chonk.below(),
.get(Vec3::new(0, 0, chonk.get_min_z().saturating_sub(1))) above: *chonk.above(),
.ok()?,
above: *chonk.get(Vec3::new(0, 0, chonk.get_max_z() + 1)).ok()?,
meta: chonk.meta().clone(), meta: chonk.meta().clone(),
vie, vie,
packing, packing,
@ -841,7 +881,7 @@ impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone,
}) })
} }
pub fn to_chonk<Storage: Clone + core::ops::DerefMut<Target=Vec<Block>> + From<Vec<Block>>>(&self) -> Option<Chonk<Block, Storage, S, M>> { pub fn to_chonk<Storage: Clone + core::ops::DerefMut<Target=Vec<Block>> + From<Vec<Block>>>(&self) -> Result<Chonk<Block, Storage, S, M>, DecodeError> {
let mut chonk = Chonk::new(self.zmin, self.below, self.above, self.meta.clone()); let mut chonk = Chonk::new(self.zmin, self.below, self.above, self.meta.clone());
write_image_terrain( write_image_terrain(
&self.vie, &self.vie,
@ -851,6 +891,6 @@ impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone,
Vec3::new(0, 0, self.zmin as u32), Vec3::new(0, 0, self.zmin as u32),
Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, self.zmax as u32), Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, self.zmax as u32),
)?; )?;
Some(chonk) Ok(chonk)
} }
} }

View File

@ -8,7 +8,7 @@ pub mod world_msg;
pub use self::{ pub use self::{
client::{ClientGeneral, ClientMsg, ClientRegister, ClientType}, client::{ClientGeneral, ClientMsg, ClientRegister, ClientType},
compression::{ compression::{
CompressedData, GridLtrPacking, PackingFormula, QuadPngEncoding, TriPngEncoding, CompressedData, DecodeError, GridLtrPacking, PackingFormula, QuadPngEncoding, TriPngEncoding,
VoxelImageEncoding, WidePacking, WireChonk, VoxelImageEncoding, WidePacking, WireChonk,
}, },
ecs_packet::EcsCompPacket, ecs_packet::EcsCompPacket,

View File

@ -1,5 +1,5 @@
use super::{ use super::{
world_msg::EconomyInfo, ClientType, CompressedData, EcsCompPacket, PingMsg, QuadPngEncoding, world_msg::EconomyInfo, ClientType, CompressedData, DecodeError, EcsCompPacket, PingMsg, QuadPngEncoding,
TriPngEncoding, WidePacking, WireChonk, TriPngEncoding, WidePacking, WireChonk,
}; };
use crate::sync; use crate::sync;
@ -104,24 +104,26 @@ impl SerializedTerrainChunk<'_> {
} }
pub fn quadpng(chunk: &TerrainChunk) -> Self { pub fn quadpng(chunk: &TerrainChunk) -> Self {
if let Some(wc) = WireChonk::from_chonk(QuadPngEncoding(PhantomData), WidePacking(), chunk) { match WireChonk::from_chonk(QuadPngEncoding(PhantomData), WidePacking(), chunk) {
Self::QuadPng(wc) Ok(wc) => Self::QuadPng(wc),
} else { Err(err) => {
warn!("Image encoding failure occurred, falling back to deflate"); warn!("Image encoding failure occurred, falling back to deflate: {}", err);
Self::deflate(chunk) Self::deflate(chunk)
}
} }
} }
pub fn tripng(chunk: &TerrainChunk) -> Self { pub fn tripng(chunk: &TerrainChunk) -> Self {
if let Some(wc) = WireChonk::from_chonk(TriPngEncoding(PhantomData), WidePacking(), chunk) { match WireChonk::from_chonk(TriPngEncoding(PhantomData), WidePacking(), chunk) {
Self::TriPng(wc) Ok(wc) => Self::TriPng(wc),
} else { Err(err) => {
warn!("Image encoding failure occurred, falling back to deflate"); warn!("Image encoding failure occurred, falling back to deflate: {}", err);
Self::deflate(chunk) Self::deflate(chunk)
},
} }
} }
pub fn to_chunk(&self) -> Option<TerrainChunk> { pub fn to_chunk(&self) -> Result<TerrainChunk, DecodeError> {
match self { match self {
Self::DeflatedChonk(chonk) => chonk.decompress(), Self::DeflatedChonk(chonk) => chonk.decompress(),
Self::QuadPng(wc) => wc.to_chonk(), Self::QuadPng(wc) => wc.to_chonk(),

View File

@ -84,6 +84,10 @@ impl<V, Storage: core::ops::DerefMut<Target=Vec<V>>, S: RectVolSize, M: Clone> C
} }
} }
pub fn below(&self) -> &V { &self.below }
pub fn above(&self) -> &V { &self.above }
pub fn meta(&self) -> &M { &self.meta } pub fn meta(&self) -> &M { &self.meta }
#[inline] #[inline]

View File

@ -432,6 +432,7 @@ fn get_client_msg_error(
localization.get("main.login.authentication_error"), localization.get("main.login.authentication_error"),
e e
), ),
Error::CompressionError(e) => net_error(e.to_string(), mismatched_server_info),
Error::Kicked(e) => e, Error::Kicked(e) => e,
Error::TooManyPlayers => localization.get("main.login.server_full").into(), Error::TooManyPlayers => localization.get("main.login.server_full").into(),
Error::AuthServerNotTrusted => { Error::AuthServerNotTrusted => {

View File

@ -9,13 +9,13 @@ use common::{
}, },
}; };
use common_net::msg::compression::{ use common_net::msg::compression::{
image_from_bytes, image_terrain_chonk, image_terrain_volgrid, CompressedData, GridLtrPacking, image_from_bytes, image_terrain_chonk, image_terrain_volgrid, CompressedData, DecodeError, GridLtrPacking,
PackingFormula, QuadPngEncoding, TriPngEncoding, VoxelImageDecoding, VoxelImageEncoding, PackingFormula, QuadPngEncoding, TriPngEncoding, VoxelImageDecoding, VoxelImageEncoding,
WidePacking, WidePacking,
}; };
use core::marker::PhantomData; use core::marker::PhantomData;
use hashbrown::HashMap; use hashbrown::HashMap;
use image::{ImageBuffer, ImageEncoder}; use image::{ImageBuffer, ImageEncoder, ImageError};
use num_traits::cast::FromPrimitive; use num_traits::cast::FromPrimitive;
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -166,6 +166,7 @@ pub struct PngEncoding;
impl VoxelImageEncoding for PngEncoding { impl VoxelImageEncoding for PngEncoding {
type Output = Vec<u8>; type Output = Vec<u8>;
type EncodeError = ImageError;
type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>; type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>;
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
@ -193,7 +194,7 @@ impl VoxelImageEncoding for PngEncoding {
); );
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut buf = Vec::new(); let mut buf = Vec::new();
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
@ -206,9 +207,8 @@ impl VoxelImageEncoding for PngEncoding {
ws.width(), ws.width(),
ws.height(), ws.height(),
image::ColorType::Rgba8, image::ColorType::Rgba8,
) )?;
.ok()?; Ok(buf)
Some(buf)
} }
} }
@ -217,6 +217,7 @@ pub struct JpegEncoding;
impl VoxelImageEncoding for JpegEncoding { impl VoxelImageEncoding for JpegEncoding {
type Output = Vec<u8>; type Output = Vec<u8>;
type EncodeError = ImageError;
type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>; type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>;
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
@ -240,11 +241,11 @@ impl VoxelImageEncoding for JpegEncoding {
ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255])); ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(ws).ok()?; jpeg.encode_image(ws)?;
Some(buf) Ok(buf)
} }
} }
@ -253,6 +254,7 @@ pub struct MixedEncoding;
impl VoxelImageEncoding for MixedEncoding { impl VoxelImageEncoding for MixedEncoding {
type Output = (Vec<u8>, [usize; 3]); type Output = (Vec<u8>, [usize; 3]);
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
@ -291,33 +293,32 @@ impl VoxelImageEncoding for MixedEncoding {
ws.3.put_pixel(x, y, image::Rgb([0; 3])); ws.3.put_pixel(x, y, image::Rgb([0; 3]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3]; let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| -> Result<_, Self::EncodeError> {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Rle, CompressionType::Rle,
FilterType::Up, FilterType::Up,
); );
png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8) png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)?;
.ok()?;
indices[i] = buf.len(); indices[i] = buf.len();
Some(()) Ok(())
}; };
f(&ws.0, 0)?; f(&ws.0, 0)?;
f(&ws.1, 1)?; f(&ws.1, 1)?;
f(&ws.2, 2)?; f(&ws.2, 2)?;
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 10); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 10);
jpeg.encode_image(&ws.3).ok()?; jpeg.encode_image(&ws.3)?;
Some((buf, indices)) Ok((buf, indices))
} }
} }
impl VoxelImageDecoding for MixedEncoding { impl VoxelImageDecoding for MixedEncoding {
fn start((quad, indices): &Self::Output) -> Option<Self::Workspace> { fn start((quad, indices): &Self::Output) -> Result<Self::Workspace, DecodeError> {
use image::codecs::{jpeg::JpegDecoder, png::PngDecoder}; use image::codecs::{jpeg::JpegDecoder, png::PngDecoder};
let ranges: [_; 4] = [ let ranges: [_; 4] = [
0..indices[0], 0..indices[0],
@ -325,11 +326,11 @@ impl VoxelImageDecoding for MixedEncoding {
indices[1]..indices[2], indices[1]..indices[2],
indices[2]..quad.len(), indices[2]..quad.len(),
]; ];
let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()]).ok()?)?; let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()])?)?;
let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()]).ok()?)?; let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()])?)?;
let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()]).ok()?)?; let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()])?)?;
let d = image_from_bytes(JpegDecoder::new(&quad[ranges[3].clone()]).ok()?)?; let d = image_from_bytes(JpegDecoder::new(&quad[ranges[3].clone()])?)?;
Some((a, b, c, d)) Ok((a, b, c, d))
} }
fn get_block(ws: &Self::Workspace, x: u32, y: u32, _: bool) -> Block { fn get_block(ws: &Self::Workspace, x: u32, y: u32, _: bool) -> Block {
@ -366,6 +367,7 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
usize, usize,
CompressedData<'static, HashMap<Vec2<u32>, (SpriteKind, u8)>>, CompressedData<'static, HashMap<Vec2<u32>, (SpriteKind, u8)>>,
); );
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
image::ImageBuffer<image::Luma<u8>, Vec<u8>>, image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
image::ImageBuffer<image::Rgb<u8>, Vec<u8>>, image::ImageBuffer<image::Rgb<u8>, Vec<u8>>,
@ -399,7 +401,7 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
ws.2.insert(Vec2::new(x, y), (sprite, ori.unwrap_or(0))); ws.2.insert(Vec2::new(x, y), (sprite, ori.unwrap_or(0)));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
@ -412,12 +414,11 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
ws.0.width(), ws.0.width(),
ws.0.height(), ws.0.height(),
image::ColorType::L8, image::ColorType::L8,
) )?;
.ok()?;
let index = buf.len(); let index = buf.len();
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(&ws.1).ok()?; jpeg.encode_image(&ws.1)?;
Some((buf, index, CompressedData::compress(&ws.2, 4))) Ok((buf, index, CompressedData::compress(&ws.2, 4)))
} }
} }
@ -426,6 +427,7 @@ pub struct MixedEncodingDenseSprites;
impl VoxelImageEncoding for MixedEncodingDenseSprites { impl VoxelImageEncoding for MixedEncodingDenseSprites {
type Output = (Vec<u8>, [usize; 3]); type Output = (Vec<u8>, [usize; 3]);
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
Vec<u8>, Vec<u8>,
@ -462,20 +464,19 @@ impl VoxelImageEncoding for MixedEncodingDenseSprites {
ws.3.put_pixel(x, y, image::Rgb([0; 3])); ws.3.put_pixel(x, y, image::Rgb([0; 3]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3]; let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| -> Result<_, Self::EncodeError> {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Fast, CompressionType::Fast,
FilterType::Up, FilterType::Up,
); );
png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8) png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)?;
.ok()?;
indices[i] = buf.len(); indices[i] = buf.len();
Some(()) Ok(())
}; };
f(&ws.0, 0)?; f(&ws.0, 0)?;
let mut g = |x: &[u8], i| { let mut g = |x: &[u8], i| {
@ -487,8 +488,8 @@ impl VoxelImageEncoding for MixedEncodingDenseSprites {
g(&ws.2, 2); g(&ws.2, 2);
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(&ws.3).ok()?; jpeg.encode_image(&ws.3)?;
Some((buf, indices)) Ok((buf, indices))
} }
} }
@ -592,6 +593,7 @@ pub struct PaletteEncoding<'a, NN: NearestNeighbor, const N: u32>(&'a HashMap<Bl
impl<'a, NN: NearestNeighbor, const N: u32> VoxelImageEncoding for PaletteEncoding<'a, NN, N> { impl<'a, NN: NearestNeighbor, const N: u32> VoxelImageEncoding for PaletteEncoding<'a, NN, N> {
type Output = CompressedData<'a, (Vec<u8>, [usize; 4])>; type Output = CompressedData<'a, (Vec<u8>, [usize; 4])>;
type EncodeError = ImageError;
type Workspace = ( type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
@ -628,27 +630,26 @@ impl<'a, NN: NearestNeighbor, const N: u32> VoxelImageEncoding for PaletteEncodi
ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)])); ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)]));
} }
fn finish(ws: &Self::Workspace) -> Option<Self::Output> { fn finish(ws: &Self::Workspace) -> Result<Self::Output, Self::EncodeError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 4]; let mut indices = [0; 4];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| -> Result<_, Self::EncodeError> {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Rle, CompressionType::Rle,
FilterType::Up, FilterType::Up,
); );
png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8) png.write_image(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)?;
.ok()?;
indices[i] = buf.len(); indices[i] = buf.len();
Some(()) Ok(())
}; };
f(&ws.0, 0)?; f(&ws.0, 0)?;
f(&ws.1, 1)?; f(&ws.1, 1)?;
f(&ws.2, 2)?; f(&ws.2, 2)?;
f(&ws.3, 3)?; f(&ws.3, 3)?;
Some(CompressedData::compress(&(buf, indices), 1)) Ok(CompressedData::compress(&(buf, indices), 1))
} }
} }

View File

@ -1347,7 +1347,7 @@ impl SiteKind {
}, },
SiteKind::Citadel => (-0.3..0.7).contains(&chunk.temp) && chunk.tree_density < 0.4, SiteKind::Citadel => (-0.3..0.7).contains(&chunk.temp) && chunk.tree_density < 0.4,
SiteKind::GiantTree/* | SiteKind::Tree*/ => { SiteKind::GiantTree/* | SiteKind::Tree*/ => {
chunk.tree_density > 0.4 && (-0.3..0.4).contains(&chunk.temp) chunk.tree_density > 0.4/* && (-0.3..0.4).contains(&chunk.temp) */
}, },
SiteKind::CliffTown => { SiteKind::CliffTown => {
(-0.6..0.4).contains(&chunk.temp) (-0.6..0.4).contains(&chunk.temp)