Skip to content

Commit 5df6a4f

Browse files
committed
Variable chunk radius based on player view distance.
1 parent 05da0fe commit 5df6a4f

File tree

4 files changed

+72
-55
lines changed

4 files changed

+72
-55
lines changed

src/ecs/query.rs

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -151,44 +151,22 @@ impl_query_item_tuple!(A, B, C, D);
151151
impl_query_item_tuple!(A, B, C, D, E);
152152
impl_query_item_tuple!(A, B, C, D, E, F);
153153

154-
/// Examples of using the Query system
155-
///
156-
/// ```
157-
/// // Example 1: Basic query for a single component
158-
/// let query = Query::<&Position>::new(&entity_manager, &component_storage);
159-
/// for (entity_id, position) in query.iter().await {
160-
/// println!("Entity {} is at position {:?}", entity_id, position);
161-
/// }
162-
///
163-
/// // Example 2: Query for multiple components
164-
/// let query = Query::<(&Position, &Velocity)>::new(&entity_manager, &component_storage);
165-
/// for (entity_id, (position, velocity)) in query.iter().await {
166-
/// println!("Entity {} is at {:?} moving at {:?}", entity_id, position, velocity);
167-
/// }
168-
///
169-
/// // Example 3: Query with mutable components
170-
/// let query = Query::<(&mut Position, &Velocity)>::new(&entity_manager, &component_storage);
171-
/// for (entity_id, (mut position, velocity)) in query.iter().await {
172-
/// position.x += velocity.x;
173-
/// position.y += velocity.y;
174-
/// println!("Updated position of entity {} to {:?}", entity_id, position);
175-
/// }
176-
///
177-
/// // Example 4: Combining mutable and immutable queries
178-
/// let query = Query::<(&mut Health, &Position, &Attack)>::new(&entity_manager, &component_storage);
179-
/// for (entity_id, (mut health, position, attack)) in query.iter().await {
180-
/// if position.x < 0.0 {
181-
/// health.current -= attack.damage;
182-
/// println!("Entity {} took damage, new health: {}", entity_id, health.current);
183-
/// }
184-
/// }
185-
///
186-
/// // Example 5: Using next() for manual iteration
187-
/// let mut query = Query::<&Position>::new(&entity_manager, &component_storage);
188-
/// while let Some((entity_id, position)) = query.next().await {
189-
/// println!("Found entity {} at position {:?}", entity_id, position);
190-
/// }
191-
/// ```
154+
mod helpers {
155+
use super::*;
156+
157+
// optional query
158+
impl<T: QueryItem> QueryItem for Option<T> {
159+
type Item<'a> = Option<T::Item<'a>>;
160+
161+
async fn fetch<'a>(entity_id: impl Into<usize>, storage: &'a ComponentStorage) -> Result<Self::Item<'a>> {
162+
let entity_id = entity_id.into();
163+
let component = T::fetch(entity_id, storage).await;
164+
Ok(component.ok())
165+
}
166+
}
167+
168+
impl<T: Component> Component for Option<T> {}
169+
}
192170

