Skip to content

Commit

Permalink
World: Add GZip compression
Browse files Browse the repository at this point in the history
  • Loading branch information
Snowiiii committed Aug 16, 2024
1 parent 94a7a60 commit 231bdb5
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 64 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ and customizable experience. It prioritizes performance and player enjoyment whi
- **Flexibility**: Highly configurable with the ability to disable unnecessary features.
- **Extensibility**: Provides a foundation for plugin development.

### What Pumpkin will not be:
- A direct replacement for Vanilla or Bukkit servers.
- A framework for building a server from scratch.
### What Pumpkin will not:
- Provide compatibility with Vanilla or Bukkit servers (including configs and plugins).
- Function as a framework for building a server from scratch.

> [!IMPORTANT]
> Pumpkin is currently under heavy development.
Expand Down
14 changes: 3 additions & 11 deletions pumpkin-protocol/src/client/play/c_disguised_chat_message.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use pumpkin_macros::packet;
use pumpkin_text::TextComponent;
use serde::Serialize;

use crate::{ClientPacket, VarInt};
use crate::VarInt;

#[derive(Clone)]
#[derive(Serialize)]
#[packet(0x1E)]
pub struct CDisguisedChatMessage {
message: TextComponent,
Expand All @@ -27,12 +28,3 @@ impl CDisguisedChatMessage {
}
}
}

