use crate::server::ServerProcess; use clap::ValueEnum; use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum ServerType { Paper, Forge, Vanilla, } impl fmt::Display for ServerType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { ServerType::Paper => "PaperMC", ServerType::Forge => "Forge", ServerType::Vanilla => "Vanilla", }; write!(f, "{}", s) } } pub struct ServerCommand { type_: ServerType, version: String, java: String, jar: PathBuf, config_dir: PathBuf, world_dir: PathBuf, backup_dir: PathBuf, xms: u64, xmx: u64, max_backups: u64, } impl ServerCommand { pub fn new(type_: ServerType, version: &str) -> Self { ServerCommand { type_, version: String::from(version), java: String::from("java"), jar: PathBuf::from("server.jar"), config_dir: PathBuf::from("config"), world_dir: PathBuf::from("worlds"), backup_dir: PathBuf::from("backups"), xms: 1024, xmx: 2048, max_backups: 7, } } pub fn java(mut self, java: &str) -> Self { self.java = String::from(java); self } pub fn jar>(mut self, path: T) -> Self { self.jar = PathBuf::from(path.as_ref()); self } pub fn config>(mut self, path: T) -> Self { self.config_dir = PathBuf::from(path.as_ref()); self } pub fn world>(mut self, path: T) -> Self { self.world_dir = PathBuf::from(path.as_ref()); self } pub fn backup>(mut self, path: T) -> Self { self.backup_dir = PathBuf::from(path.as_ref()); self } pub fn xms(mut self, v: u64) -> Self { self.xms = v; self } pub fn xmx(mut self, v: u64) -> Self { self.xmx = v; self } pub fn max_backups(mut self, v: u64) -> Self { self.max_backups = v; self } fn accept_eula(&self) -> std::io::Result<()> { let mut eula_path = self.config_dir.clone(); eula_path.push("eula.txt"); let mut eula_file = File::create(eula_path)?; eula_file.write_all(b"eula=true")?; Ok(()) } pub fn spawn(self) -> std::io::Result { // To avoid any issues, we use absolute paths for everything when spawning the process let jar = self.jar.canonicalize()?; let config_dir = self.config_dir.canonicalize()?; let world_dir = self.world_dir.canonicalize()?; let backup_dir = self.backup_dir.canonicalize()?; self.accept_eula()?; let mut cmd = Command::new(&self.java); // Apply JVM optimisation flags // https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/ cmd.arg(format!("-Xms{}M", self.xms)) .arg(format!("-Xmx{}M", self.xmx)) .args([ "-XX:+UseG1GC", "-XX:+ParallelRefProcEnabled", "-XX:MaxGCPauseMillis=200", "-XX:+UnlockExperimentalVMOptions", "-XX:+DisableExplicitGC", "-XX:+AlwaysPreTouch", "-XX:G1HeapWastePercent=5", "-XX:G1MixedGCCountTarget=4", "-XX:G1MixedGCLiveThresholdPercent=90", "-XX:G1RSetUpdatingPauseTimePercent=5", "-XX:SurvivorRatio=32", "-XX:+PerfDisableSharedMem", "-XX:MaxTenuringThreshold=1", "-Dusing.aikars.flags=https://mcflags.emc.gs", "-Daikars.new.flags=true", ]); if self.xms > 12 * 1024 { cmd.args([ "-XX:G1NewSizePercent=40", "-XX:G1MaxNewSizePercent=50", "-XX:G1HeapRegionSize=16M", "-XX:G1ReservePercent=15", "-XX:InitiatingHeapOccupancyPercent=20", ]); } else { cmd.args([ "-XX:G1NewSizePercent=30", "-XX:G1MaxNewSizePercent=40", "-XX:G1HeapRegionSize=8M", "-XX:G1ReservePercent=15", "-XX:InitiatingHeapOccupancyPercent=15", ]); } cmd.current_dir(&config_dir) .arg("-jar") .arg(&jar) .arg("--universe") .arg(&world_dir) .arg("--nogui") .stdin(Stdio::piped()); let child = cmd.spawn()?; Ok(ServerProcess::new( self.type_, self.version, config_dir, world_dir, backup_dir, self.max_backups, child, )) } }