193171
#[cfg(test)]
194172
mod tests {

src/net/packets/incoming/client_info.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use tracing::trace;
1+
use tracing::{trace};
22

3-
use ferrumc_macros::{packet, NetDecode};
3+
use ferrumc_macros::{packet, Component, NetDecode};
44

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

8-
#[derive(NetDecode)]
8+
#[derive(NetDecode, Component, Clone, Debug)]
99
#[packet(packet_id = 0x08, state = "play")]
1010
pub struct ClientInfo {
1111
pub locale: String,
@@ -19,8 +19,8 @@ pub struct ClientInfo {
1919
impl IncomingPacket for ClientInfo {
2020
async fn handle(
2121
self,
22-
_: ConnectionId,
23-
_state: GlobalState,
22+
entity_id: ConnectionId,
23+
state: GlobalState,
2424
) -> crate::utils::prelude::Result<()> {
2525
trace!("ClientInfo packet received");
2626
trace!("Locale: {}", self.locale);
@@ -29,6 +29,14 @@ impl IncomingPacket for ClientInfo {
2929
trace!("Chat Colors: {}", self.chat_colors);
3030
trace!("Displayed Skin Parts: {}", self.displayed_skin_parts);
3131
trace!("Main Hand: {}", self.main_hand);
32+
33+
// ClientInfo is a packet & also a component.
34+
state
35+
.world
36+
.get_component_storage()
37+
.insert(entity_id, self);
38+
39+
3240
Ok(())
3341
}
3442
}

src/net/systems/chunk_sender.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use ferrumc_codec::enc::NetEncode;
55
use tokio::sync::RwLock;
66
use tracing::{debug, error, warn};
77

8+
use crate::net::packets::incoming::client_info::ClientInfo;
89
use crate::net::packets::outgoing::chunk_and_light_data::ChunkDataAndUpdateLight;
910
use crate::net::packets::outgoing::set_center_chunk::SetCenterChunk;
1011
use crate::net::systems::System;
@@ -16,7 +17,7 @@ use crate::utils::encoding::position::Position;
1617
use crate::utils::prelude::*;
1718
use ferrumc_macros::AutoGenName;
1819

19-
pub const CHUNK_RADIUS: i32 = 16;
20+
pub const DEFAULT_CHUNK_RADIUS: i8 = 16;
2021
const CHUNK_TX_INTERVAL_MS: u64 = 50000;
2122

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

70+
let client_info = state
71+
.world
72+
.get_component::<ClientInfo>(entity_id)
73+
.await?;
74+
6975
let distance = last_chunk_tx_pos.distance_to(current_pos.0, current_pos.1);
7076

71-
if distance < (CHUNK_RADIUS as f64 / 5f64) {
77+
if distance < (client_info.view_distance as f64 / 5f64) {
7278
return Ok(());
7379
}
7480

@@ -77,9 +83,9 @@ impl ChunkSender {
7783
let state_clone = state.clone();
7884
tokio::spawn(
7985
async move {
80-
ChunkSender::send_chunks_to_player(state_clone, entity_id).await?;
81-
82-
Ok::<(), Error>(())
86+
if let Err(e) = ChunkSender::send_chunks_to_player(state_clone, entity_id).await {
87+
error!("Failed to send chunk to player: {}", e);
88+
}
8389
}
8490
);
8591

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

106+
let c_info = state
107+
.world
108+
.get_component::<ClientInfo>(entity_id)
109+
.await.ok();
110+
111+
100112
let pos = c_pos.clone();
113+
let view_distance: i8 = c_info.as_ref().map_or(DEFAULT_CHUNK_RADIUS, |c| c.view_distance);
101114
let conn = c_conn.0.clone();
102115

103116
drop(c_pos);
@@ -112,23 +125,26 @@ impl ChunkSender {
112125
drop(player);
113126

114127
ChunkSender::send_set_center_chunk(&pos, conn.clone()).await?;
115-
ChunkSender::send_chunk_data_to_player(state.clone(), &pos, conn.clone()).await?;
128+
ChunkSender::send_chunk_data_to_player(state.clone(), &pos, view_distance, conn.clone()).await?;
116129

117130
Ok(())
118131
}
119132

120133
async fn send_chunk_data_to_player(
121134
state: GlobalState,
122135
pos: &Position,
136+
player_view_distance: i8,
123137
conn: Arc<RwLock<Connection>>,
124138
) -> Result<()> {
125139
let start = std::time::Instant::now();
126140

127141
let pos_x = pos.x;
128142
let pos_z = pos.z;
129143

130-
for x in -CHUNK_RADIUS..=CHUNK_RADIUS {
131-
for z in -CHUNK_RADIUS..=CHUNK_RADIUS {
144+
let chunk_radius = player_view_distance as i32;
145+
146+
for x in -chunk_radius..=chunk_radius {
147+
for z in -chunk_radius..=chunk_radius {
132148
let Ok(packet) = ChunkDataAndUpdateLight::new(
133149
state.clone(),
134150
(pos_x >> 4) + x,
@@ -147,11 +163,14 @@ impl ChunkSender {
147163
let sample_chunk = ChunkDataAndUpdateLight::new(state.clone(), pos_x >> 4, pos_z >> 4).await?;
148164
let mut vec = vec![];
149165
sample_chunk.net_encode(&mut vec).await?;
166+
let chunk_rad_axis = chunk_radius * 2 + 1;
150167
debug!(
151-
"Send {} chunks to player in {:?}. Approximately {} kb of data (~{} kb per chunk)",
152-
(CHUNK_RADIUS * 2 + 1) * (CHUNK_RADIUS * 2 + 1),
168+
"Send {}({}x{}) chunks to player in {:?}. Approximately {} kb of data (~{} kb per chunk)",
169+
chunk_rad_axis * chunk_rad_axis,
170+
chunk_rad_axis,
171+
chunk_rad_axis,
153172
start.elapsed(),
154-
vec.len() as i32 * ((CHUNK_RADIUS * 2 + 1) * (CHUNK_RADIUS * 2 + 1)) / 1024,
173+
vec.len() as i32 * chunk_rad_axis * chunk_rad_axis / 1024,
155174
vec.len() as i32 / 1024
156175
);
157176

src/utils/components/last_chunk_tx_pos.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
use ferrumc_macros::{Component, Constructor, Getter};
22

3-
#[derive(Debug, Default, Component, Getter, Constructor)]
3+
#[derive(Debug, Component, Getter, Constructor)]
44
pub struct LastChunkTxPos {
55
pub x: i32,
66
pub z: i32,
77
}
88

9+
impl Default for LastChunkTxPos {
10+
fn default() -> Self {
11+
// The player has not moved yet.
12+
// So, when player joins the world, it sends chunks instantly since
13+
// the threshold is passed by lots.
14+
Self {
15+
x: i32::MAX,
16+
z: i32::MAX,
17+
}
18+
}
19+
}
20+
921
impl LastChunkTxPos {
1022
pub fn set_last_chunk_tx_pos(&mut self, x: i32, z: i32) {
1123
self.x = x;

0 commit comments

Comments
 (0)