diff --git a/README.md b/README.md index c87d69a3..5118410a 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ server_context = "global" | `/speed ` | None | Sets your flyspeed. | | `/gamemode ` | `/gmc`, `/gmsp` | Sets your gamemode. | | `/container ` | None | Gives you a container (e.g. barrel) which outputs a specified amount of power when used with a comparator. | -| `/worldsendrate [hertz]` | `/wsr` | Sets the world send rate to `[hertz]` (frequency of world updates sent to clients). Range: 1-1000. Default: 60. | +| `/worldsendrate [hertz]` | `/wsr` | Sets the world send rate to `[hertz]` (frequency of world updates sent to clients). Range: 0-1000. Default: 60. | | `/toggleautorp` | None | Toggles automatic redpiler compilation. | | `/stop` | None | Stops the server. | diff --git a/crates/core/src/plot/commands.rs b/crates/core/src/plot/commands.rs index 2732a7c7..12d4dc6f 100644 --- a/crates/core/src/plot/commands.rs +++ b/crates/core/src/plot/commands.rs @@ -293,7 +293,11 @@ impl Plot { return false; } - let tps = if let Ok(tps) = args[0].parse::() { + let tps = if let Ok(tps) = args[0].parse::() { + if tps < 0.0 { + self.players[player].send_error_message("RTPS must be cannot be negative!"); + return false; + } Tps::Limited(tps) } else if !args[0].is_empty() && "unlimited".starts_with(args[0]) { Tps::Unlimited @@ -490,7 +494,7 @@ impl Plot { "worldsendrate" | "wsr" => { if args.is_empty() { self.players[player].send_system_message(&format!( - "Current world send rate: {} Hz", + "Current world send rate: {:.2} Hz", self.world_send_rate.0 )); return false; @@ -501,17 +505,18 @@ impl Plot { return false; } - let Ok(hertz) = args[0].parse::() else { + let Ok(hertz) = args[0].parse::() else { self.players[player].send_error_message("Unable to parse send rate!"); return false; }; - if hertz == 0 { - self.players[player].send_error_message("The world send rate cannot be 0!"); + if hertz < 0.0 { + self.players[player] + .send_error_message("The world send rate cannot be negative!"); return false; } - if hertz > 1000 { + if hertz > 1000.0 { self.players[player] - .send_error_message("The world send rate cannot go higher than 1000!"); + .send_error_message("The world send rate cannot be higher than 1000!"); return false; } @@ -615,7 +620,7 @@ pub static DECLARE_COMMANDS: Lazy = Lazy::new(|| { children: vec![], redirect_node: None, name: Some("rtps"), - parser: Some(Parser::Integer(0, i32::MAX)), + parser: Some(Parser::Float(0.0, f32::MAX)), suggestions_type: None, }, // 8: /radvance @@ -999,13 +1004,13 @@ pub static DECLARE_COMMANDS: Lazy = Lazy::new(|| { parser: None, suggestions_type: None, }, - // 50: /worldsendrate [rticks] + // 50: /worldsendrate [hertz] Node { flags: (CommandFlags::ARGUMENT | CommandFlags::EXECUTABLE).bits() as i8, children: vec![], redirect_node: None, name: Some("hertz"), - parser: Some(Parser::Integer(0, 1000)), + parser: Some(Parser::Float(0.0, 1000.0)), suggestions_type: None, }, // 51: /wsr diff --git a/crates/core/src/plot/data.rs b/crates/core/src/plot/data.rs index 4c7c880f..299a06a4 100644 --- a/crates/core/src/plot/data.rs +++ b/crates/core/src/plot/data.rs @@ -9,8 +9,8 @@ use std::time::Duration; pub fn sleep_time_for_tps(tps: Tps) -> Duration { match tps { Tps::Limited(tps) => { - if tps > 10 { - Duration::from_micros(1_000_000 / tps as u64) + if tps > 10.0 { + Duration::from_secs_f32(1.0 / tps) } else { Duration::from_millis(50) } @@ -53,7 +53,7 @@ static EMPTY_PLOT: Lazy = Lazy::new(|| { }; let chunk_data: Vec = world.chunks.iter_mut().map(ChunkData::new).collect(); PlotData { - tps: Tps::Limited(10), + tps: Tps::Limited(10.0), world_send_rate: WorldSendRate::default(), chunk_data, pending_ticks: Vec::new(), diff --git a/crates/core/src/plot/mod.rs b/crates/core/src/plot/mod.rs index 588a2ec1..68e91a1b 100644 --- a/crates/core/src/plot/mod.rs +++ b/crates/core/src/plot/mod.rs @@ -987,25 +987,31 @@ impl Plot { let now = Instant::now(); self.last_player_time = now; - let world_send_rate = - Duration::from_nanos(1_000_000_000 / self.world_send_rate.0 as u64); + let world_send_rate = if self.world_send_rate.0 == 0.0 { + Duration::MAX + } else { + Duration::from_secs_f32(1.0 / self.world_send_rate.0) + }; + // 50_000 (= 3.33 MHz) here is arbitrary. + // We just need a number that's not too high so we actually get around to sending block updates. let max_batch_size = match self.last_nspt { Some(Duration::ZERO) | None => 1, Some(last_nspt) => { - let ticks_fit = (world_send_rate.as_nanos() / last_nspt.as_nanos()) as u64; + let ticks_fit = (world_send_rate.as_nanos() / last_nspt.as_nanos()) as u32; // A tick previously took longer than the world send rate. // Run at least one just so we're not stuck doing nothing ticks_fit.max(1) } - }; + } + .min(50_000); let batch_size = match self.tps { - Tps::Limited(tps) if tps != 0 => { - let dur_per_tick = Duration::from_nanos(1_000_000_000 / tps as u64); + Tps::Limited(tps) if tps != 0.0 => { + let dur_per_tick = Duration::from_secs_f32(1.0 / tps); self.lag_time += now - self.last_update_time; - let batch_size = (self.lag_time.as_nanos() / dur_per_tick.as_nanos()) as u64; - self.lag_time -= dur_per_tick * batch_size as u32; + let batch_size = (self.lag_time.as_nanos() / dur_per_tick.as_nanos()) as u32; + self.lag_time -= dur_per_tick * batch_size; batch_size.min(max_batch_size) } Tps::Unlimited => max_batch_size, @@ -1014,24 +1020,24 @@ impl Plot { self.last_update_time = now; if batch_size != 0 { - // 50_000 (= 3.33 MHz) here is arbitrary. - // We just need a number that's not too high so we actually get around to sending - // block updates. - let batch_size = batch_size.min(50_000) as u32; - let mut ticks_completed = batch_size; + let mut ticks_completed = 0; if self.redpiler.is_active() { self.tickn(batch_size as u64); self.redpiler.flush(&mut self.world); + ticks_completed += batch_size; } else { - for i in 0..batch_size { + for _ in 0..batch_size { self.tick(); + ticks_completed += 1; if now.elapsed() > Duration::from_millis(200) { - ticks_completed = i + 1; break; } } } - self.last_nspt = Some(self.last_update_time.elapsed() / ticks_completed); + + if ticks_completed != 0 { + self.last_nspt = Some(self.last_update_time.elapsed() / ticks_completed); + } } if self.auto_redpiler diff --git a/crates/core/src/plot/monitor.rs b/crates/core/src/plot/monitor.rs index fd90e89d..50348c5f 100644 --- a/crates/core/src/plot/monitor.rs +++ b/crates/core/src/plot/monitor.rs @@ -7,33 +7,41 @@ use std::thread::JoinHandle; use std::time::Duration; use tracing::warn; +const MONITOR_SCHEDULE: Duration = Duration::from_millis(500); + #[derive(Default)] struct AtomicTps { - tps: AtomicU32, - unlimited: AtomicBool, + tps_bits: AtomicU32, } impl AtomicTps { fn from_tps(tps: Tps) -> Self { - match tps { - Tps::Limited(tps) => AtomicTps { - tps: AtomicU32::new(tps), - unlimited: AtomicBool::new(false), - }, - Tps::Unlimited => AtomicTps { - tps: AtomicU32::new(0), - unlimited: AtomicBool::new(true), - }, + AtomicTps { + tps_bits: AtomicU32::new(Self::tps_to_bits(tps)), } } fn update(&self, tps: Tps) { + self.tps_bits + .store(Self::tps_to_bits(tps), Ordering::Relaxed); + } + + fn tps_to_bits(tps: Tps) -> u32 { match tps { - Tps::Limited(tps) => { - self.tps.store(tps, Ordering::Relaxed); - self.unlimited.store(false, Ordering::Relaxed); + Tps::Limited(tps) if tps.is_nan() => { + panic!("Tps should never be NaN under any circumstance") } - Tps::Unlimited => self.unlimited.store(true, Ordering::Relaxed), + Tps::Limited(tps) => tps.to_bits(), + Tps::Unlimited => f32::NAN.to_bits(), + } + } + + fn get(&self) -> Tps { + let tps = f32::from_bits(self.tps_bits.load(Ordering::Relaxed)); + if tps.is_nan() { + Tps::Unlimited + } else { + Tps::Limited(tps) } } } @@ -148,13 +156,13 @@ impl TimingsMonitor { fn run_thread(data: Arc) -> JoinHandle<()> { thread::spawn(move || { - let mut last_tps = data.tps.tps.load(Ordering::Relaxed); + let mut last_tps = data.tps.get(); let mut last_ticks_count = data.ticks_passed.load(Ordering::Relaxed); let mut was_ticking_before = data.ticking.load(Ordering::Relaxed); let mut behind_for = 0; loop { - thread::sleep(Duration::from_millis(500)); + thread::sleep(MONITOR_SCHEDULE); if !data.running.load(Ordering::Relaxed) { return; } @@ -166,7 +174,7 @@ impl TimingsMonitor { let ticks_passed = (ticks_count - last_ticks_count) as u32; last_ticks_count = ticks_count; - let tps = data.tps.tps.load(Ordering::Relaxed); + let tps = data.tps.get(); let ticking = data.ticking.load(Ordering::Relaxed); if !(ticking && was_ticking_before) || tps != last_tps @@ -179,8 +187,14 @@ impl TimingsMonitor { } // 5% threshold - if data.tps.unlimited.load(Ordering::Relaxed) || ticks_passed < (tps / 2) * 95 / 100 - { + let is_behind = match tps { + Tps::Unlimited => false, + Tps::Limited(tps_val) => { + (ticks_passed as f32) < tps_val * MONITOR_SCHEDULE.as_secs_f32() * 0.95 + } + }; + + if is_behind { behind_for += 1; } else { behind_for = 0; @@ -190,8 +204,8 @@ impl TimingsMonitor { if behind_for >= 3 { data.too_slow.store(true, Ordering::Relaxed); // warn!( - // "running behind by {} ticks", - // ((tps / 2) * 95 / 100) - ticks_passed + // "running behind by {:.1} ticks", + // (tps * MONITOR_SCHEDULE.as_secs_f32() * 0.95) - (ticks_passed as f32) // ); } diff --git a/crates/save_data/src/plot_data.rs b/crates/save_data/src/plot_data.rs index 628d7434..f80e480b 100644 --- a/crates/save_data/src/plot_data.rs +++ b/crates/save_data/src/plot_data.rs @@ -1,4 +1,5 @@ mod fixer; +mod v2_to_v3; use self::fixer::FixInfo; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; @@ -18,7 +19,8 @@ use thiserror::Error; /// 0: Initial plot data file with header (MC 1.18.2) /// 1: Add world send rate /// 2: Update to MC 1.20.4 -pub const VERSION: u32 = 2; +/// 3: Change Tps and WorldSendRate to support real values (f32) +pub const VERSION: u32 = 3; #[derive(Error, Debug)] pub enum PlotLoadError { @@ -132,18 +134,18 @@ impl ChunkData { } } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] pub enum Tps { - Limited(u32), + Limited(f32), Unlimited, } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -pub struct WorldSendRate(pub u32); +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] +pub struct WorldSendRate(pub f32); impl Default for WorldSendRate { fn default() -> Self { - Self(60) + Self(60.0) } } diff --git a/crates/save_data/src/plot_data/fixer.rs b/crates/save_data/src/plot_data/fixer.rs index d2463e6b..7bf63ccc 100644 --- a/crates/save_data/src/plot_data/fixer.rs +++ b/crates/save_data/src/plot_data/fixer.rs @@ -7,6 +7,7 @@ //! seperate download. As our save format changes in the future, the fixer //! module may become quite big. +use super::v2_to_v3; use super::{PlotData, PlotLoadError}; use crate::plot_data::VERSION; use std::fs; @@ -41,6 +42,7 @@ pub fn try_fix(path: impl AsRef, info: FixInfo) -> Result FixInfo::OldVersion { version: version @ 0..=1, } => return Err(PlotLoadError::ConversionUnavailable(version)), + FixInfo::OldVersion { version: 2 } => v2_to_v3::convert_v2_to_v3(&path)?, _ => None, }; diff --git a/crates/save_data/src/plot_data/v2_to_v3.rs b/crates/save_data/src/plot_data/v2_to_v3.rs new file mode 100644 index 00000000..fa1a542d --- /dev/null +++ b/crates/save_data/src/plot_data/v2_to_v3.rs @@ -0,0 +1,68 @@ +//! Migration from version 2 to version 3 +//! +//! Version 2: Tps and WorldSendRate used u32 values +//! Version 3: Tps and WorldSendRate use f32 values + +use super::{ChunkData, PlotData, PlotLoadError, Tps, WorldSendRate, PLOT_MAGIC}; +use byteorder::{LittleEndian, ReadBytesExt}; +use mchprs_world::TickEntry; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use tracing::debug; + +// Version 2 data structures (with u32 values) +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum TpsV2 { + Limited(u32), + Unlimited, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub struct WorldSendRateV2(pub u32); + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PlotDataV2 { + pub tps: TpsV2, + pub world_send_rate: WorldSendRateV2, + pub chunk_data: Vec, + pub pending_ticks: Vec, +} + +pub fn convert_v2_to_v3(path: impl AsRef) -> Result, PlotLoadError> { + let mut file = File::open(&path)?; + + let mut magic = [0; 8]; + file.read_exact(&mut magic)?; + if &magic != PLOT_MAGIC { + return Ok(None); + } + + let version = file.read_u32::()?; + if version != 2 { + return Ok(None); + } + + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + let old_data: PlotDataV2 = bincode::deserialize(&buf)?; + + // Convert old data to new format + let new_tps = match old_data.tps { + TpsV2::Limited(val) => Tps::Limited(val as f32), + TpsV2::Unlimited => Tps::Unlimited, + }; + + let new_world_send_rate = WorldSendRate(old_data.world_send_rate.0 as f32); + + let new_data = PlotData { + tps: new_tps, + world_send_rate: new_world_send_rate, + chunk_data: old_data.chunk_data, + pending_ticks: old_data.pending_ticks, + }; + + debug!("Successfully converted plot data from version 2 to version 3"); + Ok(Some(new_data)) +}