Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Block editing #150

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ hashbrown = "0.15.0"
tinyvec = "1.8.0"
dashmap = "6.1.0"
uuid = { version = "1.1", features = ["v4", "v3", "serde"] }
whirlwind = "0.1.1"

# Macros
lazy_static = "1.5.0"
Expand All @@ -170,12 +169,12 @@ libflate = "2.1.0"
flate2 = { version = "1.0.33", features = ["zlib"], default-features = false }
zstd = { version = "0.13.2" }
brotli = "7.0.0"
lzzzz = "1.1.0"
lzzzz = "2.0.0"
yazi = "0.2.0"
bzip2 = "0.4.1"
bzip2 = "0.5.0"

# Database
heed = "0.20.5"
heed = "0.21.0"
moka = "0.12.8"

# CLI
Expand All @@ -189,7 +188,6 @@ page_size = "0.6.0"
regex = "1.11.1"

# I/O
tempfile = "3.12.0"
memmap2 = "0.9.5"

# Benchmarking
Expand Down
1 change: 1 addition & 0 deletions src/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ futures = { workspace = true }
serde_json = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive"] }
rand = "0.9.0-beta.1"


[[bin]]
Expand Down
1 change: 1 addition & 0 deletions src/bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(portable_simd)]
#![forbid(unsafe_code)]
#![feature(random)]
extern crate core;

