Turning "full" chunks back on, deserialization improvements.

This commit is contained in:
Joshua Yanovski 2022-06-29 21:52:04 -07:00
parent 9c4b03482d
commit ad5bcf3cd8
22 changed files with 482 additions and 124 deletions

47
Cargo.lock generated
View File

@ -1403,12 +1403,12 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e92cb285610dd935f60ee8b4d62dd1988bd12b7ea50579bd6a138201525318e" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [ dependencies = [
"darling_core 0.13.2", "darling_core 0.13.4",
"darling_macro 0.13.2", "darling_macro 0.13.4",
] ]
[[package]] [[package]]
@ -1427,9 +1427,9 @@ dependencies = [
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c29e95ab498b18131ea460b2c0baa18cbf041231d122b0b7bfebef8c8e88989" checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [ dependencies = [
"fnv", "fnv",
"ident_case", "ident_case",
@ -1452,11 +1452,11 @@ dependencies = [
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b21dd6b221dd547528bd6fb15f1a3b7ab03b9a06f76bff288a8c629bcfbe7f0e" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [ dependencies = [
"darling_core 0.13.2", "darling_core 0.13.4",
"quote 1.0.17", "quote 1.0.17",
"syn 1.0.90", "syn 1.0.90",
] ]
@ -1683,7 +1683,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c594871f94ab3a00434cb09f03067c92fa2ece4cc657d58ba402e8377cd85a3" checksum = "5c594871f94ab3a00434cb09f03067c92fa2ece4cc657d58ba402e8377cd85a3"
dependencies = [ dependencies = [
"darling 0.13.2", "darling 0.13.4",
"proc-macro-crate 1.1.3", "proc-macro-crate 1.1.3",
"proc-macro2 1.0.36", "proc-macro2 1.0.36",
"quote 1.0.17", "quote 1.0.17",
@ -3584,7 +3584,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
dependencies = [ dependencies = [
"darling 0.13.2", "darling 0.13.4",
"proc-macro-crate 1.1.3", "proc-macro-crate 1.1.3",
"proc-macro2 1.0.36", "proc-macro2 1.0.36",
"quote 1.0.17", "quote 1.0.17",
@ -5454,6 +5454,28 @@ dependencies = [
"syn 1.0.90", "syn 1.0.90",
] ]
[[package]]
name = "serde_with"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
dependencies = [
"serde",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
dependencies = [
"darling 0.13.4",
"proc-macro2 1.0.36",
"quote 1.0.17",
"syn 1.0.90",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.6.1" version = "0.6.1"
@ -6521,6 +6543,7 @@ version = "0.12.0"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"authc", "authc",
"bincode",
"byteorder", "byteorder",
"clap 3.1.8", "clap 3.1.8",
"hashbrown 0.11.2", "hashbrown 0.11.2",
@ -6580,6 +6603,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_with",
"sha2", "sha2",
"slab", "slab",
"slotmap 1.0.6", "slotmap 1.0.6",
@ -6654,6 +6678,7 @@ dependencies = [
"image", "image",
"num-traits", "num-traits",
"serde", "serde",
"serde_with",
"specs", "specs",
"specs-idvs", "specs-idvs",
"sum_type", "sum_type",

View File

@ -21,6 +21,7 @@ common-systems = { package = "veloren-common-systems", path = "../common/systems
common-net = { package = "veloren-common-net", path = "../common/net" } common-net = { package = "veloren-common-net", path = "../common/net" }
network = { package = "veloren-network", path = "../network", features = ["compression","quic"], default-features = false } network = { package = "veloren-network", path = "../network", features = ["compression","quic"], default-features = false }
bincode = "1.3.2"
byteorder = "1.3.2" byteorder = "1.3.2"
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"

View File

@ -42,6 +42,10 @@ impl From<StreamError> for Error {
fn from(err: StreamError) -> Self { Self::StreamErr(err) } fn from(err: StreamError) -> Self { Self::StreamErr(err) }
} }
impl From<bincode::Error> for Error {
fn from(err: bincode::Error) -> Self { Self::StreamErr(StreamError::Deserialize(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

@ -2300,28 +2300,30 @@ impl Client {
loop { loop {
let cnt_start = cnt; let cnt_start = cnt;
while let Some(msg) = self.general_stream.try_recv()? { while let Some(msg) = self.general_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
self.handle_server_msg(frontend_events, msg)?; self.handle_server_msg(frontend_events, bincode::deserialize(&msg.decompress()?)?)?;
} }
while let Some(msg) = self.ping_stream.try_recv()? { while let Some(msg) = self.ping_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
self.handle_ping_msg(msg)?; self.handle_ping_msg(bincode::deserialize(&msg.decompress()?)?)?;
} }
while let Some(msg) = self.character_screen_stream.try_recv()? { while let Some(msg) = self.character_screen_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
self.handle_server_character_screen_msg(frontend_events, msg)?; self.handle_server_character_screen_msg(frontend_events, bincode::deserialize(&msg.decompress()?)?)?;
} }
while let Some(msg) = self.in_game_stream.try_recv()? { while let Some(msg) = self.in_game_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
{ {
ingame_cnt += 1; ingame_cnt += 1;
} }
self.handle_server_in_game_msg(frontend_events, msg)?; self.handle_server_in_game_msg(frontend_events, bincode::deserialize(&msg.decompress()?)?)?;
} }
while let Some(msg) = self.terrain_stream.try_recv()? { while let Some(msg) = self.terrain_stream.try_recv_raw()? {
cnt += 1; cnt += 1;
let msg = msg.decompress()?;
let msg = bincode::deserialize(&msg)?;
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
{ {
if let ServerGeneral::TerrainChunkUpdate { chunk, .. } = &msg { if let ServerGeneral::TerrainChunkUpdate { chunk, .. } = &msg {

View File

@ -32,6 +32,7 @@ chrono = "0.4"
chrono-tz = "0.6" chrono-tz = "0.6"
sha2 = "0.10" sha2 = "0.10"
serde_json = "1.0.50" serde_json = "1.0.50"
serde_with = { version = "1.14.0" }
# Strum # Strum
strum = { version = "0.24", features = ["derive"] } strum = { version = "0.24", features = ["derive"] }

View File

@ -30,3 +30,4 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "8be2abc
# Serde # Serde
serde = { version = "1.0.110", features = ["derive"] } serde = { version = "1.0.110", features = ["derive"] }
serde_with = { version = "1.14.0" }

View File

@ -8,23 +8,28 @@ use image::{ImageBuffer, ImageDecoder, 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,
fmt::Debug, fmt::Debug,
io::{Read, Write}, io::{Read, Write},
marker::PhantomData, marker::PhantomData,
}; };
use serde_with::{serde_as, Bytes};
use tracing::warn; use tracing::warn;
use vek::*; use vek::*;
/// 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]
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompressedData<T> { pub struct CompressedData<'a, T> {
pub data: Vec<u8>, #[serde_as(as = "Bytes")]
#[serde(borrow)]
pub data: Cow<'a, [u8]>,
compressed: bool, compressed: bool,
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<T: Serialize> CompressedData<T> { impl<'a, T: Serialize> CompressedData<'a, T> {
pub fn compress(t: &T, level: u32) -> Self { pub fn compress(t: &T, level: u32) -> Self {
use flate2::{write::DeflateEncoder, Compression}; use flate2::{write::DeflateEncoder, Compression};
let uncompressed = bincode::serialize(t) let uncompressed = bincode::serialize(t)
@ -39,13 +44,13 @@ impl<T: Serialize> CompressedData<T> {
encoder.write_all(&*uncompressed).expect(EXPECT_MSG); encoder.write_all(&*uncompressed).expect(EXPECT_MSG);
let compressed = encoder.finish().expect(EXPECT_MSG); let compressed = encoder.finish().expect(EXPECT_MSG);
CompressedData { CompressedData {
data: compressed, data: Cow::Owned(compressed),
compressed: true, compressed: true,
_phantom: PhantomData, _phantom: PhantomData,
} }
} else { } else {
CompressedData { CompressedData {
data: uncompressed, data: Cow::Owned(uncompressed),
compressed: false, compressed: false,
_phantom: PhantomData, _phantom: PhantomData,
} }
@ -53,7 +58,7 @@ impl<T: Serialize> CompressedData<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) -> Option<T> {
if self.compressed { if self.compressed {
let mut uncompressed = Vec::with_capacity(self.data.len()); let mut uncompressed = Vec::with_capacity(self.data.len());
@ -186,10 +191,10 @@ impl<'a, VIE: VoxelImageDecoding> VoxelImageDecoding for &'a VIE {
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct QuadPngEncoding<const RESOLUTION_DIVIDER: u32>(); pub struct QuadPngEncoding<'a, const RESOLUTION_DIVIDER: u32>(pub PhantomData<&'a ()>);
impl<const N: u32> VoxelImageEncoding for QuadPngEncoding<N> { impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> {
type Output = CompressedData<(Vec<u8>, [usize; 3])>; type Output = CompressedData<'a, (Vec<u8>, [usize; 3])>;
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>>,
@ -317,7 +322,7 @@ const fn gen_lanczos_lookup<const N: u32, const R: u32>(
array array
} }
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) -> Option<Self::Workspace> {
use image::codecs::png::PngDecoder; use image::codecs::png::PngDecoder;
let (quad, indices) = data.decompress()?; let (quad, indices) = data.decompress()?;
@ -460,10 +465,10 @@ impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<N> {
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TriPngEncoding<const AVERAGE_PALETTE: bool>(); pub struct TriPngEncoding<'a, const AVERAGE_PALETTE: bool>(pub PhantomData<&'a ()>);
impl<const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<AVERAGE_PALETTE> { impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a, AVERAGE_PALETTE> {
type Output = CompressedData<(Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>; type Output = CompressedData<'a, (Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>;
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>>,
@ -549,7 +554,7 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<AVERAGE_
} }
} }
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) -> Option<Self::Workspace> {
use image::codecs::png::PngDecoder; use image::codecs::png::PngDecoder;
let (quad, palette, indices) = data.decompress()?; let (quad, palette, indices) = data.decompress()?;

View File

@ -15,6 +15,7 @@ use common::{
trade::{PendingTrade, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
}; };
use core::marker::PhantomData;
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
@ -24,7 +25,7 @@ use vek::*;
///This struct contains all messages the server might send (on different ///This struct contains all messages the server might send (on different
/// streams though) /// streams though)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ServerMsg { pub enum ServerMsg<'a> {
/// Basic info about server, send ONCE, clients need it to Register /// Basic info about server, send ONCE, clients need it to Register
Info(ServerInfo), Info(ServerInfo),
/// Initial data package, send BEFORE Register ONCE. Not Register relevant /// Initial data package, send BEFORE Register ONCE. Not Register relevant
@ -32,7 +33,7 @@ pub enum ServerMsg {
/// Result to `ClientMsg::Register`. send ONCE /// Result to `ClientMsg::Register`. send ONCE
RegisterAnswer(ServerRegisterAnswer), RegisterAnswer(ServerRegisterAnswer),
///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat` ///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat`
General(ServerGeneral), General(ServerGeneral<'a>),
Ping(PingMsg), Ping(PingMsg),
} }
@ -70,13 +71,16 @@ pub enum ServerInit {
pub type ServerRegisterAnswer = Result<(), RegisterError>; pub type ServerRegisterAnswer = Result<(), RegisterError>;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SerializedTerrainChunk { pub enum SerializedTerrainChunk<'a> {
DeflatedChonk(CompressedData<TerrainChunk>), #[serde(borrow)]
QuadPng(WireChonk<QuadPngEncoding<4>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>), DeflatedChonk(CompressedData<'a, TerrainChunk>),
TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>), #[serde(borrow)]
QuadPng(WireChonk<QuadPngEncoding<'a, 4>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
#[serde(borrow)]
TriPng(WireChonk<TriPngEncoding<'a, false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
} }
impl SerializedTerrainChunk { impl SerializedTerrainChunk<'_> {
pub fn approx_len(&self) -> usize { pub fn approx_len(&self) -> usize {
match self { match self {
SerializedTerrainChunk::DeflatedChonk(data) => data.data.len(), SerializedTerrainChunk::DeflatedChonk(data) => data.data.len(),
@ -98,7 +102,7 @@ impl SerializedTerrainChunk {
} }
pub fn quadpng(chunk: &TerrainChunk) -> Self { pub fn quadpng(chunk: &TerrainChunk) -> Self {
if let Some(wc) = WireChonk::from_chonk(QuadPngEncoding(), WidePacking(), chunk) { if let Some(wc) = WireChonk::from_chonk(QuadPngEncoding(PhantomData), WidePacking(), chunk) {
Self::QuadPng(wc) Self::QuadPng(wc)
} else { } else {
warn!("Image encoding failure occurred, falling back to deflate"); warn!("Image encoding failure occurred, falling back to deflate");
@ -107,7 +111,7 @@ impl SerializedTerrainChunk {
} }
pub fn tripng(chunk: &TerrainChunk) -> Self { pub fn tripng(chunk: &TerrainChunk) -> Self {
if let Some(wc) = WireChonk::from_chonk(TriPngEncoding(), WidePacking(), chunk) { if let Some(wc) = WireChonk::from_chonk(TriPngEncoding(PhantomData), WidePacking(), chunk) {
Self::TriPng(wc) Self::TriPng(wc)
} else { } else {
warn!("Image encoding failure occurred, falling back to deflate"); warn!("Image encoding failure occurred, falling back to deflate");
@ -126,7 +130,7 @@ impl SerializedTerrainChunk {
/// Messages sent from the server to the client /// Messages sent from the server to the client
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerGeneral { pub enum ServerGeneral<'a> {
//Character Screen related //Character Screen related
/// An error occurred while loading character data /// An error occurred while loading character data
CharacterDataLoadError(String), CharacterDataLoadError(String),
@ -169,13 +173,14 @@ pub enum ServerGeneral {
// Ingame related AND terrain stream // Ingame related AND terrain stream
TerrainChunkUpdate { TerrainChunkUpdate {
key: Vec2<i32>, key: Vec2<i32>,
chunk: Result<SerializedTerrainChunk, ()>, chunk: Result<SerializedTerrainChunk<'a>, ()>,
}, },
LodZoneUpdate { LodZoneUpdate {
key: Vec2<i32>, key: Vec2<i32>,
zone: lod::Zone, zone: lod::Zone,
}, },
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>), #[serde(borrow)]
TerrainBlockUpdates(CompressedData<'a, HashMap<Vec3<i32>, Block>>),
// Always possible // Always possible
PlayerListUpdate(PlayerListUpdate), PlayerListUpdate(PlayerListUpdate),
/// A message to go into the client chat box. The client is responsible for /// A message to go into the client chat box. The client is responsible for
@ -198,7 +203,7 @@ pub enum ServerGeneral {
MapMarker(comp::MapMarkerUpdate), MapMarker(comp::MapMarkerUpdate),
} }
impl ServerGeneral { impl ServerGeneral<'_> {
pub fn server_msg<S>(chat_type: comp::ChatType<String>, msg: S) -> Self pub fn server_msg<S>(chat_type: comp::ChatType<String>, msg: S) -> Self
where where
S: Into<String>, S: Into<String>,
@ -266,7 +271,7 @@ pub enum RegisterError {
//TODO: InvalidAlias, //TODO: InvalidAlias,
} }
impl ServerMsg { impl ServerMsg<'_> {
pub fn verify( pub fn verify(
&self, &self,
c_type: ClientType, c_type: ClientType,
@ -329,26 +334,26 @@ impl ServerMsg {
} }
} }
impl From<comp::ChatMsg> for ServerGeneral { impl From<comp::ChatMsg> for ServerGeneral<'_> {
fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) } fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) }
} }
impl From<ServerInfo> for ServerMsg { impl From<ServerInfo> for ServerMsg<'_> {
fn from(o: ServerInfo) -> ServerMsg { ServerMsg::Info(o) } fn from(o: ServerInfo) -> Self { ServerMsg::Info(o) }
} }
impl From<ServerInit> for ServerMsg { impl From<ServerInit> for ServerMsg<'_> {
fn from(o: ServerInit) -> ServerMsg { ServerMsg::Init(Box::new(o)) } fn from(o: ServerInit) -> Self { ServerMsg::Init(Box::new(o)) }
} }
impl From<ServerRegisterAnswer> for ServerMsg { impl From<ServerRegisterAnswer> for ServerMsg<'_> {
fn from(o: ServerRegisterAnswer) -> ServerMsg { ServerMsg::RegisterAnswer(o) } fn from(o: ServerRegisterAnswer) -> Self { ServerMsg::RegisterAnswer(o) }
} }
impl From<ServerGeneral> for ServerMsg { impl<'a> From<ServerGeneral<'a>> for ServerMsg<'a> {
fn from(o: ServerGeneral) -> ServerMsg { ServerMsg::General(o) } fn from(o: ServerGeneral<'a>) -> Self { ServerMsg::General(o) }
} }
impl From<PingMsg> for ServerMsg { impl From<PingMsg> for ServerMsg<'_> {
fn from(o: PingMsg) -> ServerMsg { ServerMsg::Ping(o) } fn from(o: PingMsg) -> Self { ServerMsg::Ping(o) }
} }

View File

@ -1,8 +1,10 @@
use common::{grid::Grid, trade::Good}; use common::{grid::Grid, trade::Good};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{serde_as, Bytes};
use std::collections::HashMap; use std::collections::HashMap;
use vek::*; use vek::*;
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
/// World map information. Note that currently, we always send the whole thing /// World map information. Note that currently, we always send the whole thing
/// in one go, but the structure aims to try to provide information as locally /// in one go, but the structure aims to try to provide information as locally
@ -121,6 +123,7 @@ pub struct WorldMapMsg {
/// more predictible sequence) would end up compressing better than storing /// more predictible sequence) would end up compressing better than storing
/// angles, or that we don't need as much precision as we currently have /// angles, or that we don't need as much precision as we currently have
/// (256 possible angles). /// (256 possible angles).
#[serde_as(as = "[(Bytes, Bytes); 2]")]
pub horizons: [(Vec<u8>, Vec<u8>); 2], pub horizons: [(Vec<u8>, Vec<u8>); 2],
pub sites: Vec<SiteInfo>, pub sites: Vec<SiteInfo>,
pub pois: Vec<PoiInfo>, pub pois: Vec<PoiInfo>,

View File

@ -13,6 +13,8 @@
generic_arg_infer, generic_arg_infer,
label_break_value, label_break_value,
option_zip, option_zip,
portable_simd,
slice_as_chunks,
trait_alias, trait_alias,
type_alias_impl_trait, type_alias_impl_trait,
extend_one, extend_one,

View File

@ -7,7 +7,8 @@ use crate::{
}; };
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde::{ser, Deserialize, Serialize}; use serde::{de::{self, Error}, ser, Deserialize, Serialize};
use serde_with::{serde_as, Bytes, DeserializeAs};
use std::ops::Deref; use std::ops::Deref;
use strum::{Display, EnumIter, EnumString}; use strum::{Display, EnumIter, EnumString};
use vek::*; use vek::*;
@ -123,11 +124,13 @@ impl BlockKind {
/// the implementation of BlockVec! BlockVec uses unsafe code that depends on being able to /// the implementation of BlockVec! BlockVec uses unsafe code that depends on being able to
/// independently validate the kind and treat attr as bytes; changing things so that this no longer /// independently validate the kind and treat attr as bytes; changing things so that this no longer
/// works will require careful review. /// works will require careful review.
#[serde_as]
#[derive(AsBytes, Copy, Clone, Debug, Eq, Serialize, Deserialize)] #[derive(AsBytes, Copy, Clone, Debug, Eq, Serialize, Deserialize)]
/// NOTE: repr(C) appears to preserve niche optimizations! /// NOTE: repr(C) appears to preserve niche optimizations!
#[repr(align(4), C)] #[repr(align(4), C)]
pub struct Block { pub struct Block {
kind: BlockKind, kind: BlockKind,
#[serde_as(as = "Bytes")]
attr: [u8; 3], attr: [u8; 3],
} }
@ -449,13 +452,15 @@ impl Block {
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
Deserialize,
Hash, Hash,
Eq, Eq,
PartialEq, PartialEq,
)] )]
#[serde(try_from = "&'_ [u8]")] /* #[serde(try_from = "&'_ [u8]")] */
pub struct BlockVec(Vec<Block>); pub struct BlockVec(
/* #[serde_as(as = "Bytes")] */
Vec<Block>
);
impl core::ops::Deref for BlockVec { impl core::ops::Deref for BlockVec {
type Target = Vec<Block>; type Target = Vec<Block>;
@ -491,9 +496,184 @@ impl Serialize for BlockVec {
} }
} }
impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec { impl<'a> Deserialize<'a> for BlockVec {
type Error = &'static str; /// XXX(@Sharp): This implementation is subtle and safety depends on correct implementation!
/// XXX(@Sharp): This implementation is subtle and its safety depens on correct implementation! /// It is well-commented, but those comments are only valid so long as this implementation
/// doesn't change. If you do need to change this implementation, please seek careful review!
///
/// NOTE: Ideally, we would perform a try_from(Vec<u8>) instead, to avoid the extra copy.
/// Unfortunately this is not generally sound, since Vec allocations must be deallocated with
/// the same layout with which they were allocated, which includes alignment (and no, it does
/// not matter if they in practice have the same alignment at runtime, it's still UB). If we
/// were to do this, we'd effectively have to hold a Vec<u8> inside BlockVec at all times, not
/// exposing &mut access at all, and instead requiring transmutes to get access to Blocks.
/// This seems like a huge pain so for now, hopefully deserialize (the non-owned version) is
/// sufficient.
#[allow(unsafe_code)]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'a>,
{
let blocks = <Bytes as DeserializeAs::<&'a [u8]>>::deserialize_as::<D>(deserializer)?;
// First, make sure we're correctly interpretable as a [[u8; 4]].
//
let blocks: &[[u8; 4]] = bytemuck::try_cast_slice(blocks)
.map_err(|_| D::Error::invalid_length(blocks.len(), &"a multiple of 4")/*"Length must be a multiple of 4"*/)?;
// The basic observation here is that a slice of [u8; 4] is *almost* the same as a slice of
// blocks, so conversion from the former to the latter can be very cheap. The only problem
// is that BlockKind (the first byte in `Block`) has some invalid states, so not every u8
// slice of the appropriate size is a block slice. Fortunately, since we don't care about
// figuring out which block triggered the error, we can figure this out really cheaply--we
// just have to set a bit for every block we see, then check at the end to make sure all
// the bits we set are valid elements. We can construct the valid bit set using EnumIter,
// and the requirement is: (!valid & set_bits) = 0.
// Construct the invalid list. Initially, it's all 1s, then we set all the bits
// corresponding to valid block kinds to 0, leaving a set bit for each invalid block kind.
//
// TODO: Verify whether this gets constant folded away; if not, try to do this as a const
// fn? Might need to modify the EnumIter implementation.
let mut invalid_bits = [true; 256];
<BlockKind as strum::IntoEnumIterator>::iter().for_each(|bk| {
invalid_bits[(bk as u8) as usize] = false;
});
// Blocks per chunk. Currently 16, since blocks are 4 bytes and 16 * 4 = 64 bytes is the
// size of a cacheline on most architectures.
const BLOCKS_PER_CHUNK: usize = 8;
/* // Initially, the set bit list is empty.
let mut set_bits: std::simd::Mask<i8, BLOCKS_PER_CHUNK> = std::simd::Mask::splat(false);
// NOTE: We iterate in chunks of BLOCKS_PER_CHUNK blocks. This also lets us
// independently fetch BLOCKS_PER_CHUNK entries from the lookup table at once, setting
// BLOCKS_PER_CHUNK independent lanes (reducing CPU level contention on the register)
// instead of updating a single register in each iteration (which causes contention).
let (chunks, remainder) = blocks.as_chunks::<BLOCKS_PER_CHUNK>();
chunks.iter().for_each(|array| {
// Look up each of the block kind in the chunk, setting each lane of the
// BLOCKS_PER_CHUNK-word mask to true if the block kind is invalid.
//
// NOTE: The block kind is guaranteed to be at the front of each 4-byte word,
// thanks to the repr(C).
//
// NOTE: Bounds checks here appears to be either elided, or perfectly predicted, so we
// fortunately avoid using unsafe here.
//
// FIXME: simd table lookup, and simd packing.
let block_kinds = std::simd::Mask::from_array(
[
invalid_bits[array[0x0][0] as u8 as usize],
invalid_bits[array[0x1][0] as u8 as usize],
invalid_bits[array[0x2][0] as u8 as usize],
invalid_bits[array[0x3][0] as u8 as usize],
invalid_bits[array[0x4][0] as u8 as usize],
invalid_bits[array[0x5][0] as u8 as usize],
invalid_bits[array[0x6][0] as u8 as usize],
invalid_bits[array[0x7][0] as u8 as usize],
/* invalid_bits[array[0x8][0] as u8 as usize],
invalid_bits[array[0x9][0] as u8 as usize],
invalid_bits[array[0xA][0] as u8 as usize],
invalid_bits[array[0xB][0] as u8 as usize],
invalid_bits[array[0xC][0] as u8 as usize],
invalid_bits[array[0xD][0] as u8 as usize],
invalid_bits[array[0xE][0] as u8 as usize],
invalid_bits[array[0xF][0] as u8 as usize], */
]
);
// Now, lanewise OR whether the block kind in this iteration was invalid, with whether
// the same block kind was invalid in this lane the previous iteration.
set_bits |= block_kinds;
});
// Now, we OR all the lanes together, to find out whether *any* of the lanes witnessed an
// invalid block kind.
let mut set_bits = set_bits.any(); */
let mut set_bits0 = false;
let mut set_bits1 = false;
let mut set_bits2 = false;
let mut set_bits3 = false;
let mut set_bits4 = false;
let mut set_bits5 = false;
let mut set_bits6 = false;
let mut set_bits7 = false;
let mut set_bits8 = false;
/* // Initially, the set bit list is empty.
let mut set_bits: std::simd::Mask<i8, BLOCKS_PER_CHUNK> = std::simd::Mask::splat(false); */
// NOTE: We iterate in chunks of BLOCKS_PER_CHUNK blocks. This also lets us
// independently fetch BLOCKS_PER_CHUNK entries from the lookup table at once, setting
// BLOCKS_PER_CHUNK independent lanes (reducing CPU level contention on the register)
// instead of updating a single register in each iteration (which causes contention).
let (chunks, remainder) = blocks.as_chunks::<BLOCKS_PER_CHUNK>();
chunks.iter().for_each(|array| {
// Look up each of the block kind in the chunk, setting each lane of the
// BLOCKS_PER_CHUNK-word mask to true if the block kind is invalid.
//
// NOTE: The block kind is guaranteed to be at the front of each 4-byte word,
// thanks to the repr(C).
//
// NOTE: Bounds checks here appears to be either elided, or perfectly predicted, so we
// fortunately avoid using unsafe here.
//
// FIXME: simd table lookup, and simd packing.
/* let block_kinds = std::simd::Mask::from_array(
[ */
set_bits0 |= invalid_bits[array[0x0][0] as u8 as usize];
set_bits1 |= invalid_bits[array[0x1][0] as u8 as usize];
set_bits2 |= invalid_bits[array[0x2][0] as u8 as usize];
set_bits3 |= invalid_bits[array[0x3][0] as u8 as usize];
set_bits4 |= invalid_bits[array[0x4][0] as u8 as usize];
set_bits5 |= invalid_bits[array[0x5][0] as u8 as usize];
set_bits6 |= invalid_bits[array[0x6][0] as u8 as usize];
set_bits7 |= invalid_bits[array[0x7][0] as u8 as usize];
/* invalid_bits[array[0x8][0] as u8 as usize],
invalid_bits[array[0x9][0] as u8 as usize],
invalid_bits[array[0xA][0] as u8 as usize],
invalid_bits[array[0xB][0] as u8 as usize],
invalid_bits[array[0xC][0] as u8 as usize],
invalid_bits[array[0xD][0] as u8 as usize],
invalid_bits[array[0xE][0] as u8 as usize],
invalid_bits[array[0xF][0] as u8 as usize], */
/* ]
)*/
// Now, lanewise OR whether the block kind in this iteration was invalid, with whether
// the same block kind was invalid in this lane the previous iteration.
// set_bits |= block_kinds;
});
// Now, we OR all the lanes together, to find out whether *any* of the lanes witnessed an
// invalid block kind.
let mut set_bits = /*set_bits.any()*/set_bits0 | set_bits1 | set_bits2 | set_bits3 | set_bits4 | set_bits5 | set_bits6 | set_bits7;
// Now handle the remainder (if this wasn't a precise fit for BLOCKS_PER_CHUNK blocks;
// this will never be the case for valid terrain chunks).
remainder.iter().for_each(|block| {
set_bits |= invalid_bits[block[0] as u8 as usize];
});
// The invalid bits and the set bits should have no overlap.
if set_bits {
// At least one invalid bit was set, so there was an invalid BlockKind somewhere.
//
// TODO: Use radix representation of the bad block kind.
return Err(D::Error::unknown_variant("an invalid u8", &["see the definition of BlockKind for details"])/*"Found an unknown BlockKind while parsing Vec<Block>"*/);
}
// All set bits are cleared, so all block kinds were valid. Combined with the slice being
// compatible with [u8; 4], we can transmute the slice to a slice of Blocks and then
// construct a new vector from it.
let blocks = unsafe { core::mem::transmute::<&'a [[u8; 4]], &'a [Block]>(blocks) };
// Finally, *safely* construct a vector from the new blocks (as mentioned above, we cannot
// reuse the old byte vector even if we wanted to, since it doesn't have the same
// alignment as Block).
Ok(Self(blocks.to_vec()/*Vec::new()*/))
}
}
/* impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec {
/* type Error = /*&'static str*/serde::de::Error;
/// XXX(@Sharp): This implementation is subtle and safety depends on correct implementation!
/// It is well-commented, but those comments are only valid so long as this implementation /// It is well-commented, but those comments are only valid so long as this implementation
/// doesn't change. If you do need to change this implementation, please seek careful review! /// doesn't change. If you do need to change this implementation, please seek careful review!
/// ///
@ -507,8 +687,9 @@ impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec {
/// sufficient. /// sufficient.
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn try_from(blocks: &'a [u8]) -> Result<Self, Self::Error> fn try_from(blocks: &'a [u8]) -> Result<Self, Self::Error>
{ { */
// First, make sure we're correctly interpretable as a [u8; 4]. /* // First, make sure we're correctly interpretable as a [[u8; 4]].
//
let blocks: &[[u8; 4]] = bytemuck::try_cast_slice(blocks) let blocks: &[[u8; 4]] = bytemuck::try_cast_slice(blocks)
.map_err(|_| /*Error::invalid_length(blocks.len(), &"a multiple of 4")*/"Length must be a multiple of 4")?; .map_err(|_| /*Error::invalid_length(blocks.len(), &"a multiple of 4")*/"Length must be a multiple of 4")?;
// The basic observation here is that a slice of [u8; 4] is *almost* the same as a slice of // The basic observation here is that a slice of [u8; 4] is *almost* the same as a slice of
@ -525,26 +706,127 @@ impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec {
// //
// TODO: Verify whether this gets constant folded away; if not, try to do this as a const // TODO: Verify whether this gets constant folded away; if not, try to do this as a const
// fn? Might need to modify the EnumIter implementation. // fn? Might need to modify the EnumIter implementation.
let mut invalid_bits = bitarr![1; 256]; let mut invalid_bits = [true; 256];
<BlockKind as strum::IntoEnumIterator>::iter().for_each(|bk| { <BlockKind as strum::IntoEnumIterator>::iter().for_each(|bk| {
invalid_bits.set((bk as u8).into(), false); invalid_bits[(bk as u8) as usize] = false;
}); });
// Initially, the set bit list is empty. // Blocks per chunk. Currently 16, since blocks are 4 bytes and 16 * 4 = 64 bytes is the
let mut set_bits = /*bitarr!*/[false; 256]; // size of a cacheline on most architectures.
const BLOCKS_PER_CHUNK: usize = 8;
// TODO: SIMD iteration. /* // Initially, the set bit list is empty.
// NOTE: The block kind is guaranteed to be at the front, thanks to the repr(C). let mut set_bits: std::simd::Mask<i8, BLOCKS_PER_CHUNK> = std::simd::Mask::splat(false);
blocks.into_iter().for_each(|&[kind, _, _, _]| {
// NOTE: Bounds check here appears to be either elided, or perfectly predicted, so we // NOTE: We iterate in chunks of BLOCKS_PER_CHUNK blocks. This also lets us
// independently fetch BLOCKS_PER_CHUNK entries from the lookup table at once, setting
// BLOCKS_PER_CHUNK independent lanes (reducing CPU level contention on the register)
// instead of updating a single register in each iteration (which causes contention).
let (chunks, remainder) = blocks.as_chunks::<BLOCKS_PER_CHUNK>();
chunks.iter().for_each(|array| {
// Look up each of the block kind in the chunk, setting each lane of the
// BLOCKS_PER_CHUNK-word mask to true if the block kind is invalid.
//
// NOTE: The block kind is guaranteed to be at the front of each 4-byte word,
// thanks to the repr(C).
//
// NOTE: Bounds checks here appears to be either elided, or perfectly predicted, so we
// fortunately avoid using unsafe here. // fortunately avoid using unsafe here.
/* set_bits.set(kind.into(), true); */ //
set_bits[kind as usize] = true; // FIXME: simd table lookup, and simd packing.
let block_kinds = std::simd::Mask::from_array(
[
invalid_bits[array[0x0][0] as u8 as usize],
invalid_bits[array[0x1][0] as u8 as usize],
invalid_bits[array[0x2][0] as u8 as usize],
invalid_bits[array[0x3][0] as u8 as usize],
invalid_bits[array[0x4][0] as u8 as usize],
invalid_bits[array[0x5][0] as u8 as usize],
invalid_bits[array[0x6][0] as u8 as usize],
invalid_bits[array[0x7][0] as u8 as usize],
/* invalid_bits[array[0x8][0] as u8 as usize],
invalid_bits[array[0x9][0] as u8 as usize],
invalid_bits[array[0xA][0] as u8 as usize],
invalid_bits[array[0xB][0] as u8 as usize],
invalid_bits[array[0xC][0] as u8 as usize],
invalid_bits[array[0xD][0] as u8 as usize],
invalid_bits[array[0xE][0] as u8 as usize],
invalid_bits[array[0xF][0] as u8 as usize], */
]
);
// Now, lanewise OR whether the block kind in this iteration was invalid, with whether
// the same block kind was invalid in this lane the previous iteration.
set_bits |= block_kinds;
});
// Now, we OR all the lanes together, to find out whether *any* of the lanes witnessed an
// invalid block kind.
let mut set_bits = set_bits.any(); */
let mut set_bits0 = false;
let mut set_bits1 = false;
let mut set_bits2 = false;
let mut set_bits3 = false;
let mut set_bits4 = false;
let mut set_bits5 = false;
let mut set_bits6 = false;
let mut set_bits7 = false;
let mut set_bits8 = false;
/* // Initially, the set bit list is empty.
let mut set_bits: std::simd::Mask<i8, BLOCKS_PER_CHUNK> = std::simd::Mask::splat(false); */
// NOTE: We iterate in chunks of BLOCKS_PER_CHUNK blocks. This also lets us
// independently fetch BLOCKS_PER_CHUNK entries from the lookup table at once, setting
// BLOCKS_PER_CHUNK independent lanes (reducing CPU level contention on the register)
// instead of updating a single register in each iteration (which causes contention).
let (chunks, remainder) = blocks.as_chunks::<BLOCKS_PER_CHUNK>();
chunks.iter().for_each(|array| {
// Look up each of the block kind in the chunk, setting each lane of the
// BLOCKS_PER_CHUNK-word mask to true if the block kind is invalid.
//
// NOTE: The block kind is guaranteed to be at the front of each 4-byte word,
// thanks to the repr(C).
//
// NOTE: Bounds checks here appears to be either elided, or perfectly predicted, so we
// fortunately avoid using unsafe here.
//
// FIXME: simd table lookup, and simd packing.
/* let block_kinds = std::simd::Mask::from_array(
[ */
set_bits0 |= invalid_bits[array[0x0][0] as u8 as usize];
set_bits1 |= invalid_bits[array[0x1][0] as u8 as usize];
set_bits2 |= invalid_bits[array[0x2][0] as u8 as usize];
set_bits3 |= invalid_bits[array[0x3][0] as u8 as usize];
set_bits4 |= invalid_bits[array[0x4][0] as u8 as usize];
set_bits5 |= invalid_bits[array[0x5][0] as u8 as usize];
set_bits6 |= invalid_bits[array[0x6][0] as u8 as usize];
set_bits7 |= invalid_bits[array[0x7][0] as u8 as usize];
/* invalid_bits[array[0x8][0] as u8 as usize],
invalid_bits[array[0x9][0] as u8 as usize],
invalid_bits[array[0xA][0] as u8 as usize],
invalid_bits[array[0xB][0] as u8 as usize],
invalid_bits[array[0xC][0] as u8 as usize],
invalid_bits[array[0xD][0] as u8 as usize],
invalid_bits[array[0xE][0] as u8 as usize],
invalid_bits[array[0xF][0] as u8 as usize], */
/* ]
)*/
// Now, lanewise OR whether the block kind in this iteration was invalid, with whether
// the same block kind was invalid in this lane the previous iteration.
// set_bits |= block_kinds;
});
// Now, we OR all the lanes together, to find out whether *any* of the lanes witnessed an
// invalid block kind.
let mut set_bits = /*set_bits.any()*/set_bits0 | set_bits1 | set_bits2 | set_bits3 | set_bits4 | set_bits5 | set_bits6 | set_bits7;
// Now handle the remainder (if this wasn't a precise fit for BLOCKS_PER_CHUNK blocks;
// this will never be the case for valid terrain chunks).
remainder.iter().for_each(|block| {
set_bits |= invalid_bits[block[0] as u8 as usize];
}); });
// The invalid bits and the set bits should have no overlap. // The invalid bits and the set bits should have no overlap.
invalid_bits &= set_bits; if set_bits {
if invalid_bits.any() {
// At least one invalid bit was set, so there was an invalid BlockKind somewhere. // At least one invalid bit was set, so there was an invalid BlockKind somewhere.
// //
// TODO: Use radix representation of the bad block kind. // TODO: Use radix representation of the bad block kind.
@ -556,10 +838,10 @@ impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec {
let blocks = unsafe { core::mem::transmute::<&'a [[u8; 4]], &'a [Block]>(blocks) }; let blocks = unsafe { core::mem::transmute::<&'a [[u8; 4]], &'a [Block]>(blocks) };
// Finally, *safely* construct a vector from the new blocks (as mentioned above, we cannot // Finally, *safely* construct a vector from the new blocks (as mentioned above, we cannot
// reuse the old byte vector even if we wanted to, since it doesn't have the same // reuse the old byte vector even if we wanted to, since it doesn't have the same
// alignment as Block). // alignment as Block). */
Ok(Self(blocks.to_vec())) Ok(Self(/*blocks.to_vec()*/Vec::new()))
} }
} } */
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -60,7 +60,7 @@ impl<V, Storage, ChonkSize: RectVolSize> VolSize<V> for SubChunkSize<V, Storage,
}; };
} }
type SubChunk<V, Storage, S, M> = Chunk<V, SubChunkSize<V, Storage, S>, M>; pub type SubChunk<V, Storage, S, M> = Chunk<V, SubChunkSize<V, Storage, S>, M>;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chonk<V, Storage, S: RectVolSize, M: Clone> { pub struct Chonk<V, Storage, S: RectVolSize, M: Clone> {
@ -100,6 +100,10 @@ impl<V, Storage: core::ops::DerefMut<Target=Vec<V>>, S: RectVolSize, M: Clone> C
self.sub_chunks.iter().map(SubChunk::num_groups).sum() self.sub_chunks.iter().map(SubChunk::num_groups).sum()
} }
pub fn sub_chunks<'a>(&'a self) -> impl Iterator<Item = &'a SubChunk<V, Storage, S, M>> {
self.sub_chunks.iter()
}
/// Iterate through the voxels in this chunk, attempting to avoid those that /// Iterate through the voxels in this chunk, attempting to avoid those that
/// are unchanged (i.e: match the `below` and `above` voxels). This is /// are unchanged (i.e: match the `below` and `above` voxels). This is
/// generally useful for performance reasons. /// generally useful for performance reasons.

View File

@ -158,6 +158,7 @@ impl TerrainChunkMeta {
// Terrain type aliases // Terrain type aliases
pub type TerrainChunk = chonk::Chonk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>; pub type TerrainChunk = chonk::Chonk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainSubChunk = chonk::SubChunk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainGrid = VolGrid2d<TerrainChunk>; pub type TerrainGrid = VolGrid2d<TerrainChunk>;
impl TerrainGrid { impl TerrainGrid {

View File

@ -5,6 +5,7 @@ use bitvec::prelude::*;
use core::{hash::Hash, iter::Iterator, marker::PhantomData, mem}; use core::{hash::Hash, iter::Iterator, marker::PhantomData, mem};
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{serde_as, Bytes};
use zerocopy::AsBytes; use zerocopy::AsBytes;
use vek::*; use vek::*;
@ -48,8 +49,10 @@ pub enum ChunkError {
/// The number of 256 groups is particularly nice because it means that the /// The number of 256 groups is particularly nice because it means that the
/// index buffer can consist of `u8`s. This keeps the space requirement for the /// index buffer can consist of `u8`s. This keeps the space requirement for the
/// index buffer as low as 4 cache lines. /// index buffer as low as 4 cache lines.
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk<V, S: VolSize<V>, M> { pub struct Chunk<V, S: VolSize<V>, M> {
#[serde_as(as = "Bytes")]
indices: Vec<u8>, /* TODO (haslersn): Box<[u8; S::SIZE.x * S::SIZE.y * S::SIZE.z]>, this is indices: Vec<u8>, /* TODO (haslersn): Box<[u8; S::SIZE.x * S::SIZE.y * S::SIZE.z]>, this is
* however not possible in Rust yet */ * however not possible in Rust yet */
vox: S, vox: S,
@ -121,6 +124,10 @@ impl<V, S: core::ops::DerefMut<Target=Vec<V>> + VolSize<V>, M> Chunk<V, S, M> {
} }
} }
pub fn get_vox(&self) -> &S {
&self.vox
}
/// Compress this subchunk by frequency. /// Compress this subchunk by frequency.
pub fn defragment(&mut self) pub fn defragment(&mut self)
where where

View File

@ -1005,6 +1005,27 @@ impl Stream {
} }
} }
#[inline]
pub fn try_recv_raw(&mut self) -> Result<Option<Message>, StreamError> {
match &mut self.b2a_msg_recv_r {
Some(b2a_msg_recv_r) => match b2a_msg_recv_r.try_recv() {
Ok(data) => Ok(Some(
Message {
data,
#[cfg(feature = "compression")]
compressed: self.promises.contains(Promises::COMPRESSED),
}
)),
Err(async_channel::TryRecvError::Empty) => Ok(None),
Err(async_channel::TryRecvError::Closed) => {
self.b2a_msg_recv_r = None; //prevent panic
Err(StreamError::StreamClosed)
},
},
None => Err(StreamError::StreamClosed),
}
}
/// use `try_recv` to check for a Message send from the remote side by their /// use `try_recv` to check for a Message send from the remote side by their
/// `Stream`. This function does not block and returns immediately. It's /// `Stream`. This function does not block and returns immediately. It's
/// intended for use in non-async context only. Other then that, the /// intended for use in non-async context only. Other then that, the
@ -1041,24 +1062,7 @@ impl Stream {
/// [`recv`]: Stream::recv /// [`recv`]: Stream::recv
#[inline] #[inline]
pub fn try_recv<M: DeserializeOwned>(&mut self) -> Result<Option<M>, StreamError> { pub fn try_recv<M: DeserializeOwned>(&mut self) -> Result<Option<M>, StreamError> {
match &mut self.b2a_msg_recv_r { self.try_recv_raw()?.map(|uncompressed| uncompressed.deserialize()).transpose()
Some(b2a_msg_recv_r) => match b2a_msg_recv_r.try_recv() {
Ok(data) => Ok(Some(
Message {
data,
#[cfg(feature = "compression")]
compressed: self.promises.contains(Promises::COMPRESSED),
}
.deserialize()?,
)),
Err(async_channel::TryRecvError::Empty) => Ok(None),
Err(async_channel::TryRecvError::Closed) => {
self.b2a_msg_recv_r = None; //prevent panic
Err(StreamError::StreamClosed)
},
},
None => Err(StreamError::StreamClosed),
}
} }
pub fn params(&self) -> StreamParams { pub fn params(&self) -> StreamParams {

View File

@ -3,7 +3,7 @@ use bytes::Bytes;
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
use network_protocol::Promises; use network_protocol::Promises;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::io; use std::{borrow::Cow, io};
#[cfg(all(feature = "compression", debug_assertions))] #[cfg(all(feature = "compression", debug_assertions))]
use tracing::warn; use tracing::warn;
@ -98,9 +98,20 @@ impl Message {
/// ``` /// ```
/// ///
/// [`recv_raw`]: crate::api::Stream::recv_raw /// [`recv_raw`]: crate::api::Stream::recv_raw
pub fn deserialize<M: DeserializeOwned>(self) -> Result<M, StreamError> { #[inline]
pub fn deserialize<M: DeserializeOwned>(&self) -> Result<M, StreamError> {
let uncompressed_data = self.decompress()?;
match bincode::deserialize(&uncompressed_data) {
Ok(m) => Ok(m),
Err(e) => Err(StreamError::Deserialize(e)),
}
}
/// Decompress a message without deserializing it.
#[inline]
pub fn decompress<'a>(&'a self) -> Result<Cow<'a, [u8]>, StreamError> {
#[cfg(not(feature = "compression"))] #[cfg(not(feature = "compression"))]
let uncompressed_data = self.data; let uncompressed_data = Cow::Borrowed(&self.data);
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
let uncompressed_data = if self.compressed { let uncompressed_data = if self.compressed {
@ -114,16 +125,13 @@ impl Message {
) { ) {
return Err(StreamError::Compression(e)); return Err(StreamError::Compression(e));
} }
Bytes::from(uncompressed_data) Cow::Owned(uncompressed_data)
} }
} else { } else {
self.data Cow::Borrowed(&*self.data)
}; };
match bincode::deserialize(&uncompressed_data) { Ok(uncompressed_data)
Ok(m) => Ok(m),
Err(e) => Err(StreamError::Deserialize(e)),
}
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]

View File

@ -81,7 +81,7 @@ impl Client {
} }
} }
pub(crate) fn send<M: Into<ServerMsg>>(&self, msg: M) -> Result<(), StreamError> { pub(crate) fn send<M: Into<ServerMsg<'static>>>(&self, msg: M) -> Result<(), StreamError> {
// TODO: hack to avoid locking stream mutex while serializing the message, // TODO: hack to avoid locking stream mutex while serializing the message,
// remove this when the mutexes on the Streams are removed // remove this when the mutexes on the Streams are removed
let prepared = self.prepare(msg); let prepared = self.prepare(msg);
@ -140,7 +140,7 @@ impl Client {
}*/ }*/
} }
pub(crate) fn send_fallible<M: Into<ServerMsg>>(&self, msg: M) { let _ = self.send(msg); } pub(crate) fn send_fallible<M: Into<ServerMsg<'static>>>(&self, msg: M) { let _ = self.send(msg); }
pub(crate) fn send_prepared(&self, msg: &PreparedMsg) -> Result<(), StreamError> { pub(crate) fn send_prepared(&self, msg: &PreparedMsg) -> Result<(), StreamError> {
match msg.stream_id { match msg.stream_id {
@ -158,7 +158,7 @@ impl Client {
} }
} }
pub(crate) fn prepare<M: Into<ServerMsg>>(&self, msg: M) -> PreparedMsg { pub(crate) fn prepare<M: Into<ServerMsg<'static>>>(&self, msg: M) -> PreparedMsg {
match msg.into() { match msg.into() {
ServerMsg::Info(m) => PreparedMsg::new(0, &m, &self.register_stream_params), ServerMsg::Info(m) => PreparedMsg::new(0, &m, &self.register_stream_params),
ServerMsg::Init(m) => PreparedMsg::new(0, &m, &self.register_stream_params), ServerMsg::Init(m) => PreparedMsg::new(0, &m, &self.register_stream_params),

View File

@ -1180,7 +1180,7 @@ impl Server {
pub fn notify_client<S>(&self, entity: EcsEntity, msg: S) pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
where where
S: Into<ServerMsg>, S: Into<ServerMsg<'static>>,
{ {
self.state self.state
.ecs() .ecs()
@ -1189,7 +1189,7 @@ impl Server {
.map(|c| c.send(msg)); .map(|c| c.send(msg));
} }
pub fn notify_players(&mut self, msg: ServerGeneral) { self.state.notify_players(msg); } pub fn notify_players(&mut self, msg: ServerGeneral<'static>) { self.state.notify_players(msg); }
pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) { pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) {
let ecs = self.state.ecs(); let ecs = self.state.ecs();

View File

@ -111,8 +111,8 @@ pub trait StateExt {
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
/// Iterates over registered clients and send each `ServerMsg` /// Iterates over registered clients and send each `ServerMsg`
fn send_chat(&self, msg: comp::UnresolvedChatMsg); fn send_chat(&self, msg: comp::UnresolvedChatMsg);
fn notify_players(&self, msg: ServerGeneral); fn notify_players(&self, msg: ServerGeneral<'static>);
fn notify_in_game_clients(&self, msg: ServerGeneral); fn notify_in_game_clients(&self, msg: ServerGeneral<'static>);
/// Create a new link between entities (see [`common::mounting`] for an /// Create a new link between entities (see [`common::mounting`] for an
/// example). /// example).
fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>; fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>;
@ -819,7 +819,7 @@ impl StateExt for State {
} }
/// Sends the message to all connected clients /// Sends the message to all connected clients
fn notify_players(&self, msg: ServerGeneral) { fn notify_players(&self, msg: ServerGeneral<'static>) {
let mut msg = Some(msg); let mut msg = Some(msg);
let mut lazy_msg = None; let mut lazy_msg = None;
for (client, _) in ( for (client, _) in (
@ -836,7 +836,7 @@ impl StateExt for State {
} }
/// Sends the message to all clients playing in game /// Sends the message to all clients playing in game
fn notify_in_game_clients(&self, msg: ServerGeneral) { fn notify_in_game_clients(&self, msg: ServerGeneral<'static>) {
let mut msg = Some(msg); let mut msg = Some(msg);
let mut lazy_msg = None; let mut lazy_msg = None;
for (client, _) in ( for (client, _) in (

View File

@ -1,7 +1,7 @@
use common::{ use common::{
generation::EntityInfo, generation::EntityInfo,
store::{Id, Store}, store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, terrain::{Block, BlockKind, SpriteKind, TerrainSubChunk, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
}; };
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
@ -316,11 +316,13 @@ fn dungeon(c: &mut Criterion) {
// let chunk_pos = Vec2::new(26944 / 32, 26848 / 32); // let chunk_pos = Vec2::new(26944 / 32, 26848 / 32);
let chunk_pos = Vec2::new(842, 839); let chunk_pos = Vec2::new(842, 839);
let chunk = world.generate_chunk(index.as_index_ref(), chunk_pos, || false, None).unwrap().0; let chunk = world.generate_chunk(index.as_index_ref(), chunk_pos, || false, None).unwrap().0;
/* println!("{:?}", chunk.sub_chunks_len());
let chunk = chunk.sub_chunks().next().unwrap(); */
let serialized = bincode::serialize(&chunk).unwrap(); let serialized = bincode::serialize(&chunk).unwrap();
// let chunk_pos = Vec2::new(24507/32, 20682/32); // let chunk_pos = Vec2::new(24507/32, 20682/32);
// let chunk_pos = Vec2::new(19638/32, 19621/32); // let chunk_pos = Vec2::new(19638/32, 19621/32);
b.iter(|| { b.iter(|| {
black_box(bincode::deserialize::<TerrainChunk>(&serialized).unwrap()); black_box(bincode::deserialize::<TerrainChunk/*TerrainSubChunk*//*BlockVec*/>(&*serialized).unwrap());
}); });
}); });

View File

@ -362,8 +362,8 @@ impl World {
let wpos = Vec3::from(chunk_wpos2d) + lpos; let wpos = Vec3::from(chunk_wpos2d) + lpos;
if let Some(block) = sampler.get_with_z_cache(wpos, /*Some(&*/z_cache/*)*/) { if let Some(block) = sampler.get_with_z_cache(wpos, /*Some(&*/z_cache/*)*/) {
block_ = Some(block); // block_ = Some(block);
// let _ = chunk.set(lpos, block); let _ = chunk.set(lpos, block);
} }
}); });
if let Some(block_) = block_ { if let Some(block_) = block_ {

View File

@ -479,6 +479,7 @@ impl Site {
..Site::default() ..Site::default()
}; };
// NOTE: Remove to run benchmarks.
// site.demarcate_obstacles(land); // site.demarcate_obstacles(land);
site.make_plaza(land, &mut rng); site.make_plaza(land, &mut rng);