Skip to content

Commit 719a770

Browse files
committed
Merge branch 'import_optimize' of https://github.com/0xnim/ferrumc into import_optimize
2 parents ce064d9 + 669bbf6 commit 719a770

49 files changed

Lines changed: 1325 additions & 542 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.etc/example-config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ max_players = 100
1010
network_tick_rate = 30
1111
# World name to load
1212
world = "world"
13+
# Whether the server should validate players via the whitelist
14+
whitelist = false
1315
# Network compression threshold (can be negative). This decides how long a packet has to be before it is compressed.
1416
# Very small packets may actually increase in size when compressed, so setting it to 0 won't be perfect in all situations.
1517
# Set to -1 to disable compression.
@@ -34,3 +36,5 @@ map_size = 1_000
3436
cache_ttl = 60
3537
# How big the cache can be in kb.
3638
cache_capacity = 20_000
39+
40+
whitelist = false

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# - Workspace lints
77
# - Workspace dependencies.
88

9+
910
[workspace]
1011
resolver = "2"
1112

@@ -104,6 +105,7 @@ ferrumc-utils = { path = "src/lib/utils" }
104105
ferrumc-world = { path = "src/lib/world" }
105106

106107

108+
107109
# Asynchronous
108110
tokio = { version = "1.40.0", features = ["full"] }
109111
socket2 = "0.5.7"
@@ -114,12 +116,14 @@ async-trait = "0.1.82"
114116
tracing = "0.1.40"
115117
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
116118
log = "0.4.22"
119+
console-subscriber = "0.4.1"
117120

118121
# Concurrency/Parallelism
119122
parking_lot = "0.12.3"
120123
rayon = "1.10.0"
121124

122125
# Network
126+
reqwest = { version = "0.12.9", features = ["json"] }
123127

124128
# Error handling
125129
thiserror = "2.0.3"
@@ -136,6 +140,7 @@ serde_derive = "1.0.210"
136140
base64 = "0.22.1"
137141
bitcode = "0.6.3"
138142
bitcode_derive = "0.6.3"
143+
toml = "0.8.19"
139144

140145
# Bit manipulation
141146
byteorder = "1.5.0"
@@ -145,6 +150,7 @@ hashbrown = "0.15.0"
145150
tinyvec = "1.8.0"
146151
dashmap = "6.1.0"
147152
uuid = { version = "1.1", features = ["v4", "v3", "serde"] }
153+
whirlwind = "0.1.1"
148154

149155
# Macros
150156
lazy_static = "1.5.0"
@@ -180,6 +186,7 @@ colored = "2.1.0"
180186
# Misc
181187
deepsize = "0.2.0"
182188
page_size = "0.6.0"
189+
regex = "1.11.1"
183190

