Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5169a05
fix: speed up the block! and block_match! macro
lexeyOK Oct 31, 2025
ec07508
placeholders and multi-value
lexeyOK Nov 6, 2025
faaea4a
add .. to allow skip missing properties
lexeyOK Nov 7, 2025
5490552
add _ in props position and use `block!` macro in worldgen crate
lexeyOK Nov 7, 2025
39ceafc
updete phf dependency
lexeyOK Dec 8, 2025
6f3a75a
updete phf dependency
lexeyOK Dec 8, 2025
1ea558e
unused import
lexeyOK Dec 8, 2025
1e1e271
remove skipped generated bock! tests
lexeyOK Dec 8, 2025
967de82
introduce Position structs for the Chunk impl (#284)
Mcrtin Dec 9, 2025
a46fb5c
feat: allow player to swim (#290)
Tonguechaude Dec 21, 2025
dbbd06a
feat: Server Statistics (#296)
Outspending Dec 22, 2025
f4613ee
feat : add gravity/collisions system (#276)
Tonguechaude Dec 26, 2025
8317369
Add particle packets and systems (#297)
ReCore-sys Dec 28, 2025
e239b9e
feat : add ungrounding system for entities (#298)
Tonguechaude Dec 28, 2025
30ad800
fix: speed up the block! and block_match! macro
lexeyOK Oct 31, 2025
06a0109
placeholders and multi-value
lexeyOK Nov 6, 2025
7fbd8ed
add .. to allow skip missing properties
lexeyOK Nov 7, 2025
47eb7ed
add _ in props position and use `block!` macro in worldgen crate
lexeyOK Nov 7, 2025
1923a5f
updete phf dependency
lexeyOK Dec 8, 2025
03112ae
updete phf dependency
lexeyOK Dec 8, 2025
de37469
unused import
lexeyOK Dec 8, 2025
4d1fc0b
remove skipped generated bock! tests
lexeyOK Dec 8, 2025
a39b96b
Merge remote-tracking branch 'lexeyOK/fix/clean-block-macro-speedup' …
ReCore-sys Dec 28, 2025
e9c7340
Fixes for merge
ReCore-sys Dec 28, 2025
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
1 change: 0 additions & 1 deletion .etc/example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,3 @@ map_size = 1_000
cache_ttl = 60
# How big the cache can be in kb.
cache_capacity = 20_000

75 changes: 39 additions & 36 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ members = [
"src/lib/messages",
"src/lib/data",
"src/lib/entities",
"src/lib/physics",
"src/lib/performance",
"src/lib/particles",
]

[workspace.package]
Expand Down Expand Up @@ -104,6 +107,7 @@ ferrumc-logging = { path = "src/lib/utils/logging" }
ferrumc-macros = { path = "src/lib/derive_macros" }
ferrumc-nbt = { path = "src/lib/adapters/nbt" }
ferrumc-net = { path = "src/lib/net" }
ferrumc-performance = { path = "src/lib/performance" }
ferrumc-net-codec = { path = "src/lib/net/crates/codec" }
ferrumc-net-encryption = { path = "src/lib/net/crates/encryption" }
ferrumc-plugins = { path = "src/lib/plugins" }
Expand All @@ -121,7 +125,9 @@ ferrumc-inventories = { path = "src/lib/inventories" }
ferrumc-components = { path = "src/lib/components" }
ferrumc-messages = { path = "src/lib/messages" }
ferrumc-data = { path = "src/lib/data" }
ferrumc-entities = { path = "src/lib/entities"}
ferrumc-entities = { path = "src/lib/entities" }
ferrumc-physics = { path = "src/lib/physics" }
ferrumc-particles = { path = "src/lib/particles" }

# Asynchronous
tokio = { version = "1.48.0", features = [
Expand All @@ -135,10 +141,10 @@ tokio = { version = "1.48.0", features = [
], default-features = false }

# Logging
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
tracing-appender = "0.2.3"
log = "0.4.28"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
tracing-appender = "0.2.4"
log = "0.4.29"

# Concurrency/Parallelism
parking_lot = "0.12.5"
Expand All @@ -148,33 +154,31 @@ rusty_pool = "0.7.0"
crossbeam-queue = "0.3.12"

# Network
ureq = "3.1.2"
reqwest = { version = "0.12.24", features = ["json"] }
ureq = "3.1.4"
reqwest = { version = "0.12.28", features = ["json"] }

# Error handling
thiserror = "2.0.17"

# Cryptography
rand = "0.10.0-rc.0"
rand = "0.9.2"
fnv = "1.0.7"
wyhash = "0.6.0"
ahash = "0.8.12"
rsa = { version = "0.10.0-rc.10" }
rsa = { version = "0.9.9" }
aes = { version = "0.8.4" }
cfb8 = "0.8.1"
sha1 = "0.10.6"
num-bigint = "0.4.6"

# Encoding/Serialization
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
serde_json = "1.0.146"
serde_derive = "1.0.228"
base64 = "0.22.1"

# When if bitcode's latest version has been changed, see if clippy still complains about
# src/lib/net/src/packets/outgoing/chunk_and_light_data.rs
#bitcode = "0.6.7"
#bitcode_derive = "0.6.7"
bitcode = "0.6.9"
bitcode_derive = "0.6.9"
toml = "0.9.8"
craftflow-nbt = "2.1.0"
figment = { version = "0.10.19", features = ["toml", "env"] }
Expand All @@ -185,59 +189,58 @@ byteorder = "1.5.0"

# Data types
dashmap = "7.0.0-rc2"
uuid = { version = "1.18.1", features = ["v4", "v3", "serde"] }
indexmap = { version = "2.12.0", features = ["serde"] }
uuid = { version = "1.19.0", features = ["v4", "v3", "serde"] }
indexmap = { version = "2.12.1", features = ["serde"] }
bimap = "0.6.3"

# Macros
lazy_static = "1.5.0"
quote = "1.0.41"
syn = "2.0.106"
proc-macro2 = "1.0.101"
quote = "1.0.42"
syn = "2.0.111"
proc-macro2 = "1.0.103"
paste = "1.0.15"
maplit = "1.0.2"
macro_rules_attribute = "0.2.2"
enum_discriminant = "1.0.1"

# Magic
dhat = "0.3.3"
ctor = "0.6.0"
ctor = "0.6.3"

# Compression/Decompression
flate2 = { version = "1.1.4", features = ["zlib"], default-features = false }
flate2 = { version = "1.1.5", features = ["zlib"], default-features = false }
lzzzz = "2.0.0"
yazi = "0.2.1"

# Database
heed = "0.22.0"
moka = "0.12.11"
heed = "0.22.1-nested-rtxns-6"
moka = "0.12.12"

# CLI
clap = "4.5.49"
indicatif = "0.18.0"
clap = "4.5.53"
indicatif = "0.18.3"
colored = "3.0.0"

# Misc
deepsize = "0.2.0"
page_size = "0.6.0"
enum-ordinalize = "4.3.0"
enum-ordinalize = "4.3.2"
regex = "1.12.2"
noise = "0.9.0"
ctrlc = "3.5.0"
ctrlc = "3.5.1"
num_cpus = "1.17.0"
typename = "0.1.2"
bevy_ecs = { version = "0.17.2", features = ["multi_threaded", "trace"] }
bevy_math = "0.16.1"
bevy_ecs = { version = "0.17.3", features = ["multi_threaded", "trace"] }
bevy_math = "0.17.3"
once_cell = "1.21.3"
sysinfo = "0.37.2"

# I/O
memmap2 = "0.9.8"
memmap2 = "0.9.9"
tempfile = "3.23.0"

# Benchmarking
criterion = { version = "0.7.0", features = ["html_reports"] }


bitcode = { git = "https://github.com/SoftbearStudios/bitcode" }
bitcode_derive = { git = "https://github.com/SoftbearStudios/bitcode" }

phf = { version = "0.11", features = ["macros"] }
phf_codegen = { version = "0.11" }
phf = { version = "0.13", features = ["macros"] }
phf_codegen = { version = "0.13" }
49 changes: 27 additions & 22 deletions docs/protocol-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ stateDiagram-v2

## Connection States

| State | Purpose | Code Reference |
|-------|---------|----------------|
| **Handshake** | Initial packet determines intent | `ConnState::Handshake` in [`lib.rs`](../src/lib/net/src/lib.rs) |
| **Status** | Server list ping (MOTD, player count) | `ConnState::Status` |
| **Login** | Authentication, compression setup | `ConnState::Login` |
| **Configuration** | Registry data, resource packs | `ConnState::Configuration` |
| **Play** | Actual gameplay packets | `ConnState::Play` |
| State | Purpose | Code Reference |
|-------------------|---------------------------------------|-----------------------------------------------------------------|
| **Handshake** | Initial packet determines intent | `ConnState::Handshake` in [`lib.rs`](../src/lib/net/src/lib.rs) |
| **Status** | Server list ping (MOTD, player count) | `ConnState::Status` |
| **Login** | Authentication, compression setup | `ConnState::Login` |
| **Configuration** | Registry data, resource packs | `ConnState::Configuration` |
| **Play** | Actual gameplay packets | `ConnState::Play` |

---

Expand Down Expand Up @@ -125,14 +125,14 @@ sequenceDiagram

### Code Flow

| Step | Function | File |
|------|----------|------|
| 1. TCP Accept | `tcp_conn_acceptor()` | [`game_loop.rs`](../src/bin/src/game_loop.rs#L309) |
| 2. Connection Handler | `handle_connection()` | [`connection.rs`](../src/lib/net/src/connection.rs#L253) |
| 3. Handshake | `handle_handshake()` | [`conn_init/mod.rs`](../src/lib/net/src/conn_init/mod.rs#L60) |
| 4. Login Sequence | `login()` | [`conn_init/login.rs`](../src/lib/net/src/conn_init/login.rs#L50) |
| 5. ECS Registration | `accept_new_connections()` | [`new_connections.rs`](../src/bin/src/systems/new_connections.rs) |
| 6. Packet Loop | `handle_connection()` recv loop | [`connection.rs`](../src/lib/net/src/connection.rs#L358) |
| Step | Function | File |
|-----------------------|---------------------------------|-------------------------------------------------------------------|
| 1. TCP Accept | `tcp_conn_acceptor()` | [`game_loop.rs`](../src/bin/src/game_loop.rs#L309) |
| 2. Connection Handler | `handle_connection()` | [`connection.rs`](../src/lib/net/src/connection.rs#L253) |
| 3. Handshake | `handle_handshake()` | [`conn_init/mod.rs`](../src/lib/net/src/conn_init/mod.rs#L60) |
| 4. Login Sequence | `login()` | [`conn_init/login.rs`](../src/lib/net/src/conn_init/login.rs#L50) |
| 5. ECS Registration | `accept_new_connections()` | [`new_connections.rs`](../src/bin/src/systems/new_connections.rs) |
| 6. Packet Loop | `handle_connection()` recv loop | [`connection.rs`](../src/lib/net/src/connection.rs#L358) |

---

Expand All @@ -141,6 +141,7 @@ sequenceDiagram
All packets follow this format:

### Uncompressed

```
┌──────────────┬────────────┬──────────────────┐
│ Length │ Packet ID │ Data │
Expand All @@ -149,6 +150,7 @@ All packets follow this format:
```

### Compressed (when data length > threshold)

```
┌──────────────┬────────────────┬────────────────────────┐
│ Packet Len │ Data Length │ Compressed │
Expand Down Expand Up @@ -176,19 +178,19 @@ flowchart LR

### Key Components

| Component | Purpose | File |
|-----------|---------|------|
| `StreamWriter` | Async packet output | [`connection.rs`](../src/lib/net/src/connection.rs#L41) |
| `EncryptedReader` | AES-encrypted input | `ferrumc_net_encryption` |
| `PacketSkeleton` | Parsed packet frame | [`packet_skeleton.rs`](../src/lib/net/src/packets/incoming/packet_skeleton.rs) |
| `PacketSender` | Channel to ECS | [`lib.rs`](../src/lib/net/src/lib.rs) |
| Component | Purpose | File |
|-------------------|---------------------|--------------------------------------------------------------------------------|
| `StreamWriter` | Async packet output | [`connection.rs`](../src/lib/net/src/connection.rs#L41) |
| `EncryptedReader` | AES-encrypted input | `ferrumc_net_encryption` |
| `PacketSkeleton` | Parsed packet frame | [`packet_skeleton.rs`](../src/lib/net/src/packets/incoming/packet_skeleton.rs) |
| `PacketSender` | Channel to ECS | [`lib.rs`](../src/lib/net/src/lib.rs) |

### Packet Handler Registration

Packets are dispatched via the `setup_packet_handling!` macro:

```rust
// src/lib/net/src/lib.rs
// src/lib/net/src/mod
setup_packet_handling!("\\src\\packets\\incoming");
```

Expand All @@ -211,6 +213,7 @@ flowchart TD
```

**Example**: Player join triggers `PlayerJoined` event:

- [`new_connections.rs`](../src/bin/src/systems/new_connections.rs) sends `PlayerJoined`
- Listeners in [`register_gameplay_listeners()`](../src/bin/src/systems/listeners/mod.rs) react

Expand Down Expand Up @@ -248,10 +251,12 @@ See [`game_loop.rs`](../src/bin/src/game_loop.rs) for the threading setup.
## Quick Reference

### Protocol Version

- **Minecraft**: 1.21.8
- **Protocol**: 772
- **Constant**: `PROTOCOL_VERSION_1_21_8` in [`conn_init/mod.rs`](../src/lib/net/src/conn_init/mod.rs#L31)

### External References

- [Minecraft Protocol Wiki](https://minecraft.wiki/w/Java_Edition_protocol/Packets)
- [Protocol Version Numbers](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers)
3 changes: 3 additions & 0 deletions src/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ferrumc-registry = { workspace = true }
ferrumc-messages = { workspace = true }
ferrumc-components = { workspace = true }
ferrumc-net = { workspace = true }
ferrumc-performance = { workspace = true }
ferrumc-net-codec = { workspace = true }
ferrumc-plugins = { workspace = true }
ferrumc-storage = { workspace = true }
Expand All @@ -36,6 +37,8 @@ ferrumc-threadpool = { workspace = true }
ferrumc-inventories = { workspace = true }
ferrumc-data = { workspace = true }
ferrumc-entities = { workspace = true }
ferrumc-physics = { workspace = true }
ferrumc-particles = { workspace = true }
bevy_math = { workspace = true }
once_cell = { workspace = true }
serde_json = { workspace = true }
Expand Down
22 changes: 22 additions & 0 deletions src/bin/src/game_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::register_messages::register_messages;
use crate::register_resources::register_resources;
use crate::systems::lan_pinger::LanPinger;
use crate::systems::listeners::register_gameplay_listeners;
use crate::systems::mobs::register_mob_systems;
use crate::systems::physics::register_physics;
use crate::systems::register_game_systems;
use crate::systems::shutdown_systems::register_shutdown_systems;
use bevy_ecs::prelude::World;
Expand All @@ -22,6 +24,8 @@ use ferrumc_config::server_config::get_global_config;
use ferrumc_net::connection::{handle_connection, NewConnection};
use ferrumc_net::server::create_server_listener;
use ferrumc_net::PacketSender;
use ferrumc_performance::tick::TickData;
use ferrumc_performance::ServerPerformance;
use ferrumc_scheduler::MissedTickBehavior;
use ferrumc_scheduler::{drain_registered_schedules, Scheduler, TimedSchedule};
use ferrumc_state::{GlobalState, GlobalStateResource};
Expand Down Expand Up @@ -114,11 +118,14 @@ pub fn start_game_loop(global_state: GlobalState) -> Result<(), BinaryError> {
// This prevents starvation if we fall behind (e.g., after a lag spike).
const MAX_GLOBAL_CATCH_UP: usize = 64;

let tick_zero = Instant::now();

// Main loop - runs until shutdown flag is set (e.g., via Ctrl+C or /stop command)
while !global_state
.shut_down
.load(std::sync::atomic::Ordering::Relaxed)
{
let tick_start = Instant::now();
let mut ran_any = false;
let mut ran_count = 0;

Expand Down Expand Up @@ -182,9 +189,22 @@ pub fn start_game_loop(global_state: GlobalState) -> Result<(), BinaryError> {
ran_count += 1;
}

let tick_duration = tick_start.elapsed();

// If no schedules were ready, sleep until the next one is due
// If schedules were ran, store tick data.
if !ran_any {
timed.park_until_next_due();
} else {
let tick_data = TickData {
start_ns: tick_zero.elapsed().as_nanos(),
duration_ns: tick_duration.as_nanos(),
entity_count: 0,
ran_count,
};

let mut performance = ecs_world.resource_mut::<ServerPerformance>();
performance.tps.record_tick(tick_data);
}
}

Expand Down Expand Up @@ -232,6 +252,8 @@ fn build_timed_scheduler() -> Scheduler {
register_command_systems(s); // Process queued commands
register_game_systems(s); // General game logic
register_gameplay_listeners(s); // Event listeners for gameplay events
register_physics(s); // Physics systems (movement, collision, etc.)
register_mob_systems(s); // Mob AI and behavior
};
let tick_period = Duration::from_secs(1) / get_global_config().tps;
timed.register(
Expand Down
6 changes: 4 additions & 2 deletions src/bin/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ferrumc_state::player_cache::PlayerCache;
use ferrumc_state::player_list::PlayerList;
use ferrumc_state::{GlobalState, ServerState};
use ferrumc_threadpool::ThreadPool;
use ferrumc_world::pos::ChunkPos;
use ferrumc_world::World;
use ferrumc_world_gen::WorldGenerator;
use std::sync::Arc;
Expand Down Expand Up @@ -44,14 +45,15 @@ pub fn generate_spawn_chunks(state: GlobalState) -> Result<(), BinaryError> {
for (x, z) in chunks {
let state_clone = state.clone();
batch.execute(move || {
let pos = ChunkPos::new(x, z);
let chunk = state_clone
.terrain_generator
.generate_chunk(x, z)
.generate_chunk(pos)
.map(Arc::new);

match chunk {
Ok(chunk) => {
if let Err(e) = state_clone.world.save_chunk(chunk) {
if let Err(e) = state_clone.world.save_chunk(pos, "overworld", chunk) {
error!("Error saving chunk ({}, {}): {:?}", x, z, e);
}
}
Expand Down
Loading