impl ClientPacket for CDisguisedChatMessage {
fn write(&self, bytebuf: &mut crate::bytebuf::ByteBuffer) {
bytebuf.put_slice(&self.message.encode());
bytebuf.put_var_int(&self.chat_type);
bytebuf.put_slice(&self.sender_name.encode());
bytebuf.put_option(&self.target_name, |p, v| p.put_slice(&v.encode()));
}
}
4 changes: 2 additions & 2 deletions pumpkin-text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ impl TextComponent {

pub fn encode(&self) -> Vec<u8> {
// TODO: Somehow fix this ugly mess
#[derive(serde::Serialize, Debug)]
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct TempStruct<'a> {
#[serde(flatten)]
text: &'a TextContent,
#[serde(flatten)]
pub style: &'a Style,
style: &'a Style,
}
let astruct = TempStruct {
text: &self.content,
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion pumpkin-world/src/block_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use lazy_static::lazy_static;

use crate::world::WorldError;

const BLOCKS_JSON: &str = include_str!("../blocks.json");
const BLOCKS_JSON: &str = include_str!("../assets/blocks.json");

#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)]
struct BlockDefinition {
Expand Down
1 change: 1 addition & 0 deletions pumpkin-world/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod chunk;
pub mod dimension;
mod gen;
pub const WORLD_HEIGHT: usize = 384;
pub const WORLD_Y_START_AT: i32 = -64;
pub const DIRECT_PALETTE_BITS: u32 = 15;
Expand Down
96 changes: 68 additions & 28 deletions pumpkin-world/src/world.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{
fs::OpenOptions,
io::{Read, Seek},
path::PathBuf,
};

use flate2::bufread::ZlibDecoder;
use flate2::{bufread::ZlibDecoder, read::GzDecoder};
use itertools::Itertools;
use rayon::prelude::*;
use thiserror::Error;
Expand All @@ -16,6 +17,7 @@ use crate::chunk::ChunkData;

pub struct Level {
root_folder: PathBuf,
region_folder: PathBuf,
}

#[derive(Error, Debug)]
Expand All @@ -29,16 +31,24 @@ pub enum WorldError {
RegionIsInvalid,
#[error("Chunk not found")]
ChunkNotFound,
#[error("Compression scheme not recognised")]
UnknownCompression,
#[error("Error while working with zlib compression: {0}")]
ZlibError(std::io::Error),
#[error("Compression Error")]
Compression(CompressionError),
#[error("Error deserializing chunk: {0}")]
ErrorDeserializingChunk(String),
#[error("The requested block state id does not exist")]
BlockStateIdNotFound,
}

#[derive(Error, Debug)]
pub enum CompressionError {
#[error("Compression scheme not recognised")]
UnknownCompression,
#[error("Error while working with zlib compression: {0}")]
ZlibError(std::io::Error),
#[error("Error while working with Gzip compression: {0}")]
GZipError(std::io::Error),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
Gzip,
Expand All @@ -49,7 +59,12 @@ pub enum Compression {

impl Level {
pub fn from_root_folder(root_folder: PathBuf) -> Self {
Level { root_folder }
// TODO: Check if exists
let region_folder = root_folder.join("region");
Level {
root_folder,
region_folder,
}
}

// /// Read one chunk in the world
Expand Down Expand Up @@ -80,16 +95,12 @@ impl Level {
((chunk.1 as f32) / 32.0).floor() as i32,
);
let channel = channel.clone();
let mut region_file_path = self.root_folder.clone();
region_file_path.push("region");
region_file_path.push(format!("r.{}.{}.mca", region.0, region.1));

// return different error when file is not found (because that means that the chunks have just not been generated yet)
let mut region_file = match std::fs::File::options()
.read(true)
.write(false)
.open(&region_file_path)
{
let mut region_file = match OpenOptions::new().read(true).open(
self.region_folder
.join(format!("r.{}.{}.mca", region.0, region.1)),
) {
Ok(f) => f,
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
Expand Down Expand Up @@ -172,34 +183,63 @@ impl Level {
3 => Compression::None,
4 => Compression::LZ4,
_ => {
let _ =
channel.send(((chunk.0, chunk.1), Err(WorldError::RegionIsInvalid)));
let _ = channel.send((
(chunk.0, chunk.1),
Err(WorldError::Compression(
CompressionError::UnknownCompression,
)),
));
return;
}
};

match compression {
Compression::Zlib => {}
_ => panic!("Compression type is not supported"), // TODO: support other compression types
}

let size = u32::from_be_bytes(header[0..4].try_into().unwrap());

// size includes the compression scheme byte, so we need to subtract 1
let chunk_data = file_buf.drain(0..size as usize - 1).collect_vec();
let decompressed_chunk = match Self::decompress_data(compression, chunk_data) {
Ok(data) => data,
Err(e) => {
let _ = channel.send(((chunk.0, chunk.1), Err(WorldError::Compression(e))));
return;
}
};

let _ = channel
.blocking_send((chunk, ChunkData::from_bytes(decompressed_chunk, chunk)));
})
.collect::<Vec<()>>();
}

let mut z = ZlibDecoder::new(&chunk_data[..]);
fn decompress_data(
compression: Compression,
compressed_data: Vec<u8>,
) -> Result<Vec<u8>, CompressionError> {
match compression {
Compression::Gzip => {
let mut z = GzDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new();
match z.read_to_end(&mut chunk_data) {
Ok(_) => {}
Err(e) => {
let _ = channel.blocking_send((chunk, Err(WorldError::ZlibError(e))));
return;
return Err(CompressionError::GZipError(e));
}
}

let _ = channel.blocking_send((chunk, ChunkData::from_bytes(chunk_data, chunk)));
})
.collect::<Vec<()>>();
Ok(chunk_data)
}
Compression::Zlib => {
let mut z = ZlibDecoder::new(&compressed_data[..]);
let mut chunk_data = Vec::new();
match z.read_to_end(&mut chunk_data) {
Ok(_) => {}
Err(e) => {
return Err(CompressionError::ZlibError(e));
}
}
Ok(chunk_data)
}
Compression::None => Ok(compressed_data),
Compression::LZ4 => todo!(),
}
}
}
31 changes: 12 additions & 19 deletions pumpkin/src/client/player_packet.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
use num_traits::FromPrimitive;
use pumpkin_entity::EntityId;
use pumpkin_inventory::WindowType;
use pumpkin_protocol::{
client::play::{
Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, COpenScreen,
CSetEntityMetadata, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, Metadata,
Animation, CEntityAnimation, CEntityVelocity, CHeadRot, CHurtAnimation, CSystemChatMessge,
CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot,
},
server::play::{
Action, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation,
SSwingArm,
},
VarInt,
};
use pumpkin_text::TextComponent;

use crate::{
commands::{handle_command, CommandSender},
entity::player::{ChatMode, GameMode, Hand, Player},
entity::player::{ChatMode, GameMode, Hand},
server::Server,
util::math::wrap_degrees,
};
Expand Down Expand Up @@ -176,7 +174,7 @@ impl Client {
return;
}

if let Some(action) = Action::from_i32(command.action.0 as i32) {
if let Some(action) = Action::from_i32(command.action.0) {
match action {
pumpkin_protocol::server::play::Action::StartSneaking => player.sneaking = true,
pumpkin_protocol::server::play::Action::StopSneaking => player.sneaking = false,
Expand Down Expand Up @@ -208,22 +206,17 @@ impl Client {

pub fn handle_chat_message(&mut self, server: &mut Server, chat_message: SChatMessage) {
let message = chat_message.message;
self.send_packet(&COpenScreen::new(
VarInt(0),
VarInt(WindowType::CraftingTable as i32),
TextComponent::from("Test Crafter"),
));
// TODO: filter message & validation
let gameprofile = self.gameprofile.as_ref().unwrap();
dbg!("got message");
// yeah a "raw system message", the ugly way to do that, but it works
// server.broadcast_packet(
// self,
// CSystemChatMessge::new(
// TextComponent::from(format!("{}: {}", gameprofile.name, message)),
// false,
// ),
// );
server.broadcast_packet(
self,
&CSystemChatMessge::new(
TextComponent::from(format!("{}: {}", gameprofile.name, message)),
false,
),
);
/* server.broadcast_packet(
self,
CPlayerChatMessage::new(
Expand All @@ -245,7 +238,7 @@ impl Client {
*/
/* server.broadcast_packet(
self,
CDisguisedChatMessage::new(
&CDisguisedChatMessage::new(
TextComponent::from(message.clone()),
VarInt(0),
gameprofile.name.clone().into(),
Expand Down

0 comments on commit 231bdb5

Please sign in to comment.