184191
# I/O
185192
tempfile = "3.12.0"

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ our [Discord server](https://discord.gg/qT5J8EMjwk) for help or to discuss the p
7272
<h4>📝 Custom made network, NBT and Anvil encoding systems to allow for minimal I/O lag</h4>
7373
</li>
7474
<li>
75-
<h4>💾 Multiple database options to finetune the server to your needs</h4>
75+
<h4>💾 Crazy fast K/V database </h4>
7676
<i>32 render distance*</i>
7777
<img src="https://github.com/ferrumc-rs/ferrumc/blob/master/assets/README/chunk_loading.gif?raw=true" alt="Chunk Loading DEMO">
7878
</li>
@@ -94,7 +94,7 @@ our [Discord server](https://discord.gg/qT5J8EMjwk) for help or to discuss the p
9494
<h4>Optimizations</h4>
9595
</li>
9696
<li>
97-
<h4>Plugin support (JVM currently, other languages will be considered later)</h4>
97+
<h4>Plugin support (FFI currently, other languages will be considered later)</h4>
9898
</li>
9999
</ul>
100100

@@ -148,9 +148,23 @@ cargo build --release
148148

149149
## 🖥️ Usage
150150

151+
```plaintext
152+
Usage: ferrumc.exe [OPTIONS] [COMMAND]
153+
154+
Commands:
155+
setup Sets up the config
156+
import Import the world data
157+
run Start the server (default, if no command is given)
158+
help Print this message or the help of the given subcommand(s)
159+
160+
Options:
161+
--log <LOG> [default: debug] [possible values: trace, debug, info, warn, error]
162+
-h, --help Print help
163+
```
164+
151165
1. Move the FerrumC binary (`ferrumc.exe` or `ferrumc` depending on the OS) to your desired server directory
152166
2. Open a terminal in that directory
153-
3. (Optional) Generate a config file: `./ferrumc --setup`
167+
3. (Optional) Generate a config file: `./ferrumc setup`
154168
- Edit the generated `config.toml` file to customize your server settings
155169
4. Import an existing world: Either copy your world files to the server directory or specify the path to the world files
156170
in the `config.toml` file. This should be the root directory of your world files, containing the `region` directory
@@ -218,10 +232,9 @@ with the vanilla server, but we do plan on implementing some sort of terrain gen
218232
219233
### Will there be plugins? And how?
220234
221-
We do very much plan to have a plugin system and as of right now, our plan is to leverage the
222-
JVM to allow for plugins to be written in Kotlin, Java, or any other JVM language. We are also considering other
223-
languages
224-
such as Rust, JavaScript and possibly other native languages, but that is a fair way off for now.
235+
We do very much plan to have a plugin system and as of right now we are planning to use
236+
some kind of ffi (foreign function interface) to allow for plugins to be written in other languages.
237+
Not confirmed yet.
225238
226239
### What does 'FerrumC' mean?
227240

scripts/new_packet.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os.path
2+
3+
incoming_template = """
4+
use crate::packets::IncomingPacket;
5+
use crate::NetResult;
6+
use ferrumc_macros::{packet, NetDecode};
7+
use ferrumc_state::ServerState;
8+
use std::sync::Arc;
9+
10+
#[derive(NetDecode)]
11+
#[packet(packet_id = ++id++, state = "play")]
12+
pub struct ++name++ {
13+
}
14+
15+
impl IncomingPacket for ++name++ {
16+
async fn handle(self, conn_id: usize, state: Arc<ServerState>) -> NetResult<()> {
17+
todo!()
18+
}
19+
}
20+
"""
21+
22+
outgoing_template = """
23+
use ferrumc_macros::{packet, NetEncode};\
24+
use std::io::Write;
25+
26+
#[derive(NetEncode)]
27+
#[packet(packet_id = ++id++)]
28+
pub struct ++name++ {}
29+
"""
30+
31+
32+
def to_snake_case(string) -> str:
33+
return string.lower().replace(" ", "_")
34+
35+
36+
def to_camel_case(string) -> str:
37+
return string.title().replace(" ", "")
38+
39+
40+
packet_type_input = input("Incoming or outgoing packet? (i/o): ")
41+
packet_type = ""
42+
if packet_type_input == "i":
43+
packet_type = "incoming"
44+
elif packet_type_input == "o":
45+
packet_type = "outgoing"
46+
else:
47+
print("Invalid input")
48+
exit()
49+
50+
packet_name = input("Packet name: ")
51+
packets_dir = os.path.join(os.path.join(os.path.dirname(__file__), ".."), "src/lib/net/src/packets")
52+
53+
packet_id = input("Packet ID (formatted like 0x01): ")
54+
packet_id = packet_id[:-2] + packet_id[-2:].upper()
55+
56+
with open(f"{packets_dir}/{packet_type}/{to_snake_case(packet_name)}.rs", "x") as f:
57+
if packet_type == "incoming":
58+
f.write(incoming_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id))
59+
with open(f"{packets_dir}/incoming/mod.rs", "a") as modfile:
60+
modfile.write(f"\npub mod {to_snake_case(packet_name)};")
61+
else:
62+
f.write(outgoing_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id))
63+
with open(f"{packets_dir}/outgoing/mod.rs", "a") as modfile:
64+
modfile.write(f"\npub mod {to_snake_case(packet_name)};")

src/bin/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ ferrumc-general-purpose = { workspace = true }
2828
ferrumc-state = { workspace = true }
2929

