mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Turning "full" chunks back on, deserialization improvements.
This commit is contained in:
parent
9c4b03482d
commit
ad5bcf3cd8
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -1403,12 +1403,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e92cb285610dd935f60ee8b4d62dd1988bd12b7ea50579bd6a138201525318e"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core 0.13.2",
|
||||
"darling_macro 0.13.2",
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1427,9 +1427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c29e95ab498b18131ea460b2c0baa18cbf041231d122b0b7bfebef8c8e88989"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@ -1452,11 +1452,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b21dd6b221dd547528bd6fb15f1a3b7ab03b9a06f76bff288a8c629bcfbe7f0e"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core 0.13.2",
|
||||
"darling_core 0.13.4",
|
||||
"quote 1.0.17",
|
||||
"syn 1.0.90",
|
||||
]
|
||||
@ -1683,7 +1683,7 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c594871f94ab3a00434cb09f03067c92fa2ece4cc657d58ba402e8377cd85a3"
|
||||
dependencies = [
|
||||
"darling 0.13.2",
|
||||
"darling 0.13.4",
|
||||
"proc-macro-crate 1.1.3",
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.17",
|
||||
@ -3584,7 +3584,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
|
||||
dependencies = [
|
||||
"darling 0.13.2",
|
||||
"darling 0.13.4",
|
||||
"proc-macro-crate 1.1.3",
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.17",
|
||||
@ -5454,6 +5454,28 @@ dependencies = [
|
||||
"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]]
|
||||
name = "sha1"
|
||||
version = "0.6.1"
|
||||
@ -6521,6 +6543,7 @@ version = "0.12.0"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"authc",
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"clap 3.1.8",
|
||||
"hashbrown 0.11.2",
|
||||
@ -6580,6 +6603,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"slab",
|
||||
"slotmap 1.0.6",
|
||||
@ -6654,6 +6678,7 @@ dependencies = [
|
||||
"image",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"specs",
|
||||
"specs-idvs",
|
||||
"sum_type",
|
||||
|
@ -21,6 +21,7 @@ common-systems = { package = "veloren-common-systems", path = "../common/systems
|
||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||
network = { package = "veloren-network", path = "../network", features = ["compression","quic"], default-features = false }
|
||||
|
||||
bincode = "1.3.2"
|
||||
byteorder = "1.3.2"
|
||||
tokio = { version = "1.14", default-features = false, features = ["rt-multi-thread"] }
|
||||
quinn = "0.8"
|
||||
|
@ -42,6 +42,10 @@ impl From<StreamError> for Error {
|
||||
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 {
|
||||
fn from(err: AuthClientError) -> Self { Self::AuthClientError(err) }
|
||||
}
|
||||
|
@ -2300,28 +2300,30 @@ impl Client {
|
||||
loop {
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
#[cfg(feature = "tracy")]
|
||||
{
|
||||
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;
|
||||
let msg = msg.decompress()?;
|
||||
let msg = bincode::deserialize(&msg)?;
|
||||
#[cfg(feature = "tracy")]
|
||||
{
|
||||
if let ServerGeneral::TerrainChunkUpdate { chunk, .. } = &msg {
|
||||
|
@ -32,6 +32,7 @@ chrono = "0.4"
|
||||
chrono-tz = "0.6"
|
||||
sha2 = "0.10"
|
||||
serde_json = "1.0.50"
|
||||
serde_with = { version = "1.14.0" }
|
||||
|
||||
# Strum
|
||||
strum = { version = "0.24", features = ["derive"] }
|
||||
|
@ -30,3 +30,4 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "8be2abc
|
||||
|
||||
# Serde
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde_with = { version = "1.14.0" }
|
||||
|
@ -8,23 +8,28 @@ use image::{ImageBuffer, ImageDecoder, Pixel};
|
||||
use num_traits::cast::FromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Debug,
|
||||
io::{Read, Write},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use serde_with::{serde_as, Bytes};
|
||||
use tracing::warn;
|
||||
use vek::*;
|
||||
|
||||
/// Wrapper for compressed, serialized data (for stuff that doesn't use the
|
||||
/// default lz4 compression)
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CompressedData<T> {
|
||||
pub data: Vec<u8>,
|
||||
pub struct CompressedData<'a, T> {
|
||||
#[serde_as(as = "Bytes")]
|
||||
#[serde(borrow)]
|
||||
pub data: Cow<'a, [u8]>,
|
||||
compressed: bool,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Serialize> CompressedData<T> {
|
||||
impl<'a, T: Serialize> CompressedData<'a, T> {
|
||||
pub fn compress(t: &T, level: u32) -> Self {
|
||||
use flate2::{write::DeflateEncoder, Compression};
|
||||
let uncompressed = bincode::serialize(t)
|
||||
@ -39,13 +44,13 @@ impl<T: Serialize> CompressedData<T> {
|
||||
encoder.write_all(&*uncompressed).expect(EXPECT_MSG);
|
||||
let compressed = encoder.finish().expect(EXPECT_MSG);
|
||||
CompressedData {
|
||||
data: compressed,
|
||||
data: Cow::Owned(compressed),
|
||||
compressed: true,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
} else {
|
||||
CompressedData {
|
||||
data: uncompressed,
|
||||
data: Cow::Owned(uncompressed),
|
||||
compressed: false,
|
||||
_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> {
|
||||
if self.compressed {
|
||||
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)]
|
||||
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> {
|
||||
type Output = CompressedData<(Vec<u8>, [usize; 3])>;
|
||||
impl<'a, const N: u32> VoxelImageEncoding for QuadPngEncoding<'a, N> {
|
||||
type Output = CompressedData<'a, (Vec<u8>, [usize; 3])>;
|
||||
type Workspace = (
|
||||
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
|
||||
}
|
||||
|
||||
impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<N> {
|
||||
impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<'_, N> {
|
||||
fn start(data: &Self::Output) -> Option<Self::Workspace> {
|
||||
use image::codecs::png::PngDecoder;
|
||||
let (quad, indices) = data.decompress()?;
|
||||
@ -460,10 +465,10 @@ impl<const N: u32> VoxelImageDecoding for QuadPngEncoding<N> {
|
||||
}
|
||||
|
||||
#[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> {
|
||||
type Output = CompressedData<(Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>;
|
||||
impl<'a, const AVERAGE_PALETTE: bool> VoxelImageEncoding for TriPngEncoding<'a, AVERAGE_PALETTE> {
|
||||
type Output = CompressedData<'a, (Vec<u8>, Vec<Rgb<u8>>, [usize; 3])>;
|
||||
type Workspace = (
|
||||
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> {
|
||||
use image::codecs::png::PngDecoder;
|
||||
let (quad, palette, indices) = data.decompress()?;
|
||||
|
@ -15,6 +15,7 @@ use common::{
|
||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||
uid::Uid,
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -24,7 +25,7 @@ use vek::*;
|
||||
///This struct contains all messages the server might send (on different
|
||||
/// streams though)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ServerMsg {
|
||||
pub enum ServerMsg<'a> {
|
||||
/// Basic info about server, send ONCE, clients need it to Register
|
||||
Info(ServerInfo),
|
||||
/// Initial data package, send BEFORE Register ONCE. Not Register relevant
|
||||
@ -32,7 +33,7 @@ pub enum ServerMsg {
|
||||
/// Result to `ClientMsg::Register`. send ONCE
|
||||
RegisterAnswer(ServerRegisterAnswer),
|
||||
///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat`
|
||||
General(ServerGeneral),
|
||||
General(ServerGeneral<'a>),
|
||||
Ping(PingMsg),
|
||||
}
|
||||
|
||||
@ -70,13 +71,16 @@ pub enum ServerInit {
|
||||
pub type ServerRegisterAnswer = Result<(), RegisterError>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum SerializedTerrainChunk {
|
||||
DeflatedChonk(CompressedData<TerrainChunk>),
|
||||
QuadPng(WireChonk<QuadPngEncoding<4>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
|
||||
TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
|
||||
pub enum SerializedTerrainChunk<'a> {
|
||||
#[serde(borrow)]
|
||||
DeflatedChonk(CompressedData<'a, TerrainChunk>),
|
||||
#[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 {
|
||||
match self {
|
||||
SerializedTerrainChunk::DeflatedChonk(data) => data.data.len(),
|
||||
@ -98,7 +102,7 @@ impl SerializedTerrainChunk {
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
warn!("Image encoding failure occurred, falling back to deflate");
|
||||
@ -107,7 +111,7 @@ impl SerializedTerrainChunk {
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
warn!("Image encoding failure occurred, falling back to deflate");
|
||||
@ -126,7 +130,7 @@ impl SerializedTerrainChunk {
|
||||
|
||||
/// Messages sent from the server to the client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerGeneral {
|
||||
pub enum ServerGeneral<'a> {
|
||||
//Character Screen related
|
||||
/// An error occurred while loading character data
|
||||
CharacterDataLoadError(String),
|
||||
@ -169,13 +173,14 @@ pub enum ServerGeneral {
|
||||
// Ingame related AND terrain stream
|
||||
TerrainChunkUpdate {
|
||||
key: Vec2<i32>,
|
||||
chunk: Result<SerializedTerrainChunk, ()>,
|
||||
chunk: Result<SerializedTerrainChunk<'a>, ()>,
|
||||
},
|
||||
LodZoneUpdate {
|
||||
key: Vec2<i32>,
|
||||
zone: lod::Zone,
|
||||
},
|
||||
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
|
||||
#[serde(borrow)]
|
||||
TerrainBlockUpdates(CompressedData<'a, HashMap<Vec3<i32>, Block>>),
|
||||
// Always possible
|
||||
PlayerListUpdate(PlayerListUpdate),
|
||||
/// A message to go into the client chat box. The client is responsible for
|
||||
@ -198,7 +203,7 @@ pub enum ServerGeneral {
|
||||
MapMarker(comp::MapMarkerUpdate),
|
||||
}
|
||||
|
||||
impl ServerGeneral {
|
||||
impl ServerGeneral<'_> {
|
||||
pub fn server_msg<S>(chat_type: comp::ChatType<String>, msg: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
@ -266,7 +271,7 @@ pub enum RegisterError {
|
||||
//TODO: InvalidAlias,
|
||||
}
|
||||
|
||||
impl ServerMsg {
|
||||
impl ServerMsg<'_> {
|
||||
pub fn verify(
|
||||
&self,
|
||||
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) }
|
||||
}
|
||||
|
||||
impl From<ServerInfo> for ServerMsg {
|
||||
fn from(o: ServerInfo) -> ServerMsg { ServerMsg::Info(o) }
|
||||
impl From<ServerInfo> for ServerMsg<'_> {
|
||||
fn from(o: ServerInfo) -> Self { ServerMsg::Info(o) }
|
||||
}
|
||||
|
||||
impl From<ServerInit> for ServerMsg {
|
||||
fn from(o: ServerInit) -> ServerMsg { ServerMsg::Init(Box::new(o)) }
|
||||
impl From<ServerInit> for ServerMsg<'_> {
|
||||
fn from(o: ServerInit) -> Self { ServerMsg::Init(Box::new(o)) }
|
||||
}
|
||||
|
||||
impl From<ServerRegisterAnswer> for ServerMsg {
|
||||
fn from(o: ServerRegisterAnswer) -> ServerMsg { ServerMsg::RegisterAnswer(o) }
|
||||
impl From<ServerRegisterAnswer> for ServerMsg<'_> {
|
||||
fn from(o: ServerRegisterAnswer) -> Self { ServerMsg::RegisterAnswer(o) }
|
||||
}
|
||||
|
||||
impl From<ServerGeneral> for ServerMsg {
|
||||
fn from(o: ServerGeneral) -> ServerMsg { ServerMsg::General(o) }
|
||||
impl<'a> From<ServerGeneral<'a>> for ServerMsg<'a> {
|
||||
fn from(o: ServerGeneral<'a>) -> Self { ServerMsg::General(o) }
|
||||
}
|
||||
|
||||
impl From<PingMsg> for ServerMsg {
|
||||
fn from(o: PingMsg) -> ServerMsg { ServerMsg::Ping(o) }
|
||||
impl From<PingMsg> for ServerMsg<'_> {
|
||||
fn from(o: PingMsg) -> Self { ServerMsg::Ping(o) }
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use common::{grid::Grid, trade::Good};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, Bytes};
|
||||
use std::collections::HashMap;
|
||||
use vek::*;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// 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
|
||||
@ -121,6 +123,7 @@ pub struct WorldMapMsg {
|
||||
/// more predictible sequence) would end up compressing better than storing
|
||||
/// angles, or that we don't need as much precision as we currently have
|
||||
/// (256 possible angles).
|
||||
#[serde_as(as = "[(Bytes, Bytes); 2]")]
|
||||
pub horizons: [(Vec<u8>, Vec<u8>); 2],
|
||||
pub sites: Vec<SiteInfo>,
|
||||
pub pois: Vec<PoiInfo>,
|
||||
|
@ -13,6 +13,8 @@
|
||||
generic_arg_infer,
|
||||
label_break_value,
|
||||
option_zip,
|
||||
portable_simd,
|
||||
slice_as_chunks,
|
||||
trait_alias,
|
||||
type_alias_impl_trait,
|
||||
extend_one,
|
||||
|
@ -7,7 +7,8 @@ use crate::{
|
||||
};
|
||||
use num_derive::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 strum::{Display, EnumIter, EnumString};
|
||||
use vek::*;
|
||||
@ -123,11 +124,13 @@ impl BlockKind {
|
||||
/// 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
|
||||
/// works will require careful review.
|
||||
#[serde_as]
|
||||
#[derive(AsBytes, Copy, Clone, Debug, Eq, Serialize, Deserialize)]
|
||||
/// NOTE: repr(C) appears to preserve niche optimizations!
|
||||
#[repr(align(4), C)]
|
||||
pub struct Block {
|
||||
kind: BlockKind,
|
||||
#[serde_as(as = "Bytes")]
|
||||
attr: [u8; 3],
|
||||
}
|
||||
|
||||
@ -449,13 +452,15 @@ impl Block {
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Deserialize,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
)]
|
||||
#[serde(try_from = "&'_ [u8]")]
|
||||
pub struct BlockVec(Vec<Block>);
|
||||
/* #[serde(try_from = "&'_ [u8]")] */
|
||||
pub struct BlockVec(
|
||||
/* #[serde_as(as = "Bytes")] */
|
||||
Vec<Block>
|
||||
);
|
||||
|
||||
impl core::ops::Deref for BlockVec {
|
||||
type Target = Vec<Block>;
|
||||
@ -491,9 +496,184 @@ impl Serialize for BlockVec {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a/*, Error: de::Error*/> TryFrom<&'a [u8]> for BlockVec {
|
||||
type Error = &'static str;
|
||||
/// XXX(@Sharp): This implementation is subtle and its safety depens on correct implementation!
|
||||
impl<'a> Deserialize<'a> for BlockVec {
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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.
|
||||
#[allow(unsafe_code)]
|
||||
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)
|
||||
.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
|
||||
@ -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
|
||||
// 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| {
|
||||
invalid_bits.set((bk as u8).into(), false);
|
||||
invalid_bits[(bk as u8) as usize] = false;
|
||||
});
|
||||
|
||||
// Initially, the set bit list is empty.
|
||||
let mut set_bits = /*bitarr!*/[false; 256];
|
||||
// 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;
|
||||
|
||||
// TODO: SIMD iteration.
|
||||
// NOTE: The block kind is guaranteed to be at the front, thanks to the repr(C).
|
||||
blocks.into_iter().for_each(|&[kind, _, _, _]| {
|
||||
// NOTE: Bounds check here appears to be either elided, or perfectly predicted, so we
|
||||
/* // 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.
|
||||
/* 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.
|
||||
invalid_bits &= set_bits;
|
||||
if invalid_bits.any() {
|
||||
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.
|
||||
@ -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) };
|
||||
// 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()))
|
||||
// alignment as Block). */
|
||||
Ok(Self(/*blocks.to_vec()*/Vec::new()))
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -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)]
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
/// are unchanged (i.e: match the `below` and `above` voxels). This is
|
||||
/// generally useful for performance reasons.
|
||||
|
@ -158,6 +158,7 @@ impl TerrainChunkMeta {
|
||||
// Terrain type aliases
|
||||
|
||||
pub type TerrainChunk = chonk::Chonk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
|
||||
pub type TerrainSubChunk = chonk::SubChunk<Block, BlockVec, TerrainChunkSize, TerrainChunkMeta>;
|
||||
pub type TerrainGrid = VolGrid2d<TerrainChunk>;
|
||||
|
||||
impl TerrainGrid {
|
||||
|
@ -5,6 +5,7 @@ use bitvec::prelude::*;
|
||||
use core::{hash::Hash, iter::Iterator, marker::PhantomData, mem};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, Bytes};
|
||||
use zerocopy::AsBytes;
|
||||
use vek::*;
|
||||
|
||||
@ -48,8 +49,10 @@ pub enum ChunkError {
|
||||
/// 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 as low as 4 cache lines.
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
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
|
||||
* however not possible in Rust yet */
|
||||
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.
|
||||
pub fn defragment(&mut self)
|
||||
where
|
||||
|
@ -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
|
||||
/// `Stream`. This function does not block and returns immediately. It's
|
||||
/// intended for use in non-async context only. Other then that, the
|
||||
@ -1041,24 +1062,7 @@ impl Stream {
|
||||
/// [`recv`]: Stream::recv
|
||||
#[inline]
|
||||
pub fn try_recv<M: DeserializeOwned>(&mut self) -> Result<Option<M>, 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),
|
||||
}
|
||||
.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),
|
||||
}
|
||||
self.try_recv_raw()?.map(|uncompressed| uncompressed.deserialize()).transpose()
|
||||
}
|
||||
|
||||
pub fn params(&self) -> StreamParams {
|
||||
|
@ -3,7 +3,7 @@ use bytes::Bytes;
|
||||
#[cfg(feature = "compression")]
|
||||
use network_protocol::Promises;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::io;
|
||||
use std::{borrow::Cow, io};
|
||||
#[cfg(all(feature = "compression", debug_assertions))]
|
||||
use tracing::warn;
|
||||
|
||||
@ -98,9 +98,20 @@ impl Message {
|
||||
/// ```
|
||||
///
|
||||
/// [`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"))]
|
||||
let uncompressed_data = self.data;
|
||||
let uncompressed_data = Cow::Borrowed(&self.data);
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
let uncompressed_data = if self.compressed {
|
||||
@ -114,16 +125,13 @@ impl Message {
|
||||
) {
|
||||
return Err(StreamError::Compression(e));
|
||||
}
|
||||
Bytes::from(uncompressed_data)
|
||||
Cow::Owned(uncompressed_data)
|
||||
}
|
||||
} else {
|
||||
self.data
|
||||
Cow::Borrowed(&*self.data)
|
||||
};
|
||||
|
||||
match bincode::deserialize(&uncompressed_data) {
|
||||
Ok(m) => Ok(m),
|
||||
Err(e) => Err(StreamError::Deserialize(e)),
|
||||
}
|
||||
Ok(uncompressed_data)
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -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,
|
||||
// remove this when the mutexes on the Streams are removed
|
||||
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> {
|
||||
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() {
|
||||
ServerMsg::Info(m) => PreparedMsg::new(0, &m, &self.register_stream_params),
|
||||
ServerMsg::Init(m) => PreparedMsg::new(0, &m, &self.register_stream_params),
|
||||
|
@ -1180,7 +1180,7 @@ impl Server {
|
||||
|
||||
pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
|
||||
where
|
||||
S: Into<ServerMsg>,
|
||||
S: Into<ServerMsg<'static>>,
|
||||
{
|
||||
self.state
|
||||
.ecs()
|
||||
@ -1189,7 +1189,7 @@ impl Server {
|
||||
.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>) {
|
||||
let ecs = self.state.ecs();
|
||||
|
@ -111,8 +111,8 @@ pub trait StateExt {
|
||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||
/// Iterates over registered clients and send each `ServerMsg`
|
||||
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
|
||||
fn notify_players(&self, msg: ServerGeneral);
|
||||
fn notify_in_game_clients(&self, msg: ServerGeneral);
|
||||
fn notify_players(&self, msg: ServerGeneral<'static>);
|
||||
fn notify_in_game_clients(&self, msg: ServerGeneral<'static>);
|
||||
/// Create a new link between entities (see [`common::mounting`] for an
|
||||
/// example).
|
||||
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
|
||||
fn notify_players(&self, msg: ServerGeneral) {
|
||||
fn notify_players(&self, msg: ServerGeneral<'static>) {
|
||||
let mut msg = Some(msg);
|
||||
let mut lazy_msg = None;
|
||||
for (client, _) in (
|
||||
@ -836,7 +836,7 @@ impl StateExt for State {
|
||||
}
|
||||
|
||||
/// 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 lazy_msg = None;
|
||||
for (client, _) in (
|
||||
|
@ -1,7 +1,7 @@
|
||||
use common::{
|
||||
generation::EntityInfo,
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainSubChunk, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
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(842, 839);
|
||||
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 chunk_pos = Vec2::new(24507/32, 20682/32);
|
||||
// let chunk_pos = Vec2::new(19638/32, 19621/32);
|
||||
b.iter(|| {
|
||||
black_box(bincode::deserialize::<TerrainChunk>(&serialized).unwrap());
|
||||
black_box(bincode::deserialize::<TerrainChunk/*TerrainSubChunk*//*BlockVec*/>(&*serialized).unwrap());
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -362,8 +362,8 @@ impl World {
|
||||
let wpos = Vec3::from(chunk_wpos2d) + lpos;
|
||||
|
||||
if let Some(block) = sampler.get_with_z_cache(wpos, /*Some(&*/z_cache/*)*/) {
|
||||
block_ = Some(block);
|
||||
// let _ = chunk.set(lpos, block);
|
||||
// block_ = Some(block);
|
||||
let _ = chunk.set(lpos, block);
|
||||
}
|
||||
});
|
||||
if let Some(block_) = block_ {
|
||||
|
@ -479,6 +479,7 @@ impl Site {
|
||||
..Site::default()
|
||||
};
|
||||
|
||||
// NOTE: Remove to run benchmarks.
|
||||
// site.demarcate_obstacles(land);
|
||||
|
||||
site.make_plaza(land, &mut rng);
|
||||
|
Loading…
Reference in New Issue
Block a user