Skip to content

Commit

Permalink
Variable chunk radius based on player view distance.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sweattypalms committed Sep 11, 2024
1 parent 05da0fe commit 5df6a4f
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 55 deletions.
54 changes: 16 additions & 38 deletions src/ecs/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,44 +151,22 @@ impl_query_item_tuple!(A, B, C, D);
impl_query_item_tuple!(A, B, C, D, E);
impl_query_item_tuple!(A, B, C, D, E, F);

/// Examples of using the Query system
///
/// ```
/// // Example 1: Basic query for a single component
/// let query = Query::<&Position>::new(&entity_manager, &component_storage);
/// for (entity_id, position) in query.iter().await {
/// println!("Entity {} is at position {:?}", entity_id, position);
/// }
///
/// // Example 2: Query for multiple components
/// let query = Query::<(&Position, &Velocity)>::new(&entity_manager, &component_storage);
/// for (entity_id, (position, velocity)) in query.iter().await {
/// println!("Entity {} is at {:?} moving at {:?}", entity_id, position, velocity);
/// }
///
/// // Example 3: Query with mutable components
/// let query = Query::<(&mut Position, &Velocity)>::new(&entity_manager, &component_storage);
/// for (entity_id, (mut position, velocity)) in query.iter().await {
/// position.x += velocity.x;
/// position.y += velocity.y;
/// println!("Updated position of entity {} to {:?}", entity_id, position);
/// }
///
/// // Example 4: Combining mutable and immutable queries
/// let query = Query::<(&mut Health, &Position, &Attack)>::new(&entity_manager, &component_storage);
/// for (entity_id, (mut health, position, attack)) in query.iter().await {
/// if position.x < 0.0 {
/// health.current -= attack.damage;
/// println!("Entity {} took damage, new health: {}", entity_id, health.current);
/// }
/// }
///
/// // Example 5: Using next() for manual iteration
/// let mut query = Query::<&Position>::new(&entity_manager, &component_storage);
/// while let Some((entity_id, position)) = query.next().await {
/// println!("Found entity {} at position {:?}", entity_id, position);
/// }
/// ```
mod helpers {
use super::*;

// optional query
impl<T: QueryItem> QueryItem for Option<T> {
type Item<'a> = Option<T::Item<'a>>;

async fn fetch<'a>(entity_id: impl Into<usize>, storage: &'a ComponentStorage) -> Result<Self::Item<'a>> {
let entity_id = entity_id.into();
let component = T::fetch(entity_id, storage).await;
Ok(component.ok())
}
}

impl<T: Component> Component for Option<T> {}
}

