#![deny(clippy::all)] #![deny(clippy::pedantic)] // #![warn(clippy::restriction)] #![deny(clippy::cargo)] // to keep consistency #![deny(clippy::if_then_some_else_none)] #![deny(clippy::empty_enum_variants_with_brackets)] #![deny(clippy::empty_structs_with_brackets)] #![deny(clippy::separated_literal_suffix)] #![deny(clippy::semicolon_outside_block)] #![deny(clippy::non_zero_suggestions)] #![deny(clippy::string_lit_chars_any)] #![deny(clippy::use_self)] #![deny(clippy::useless_let_if_seq)] #![deny(clippy::branches_sharing_code)] #![deny(clippy::equatable_if_let)] #![deny(clippy::option_if_let_else)] // use log crate #![deny(clippy::print_stdout)] #![deny(clippy::print_stderr)] // REMOVE SOME WHEN RELEASE #![expect(clippy::cargo_common_metadata)] #![expect(clippy::cast_precision_loss)] #![expect(clippy::multiple_crate_versions)] #![expect(clippy::single_call_fn)] #![expect(clippy::cast_sign_loss)] #![expect(clippy::cast_possible_truncation)] #![expect(clippy::cast_possible_wrap)] #![expect(clippy::missing_panics_doc)] #![expect(clippy::missing_errors_doc)] #![expect(clippy::module_name_repetitions)] #![expect(clippy::struct_excessive_bools)] // Don't warn on event sending macros #![expect(unused_labels)] #[cfg(target_os = "wasi")] compile_error!("Compiling for WASI targets is not supported!"); use plugin::PluginManager; use pumpkin_config::BASIC_CONFIG; use pumpkin_data::packet::CURRENT_MC_PROTOCOL; use std::{ io::{self}, sync::{Arc, LazyLock}, }; #[cfg(not(unix))] use tokio::signal::ctrl_c; #[cfg(unix)] use tokio::signal::unix::{SignalKind, signal}; use tokio::sync::RwLock; use pumpkin::{LOGGER_IMPL, PumpkinServer, SHOULD_STOP, STOP_INTERRUPT, init_log, stop_server}; use pumpkin_util::{ permission::{PermissionManager, PermissionRegistry}, text::{TextComponent, color::NamedColor}, }; use std::time::Instant; // Setup some tokens to allow us to identify which event is for which socket. pub mod block; pub mod command; pub mod data; pub mod entity; pub mod error; pub mod item; pub mod net; pub mod plugin; pub mod server; pub mod world; #[cfg(feature = "dhat-heap")] #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; #[cfg(feature = "dhat-heap")] use pumpkin::HEAP_PROFILER; pub static PLUGIN_MANAGER: LazyLock> = LazyLock::new(|| Arc::new(PluginManager::new())); pub static PERMISSION_REGISTRY: LazyLock>> = LazyLock::new(|| Arc::new(RwLock::new(PermissionRegistry::new()))); pub static PERMISSION_MANAGER: LazyLock>> = LazyLock::new(|| { Arc::new(RwLock::new(PermissionManager::new( PERMISSION_REGISTRY.clone(), ))) }); const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); // WARNING: All rayon calls from the tokio runtime must be non-blocking! This includes things // like `par_iter`. These should be spawned in the the rayon pool and then passed to the tokio // runtime with a channel! See `Level::fetch_chunks` as an example! #[tokio::main] async fn main() { #[cfg(feature = "console-subscriber")] console_subscriber::init(); #[cfg(feature = "dhat-heap")] { let profiler = dhat::Profiler::new_heap(); let mut static_loc = HEAP_PROFILER.lock().await; *static_loc = Some(profiler); }; let time = Instant::now(); init_log!(); let default_panic = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { if let Some((wrapper, _)) = LOGGER_IMPL.as_ref() { // Drop readline to reset terminal state let _ = wrapper.take_readline(); } default_panic(info); // TODO: Gracefully exit? // We need to abide by the panic rules here. std::process::exit(1); })); log::info!("Starting Pumpkin {CARGO_PKG_VERSION} Minecraft (Protocol {CURRENT_MC_PROTOCOL})",); log::debug!( "Build info: FAMILY: \"{}\", OS: \"{}\", ARCH: \"{}\", BUILD: \"{}\"", std::env::consts::FAMILY, std::env::consts::OS, std::env::consts::ARCH, if cfg!(debug_assertions) { "Debug" } else { "Release" } ); log::warn!("Pumpkin is currently under heavy development!"); log::info!("Report issues on https://github.com/Pumpkin-MC/Pumpkin/issues"); log::info!("Join our Discord for community support: https://discord.com/invite/wT8XjrjKkf"); tokio::spawn(async { setup_sighandler() .await .expect("Unable to setup signal handlers"); }); let pumpkin_server = PumpkinServer::new().await; pumpkin_server.init_plugins().await; log::info!("Started server; took {}ms", time.elapsed().as_millis()); log::info!( "Server is now running. Connect using port: {}{}{}", if BASIC_CONFIG.java_edition { format!("Java Edition: {}", BASIC_CONFIG.java_edition_address) } else { String::new() }, if BASIC_CONFIG.java_edition && BASIC_CONFIG.bedrock_edition { " | " // Separator if both are enabled } else { "" }, if BASIC_CONFIG.bedrock_edition { format!("Bedrock Edition: {}", BASIC_CONFIG.bedrock_edition_address) } else { String::new() } ); pumpkin_server.start().await; log::info!("The server has stopped."); } fn handle_interrupt() { log::warn!( "{}", TextComponent::text("Received interrupt signal; stopping server...") .color_named(NamedColor::Red) .to_pretty_console() ); stop_server(); } // Non-UNIX Ctrl-C handling #[cfg(not(unix))] async fn setup_sighandler() -> io::Result<()> { if ctrl_c().await.is_ok() { handle_interrupt(); } Ok(()) } // Unix signal handling #[cfg(unix)] async fn setup_sighandler() -> io::Result<()> { if signal(SignalKind::interrupt())?.recv().await.is_some() { handle_interrupt(); } if signal(SignalKind::hangup())?.recv().await.is_some() { handle_interrupt(); } if signal(SignalKind::terminate())?.recv().await.is_some() { handle_interrupt(); } Ok(()) }