Skip to content

Commit 318bbf2

Browse files
authored
Merge pull request #179 from ferrumc-rs/feature/optimised-chunks
2 parents ea7e3c3 + 0af06d2 commit 318bbf2

File tree

20 files changed

+994
-723
lines changed

20 files changed

+994
-723
lines changed

.etc/raw_chunk.dat

-394 Bytes
Binary file not shown.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ page_size = "0.6.0"
189189
regex = "1.11.1"
190190
noise = "0.9.0"
191191
ctrlc = "3.4.7"
192+
num_cpus = "1.16.0"
192193
typename = "0.1.2"
193194
bevy_ecs = { version = "0.16.0", features = ["multi_threaded", "trace"] }
194195

src/bin/src/main.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use ferrumc_config::statics::get_global_config;
66
use ferrumc_config::whitelist::create_whitelist;
77
use ferrumc_general_purpose::paths::get_root_path;
88
use ferrumc_state::{GlobalState, ServerState};
9-
use ferrumc_world::chunk_format::Chunk;
9+
use ferrumc_threadpool::ThreadPool;
1010
use ferrumc_world::World;
11-
use ferrumc_world_gen::errors::WorldGenError;
1211
use ferrumc_world_gen::WorldGenerator;
1312
use std::sync::Arc;
13+
use std::time::Instant;
1414
use tracing::{error, info};
1515