#[cfg(test)]
mod tests {
Expand Down
18 changes: 13 additions & 5 deletions src/net/packets/incoming/client_info.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use tracing::trace;
use tracing::{trace};

use ferrumc_macros::{packet, NetDecode};
use ferrumc_macros::{packet, Component, NetDecode};

use crate::net::packets::{ConnectionId, IncomingPacket};
use crate::state::GlobalState;

#[derive(NetDecode)]
#[derive(NetDecode, Component, Clone, Debug)]
#[packet(packet_id = 0x08, state = "play")]
pub struct ClientInfo {
pub locale: String,
Expand All @@ -19,8 +19,8 @@ pub struct ClientInfo {
impl IncomingPacket for ClientInfo {
async fn handle(
self,
_: ConnectionId,
_state: GlobalState,
entity_id: ConnectionId,
state: GlobalState,
) -> crate::utils::prelude::Result<()> {
trace!("ClientInfo packet received");
trace!("Locale: {}", self.locale);
Expand All @@ -29,6 +29,14 @@ impl IncomingPacket for ClientInfo {
trace!("Chat Colors: {}", self.chat_colors);
trace!("Displayed Skin Parts: {}", self.displayed_skin_parts);
trace!("Main Hand: {}", self.main_hand);

// ClientInfo is a packet & also a component.
state
.world
.get_component_storage()
.insert(entity_id, self);


Ok(())
}
}
41 changes: 30 additions & 11 deletions src/net/systems/chunk_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ferrumc_codec::enc::NetEncode;
use tokio::sync::RwLock;
use tracing::{debug, error, warn};

use crate::net::packets::incoming::client_info::ClientInfo;
use crate::net::packets::outgoing::chunk_and_light_data::ChunkDataAndUpdateLight;
use crate::net::packets::outgoing::set_center_chunk::SetCenterChunk;
use crate::net::systems::System;
Expand All @@ -16,7 +17,7 @@ use crate::utils::encoding::position::Position;
use crate::utils::prelude::*;
use ferrumc_macros::AutoGenName;

pub const CHUNK_RADIUS: i32 = 16;
pub const DEFAULT_CHUNK_RADIUS: i8 = 16;
const CHUNK_TX_INTERVAL_MS: u64 = 50000;

#[derive(AutoGenName)]
Expand Down Expand Up @@ -66,9 +67,14 @@ impl ChunkSender {
.get_mut_or_insert_with::<LastChunkTxPos>(entity_id, Default::default)
.await;

let client_info = state
.world
.get_component::<ClientInfo>(entity_id)
.await?;

let distance = last_chunk_tx_pos.distance_to(current_pos.0, current_pos.1);

if distance < (CHUNK_RADIUS as f64 / 5f64) {
if distance < (client_info.view_distance as f64 / 5f64) {
return Ok(());
}

Expand All @@ -77,9 +83,9 @@ impl ChunkSender {
let state_clone = state.clone();
tokio::spawn(
async move {
ChunkSender::send_chunks_to_player(state_clone, entity_id).await?;

Ok::<(), Error>(())
if let Err(e) = ChunkSender::send_chunks_to_player(state_clone, entity_id).await {
error!("Failed to send chunk to player: {}", e);
}
}
);

Expand All @@ -97,7 +103,14 @@ impl ChunkSender {
.get_components::<(Player, Position, ConnectionWrapper)>(entity_id)
.await?;

let c_info = state
.world
.get_component::<ClientInfo>(entity_id)
.await.ok();


let pos = c_pos.clone();
let view_distance: i8 = c_info.as_ref().map_or(DEFAULT_CHUNK_RADIUS, |c| c.view_distance);
let conn = c_conn.0.clone();

drop(c_pos);
Expand All @@ -112,23 +125,26 @@ impl ChunkSender {
drop(player);

ChunkSender::send_set_center_chunk(&pos, conn.clone()).await?;
ChunkSender::send_chunk_data_to_player(state.clone(), &pos, conn.clone()).await?;
ChunkSender::send_chunk_data_to_player(state.clone(), &pos, view_distance, conn.clone()).await?;

Ok(())
}

async fn send_chunk_data_to_player(
state: GlobalState,
pos: &Position,
player_view_distance: i8,
conn: Arc<RwLock<Connection>>,
) -> Result<()> {
let start = std::time::Instant::now();

let pos_x = pos.x;
let pos_z = pos.z;

for x in -CHUNK_RADIUS..=CHUNK_RADIUS {
for z in -CHUNK_RADIUS..=CHUNK_RADIUS {
let chunk_radius = player_view_distance as i32;

for x in -chunk_radius..=chunk_radius {
for z in -chunk_radius..=chunk_radius {
let Ok(packet) = ChunkDataAndUpdateLight::new(
state.clone(),
(pos_x >> 4) + x,
Expand All @@ -147,11 +163,14 @@ impl ChunkSender {
let sample_chunk = ChunkDataAndUpdateLight::new(state.clone(), pos_x >> 4, pos_z >> 4).await?;
let mut vec = vec![];
sample_chunk.net_encode(&mut vec).await?;
let chunk_rad_axis = chunk_radius * 2 + 1;
debug!(
"Send {} chunks to player in {:?}. Approximately {} kb of data (~{} kb per chunk)",
(CHUNK_RADIUS * 2 + 1) * (CHUNK_RADIUS * 2 + 1),
"Send {}({}x{}) chunks to player in {:?}. Approximately {} kb of data (~{} kb per chunk)",
chunk_rad_axis * chunk_rad_axis,
chunk_rad_axis,
chunk_rad_axis,
start.elapsed(),
vec.len() as i32 * ((CHUNK_RADIUS * 2 + 1) * (CHUNK_RADIUS * 2 + 1)) / 1024,
vec.len() as i32 * chunk_rad_axis * chunk_rad_axis / 1024,
vec.len() as i32 / 1024
);

Expand Down
14 changes: 13 additions & 1 deletion src/utils/components/last_chunk_tx_pos.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
use ferrumc_macros::{Component, Constructor, Getter};

#[derive(Debug, Default, Component, Getter, Constructor)]
#[derive(Debug, Component, Getter, Constructor)]
pub struct LastChunkTxPos {
pub x: i32,
pub z: i32,
}

impl Default for LastChunkTxPos {
fn default() -> Self {
// The player has not moved yet.
// So, when player joins the world, it sends chunks instantly since
// the threshold is passed by lots.
Self {
x: i32::MAX,
z: i32::MAX,
}
}
}

impl LastChunkTxPos {
pub fn set_last_chunk_tx_pos(&mut self, x: i32, z: i32) {
self.x = x;
Expand Down

0 comments on commit 5df6a4f

Please sign in to comment.