3030
ctor = { workspace = true }
31-
parking_lot = { workspace = true }
31+
parking_lot = { workspace = true, features = ["deadlock_detection"] }
3232
tracing = { workspace = true }
3333
tokio = { workspace = true }
3434
rayon = { workspace = true }

src/bin/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub struct CLIArgs {
66
#[command(subcommand)]
77
pub command: Option<Command>,
88
#[clap(long)]
9-
#[arg(value_enum, default_value_t = LogLevel(Level::TRACE))]
9+
#[arg(value_enum, default_value_t = LogLevel(Level::DEBUG))]
1010
pub log: LogLevel,
1111
}
1212

src/bin/src/main.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ extern crate core;
55
use crate::errors::BinaryError;
66
use clap::Parser;
77
use ferrumc_config::statics::get_global_config;
8+
use ferrumc_config::whitelist::create_whitelist;
9+
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
810
use ferrumc_ecs::Universe;
911
use ferrumc_general_purpose::paths::get_root_path;
12+
use ferrumc_net::connection::StreamWriter;
1013
use ferrumc_net::server::create_server_listener;
1114
use ferrumc_state::ServerState;
1215
use ferrumc_world::World;
16+
use std::hash::{Hash, Hasher};
1317
use std::sync::Arc;
1418
use systems::definition;
1519
use tracing::{error, info};
@@ -27,6 +31,19 @@ async fn main() {
2731
let cli_args = CLIArgs::parse();
2832
ferrumc_logging::init_logging(cli_args.log.into());
2933

34+
check_deadlocks();
35+
36+
{
37+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
38+
std::any::TypeId::of::<ChunkReceiver>().hash(&mut hasher);
39+
let digest = hasher.finish();
40+
println!("ChunkReceiver: {:X}", digest);
41+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
42+
std::any::TypeId::of::<StreamWriter>().hash(&mut hasher);
43+
let digest = hasher.finish();
44+
println!("StreamWriter: {:X}", digest);
45+
}
46+
3047
match cli_args.command {
3148
Some(Command::Setup) => {
3249
info!("Starting setup...");
@@ -59,10 +76,11 @@ async fn main() {
5976
async fn entry() -> Result<()> {
6077
let state = create_state().await?;
6178
let global_state = Arc::new(state);
79+
create_whitelist().await;
6280

6381
let all_system_handles = tokio::spawn(definition::start_all_systems(global_state.clone()));
6482

65-
// Start the systems and wait until all of them are done
83+
//Start the systems and wait until all of them are done
6684
all_system_handles.await??;
6785

6886
// Stop all systems
@@ -107,3 +125,28 @@ async fn create_state() -> Result<ServerState> {
107125
world: World::new().await,
108126
})
109127
}
128+
fn check_deadlocks() {
129+
{
130+
use parking_lot::deadlock;
131+
use std::thread;
132+
use std::time::Duration;
133+
134+
// Create a background thread which checks for deadlocks every 10s
135+
thread::spawn(move || loop {
136+
thread::sleep(Duration::from_secs(10));
137+
let deadlocks = deadlock::check_deadlock();
138+
if deadlocks.is_empty() {
139+
continue;
140+
}
141+
142+
println!("{} deadlocks detected", deadlocks.len());
143+
for (i, threads) in deadlocks.iter().enumerate() {
144+
println!("Deadlock #{}", i);
145+
for t in threads {
146+
println!("Thread Id {:#?}", t.thread_id());
147+
println!("{:#?}", t.backtrace());
148+
}
149+
}
150+
});
151+
}
152+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use ferrumc_macros::event_handler;
2+
use ferrumc_net::errors::NetError;
3+
use ferrumc_net::packets::outgoing::entity_animation::EntityAnimationEvent;
4+
use ferrumc_net::utils::broadcast::{broadcast, BroadcastOptions};
5+
use ferrumc_state::GlobalState;
6+
7+
#[event_handler]
8+
async fn entity_animation(
9+
event: EntityAnimationEvent,
10+
state: GlobalState,
11+
) -> Result<EntityAnimationEvent, NetError> {
12+
//TODO change this global broadcast to a broadcast that affects only players in the view distance
13+
// of the player doing it, but as long as we still cant see other players, this will be fine.
14+
broadcast(
15+
&event.packet,
16+
&state,
17+
BroadcastOptions::default().except([event.entity]),
18+
)
19+
.await?;
20+
Ok(event)
21+
}

src/bin/src/packet_handlers/login_process.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use ferrumc_config::statics::{get_global_config, get_whitelist};
2+
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
13
use ferrumc_core::identity::player_identity::PlayerIdentity;
24
use ferrumc_core::transform::grounded::OnGround;
35
use ferrumc_core::transform::position::Position;
@@ -15,6 +17,7 @@ use ferrumc_net::packets::outgoing::client_bound_known_packs::ClientBoundKnownPa
1517
use ferrumc_net::packets::outgoing::finish_configuration::FinishConfigurationPacket;
1618
use ferrumc_net::packets::outgoing::game_event::GameEventPacket;
1719
use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket;
20+
use ferrumc_net::packets::outgoing::login_disconnect::LoginDisconnectPacket;
1821
use ferrumc_net::packets::outgoing::login_play::LoginPlayPacket;
1922
use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket;
2023
use ferrumc_net::packets::outgoing::registry_data::get_registry_packets;
@@ -31,23 +34,47 @@ async fn handle_login_start(
3134
login_start_event: LoginStartEvent,
3235
state: GlobalState,
3336
) -> Result<LoginStartEvent, NetError> {
34-
debug!("Handling login start event");
35-
3637
let uuid = login_start_event.login_start_packet.uuid;
3738
let username = login_start_event.login_start_packet.username.as_str();
38-
debug!("Received login start from user with username {}", username);
39+
let player_identity = PlayerIdentity::new(username.to_string(), uuid);
40+
debug!("Handling login start event for user: {username}, uuid: {uuid}");
3941

4042
// Add the player identity component to the ECS for the entity.
41-
state.universe.add_component::<PlayerIdentity>(
42-
login_start_event.conn_id,
43-
PlayerIdentity::new(username.to_string(), uuid),
44-
)?;
43+
state
44+
.universe
45+
.add_component::<PlayerIdentity>(
46+
login_start_event.conn_id,
47+
PlayerIdentity::new(username.to_string(), uuid),
48+
)?
49+
.add_component::<ChunkReceiver>(login_start_event.conn_id, ChunkReceiver::default())?;
4550

4651
//Send a Login Success Response to further the login sequence
4752
let mut writer = state
4853
.universe
4954
.get_mut::<StreamWriter>(login_start_event.conn_id)?;
5055

56+
if get_global_config().whitelist {
57+
let whitelist = get_whitelist();
58+
59+
if whitelist.get(&uuid).is_none() {
60+
writer
61+
.send_packet(
62+
&LoginDisconnectPacket::new(
63+
"{\"translate\":\"multiplayer.disconnect.not_whitelisted\"}",
64+
),
65+
&NetEncodeOpts::WithLength,
66+
)
67+
.await?;
68+
return Ok(login_start_event);
69+
}
70+
}
71+
72+
// Add the player identity component to the ECS for the entity.
73+
state
74+
.universe
75+
.add_component::<PlayerIdentity>(login_start_event.conn_id, player_identity)?;
76+
77+
//Send a Login Success Response to further the login sequence
5178
writer
5279
.send_packet(
5380
&LoginSuccessPacket::new(uuid, username),
@@ -168,6 +195,12 @@ async fn handle_ack_finish_configuration(
168195
&NetEncodeOpts::WithLength,
169196
)
170197
.await?;
198+
199+
let pos = state.universe.get_mut::<Position>(conn_id)?;
200+
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(conn_id)?;
201+
chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld")));
202+
chunk_recv.calculate_chunks().await;
203+
171204
send_keep_alive(conn_id, state, &mut writer).await?;
172205

173206
Ok(ack_finish_configuration_event)

src/bin/src/packet_handlers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod animations;
12
mod handshake;
23
mod login_process;
34
mod tick_handler;

0 commit comments

Comments
 (0)