diff --git a/.etc/example-config.toml b/.etc/example-config.toml
index cc43e3fc..a0cef8e2 100644
--- a/.etc/example-config.toml
+++ b/.etc/example-config.toml
@@ -36,3 +36,5 @@ map_size = 1_000
cache_ttl = 60
# How big the cache can be in kb.
cache_capacity = 20_000
+
+whitelist = false
\ No newline at end of file
diff --git a/README.md b/README.md
index 441bce29..9a4ce882 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ our [Discord server](https://discord.gg/qT5J8EMjwk) for help or to discuss the p
📝 Custom made network, NBT and Anvil encoding systems to allow for minimal I/O lag
- 💾 Multiple database options to finetune the server to your needs
+ 💾 Crazy fast K/V database
32 render distance*
@@ -94,7 +94,7 @@ our [Discord server](https://discord.gg/qT5J8EMjwk) for help or to discuss the p
Optimizations
- Plugin support (JVM currently, other languages will be considered later)
+ Plugin support (FFI currently, other languages will be considered later)
@@ -148,9 +148,23 @@ cargo build --release
## 🖥️ Usage
+```plaintext
+Usage: ferrumc.exe [OPTIONS] [COMMAND]
+
+Commands:
+setup Sets up the config
+import Import the world data
+run Start the server (default, if no command is given)
+help Print this message or the help of the given subcommand(s)
+
+Options:
+--log [default: debug] [possible values: trace, debug, info, warn, error]
+-h, --help Print help
+```
+
1. Move the FerrumC binary (`ferrumc.exe` or `ferrumc` depending on the OS) to your desired server directory
2. Open a terminal in that directory
-3. (Optional) Generate a config file: `./ferrumc --setup`
+3. (Optional) Generate a config file: `./ferrumc setup`
- Edit the generated `config.toml` file to customize your server settings
4. Import an existing world: Either copy your world files to the server directory or specify the path to the world files
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
### Will there be plugins? And how?
-We do very much plan to have a plugin system and as of right now, our plan is to leverage the
-JVM to allow for plugins to be written in Kotlin, Java, or any other JVM language. We are also considering other
-languages
-such as Rust, JavaScript and possibly other native languages, but that is a fair way off for now.
+We do very much plan to have a plugin system and as of right now we are planning to use
+some kind of ffi (foreign function interface) to allow for plugins to be written in other languages.
+Not confirmed yet.
### What does 'FerrumC' mean?
diff --git a/scripts/new_packet.py b/scripts/new_packet.py
index 9e8d518b..0f493608 100644
--- a/scripts/new_packet.py
+++ b/scripts/new_packet.py
@@ -61,4 +61,4 @@ def to_camel_case(string) -> str:
else:
f.write(outgoing_template.replace("++name++", to_camel_case(packet_name)).replace("++id++", packet_id))
with open(f"{packets_dir}/outgoing/mod.rs", "a") as modfile:
- modfile.write(f"\npub mod {to_snake_case(packet_name)};")
\ No newline at end of file
+ modfile.write(f"\npub mod {to_snake_case(packet_name)};")
diff --git a/src/bin/src/systems/chunk_fetcher.rs b/src/bin/src/systems/chunk_fetcher.rs
index 47741926..601a34c4 100644
--- a/src/bin/src/systems/chunk_fetcher.rs
+++ b/src/bin/src/systems/chunk_fetcher.rs
@@ -7,7 +7,7 @@ use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use tokio::task::JoinSet;
-use tracing::{error, info, trace};
+use tracing::{debug, info, trace};
pub struct ChunkFetcher {
stop: AtomicBool,
@@ -70,11 +70,11 @@ impl System for ChunkFetcher {
match result {
Ok(task_res) => {
if let Err(e) = task_res {
- error!("Error fetching chunk: {:?}", e);
+ debug!("Error fetching chunk: {:?}", e);
}
}
Err(e) => {
- error!("Error fetching chunk: {:?}", e);
+ debug!("Error fetching chunk: {:?}", e);
}
}
}
diff --git a/src/bin/src/systems/ticking_system.rs b/src/bin/src/systems/ticking_system.rs
index ffe6f21f..6e72394f 100644
--- a/src/bin/src/systems/ticking_system.rs
+++ b/src/bin/src/systems/ticking_system.rs
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::Instant;
-use tracing::{debug, info};
+use tracing::{debug, info, trace};
pub struct TickingSystem;
static KILLED: AtomicBool = AtomicBool::new(false);
@@ -19,12 +19,18 @@ impl System for TickingSystem {
let mut tick = 0;
while !KILLED.load(Ordering::Relaxed) {
let required_end = Instant::now() + Duration::from_millis(50);
- // TODO handle error
- let res = TickEvent::trigger(TickEvent::new(tick), state.clone()).await;
+ let res = {
+ let start = Instant::now();
+ let res = TickEvent::trigger(TickEvent::new(tick), state.clone()).await;
+ trace!("Tick took {:?}", Instant::now() - start);
+
+ res
+ };
if res.is_err() {
debug!("error handling tick event: {:?}", res);
}
let now = Instant::now();
+
if required_end > now {
tokio::time::sleep(required_end - now).await;
} else {
diff --git a/src/lib/derive_macros/src/net/decode.rs b/src/lib/derive_macros/src/net/decode.rs
index 5a964eb9..b3407f27 100644
--- a/src/lib/derive_macros/src/net/decode.rs
+++ b/src/lib/derive_macros/src/net/decode.rs
@@ -1,4 +1,4 @@
-use crate::helpers::{get_derive_attributes, StructInfo};
+use crate::helpers::{extract_struct_info, get_derive_attributes, StructInfo};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, LitStr};
@@ -6,125 +6,124 @@ use syn::{parse_macro_input, DeriveInput, LitStr};
pub(crate) fn derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
+ // Collect attributes relevant to our `net(...)` usage:
let net_attributes = get_derive_attributes(&input, "net");
let repr_attr = get_derive_attributes(&input, "repr");
- // check the type of repr attribute
- let repr_attr = {
- let mut repr_type = None;
- repr_attr.iter().for_each(|attr| {
- attr.parse_nested_meta(|meta| {
- let Some(ident) = meta.path.get_ident() else {
- return Ok(());
- };
-
- repr_type = Some(ident.to_string());
+ // Attempt to parse the `#[repr(...)]` attribute if it exists.
+ let repr_type = {
+ let mut repr_t = None;
+ for attr in &repr_attr {
+ attr.parse_nested_meta(|meta| {
+ if let Some(ident) = meta.path.get_ident() {
+ repr_t = Some(ident.to_string());
+ }
Ok(())
})
.unwrap();
- });
-
- repr_type.map(|val| syn::parse_str::(&val).expect("Failed to parse repr type"))
+ }
+ repr_t.map(|val| syn::parse_str::(&val).expect("Failed to parse repr type"))
};
- // check if any attribute that has "#[net(u8_cast)]"
+ // Look for `#[net(type_cast = "X", type_cast_handler = "Y")]` usage for enum casting.
let (type_cast, type_cast_handler) = {
- let mut type_cast = None;
- let mut type_cast_handler = None;
- net_attributes.iter().for_each(|attr| {
+ let mut cast = None;
+ let mut cast_handler = None;
+ for attr in &net_attributes {
attr.parse_nested_meta(|meta| {
- let Some(ident) = meta.path.get_ident() else {
- return Ok(());
- };
-
- match ident.to_string().as_str() {
- "type_cast" => {
- let value = meta.value().expect("value failed");
- let value = value.parse::().expect("parse failed");
- let n = value.value();
- type_cast = Some(n);
- }
- "type_cast_handler" => {
- let value = meta.value().expect("value failed");
- let value = value.parse::().expect("parse failed");
- let n = value.value();
- type_cast_handler = Some(n);
- }
- &_ => {
- return Ok(());
+ if let Some(ident) = meta.path.get_ident() {
+ match ident.to_string().as_str() {
+ "type_cast" => {
+ let value = meta.value().expect("Missing type_cast value");
+ let value = value.parse::().expect("Failed to parse type_cast");
+ cast = Some(value.value());
+ }
+ "type_cast_handler" => {
+ let value = meta.value().expect("Missing type_cast_handler value");
+ let value = value
+ .parse::()
+ .expect("Failed to parse type_cast_handler");
+ cast_handler = Some(value.value());
+ }
+ _ => {}
}
}
-
Ok(())
})
.unwrap();
- });
-
- (type_cast, type_cast_handler)
+ }
+ (cast, cast_handler)
};
- // So for enums we can simply read the type and then cast it directly.
+ // If `type_cast` is present, we assume this is an enum. We'll decode by reading
+ // the specified type, then casting into the enum.
if let Some(type_cast) = type_cast {
- let Some(repr_attr) = repr_attr else {
- panic!(
- "NetDecode with type_cast enabled requires a repr attribute. Example: #[repr(u8)]"
- );
+ let Some(repr_ident) = repr_type else {
+ panic!("NetDecode with type_cast requires a repr attribute. Example: #[repr(u8)]");
};
- // in netdecode, read a type of type_cast and then if type_cast_handler exists, use it to do `type_cast_handler(type_cast)`
-
- let type_cast = syn::parse_str::(&type_cast).expect("Failed to parse type_cast");
+ let type_cast_ty =
+ syn::parse_str::(&type_cast).expect("Failed to parse type_cast as a type");
let StructInfo {
- struct_name: name,
+ struct_name: enum_name,
impl_generics,
ty_generics,
where_clause,
- lifetime: _lifetime,
..
- } = crate::helpers::extract_struct_info(&input, None);
+ } = extract_struct_info(&input, None);
- let type_cast_handler = match type_cast_handler {
- None => {
- quote! { value }
- }
- Some(handler) => {
- let handler = syn::parse_str::(&handler)
+ let cast_handler_expr = match type_cast_handler {
+ None => quote!(value),
+ Some(handler_str) => {
+ let handler_expr = syn::parse_str::(&handler_str)
.expect("Failed to parse type_cast_handler");
- quote! { #handler }
+ quote!(#handler_expr)
}
};
+ // Build match arms for each variant's discriminant (explicit or implicit).
let enum_arms = if let syn::Data::Enum(data) = &input.data {
- let mut next_discriminant = 0;
+ let mut next_disc = 0;
data.variants
.iter()
.map(|variant| {
- let variant_name = &variant.ident;
- let discriminant = if let Some((_, expr)) = &variant.discriminant {
- // Use the explicit discriminant
- quote! { #expr }
+ let variant_ident = &variant.ident;
+ // If the variant has a discriminant (e.g., `Variant = 5`), use that.
+ // Otherwise, use the running `next_disc`.
+ let disc_expr = if let Some((_, disc)) = &variant.discriminant {
+ quote! { #disc }
} else {
- // Use the next implicit discriminant
- let disc = quote! { #next_discriminant };
- next_discriminant += 1;
- disc
+ let disc_token = quote! { #next_disc };
+ next_disc += 1;
+ disc_token
};
quote! {
- #discriminant => Ok(#name::#variant_name),
+ #disc_expr => Ok(#enum_name::#variant_ident),
}
})
.collect::>()
} else {
- panic!("NetDecode with type_cast enabled can only be derived for enums.");
+ panic!("`#[net(type_cast = ...)]` is only valid on enums.");
};
let expanded = quote! {
- impl #impl_generics ferrumc_net_codec::decode::NetDecode for #name #ty_generics #where_clause {
- fn decode(reader: &mut R, opts: &ferrumc_net_codec::decode::NetDecodeOpts) -> ferrumc_net_codec::decode::NetDecodeResult {
- let value = <#type_cast as ferrumc_net_codec::decode::NetDecode>::decode(reader, opts)?;
- let value = #type_cast_handler;
- let value = value as #repr_attr;
+ impl #impl_generics ferrumc_net_codec::decode::NetDecode
+ for #enum_name #ty_generics
+ #where_clause
+ {
+ fn decode(
+ reader: &mut R,
+ opts: &ferrumc_net_codec::decode::NetDecodeOpts
+ ) -> ferrumc_net_codec::decode::NetDecodeResult {
+ // Decode the initial numeric value
+ let value = <#type_cast_ty as ferrumc_net_codec::decode::NetDecode>::decode(reader, opts)?;
+ // Possibly transform via the handler
+ let value = #cast_handler_expr;
+ // Cast to the repr type
+ let value = value as #repr_ident;
+
+ // Match against the known variant discriminants
match (value as i32) {
#(#enum_arms)*
_ => Err(ferrumc_net_codec::decode::errors::NetDecodeError::InvalidEnumVariant),
@@ -132,41 +131,126 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
}
}
};
-
return TokenStream::from(expanded);
}
- let fields = if let syn::Data::Struct(data) = &input.data {
- &data.fields
- } else {
- panic!("NetDecode can only be derived for structs or enums with u8_cast enabled.");
- };
-
- let decode_fields = fields.iter().map(|field| {
- let field_name = field.ident.as_ref().unwrap();
- let field_ty = &field.ty;
- quote! {
- #field_name: <#field_ty as ferrumc_net_codec::decode::NetDecode>::decode(reader, opts)?,
- }
- });
-
+ // Otherwise, handle struct decoding. We'll check if each field has an optional trigger.
let StructInfo {
struct_name,
impl_generics,
ty_generics,
where_clause,
- lifetime: _lifetime,
..
- } = crate::helpers::extract_struct_info(&input, None);
+ } = extract_struct_info(&input, None);
- let expanded = quote! {
- // impl ferrumc_net_codec::decode::NetDecode for #name {
- impl #impl_generics ferrumc_net_codec::decode::NetDecode for #struct_name #ty_generics #where_clause {
- fn decode(reader: &mut R, opts: &ferrumc_net_codec::decode::NetDecodeOpts) -> ferrumc_net_codec::decode::NetDecodeResult {
- Ok(Self {
- #(#decode_fields)*
+ let fields = match &input.data {
+ syn::Data::Struct(data) => &data.fields,
+ _ => panic!("NetDecode can only be derived for structs or for enums with `u8_cast`."),
+ };
+
+ // Generate per-field decode statements. We'll build them in order, storing
+ // them in local variables named the same as the field, so the subsequent fields
+ // can use them in the optional triggers if needed.
+ let mut decode_statements = Vec::new();
+ let mut field_names = Vec::new();
+
+ for field in fields {
+ let field_name = field
+ .ident
+ .clone()
+ .expect("Unnamed fields are not currently supported");
+ let field_ty = &field.ty;
+
+ // Check for optional trigger attribute: `#[net(optional_trigger = "...expr...")]`
+ // or something like `#[net(optional_trigger = { some_field == true })]`.
+ let mut optional_trigger_expr: Option = None;
+
+ // Check the `net(...)` attributes on this field
+ for attr in &field.attrs {
+ if attr.path().is_ident("net") {
+ // e.g., #[net(optional_trigger = { some_field == true })]
+
+ attr.parse_nested_meta(|meta| {
+ if let Some(ident) = meta.path.get_ident() {
+ if ident.to_string().as_str() == "optional_trigger" {
+ meta.parse_nested_meta(|meta| {
+ if let Some(expr) = meta.path.get_ident() {
+ let val = syn::parse_str::(&expr.to_string())
+ .expect("Failed to parse optional_trigger expression");
+
+ optional_trigger_expr = Some(val);
+ } else {
+ panic!("Expected an expression for optional_trigger");
+ }
+
+ Ok(())
+ })
+ .expect("Failed to parse optional_trigger expression");
+ }
+ }
+ Ok(())
+ })
+ .unwrap();
+ }
+ }
+
+ // Generate decoding code depending on whether there's an optional trigger
+ if let Some(expr) = optional_trigger_expr {
+ // For an optional field, we decode it only if `expr` is true at runtime.
+ // We'll store the result in a local variable `field_name` which will be an Option.
+ // Then at the end, we can build the struct using those local variables.
+ decode_statements.push(quote! {
+ let #field_name = {
+ if #expr {
+ Some(<#field_ty as ferrumc_net_codec::decode::NetDecode>::decode(reader, opts)?)
+ } else {
+ None
+ }
+ };
+ });
+ } else {
+ // Check if the field is an Option and handle it accordingly.
+ let is_optional = {
+ let ty_str = quote! { #field_ty }.to_string();
+ ty_str.contains("Option<")
+ };
+
+ if is_optional {
+ decode_statements.push(quote! {
+ compile_error!("Optional fields must have an `optional_trigger` attribute\n\
+ Example: #[net(optional_trigger = { some_field == true })]");
})
}
+
+ // Normal (non-optional) field decode:
+ decode_statements.push(quote! {
+ let #field_name = <#field_ty as ferrumc_net_codec::decode::NetDecode>::decode(reader, opts)?;
+ });
+ }
+
+ field_names.push(field_name);
+ }
+
+ // After decoding everything into local variables, construct the struct.
+ let build_struct = quote! {
+ Ok(Self {
+ #(#field_names),*
+ })
+ };
+
+ let expanded = quote! {
+ impl #impl_generics ferrumc_net_codec::decode::NetDecode
+ for #struct_name #ty_generics
+ #where_clause
+ {
+ fn decode(
+ reader: &mut R,
+ opts: &ferrumc_net_codec::decode::NetDecodeOpts
+ ) -> ferrumc_net_codec::decode::NetDecodeResult {
+ #(#decode_statements)*
+
+ #build_struct
+ }
}
};
diff --git a/src/lib/ecs/Cargo.toml b/src/lib/ecs/Cargo.toml
index eb8ac550..b33e4980 100644
--- a/src/lib/ecs/Cargo.toml
+++ b/src/lib/ecs/Cargo.toml
@@ -9,4 +9,7 @@ thiserror = { workspace = true }
dashmap = { workspace = true }
parking_lot = { workspace = true }
rayon = { workspace = true }
-tracing = { workspace = true }
\ No newline at end of file
+tracing = { workspace = true }
+
+[dev-dependencies]
+criterion = { workspace = true}
\ No newline at end of file
diff --git a/src/lib/ecs/benches/bench.rs b/src/lib/ecs/benches/bench.rs
new file mode 100644
index 00000000..dc498388
--- /dev/null
+++ b/src/lib/ecs/benches/bench.rs
@@ -0,0 +1,100 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use ferrumc_ecs::Universe;
+
+#[allow(dead_code)]
+struct Position {
+ x: f32,
+ y: f32,
+}
+
+#[allow(dead_code)]
+struct Velocity {
+ x: f32,
+ y: f32,
+}
+
+fn create_entity(universe: &Universe) {
+ // entity is 0 here;
+ universe
+ .builder()
+ .with(Position { x: 0.0, y: 0.0 })
+ .unwrap()
+ .build();
+}
+
+fn get_position_immut(universe: &Universe) {
+ let position = universe.get::(0).unwrap();
+ assert_eq!(position.x, 0.0);
+ assert_eq!(position.y, 0.0);
+}
+
+fn get_position_mut(universe: &Universe) {
+ let position = universe.get_mut::(0).unwrap();
+ assert_eq!(position.x, 0.0);
+ assert_eq!(position.y, 0.0);
+}
+
+fn _create_1000_entities_with_pos_and_vel(universe: &Universe) {
+ for i in 0..1000 {
+ let builder = universe
+ .builder()
+ .with(Position {
+ x: i as f32,
+ y: i as f32,
+ })
+ .unwrap();
+ if i % 2 == 0 {
+ builder
+ .with(Velocity {
+ x: i as f32,
+ y: i as f32,
+ })
+ .unwrap();
+ }
+ }
+}
+
+fn query_10k_entities(universe: &Universe) {
+ let query = universe.query::<(&Position, &Velocity)>();
+ for (_, (position, velocity)) in query {
+ assert_eq!(position.x, velocity.x);
+ assert_eq!(position.y, velocity.y);
+ }
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let mut world = Universe::new();
+ c.benchmark_group("entity")
+ .bench_function("create_entity", |b| {
+ b.iter(|| {
+ create_entity(black_box(&world));
+ });
+ // Create a new world after bench is done.
+ world = Universe::new();
+ world
+ .builder()
+ .with(Position { x: 0.0, y: 0.0 })
+ .unwrap()
+ .build();
+ })
+ .bench_function("get immut", |b| {
+ b.iter(|| {
+ get_position_immut(black_box(&world));
+ });
+ })
+ .bench_function("get mut", |b| {
+ b.iter(|| {
+ get_position_mut(black_box(&world));
+ });
+ })
+ .bench_function("query 10k entities", |b| {
+ let universe = Universe::new();
+ _create_1000_entities_with_pos_and_vel(&universe);
+ b.iter(|| {
+ query_10k_entities(black_box(&world));
+ });
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/src/lib/events/src/infrastructure.rs b/src/lib/events/src/infrastructure.rs
index 25f612ce..5f3e09b5 100644
--- a/src/lib/events/src/infrastructure.rs
+++ b/src/lib/events/src/infrastructure.rs
@@ -99,62 +99,6 @@ pub trait Event: Sized + Send + Sync + 'static {
Ok(())
}
- /*/// Trigger the execution of an event with concurrency support
- ///
- /// If the event structure supports cloning. This method can be used to execute
- /// listeners of the same priority concurrently (using tokio::task). This imply a
- /// cloning cost at each listener execution. See `Event::trigger` for a more
- /// efficient but more linear approach.
- ///
- /// # Mutability policy
- ///
- /// The listeners having the same priority being runned concurrently, there are no
- /// guarantees in the order of mutation of the event data.
- ///
- /// It is recommended to ensure listeners of the same priority exclusively update fields
- /// in the event data that are untouched by other listeners of the same group.
- async fn trigger_concurrently(event: Self::Data) -> Result<(), Self::Error>
- where
- Self::Data: Clone,
- {
- let read_guard = &EVENTS_LISTENERS;
- let listeners = read_guard.get(Self::name()).unwrap();
-
- // Convert listeners iterator into Stream
- let mut stream = stream::iter(listeners.iter());
-
- let mut priority_join_set = Vec::new();
- let mut current_priority = 0;
-
- while let Some(Some(listener)) = stream
- .next()
- .await
- .map(|l| l.downcast_ref::>())
- {
- if listener.priority == current_priority {
- priority_join_set.push(tokio::spawn((listener.listener)(event.clone())));
- } else {
- // Await over all listeners launched
- let joined = future::join_all(priority_join_set.iter_mut()).await;
-
- // If one listener fail we return the first error
- if let Some(err) = joined
- .into_iter()
- .filter_map(|res| res.expect("No task should ever panic. Impossible;").err())
- .next()
- {
- return Err(err);
- }
-
- // Update priority to the new listener(s)
- current_priority = listener.priority;
- priority_join_set.push(tokio::spawn((listener.listener)(event.clone())));
- }
- }
-
- Ok(())
- }
- */
/// Register a new event listener for this event
fn register(listener: AsyncEventListener, priority: u8) {
// Create the event listener structure
diff --git a/src/lib/net/crates/codec/src/net_types/network_position.rs b/src/lib/net/crates/codec/src/net_types/network_position.rs
index dd138782..4a745b0a 100644
--- a/src/lib/net/crates/codec/src/net_types/network_position.rs
+++ b/src/lib/net/crates/codec/src/net_types/network_position.rs
@@ -40,6 +40,7 @@ impl NetEncode for NetworkPosition {
_: &NetEncodeOpts,
) -> NetEncodeResult<()> {
use tokio::io::AsyncWriteExt;
+
writer
.write_all(self.as_u64().to_be_bytes().as_ref())
.await?;
diff --git a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs
index 39ca09c7..345cba66 100644
--- a/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs
+++ b/src/lib/net/src/packets/outgoing/chunk_and_light_data.rs
@@ -7,7 +7,7 @@ use ferrumc_net_codec::net_types::var_int::VarInt;
use ferrumc_world::chunk_format::{Chunk, Heightmaps};
use std::io::{Cursor, Write};
use std::ops::Not;
-use tracing::warn;
+use tracing::{trace, warn};
const SECTIONS: usize = 24; // Number of sections, adjust for your Y range (-64 to 319)
@@ -117,9 +117,11 @@ impl ChunkAndLightData {
// If there is no palette entry, write a 0 (air) and log a warning
None => {
VarInt::new(0).write(&mut data)?;
- warn!(
+ trace!(
"No palette entry found for section at {}, {}, {}",
- chunk.x, section.y, chunk.z
+ chunk.x,
+ section.y,
+ chunk.z
);
}
}
diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs
index b21475d4..c67b068d 100644
--- a/src/lib/net/src/utils/broadcast.rs
+++ b/src/lib/net/src/utils/broadcast.rs
@@ -1,6 +1,7 @@
use crate::connection::StreamWriter;
use crate::NetResult;
use async_trait::async_trait;
+use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
use ferrumc_ecs::entities::Entity;
use ferrumc_net_codec::encode::{NetEncode, NetEncodeOpts};
use ferrumc_state::GlobalState;
@@ -64,10 +65,12 @@ impl BroadcastOptions {
}
fn get_all_entities(state: &GlobalState) -> HashSet {
+ // If it needs a chunk, then it's player!! :)
+ // !!!= === =.>>> if it works dont break it
state
.universe
.get_component_manager()
- .get_entities_with::()
+ .get_entities_with::()
.into_iter()
.collect()
}
diff --git a/src/lib/utils/general_purpose/src/paths/exe_path.rs b/src/lib/utils/general_purpose/src/paths/exe_path.rs
new file mode 100644
index 00000000..d6ab1366
--- /dev/null
+++ b/src/lib/utils/general_purpose/src/paths/exe_path.rs
@@ -0,0 +1,43 @@
+use std::path::PathBuf;
+use std::env::current_exe;
+
+#[derive(thiserror::Error, Debug)]
+pub enum RootPathError {
+ #[error("Failed to get the current executable location.")]
+ IoError(#[from] std::io::Error),
+ #[error("Failed to get the parent directory of the executable.")]
+ NoParent,
+}
+
+pub fn get_root_path() -> PathBuf {
+ // Since it should technically never fail.
+ // And if it fails, then it's a critical error, and the program should exit.
+ get_root_path_internal().unwrap()
+}
+
+fn get_root_path_internal() -> Result {
+ //! Returns the root path of the executable.
+ //! e.g.
+ //! - If the executable is located at "D:/server/ferrumc.exe",
+ //! this function will return "D:/server".
+ //!
+ //!
+ //! # Errors
+ //! - If the current executable location cannot be found. (RootPathError::IoError)
+ //! - If the parent directory of the executable cannot be found. (RootPathError::NoParent)
+ //!
+ //! # Examples
+ //! ```rust
+ //! use ferrumc_general_purpose::paths::get_root_path;
+ //!
+ //! // Returns a Result
+ //! let root_path = get_root_path();
+ //!
+ //! let favicon_path = root_path.join("icon.png");
+ //! ```
+ //!
+ let exe_location = current_exe()?;
+ let exe_dir = exe_location.parent().ok_or(RootPathError::NoParent)?;
+
+ Ok(exe_dir.to_path_buf())
+}
\ No newline at end of file