1616
pub(crate) mod errors;
@@ -76,27 +76,30 @@ fn generate_chunks(state: GlobalState) -> Result<(), BinaryError> {
7676
info!("No overworld spawn chunk found, generating spawn chunks...");
7777
// Generate a 12x12 chunk area around the spawn point
7878
let mut chunks = Vec::new();
79+
let start = Instant::now();
7980
let radius = get_global_config().chunk_render_distance as i32;
8081
for x in -radius..=radius {
8182
for z in -radius..=radius {
8283
chunks.push((x, z));
8384
}
8485
}
85-
let generated_chunks: Vec<Result<Chunk, WorldGenError>> = chunks
86-
.iter()
87-
.map(|(x, z)| {
88-
let state = state.clone();
89-
state.terrain_generator.generate_chunk(*x, *z)
90-
})
91-
.collect();
92-
for chunk in generated_chunks {
93-
let chunk = chunk.map_err(|e| {
94-
error!("Error generating chunk: {:?}", e);
95-
BinaryError::Custom("Error generating chunk".to_string())
96-
})?;
97-
state.world.save_chunk(chunk)?;
86+
let mut batch = state.thread_pool.batch();
87+
for (x, z) in chunks {
88+
let state_clone = state.clone();
89+
batch.execute(move || {
90+
let chunk = state_clone.terrain_generator.generate_chunk(x, z);
91+
if let Err(e) = chunk {
92+
error!("Error generating chunk ({}, {}): {:?}", x, z, e);
93+
} else {
94+
let chunk = chunk.unwrap();
95+
if let Err(e) = state_clone.world.save_chunk(chunk) {
96+
error!("Error saving chunk ({}, {}): {:?}", x, z, e);
97+
}
98+
}
99+
});
98100
}
99-
info!("Finished generating spawn chunks...");
101+
batch.wait();
102+
info!("Finished generating spawn chunks in {:?}", start.elapsed());
100103
Ok(())
101104
}
102105

@@ -133,7 +136,7 @@ fn handle_import(import_args: ImportArgs) -> Result<(), BinaryError> {
133136
info!("Importing world...");
134137

135138
// let config = get_global_config();
136-
let mut world = World::new();
139+
let mut world = World::new(get_global_config().database.db_path.clone().into());
137140

138141
let root_path = get_root_path();
139142
let mut import_path = root_path.join(import_args.import_path);
@@ -155,9 +158,10 @@ fn handle_import(import_args: ImportArgs) -> Result<(), BinaryError> {
155158

156159
fn create_state() -> Result<ServerState, BinaryError> {
157160
Ok(ServerState {
158-
world: World::new(),
161+
world: World::new(get_global_config().database.db_path.clone().into()),
159162
terrain_generator: WorldGenerator::new(0),
160163
shut_down: false.into(),
161164
players: DashMap::default(),
165+
thread_pool: ThreadPool::new(),
162166
})
163167
}

src/bin/src/packet_handlers/play_packets/chunk_batch_ack.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ pub fn handle(
4242
pos.z as i32,
4343
"overworld",
4444
)?;
45-
if head_block.name == "minecraft:air" {
45+
// Check if air
46+
if head_block.0 == 0 {
4647
move_to_spawn = false;
4748
}
4849
if move_to_spawn {

src/bin/src/packet_handlers/play_packets/place_block.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,21 @@ pub fn handle(
2929
match event.hand.0 {
3030
0 => {
3131
debug!("Placing block at {:?}", event.position);
32-
let block_clicked = state.0.clone().world.get_block_and_fetch(
32+
let mut chunk = match state.0.world.load_chunk(
33+
event.position.x >> 4,
34+
event.position.z >> 4,
35+
"overworld",
36+
) {
37+
Ok(chunk) => chunk,
38+
Err(e) => {
39+
debug!("Failed to load chunk: {:?}", e);
40+
continue 'ev_loop;
41+
}
42+
};
43+
let block_clicked = chunk.get_block(
3344
event.position.x,
3445
event.position.y as i32,
3546
event.position.z,
36-
"overworld",
3747
)?;
3848
trace!("Block clicked: {:?}", block_clicked);
3949
// Use the face to determine the offset of the block to place
@@ -73,10 +83,9 @@ pub fn handle(
7383
continue 'ev_loop;
7484
}
7585
let packet = BlockChangeAck {
76-
sequence: event.sequence.clone(),
86+
sequence: event.sequence,
7787
};
7888
conn.send_packet(packet)?;
79-
let mut chunk = state.0.world.load_chunk(x >> 4, z >> 4, "overworld")?;
8089

8190
chunk.set_block(
8291
x & 0xF,
@@ -88,7 +97,7 @@ pub fn handle(
8897
},
8998
)?;
9099
let ack_packet = BlockChangeAck {
91-
sequence: event.sequence.clone(),
100+
sequence: event.sequence,
92101
};
93102
// Make this use the much more efficient block change packet
94103
let chunk_packet = ChunkAndLightData::from_chunk(&chunk)?;

src/bin/src/packet_handlers/play_packets/player_action.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use ferrumc_net::packets::outgoing::block_update::BlockUpdate;
66
use ferrumc_net::PlayerActionReceiver;
77
use ferrumc_net_codec::net_types::var_int::VarInt;
88
use ferrumc_state::GlobalStateResource;
9-
use ferrumc_world::chunk_format::BLOCK2ID;
9+
use ferrumc_world::block_id::BlockId;
1010
use ferrumc_world::vanilla_chunk_format::BlockData;
11-
use tracing::{debug, error};
11+
use tracing::{debug, error, trace};
1212

1313
pub fn handle(
1414
events: Res<PlayerActionReceiver>,
@@ -20,11 +20,21 @@ pub fn handle(
2020
let res: Result<(), BinaryError> = try {
2121
match event.status.0 {
2222
0 => {
23-
let mut chunk = state.0.clone().world.load_chunk(
23+
let mut chunk = match state.0.clone().world.load_chunk(
2424
event.location.x >> 4,
2525
event.location.z >> 4,
2626
"overworld",
27-
)?;
27+
) {
28+
Ok(chunk) => chunk,
29+
Err(e) => {
30+
trace!("Chunk not found, generating new chunk: {:?}", e);
31+
state
32+
.0
33+
.clone()
34+
.terrain_generator
35+
.generate_chunk(event.location.x >> 4, event.location.z >> 4)?
36+
}
37+
};
2838
let block = chunk.get_block(
2939
event.location.x,
3040
event.location.y as i32,
@@ -38,22 +48,20 @@ pub fn handle(
3848
);
3949
chunk.set_block(relative_x, relative_y, relative_z, BlockData::default())?;
4050
// Save the chunk to disk
41-
state.0.world.save_chunk(chunk.clone())?;
51+
state.0.world.save_chunk(chunk)?;
4252
for (eid, conn) in query {
4353
if !conn.running.load(std::sync::atomic::Ordering::Relaxed) {
4454
continue;
4555
}
4656
// If the player is the one who placed the block, send the BlockChangeAck packet
4757
let block_update_packet = BlockUpdate {
4858
location: event.location.clone(),
49-
block_id: VarInt::from(*BLOCK2ID.get(&BlockData::default()).expect(
50-
"BlockData::default() should always have a corresponding block ID",
51-
)),
59+
block_id: VarInt::from(BlockId::default()),
5260
};
5361
conn.send_packet(block_update_packet)?;
5462
if eid == trigger_eid {
5563
let ack_packet = BlockChangeAck {
56-
sequence: event.sequence.clone(),
64+
sequence: event.sequence,
5765
};
5866
conn.send_packet(ack_packet)?;
5967
}

src/bin/src/systems/send_chunks.rs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use ferrumc_net::packets::outgoing::chunk_batch_start::ChunkBatchStart;
77
use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk;
88
use ferrumc_net_codec::net_types::var_int::VarInt;
99
use ferrumc_state::GlobalState;
10-
use tracing::trace;
10+
use ferrumc_world_gen::errors::WorldGenError::WorldError;
11+
use tracing::{error, trace};
1112

1213
pub fn send_chunks(
1314
state: GlobalState,
@@ -33,25 +34,43 @@ pub fn send_chunks(
3334

3435
let mut chunks_sent = 0;
3536

37+
let mut batch = state.thread_pool.batch();
38+
3639
for (x, z, dim) in chunk_coords {
37-
let chunk = if state.world.chunk_exists(x, z, &dim)? {
38-
state.world.load_chunk(x, z, &dim)?
39-
} else {
40-
trace!("Generating chunk {}x{} in dimension {}", x, z, dim);
41-
let generated_chunk = state.terrain_generator.generate_chunk(x, z)?;
42-
// TODO: Remove this clone
43-
state.world.save_chunk(generated_chunk.clone())?;
44-
generated_chunk
45-
};
46-
assert_eq!(chunk.x, x);
47-
assert_eq!(chunk.z, z);
48-
let packet = ChunkAndLightData::from_chunk(&chunk)?;
49-
conn.send_packet(packet)?;
50-
// This never actually gets emptied out so if someone goes to enough new chunks and doesn't
51-
// leave the server, this will eventually run out of memory. Should probably be fixed.
52-
// recv.seen.insert((x, z, dim.clone()));
53-
// recv.needs_reload.remove(&(x, z, dim));
54-
chunks_sent += 1;
40+
let state_clone = state.clone();
41+
batch.execute(move || {
42+
let chunk = if state_clone.world.chunk_exists(x, z, &dim).unwrap_or(false) {
43+
state_clone
44+
.world
45+
.load_chunk(x, z, &dim)
46+
.map_err(WorldError)?
47+
} else {
48+
trace!("Generating chunk {}x{} in dimension {}", x, z, dim);
49+
// Don't bother saving the chunk if it hasn't been edited yet
50+
state_clone.terrain_generator.generate_chunk(x, z)?
51+
};
52+
Ok((ChunkAndLightData::from_chunk(&chunk), x, z))
53+
})
54+
}
55+
56+
let packets = batch.wait();
57+
58+
for packet in packets {
59+
match packet {
60+
Ok((packet, x, z)) => {
61+
trace!("Sending chunk data for chunk at coordinates ({}, {})", x, z);
62+
conn.send_packet(packet?)?;
63+
chunks_sent += 1;
64+
}
65+
Err(WorldError(e)) => {
66+
error!("Failed to generate or load chunk: {:?}", e);
67+
continue;
68+
}
69+
Err(e) => {
70+
error!("Unexpected error while processing chunk: {:?}", e);
71+
continue;
72+
}
73+
}
5574
}
5675

5776
let batch_end_packet = ChunkBatchFinish {

src/lib/core/state/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ bevy_ecs = { workspace = true }
88
ferrumc-world = { workspace = true }
99
ferrumc-world-gen = { workspace = true }
1010
dashmap = { workspace = true }
11+
ferrumc-threadpool = { workspace = true }

src/lib/core/state/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bevy_ecs::prelude::Resource;
22
use dashmap::DashMap;
3+
use ferrumc_threadpool::ThreadPool;
34
use ferrumc_world::World;
45
use ferrumc_world_gen::WorldGenerator;
56
use std::sync::atomic::AtomicBool;
@@ -10,6 +11,7 @@ pub struct ServerState {
1011
pub terrain_generator: WorldGenerator,
1112
pub shut_down: AtomicBool,
1213
pub players: DashMap<u128, String>,
14+
pub thread_pool: ThreadPool,
1315
}
1416

1517
pub type GlobalState = Arc<ServerState>;

src/lib/net/crates/codec/src/net_types/var_int.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use tokio::io::AsyncRead;
1010
use tokio::io::AsyncWriteExt;
1111
use tokio::io::{AsyncReadExt, AsyncWrite};
1212

13-
#[derive(Debug, Encode, Decode, Clone, DeepSizeOf, PartialEq, Eq, PartialOrd, Ord)]
13+
#[derive(Debug, Encode, Decode, Clone, DeepSizeOf, PartialEq, Eq, PartialOrd, Ord, Copy)]
1414
pub struct VarInt(pub i32);
1515

1616
mod adapters {

0 commit comments

Comments
 (0)