From d630d83d3a412ebd0e0706bfad10834284775e84 Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Wed, 17 Jan 2024 22:54:58 +0100 Subject: [PATCH] harmonize time usage and definition --- base_mod/companies.lua | 2 +- common/src/timestep.rs | 6 +- native_app/src/game_loop.rs | 2 +- native_app/src/gui/chat.rs | 9 +- native_app/src/gui/topgui.rs | 12 +- native_app/src/gui/windows/debug.rs | 35 +- native_app/src/gui/windows/economy.rs | 4 +- prototypes/src/macros.rs | 10 - prototypes/src/prototypes/mod.rs | 15 +- prototypes/src/types/mod.rs | 20 +- prototypes/src/types/money.rs | 16 + prototypes/src/types/recipe.rs | 8 +- prototypes/src/types/time.rs | 449 ++++++++++++++++++ simulation/src/economy/market.rs | 13 +- simulation/src/economy/mod.rs | 13 +- simulation/src/init.rs | 11 +- simulation/src/lib.rs | 12 +- simulation/src/map/light_policy.rs | 2 +- simulation/src/map/map.rs | 3 +- simulation/src/map/pathfinding.rs | 2 +- simulation/src/map/terrain.rs | 9 +- simulation/src/map_dynamic/itinerary.rs | 6 +- simulation/src/multiplayer/chat.rs | 4 +- simulation/src/souls/desire/buyfood.rs | 5 +- simulation/src/souls/desire/work.rs | 6 +- simulation/src/souls/freight_station.rs | 5 +- simulation/src/souls/goods_company.rs | 14 +- simulation/src/souls/human.rs | 3 +- simulation/src/tests/mod.rs | 3 +- simulation/src/tests/test_iso.rs | 5 +- simulation/src/tests/vehicles.rs | 2 +- simulation/src/transportation/pedestrian.rs | 24 +- simulation/src/transportation/road.rs | 26 +- .../src/transportation/testing_vehicles.rs | 6 +- simulation/src/transportation/train.rs | 33 +- simulation/src/transportation/vehicle.rs | 2 +- simulation/src/utils/mod.rs | 1 - simulation/src/utils/replay.rs | 2 +- simulation/src/utils/resources.rs | 5 + simulation/src/utils/time.rs | 220 --------- simulation/src/world_command.rs | 12 +- 41 files changed, 625 insertions(+), 412 deletions(-) create mode 100644 prototypes/src/types/time.rs delete mode 100644 simulation/src/utils/time.rs diff --git a/base_mod/companies.lua b/base_mod/companies.lua index 103f5b99e..103b06984 100644 --- a/base_mod/companies.lua +++ b/base_mod/companies.lua @@ -12,7 +12,7 @@ data:extend { recipe = { consumption = {{"flour", 1}}, production = {{"bread", 1}}, - complexity = 100, + complexity = "100s", storage_multiplier = 5, }, n_workers = 3, diff --git a/common/src/timestep.rs b/common/src/timestep.rs index e41f99921..7de4454e1 100644 --- a/common/src/timestep.rs +++ b/common/src/timestep.rs @@ -1,6 +1,10 @@ use std::time::{Duration, Instant}; -pub const UP_DT: Duration = Duration::from_millis(20); +const UP_DT: Duration = Duration::from_millis(20); + +pub fn debug_up_dt() -> Duration { + UP_DT +} /// A timestep that can be used to update the game state. /// It will try to keep a constant update rate. diff --git a/native_app/src/game_loop.rs b/native_app/src/game_loop.rs index 699047f39..400b4f0dd 100644 --- a/native_app/src/game_loop.rs +++ b/native_app/src/game_loop.rs @@ -6,7 +6,6 @@ use crate::rendering::immediate::{ImmediateDraw, ImmediateSound}; use common::history::History; use engine::{Context, FrameContext, Tesselator}; use geom::{vec2, vec3, Camera, LinearColor}; -use simulation::utils::time::GameTime; use simulation::Simulation; use crate::audio::GameAudio; @@ -18,6 +17,7 @@ use crate::inputmap::{Bindings, InputAction, InputMap}; use crate::rendering::{InstancedRender, MapRenderOptions, MapRenderer, OrbitCamera}; use crate::uiworld::{SaveLoadState, UiWorld}; use common::saveload::Encoder; +use prototypes::GameTime; use simulation::utils::scheduler::SeqSchedule; pub const VERSION: &str = include_str!("../../VERSION"); diff --git a/native_app/src/gui/chat.rs b/native_app/src/gui/chat.rs index 30371727d..980ac6cd8 100644 --- a/native_app/src/gui/chat.rs +++ b/native_app/src/gui/chat.rs @@ -2,9 +2,9 @@ use egui::panel::TopBottomSide; use egui::{Align2, Color32, Frame, RichText, ScrollArea, TextBuffer, TopBottomPanel}; use geom::Color; +use prototypes::{GameDuration, GameTime}; use simulation::multiplayer::chat::{Message, MessageKind}; use simulation::multiplayer::MultiplayerState; -use simulation::utils::time::{GameInstant, GameTime, SECONDS_PER_REALTIME_SECOND}; use simulation::world_command::WorldCommand; use simulation::Simulation; @@ -20,10 +20,7 @@ pub struct GUIChatState { pub fn chat(ui: &egui::Context, uiw: &mut UiWorld, sim: &Simulation) { const MAX_MESSAGES: usize = 30; let mut state = uiw.write::(); - let one_minute_ago = GameInstant { - timestamp: sim.read::().instant().timestamp - - 120.0 * SECONDS_PER_REALTIME_SECOND as f64, - }; + let five_minute_ago = sim.read::().instant() - GameDuration::from_minutes(5); let mstate = sim.read::(); @@ -38,7 +35,7 @@ pub fn chat(ui: &egui::Context, uiw: &mut UiWorld, sim: &Simulation) { let msgs: Vec<_> = mstate .chat - .messages_since(one_minute_ago) + .messages_since(five_minute_ago) .take(MAX_MESSAGES) .collect(); diff --git a/native_app/src/gui/topgui.rs b/native_app/src/gui/topgui.rs index dc52f8806..d3032b785 100644 --- a/native_app/src/gui/topgui.rs +++ b/native_app/src/gui/topgui.rs @@ -19,15 +19,14 @@ use egui::{ use egui_inspect::{Inspect, InspectArgs}; use geom::{Polygon, Vec2}; use prototypes::{ - prototypes_iter, BuildingGen, FreightStationPrototype, GoodsCompanyPrototype, ItemID, Money, - Power, + prototypes_iter, BuildingGen, FreightStationPrototype, GameTime, GoodsCompanyPrototype, ItemID, + Money, Power, }; use serde::{Deserialize, Serialize}; use simulation::economy::Government; use simulation::map::{ BuildingKind, LanePatternBuilder, LightPolicy, MapProject, TerraformKind, TurnPolicy, Zone, }; -use simulation::utils::time::{GameTime, SECONDS_PER_HOUR}; use simulation::world_command::WorldCommand; use simulation::Simulation; use std::sync::atomic::Ordering; @@ -638,7 +637,7 @@ impl Gui { } ui.add_space(10.0); } - ui.label(format!("time: {}s", recipe.complexity)); + ui.label(format!("time: {}", recipe.duration)); ui.label(format!( "storage multiplier: {}", recipe.storage_multiplier @@ -708,12 +707,9 @@ impl Gui { ui.horizontal(|ui| { ui.label(format!(" Day {}", time.day)); ui.add_space(40.0); - const OFF: i32 = SECONDS_PER_HOUR / 60; ui.label(format!( "{:02}:{:02}:{:02}", - time.hour, - time.second / OFF, - time.second % OFF * 60 / OFF + time.hour, time.minute, time.second )); }); diff --git a/native_app/src/gui/windows/debug.rs b/native_app/src/gui/windows/debug.rs index d64c34fd2..4f4d5fd80 100644 --- a/native_app/src/gui/windows/debug.rs +++ b/native_app/src/gui/windows/debug.rs @@ -5,13 +5,13 @@ use crate::gui::InspectedEntity; use crate::uiworld::UiWorld; use simulation::map_dynamic::ParkingManagement; use simulation::transportation::TransportGrid; -use simulation::utils::time::{GameTime, Tick, SECONDS_PER_DAY}; use simulation::{Simulation, TrainID}; use crate::inputmap::InputMap; use egui::Widget; use engine::{PerfCountersStatic, Tesselator}; use geom::{Camera, Color, LinearColor, Spline3, Vec2}; +use prototypes::{GameDuration, GameTime, SECONDS_PER_DAY}; use simulation::map::{ IntersectionID, Map, MapSubscriber, NetworkObjectID, RoadSegmentKind, TraverseKind, UpdateType, }; @@ -82,34 +82,37 @@ pub fn debug( ); drop(objs); - let time = sim.read::().timestamp; + let time = *sim.read::(); let daysecleft = SECONDS_PER_DAY - sim.read::().daytime.daysec(); if ui.small_button("set night").clicked() { uiworld .commands() - .set_game_time(GameTime::new(0.1, time + daysecleft as f64)); + .set_game_time(time + GameDuration::from_secs(daysecleft as u64)); } if ui.small_button("set morning").clicked() { - uiworld.commands().set_game_time(GameTime::new( - 0.1, - time + daysecleft as f64 + 5.5 * GameTime::HOUR as f64, - )); + uiworld.commands().set_game_time( + time + GameDuration::from_secs( + (daysecleft as f64 + 6.0 * GameTime::HOUR as f64) as u64, + ), + ); } if ui.small_button("set day").clicked() { - uiworld.commands().set_game_time(GameTime::new( - 0.1, - time + daysecleft as f64 + 12.0 * GameTime::HOUR as f64, - )); + uiworld.commands().set_game_time( + time + GameDuration::from_secs( + (daysecleft as f64 + 12.0 * GameTime::HOUR as f64) as u64, + ), + ); } if ui.small_button("set dawn").clicked() { - uiworld.commands().set_game_time(GameTime::new( - 0.1, - time + daysecleft as f64 + 21.7 * GameTime::HOUR as f64, - )); + uiworld.commands().set_game_time( + time + GameDuration::from_secs( + (daysecleft as f64 + 21.7 * GameTime::HOUR as f64) as u64, + ), + ); } ui.label(format!( @@ -117,7 +120,7 @@ pub fn debug( sim.read::().timestamp )); - ui.label(format!("Tick: {}", sim.read::().0)); + ui.label(format!("Tick: {}", time.tick)); let timings = uiworld.read::(); let mouse = uiworld.read::().unprojected; diff --git a/native_app/src/gui/windows/economy.rs b/native_app/src/gui/windows/economy.rs index 5d2d7f234..77f2788e1 100644 --- a/native_app/src/gui/windows/economy.rs +++ b/native_app/src/gui/windows/economy.rs @@ -1,8 +1,8 @@ use crate::uiworld::UiWorld; -use common::timestep::UP_DT; use egui::{Align2, Color32, Ui}; use egui_plot::{Line, PlotPoints}; use geom::Color; +use prototypes::DELTA_F64; use simulation::economy::{ EcoStats, ItemHistories, Market, HISTORY_SIZE, LEVEL_FREQS, LEVEL_NAMES, }; @@ -97,7 +97,7 @@ pub fn economy(window: egui::Window<'_>, ui: &egui::Context, uiw: &mut UiWorld, } }); - let seconds_per_step = LEVEL_FREQS[state.curlevel] as f64 * UP_DT.as_secs_f64(); + let seconds_per_step = LEVEL_FREQS[state.curlevel] as f64 * DELTA_F64; let xs: Vec = (0..HISTORY_SIZE) .map(|i| i as f64 * seconds_per_step) .collect(); diff --git a/prototypes/src/macros.rs b/prototypes/src/macros.rs index 60a2d3e11..d5b141360 100644 --- a/prototypes/src/macros.rs +++ b/prototypes/src/macros.rs @@ -1,13 +1,3 @@ -#[macro_export] -macro_rules! export_mods { - {$(mod $v: ident;)+} => { - $( - mod $v; - pub use $v::*; - )+ - }; -} - #[macro_export] macro_rules! gen_prototypes { ($($name:ident : $id:ident = $t:ty $(=> $parent_id:ident)?,)+) => { diff --git a/prototypes/src/prototypes/mod.rs b/prototypes/src/prototypes/mod.rs index 3fed6ec4a..363cb8448 100644 --- a/prototypes/src/prototypes/mod.rs +++ b/prototypes/src/prototypes/mod.rs @@ -1,9 +1,12 @@ -crate::export_mods! { - mod company; - mod item; - mod solar; - mod freightstation; -} +mod company; +mod freightstation; +mod item; +mod solar; + +pub use company::*; +pub use freightstation::*; +pub use item::*; +pub use solar::*; crate::gen_prototypes!( companies: GoodsCompanyID = GoodsCompanyPrototype, diff --git a/prototypes/src/types/mod.rs b/prototypes/src/types/mod.rs index 6b23a2181..a9aa27b34 100644 --- a/prototypes/src/types/mod.rs +++ b/prototypes/src/types/mod.rs @@ -1,7 +1,13 @@ -crate::export_mods! { - mod power; - mod recipe; - mod zone; - mod money; - mod size; -} +mod money; +mod power; +mod recipe; +mod size; +mod time; +mod zone; + +pub use money::*; +pub use power::*; +pub use recipe::*; +pub use size::*; +pub use time::*; +pub use zone::*; diff --git a/prototypes/src/types/money.rs b/prototypes/src/types/money.rs index 48bcc7af5..f402b0379 100644 --- a/prototypes/src/types/money.rs +++ b/prototypes/src/types/money.rs @@ -194,3 +194,19 @@ impl Div for Money { Money(self.0 / rhs) } } + +impl Mul for Money { + type Output = Money; + + fn mul(self, rhs: f64) -> Self::Output { + Money((self.0 as f64 * rhs) as i64) + } +} + +impl Mul for f64 { + type Output = Money; + + fn mul(self, rhs: Money) -> Self::Output { + rhs * self + } +} diff --git a/prototypes/src/types/recipe.rs b/prototypes/src/types/recipe.rs index c324146c0..568c79050 100644 --- a/prototypes/src/types/recipe.rs +++ b/prototypes/src/types/recipe.rs @@ -1,4 +1,4 @@ -use crate::{get_with_err, ItemID}; +use crate::{get_with_err, GameDuration, ItemID}; use egui_inspect::Inspect; use mlua::{FromLua, Lua, Table, Value}; @@ -37,8 +37,8 @@ pub struct Recipe { pub consumption: Vec, pub production: Vec, - /// Time to execute the recipe when the facility is at full capacity, in seconds - pub complexity: i32, + /// Time to execute the recipe when the facility is at full capacity + pub duration: GameDuration, /// Quantity to store per production in terms of quantity produced. So if it takes 1ton of flour to make /// 1 ton of bread. A storage multiplier of 3 means 3 tons of bread will be stored before stopping to @@ -52,7 +52,7 @@ impl<'lua> FromLua<'lua> for Recipe { Ok(Self { consumption: get_with_err(&table, "consumption")?, production: get_with_err(&table, "production")?, - complexity: get_with_err(&table, "complexity")?, + duration: get_with_err(&table, "duration")?, storage_multiplier: get_with_err(&table, "storage_multiplier")?, }) } diff --git a/prototypes/src/types/time.rs b/prototypes/src/types/time.rs new file mode 100644 index 000000000..3139e13e9 --- /dev/null +++ b/prototypes/src/types/time.rs @@ -0,0 +1,449 @@ +use egui_inspect::{debug_inspect_impl, Inspect}; +use mlua::{FromLua, Lua, Number, Value}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::{Add, Sub}; +use std::str::FromStr; +use thiserror::Error; + +pub const SECONDS_PER_REALTIME_SECOND: u32 = 10; +pub const SECONDS_PER_HOUR: i32 = 60 * SECONDS_PER_MINUTE; +pub const SECONDS_PER_MINUTE: i32 = 60; +pub const MINUTES_PER_HOUR: i32 = 60; +pub const HOURS_PER_DAY: i32 = 24; +pub const SECONDS_PER_DAY: i32 = SECONDS_PER_HOUR * HOURS_PER_DAY; +pub const TICKS_PER_REALTIME_SECOND: u64 = 50; +pub const TICKS_PER_SECOND: u64 = TICKS_PER_REALTIME_SECOND / SECONDS_PER_REALTIME_SECOND as u64; +pub const TICKS_PER_MINUTE: u64 = TICKS_PER_SECOND * SECONDS_PER_MINUTE as u64; +pub const TICKS_PER_HOUR: u64 = TICKS_PER_SECOND * SECONDS_PER_HOUR as u64; +pub const DELTA_F64: f64 = 1.0 / TICKS_PER_REALTIME_SECOND as f64; +pub const DELTA: f32 = DELTA_F64 as f32; + +/// The amount of time the game was updated +/// Used as a resource +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +pub struct Tick(pub u64); +debug_inspect_impl!(Tick); + +/// An in-game instant used to measure time differences +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Serialize, Deserialize)] +pub struct GameInstant(pub Tick); +debug_inspect_impl!(GameInstant); + +/// The duration of a game event, in ticks +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Serialize, Deserialize)] +pub struct GameDuration(pub Tick); + +debug_inspect_impl!(GameDuration); + +/// The resource to know everything about the current in-game time +/// `GameTime` is subject to timewarp +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct GameTime { + /// The number of ticks elapsed since the start of the game + pub tick: Tick, + + /// Monotonic time in (game) seconds elapsed since the start of the game. + pub timestamp: f64, + + /// Game time in seconds elapsed since the start of the game + pub seconds: u32, + + /// Information about the time of the current day + pub daytime: DayTime, +} + +/// A useful format to define intervals or points in game time +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct DayTime { + /// Days elapsed since the start of the game + pub day: i32, + + /// Hours elapsed since the start of the day + pub hour: i32, + + /// Minutes elapsed since the start of the hour + pub minute: i32, + + /// Seconds elapsed since the start of the minute + pub second: i32, +} + +/// An interval of in-game time +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct TimeInterval { + pub start: DayTime, + pub end: DayTime, +} + +impl TimeInterval { + pub fn new(start: DayTime, end: DayTime) -> Self { + TimeInterval { start, end } + } + + pub fn dist(&self, t: DayTime) -> i32 { + if t < self.start { + self.start.gamesec() - t.gamesec() + } else if t > self.end { + t.gamesec() - self.end.gamesec() + } else { + 0 + } + } +} + +/// A periodic interval of in-game time. Used for schedules. (for example 9am -> 6pm) +#[derive(Inspect, Debug, Copy, Clone, Serialize, Deserialize)] +pub struct RecTimeInterval { + pub start_hour: i32, + pub start_minute: i32, + + pub end_hour: i32, + pub end_minute: i32, + + /// Does the interval go through midnight + overlap: bool, +} + +impl RecTimeInterval { + pub fn new((start_hour, start_minute): (i32, i32), (end_hour, end_minute): (i32, i32)) -> Self { + RecTimeInterval { + start_hour, + start_minute, + end_hour, + end_minute, + + overlap: end_hour < start_hour || (end_hour == start_hour && end_minute < start_minute), + } + } + + pub fn dist_until(&self, t: DayTime) -> i32 { + let mut start_dt = DayTime { + day: t.day, + hour: self.start_hour, + minute: self.start_minute, + second: 0, + }; + + let end_dt = DayTime { + day: t.day, + hour: self.end_hour, + minute: self.end_minute, + second: 0, + }; + + if !self.overlap { + if t < start_dt { + start_dt.gamesec() - t.gamesec() + } else if t > end_dt { + start_dt.day += 1; + start_dt.gamesec() - t.gamesec() + } else { + 0 + } + } else if t >= end_dt && t <= start_dt { + start_dt.gamesec() - t.gamesec() + } else { + 0 + } + } +} + +impl DayTime { + pub fn new(seconds: i32) -> DayTime { + DayTime { + day: 1 + seconds / SECONDS_PER_DAY, + hour: (seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR, + minute: (seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE, + second: seconds % SECONDS_PER_MINUTE, + } + } + + /// Returns the absolute difference (going either backward or forward in time) in seconds to the given daytime + pub fn dist(&self, to: &DayTime) -> i32 { + (self.gamesec() - to.gamesec()).abs() + } + + /// Returns the number of seconds elapsed since the start of the day + pub fn daysec(&self) -> i32 { + self.hour * SECONDS_PER_HOUR + self.minute * SECONDS_PER_MINUTE + self.second + } + + pub fn gamesec(&self) -> i32 { + self.day * SECONDS_PER_DAY + self.daysec() + } +} + +impl GameTime { + pub const HOUR: i32 = SECONDS_PER_HOUR; + pub const DAY: i32 = SECONDS_PER_DAY; + + pub fn new(tick: Tick) -> GameTime { + let timestamp = (tick.0 as f64 + 8.0 * TICKS_PER_HOUR as f64) / TICKS_PER_SECOND as f64; + let seconds = timestamp as u32; + + GameTime { + tick, + timestamp, + seconds, + daytime: DayTime::new(seconds as i32), + } + } + + pub fn instant(&self) -> GameInstant { + GameInstant(self.tick) + } + + pub fn daysec(&self) -> f64 { + self.timestamp % Self::DAY as f64 + } +} + +impl GameDuration { + pub fn from_secs(secs: u64) -> Self { + GameDuration(Tick(secs * TICKS_PER_SECOND)) + } + + pub fn from_minutes(mins: u64) -> Self { + GameDuration(Tick(mins * TICKS_PER_MINUTE)) + } + + pub fn seconds(&self) -> f64 { + self.0 .0 as f64 / TICKS_PER_SECOND as f64 + } + + pub fn minutes(&self) -> f64 { + self.0 .0 as f64 / TICKS_PER_MINUTE as f64 + } +} + +impl GameInstant { + /// Time elapsed since instant was taken + pub fn elapsed(&self, time: &GameTime) -> GameDuration { + GameDuration(Tick(time.tick.0 - self.0 .0)) + } +} + +impl Display for GameInstant { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let d = GameTime::new(self.0); + write!(f, "{}", d.daytime) + } +} + +impl Display for DayTime { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}d {:02}:{:02}", self.day, self.hour, self.minute) + } +} + +impl Add for GameInstant { + type Output = GameInstant; + + fn add(self, rhs: GameDuration) -> Self::Output { + GameInstant(Tick(self.0 .0 + rhs.0 .0)) + } +} + +impl Add for GameDuration { + type Output = GameInstant; + + fn add(self, rhs: GameInstant) -> Self::Output { + GameInstant(Tick(rhs.0 .0 + self.0 .0)) + } +} + +impl Add for GameTime { + type Output = GameTime; + + fn add(self, rhs: GameDuration) -> Self::Output { + GameTime::new(Tick(self.tick.0 + rhs.0 .0)) + } +} + +impl Add for GameDuration { + type Output = GameTime; + + fn add(self, rhs: GameTime) -> Self::Output { + GameTime::new(Tick(rhs.tick.0 + self.0 .0)) + } +} + +impl Sub for GameInstant { + type Output = GameInstant; + + fn sub(self, rhs: GameDuration) -> Self::Output { + GameInstant(Tick(self.0 .0.saturating_sub(rhs.0 .0))) + } +} + +#[cfg(test)] +mod test { + use common::timestep::debug_up_dt; + + #[test] + fn assert_up_dt_ticks_per_second_match() { + assert!((debug_up_dt().as_secs_f64() - super::DELTA_F64).abs() < 0.0001); + } +} + +impl Debug for Tick { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Display for Tick { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Display for GameDuration { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let x = self.0 .0 as f64; + #[rustfmt::skip] + let (v, unit) = match () { + _ if x < TICKS_PER_MINUTE as f64 => (x / TICKS_PER_SECOND as f64, "s"), + _ if x < TICKS_PER_HOUR as f64 => (x / TICKS_PER_MINUTE as f64, "m"), + _ => (x / TICKS_PER_HOUR as f64, "h"), + }; + + if (v.round() - v).abs() < 0.01 { + write!(f, "{}{}", v.round(), unit) + } else { + write!(f, "{:.2}{}", v, unit) + } + } +} + +#[derive(Debug, Error)] +pub enum TickParsingError { + #[error("expected positive number")] + NegativeNumber, + #[error("expected valid number")] + InvalidNumber, + #[error("expected string ending with t, s, m, h or d (or tick, second, month, hour, day)")] + InvalidSuffix, +} + +impl FromStr for GameDuration { + type Err = TickParsingError; + + fn from_str(s: &str) -> Result { + Tick::from_str(s).map(GameDuration) + } +} + +impl<'lua> FromLua<'lua> for GameDuration { + fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> mlua::Result { + Ok(GameDuration(Tick::from_lua(value, lua)?)) + } +} + +fn parse_tick_suffix(v: f64, suffix: &str) -> Option { + Some(match suffix { + "t" | "tick" | "ticks" => v, + "s" | "sec" | "secs" | "second" | "seconds" => (v * TICKS_PER_SECOND as f64).round(), + "m" | "min" | "mins" | "minute" | "minutes" => { + (v * TICKS_PER_SECOND as f64 * SECONDS_PER_MINUTE as f64).round() + } + "h" | "hour" | "hours" => (v * TICKS_PER_SECOND as f64 * SECONDS_PER_HOUR as f64).round(), + "d" | "day" | "days" => (v * TICKS_PER_SECOND as f64 * SECONDS_PER_DAY as f64).round(), + _ => return None, + }) +} + +impl FromStr for Tick { + type Err = TickParsingError; + + fn from_str(s: &str) -> Result { + let s = s.trim(); + let (v, end) = common::parse_f64(s).map_err(|_| TickParsingError::InvalidNumber)?; + let end = end.trim(); + let v = parse_tick_suffix(v, end).ok_or(TickParsingError::InvalidSuffix)?; + if v < 0.0 { + return Err(TickParsingError::NegativeNumber); + } + if v > u64::MAX as f64 { + return Err(TickParsingError::InvalidNumber); + } + Ok(Tick(v as u64)) + } +} + +impl<'lua> FromLua<'lua> for Tick { + fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> mlua::Result { + Ok(match value { + Value::Nil => Tick(0), + Value::Integer(i) => { + if i < 0 { + return Err(mlua::Error::FromLuaConversionError { + from: "negative integer", + to: "Tick", + message: Some("expected positive integer".into()), + }); + } + Tick(i as u64) + } + Value::Number(n) => { + if n < 0.0 { + return Err(mlua::Error::FromLuaConversionError { + from: "negative number", + to: "Tick", + message: Some("expected positive number".into()), + }); + } + Tick(n.round() as u64) + } + Value::String(s) => { + let s = s.to_str()?.trim(); + Tick::from_str(s).map_err(|e: TickParsingError| { + mlua::Error::FromLuaConversionError { + from: "string", + to: "Tick", + message: Some(e.to_string()), + } + })? + } + Value::Table(t) => { + let mut total = 0; + for kv in t.pairs::() { + let (k, v) = kv?; + + let v = parse_tick_suffix(v, &*k).ok_or_else(|| { + mlua::Error::FromLuaConversionError { + from: "string", + to: "Tick", + message: Some(TickParsingError::InvalidSuffix.to_string()), + } + })?; + + if v < 0.0 { + return Err(mlua::Error::FromLuaConversionError { + from: "negative number", + to: "Tick", + message: Some("expected positive number".into()), + }); + } + if total as f64 + v > u64::MAX as f64 { + return Err(mlua::Error::FromLuaConversionError { + from: "number", + to: "Tick", + message: Some("too big".into()), + }); + } + total += v as u64; + } + Tick(total) + } + _ => { + return Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "Tick", + message: Some("expected nil, number, table or string".into()), + }) + } + }) + } +} diff --git a/simulation/src/economy/market.rs b/simulation/src/economy/market.rs index 1d7aa478c..bf0c21c3d 100644 --- a/simulation/src/economy/market.rs +++ b/simulation/src/economy/market.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use geom::Vec2; use prototypes::{prototypes_iter, GoodsCompanyID, GoodsCompanyPrototype, ItemPrototype, Money}; -use crate::economy::{ItemID, WORKER_CONSUMPTION_PER_SECOND}; +use crate::economy::{ItemID, WORKER_CONSUMPTION_PER_MINUTE}; use crate::map::BuildingID; use crate::map_dynamic::BuildingInfos; use crate::SoulID; @@ -379,8 +379,9 @@ fn calculate_prices(price_multiplier: f32) -> BTreeMap { .find_map(|x| (x.id == id).then_some(x.amount)) .unwrap_or(0) as i64; - let price_workers = - recipe.complexity as i64 * company.n_workers as i64 * WORKER_CONSUMPTION_PER_SECOND; + let price_workers = recipe.duration.minutes() + * company.n_workers as f64 + * WORKER_CONSUMPTION_PER_MINUTE; let newprice = (price_consumption + Money::new_inner((price_workers.inner() as f32 * price_multiplier) as i64)) @@ -405,7 +406,7 @@ mod tests { use prototypes::test_prototypes; use prototypes::ItemID; - use crate::economy::WORKER_CONSUMPTION_PER_SECOND; + use crate::economy::WORKER_CONSUMPTION_PER_MINUTE; use crate::world::CompanyID; use crate::{FreightStationID, SoulID}; @@ -529,11 +530,11 @@ mod tests { let prices = super::calculate_prices(1.0); assert_eq!(prices.len(), 2); - let price_cereal = 2 * WORKER_CONSUMPTION_PER_SECOND; + let price_cereal = 2 * WORKER_CONSUMPTION_PER_MINUTE; assert_eq!(prices[&cereal], price_cereal); assert_eq!( prices[&wheat], - (price_cereal * 2 + 5 * WORKER_CONSUMPTION_PER_SECOND * 10) / 2 + (price_cereal * 2 + 5 * WORKER_CONSUMPTION_PER_MINUTE * 10) / 2 ); } } diff --git a/simulation/src/economy/mod.rs b/simulation/src/economy/mod.rs index 6d8d19c9f..aa686ed5f 100644 --- a/simulation/src/economy/mod.rs +++ b/simulation/src/economy/mod.rs @@ -21,14 +21,13 @@ mod government; mod market; use crate::map::Map; -use crate::utils::time::{Tick, TICKS_PER_SECOND}; use crate::world::HumanID; pub use ecostats::*; pub use government::*; pub use market::*; -use prototypes::{ItemID, Money}; +use prototypes::{GameTime, ItemID, Money, TICKS_PER_MINUTE}; -const WORKER_CONSUMPTION_PER_SECOND: Money = Money::new_cents(1); +const WORKER_CONSUMPTION_PER_MINUTE: Money = Money::new_cents(10); #[derive(Inspect, Default, Serialize, Deserialize)] pub struct Sold(pub Vec); @@ -46,10 +45,10 @@ pub fn market_update(world: &mut World, resources: &mut Resources) { let mut m = resources.write::(); let job_opening = ItemID::new("job-opening"); let mut gvt = resources.write::(); - let tick = resources.read::().0; + let tick = resources.read::().tick; - if tick % TICKS_PER_SECOND == 0 { - gvt.money -= n_workers as i64 * WORKER_CONSUMPTION_PER_SECOND; + if tick.0 % TICKS_PER_MINUTE == 0 { + gvt.money -= n_workers as i64 * WORKER_CONSUMPTION_PER_MINUTE; } let freights = &world.freight_stations; @@ -67,7 +66,7 @@ pub fn market_update(world: &mut World, resources: &mut Resources) { .map(|(id, _)| SoulID::FreightStation(id)) }); - resources.write::().advance(tick, trades); + resources.write::().advance(tick.0, trades); for &trade in trades.iter() { log::debug!("A trade was made! {:?}", trade); diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 3505cf7d1..3c452a22e 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -16,14 +16,14 @@ use crate::transportation::train::{ }; use crate::transportation::{transport_grid_synchronize, TransportGrid}; use crate::utils::resources::Resources; -use crate::utils::time::Tick; use crate::world::{CompanyEnt, FreightStationEnt, HumanEnt, TrainEnt, VehicleEnt, WagonEnt}; use crate::World; use crate::{ - add_souls_to_empty_buildings, utils, GameTime, ParCommandBuffer, RandProvider, Replay, - RunnableSystem, Simulation, SimulationOptions, RNG_SEED, SECONDS_PER_DAY, SECONDS_PER_HOUR, + add_souls_to_empty_buildings, utils, ParCommandBuffer, RandProvider, Replay, RunnableSystem, + Simulation, SimulationOptions, RNG_SEED, }; use common::saveload::{Bincode, Encoder, JSON}; +use prototypes::{GameTime, Tick}; use serde::de::DeserializeOwned; use serde::Serialize; @@ -77,15 +77,12 @@ pub fn init() { register_resource_default::("ecostats"); register_resource_default::("multiplayer_state"); register_resource_default::("random_vehicles"); - register_resource_default::("tick"); register_resource_default::("map"); register_resource_default::("train_reservations"); register_resource_default::("government"); register_resource_default::("pmanagement"); register_resource_default::("binfos"); - register_resource::("game_time", || { - GameTime::new(0.0, SECONDS_PER_DAY as f64 + 10.0 * SECONDS_PER_HOUR as f64) - }); + register_resource::("game_time", || GameTime::new(Tick(1))); register_resource::("transport_grid", || TransportGrid::new(100)); register_resource::("randprovider", || RandProvider::new(RNG_SEED)); register_resource_default::("dispatcher"); diff --git a/simulation/src/lib.rs b/simulation/src/lib.rs index 76f5545d6..5e0f674df 100644 --- a/simulation/src/lib.rs +++ b/simulation/src/lib.rs @@ -17,7 +17,6 @@ use std::hash::Hash; use std::time::{Duration, Instant}; use utils::rand_provider::RandProvider; use utils::scheduler::SeqSchedule; -use utils::time::{GameTime, SECONDS_PER_DAY, SECONDS_PER_HOUR}; #[macro_use] extern crate common; @@ -49,9 +48,9 @@ pub use world::*; use crate::init::{GSYSTEMS, INIT_FUNCS, SAVELOAD_FUNCS}; use crate::utils::scheduler::RunnableSystem; -use crate::utils::time::{Tick, SECONDS_PER_REALTIME_SECOND}; use crate::world_command::WorldCommand::Init; use common::FastMap; +use prototypes::{GameTime, Tick}; pub use utils::config::*; pub use utils::par_command_buffer::ParCommandBuffer; pub use utils::replay::*; @@ -245,23 +244,18 @@ impl Simulation { } } - const WORLD_TICK_DT: f32 = 0.05; { let mut time = self.write::(); - *time = GameTime::new( - WORLD_TICK_DT, - time.timestamp + SECONDS_PER_REALTIME_SECOND as f64 * WORLD_TICK_DT as f64, - ); + *time = GameTime::new(Tick(time.tick.0 + 1)); } game_schedule.execute(self); - self.write::().0 += 1; t.elapsed() } pub fn get_tick(&self) -> u64 { - self.resources.read::().0 + self.resources.read::().tick.0 } pub fn hashes(&self) -> BTreeMap { diff --git a/simulation/src/map/light_policy.rs b/simulation/src/map/light_policy.rs index 7e06af8a0..d513fde57 100644 --- a/simulation/src/map/light_policy.rs +++ b/simulation/src/map/light_policy.rs @@ -1,6 +1,6 @@ use crate::map::{Intersection, LaneID, Lanes, Roads, TrafficControl, TrafficLightSchedule}; -use crate::utils::time::SECONDS_PER_REALTIME_SECOND; use egui_inspect::{egui, egui::Ui, Inspect, InspectArgs}; +use prototypes::SECONDS_PER_REALTIME_SECOND; use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/simulation/src/map/map.rs b/simulation/src/map/map.rs index d523f30c4..998a83940 100644 --- a/simulation/src/map/map.rs +++ b/simulation/src/map/map.rs @@ -7,11 +7,10 @@ use crate::map::{ ParkingSpots, ProjectFilter, ProjectKind, Road, RoadID, RoadSegmentKind, SpatialMap, SubscriberChunkID, TerraformKind, UpdateType, Zone, }; -use crate::utils::time::Tick; use geom::OBB; use geom::{Spline3, Vec2, Vec3}; use ordered_float::OrderedFloat; -use prototypes::BuildingGen; +use prototypes::{BuildingGen, Tick}; use serde::{Deserialize, Serialize}; use slotmapd::HopSlotMap; diff --git a/simulation/src/map/pathfinding.rs b/simulation/src/map/pathfinding.rs index 5c57ee7ad..e0f117315 100644 --- a/simulation/src/map/pathfinding.rs +++ b/simulation/src/map/pathfinding.rs @@ -1,10 +1,10 @@ use crate::map::{ LaneID, LaneKind, LanePatternBuilder, Map, Traversable, TraverseDirection, TraverseKind, TurnID, }; -use crate::utils::time::Tick; use common::hash_u64; use geom::{PolyLine3, Vec3}; use ordered_float::OrderedFloat; +use prototypes::Tick; use serde::{Deserialize, Serialize}; use slotmapd::Key; diff --git a/simulation/src/map/terrain.rs b/simulation/src/map/terrain.rs index 8caf575ee..55ffb8e7c 100644 --- a/simulation/src/map/terrain.rs +++ b/simulation/src/map/terrain.rs @@ -1,9 +1,8 @@ use crate::map::procgen::heightmap; use crate::map::procgen::heightmap::tree_density; -use crate::utils::time::Tick; -use common::timestep::UP_DT; use flat_spatial::Grid; use geom::{lerp, vec2, Intersect, Radians, Ray3, Vec2, Vec3, AABB}; +use prototypes::{Tick, DELTA}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -175,7 +174,7 @@ impl Environment { return pos.z; } let phi = (-1.0 / (1.0 - dist * dist)).exp(); - pos.z + (amount * UP_DT.as_secs_f32()) * phi + pos.z + (amount * DELTA) * phi }), TerraformKind::Smooth => self .heightmap @@ -206,7 +205,7 @@ impl Environment { } let phi = (-1.0 / (1.0 - dist * dist)).exp(); pos.z - + (amount * UP_DT.as_secs_f32()) + + (amount * DELTA) * phi * (level - pos.z).signum() * (level - pos.z).abs().mul(0.1).clamp(0.0, 1.0) @@ -223,7 +222,7 @@ impl Environment { let coeff_along_d = (pos.xy() - p1.xy()).dot(d) / d.mag2(); let desired_height = lerp(p1.z, p2.z, coeff_along_d.clamp(0.0, 1.0)); - z += (amount * UP_DT.as_secs_f32()) + z += (amount * DELTA) * phi * (desired_height - pos.z).signum() * (desired_height - pos.z).abs().mul(0.1).clamp(0.0, 1.0); diff --git a/simulation/src/map_dynamic/itinerary.rs b/simulation/src/map_dynamic/itinerary.rs index c211000b7..0c7b4b89a 100644 --- a/simulation/src/map_dynamic/itinerary.rs +++ b/simulation/src/map_dynamic/itinerary.rs @@ -1,11 +1,11 @@ use crate::map::{Map, PathKind, Pathfinder, Traversable, TraverseDirection, TraverseKind}; use crate::utils::resources::Resources; -use crate::utils::time::{GameTime, Tick}; use crate::world::TrainID; use crate::World; use egui_inspect::egui::Ui; use egui_inspect::{Inspect, InspectArgs}; use geom::{Follower, Polyline3Queue, Transform, Vec3}; +use prototypes::{GameTime, Tick, DELTA}; use serde::{Deserialize, Serialize}; #[derive(Inspect, Debug, Serialize, Deserialize)] @@ -416,11 +416,11 @@ pub fn itinerary_update(world: &mut World, resources: &mut Resources) { profiling::scope!("map_dynamic::itinerary_update"); let time = &*resources.read::(); let map = &*resources.read::(); - let tick = *resources.read::(); + let tick = resources.read::().tick; world.query_it_trans_speed().for_each( |(it, trans, speed): (&mut Itinerary, &mut Transform, f32)| { - trans.pos = it.update(trans.pos, speed * time.realdelta, tick, time.seconds, map); + trans.pos = it.update(trans.pos, speed * DELTA, tick, time.seconds, map); }, ); diff --git a/simulation/src/multiplayer/chat.rs b/simulation/src/multiplayer/chat.rs index e8fae1c6f..0fddbef32 100644 --- a/simulation/src/multiplayer/chat.rs +++ b/simulation/src/multiplayer/chat.rs @@ -1,5 +1,5 @@ -use crate::utils::time::{GameInstant, GameTime}; use geom::Color; +use prototypes::{GameInstant, GameTime}; use serde::{Deserialize, Serialize}; #[derive(Default, Serialize, Deserialize)] @@ -39,6 +39,6 @@ impl Chat { impl Message { /// Returns the number of (game) seconds elapsed since the message was sent pub fn age_secs(&self, now: &GameTime) -> f64 { - self.sent_at.elapsed(now) + self.sent_at.elapsed(now).seconds() } } diff --git a/simulation/src/souls/desire/buyfood.rs b/simulation/src/souls/desire/buyfood.rs index 9551f38de..6eac10c64 100644 --- a/simulation/src/souls/desire/buyfood.rs +++ b/simulation/src/souls/desire/buyfood.rs @@ -2,14 +2,13 @@ use serde::{Deserialize, Serialize}; use egui_inspect::Inspect; use geom::Transform; -use prototypes::ItemID; +use prototypes::{GameInstant, GameTime, ItemID}; use crate::economy::{find_trade_place, Bought, Market}; use crate::map::BuildingID; use crate::map_dynamic::{BuildingInfos, Destination}; use crate::souls::human::HumanDecisionKind; use crate::transportation::Location; -use crate::utils::time::{GameInstant, GameTime}; use crate::world::{HumanEnt, HumanID}; use crate::{ParCommandBuffer, SoulID}; @@ -53,7 +52,7 @@ impl BuyFood { return 1.0; } } - self.last_ate.elapsed(time) as f32 / GameTime::DAY as f32 - 1.0 + self.last_ate.elapsed(time).seconds() as f32 / GameTime::DAY as f32 - 1.0 } pub fn apply( diff --git a/simulation/src/souls/desire/work.rs b/simulation/src/souls/desire/work.rs index ced0e9078..69d00cda4 100644 --- a/simulation/src/souls/desire/work.rs +++ b/simulation/src/souls/desire/work.rs @@ -2,9 +2,9 @@ use crate::map::BuildingID; use crate::map_dynamic::{Destination, Router}; use crate::souls::human::HumanDecisionKind; use crate::transportation::Location; -use crate::utils::time::{GameTime, RecTimeInterval, SECONDS_PER_HOUR}; use crate::world::VehicleID; use egui_inspect::Inspect; +use prototypes::{GameTime, RecTimeInterval, MINUTES_PER_HOUR}; use serde::{Deserialize, Serialize}; #[derive(Debug, Copy, Clone, Serialize, Deserialize)] @@ -30,8 +30,8 @@ impl Work { Work { workplace, work_inter: RecTimeInterval::new( - (8, (offset * SECONDS_PER_HOUR as f32) as i32), - (18, (offset * SECONDS_PER_HOUR as f32) as i32), + (8, (offset * MINUTES_PER_HOUR as f32) as i32), + (18, (offset * MINUTES_PER_HOUR as f32) as i32), ), kind, last_score: 0.0, diff --git a/simulation/src/souls/freight_station.rs b/simulation/src/souls/freight_station.rs index b3c5bc754..68987b22d 100644 --- a/simulation/src/souls/freight_station.rs +++ b/simulation/src/souls/freight_station.rs @@ -1,14 +1,13 @@ use serde::{Deserialize, Serialize}; use geom::Transform; -use prototypes::FreightStationPrototypeID; +use prototypes::{FreightStationPrototypeID, GameTime}; use crate::map::{BuildingID, Map, PathKind}; use crate::map_dynamic::{ BuildingInfos, DispatchID, DispatchKind, DispatchQueryTarget, Dispatcher, Itinerary, }; use crate::utils::resources::Resources; -use crate::utils::time::{GameTime, Tick}; use crate::world::{FreightStationEnt, FreightStationID, TrainID}; use crate::World; use crate::{ParCommandBuffer, Simulation, SoulID}; @@ -76,7 +75,7 @@ pub fn freight_station_system(world: &mut World, resources: &mut Resources) { let mut dispatch = resources.write::(); let map = resources.read::(); let time = resources.read::(); - let tick = *resources.read::(); + let tick = time.tick; for (me, f) in world.freight_stations.iter_mut() { let pos = f.trans; diff --git a/simulation/src/souls/goods_company.rs b/simulation/src/souls/goods_company.rs index 3924de562..fc8432c03 100644 --- a/simulation/src/souls/goods_company.rs +++ b/simulation/src/souls/goods_company.rs @@ -2,7 +2,9 @@ use serde::{Deserialize, Serialize}; use egui_inspect::Inspect; use geom::{Transform, Vec2}; -use prototypes::{CompanyKind, GoodsCompanyID, GoodsCompanyPrototype, ItemID, Power, Recipe}; +use prototypes::{ + CompanyKind, GoodsCompanyID, GoodsCompanyPrototype, ItemID, Power, Recipe, DELTA, +}; use crate::economy::{find_trade_place, Market}; use crate::map::{Building, BuildingID, Map, Zone, MAX_ZONE_AREA}; @@ -10,7 +12,6 @@ use crate::map_dynamic::{BuildingInfos, ElectricityFlow}; use crate::souls::desire::WorkKind; use crate::transportation::{spawn_parked_vehicle, VehicleKind}; use crate::utils::resources::Resources; -use crate::utils::time::GameTime; use crate::world::{CompanyEnt, HumanEnt, HumanID, VehicleID}; use crate::{ParCommandBuffer, SoulID, VehicleEnt}; use crate::{Simulation, World}; @@ -170,7 +171,6 @@ pub fn company_soul( pub fn company_system(world: &mut World, res: &mut Resources) { profiling::scope!("souls::company_system"); - let delta = res.read::().realdelta; let cbuf: &ParCommandBuffer = &res.read(); let cbuf_human: &ParCommandBuffer = &res.read(); let binfos: &BuildingInfos = &res.read(); @@ -191,7 +191,7 @@ pub fn company_system(world: &mut World, res: &mut Resources) { if recipe_should_produce(recipe, soul, market) { let productivity = c.productivity(proto, b.zone.as_ref(), map, elec_flow); - c.comp.progress += productivity * delta / recipe.complexity as f32; + c.comp.progress += productivity * DELTA / recipe.duration.seconds() as f32; } if c.comp.progress >= 1.0 { @@ -225,9 +225,6 @@ pub fn company_system(world: &mut World, res: &mut Resources) { } (|| { - let Some(trade) = c.sold.0.pop() else { - return; - }; let Some(driver) = c.comp.driver else { return; }; @@ -243,6 +240,9 @@ pub fn company_system(world: &mut World, res: &mut Resources) { ) { return; } + let Some(trade) = c.sold.0.pop() else { + return; + }; let Some(owner_build) = find_trade_place(trade.buyer, binfos) else { log::warn!("driver can't find the place to deliver for {:?}", &trade); return; diff --git a/simulation/src/souls/human.rs b/simulation/src/souls/human.rs index 91342b805..ce2c2db00 100644 --- a/simulation/src/souls/human.rs +++ b/simulation/src/souls/human.rs @@ -8,14 +8,13 @@ use crate::transportation::{ }; use crate::utils::rand_provider::RandProvider; use crate::utils::resources::Resources; -use crate::utils::time::GameTime; use crate::world::{FreightStationEnt, HumanEnt, HumanID, VehicleID}; use crate::World; use crate::{BuildingKind, Map, ParCommandBuffer, Simulation, SoulID}; use egui_inspect::Inspect; use geom::Transform; use lazy_static::lazy_static; -use prototypes::ItemID; +use prototypes::{GameTime, ItemID}; use serde::{Deserialize, Serialize}; #[derive(Inspect, Serialize, Deserialize, Default)] diff --git a/simulation/src/tests/mod.rs b/simulation/src/tests/mod.rs index c6979b24b..86833915a 100644 --- a/simulation/src/tests/mod.rs +++ b/simulation/src/tests/mod.rs @@ -4,7 +4,6 @@ use crate::map::{BuildingID, LanePatternBuilder, ProjectFilter}; use crate::map_dynamic::BuildingInfos; use crate::utils::scheduler::SeqSchedule; -use crate::utils::time::Tick; use crate::world_command::{WorldCommand, WorldCommands}; use crate::{Simulation, SimulationOptions}; use common::logger::MyLog; @@ -77,7 +76,7 @@ impl TestCtx { Some(hash), "key: {:?} at tick {}", key, - self.g.read::().0, + self.g.get_tick(), ); } } diff --git a/simulation/src/tests/test_iso.rs b/simulation/src/tests/test_iso.rs index cf512e958..a5e25db5a 100644 --- a/simulation/src/tests/test_iso.rs +++ b/simulation/src/tests/test_iso.rs @@ -1,7 +1,6 @@ use crate::init::init; use crate::map::{LanePatternBuilder, Map, MapProject, ProjectKind}; use crate::utils::scheduler::SeqSchedule; -use crate::utils::time::Tick; use crate::World; use crate::{Replay, Simulation}; use common::logger::MyLog; @@ -259,14 +258,14 @@ fn test_world_survives_serde() { idx = next_idx;*/ - let tick = sim.read::().0; + let tick = sim.get_tick(); if tick % 1000 != 0 || (tick < 7840) { continue; } println!( "--- tick {} ({}/{})", - sim.read::().0, + sim.get_tick(), loader.pastt.0, loader.replay.commands.last().unwrap().0 .0 ); diff --git a/simulation/src/tests/vehicles.rs b/simulation/src/tests/vehicles.rs index dfa5feb14..96a337364 100644 --- a/simulation/src/tests/vehicles.rs +++ b/simulation/src/tests/vehicles.rs @@ -1,6 +1,6 @@ /* use crate::map_dynamic::{Destination, Itinerary, ParkingManagement, Router}; -use crate::utils::time::GameTime; +use prototypes::GameTime; use crate::vehicles::{spawn_parked_vehicle, unpark, VehicleKind}; use geom::{vec2, vec3, Vec3}; use crate::map::{Map, PathKind}; diff --git a/simulation/src/transportation/pedestrian.rs b/simulation/src/transportation/pedestrian.rs index 351a9a512..43fbc8a82 100644 --- a/simulation/src/transportation/pedestrian.rs +++ b/simulation/src/transportation/pedestrian.rs @@ -4,10 +4,10 @@ use crate::transportation::{ }; use crate::utils::rand_provider::RandProvider; use crate::utils::resources::Resources; -use crate::utils::time::GameTime; use crate::World; use egui_inspect::Inspect; use geom::{angle_lerpxy, Color, Transform, Vec3}; +use prototypes::DELTA; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Inspect)] @@ -65,17 +65,15 @@ pub fn random_pedestrian_shirt_color(r: &mut RandProvider) -> Color { unreachable!(); } -pub fn pedestrian_decision_system(world: &mut World, resources: &mut Resources) { +pub fn pedestrian_decision_system(world: &mut World, _resources: &mut Resources) { profiling::scope!("transportation::pedestrian_decision_system"); - let ra = &*resources.read(); world.humans .values_mut() //.par_bridge() - .for_each(|human| pedestrian_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.pedestrian)) + .for_each(|human| pedestrian_decision(&mut human.it, &mut human.trans, &mut human.speed, &mut human.pedestrian)) } pub fn pedestrian_decision( - time: &GameTime, it: &mut Itinerary, trans: &mut Transform, kin: &mut Speed, @@ -83,27 +81,21 @@ pub fn pedestrian_decision( ) { let (desired_v, desired_dir) = calc_decision(pedestrian, trans, it); - pedestrian.walk_anim += 7.0 * kin.0 * time.realdelta / pedestrian.walking_speed; + pedestrian.walk_anim += 7.0 * kin.0 * DELTA / pedestrian.walking_speed; pedestrian.walk_anim %= 2.0 * std::f32::consts::PI; - physics(kin, trans, time, desired_v, desired_dir); + physics(kin, trans, desired_v, desired_dir); } const PEDESTRIAN_ACC: f32 = 1.5; -pub fn physics( - kin: &mut Speed, - trans: &mut Transform, - time: &GameTime, - desired_velocity: f32, - desired_dir: Vec3, -) { +pub fn physics(kin: &mut Speed, trans: &mut Transform, desired_velocity: f32, desired_dir: Vec3) { let diff = desired_velocity - kin.0; - let mag = diff.min(time.realdelta * PEDESTRIAN_ACC); + let mag = diff.min(DELTA * PEDESTRIAN_ACC); if mag > 0.0 { kin.0 += mag; } const ANG_VEL: f32 = 1.0; - trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.realdelta); + trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * DELTA); } pub fn calc_decision( diff --git a/simulation/src/transportation/road.rs b/simulation/src/transportation/road.rs index d0cafe7d7..26203c7a2 100644 --- a/simulation/src/transportation/road.rs +++ b/simulation/src/transportation/road.rs @@ -5,11 +5,11 @@ use crate::transportation::{ }; use crate::transportation::{Vehicle, VehicleState, TIME_TO_PARK}; use crate::utils::resources::Resources; -use crate::utils::time::GameTime; use crate::world::{VehicleEnt, VehicleID}; use crate::ParCommandBuffer; use crate::World; use geom::{angle_lerpxy, Ray, Transform, Vec2, Vec3}; +use prototypes::{GameTime, DELTA}; use slotmapd::Key; pub fn vehicle_decision_system(world: &mut World, resources: &mut Resources) { @@ -71,7 +71,6 @@ pub fn vehicle_decision( trans, kin, vehicle, - time, self_obj, map, desired_speed, @@ -83,13 +82,11 @@ pub fn vehicle_state_update_system(world: &mut World, resources: &mut Resources) profiling::scope!("transportation::vehicle_state_update_system"); let ra = &*resources.read(); let rb = &*resources.read(); - let rc = &*resources.read(); world.vehicles.iter_mut().for_each(|(ent, v)| { vehicle_state_update( ra, rb, - rc, ent, &mut v.vehicle, &mut v.trans, @@ -102,7 +99,6 @@ pub fn vehicle_state_update_system(world: &mut World, resources: &mut Resources) /// Decides whether a vehicle should change states, from parked to unparking to driving etc pub fn vehicle_state_update( buf: &ParCommandBuffer, - time: &GameTime, map: &Map, ent: VehicleID, vehicle: &mut Vehicle, @@ -113,7 +109,7 @@ pub fn vehicle_state_update( match vehicle.state { VehicleState::RoadToPark(_, ref mut t, _) => { // Vehicle is on rails when parking. - *t += time.realdelta / TIME_TO_PARK; + *t += DELTA / TIME_TO_PARK; if *t >= 1.0 { let v = coll.take(); @@ -146,7 +142,6 @@ fn physics( trans: &mut Transform, kin: &mut Speed, vehicle: &mut Vehicle, - time: &GameTime, obj: &TransportState, map: &Map, desired_speed: f32, @@ -170,26 +165,19 @@ fn physics( let kind = vehicle.kind; let speed = speed - + (desired_speed - speed).clamp( - -time.realdelta * kind.deceleration(), - time.realdelta * kind.acceleration(), - ); + + (desired_speed - speed).clamp(-DELTA * kind.deceleration(), DELTA * kind.acceleration()); let max_ang_vel = (speed.abs() / kind.min_turning_radius()).clamp(0.0, 3.0); let approx_angle = trans.dir.distance(desired_dir); - vehicle.ang_velocity += time.realdelta * kind.ang_acc(); + vehicle.ang_velocity += DELTA * kind.ang_acc(); vehicle.ang_velocity = vehicle .ang_velocity .min(4.0 * approx_angle) .min(max_ang_vel); - trans.dir = angle_lerpxy( - trans.dir, - desired_dir, - vehicle.ang_velocity * time.realdelta, - ); + trans.dir = angle_lerpxy(trans.dir, desired_dir, vehicle.ang_velocity * DELTA); kin.0 = speed; } @@ -207,7 +195,7 @@ pub fn calc_decision<'a>( ) -> (f32, Vec3) { let default_return = (0.0, trans.dir); if vehicle.wait_time > 0.0 { - vehicle.wait_time -= time.realdelta; + vehicle.wait_time -= DELTA; return default_return; } let objective: Vec3 = unwrap_or!(it.get_point(), return default_return); @@ -227,7 +215,7 @@ pub fn calc_decision<'a>( ); if let VehicleState::Panicking(since) = vehicle.state { - if since.elapsed(time) > 5.0 { + if since.elapsed(time).seconds() > 200.0 { vehicle.state = VehicleState::Driving; } } else if speed.abs() < 0.2 && front_dist < 1.5 { diff --git a/simulation/src/transportation/testing_vehicles.rs b/simulation/src/transportation/testing_vehicles.rs index bcbda1101..0b60a885b 100644 --- a/simulation/src/transportation/testing_vehicles.rs +++ b/simulation/src/transportation/testing_vehicles.rs @@ -1,7 +1,6 @@ use crate::map::{Map, PathKind}; use crate::map_dynamic::Itinerary; use crate::utils::resources::Resources; -use crate::utils::time::Tick; use crate::{VehicleID, World}; use common::scroll::BTreeSetScroller; use serde::{Deserialize, Serialize}; @@ -19,7 +18,7 @@ pub fn random_vehicles_update(world: &mut World, res: &mut Resources) { let mut to_kill = Vec::new(); - let tick = res.read::(); + let tick = res.tick(); for &v_id in rv.vehicle_scroller.iter_looped(&rv.vehicles).take(100) { let v = match world.vehicles.get_mut(v_id) { @@ -35,8 +34,7 @@ pub fn random_vehicles_update(world: &mut World, res: &mut Resources) { } let rng = common::hash_u64((tick.0, v_id)); - if let Some(it) = Itinerary::random_route(rng, v.trans.pos, *tick, &map, PathKind::Vehicle) - { + if let Some(it) = Itinerary::random_route(rng, v.trans.pos, tick, &map, PathKind::Vehicle) { v.it = it; } } diff --git a/simulation/src/transportation/train.rs b/simulation/src/transportation/train.rs index a2ed2eaa0..5ff34d7c0 100644 --- a/simulation/src/transportation/train.rs +++ b/simulation/src/transportation/train.rs @@ -1,16 +1,20 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use ordered_float::OrderedFloat; +use serde::{Deserialize, Serialize}; +use slotmapd::HopSlotMap; + +use egui_inspect::Inspect; +use geom::{PolyLine3, Polyline3Queue, Transform, Vec3}; +use prototypes::DELTA; + use crate::map::{IntersectionID, LaneID, Map, TraverseKind}; use crate::map_dynamic::ItineraryFollower; use crate::transportation::Speed; use crate::utils::resources::Resources; use crate::world::{TrainEnt, TrainID, WagonEnt}; -use crate::{GameTime, Itinerary, ItineraryLeader, Simulation, World}; -use egui_inspect::Inspect; -use geom::{PolyLine3, Polyline3Queue, Transform, Vec3}; -use ordered_float::OrderedFloat; -use serde::{Deserialize, Serialize}; -use slotmapd::HopSlotMap; -use std::collections::btree_map::Entry; -use std::collections::BTreeMap; +use crate::{Itinerary, ItineraryLeader, Simulation, World}; #[derive(Default, Serialize, Deserialize)] pub struct TrainReservations { @@ -303,7 +307,6 @@ pub fn train_reservations_update(world: &mut World, resources: &mut Resources) { pub fn locomotive_system(world: &mut World, resources: &mut Resources) { profiling::scope!("transportation::locomotive_system"); let map: &Map = &resources.read(); - let time: &GameTime = &resources.read(); let reservs: &TrainReservations = &resources.read(); // asume iter order stays the same @@ -333,21 +336,21 @@ pub fn locomotive_system(world: &mut World, resources: &mut Resources) { t.trans.dir = desired_dir; t.speed.0 += (desired_speed - t.speed.0).clamp( - -time.realdelta * t.locomotive.dec_force, - time.realdelta * t.locomotive.acc_force, + -DELTA * t.locomotive.dec_force, + DELTA * t.locomotive.acc_force, ); if t.speed.0 <= 0.001 { - t.res.waited_for += time.realdelta; + t.res.waited_for += DELTA; } else { t.res.waited_for = 0.0; } for v in t.res.past_travers.values_mut() { - *v += t.speed.0 * time.realdelta; + *v += t.speed.0 * DELTA; if t.res.waited_for > 60.0 { - *v += 0.1 * time.realdelta; + *v += 0.1 * DELTA; } } - t.res.cur_travers_dist += t.speed.0 * time.realdelta; + t.res.cur_travers_dist += t.speed.0 * DELTA; } } diff --git a/simulation/src/transportation/vehicle.rs b/simulation/src/transportation/vehicle.rs index 5712c78d2..9343a67ed 100644 --- a/simulation/src/transportation/vehicle.rs +++ b/simulation/src/transportation/vehicle.rs @@ -1,12 +1,12 @@ use crate::map_dynamic::{Itinerary, ParkingManagement, SpotReservation}; use crate::transportation::{TransportGrid, TransportState, TransportationGroup, Transporter}; use crate::utils::rand_provider::RandProvider; -use crate::utils::time::GameInstant; use crate::world::{VehicleEnt, VehicleID}; use crate::Simulation; use egui_inspect::Inspect; use geom::Transform; use geom::{Color, Spline3, Vec3}; +use prototypes::GameInstant; use serde::{Deserialize, Serialize}; /// The duration for the parking animation. diff --git a/simulation/src/utils/mod.rs b/simulation/src/utils/mod.rs index e2ac33a1f..b534031e0 100644 --- a/simulation/src/utils/mod.rs +++ b/simulation/src/utils/mod.rs @@ -4,6 +4,5 @@ pub mod rand_provider; pub mod replay; pub mod resources; pub mod scheduler; -pub mod time; pub use config::*; diff --git a/simulation/src/utils/replay.rs b/simulation/src/utils/replay.rs index 3ed358756..50120a7de 100644 --- a/simulation/src/utils/replay.rs +++ b/simulation/src/utils/replay.rs @@ -1,7 +1,7 @@ use crate::utils::scheduler::SeqSchedule; -use crate::utils::time::Tick; use crate::world_command::WorldCommand; use crate::Simulation; +use prototypes::Tick; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/simulation/src/utils/resources.rs b/simulation/src/utils/resources.rs index 0186345f6..7b196500a 100644 --- a/simulation/src/utils/resources.rs +++ b/simulation/src/utils/resources.rs @@ -1,3 +1,4 @@ +use prototypes::{GameTime, Tick}; use std::any::{Any, TypeId}; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -23,6 +24,10 @@ impl Resources { Self::default() } + pub fn tick(&self) -> Tick { + self.read::().tick + } + pub fn contains(&self) -> bool { self.resources.contains_key(&TypeId::of::()) } diff --git a/simulation/src/utils/time.rs b/simulation/src/utils/time.rs deleted file mode 100644 index dd0742d00..000000000 --- a/simulation/src/utils/time.rs +++ /dev/null @@ -1,220 +0,0 @@ -use egui_inspect::Inspect; -use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; - -pub const SECONDS_PER_REALTIME_SECOND: u32 = 15; -pub const SECONDS_PER_HOUR: i32 = 3600; -pub const HOURS_PER_DAY: i32 = 24; -pub const SECONDS_PER_DAY: i32 = SECONDS_PER_HOUR * HOURS_PER_DAY; -pub const TICKS_PER_SECOND: u64 = 50; - -/// The amount of time the game was updated -/// Used as a resource -#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub struct Tick(pub u64); - -/// An in-game instant used to measure time differences -#[derive(Inspect, PartialEq, PartialOrd, Debug, Copy, Clone, Serialize, Deserialize)] -pub struct GameInstant { - /// Time in seconds elapsed since the start of the game - pub timestamp: f64, -} - -/// The resource to know everything about the current in-game time -/// `GameTime` is subject to timewarp -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub struct GameTime { - /// Monotonic time in (game) seconds elapsed since the start of the game. - pub timestamp: f64, - - /// Real time elapsed since the last frame, useful for animations - pub realdelta: f32, - - /// Game time in seconds elapsed since the start of the game - pub seconds: u32, - - /// Information about the time of the current day - pub daytime: DayTime, -} - -/// A useful format to define intervals or points in game time -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct DayTime { - /// Days elapsed since the start of the game - pub day: i32, - - /// Hours elapsed since the start of the day - pub hour: i32, - - /// Seconds elapsed since the start of the hour - pub second: i32, -} - -/// An interval of in-game time -#[derive(Copy, Clone, Serialize, Deserialize)] -pub struct TimeInterval { - pub start: DayTime, - pub end: DayTime, -} - -impl TimeInterval { - pub fn new(start: DayTime, end: DayTime) -> Self { - TimeInterval { start, end } - } - - pub fn dist(&self, t: DayTime) -> i32 { - if t < self.start { - self.start.gamesec() - t.gamesec() - } else if t > self.end { - t.gamesec() - self.end.gamesec() - } else { - 0 - } - } -} - -/// A periodic interval of in-game time. Used for schedules. (for example 9am -> 6pm) -#[derive(Inspect, Debug, Copy, Clone, Serialize, Deserialize)] -pub struct RecTimeInterval { - pub start_hour: i32, - pub start_second: i32, - - pub end_hour: i32, - pub end_second: i32, - - /// Does the interval go through midnight - overlap: bool, -} - -impl RecTimeInterval { - pub fn new((start_hour, start_second): (i32, i32), (end_hour, end_second): (i32, i32)) -> Self { - RecTimeInterval { - start_hour, - start_second, - end_hour, - end_second, - - overlap: end_hour < start_hour || (end_hour == start_hour && end_second < start_second), - } - } - - pub fn dist_until(&self, t: DayTime) -> i32 { - let mut start_dt = DayTime { - day: t.day, - hour: self.start_hour, - second: self.start_second, - }; - - let end_dt = DayTime { - day: t.day, - hour: self.end_hour, - second: self.end_second, - }; - - if !self.overlap { - if t < start_dt { - start_dt.gamesec() - t.gamesec() - } else if t > end_dt { - start_dt.day += 1; - start_dt.gamesec() - t.gamesec() - } else { - 0 - } - } else if t >= end_dt && t <= start_dt { - start_dt.gamesec() - t.gamesec() - } else { - 0 - } - } -} - -impl DayTime { - pub fn new(seconds: i32) -> DayTime { - DayTime { - day: seconds / SECONDS_PER_DAY, - hour: (seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR, - second: (seconds % SECONDS_PER_HOUR), - } - } - - /// Returns the absolute difference (going either backward or forward in time) in seconds to the given daytime - pub fn dist(&self, to: &DayTime) -> i32 { - (self.gamesec() - to.gamesec()).abs() - } - - /// Returns the number of seconds elapsed since the start of the day - pub fn daysec(&self) -> i32 { - self.hour * SECONDS_PER_HOUR + self.second - } - - pub fn gamesec(&self) -> i32 { - self.day * SECONDS_PER_DAY + self.daysec() - } -} - -impl GameTime { - pub const HOUR: i32 = SECONDS_PER_HOUR; - pub const DAY: i32 = SECONDS_PER_DAY; - - pub fn new(delta: f32, timestamp: f64) -> GameTime { - if timestamp > 1e9 { - log::warn!("Time went too fast, approaching limit."); - } - - let seconds = timestamp as u32; - GameTime { - timestamp, - realdelta: delta, - seconds, - daytime: DayTime::new(seconds as i32), - } - } - - pub fn instant(&self) -> GameInstant { - GameInstant { - timestamp: self.timestamp, - } - } - - /// Returns true every freq seconds - pub fn tick(&self, freq: u32) -> bool { - let time_near = (self.seconds / freq * freq) as f64; - self.timestamp > time_near - && (self.timestamp - SECONDS_PER_REALTIME_SECOND as f64 * self.realdelta as f64) - <= time_near - } - - pub fn daysec(&self) -> f64 { - self.timestamp % Self::DAY as f64 - } -} - -impl GameInstant { - /// Time elapsed since instant was taken, in seconds - pub fn elapsed(&self, time: &GameTime) -> f64 { - time.timestamp - self.timestamp - } -} - -impl Display for GameInstant { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let d = GameTime::new(0.0, self.timestamp); - write!(f, "{}", d.daytime) - } -} - -impl Display for DayTime { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}d {:02}:{:02}", self.day, self.hour, self.second) - } -} - -#[cfg(test)] -mod test { - use common::timestep::UP_DT; - - #[test] - fn assert_up_dt_ticks_per_second_match() { - assert!((1.0 / UP_DT.as_secs_f64() - super::TICKS_PER_SECOND as f64).abs() < 0.0001); - } -} diff --git a/simulation/src/world_command.rs b/simulation/src/world_command.rs index 2db80e2b5..ba12fd94c 100644 --- a/simulation/src/world_command.rs +++ b/simulation/src/world_command.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use geom::{vec3, Vec2, Vec3, OBB}; use prototypes::BuildingGen; +use prototypes::GameTime; use WorldCommand::*; use crate::economy::Government; @@ -20,7 +21,6 @@ use crate::transportation::testing_vehicles::RandomVehicles; use crate::transportation::train::{spawn_train, RailWagonKind}; use crate::transportation::{spawn_parked_vehicle_with_spot, unpark, VehicleKind}; use crate::utils::rand_provider::RandProvider; -use crate::utils::time::{GameTime, Tick}; use crate::{Replay, Simulation, SimulationOptions}; #[derive(Clone, Default)] @@ -220,8 +220,8 @@ impl WorldCommand { let mut rep = sim.resources.write::(); if rep.enabled { - let tick = sim.read::(); - rep.commands.push((*tick, self.clone())); + let tick = sim.read::().tick; + rep.commands.push((tick, self.clone())); } drop(rep); @@ -308,8 +308,8 @@ impl WorldCommand { if opts.save_replay { let mut rep = sim.resources.write::(); rep.enabled = true; - let tick = sim.read::(); - rep.commands.push((*tick, Init(opts.clone()))); + let tick = sim.read::().tick; + rep.commands.push((tick, Init(opts.clone()))); } if opts.terrain_size > 0 { @@ -359,7 +359,7 @@ impl WorldCommand { level, slope, } => { - let tick = *sim.read::(); + let tick = sim.read::().tick; sim.map_mut() .terraform(tick, kind, center, radius, amount, level, slope); }