use crate::errors::BinaryError;
Expand Down
1 change: 0 additions & 1 deletion src/bin/src/packet_handlers/login_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ async fn handle_ack_finish_configuration(
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(conn_id)?;
chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld")));
chunk_recv.calculate_chunks().await;

send_keep_alive(conn_id, state, &mut writer).await?;

Ok(ack_finish_configuration_event)
Expand Down
29 changes: 19 additions & 10 deletions src/bin/src/systems/chunk_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,26 @@ impl System for ChunkSenderSystem {
centre_coords = (chunk.0, chunk.1);
}
}
let mut sent_chunks = 0;
{
let Ok(chunk_recv) = state.universe.get::<ChunkReceiver>(eid) else {
trace!("A player disconnected before we could get the ChunkReceiver");
return Ok(());
};
for possible_chunk in chunk_recv.needed_chunks.iter_mut() {
if let Some(chunk) = possible_chunk.pair().1 {
let key = possible_chunk.pair().0;
trace!("Getting chunk_recv 3 for sender");
let chunk_recv = state
.universe
.get_mut::<ChunkReceiver>(eid)
.expect("ChunkReceiver not found");
trace!("Got chunk_recv 3 for sender");
for mut possible_chunk in chunk_recv.needed_chunks.iter_mut() {
if let (key, Some(chunk)) = possible_chunk.pair_mut() {
chunk.sections.iter_mut().for_each(|section| {
// if random::<u8>() < 25 {
if let Err(e) = section.block_states.resize(8) {
error!("Error resizing block states: {:?}", e);
}
// }
});
to_drop.push(key.clone());
match ChunkAndLightData::from_chunk(&chunk.clone()) {
Ok(packet) => {
packets.push(packet);
sent_chunks += 1;
}
Err(e) => {
error!("Error sending chunk: {:?}", e);
Expand Down Expand Up @@ -125,17 +131,20 @@ impl System for ChunkSenderSystem {
{
error!("Error sending chunk: {:?}", e);
}
let mut count = 0;
for packet in packets {
if let Err(e) =
conn.send_packet(&packet, &NetEncodeOpts::WithLength).await
{
error!("Error sending chunk: {:?}", e);
} else {
count += 1;
}
}
if let Err(e) = conn
.send_packet(
&ChunkBatchFinish {
batch_size: VarInt::new(sent_chunks),
batch_size: VarInt::new(count),
},
&NetEncodeOpts::WithLength,
)
Expand Down
2 changes: 2 additions & 0 deletions src/lib/core/src/chunks/chunk_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct ChunkReceiver {
pub can_see: DashSet<(i32, i32, String)>,
pub last_update: Instant,
pub last_chunk: Option<(i32, i32, String)>,
pub chunks_per_tick: f32,
}

impl Default for ChunkReceiver {
Expand All @@ -23,6 +24,7 @@ impl ChunkReceiver {
can_see: DashSet::new(),
last_update: Instant::now(),
last_chunk: None,
chunks_per_tick: 0.0,
}
}
}
Expand Down
28 changes: 27 additions & 1 deletion src/lib/net/crates/codec/src/net_types/network_position.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// I have no clue why it is saving i32 and i16. There is no precision. The actual player position is saved in f32.

use crate::decode::{NetDecode, NetDecodeOpts, NetDecodeResult};
use crate::encode::{NetEncode, NetEncodeOpts, NetEncodeResult};
use std::fmt::Display;
use std::io::Write;
use std::io::{Read, Write};
use tokio::io::AsyncWrite;

/// The definition of a "Position" in the Minecraft protocol.
Expand Down Expand Up @@ -47,10 +48,35 @@ impl NetEncode for NetworkPosition {
Ok(())
}
}

impl NetDecode for NetworkPosition {
fn decode<R: Read>(reader: &mut R, _: &NetDecodeOpts) -> NetDecodeResult<Self> {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
Ok(NetworkPosition::from_u64(u64::from_be_bytes(buf)))
}
}

impl NetworkPosition {
pub fn as_u64(&self) -> u64 {
((self.x as u64 & 0x3FFFFFF) << 38)
| ((self.z as u64 & 0x3FFFFFF) << 12)
| (self.y as u64 & 0xFFF)
}

pub fn from_u64(val: u64) -> Self {
let mut x = (val >> 38) as i32;
let mut y = (val << 52 >> 52) as i16;
let mut z = (val << 26 >> 38) as i32;
if x >= 1 << 25 {
x -= 1 << 26
}
if y >= 1 << 11 {
y -= 1 << 12
}
if z >= 1 << 25 {
z -= 1 << 26
}
Self { x, y, z }
}
}
20 changes: 20 additions & 0 deletions src/lib/net/src/packets/incoming/chunks_per_tick.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::packets::IncomingPacket;
use crate::NetResult;
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
use ferrumc_macros::{packet, NetDecode};
use ferrumc_state::ServerState;
use std::sync::Arc;

#[derive(NetDecode)]
#[packet(packet_id = 0x08, state = "play")]
pub struct ChunksPerTick {
chunks_per_tick: f32,
}

impl IncomingPacket for ChunksPerTick {
async fn handle(self, conn_id: usize, state: Arc<ServerState>) -> NetResult<()> {
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(conn_id)?;
chunk_recv.chunks_per_tick = self.chunks_per_tick;
Ok(())
}
}
2 changes: 2 additions & 0 deletions src/lib/net/src/packets/incoming/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ pub mod set_player_position;
pub mod set_player_position_and_rotation;
pub mod set_player_rotation;

pub mod chunks_per_tick;
pub mod place_block;
pub mod swing_arm;
28 changes: 28 additions & 0 deletions src/lib/net/src/packets/incoming/place_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::packets::IncomingPacket;
use crate::NetResult;
use ferrumc_macros::{packet, NetDecode};
use ferrumc_net_codec::net_types::network_position::NetworkPosition;
use ferrumc_net_codec::net_types::var_int::VarInt;
use ferrumc_state::ServerState;
use std::sync::Arc;
use tracing::debug;

#[derive(NetDecode, Debug)]
#[packet(packet_id = 0x38, state = "play")]
pub struct PlaceBlock {
pub hand: VarInt,
pub position: NetworkPosition,
pub face: VarInt,
pub cursor_x: f32,
pub cursor_y: f32,
pub cursor_z: f32,
pub inside_block: bool,
pub sequence: VarInt,
}

impl IncomingPacket for PlaceBlock {
async fn handle(self, _conn_id: usize, _state: Arc<ServerState>) -> NetResult<()> {
debug!("{:?}", self);
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/lib/net/src/utils/broadcast.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::connection::StreamWriter;
use crate::NetResult;
use async_trait::async_trait;
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_ecs::entities::Entity;
use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts};
use ferrumc_state::GlobalState;
Expand Down Expand Up @@ -70,7 +70,7 @@ fn get_all_entities(state: &GlobalState) -> HashSet<Entity> {
state
.universe
.get_component_manager()
.get_entities_with::<ChunkReceiver>()
.get_entities_with::<PlayerIdentity>()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep this as a ChunkReceiver, since you don't want to send play packets to someone in login state.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has to be a better solution that that. Also why would a player during login have a player identity?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're more than welcome to find a better solution, but, player receives the PlayerIdentity component during login start packet.
Oh wait, you also add ChunkReceiver during login start? Add it when login process is completed, and keep this as is. So 'everyone' would be players that could receive chunks I suppose?

.into_iter()
.collect()
}
Expand Down
1 change: 0 additions & 1 deletion src/lib/storage/src/lmdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ impl From<Error> for StorageError {
Error::Io(e) => StorageError::GenericIoError(e),
Error::Encoding(e) => StorageError::WriteError(e.to_string()),
Error::Decoding(e) => StorageError::ReadError(e.to_string()),
Error::DatabaseClosing => StorageError::CloseError("Database closing".to_string()),
_ => StorageError::DatabaseError(err.to_string()),
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/utils/general_purpose/src/data_packing/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataPackingError {
#[error("Size ({0}) exceeds maximum size of data type: {1}")]
SizeExceedsMaxSize(u8, u8),
#[error("Not enough bits to read with size {0} at offset {1}")]
NotEnoughBits(u8, u32),
}
101 changes: 101 additions & 0 deletions src/lib/utils/general_purpose/src/data_packing/i16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::data_packing::errors::DataPackingError;

/// Reads a specified number of bits from a given offset in a 64-bit signed integer.
///
/// # Arguments
///
/// * `data` - A reference to the 64-bit signed integer to read from.
/// * `size` - The number of bits to read (must be 16 or less).
/// * `offset` - The bit offset from which to start reading.
///
/// # Returns
///
/// * `Ok(i16)` - The extracted bits as a 16-bit signed integer.
/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits.
///
/// # Errors
///
/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16.
/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits.
pub fn read_nbit_i16(data: &i64, size: u8, offset: u32) -> Result<i16, DataPackingError> {
if size > 16 {
return Err(DataPackingError::SizeExceedsMaxSize(size, 16));
}
if offset + size as u32 > 64 {
return Err(DataPackingError::NotEnoughBits(size, offset));
}
let mask = (1 << size) - 1;
let extracted_bits = ((data >> offset) & mask) as i16;
// Sign extend if the extracted bits represent a negative number
let sign_bit = 1 << (size - 1);
if extracted_bits & sign_bit != 0 {
Ok(extracted_bits | !mask as i16)
} else {
Ok(extracted_bits)
}
}

/// Writes a specified number of bits to a given offset in a 64-bit signed integer.
///
/// # Arguments
///
/// * `data` - A mutable reference to the 64-bit signed integer to write to.
/// * `offset` - The bit offset from which to start writing.
/// * `value` - The 16-bit signed integer value to write.
/// * `size` - The number of bits to write (must be 16 or less).
///
/// # Returns
///
/// * `Ok(())` - If the bits were successfully written.
/// * `Err(DataPackingError)` - If the size exceeds 16 bits or the offset plus size exceeds 64 bits.
///
/// # Errors
///
/// * `DataPackingError::SizeExceedsMaxSize` - If `size` is greater than 16.
/// * `DataPackingError::NotEnoughBits` - If `offset + size` exceeds 64 bits.
pub fn write_nbit_i16(
data: &mut i64,
offset: u32,
value: i16,
size: u8,
) -> Result<(), DataPackingError> {
if size > 16 {
return Err(DataPackingError::SizeExceedsMaxSize(size, 16));
}
if offset + size as u32 > 64 {
return Err(DataPackingError::NotEnoughBits(size, offset));
}
let mask = (1 << size) - 1;
*data &= !(mask << offset);
*data |= ((value as i64) & mask) << offset;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

/// Tests the `read_nbit_i16` function with various inputs.
#[test]
fn test_read_nbit_i16() {
let data: i64 = 0b110101011;
assert_eq!(read_nbit_i16(&data, 3, 0).unwrap(), 0b011);
assert_eq!(read_nbit_i16(&data, 3, 3).unwrap(), -3); // 0b101 as i16 is -3
assert_eq!(read_nbit_i16(&data, 3, 6).unwrap(), -2); // 0b110 as i16 is -2
assert_eq!(read_nbit_i16(&data, 3, 9).unwrap(), 0b000);
}

/// Tests the `write_nbit_i16` function with various inputs.
#[test]
fn test_write_nbit_i16() {
let mut data: i64 = 0;
write_nbit_i16(&mut data, 0, 0b011, 3).unwrap();
assert_eq!(data, 0b011);
write_nbit_i16(&mut data, 3, -3, 3).unwrap(); // 0b101 as i16 is -3
assert_eq!(data, 0b101011);
write_nbit_i16(&mut data, 6, -2, 3).unwrap(); // 0b110 as i16 is -2
assert_eq!(data, 0b110101011);
write_nbit_i16(&mut data, 9, 0b000, 3).unwrap();
assert_eq!(data, 0b110101011);
}
}
Loading
Loading