diff --git a/Cargo.lock b/Cargo.lock index 0246917d..8be24acd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -679,6 +685,19 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -722,11 +741,11 @@ dependencies = [ "arc-swap", "atomic_refcell", "common", + "derive_more", "easybench", "egui-inspect", "flat_spatial", "geom", - "hecs", "inline_tweak", "lazy_static", "log", @@ -1043,7 +1062,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags 1.3.2", "gpu-descriptor-types", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -1064,15 +1083,6 @@ dependencies = [ "ahash 0.7.6", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.3", -] - [[package]] name = "hassle-rs" version = "0.10.0" @@ -1108,17 +1118,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "hecs" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2711ad60b74f2f3d0c0ac338a58410a5249da44005971ae806d2925e6b5167" -dependencies = [ - "hashbrown 0.13.2", - "serde", - "spin", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -1190,7 +1189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -1561,7 +1560,6 @@ dependencies = [ "flat_spatial", "geom", "getrandom", - "hecs", "include_dir", "inline_tweak", "lewton", @@ -2170,6 +2168,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -2216,6 +2223,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" version = "1.0.163" @@ -2325,12 +2338,6 @@ dependencies = [ "wayland-protocols", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spirv" version = "0.2.0+1.5.4" diff --git a/Cargo.toml b/Cargo.toml index 0f3fe34e..2fae8cbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ resolver = "2" default-members = ["native_app"] [workspace.dependencies] -hecs = "0.10.0" egui = "0.22.0" flat_spatial = "0.5.1" egui-wgpu = "0.22.0" diff --git a/egregoria/Cargo.toml b/egregoria/Cargo.toml index 8df31ec6..59156c36 100644 --- a/egregoria/Cargo.toml +++ b/egregoria/Cargo.toml @@ -17,13 +17,13 @@ common = { path = "../common" } slotmap = { version = "1.0.2", default-features = false, features = ["serde", "unstable"] } rayon = "1.6" atomic_refcell = "0.1.6" -hecs = { workspace = true, features=["column-serialize"] } profiling = "1.0.5" inline_tweak = { version = "1.0.9", features = ["release_tweak"] } pathfinding = "4.2.1" serde-big-array = "0.5.0" lazy_static = "1.4.0" arc-swap = "1.3.0" +derive_more = "0.99.17" [dev-dependencies] easybench = "1.1.0" diff --git a/egregoria/src/economy/market.rs b/egregoria/src/economy/market.rs index 9c7a7337..92630080 100644 --- a/egregoria/src/economy/market.rs +++ b/egregoria/src/economy/market.rs @@ -79,6 +79,8 @@ pub enum TradeTarget { ExternalTrade, } +debug_inspect_impl!(TradeTarget); + impl TradeTarget { pub(crate) fn soul(self) -> SoulID { match self { @@ -88,7 +90,7 @@ impl TradeTarget { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Inspect, Copy, Clone, Debug, Serialize, Deserialize)] pub struct Trade { pub buyer: TradeTarget, pub seller: TradeTarget, @@ -438,19 +440,19 @@ mod tests { use super::Market; use crate::economy::{ItemRegistry, WORKER_CONSUMPTION_PER_SECOND}; use crate::souls::goods_company::{CompanyKind, GoodsCompanyDescription, Recipe}; + use crate::world::CompanyID; use crate::{map::BuildingGen, GoodsCompanyRegistry, SoulID}; use geom::{vec2, Vec2}; - use hecs::Entity; - fn mk_ent(id: u64) -> Entity { - Entity::from_bits(id).unwrap() + fn mk_ent(id: u64) -> CompanyID { + CompanyID::from(slotmap::KeyData::from_ffi(id)) } #[test] fn test_match_orders() { - let seller = SoulID(mk_ent((1 << 32) | 1)); - let seller_far = SoulID(mk_ent((1 << 32) | 2)); - let buyer = SoulID(mk_ent((1 << 32) | 3)); + let seller = SoulID::GoodsCompany(mk_ent((1 << 32) | 1)); + let seller_far = SoulID::GoodsCompany(mk_ent((1 << 32) | 2)); + let buyer = SoulID::GoodsCompany(mk_ent((1 << 32) | 3)); let mut registry = ItemRegistry::default(); diff --git a/egregoria/src/economy/mod.rs b/egregoria/src/economy/mod.rs index 8d785504..23c35b7e 100644 --- a/egregoria/src/economy/mod.rs +++ b/egregoria/src/economy/mod.rs @@ -8,9 +8,9 @@ //! - The government, which is the entity representing the player //! use crate::utils::resources::Resources; +use crate::World; use crate::{GoodsCompanyRegistry, SoulID}; use egui_inspect::Inspect; -use hecs::World; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt::{Debug, Display, Formatter}; @@ -22,8 +22,8 @@ mod government; mod item; mod market; -use crate::souls::human::BasicWorker; use crate::utils::time::{Tick, TICKS_PER_SECOND}; +use crate::world::HumanID; pub use ecostats::*; pub use government::*; pub use item::*; @@ -37,6 +37,8 @@ const WORKER_CONSUMPTION_PER_SECOND: Money = Money::new_cents(1); #[repr(transparent)] pub struct Money(i64); +debug_inspect_impl!(Money); + impl Display for Money { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&(self.bucks()), f)?; @@ -153,14 +155,14 @@ impl Money { } } -#[derive(Default, Serialize, Deserialize)] +#[derive(Inspect, Default, Serialize, Deserialize)] pub struct Sold(pub Vec); -#[derive(Default, Serialize, Deserialize)] +#[derive(Inspect, Default, Serialize, Deserialize)] pub struct Bought(pub BTreeMap>); -#[derive(Debug, Default, Serialize, Deserialize, Inspect)] -pub struct Workers(pub Vec); +#[derive(Inspect, Debug, Default, Serialize, Deserialize)] +pub struct Workers(pub Vec); #[cfg(not(test))] const ITEMS_PATH: &str = "assets/items.json"; @@ -193,7 +195,7 @@ pub fn init_market(_: &mut World, res: &mut Resources) { #[profiling::function] pub fn market_update(world: &mut World, resources: &mut Resources) { - let n_workers = world.query::<&BasicWorker>().into_iter().len(); + let n_workers = world.humans.len(); let mut m = resources.get_mut::().unwrap(); let job_opening = resources.get::().unwrap().id("job-opening"); @@ -215,20 +217,18 @@ pub fn market_update(world: &mut World, resources: &mut Resources) { log::debug!("A trade was made! {:?}", trade); if trade.kind == job_opening { - // Jobs are guaranteed to not be external - world - .get::<&mut Workers>(trade.seller.soul().0) - .expect("employer has no component Workers") - .0 - .push(trade.buyer.soul()); + if let SoulID::GoodsCompany(id) = trade.seller.soul() { + let comp = world.companies.get_mut(id).unwrap(); + comp.workers.0.push(trade.buyer.soul().try_into().unwrap()) + } } gvt.money += trade.money_delta; match trade.seller { TradeTarget::Soul(id) => { if trade.kind != job_opening { - if let Ok(mut v) = world.get::<&mut Sold>(id.0) { - v.0.push(trade) + if let SoulID::GoodsCompany(id) = id { + world.companies.get_mut(id).unwrap().sold.0.push(trade); } } } @@ -236,11 +236,17 @@ pub fn market_update(world: &mut World, resources: &mut Resources) { } match trade.buyer { - TradeTarget::Soul(id) => { - if let Ok(mut v) = world.get::<&mut Bought>(id.0) { - v.0.entry(trade.kind).or_default().push(trade); + TradeTarget::Soul(SoulID::Human(id)) => { + if let Some(h) = world.humans.get_mut(id) { + h.bought.0.entry(trade.kind).or_default().push(trade); + } + } + TradeTarget::Soul(SoulID::GoodsCompany(id)) => { + if let Some(c) = world.companies.get_mut(id) { + c.bought.0.entry(trade.kind).or_default().push(trade) } } + TradeTarget::Soul(SoulID::FreightStation(_)) => {} TradeTarget::ExternalTrade => {} } } diff --git a/egregoria/src/engine_interaction.rs b/egregoria/src/engine_interaction.rs index 0d22f9f0..a124c171 100644 --- a/egregoria/src/engine_interaction.rs +++ b/egregoria/src/engine_interaction.rs @@ -8,30 +8,12 @@ use crate::map_dynamic::BuildingInfos; use crate::transportation::train::{spawn_train, RailWagonKind}; use crate::utils::time::{GameTime, Tick}; use crate::{Egregoria, EgregoriaOptions, Replay}; -use geom::{vec3, Transform, Vec2, OBB}; -use hecs::Entity; +use geom::{vec3, Vec2, OBB}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::time::Instant; use WorldCommand::*; -#[derive(Clone, Serialize, Deserialize)] -pub struct Selectable { - pub radius: f32, -} - -impl Selectable { - pub fn new(radius: f32) -> Self { - Self { radius } - } -} - -impl Default for Selectable { - fn default() -> Self { - Self { radius: 5.0 } - } -} - #[derive(Clone, Default)] pub struct WorldCommands { pub(crate) commands: Vec, @@ -85,7 +67,6 @@ pub enum WorldCommand { }, ResetSave, SetGameTime(GameTime), - UpdateTransform(Entity, Transform), } impl AsRef<[WorldCommand]> for WorldCommands { @@ -123,10 +104,6 @@ impl WorldCommands { self.commands.push(MapLoadTestField { pos, size, spacing }) } - pub fn update_transform(&mut self, e: Entity, trans: Transform) { - self.commands.push(UpdateTransform(e, trans)) - } - pub fn reset_save(&mut self) { self.commands.push(ResetSave) } @@ -212,7 +189,6 @@ impl WorldCommand { | MapUpdateIntersectionPolicy { .. } | UpdateZone { .. } | SetGameTime(_) - | UpdateTransform(_, _) ) } @@ -307,11 +283,6 @@ impl WorldCommand { let opts = *goria.read::(); *goria = Egregoria::new_with_options(opts); } - UpdateTransform(e, t) => { - if let Some(mut x) = goria.comp_mut(e) { - *x = t - } - } Init(ref opts) => { if opts.save_replay { let mut rep = goria.resources.get_mut::().unwrap(); diff --git a/egregoria/src/init.rs b/egregoria/src/init.rs index 2acf41b9..8ff7a49c 100644 --- a/egregoria/src/init.rs +++ b/egregoria/src/init.rs @@ -15,13 +15,14 @@ use crate::transportation::train::{ }; 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, CollisionWorld, Egregoria, EgregoriaOptions, GameTime, ParCommandBuffer, RandProvider, Replay, RunnableSystem, RNG_SEED, SECONDS_PER_DAY, SECONDS_PER_HOUR, }; use common::saveload::Encoder; -use hecs::World; use serde::de::DeserializeOwned; use serde::Serialize; @@ -45,7 +46,12 @@ pub fn init() { register_resource_noserialize::(); register_resource_noserialize::(); - register_resource_noserialize::(); + register_resource_noserialize::>(); + register_resource_noserialize::>(); + register_resource_noserialize::>(); + register_resource_noserialize::>(); + register_resource_noserialize::>(); + register_resource_noserialize::>(); register_resource_noinit::("market"); register_resource_noinit::("ecostats"); register_resource_noinit::("egregoriaoptions"); diff --git a/egregoria/src/lib.rs b/egregoria/src/lib.rs index db3123aa..7c35f53c 100644 --- a/egregoria/src/lib.rs +++ b/egregoria/src/lib.rs @@ -1,30 +1,22 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] -use crate::economy::{Bought, Sold, Workers}; -use crate::engine_interaction::{Selectable, WorldCommand}; +use crate::engine_interaction::WorldCommand; use crate::map::{BuildingKind, Map}; -use crate::map_dynamic::{ - DispatchKind, Itinerary, ItineraryFollower, ItineraryFollower2, ItineraryLeader, Router, -}; +use crate::map_dynamic::{Itinerary, ItineraryLeader}; use crate::physics::CollisionWorld; -use crate::physics::{Collider, Speed}; +use crate::physics::Speed; use crate::souls::add_souls_to_empty_buildings; -use crate::souls::desire::{BuyFood, Home, Work}; -use crate::souls::goods_company::{GoodsCompany, GoodsCompanyRegistry}; -use crate::souls::human::{BasicWorker, HumanDecision}; -use crate::transportation::train::{Locomotive, LocomotiveReservation}; -use crate::transportation::{Pedestrian, Vehicle}; +use crate::souls::goods_company::GoodsCompanyRegistry; use crate::utils::resources::{Ref, RefMut, Resources}; use common::saveload::Encoder; -use geom::{Transform, Vec3}; -use hecs::{Component, Entity, World}; +use derive_more::{From, TryInto}; +use geom::Vec3; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::any::Any; use std::collections::BTreeMap; use std::hash::Hash; use std::time::{Duration, Instant}; -use transportation::Location; use utils::rand_provider::RandProvider; use utils::scheduler::SeqSchedule; use utils::time::{GameTime, SECONDS_PER_DAY, SECONDS_PER_HOUR}; @@ -53,11 +45,12 @@ pub mod souls; mod tests; pub mod transportation; pub mod utils; +mod world; + +pub use world::*; use crate::engine_interaction::WorldCommand::Init; use crate::init::{GSYSTEMS, INIT_FUNCS, SAVELOAD_FUNCS}; -use crate::souls::freight_station::FreightStation; -use crate::transportation::train::RailWagon; use crate::utils::scheduler::RunnableSystem; use crate::utils::time::Tick; use common::FastMap; @@ -65,9 +58,37 @@ pub use utils::config::*; pub use utils::par_command_buffer::ParCommandBuffer; pub use utils::replay::*; -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] -#[repr(transparent)] -pub struct SoulID(pub Entity); +#[derive( + Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash, From, TryInto, +)] +pub enum SoulID { + Human(HumanID), + GoodsCompany(CompanyID), + FreightStation(FreightStationID), +} + +impl From for AnyEntity { + fn from(value: SoulID) -> Self { + match value { + SoulID::Human(id) => AnyEntity::HumanID(id), + SoulID::GoodsCompany(id) => AnyEntity::CompanyID(id), + SoulID::FreightStation(id) => AnyEntity::FreightStationID(id), + } + } +} + +impl TryFrom for SoulID { + type Error = (); + + fn try_from(value: AnyEntity) -> Result { + match value { + AnyEntity::HumanID(id) => Ok(SoulID::Human(id)), + AnyEntity::CompanyID(id) => Ok(SoulID::GoodsCompany(id)), + AnyEntity::FreightStationID(id) => Ok(SoulID::FreightStation(id)), + _ => Err(()), + } + } +} debug_inspect_impl!(SoulID); @@ -232,7 +253,7 @@ impl Egregoria { pub fn hashes(&self) -> BTreeMap { let mut hashes = BTreeMap::new(); - let ser = common::saveload::Bincode::encode(&SerWorld(&self.world)).unwrap(); + let ser = common::saveload::Bincode::encode(&self.world).unwrap(); hashes.insert("world".to_string(), common::hash_u64(&*ser)); unsafe { @@ -264,22 +285,20 @@ impl Egregoria { } } - pub fn pos(&self, e: Entity) -> Option { - self.comp::(e).map(|x| x.position) + pub fn pos(&self, id: E) -> Option { + self.world.pos(id) } - pub(crate) fn add_comp(&mut self, e: Entity, c: impl Component) { - if self.world.insert_one(e, c).is_err() { - log::error!("trying to add component to entity but it doesn't exist"); - } + pub fn pos_any(&self, id: AnyEntity) -> Option { + self.world.pos_any(id) } - pub fn comp(&self, e: Entity) -> Option> { - self.world.get::<&T>(e).ok() + pub fn get(&self, id: E) -> Option<&E::Entity> { + self.world.get(id) } - pub fn comp_mut(&mut self, e: Entity) -> Option> { - self.world.get::<&mut T>(e).ok() + pub fn contains(&self, id: AnyEntity) -> bool { + self.world.contains(id) } pub fn write_or_default(&mut self) -> RefMut { @@ -315,17 +334,6 @@ impl Egregoria { } } -struct SerWorld<'a>(&'a World); - -impl<'a> Serialize for SerWorld<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - hecs::serialize::column::serialize(self.0, &mut SerContext, serializer) - } -} - impl Serialize for Egregoria { fn serialize(&self, serializer: S) -> Result where @@ -345,7 +353,7 @@ impl Serialize for Egregoria { log::info!("took {}s to serialize resources", t.elapsed().as_secs_f32()); let v = EgregoriaSer { - world: SerWorld(&self.world), + world: &self.world, version: VERSION.to_string(), res: m, } @@ -357,14 +365,14 @@ impl Serialize for Egregoria { #[derive(Serialize)] struct EgregoriaSer<'a> { - world: SerWorld<'a>, + world: &'a World, version: String, res: FastMap>, } #[derive(Deserialize)] struct EgregoriaDeser { - world: DeserWorld, + world: World, version: String, res: FastMap>, } @@ -398,7 +406,7 @@ impl<'de> Deserialize<'de> for Egregoria { } let mut goria = Self { - world: World::new(), + world: World::default(), resources: Resources::default(), }; @@ -408,7 +416,7 @@ impl<'de> Deserialize<'de> for Egregoria { } } - goria.world = goriadeser.world.0; + goria.world = goriadeser.world; unsafe { for l in &SAVELOAD_FUNCS { @@ -427,153 +435,6 @@ impl<'de> Deserialize<'de> for Egregoria { } } -struct DeserWorld(World); - -impl<'de> Deserialize<'de> for DeserWorld { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - hecs::serialize::column::deserialize(&mut DeserContext::default(), deserializer) - .map(DeserWorld) - } -} - -struct SerContext; - -#[derive(Default)] -struct DeserContext { - components: Vec, -} - -macro_rules! register { - ($($t: ty => $p:ident),+,) => { - #[derive(Serialize, Deserialize)] - enum ComponentId { - $( - $p, - )+ - } - - impl hecs::serialize::column::SerializeContext for SerContext { - fn component_count(&self, archetype: &hecs::Archetype) -> usize { - archetype.component_types() - .filter(|&t| { - $( - t == std::any::TypeId::of::<$t>() || - )+ - false - }) - .count() - } - - fn serialize_component_ids( - &mut self, - archetype: &hecs::Archetype, - mut out: S, - ) -> Result { - let mut tot = 0; - $( - if archetype.has::<$t>() { - tot += 1; - } - hecs::serialize::column::try_serialize_id::<$t, _, _>(archetype, &ComponentId::$p, &mut out)?; - )+ - assert_eq!(tot, archetype.component_types().len()); - out.end() - } - - fn serialize_components( - &mut self, - archetype: &hecs::Archetype, - mut out: S, - ) -> Result { - $( - hecs::serialize::column::try_serialize::<$t, _>(archetype, &mut out)?; - )+ - out.end() - } - } - - impl hecs::serialize::column::DeserializeContext for DeserContext { - fn deserialize_component_ids<'de, A>( - &mut self, - mut seq: A, - ) -> Result - where - A: serde::de::SeqAccess<'de>, - { - self.components.clear(); // Discard data from the previous archetype - let mut batch = hecs::ColumnBatchType::new(); - while let Some(id) = seq.next_element()? { - match id { - $( - ComponentId::$p => { - batch.add::<$t>(); - }, - )+ - } - self.components.push(id); - } - Ok(batch) - } - - fn deserialize_components<'de, A>( - &mut self, - entity_count: u32, - mut seq: A, - batch: &mut hecs::ColumnBatchBuilder, - ) -> Result<(), A::Error> - where - A: serde::de::SeqAccess<'de>, - { - // Decode component data in the order that the component IDs appeared - for component in &self.components { - match *component { - $( - ComponentId::$p => { - hecs::serialize::column::deserialize_column::<$t, _>(entity_count, &mut seq, batch)?; - }, - )+ - } - } - Ok(()) - } - } - }; -} - -pub struct NoSerialize; - -register!( - Transform => _0, - Bought => _1, - BuyFood => _2, - Collider => _3, - GoodsCompany => _4, - Home => _5, - HumanDecision => _6, - Itinerary => _7, - Speed => _8, - Location => _9, - Pedestrian => _10, - Router => _11, - Selectable => _12, - Sold => _13, - Vehicle => _14, - Work => _15, - Workers => _16, - Locomotive => _17, - RailWagon => _18, - ItineraryLeader => _20, - ItineraryFollower => _21, - ItineraryFollower2 => _25, - LocomotiveReservation => _22, - FreightStation => _23, - DispatchKind => _24, - BasicWorker => _26, -); - const START_COMMANDS: &str = r#" [ [ diff --git a/egregoria/src/map_dynamic/dispatch.rs b/egregoria/src/map_dynamic/dispatch.rs index 8da2a0bc..f13380e7 100644 --- a/egregoria/src/map_dynamic/dispatch.rs +++ b/egregoria/src/map_dynamic/dispatch.rs @@ -1,12 +1,12 @@ use crate::map::{LaneID, LaneKind, TraverseDirection}; -use crate::utils::par_command_buffer::ComponentDrop; use crate::utils::resources::Resources; -use crate::Map; -use geom::{Transform, Vec3}; -use hecs::{Entity, QueryBorrow, World}; +use crate::world::{TrainID, VehicleID}; +use crate::{Map, World}; +use derive_more::From; +use geom::Vec3; use serde::{Deserialize, Serialize}; use std::collections::btree_map::Entry; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; /// How precise the dispatcher is. Caches dispatchable entities's positions and relation to map but only in precision circle. /// So if a dispatchable entity moves less than the precision, nothing will be updated. @@ -23,12 +23,27 @@ pub struct Dispatcher { dispatches: BTreeMap, } +#[derive(Debug, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize, From)] +pub enum DispatchID { + FreightTrain(TrainID), + SmallTruck(VehicleID), +} + +impl From for DispatchKind { + fn from(id: DispatchID) -> Self { + match id { + DispatchID::FreightTrain(_) => DispatchKind::FreightTrain, + DispatchID::SmallTruck(_) => DispatchKind::SmallTruck, + } + } +} + /// Dispatcher specialized to one kind #[derive(Serialize, Deserialize)] struct DispatchOne { - positions: BTreeMap, - lanes: BTreeMap>, - reserved_by: BTreeMap, + positions: BTreeMap, + lanes: BTreeMap>, + reserved_by: BTreeSet, lanekind: LaneKind, } @@ -66,58 +81,55 @@ pub enum DispatchQueryTarget { impl Dispatcher { /// Updates the dispatcher cache about the dispatachable entities to know where they are relative /// to the map, so that queries can be answered quickly - pub fn update( - &mut self, - map: &Map, - world: &World, - mut query: QueryBorrow<(&Transform, &DispatchKind)>, - ) { - let mut disp: &mut DispatchOne = self + pub fn update(&mut self, map: &Map, world: &World) { + let disp_trains = self .dispatches .entry(DispatchKind::FreightTrain) .or_insert_with(|| DispatchOne::new(DispatchKind::FreightTrain.lane_kind())); - let mut last_kind: DispatchKind = DispatchKind::FreightTrain; - for (ent, (trans, kind)) in query.iter() { - if last_kind != *kind { - disp = self - .dispatches - .entry(*kind) - .or_insert_with(|| DispatchOne::new(kind.lane_kind())); - last_kind = *kind; - } - if let Entry::Occupied(o) = disp.reserved_by.entry(ent) { - if !world.contains(*o.get()) { - o.remove(); - } else { - continue; - } - } - disp.register(ent, map, trans.position); - } + world.trains.iter().for_each(|(ent, train)| { + disp_trains.register(DispatchID::FreightTrain(ent), map, train.trans.position); + }); + + /* + let disp_trucks = self + .dispatches + .entry(DispatchKind::SmallTruck) + .or_insert_with(|| DispatchOne::new(DispatchKind::SmallTruck.lane_kind())); + + world.vehicles.iter().for_each(|(ent, truck)| { + disp_trucks.register(DispatchID::Truck(ent), map, truck.trans.position); + })*/ } /// Frees the entity as it is no longer used /// For example if a train is no longer used by a station, it should be freed so that other stations can use it /// It should be re-added to the cache at the next update iteration - pub fn free(&mut self, kind: DispatchKind, ent: Entity) { + pub fn free(&mut self, ent: impl Into) { + let ent: DispatchID = ent.into(); + let kind: DispatchKind = ent.into(); let Some(disp) = self.dispatches.get_mut(&kind) else { return }; disp.reserved_by.remove(&ent); } + pub fn unregister(&mut self, id: DispatchID) { + let kind = id.into(); + let Some(disp) = self.dispatches.get_mut(&kind) else { return }; + disp.unregister(id); + } + /// Reserves an entity that is closest to the target (if it is found) and returns it /// it takes `me` as an argument so that if `me` is killed, the reservation is cancelled /// If no entity is found, returns None pub fn query( &mut self, map: &Map, - me: Entity, kind: DispatchKind, target: DispatchQueryTarget, - ) -> Option { + ) -> Option { let disp = self.dispatches.get_mut(&kind)?; let best_ent = disp.query(map, kind, target)?; - disp.reserve(best_ent, me); + disp.reserve(best_ent); Some(best_ent) } } @@ -125,14 +137,14 @@ impl Dispatcher { impl DispatchOne { fn new(lanekind: LaneKind) -> Self { Self { - positions: BTreeMap::new(), - lanes: BTreeMap::new(), - reserved_by: BTreeMap::new(), + positions: Default::default(), + lanes: Default::default(), + reserved_by: Default::default(), lanekind, } } - fn register(&mut self, id: Entity, map: &Map, pos: Vec3) { + fn register(&mut self, id: DispatchID, map: &Map, pos: Vec3) { let ent = self.positions.entry(id); let lanekind = self.lanekind; @@ -182,8 +194,8 @@ impl DispatchOne { } } - fn reserve(&mut self, id: Entity, me: Entity) { - self.reserved_by.insert(id, me); + fn reserve(&mut self, id: DispatchID) { + self.reserved_by.insert(id); let Some(pos) = self.positions.remove(&id) else { log::error!("Dispatcher: trying to reserve an entity that is not in the cache"); return; @@ -191,9 +203,9 @@ impl DispatchOne { self.lanes.get_mut(&pos.lane).unwrap().retain(|e| *e != id); } - pub fn unregister(&mut self, id: Entity) { - self.reserved_by.remove(&id); + pub fn unregister(&mut self, id: DispatchID) { let Some(pos) = self.positions.remove(&id) else { return }; + self.reserved_by.remove(&id); self.lanes.get_mut(&pos.lane).unwrap().retain(|e| *e != id); } @@ -204,7 +216,7 @@ impl DispatchOne { map: &Map, kind: DispatchKind, target: DispatchQueryTarget, - ) -> Option { + ) -> Option { // todo: handle the case where there are few entities in the cache // todo: probably some kind of astar on good candidates @@ -268,15 +280,7 @@ impl DispatchOne { pub fn dispatch_system(world: &mut World, resources: &mut Resources) { let mut dispatcher = resources.get_mut::().unwrap(); let map = resources.get::().unwrap(); - dispatcher.update(&map, world, world.query()); -} - -impl ComponentDrop for DispatchKind { - fn drop(&mut self, goria: &mut Resources, ent: Entity) { - let Ok(mut dispatcher) = goria.get_mut::() else { return }; - let Some(one) = dispatcher.dispatches.get_mut(self) else { return }; - one.unregister(ent); - } + dispatcher.update(&map, world); } #[cfg(test)] @@ -284,6 +288,11 @@ mod tests { use super::*; use crate::map::{LanePatternBuilder, MapProject, ProjectKind}; use common::rand::rand2; + + fn mk_ent(id: u64) -> DispatchID { + DispatchID::FreightTrain(TrainID::from(KeyData::from_ffi(id))) + } + #[test] fn dispatch_one_register_one_works() { let mut disp = DispatchOne::new(LaneKind::Rail); @@ -301,7 +310,7 @@ mod tests { let lanes: Vec = map.roads[r].lanes_iter().map(|(id, _)| id).collect(); // first insert - let ent = Entity::from_bits(1 << 32).unwrap(); + let ent = mk_ent(1 << 32); disp.register(ent, &map, Vec3::new(0.0, 0.0, 0.0)); assert_eq!(disp.positions.len(), 1); assert_eq!(disp.lanes.len(), 1); @@ -309,14 +318,14 @@ mod tests { assert!(lanes.contains(disp.lanes.keys().next().unwrap())); // second insert in same lane - let ent2 = Entity::from_bits(1 << 32 + 1).unwrap(); + let ent2 = mk_ent(1 << 32 + 1); disp.register(ent2, &map, Vec3::new(0.0, 0.0, 0.0)); assert_eq!(disp.positions.len(), 2); assert_eq!(disp.lanes.len(), 1); assert_eq!(disp.lanes.values().next().unwrap(), &vec![ent, ent2]); // insert in another lane - let ent3 = Entity::from_bits(1 << 32 + 2).unwrap(); + let ent3 = mk_ent(1 << 32 + 2); disp.register(ent3, &map, Vec3::new(100.0, 10.0, 0.0)); assert_eq!(disp.positions.len(), 3); assert_eq!(disp.lanes.len(), 2); @@ -369,17 +378,16 @@ mod tests { let (lid, _) = map.roads[r].lanes_iter().next().unwrap(); - let mut register = |id: Entity, pos: f32| { + let mut register = |id: DispatchID, pos: f32| { d.dispatches .entry(DispatchKind::FreightTrain) .or_insert(DispatchOne::new(DispatchKind::FreightTrain.lane_kind())) .register(id, &map, Vec3::x(pos)) }; - let ent0 = Entity::from_bits(1 << 32).unwrap(); - let ent1 = Entity::from_bits((1 << 32) + 1).unwrap(); - let ent2 = Entity::from_bits((1 << 32) + 2).unwrap(); - let me = Entity::from_bits((1 << 32) + 3).unwrap(); + let ent0 = mk_ent(1 << 32); + let ent1 = mk_ent((1 << 32) + 1); + let ent2 = mk_ent((1 << 32) + 2); register(ent0, 0.0); register(ent1, 10.0); @@ -388,7 +396,6 @@ mod tests { assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(70.0)), ), @@ -396,12 +403,11 @@ mod tests { ); d.dispatches[&DispatchKind::FreightTrain] .reserved_by - .contains_key(&Entity::from_bits((1 << 32) + 1).unwrap()); + .contains(&mk_ent((1 << 32) + 1)); assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(50.0)), ), @@ -409,12 +415,11 @@ mod tests { ); d.dispatches[&DispatchKind::FreightTrain] .reserved_by - .contains_key(&Entity::from_bits(1 << 32).unwrap()); + .contains(&mk_ent(1 << 32)); assert!(d .query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(50.0)), ) @@ -423,7 +428,6 @@ mod tests { assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Lane(lid), ), @@ -468,17 +472,16 @@ mod tests { let (lid, _) = map.roads[r2].lanes_iter().next().unwrap(); - let mut register = |id: Entity, pos: f32| { + let mut register = |id: DispatchID, pos: f32| { d.dispatches .entry(DispatchKind::FreightTrain) .or_insert(DispatchOne::new(DispatchKind::FreightTrain.lane_kind())) .register(id, &map, Vec3::x(pos)) }; - let ent0 = Entity::from_bits(1 << 32).unwrap(); - let ent1 = Entity::from_bits((1 << 32) + 1).unwrap(); - let ent2 = Entity::from_bits((1 << 32) + 2).unwrap(); - let me = Entity::from_bits((1 << 32) + 3).unwrap(); + let ent0 = mk_ent(1 << 32); + let ent1 = mk_ent((1 << 32) + 1); + let ent2 = mk_ent((1 << 32) + 2); register(ent0, 0.0); register(ent1, 10.0); @@ -487,7 +490,6 @@ mod tests { assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(70.0)), ), @@ -495,12 +497,11 @@ mod tests { ); d.dispatches[&DispatchKind::FreightTrain] .reserved_by - .contains_key(&Entity::from_bits((1 << 32) + 1).unwrap()); + .contains(&mk_ent((1 << 32) + 1)); assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(50.0)), ), @@ -508,12 +509,11 @@ mod tests { ); d.dispatches[&DispatchKind::FreightTrain] .reserved_by - .contains_key(&Entity::from_bits(1 << 32).unwrap()); + .contains(&mk_ent(1 << 32)); assert!(d .query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(Vec3::x(50.0)), ) @@ -522,7 +522,6 @@ mod tests { assert_eq!( d.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Lane(lid), ), @@ -532,6 +531,7 @@ mod tests { use crate::map::procgen::load_parismap; use easybench::bench; + use slotmap::KeyData; #[test] fn bench_query() { @@ -575,7 +575,7 @@ mod tests { for i in 0..100 { start.register( - Entity::from_bits((1 << 32) + i).unwrap(), + mk_ent((1 << 32) + i), &m, Vec3::new( minx + w * rand2(i as f32, 2.0), @@ -604,7 +604,7 @@ mod tests { for i in 100..1000 { start.register( - Entity::from_bits((1 << 32) + i).unwrap(), + mk_ent((1 << 32) + i), &m, Vec3::new( minx + w * rand2(i as f32, 2.0), @@ -633,7 +633,7 @@ mod tests { for i in 1000..10000 { start.register( - Entity::from_bits((1 << 32) + i).unwrap(), + mk_ent((1 << 32) + i), &m, Vec3::new( minx + w * rand2(i as f32, 2.0), diff --git a/egregoria/src/map_dynamic/itinerary.rs b/egregoria/src/map_dynamic/itinerary.rs index b080c4a4..123943cc 100644 --- a/egregoria/src/map_dynamic/itinerary.rs +++ b/egregoria/src/map_dynamic/itinerary.rs @@ -1,23 +1,19 @@ use crate::map::{Map, PathKind, Pathfinder, Traversable, TraverseDirection, TraverseKind}; use crate::utils::resources::Resources; use crate::utils::time::{GameTime, Tick}; -use crate::Speed; +use crate::world::TrainID; +use crate::World; use egui_inspect::egui::Ui; use egui_inspect::{Inspect, InspectArgs}; use geom::{Follower, Polyline3Queue, Transform, Vec3}; -use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Inspect, Debug, Serialize, Deserialize)] pub struct ItineraryFollower { - pub leader: Entity, - pub follower: Follower, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ItineraryFollower2 { - pub leader: Entity, + pub leader: TrainID, + #[inspect(skip)] pub head: Follower, + #[inspect(skip)] pub tail: Follower, } @@ -392,50 +388,23 @@ pub fn itinerary_update(world: &mut World, resources: &mut Resources) { let time = &*resources.get::().unwrap(); let map = &*resources.get::().unwrap(); let tick = *resources.get::().unwrap(); - world - .query::<(&mut Transform, &Speed, &mut Itinerary)>() - .iter_batched(32) - //.par_bridge() - .for_each(|chunk| { - chunk.for_each(|(_, (trans, kin, it))| { - trans.position = it.update_rail( - trans.position, - kin.speed * time.delta, - tick, - time.seconds, - map, - ); - }) - }); - world - .query::<(&Transform, &mut ItineraryLeader)>() - .iter_batched(32) - .for_each(|chunk| { - chunk.for_each(|(_, (trans, leader))| { - leader.past.push(trans.position); - }) - }); - world - .query::<(&mut Transform, &mut ItineraryFollower)>() - .iter_batched(32) - .for_each(|chunk| { - chunk.for_each(|(_, (trans, follow))| { - let leader = unwrap_orr!(world.get::<&ItineraryLeader>(follow.leader), return); - let (pos, dir) = follow.follower.update(&leader.past); - trans.position = pos; - trans.dir = dir; - }) - }); - world - .query::<(&mut Transform, &mut ItineraryFollower2)>() - .iter_batched(32) - .for_each(|chunk| { - chunk.for_each(|(_, (trans, follow))| { - let leader = unwrap_orr!(world.get::<&ItineraryLeader>(follow.leader), return); - let (pos, dir) = follow.head.update(&leader.past); - let (pos2, dir2) = follow.tail.update(&leader.past); - trans.position = (pos + pos2) * 0.5; - trans.dir = (dir + dir2).try_normalize().unwrap_or(dir); - }) - }); + + world.query_it_trans_speed().for_each( + |(it, trans, speed): (&mut Itinerary, &mut Transform, f32)| { + trans.position = + it.update_rail(trans.position, speed * time.delta, tick, time.seconds, map); + }, + ); + + world.trains.values_mut().for_each(|train| { + train.leader.past.push(train.trans.position); + }); + + world.wagons.values_mut().for_each(|wagon| { + let leader = &unwrap_ret!(world.trains.get(wagon.itfollower.leader)).leader; + let (pos, dir) = wagon.itfollower.head.update(&leader.past); + let (pos2, dir2) = wagon.itfollower.tail.update(&leader.past); + wagon.trans.position = (pos + pos2) * 0.5; + wagon.trans.dir = (dir + dir2).try_normalize().unwrap_or(dir); + }); } diff --git a/egregoria/src/map_dynamic/router.rs b/egregoria/src/map_dynamic/router.rs index 8a2d19b3..a90c6816 100644 --- a/egregoria/src/map_dynamic/router.rs +++ b/egregoria/src/map_dynamic/router.rs @@ -1,16 +1,14 @@ use crate::map::{BuildingID, Map, PathKind}; use crate::map_dynamic::{Itinerary, ParkingManagement, ParkingReserveError, SpotReservation}; -use crate::physics::{Collider, CollisionWorld, Speed}; -use crate::transportation::{ - put_pedestrian_in_coworld, unpark, Location, Vehicle, VehicleID, VehicleState, -}; -use crate::utils::par_command_buffer::ComponentDrop; +use crate::physics::CollisionWorld; +use crate::transportation::{put_pedestrian_in_coworld, unpark, Location, VehicleState}; use crate::utils::resources::Resources; -use crate::{Egregoria, ParCommandBuffer}; +use crate::world::{HumanEnt, HumanID, VehicleEnt, VehicleID}; +use crate::{ParCommandBuffer, World}; use egui_inspect::Inspect; use geom::{Spline3, Transform, Vec3}; -use hecs::{Component, Entity, Ref, World}; use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; #[derive(Inspect, Serialize, Deserialize)] pub struct Router { @@ -56,30 +54,21 @@ debug_inspect_impl!(RoutingStep); #[profiling::function] pub fn routing_changed_system(world: &mut World, resources: &mut Resources) { - let ra = &*resources.get().unwrap(); - let rb = &mut *resources.get_mut().unwrap(); - world - .query::<(&mut Router, &Location)>() - .iter() - .for_each(|(_, (a, b))| { - routing_changed(ra, rb, a, b, world); - }); -} - -pub fn routing_changed( - map: &Map, - parking: &mut ParkingManagement, - router: &mut Router, - loc: &Location, - world: &World, -) { - if router.cur_dest != router.target_dest { + let map: &Map = &resources.get().unwrap(); + let parking: &mut ParkingManagement = &mut resources.get_mut().unwrap(); + + world.humans.values_mut().for_each(|h| { + let router = &mut h.router; + let loc = &h.location; + if router.cur_dest == router.target_dest { + return; + } let dest = unwrap_ret!(router.target_dest); router.clear_steps(parking); match dest { Destination::Outside(pos) => { - router.steps = match router.steps_to(pos, parking, map, loc, world) { + router.steps = match router.steps_to(pos, parking, map, loc, &world.vehicles) { Ok(x) => x, Err(e) => { router.last_error = Some(e); @@ -103,7 +92,7 @@ pub fn routing_changed( } }; let door_pos = bobj.door_pos; - router.steps = match router.steps_to(door_pos, parking, map, loc, world) { + router.steps = match router.steps_to(door_pos, parking, map, loc, &world.vehicles) { Ok(x) => x, Err(e) => { router.last_error = Some(e); @@ -117,206 +106,190 @@ pub fn routing_changed( router.cur_dest = router.target_dest; router.steps.reverse(); - } + }); } #[profiling::function] pub fn routing_update_system(world: &mut World, resources: &mut Resources) { - let ra = &*resources.get().unwrap(); - let rb = &*resources.get().unwrap(); - world - .query::<( - &Transform, - &Itinerary, - &mut Router, - &mut Location, - &mut Speed, - )>() - .iter_batched(32) - //.par_bridge() - .for_each(|batch| { - batch.for_each(|(e, (a, b, c, d, f))| routing_update(ra, rb, e, a, b, c, d, f, world)) - }); -} - -pub fn routing_update( - map: &Map, - cbuf: &ParCommandBuffer, - body: Entity, - trans: &Transform, - itin: &Itinerary, - router: &mut Router, - loc: &mut Location, - kin: &mut Speed, - world: &World, -) { - if router.cur_step.is_none() && router.steps.is_empty() { - return; - } + let map: &Map = &resources.get().unwrap(); + let cbuf_human: &ParCommandBuffer = &resources.get().unwrap(); + let cbuf_vehicle: &ParCommandBuffer = &resources.get().unwrap(); - let pos = match *loc { - Location::Outside => trans.position, - Location::Vehicle(id) => comp::(world, id.0) - .map(|x| x.position) - .unwrap_or_else(|| trans.position), - Location::Building(id) => map - .buildings() - .get(id) - .map(|b| b.door_pos) - .unwrap_or_else(|| trans.position), - }; + world.humans.iter_mut().for_each(|(body, h)| { + if h.router.cur_step.is_none() && h.router.steps.is_empty() { + return; + } - let mut cur_step_over = true; - - if let Some(ref step) = router.cur_step { - cur_step_over = match *step { - RoutingStep::WalkTo(_) => itin.has_ended(0.0), - RoutingStep::DriveTo(vehicle, _) => comp::(world, vehicle.0) - .map(|x| x.has_ended(0.0)) - .unwrap_or(true), - RoutingStep::Park(vehicle, _) => comp::(world, vehicle.0) - .map(|x| matches!(x.state, VehicleState::Parked(_))) - .unwrap_or(true), - RoutingStep::Unpark(_) => true, - RoutingStep::GetInVehicle(_) => true, - RoutingStep::GetOutVehicle(_) => true, - RoutingStep::GetInBuilding(_) => true, - RoutingStep::GetOutBuilding(_) => true, - }; - } - let mut next_step_ready = true; - - if let Some(step) = router.steps.last() { - next_step_ready = match *step { - RoutingStep::WalkTo(_) => true, - RoutingStep::DriveTo(_, _) => true, - RoutingStep::Park(_, _) => true, - RoutingStep::Unpark(_) => true, - RoutingStep::GetInVehicle(vehicle) => comp::(world, vehicle.0) - .map(|x| x.position.is_close(pos, 3.0)) - .unwrap_or(true), - RoutingStep::GetOutVehicle(_) => true, - RoutingStep::GetInBuilding(build) => map + let trans: &Transform = &h.trans; + let itin: &Itinerary = &h.it; + + let pos = match h.location { + Location::Outside => trans.position, + Location::Vehicle(id) => world + .vehicles + .get(id) + .map(|x| x.trans.position) + .unwrap_or_else(|| trans.position), + Location::Building(id) => map .buildings() - .get(build) - .map(|b| b.door_pos.is_close(pos, 3.0)) - .unwrap_or(true), - RoutingStep::GetOutBuilding(_) => true, + .get(id) + .map(|b| b.door_pos) + .unwrap_or_else(|| trans.position), }; - } - if !(next_step_ready && cur_step_over) { - return; - } + let mut cur_step_over = true; + + if let Some(ref step) = h.router.cur_step { + cur_step_over = match *step { + RoutingStep::WalkTo(_) => itin.has_ended(0.0), + RoutingStep::DriveTo(vehicle, _) => world + .vehicles + .get(vehicle) + .map(|x| &x.it) + .map(|x| x.has_ended(0.0)) + .unwrap_or(true), + RoutingStep::Park(vehicle, _) => world + .vehicles + .get(vehicle) + .map(|x| &x.vehicle) + .map(|x| matches!(x.state, VehicleState::Parked(_))) + .unwrap_or(true), + RoutingStep::Unpark(_) => true, + RoutingStep::GetInVehicle(_) => true, + RoutingStep::GetOutVehicle(_) => true, + RoutingStep::GetInBuilding(_) => true, + RoutingStep::GetOutBuilding(_) => true, + }; + } + let mut next_step_ready = true; + + if let Some(step) = h.router.steps.last() { + next_step_ready = match *step { + RoutingStep::WalkTo(_) => true, + RoutingStep::DriveTo(_, _) => true, + RoutingStep::Park(_, _) => true, + RoutingStep::Unpark(_) => true, + RoutingStep::GetInVehicle(vehicle) => world + .vehicles + .get(vehicle) + .map(|v| v.trans.position.is_close(pos, 3.0)) + .unwrap_or(true), + RoutingStep::GetOutVehicle(_) => true, + RoutingStep::GetInBuilding(build) => map + .buildings() + .get(build) + .map(|b| b.door_pos.is_close(pos, 3.0)) + .unwrap_or(true), + RoutingStep::GetOutBuilding(_) => true, + }; + } - router.cur_step = router.steps.pop(); + if !(next_step_ready && cur_step_over) { + return; + } - if let Some(ref mut next_step) = router.cur_step { - match *next_step { - RoutingStep::WalkTo(obj) => { - cbuf.add_component(body, Itinerary::wait_for_reroute(PathKind::Pedestrian, obj)); - } - RoutingStep::DriveTo(vehicle, obj) => { - let route = Itinerary::wait_for_reroute(PathKind::Vehicle, obj); - cbuf.add_component(vehicle.0, route); - } - RoutingStep::Park(vehicle, ref mut spot) => { - if let Some(x) = spot.take() { - if !x.exists(&map.parking) { - router.reset_dest(); + h.router.cur_step = h.router.steps.pop(); + + if let Some(ref mut next_step) = h.router.cur_step { + match *next_step { + RoutingStep::WalkTo(obj) => { + h.it = Itinerary::wait_for_reroute(PathKind::Pedestrian, obj); + } + RoutingStep::DriveTo(vehicle, obj) => { + let route = Itinerary::wait_for_reroute(PathKind::Vehicle, obj); + if let Some(x) = world.vehicles.get_mut(vehicle) { + x.it = route + } + } + RoutingStep::Park(vehicle, ref mut spot) => { + if let Some(spot_resa) = spot.take() { + if !spot_resa.exists(&map.parking) { + h.router.reset_dest(); + return; + } + + if let Some(vehicle) = world.vehicles.get_mut(vehicle) { + park(map, vehicle, spot_resa) + } + } + } + RoutingStep::Unpark(vehicle) => { + cbuf_vehicle.exec_ent(vehicle, move |goria| unpark(goria, vehicle)); + } + RoutingStep::GetInVehicle(vehicle) => { + if !world.vehicles.contains_key(vehicle) { + h.router.reset_dest(); return; } - - cbuf.exec_ent(vehicle.0, park(vehicle, x)); + h.location = Location::Vehicle(vehicle); + walk_inside(body, h, cbuf_human); } - } - RoutingStep::Unpark(vehicle) => { - cbuf.exec_ent(vehicle.0, move |goria| unpark(goria, vehicle)); - } - RoutingStep::GetInVehicle(vehicle) => { - if !world.contains(vehicle.0) { - router.reset_dest(); - return; + RoutingStep::GetOutVehicle(vehicle) => { + let pos = world + .vehicles + .get(vehicle) + .map(|v| v.trans) + .map(|vtrans| vtrans.position + vtrans.dir.cross(Vec3::Z) * 2.0) + .unwrap_or(pos); + walk_outside(body, pos, cbuf_human, &mut h.location); } - *loc = Location::Vehicle(vehicle); - walk_inside(body, cbuf, kin); - } - RoutingStep::GetOutVehicle(vehicle) => { - let pos = comp::(world, vehicle.0) - .map(|vtrans| vtrans.position + vtrans.dir.cross(Vec3::Z) * 2.0) - .unwrap_or(pos); - walk_outside(body, pos, cbuf, loc); - } - RoutingStep::GetInBuilding(build) => { - if !map.buildings().contains_key(build) { - router.reset_dest(); - return; + RoutingStep::GetInBuilding(build) => { + if !map.buildings().contains_key(build) { + h.router.reset_dest(); + return; + } + h.location = Location::Building(build); + walk_inside(body, h, cbuf_human); + } + RoutingStep::GetOutBuilding(build) => { + let wpos = map + .buildings() + .get(build) + .map(|x| x.door_pos) + .unwrap_or(pos); + walk_outside(body, wpos, cbuf_human, &mut h.location); } - *loc = Location::Building(build); - walk_inside(body, cbuf, kin); - } - RoutingStep::GetOutBuilding(build) => { - let wpos = map - .buildings() - .get(build) - .map(|x| x.door_pos) - .unwrap_or(pos); - walk_outside(body, wpos, cbuf, loc); } } - } + }) } -impl ComponentDrop for Router { - fn drop(&mut self, res: &mut Resources, _: Entity) { - self.clear_steps(&mut res.get_mut::().unwrap()) +fn walk_inside(body: HumanID, h: &mut HumanEnt, cbuf: &ParCommandBuffer) { + if let Some(coll) = h.collider.take() { + cbuf.exec_ent(body, coll.destroy()); } + h.speed.0 = 0.0; } -fn comp(sw: &World, e: Entity) -> Option> { - sw.get::<&T>(e).ok() -} - -fn walk_inside(body: Entity, cbuf: &ParCommandBuffer, kin: &mut Speed) { - cbuf.remove_component_drop::(body); - kin.speed = 0.0; - cbuf.add_component(body, Itinerary::NONE) -} - -fn walk_outside(body: Entity, pos: Vec3, cbuf: &ParCommandBuffer, loc: &mut Location) { +fn walk_outside(body: HumanID, pos: Vec3, cbuf: &ParCommandBuffer, loc: &mut Location) { *loc = Location::Outside; cbuf.exec_ent(body, move |goria| { - unwrap_ret!(goria.comp_mut::(body)).position = pos; let coll = put_pedestrian_in_coworld(&mut goria.write::(), pos); - goria.add_comp(body, coll); + let h = unwrap_ret!(goria.world.humans.get_mut(body)); + h.trans.position = pos; + h.collider = Some(coll); }); } -fn park(vehicle: VehicleID, spot_resa: SpotReservation) -> impl FnOnce(&mut Egregoria) { - move |goria| { - let trans = unwrap_ret!(goria.comp::(vehicle.0)); - let map = goria.map(); - let spot = match spot_resa.get(&map.parking) { - Some(x) => x, - None => { - log::warn!("Couldn't park at {:?} because it doesn't exist", spot_resa); - return; - } - }; +fn park(map: &Map, vehicle: &mut VehicleEnt, spot_resa: SpotReservation) { + let trans = vehicle.trans; + let spot = match spot_resa.get(&map.parking) { + Some(x) => x, + None => { + log::warn!("Couldn't park at {:?} because it doesn't exist", spot_resa); + return; + } + }; - let s = Spline3 { - from: trans.position, - to: spot.trans.position, - from_derivative: trans.dir * 2.0, - to_derivative: spot.trans.dir * 2.0, - }; - drop(map); - drop(trans); + let s = Spline3 { + from: trans.position, + to: spot.trans.position, + from_derivative: trans.dir * 2.0, + to_derivative: spot.trans.dir * 2.0, + }; - unwrap_ret!(goria.comp_mut::(vehicle.0)).state = - VehicleState::RoadToPark(s, 0.0, spot_resa); - unwrap_ret!(goria.comp_mut::(vehicle.0)).speed = 0.0; - } + vehicle.vehicle.state = VehicleState::RoadToPark(s, 0.0, spot_resa); + vehicle.speed.0 = 0.0; } impl Router { @@ -336,7 +309,7 @@ impl Router { self.vehicle = v; } - fn clear_steps(&mut self, parking: &mut ParkingManagement) { + pub(crate) fn clear_steps(&mut self, parking: &mut ParkingManagement) { for s in self.steps.drain(..).chain(self.cur_step.take()) { if let RoutingStep::Park(_, Some(spot)) = s { parking.free(spot); @@ -365,7 +338,7 @@ impl Router { parking: &mut ParkingManagement, map: &Map, loc: &Location, - world: &World, + cars: &SlotMap, ) -> Result, RouterError> { let mut steps = vec![]; if let Location::Building(cur_build) = loc { @@ -385,8 +358,8 @@ impl Router { }; if !matches!(loc, Location::Vehicle(_)) { - if let Some(trans) = comp::(world, car.0) { - steps.push(RoutingStep::WalkTo(trans.position)); + if let Some(pos) = cars.get(car).map(|x| x.trans.position) { + steps.push(RoutingStep::WalkTo(pos)); steps.push(RoutingStep::GetInVehicle(car)); steps.push(RoutingStep::Unpark(car)); } else { diff --git a/egregoria/src/physics/mod.rs b/egregoria/src/physics/mod.rs index 5f9af56e..bd67f326 100644 --- a/egregoria/src/physics/mod.rs +++ b/egregoria/src/physics/mod.rs @@ -1,19 +1,26 @@ use crate::transportation::Vehicle; -use crate::utils::par_command_buffer::ComponentDrop; use crate::utils::resources::Resources; +use crate::{Egregoria, World}; use egui_inspect::Inspect; use egui_inspect::InspectVec2Rotation; use flat_spatial::grid::GridHandle; use geom::Transform; use geom::Vec2; -use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Formatter}; -#[derive(Clone, Default, Debug, Serialize, Deserialize, Inspect)] -pub struct Speed { - pub speed: f32, +#[derive(Clone, Default, Serialize, Deserialize)] +#[repr(transparent)] +pub struct Speed(pub f32); + +impl Debug for Speed { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.1}m/s", self.0) + } } +debug_inspect_impl!(Speed); + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum PhysicsGroup { Unknown, @@ -54,29 +61,31 @@ pub struct Collider(pub GridHandle); debug_inspect_impl!(Collider); +impl Collider { + pub fn destroy(self) -> impl FnOnce(&mut Egregoria) { + move |goria| { + let cw = &mut goria.write::(); + cw.remove_maintain(self.0); + } + } +} + #[profiling::function] pub fn coworld_synchronize(world: &mut World, resources: &mut Resources) { let mut coworld = resources.get_mut::().unwrap(); - world - .query_mut::<(&Transform, &Speed, &Collider, Option<&Vehicle>)>() - .into_iter() - .for_each(|(_, (trans, kin, coll, v))| { + + world.query_trans_speed_coll_vehicle().for_each( + |(trans, kin, coll, v): (&Transform, &Speed, Collider, Option<&Vehicle>)| { coworld.set_position(coll.0, trans.position.xy()); let (_, po) = coworld.get_mut(coll.0).unwrap(); // Unwrap ok: handle is deleted only when entity is deleted too po.dir = trans.dir.xy(); - po.speed = kin.speed; + po.speed = kin.0; po.height = trans.position.z; if let Some(v) = v { po.flag = v.flag; } - }); - coworld.maintain(); -} + }, + ); -impl ComponentDrop for Collider { - fn drop(&mut self, res: &mut Resources, _: Entity) { - res.get_mut::() - .unwrap() - .remove_maintain(self.0); - } + coworld.maintain(); } diff --git a/egregoria/src/souls/desire/buyfood.rs b/egregoria/src/souls/desire/buyfood.rs index 3e513827..52d331c4 100644 --- a/egregoria/src/souls/desire/buyfood.rs +++ b/egregoria/src/souls/desire/buyfood.rs @@ -4,6 +4,7 @@ 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::{Map, ParCommandBuffer, SoulID}; use egui_inspect::Inspect; use geom::Transform; @@ -54,11 +55,11 @@ impl BuyFood { pub fn apply( &mut self, - cbuf: &ParCommandBuffer, + cbuf: &ParCommandBuffer, binfos: &BuildingInfos, map: &Map, time: &GameTime, - soul: SoulID, + id: HumanID, trans: &Transform, loc: &Location, bought: &mut Bought, @@ -68,8 +69,8 @@ impl BuyFood { BuyFoodState::Empty => { let pos = trans.position; let bread = self.bread; - cbuf.exec_on(soul.0, move |market: &mut Market| { - market.buy(soul, pos.xy(), bread, 1) + cbuf.exec_on(id, move |market: &mut Market| { + market.buy(SoulID::Human(id), pos.xy(), bread, 1) }); self.state = BuyFoodState::WaitingForTrade; Yield @@ -88,7 +89,7 @@ impl BuyFood { if loc == &Location::Building(b) { self.state = BuyFoodState::Empty; self.last_ate = time.instant(); - log::debug!("{:?} ate at {:?}", soul, b); + log::debug!("{:?} ate at {:?}", id, b); Yield } else { GoTo(Destination::Building(b)) diff --git a/egregoria/src/souls/desire/work.rs b/egregoria/src/souls/desire/work.rs index 0a8118f6..8a94663e 100644 --- a/egregoria/src/souls/desire/work.rs +++ b/egregoria/src/souls/desire/work.rs @@ -2,8 +2,8 @@ use crate::map::BuildingID; use crate::map_dynamic::{Destination, Router}; use crate::souls::human::HumanDecisionKind; use crate::transportation::Location; -use crate::transportation::VehicleID; use crate::utils::time::{GameTime, RecTimeInterval, SECONDS_PER_HOUR}; +use crate::world::VehicleID; use egui_inspect::Inspect; use serde::{Deserialize, Serialize}; diff --git a/egregoria/src/souls/freight_station.rs b/egregoria/src/souls/freight_station.rs index db56cf07..ba9e83c5 100644 --- a/egregoria/src/souls/freight_station.rs +++ b/egregoria/src/souls/freight_station.rs @@ -1,11 +1,13 @@ use crate::map::{BuildingID, BuildingKind, Map, PathKind}; -use crate::map_dynamic::{BuildingInfos, DispatchKind, DispatchQueryTarget, Dispatcher, Itinerary}; -use crate::transportation::train::TrainID; +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::{Egregoria, ParCommandBuffer, SoulID}; use geom::Transform; -use hecs::World; use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Serialize, Deserialize, Inspect)] @@ -31,7 +33,10 @@ pub struct FreightStation { pub wanted_cargo: u32, } -pub fn freight_station_soul(goria: &mut Egregoria, building: BuildingID) -> Option { +pub fn freight_station_soul( + goria: &mut Egregoria, + building: BuildingID, +) -> Option { let map = goria.map(); let f = FreightStation { @@ -49,48 +54,50 @@ pub fn freight_station_soul(goria: &mut Egregoria, building: BuildingID) -> Opti drop(map); - let soul = SoulID(goria.world.spawn(( + let id = goria.world.insert(FreightStationEnt { f, - Transform::new_dir(pos.z(height), axis[1].z(0.0).normalize()), - ))); + trans: Transform::new_dir(pos.z(height), axis[1].z(0.0).normalize()), + }); - goria.write::().set_owner(building, soul); + goria + .write::() + .set_owner(building, SoulID::FreightStation(id)); - Some(soul) + Some(id) } pub fn freight_station_system(world: &mut World, resources: &mut Resources) { - let cbuf = resources.get::().unwrap(); + let cbuf = resources + .get::>() + .unwrap(); let mut dispatch = resources.get_mut::().unwrap(); let map = resources.get::().unwrap(); let time = resources.get::().unwrap(); let tick = *resources.get::().unwrap(); - let mut trainqry = world.query::<(&Transform, &mut Itinerary)>(); - let mut train = trainqry.view(); - - for (me, (pos, soul)) in world - .query::<(&Transform, &mut FreightStation)>() - .into_iter() - { - if !map.buildings.contains_key(soul.building) { + for (me, f) in world.freight_stations.iter_mut() { + let pos = f.trans; + let station = &mut f.f; + if !map.buildings.contains_key(station.building) { cbuf.kill(me); continue; } // update our trains, and remove the ones that are done let mut to_clean = vec![]; - for (trainid, state) in &mut soul.trains { - let Some((tpos, itin)) = train.get_mut(trainid.0) else { + for (trainid, state) in &mut station.trains { + let Some(train) = world.trains.get_mut(*trainid) else { to_clean.push(*trainid); continue }; + let itin = &mut train.it; + match state { FreightTrainState::Arriving => { if itin.has_ended(0.0) { *state = FreightTrainState::Loading; - soul.waiting_cargo = soul.waiting_cargo.saturating_sub(100); - soul.wanted_cargo = soul.wanted_cargo.saturating_sub(100); + station.waiting_cargo = station.waiting_cargo.saturating_sub(100); + station.wanted_cargo = station.wanted_cargo.saturating_sub(100); *itin = Itinerary::wait_until(time.timestamp + 10.0); } } @@ -100,7 +107,7 @@ pub fn freight_station_system(world: &mut World, resources: &mut Resources) { let bpos = map.buildings[ext].obb.center().z(0.0); *itin = if let Some(r) = - Itinerary::route(tick, tpos.position, bpos, &map, PathKind::Rail) + Itinerary::route(tick, train.trans.position, bpos, &map, PathKind::Rail) { r } else { @@ -118,38 +125,42 @@ pub fn freight_station_system(world: &mut World, resources: &mut Resources) { } } for v in to_clean { - soul.trains.retain(|x| x.0 != v); - dispatch.free(DispatchKind::FreightTrain, v.0) + station.trains.retain(|x| x.0 != v); + dispatch.free(v) } // If enough goods are waiting, query for a train to take them to the external trading station - if soul.trains.len() >= MAX_TRAINS_PER_STATION { + if station.trains.len() >= MAX_TRAINS_PER_STATION { continue; } - if soul.waiting_cargo + soul.wanted_cargo < 10 { + if station.waiting_cargo + station.wanted_cargo < 10 { continue; } let destination = pos.position + pos.dir * 75.0 - pos.dir.perp_up() * 40.0; - let Some(trainid) = dispatch.query( + let Some(DispatchID::FreightTrain(trainid)) = dispatch.query( &map, - me, DispatchKind::FreightTrain, DispatchQueryTarget::Pos(destination), ) else { continue; }; - let trainid = TrainID(trainid); - let (tpos, titin) = train.get_mut(trainid.0).unwrap(); + let train = world.trains.get_mut(trainid).unwrap(); - *titin = unwrap_or!( - Itinerary::route(tick, tpos.position, destination, &map, PathKind::Rail,), + train.it = unwrap_or!( + Itinerary::route( + tick, + train.trans.position, + destination, + &map, + PathKind::Rail, + ), continue ); - soul.trains.push((trainid, FreightTrainState::Arriving)); + station.trains.push((trainid, FreightTrainState::Arriving)); } } @@ -159,7 +170,7 @@ mod tests { use crate::map_dynamic::BuildingInfos; use crate::souls::human::{spawn_human, HumanDecisionKind}; use crate::tests::TestCtx; - use crate::{BuildingKind, FreightStation, HumanDecision, WorldCommand}; + use crate::{BuildingKind, SoulID, WorldCommand}; use geom::{vec2, vec3, OBB}; #[test] @@ -189,23 +200,22 @@ mod tests { .unwrap() .0; - test.g.comp_mut::(human.0).unwrap().kind = - HumanDecisionKind::DeliverAtBuilding(station); + test.g + .world_mut_unchecked() + .humans + .get_mut(human) + .unwrap() + .decision + .kind = HumanDecisionKind::DeliverAtBuilding(station); let binfos = test.g.read::(); - let stationsoul = binfos.owner(station).unwrap(); + let SoulID::FreightStation(stationsoul) = binfos.owner(station).unwrap() else { panic!() }; drop(binfos); for _ in 0..100 { test.tick(); - if test - .g - .comp::(stationsoul.0) - .unwrap() - .waiting_cargo - == 1 - { + if test.g.get(stationsoul).unwrap().f.waiting_cargo == 1 { return; } } diff --git a/egregoria/src/souls/goods_company.rs b/egregoria/src/souls/goods_company.rs index e3d46ef7..eb6c7027 100644 --- a/egregoria/src/souls/goods_company.rs +++ b/egregoria/src/souls/goods_company.rs @@ -1,17 +1,16 @@ use super::desire::Work; -use crate::economy::{find_trade_place, Bought, ItemID, ItemRegistry, Market, Sold, Workers}; +use crate::economy::{find_trade_place, ItemID, ItemRegistry, Market}; use crate::map::{Building, BuildingGen, BuildingID, Map, Zone, MAX_ZONE_AREA}; use crate::map_dynamic::BuildingInfos; use crate::souls::desire::WorkKind; -use crate::souls::freight_station::FreightStation; -use crate::transportation::VehicleID; use crate::utils::resources::Resources; use crate::utils::time::GameTime; +use crate::world::{CompanyEnt, HumanEnt, HumanID, VehicleID}; +use crate::World; use crate::{Egregoria, ParCommandBuffer, SoulID}; use common::saveload::Encoder; use egui_inspect::Inspect; use geom::{Transform, Vec2}; -use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; use slotmap::{new_key_type, SlotMap}; @@ -238,7 +237,7 @@ pub struct GoodsCompany { pub max_workers: i32, /// In [0; 1] range, to show how much has been made until new product pub progress: f32, - pub driver: Option, + pub driver: Option, pub trucks: Vec, } @@ -256,9 +255,17 @@ pub fn company_soul(goria: &mut Egregoria, company: GoodsCompany) -> Option().id("job-opening"); @@ -274,141 +281,122 @@ pub fn company_soul(goria: &mut Egregoria, company: GoodsCompany) -> Option() .set_owner(company.building, soul); - goria - .world - .insert( - e, - ( - company, - Workers::default(), - Sold::default(), - Bought::default(), - Transform::new(obb.center().z(height)), - ), - ) - .unwrap(); - Some(soul) } #[profiling::function] pub fn company_system(world: &mut World, res: &mut Resources) { let delta = res.get::().unwrap().delta; - let rb = res.get().unwrap(); - let rc = res.get().unwrap(); - let rd = res.get().unwrap(); - let re = res.get().unwrap(); - for (ent, (a, b, c, d)) in world - .query::<(&mut GoodsCompany, &mut Sold, &mut Bought, &Workers)>() - .iter() - { - company(delta, &rb, &rc, &rd, &re, ent, a, b, c, d, world); - } -} - -pub fn company( - delta: f32, - cbuf: &ParCommandBuffer, - binfos: &BuildingInfos, - market: &Market, - map: &Map, - me: Entity, - company: &mut GoodsCompany, - sold: &mut Sold, - bought: &mut Bought, - workers: &Workers, - world: &World, -) { - let n_workers = workers.0.len(); - let soul = SoulID(me); - let b: &Building = unwrap_or!(map.buildings.get(company.building), { - cbuf.kill(me); - return; - }); + let cbuf: &ParCommandBuffer = &res.get().unwrap(); + let cbuf_human: &ParCommandBuffer = &res.get().unwrap(); + let binfos: &BuildingInfos = &res.get().unwrap(); + let market: &Market = &res.get().unwrap(); + let map: &Map = &res.get().unwrap(); + + world.companies.iter_mut().for_each(|(me, c)| { + let n_workers = c.workers.0.len(); + let soul = SoulID::GoodsCompany(me); + let b: &Building = unwrap_or!(map.buildings.get(c.comp.building), { + cbuf.kill(me); + return; + }); - if company.recipe.should_produce(soul, market) { - company.progress += company.productivity(n_workers, b.zone.as_ref()) - / company.recipe.complexity as f32 - * delta; - } + if c.comp.recipe.should_produce(soul, market) { + c.comp.progress += c.comp.productivity(n_workers, b.zone.as_ref()) + / c.comp.recipe.complexity as f32 + * delta; + } - if company.progress >= 1.0 { - company.progress -= 1.0; - let recipe = company.recipe.clone(); - let bpos = b.door_pos; + if c.comp.progress >= 1.0 { + c.comp.progress -= 1.0; + let recipe = c.comp.recipe.clone(); + let bpos = b.door_pos; - cbuf.exec_on(soul.0, move |market| { - recipe.act(soul, bpos.xy(), market); - }); - return; - } + cbuf.exec_on(me, move |market| { + recipe.act(soul, bpos.xy(), market); + }); + return; + } - for (_, trades) in bought.0.iter_mut() { - for trade in trades.drain(..) { - if let Some(owner_build) = find_trade_place(trade.seller, b.door_pos.xy(), binfos, map) - { - cbuf.exec_ent(soul.0, move |goria| { - let (world, res) = goria.world_res(); - if let Some(owner) = res.get::().unwrap().owner(owner_build) { - if let Ok(mut f) = world.get::<&mut FreightStation>(owner.0) { - f.wanted_cargo += 1; + for (_, trades) in c.bought.0.iter_mut() { + for trade in trades.drain(..) { + if let Some(owner_build) = + find_trade_place(trade.seller, b.door_pos.xy(), binfos, map) + { + cbuf.exec_ent(me, move |goria| { + let (world, res) = goria.world_res(); + if let Some(SoulID::FreightStation(owner)) = + res.get::().unwrap().owner(owner_build) + { + if let Some(mut f) = world.freight_stations.get_mut(owner) { + f.f.wanted_cargo += 1; + } } - } - }); + }); + } } } - } - if let Some(trade) = sold.0.drain(..1.min(sold.0.len())).next() { - if let Some(driver) = company.driver { - if let Ok(w) = world.get::<&Work>(driver.0) { - if matches!( - w.kind, - WorkKind::Driver { - deliver_order: None, - .. - } - ) { - if let Some(owner_build) = - find_trade_place(trade.buyer, b.door_pos.xy(), binfos, map) - { - cbuf.exec_ent(soul.0, move |goria| { - if let Some(mut w) = goria.comp_mut::(driver.0) { - if let WorkKind::Driver { - ref mut deliver_order, - .. - } = w.kind + if let Some(trade) = c.sold.0.drain(..1.min(c.sold.0.len())).next() { + if let Some(driver) = c.comp.driver { + if let Some(ref mut w) = world.humans.get(driver).and_then(|h| h.work) { + if matches!( + w.kind, + WorkKind::Driver { + deliver_order: None, + .. + } + ) { + if let Some(owner_build) = + find_trade_place(trade.buyer, b.door_pos.xy(), binfos, map) + { + cbuf.exec_ent(me, move |goria| { + if let Some(ref mut w) = + goria.world.humans.get(driver).and_then(|h| h.work) { - *deliver_order = Some(owner_build) + if let WorkKind::Driver { + ref mut deliver_order, + .. + } = w.kind + { + *deliver_order = Some(owner_build) + } } - } - }) - } else { - log::warn!("driver can't find the place to deliver for {:?}", &trade); + }) + } else { + log::warn!("driver can't find the place to deliver for {:?}", &trade); + } } } } } - } - for &worker in workers.0.iter() { - if world.get::<&Work>(worker.0).is_err() { - let mut kind = WorkKind::Worker; + for &worker in c.workers.0.iter() { + let Some(w) = world.humans.get(worker) else { continue; }; - if let Some(truck) = company.trucks.get(0) { - if matches!(company.kind, CompanyKind::Factory { .. }) && company.driver.is_none() { - kind = WorkKind::Driver { - deliver_order: None, - truck: *truck, - }; + if w.work.is_none() { + let mut kind = WorkKind::Worker; - company.driver = Some(worker); + if let Some(truck) = c.comp.trucks.get(0) { + if matches!(c.comp.kind, CompanyKind::Factory { .. }) && c.comp.driver.is_none() + { + kind = WorkKind::Driver { + deliver_order: None, + truck: *truck, + }; + + c.comp.driver = Some(worker); + } } - } - let offset = common::rand::randu(common::hash_u64(worker) as u32); + let offset = common::rand::randu(common::hash_u64(worker) as u32); - cbuf.add_component(worker.0, Work::new(company.building, kind, offset)) + let b = c.comp.building; + cbuf_human.exec_ent(worker, move |goria| { + let Some(w) = goria.world.humans.get_mut(worker) else { return }; + w.work = Some(Work::new(b, kind, offset)); + }); + } } - } + }); } diff --git a/egregoria/src/souls/human.rs b/egregoria/src/souls/human.rs index f0ac9fe2..20135b3b 100644 --- a/egregoria/src/souls/human.rs +++ b/egregoria/src/souls/human.rs @@ -1,21 +1,21 @@ use crate::economy::{Bought, ItemRegistry, Market}; use crate::map::BuildingID; -use crate::map_dynamic::{BuildingInfos, Destination, Router}; +use crate::map_dynamic::{BuildingInfos, Destination, Itinerary, Router}; +use crate::physics::Speed; use crate::souls::desire::{BuyFood, Home, Work}; use crate::transportation::{ - spawn_parked_vehicle, spawn_pedestrian, Location, VehicleID, VehicleKind, + random_pedestrian_shirt_color, spawn_parked_vehicle, Location, Pedestrian, VehicleKind, }; +use crate::utils::rand_provider::RandProvider; use crate::utils::resources::Resources; use crate::utils::time::GameTime; -use crate::{BuildingKind, Egregoria, FreightStation, Map, ParCommandBuffer, SoulID}; +use crate::world::{FreightStationEnt, HumanEnt, HumanID, VehicleID}; +use crate::World; +use crate::{BuildingKind, Egregoria, Map, ParCommandBuffer, SoulID}; use egui_inspect::Inspect; use geom::Transform; -use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Default)] -pub struct BasicWorker; - #[derive(Inspect, Serialize, Deserialize, Default)] pub struct HumanDecision { pub kind: HumanDecisionKind, @@ -45,13 +45,13 @@ impl HumanDecisionKind { router: &mut Router, binfos: &BuildingInfos, map: &Map, - cbuf: &ParCommandBuffer, + cbuf_freight: &ParCommandBuffer, ) -> bool { match *self { HumanDecisionKind::GoTo(dest) => router.go_to(dest), HumanDecisionKind::MultiStack(ref mut decisions) => { if let Some(d) = decisions.last_mut() { - if d.update(router, binfos, map, cbuf) { + if d.update(router, binfos, map, cbuf_freight) { decisions.pop(); } false @@ -66,10 +66,10 @@ impl HumanDecisionKind { HumanDecisionKind::DeliverAtBuilding(bid) => { let Some(b) = map.buildings().get(bid) else { return true }; if matches!(b.kind, BuildingKind::RailFreightStation) { - let Some(b) = binfos.owner(bid) else { return true }; - cbuf.exec_ent(b.0, move |e| { - if let Some(mut f) = e.comp_mut::(b.0) { - f.waiting_cargo += 1; + let Some(SoulID::FreightStation(fid)) = binfos.owner(bid) else { return true }; + cbuf_freight.exec_ent(fid, move |e| { + if let Some(mut f) = e.world.freight_stations.get_mut(fid) { + f.f.waiting_cargo += 1; } }); } @@ -94,33 +94,36 @@ pub fn update_decision_system(world: &mut World, resources: &mut Resources) { let rb = &*resources.get().unwrap(); let rc = &*resources.get().unwrap(); let rd = &*resources.get().unwrap(); - world - .query::<( - &Transform, - &Location, - &mut Router, - &mut Bought, - &mut HumanDecision, - Option<&mut BuyFood>, - Option<&mut Home>, - Option<&mut Work>, - )>() - .iter_batched(32) - //.par_bridge() - .for_each(|batch| { - batch.for_each(|(ent, (a, b, c, d, e, f, g, h))| { - update_decision(ra, rb, rc, rd, ent, a, b, c, d, e, f, g, h); - }) - }) + let re = &*resources.get().unwrap(); + + world.humans.iter_mut().for_each(|(ent, h)| { + update_decision( + ra, + rb, + rc, + rd, + re, + ent, + &h.trans, + &h.location, + &mut h.router, + &mut h.bought, + &mut h.decision, + Some(&mut h.food), + Some(&mut h.home), + h.work.as_mut(), + ) + }); } #[allow(clippy::too_many_arguments)] pub fn update_decision( - cbuf: &ParCommandBuffer, + cbuf: &ParCommandBuffer, + cbuf_freight: &ParCommandBuffer, time: &GameTime, binfos: &BuildingInfos, map: &Map, - me: Entity, + me: HumanID, trans: &Transform, loc: &Location, router: &mut Router, @@ -136,11 +139,10 @@ pub fn update_decision( } let pos = trans.position; decision.wait = (30.0 + common::rand::rand2(pos.x, pos.y) * 50.0) as u8; - if !decision.kind.update(router, binfos, map, cbuf) { + if !decision.kind.update(router, binfos, map, cbuf_freight) { return; } - let soul = SoulID(me); let mut decision_id = NextDesire::None; let mut max_score = f32::NEG_INFINITY; @@ -176,47 +178,53 @@ pub fn update_decision( NextDesire::Home(home) => decision.kind = home.apply(), NextDesire::Work(work) => decision.kind = work.apply(loc, router), NextDesire::Food(food) => { - decision.kind = food.apply(cbuf, binfos, map, time, soul, trans, loc, bought) + decision.kind = food.apply(cbuf, binfos, map, time, me, trans, loc, bought) } NextDesire::None => {} } } #[profiling::function] -pub fn spawn_human(goria: &mut Egregoria, house: BuildingID) -> Option { +pub fn spawn_human(goria: &mut Egregoria, house: BuildingID) -> Option { let map = goria.map(); let housepos = map.buildings().get(house)?.door_pos; drop(map); - let human = SoulID(spawn_pedestrian(goria, house)?); - let car = spawn_parked_vehicle(goria, VehicleKind::Car, housepos); + let _color = random_pedestrian_shirt_color(&mut goria.write::()); - let mut m = goria.write::(); - {} - let registry = goria.read::(); - m.buy(human, housepos.xy(), registry.id("job-opening"), 1); - drop(m); - - goria.write::().set_owner(house, human); + let hpos = goria.map().buildings().get(house)?.door_pos; + let p = Pedestrian::new(&mut goria.write::()); + let registry = goria.read::(); let time = goria.read::().instant(); let food = BuyFood::new(time, ®istry); drop(registry); - goria - .world - .insert( - human.0, - ( - HumanDecision::default(), - Home::new(house), - food, - Bought::default(), - Router::new(car), - BasicWorker, - ), - ) - .unwrap(); - Some(human) + let car = spawn_parked_vehicle(goria, VehicleKind::Car, housepos); + + let id = goria.world.insert(HumanEnt { + trans: Transform::new(hpos), + location: Location::Building(house), + pedestrian: p, + it: Itinerary::NONE, + speed: Speed::default(), + decision: HumanDecision::default(), + home: Home::new(house), + food, + bought: Bought::default(), + router: Router::new(car), + collider: None, + work: None, + }); + + let soul = SoulID::Human(id); + let mut m = goria.write::(); + let registry = goria.read::(); + m.buy(soul, housepos.xy(), registry.id("job-opening"), 1); + + goria.write::().get_in(house, soul); + goria.write::().set_owner(house, soul); + + Some(id) } diff --git a/egregoria/src/tests/test_iso.rs b/egregoria/src/tests/test_iso.rs index 08b8f9e1..7bb5380f 100644 --- a/egregoria/src/tests/test_iso.rs +++ b/egregoria/src/tests/test_iso.rs @@ -3,10 +3,10 @@ use crate::init::init; use crate::map::{Map, MapProject, ProjectKind}; use crate::utils::scheduler::SeqSchedule; use crate::utils::time::Tick; +use crate::World; use crate::{Egregoria, Replay}; use common::logger::MyLog; use common::saveload::Encoder; -use hecs::World; static REPLAY: &'static [u8] = include_bytes!("world_replay.json"); @@ -23,16 +23,16 @@ fn check_coherent(map: &Map, proj: MapProject) { } fn check_eq(w1: &World, w2: &World) -> bool { - for (c1, c2) in w1.iter().zip(w2.iter()) { - if c1.entity().id() != c2.entity().id() { - println!("{:?} {:?}", c1.entity(), c2.entity()); + for (c1, c2) in w1.entities().zip(w2.entities()) { + if c1 != c2 { + println!("{:?} {:?}", c1, c2); return false; } } true } -//#[test] +//#[test] // uncomment when slotmap has been forked fn test_world_survives_serde() { init(); MyLog::init(); diff --git a/egregoria/src/transportation/mod.rs b/egregoria/src/transportation/mod.rs index 9481bf39..aca2ef5c 100644 --- a/egregoria/src/transportation/mod.rs +++ b/egregoria/src/transportation/mod.rs @@ -6,6 +6,7 @@ pub mod road; pub mod train; mod vehicle; +use crate::world::VehicleID; pub use pedestrian::*; pub use vehicle::*; diff --git a/egregoria/src/transportation/pedestrian.rs b/egregoria/src/transportation/pedestrian.rs index fae021eb..a2fb8c6d 100644 --- a/egregoria/src/transportation/pedestrian.rs +++ b/egregoria/src/transportation/pedestrian.rs @@ -1,16 +1,11 @@ -use crate::engine_interaction::Selectable; -use crate::map::BuildingID; -use crate::map_dynamic::{BuildingInfos, Itinerary}; +use crate::map_dynamic::Itinerary; use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject, Speed}; -use crate::transportation::Location; use crate::utils::rand_provider::RandProvider; use crate::utils::resources::Resources; use crate::utils::time::GameTime; -use crate::{Egregoria, SoulID}; +use crate::World; use egui_inspect::Inspect; use geom::{angle_lerpxy, Color, Transform, Vec3}; -use hecs::Entity; -use hecs::World; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Inspect)] @@ -21,24 +16,6 @@ pub struct Pedestrian { const PED_SIZE: f32 = 0.5; -pub fn spawn_pedestrian(goria: &mut Egregoria, house: BuildingID) -> Option { - let _color = random_pedestrian_shirt_color(&mut goria.write::()); - - let hpos = goria.map().buildings().get(house)?.door_pos; - let p = Pedestrian::new(&mut goria.write::()); - let e = goria.world.spawn(( - Transform::new(hpos), - Location::Building(house), - p, - Itinerary::NONE, - Speed::default(), - Selectable::new(3.0), - )); - - goria.write::().get_in(house, SoulID(e)); - Some(e) -} - pub fn put_pedestrian_in_coworld(coworld: &mut CollisionWorld, pos: Vec3) -> Collider { Collider(coworld.insert( pos.xy(), @@ -51,7 +28,7 @@ pub fn put_pedestrian_in_coworld(coworld: &mut CollisionWorld, pos: Vec3) -> Col } impl Pedestrian { - fn new(r: &mut RandProvider) -> Self { + pub(crate) fn new(r: &mut RandProvider) -> Self { Self { walking_speed: (0.8 + r.next_f32() * 0.8), walk_anim: 0.0, @@ -86,11 +63,10 @@ pub fn random_pedestrian_shirt_color(r: &mut RandProvider) -> Color { #[profiling::function] pub fn pedestrian_decision_system(world: &mut World, resources: &mut Resources) { let ra = &*resources.get().unwrap(); - world - .query::<(&mut Itinerary, &mut Transform, &mut Speed, &mut Pedestrian)>() - .iter_batched(32) + world.humans + .values_mut() //.par_bridge() - .for_each(|batch| batch.for_each(|(_, (a, b, c, d))| pedestrian_decision(ra, a, b, c, d))) + .for_each(|human| pedestrian_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.pedestrian)) } pub fn pedestrian_decision( @@ -102,7 +78,7 @@ pub fn pedestrian_decision( ) { let (desired_v, desired_dir) = calc_decision(pedestrian, trans, it); - pedestrian.walk_anim += 7.0 * kin.speed * time.delta / pedestrian.walking_speed; + pedestrian.walk_anim += 7.0 * kin.0 * time.delta / pedestrian.walking_speed; pedestrian.walk_anim %= 2.0 * std::f32::consts::PI; physics(kin, trans, time, desired_v, desired_dir); } @@ -116,10 +92,10 @@ pub fn physics( desired_velocity: f32, desired_dir: Vec3, ) { - let diff = desired_velocity - kin.speed; + let diff = desired_velocity - kin.0; let mag = diff.min(time.delta * PEDESTRIAN_ACC); if mag > 0.0 { - kin.speed += mag; + kin.0 += mag; } const ANG_VEL: f32 = 1.0; trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.delta); diff --git a/egregoria/src/transportation/road.rs b/egregoria/src/transportation/road.rs index 69e21841..421d2493 100644 --- a/egregoria/src/transportation/road.rs +++ b/egregoria/src/transportation/road.rs @@ -5,37 +5,40 @@ use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject}; 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 hecs::{Entity, World}; +use slotmap::Key; #[profiling::function] pub fn vehicle_decision_system(world: &mut World, resources: &mut Resources) { let ra = &*resources.get().unwrap(); let rb = &*resources.get().unwrap(); let rc = &*resources.get().unwrap(); - world - .query::<( - &mut Itinerary, - &mut Transform, - &mut Speed, - &mut Vehicle, - &Collider, - )>() - .iter_batched(32) - //.par_bridge() - .for_each(|batch| { - batch.for_each(|(ent, (a, b, c, d, e))| { - vehicle_decision(ra, rb, rc, ent, a, b, c, d, e); - }) - }) + + world.vehicles.iter_mut().for_each(|(ent, v)| { + let Some(ref coll) = v.collider else { return; }; + + vehicle_decision( + ra, + rb, + rc, + ent, + &mut v.it, + &mut v.trans, + &mut v.speed, + &mut v.vehicle, + coll, + ); + }); } pub fn vehicle_decision( map: &Map, time: &GameTime, cow: &CollisionWorld, - me: Entity, + me: VehicleID, it: &mut Itinerary, trans: &mut Transform, kin: &mut Speed, @@ -78,26 +81,31 @@ pub fn vehicle_state_update_system(world: &mut World, resources: &mut Resources) let ra = &*resources.get().unwrap(); let rb = &*resources.get().unwrap(); let rc = &*resources.get().unwrap(); - world - .query::<(&mut Vehicle, &mut Transform, &mut Speed)>() - .iter_batched(32) - //.par_bridge() - .for_each(|batch| { - batch.for_each(|(ent, (a, b, c))| { - vehicle_state_update(ra, rb, rc, ent, a, b, c); - }) - }) + + world.vehicles.iter_mut().for_each(|(ent, v)| { + vehicle_state_update( + ra, + rb, + rc, + ent, + &mut v.vehicle, + &mut v.trans, + &mut v.speed, + &mut v.collider, + ); + }); } /// Decides whether a vehicle should change states, from parked to unparking to driving etc pub fn vehicle_state_update( - buf: &ParCommandBuffer, + buf: &ParCommandBuffer, time: &GameTime, map: &Map, - ent: Entity, + ent: VehicleID, vehicle: &mut Vehicle, trans: &mut Transform, kin: &mut Speed, + coll: &mut Option, ) { match vehicle.state { VehicleState::RoadToPark(_, ref mut t, _) => { @@ -105,8 +113,11 @@ pub fn vehicle_state_update( *t += time.delta / TIME_TO_PARK; if *t >= 1.0 { - buf.remove_component_drop::(ent); - kin.speed = 0.0; + let v = coll.take(); + if let Some(x) = v { + buf.exec_ent(ent, x.destroy()); + } + kin.0 = 0.0; let spot = match std::mem::replace(&mut vehicle.state, VehicleState::Driving) { VehicleState::RoadToPark(_, _, spot) => spot, _ => unreachable!(), @@ -173,12 +184,12 @@ fn physics( trans.dir = angle_lerpxy(trans.dir, desired_dir, vehicle.ang_velocity * time.delta); - kin.speed = speed; + kin.0 = speed; } /// Decide the appropriate velocity and direction to aim for. pub fn calc_decision<'a>( - me: Entity, + me: VehicleID, vehicle: &mut Vehicle, map: &Map, time: &GameTime, @@ -213,7 +224,7 @@ pub fn calc_decision<'a>( vehicle.state = VehicleState::Driving; } } else if speed.abs() < 0.2 && front_dist < 1.5 { - let me_u64: u64 = me.to_bits().get(); + let me_u64: u64 = me.data().as_ffi(); if me_u64 == flag { vehicle.state = VehicleState::Panicking(time.instant()); log::info!("gridlock!") diff --git a/egregoria/src/transportation/train.rs b/egregoria/src/transportation/train.rs index 78cafdbc..7e58d14a 100644 --- a/egregoria/src/transportation/train.rs +++ b/egregoria/src/transportation/train.rs @@ -1,24 +1,20 @@ use crate::map::{IntersectionID, LaneID, Map, TraverseKind}; -use crate::map_dynamic::{DispatchKind, ItineraryFollower2, ItineraryKind}; +use crate::map_dynamic::{ItineraryFollower, ItineraryKind}; use crate::utils::resources::Resources; -use crate::{Egregoria, GameTime, Itinerary, ItineraryLeader, Selectable, Speed}; +use crate::world::{TrainEnt, TrainID, WagonEnt}; +use crate::{Egregoria, GameTime, Itinerary, ItineraryLeader, Speed, World}; use egui_inspect::Inspect; use geom::{PolyLine3, Polyline3Queue, Transform, Vec3}; -use hecs::{Entity, View, World}; use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; use std::collections::btree_map::Entry; use std::collections::BTreeMap; -#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub struct TrainID(pub Entity); - -debug_inspect_impl!(TrainID); - #[derive(Default, Serialize, Deserialize)] pub struct TrainReservations { - pub reservations: BTreeMap, - pub localisations: BTreeMap>, + pub reservations: BTreeMap, + pub localisations: BTreeMap>, } #[derive(Serialize, Deserialize, Inspect)] @@ -41,14 +37,16 @@ pub struct LocomotiveReservation { upcoming_inters: Vec, } -#[derive(Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] pub enum RailWagonKind { Locomotive, Passenger, Freight, } -#[derive(Serialize, Deserialize)] +debug_inspect_impl!(RailWagonKind); + +#[derive(Inspect, Serialize, Deserialize)] pub struct RailWagon { pub kind: RailWagonKind, } @@ -89,7 +87,7 @@ pub fn spawn_train( n_wagons: u32, lane: LaneID, kind: RailWagonKind, -) -> Option { +) -> Option { let (world, res) = goria.world_res(); let map = res.get::().ok()?; @@ -109,22 +107,17 @@ pub fn spawn_train( let trainlength = train_length(n_wagons); - let leader = ItineraryLeader { - past: Polyline3Queue::new(points.into_iter(), locopos, trainlength + 20.0), - }; - - let loco = world.spawn(( - Transform::new_dir(locopos, locodir), - Speed::default(), - Selectable::new(10.0), - Locomotive { + let loco = world.insert(TrainEnt { + trans: Transform::new_dir(locopos, locodir), + speed: Default::default(), + it: Itinerary::NONE, + locomotive: Locomotive { max_speed: 50.0, acc_force: 1.0, dec_force: 2.5, length: trainlength, }, - DispatchKind::FreightTrain, - LocomotiveReservation { + res: LocomotiveReservation { cur_travers_dist: dist, waited_for: 0.0, past_travers: BTreeMap::from([( @@ -133,8 +126,13 @@ pub fn spawn_train( )]), upcoming_inters: Default::default(), }, - Itinerary::NONE, - )); + leader: ItineraryLeader { + past: Polyline3Queue::new(points.into_iter(), locopos, trainlength + 20.0), + }, + }); + + let leader = &world.trains.get(loco).unwrap().leader; + let mut followers: Vec<_> = leader .past .mk_followers( @@ -145,27 +143,24 @@ pub fn spawn_train( for (i, follower) in followers.chunks_exact_mut(2).enumerate() { let (pos, dir) = follower[0].update(&leader.past); let (pos2, dir2) = follower[1].update(&leader.past); - world.spawn(( - Transform::new_dir(pos * 0.5 + pos2 * 0.5, (0.5 * (dir + dir2)).normalize()), - Speed::default(), - Selectable::new(10.0), - RailWagon { + world.wagons.insert(WagonEnt { + trans: Transform::new_dir(pos * 0.5 + pos2 * 0.5, (0.5 * (dir + dir2)).normalize()), + speed: Speed::default(), + wagon: RailWagon { kind: if i == 0 { RailWagonKind::Locomotive } else { kind }, }, - ItineraryFollower2 { + itfollower: ItineraryFollower { leader: loco, head: follower[0], tail: follower[1], }, - )); + }); } - world.insert_one(loco, leader).unwrap(); - Some(loco) } @@ -206,198 +201,178 @@ pub fn train_reservations_update(world: &mut World, resources: &mut Resources) { let reservations = &mut *resources.get_mut::().unwrap(); let lanes = map.lanes(); let inters = map.intersections(); - world - .query_mut::<(&Itinerary, &Locomotive, &mut LocomotiveReservation, &Speed)>() - .into_iter() - .for_each(move |(me, (itin, loco, locores, kin))| { - // Remember when we've been - if let Some(travers) = itin.get_travers() { - match locores.past_travers.entry(travers.kind) { - Entry::Vacant(v) => { - v.insert(10.0 - travers.kind.length(lanes, inters).unwrap_or(0.0)); - locores.cur_travers_dist = 0.0; - } - Entry::Occupied(_) => {} - }; - - // Handle upcoming intersections - // Start by cleaning them then re-reserve them (so that in event of weirdness, they stay correct) - for v in locores.upcoming_inters.drain(..) { - reservations.reservations.remove(&v); + world.trains.iter_mut().for_each(move |(me, train)| { + // Remember when we've been + if let Some(travers) = train.it.get_travers() { + match train.res.past_travers.entry(travers.kind) { + Entry::Vacant(v) => { + v.insert(10.0 - travers.kind.length(lanes, inters).unwrap_or(0.0)); + train.res.cur_travers_dist = 0.0; } + Entry::Occupied(_) => {} + }; - let dist_to_next = - travers.kind.length(lanes, inters).unwrap_or(0.0) - locores.cur_travers_dist; + // Handle upcoming intersections + // Start by cleaning them then re-reserve them (so that in event of weirdness, they stay correct) + for v in train.res.upcoming_inters.drain(..) { + reservations.reservations.remove(&v); + } - let mut want_to_reserve = vec![]; - let mut all_ok = true; - // Then look ahead stop_dist to reserve all intersections - let stop_dist = kin.speed * kin.speed / (2.0 * loco.dec_force); + let dist_to_next = + travers.kind.length(lanes, inters).unwrap_or(0.0) - train.res.cur_travers_dist; - if let Some(v) = reservations.localisations.get(&travers.kind) { - if v.len() >= 2 - && *v.values().max_by_key(|x| OrderedFloat(**x)).unwrap() - != v.get(&me).copied().unwrap_or(f32::NEG_INFINITY) - { - all_ok = false; - } - } + let mut want_to_reserve = vec![]; + let mut all_ok = true; + // Then look ahead stop_dist to reserve all intersections + let stop_dist = train.speed.0 * train.speed.0 / (2.0 * train.locomotive.dec_force); - if all_ok { - for (id, _, _, _) in traverse_forward( - map, - itin, - stop_dist + 5.0, - dist_to_next, - loco.length + 25.0, - ) { - if let Some(v) = reservations.localisations.get(&id) { - if v.len() > 2 || (v.len() == 1 && v.get(&me).is_none()) { - all_ok = false; - break; - } - } - if let TraverseKind::Turn(id) = id { - if inters - .get(id.parent) - .map(|i| i.roads.len() <= 2) - .unwrap_or(true) - { - continue; - } + if let Some(v) = reservations.localisations.get(&travers.kind) { + if v.len() >= 2 + && *v.values().max_by_key(|x| OrderedFloat(**x)).unwrap() + != v.get(&me).copied().unwrap_or(f32::NEG_INFINITY) + { + all_ok = false; + } + } - if reservations.reservations.get(&id.parent).is_some() { - all_ok = false; - break; - } - want_to_reserve.push(id.parent); + if all_ok { + for (id, _, _, _) in traverse_forward( + map, + &train.it, + stop_dist + 5.0, + dist_to_next, + train.locomotive.length + 25.0, + ) { + if let Some(v) = reservations.localisations.get(&id) { + if v.len() > 2 || (v.len() == 1 && v.get(&me).is_none()) { + all_ok = false; + break; } } + if let TraverseKind::Turn(id) = id { + if inters + .get(id.parent) + .map(|i| i.roads.len() <= 2) + .unwrap_or(true) + { + continue; + } - if all_ok { - for id in want_to_reserve { - reservations.reservations.insert(id, me); - locores.upcoming_inters.push(id); + if reservations.reservations.get(&id.parent).is_some() { + all_ok = false; + break; } + want_to_reserve.push(id.parent); } } - } - // Clean past_things and unreserve them - let length = loco.length; - locores.past_travers.retain(|&id, dist| { - reservations - .localisations - .entry(id) - .or_default() - .insert(me, *dist); - if *dist >= length { - if let TraverseKind::Turn(id) = id { - reservations.reservations.remove(&id.parent); - } - let l = unwrap_ret!(reservations.localisations.get_mut(&id), false); - l.remove(&me); - if l.is_empty() { - reservations.localisations.remove(&id); + if all_ok { + for id in want_to_reserve { + reservations.reservations.insert(id, me); + train.res.upcoming_inters.push(id); } - return false; } + } + } + + // Clean past_things and unreserve them + let length = train.locomotive.length; + train.res.past_travers.retain(|&id, dist| { + reservations + .localisations + .entry(id) + .or_default() + .insert(me, *dist); + if *dist >= length { if let TraverseKind::Turn(id) = id { - reservations.reservations.entry(id.parent).or_insert(me); + reservations.reservations.remove(&id.parent); + } + let l = unwrap_ret!(reservations.localisations.get_mut(&id), false); + l.remove(&me); + if l.is_empty() { + reservations.localisations.remove(&id); } + return false; + } + if let TraverseKind::Turn(id) = id { + reservations.reservations.entry(id.parent).or_insert(me); + } - true - }); + true }); + }); } #[profiling::function] pub fn locomotive_system(world: &mut World, resources: &mut Resources) { - let map = &*resources.get().unwrap(); - let time = &*resources.get().unwrap(); - let reservs = &*resources.get().unwrap(); - - let mut locoqry = world.query::<&Locomotive>(); - let locoview = locoqry.view(); - - world - .query::<( - &mut Itinerary, - &mut Transform, - &mut Speed, - &Locomotive, - &mut LocomotiveReservation, - )>() - .iter() - .for_each(move |(ent, (it, trans, kin, loco, locores))| { - locomotive_decision( - map, time, reservs, &locoview, ent, it, trans, kin, loco, locores, - ); - }) -} + let map: &Map = &resources.get().unwrap(); + let time: &GameTime = &resources.get().unwrap(); + let reservs: &TrainReservations = &resources.get().unwrap(); + + // asume iter order stays the same + let mut desired_speeds = Vec::with_capacity(world.trains.len()); + + for (ent, train) in world.trains.iter() { + desired_speeds.push(locomotive_desired_speed( + ent, + map, + reservs, + &world.trains, + train, + )); + } -pub fn locomotive_decision( - map: &Map, - time: &GameTime, - reservs: &TrainReservations, - locoview: &View<&Locomotive>, - me: Entity, - it: &mut Itinerary, - trans: &mut Transform, - kin: &mut Speed, - loco: &Locomotive, - locores: &mut LocomotiveReservation, -) { - let desired_speed = - locomotive_desired_speed(me, map, reservs, locoview, trans, kin, it, loco, locores); - let desired_dir = it - .get_point() - .and_then(|x| { - let d = x - trans.position; - if d.mag2() < 0.5 { - return None; + for (t, desired_speed) in world.trains.values_mut().zip(desired_speeds) { + let desired_dir = + t.it.get_point() + .and_then(|x| { + let d = x - t.trans.position; + if d.mag2() < 0.5 { + return None; + } + d.try_normalize() + }) + .unwrap_or(t.trans.dir); + t.trans.dir = desired_dir; + + t.speed.0 += (desired_speed - t.speed.0).clamp( + -time.delta * t.locomotive.dec_force, + time.delta * t.locomotive.acc_force, + ); + if t.speed.0 <= 0.001 { + t.res.waited_for += time.delta; + } else { + t.res.waited_for = 0.0; + } + for v in t.res.past_travers.values_mut() { + *v += t.speed.0 * time.delta; + if t.res.waited_for > 60.0 { + *v += 0.1 * time.delta; } - d.try_normalize() - }) - .unwrap_or(trans.dir); - trans.dir = desired_dir; - - kin.speed += (desired_speed - kin.speed) - .clamp(-time.delta * loco.dec_force, time.delta * loco.acc_force); - if kin.speed <= 0.001 { - locores.waited_for += time.delta; - } else { - locores.waited_for = 0.0; - } - for v in locores.past_travers.values_mut() { - *v += kin.speed * time.delta; - if locores.waited_for > 60.0 { - *v += 0.1 * time.delta; } + t.res.cur_travers_dist += t.speed.0 * time.delta; } - locores.cur_travers_dist += kin.speed * time.delta; } pub fn locomotive_desired_speed( - me: Entity, + me: TrainID, map: &Map, reservs: &TrainReservations, - locoview: &View<&Locomotive>, - trans: &Transform, - kin: &Speed, - it: &Itinerary, - loco: &Locomotive, - locores: &LocomotiveReservation, + locos: &SlotMap, + t: &TrainEnt, ) -> f32 { - if matches!(it.kind(), ItineraryKind::None | ItineraryKind::WaitUntil(_)) { + if matches!( + t.it.kind(), + ItineraryKind::None | ItineraryKind::WaitUntil(_) + ) { return 0.0; } - let stop_dist = kin.speed * kin.speed / (2.0 * loco.dec_force); + let stop_dist = t.speed.0 * t.speed.0 / (2.0 * t.locomotive.dec_force); let mut lastid = None; - let mydist = locores.cur_travers_dist; - if let Some(travers) = it.get_travers() { + let mydist = t.res.cur_travers_dist; + if let Some(travers) = t.it.get_travers() { let lanes = map.lanes(); let startl = travers @@ -409,7 +384,7 @@ pub fn locomotive_desired_speed( for (id, acc, travers_length, _) in std::iter::once((travers.kind, -mydist, startl, startl)) .chain(traverse_forward( map, - it, + &t.it, stop_dist + 15.0, dist_to_next, -1.0, @@ -420,10 +395,10 @@ pub fn locomotive_desired_speed( if train == me { continue; } - if let Some(otherloco) = locoview.get(train) { + if let Some(otherloco) = locos.get(train) { let dist_to_other = acc + otherdist + travers_length; if dist_to_other > 0.0 - && dist_to_other < otherloco.length + stop_dist + 10.0 + && dist_to_other < otherloco.locomotive.length + stop_dist + 10.0 { return 0.0; } @@ -448,7 +423,7 @@ pub fn locomotive_desired_speed( } let mut on_last_lane = false; - if let ItineraryKind::Route(r, _) = it.kind() { + if let ItineraryKind::Route(r, _) = t.it.kind() { if r.reversed_route.is_empty() || (lastid.is_some() && lastid == r.reversed_route.first().map(|x| x.kind)) { @@ -456,17 +431,17 @@ pub fn locomotive_desired_speed( } } - if matches!(it.kind(), ItineraryKind::Simple(_)) { + if matches!(t.it.kind(), ItineraryKind::Simple(_)) { on_last_lane = true } if on_last_lane { - if let Some(howfar) = it.end_pos().map(|term| term.distance(trans.position)) { + if let Some(howfar) = t.it.end_pos().map(|term| term.distance(t.trans.position)) { if howfar + 0.1 <= stop_dist { return 0.0; } } } - loco.max_speed + t.locomotive.max_speed } diff --git a/egregoria/src/transportation/vehicle.rs b/egregoria/src/transportation/vehicle.rs index 9ba8cf47..48ebf11a 100644 --- a/egregoria/src/transportation/vehicle.rs +++ b/egregoria/src/transportation/vehicle.rs @@ -1,26 +1,17 @@ -use crate::engine_interaction::Selectable; use crate::map_dynamic::{Itinerary, ParkingManagement, SpotReservation}; -use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject, Speed}; -use crate::utils::par_command_buffer::ComponentDrop; +use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject}; use crate::utils::rand_provider::RandProvider; -use crate::utils::resources::Resources; use crate::utils::time::GameInstant; +use crate::world::{VehicleEnt, VehicleID}; use crate::Egregoria; use egui_inspect::Inspect; use geom::Transform; use geom::{Color, Spline3, Vec3}; -use hecs::Entity; use serde::{Deserialize, Serialize}; /// The duration for the parking animation. pub const TIME_TO_PARK: f32 = 4.0; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[repr(transparent)] -pub struct VehicleID(pub Entity); - -debug_inspect_impl!(VehicleID); - #[derive(Debug, Serialize, Deserialize)] pub enum VehicleState { Parked(SpotReservation), @@ -52,18 +43,8 @@ pub struct Vehicle { pub flag: u64, } -impl ComponentDrop for Vehicle { - fn drop(&mut self, res: &mut Resources, _: Entity) { - if let VehicleState::Parked(resa) | VehicleState::RoadToPark(_, _, resa) = - std::mem::replace(&mut self.state, VehicleState::Driving) - { - res.get_mut::().unwrap().free(resa); - } - } -} - #[must_use] -pub fn put_vehicle_in_coworld(goria: &mut Egregoria, w: f32, trans: Transform) -> Collider { +pub fn put_vehicle_in_coworld(goria: &Egregoria, w: f32, trans: Transform) -> Collider { Collider(goria.write::().insert( trans.position.xy(), PhysicsObject { @@ -123,20 +104,22 @@ impl VehicleKind { } pub fn unpark(goria: &mut Egregoria, vehicle: VehicleID) { - let mut v = unwrap_ret!(goria.comp_mut::(vehicle.0)); - let w = v.kind.width(); + let v = unwrap_ret!(goria.world.vehicles.get_mut(vehicle)); + let w = v.vehicle.kind.width(); + let trans = v.trans; - if let VehicleState::Parked(spot) = std::mem::replace(&mut v.state, VehicleState::Driving) { - drop(v); + if let VehicleState::Parked(spot) = + std::mem::replace(&mut v.vehicle.state, VehicleState::Driving) + { goria.write::().free(spot); } else { - drop(v); log::warn!("Trying to unpark {:?} that wasn't parked", vehicle); } - let trans = *unwrap_ret!(goria.comp::(vehicle.0)); let coll = put_vehicle_in_coworld(goria, w, trans); - goria.add_comp(vehicle.0, coll); + + let mut v = unwrap_ret!(goria.world.vehicles.get_mut(vehicle)); + v.collider = Some(coll); } pub fn spawn_parked_vehicle( @@ -162,13 +145,13 @@ pub fn spawn_parked_vehicle( _ => Color::WHITE, }; - Some(VehicleID(make_vehicle_entity( + Some(make_vehicle_entity( goria, pos, Vehicle::new(kind, spot_id, tint), it, false, - ))) + )) } pub fn make_vehicle_entity( @@ -177,19 +160,20 @@ pub fn make_vehicle_entity( vehicle: Vehicle, it: Itinerary, mk_collider: bool, -) -> Entity { +) -> VehicleID { let w = vehicle.kind.width(); - let e = goria - .world - .spawn((trans, Speed::default(), Selectable::default(), vehicle, it)); + let mut collider = None; if mk_collider { - let c = put_vehicle_in_coworld(goria, w, trans); - #[allow(clippy::unwrap_used)] // literally just added to the world - let _ = goria.world.insert_one(e, c); + collider = Some(put_vehicle_in_coworld(goria, w, trans)); } - - e + goria.world.insert(VehicleEnt { + trans, + speed: Default::default(), + vehicle, + it, + collider, + }) } pub fn get_random_car_color(r: &mut RandProvider) -> Color { diff --git a/egregoria/src/utils/par_command_buffer.rs b/egregoria/src/utils/par_command_buffer.rs index dfe862b8..c5e06cfa 100644 --- a/egregoria/src/utils/par_command_buffer.rs +++ b/egregoria/src/utils/par_command_buffer.rs @@ -1,167 +1,79 @@ -use crate::economy::Market; -use crate::map_dynamic::{DispatchKind, Router}; -use crate::physics::Collider; -use crate::transportation::Vehicle; use crate::utils::resources::Resources; -use crate::{Egregoria, SoulID}; -use hecs::{Component, Entity}; -use std::any::{Any, TypeId}; -use std::collections::BTreeMap; +use crate::world::Entity; +use crate::Egregoria; use std::sync::Mutex; -pub trait ComponentDrop { - fn drop(&mut self, goria: &mut Resources, ent: Entity); +pub trait GoriaDrop: Entity { + fn goria_drop(self, id: Self::ID, res: &mut Resources); } type ExecType = Box FnOnce(&'a mut Egregoria) + Send>; -#[derive(Default)] -pub struct ParCommandBuffer { - to_kill: Mutex>, - add_comp: Mutex>, - remove_comp: Mutex>, - exec_ent: Mutex>>, - exec_on: Mutex>, +pub struct ParCommandBuffer { + to_kill: Mutex>, + exec_ent: Mutex>, +} + +impl Default for ParCommandBuffer { + fn default() -> Self { + Self { + to_kill: Default::default(), + exec_ent: Default::default(), + } + } } #[allow(clippy::unwrap_used)] -impl ParCommandBuffer { - pub fn kill(&self, e: Entity) { +impl ParCommandBuffer { + pub fn kill(&self, e: E::ID) { self.to_kill.lock().unwrap().push(e); } - pub fn kill_all(&self, e: &[Entity]) { + pub fn kill_all(&self, e: &[E::ID]) { self.to_kill.lock().unwrap().extend_from_slice(e); } - pub fn exec_ent(&self, e: Entity, f: impl for<'a> FnOnce(&'a mut Egregoria) + 'static + Send) { - self.exec_ent - .lock() - .unwrap() - .entry(e) - .or_default() - .push(Box::new(f)) + pub fn exec_ent(&self, e: E::ID, f: impl for<'a> FnOnce(&'a mut Egregoria) + 'static + Send) { + self.exec_ent.lock().unwrap().push((e, Box::new(f))); } - pub fn exec_on( + pub fn exec_on( &self, - e: Entity, + e: E::ID, f: impl for<'a> FnOnce(&'a mut T) + 'static + Send, ) { - let key = (e, TypeId::of::()); - let v = self - .exec_on - .lock() - .unwrap() - .insert(key, Box::new(move |goria| f(&mut *goria.write::()))); - if v.is_some() { - log::error!( - "executing two exec_on closures relating to an entity. Might cause desyncs" - ); - } - } - - pub fn add_component(&self, e: Entity, c: T) { - let key = (e, TypeId::of::()); - let v = self.add_comp.lock().unwrap().insert( - key, - Box::new(move |w| { - let _ = w.world.insert_one(e, c); - }), - ); - if v.is_some() { - log::error!("adding two times the same component to a struct. Might cause desyncs"); - } - } - - pub fn remove_component(&self, e: Entity) { - let key = (e, TypeId::of::()); - self.remove_comp.lock().unwrap().insert( - key, - Box::new(move |w| { - let _ = w.world.remove_one::(e); - }), - ); - } - - pub fn remove_component_drop(&self, e: Entity) { - let key = (e, TypeId::of::()); - self.remove_comp.lock().unwrap().insert( - key, - Box::new(move |w| { - if let Ok(mut c) = w.world.remove_one::(e) { - ComponentDrop::drop(&mut c, &mut w.resources, e); - } - }), - ); + self.exec_ent(e, move |goria| { + f(&mut *goria.write::()); + }) } #[profiling::function] pub fn apply(goria: &mut Egregoria) { - let mut deleted: Vec = - std::mem::take(&mut *goria.write::().to_kill.get_mut().unwrap()); - - deleted.sort_unstable(); - - for entity in deleted { - if goria.world.despawn(entity).is_err() { - continue; - } - goria.write::().remove(SoulID(entity)); - if let Ok(mut v) = goria.world.get::<&mut Collider>(entity) { - ComponentDrop::drop(&mut *v, &mut goria.resources, entity); - } - if let Ok(mut v) = goria.world.get::<&mut Vehicle>(entity) { - ComponentDrop::drop(&mut *v, &mut goria.resources, entity); - } - if let Ok(mut v) = goria.world.get::<&mut Router>(entity) { - ComponentDrop::drop(&mut *v, &mut goria.resources, entity); - } - if let Ok(mut v) = goria.world.get::<&mut DispatchKind>(entity) { - ComponentDrop::drop(&mut *v, &mut goria.resources, entity); - } - } - - let added = std::mem::take( + let mut deleted: Vec = std::mem::take( &mut *goria - .write::() - .add_comp + .write::>() + .to_kill .get_mut() .unwrap(), ); - for (_, add) in added { - add(goria); - } + deleted.sort_unstable(); - let removed = std::mem::take( - &mut *goria - .write::() - .remove_comp - .get_mut() - .unwrap(), - ); + for entity in deleted { + let Some(v) = E::storage_mut(&mut goria.world).remove(entity) else { continue }; - for (_, remove) in removed { - remove(goria); + E::goria_drop(v, entity, &mut goria.resources); } - let exec_ent = std::mem::take( + let mut exec_ent = std::mem::take( &mut *goria - .write::() + .write::>() .exec_ent .get_mut() .unwrap(), ); - for (_, execs) in exec_ent { - for exec in execs { - exec(goria); - } - } - - let exec_ent = - std::mem::take(&mut *goria.write::().exec_on.get_mut().unwrap()); + exec_ent.sort_unstable_by_key(|(id, _)| *id); for (_, exec) in exec_ent { exec(goria); diff --git a/egregoria/src/utils/resources.rs b/egregoria/src/utils/resources.rs index ee8dc913..37d611c1 100644 --- a/egregoria/src/utils/resources.rs +++ b/egregoria/src/utils/resources.rs @@ -66,6 +66,7 @@ impl Resources { )?) } + // TODO: try_get_mut instead of get_mut pub fn get_mut(&self) -> Result, CantGetResource> { Ok(RefMut::from_lock( self.resources diff --git a/egregoria/src/utils/scheduler.rs b/egregoria/src/utils/scheduler.rs index 981171dc..fac941f9 100644 --- a/egregoria/src/utils/scheduler.rs +++ b/egregoria/src/utils/scheduler.rs @@ -1,4 +1,5 @@ -use crate::{Egregoria, ParCommandBuffer}; +use crate::world::{CompanyEnt, HumanEnt, TrainEnt, VehicleEnt, WagonEnt}; +use crate::{Egregoria, FreightStationEnt, ParCommandBuffer}; use common::History; use ordered_float::OrderedFloat; use std::time::Instant; @@ -42,7 +43,12 @@ impl SeqSchedule { sys.run(goria); - ParCommandBuffer::apply(goria); + ParCommandBuffer::::apply(goria); + ParCommandBuffer::::apply(goria); + ParCommandBuffer::::apply(goria); + ParCommandBuffer::::apply(goria); + ParCommandBuffer::::apply(goria); + ParCommandBuffer::::apply(goria); let elapsed = start.elapsed(); diff --git a/egregoria/src/world.rs b/egregoria/src/world.rs new file mode 100644 index 00000000..a97ec291 --- /dev/null +++ b/egregoria/src/world.rs @@ -0,0 +1,431 @@ +use crate::economy::{Bought, Market, Sold, Workers}; +use crate::map_dynamic::{ + DispatchID, Dispatcher, Itinerary, ItineraryFollower, ItineraryLeader, ParkingManagement, + Router, +}; +use crate::physics::{Collider, CollisionWorld, Speed}; +use crate::souls::desire::{BuyFood, Home, Work}; +use crate::souls::freight_station::FreightStation; +use crate::souls::goods_company::GoodsCompany; +use crate::souls::human::HumanDecision; +use crate::transportation::train::{Locomotive, LocomotiveReservation, RailWagon}; +use crate::transportation::{Location, Pedestrian, Vehicle, VehicleKind, VehicleState}; +use crate::utils::par_command_buffer::GoriaDrop; +use crate::utils::resources::Resources; +use crate::{impl_entity, impl_trans, SoulID}; +use derive_more::{From, TryInto}; +use geom::{Transform, Vec2, Vec3}; +use serde::Deserialize; +use slotmap::__impl::Serialize; +use slotmap::{new_key_type, SlotMap}; + +new_key_type! { + pub struct VehicleID; + pub struct TrainID; + pub struct HumanID; + pub struct WagonID; + pub struct FreightStationID; + pub struct CompanyID; +} + +impl_entity!(VehicleID, VehicleEnt, vehicles); +impl_entity!(HumanID, HumanEnt, humans); +impl_entity!(TrainID, TrainEnt, trains); +impl_entity!(WagonID, WagonEnt, wagons); +impl_entity!(FreightStationID, FreightStationEnt, freight_stations); +impl_entity!(CompanyID, CompanyEnt, companies); + +impl_trans!(HumanID); +impl_trans!(VehicleID); +impl_trans!(TrainID); +impl_trans!(WagonID); +impl_trans!(FreightStationID); +impl_trans!(CompanyID); + +#[derive(PartialEq, Eq, Copy, Clone, Debug, From, TryInto)] +pub enum AnyEntity { + VehicleID(VehicleID), + TrainID(TrainID), + WagonID(WagonID), + FreightStationID(FreightStationID), + CompanyID(CompanyID), + HumanID(HumanID), +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct VehicleEnt { + pub trans: Transform, + pub speed: Speed, + pub vehicle: Vehicle, + pub it: Itinerary, + pub collider: Option, +} + +impl GoriaDrop for VehicleEnt { + fn goria_drop(mut self, id: VehicleID, res: &mut Resources) { + if let Some(collider) = self.collider { + res.get_mut::() + .unwrap() + .remove_maintain(collider.0); + } + + if let VehicleState::Parked(resa) | VehicleState::RoadToPark(_, _, resa) = + std::mem::replace(&mut self.vehicle.state, VehicleState::Driving) + { + res.get_mut::().unwrap().free(resa); + } + + if matches!(self.vehicle.kind, VehicleKind::Truck) { + res.get_mut::() + .unwrap() + .unregister(DispatchID::SmallTruck(id)) + } + } +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct HumanEnt { + pub trans: Transform, + pub location: Location, + pub pedestrian: Pedestrian, + pub it: Itinerary, + pub speed: Speed, + pub collider: Option, + + pub decision: HumanDecision, + pub home: Home, + pub food: BuyFood, + pub bought: Bought, + pub router: Router, + pub work: Option, +} + +impl GoriaDrop for HumanEnt { + fn goria_drop(mut self, id: HumanID, res: &mut Resources) { + if let Some(collider) = self.collider { + res.get_mut::() + .unwrap() + .remove_maintain(collider.0); + } + + res.get_mut::().unwrap().remove(SoulID::Human(id)); + + self.router + .clear_steps(&mut res.get_mut::().unwrap()) + } +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct TrainEnt { + pub trans: Transform, + pub speed: Speed, + pub it: Itinerary, + pub locomotive: Locomotive, + pub res: LocomotiveReservation, + #[inspect(skip)] + pub leader: ItineraryLeader, +} + +impl GoriaDrop for TrainEnt { + fn goria_drop(self, id: TrainID, res: &mut Resources) { + res.get_mut::() + .unwrap() + .unregister(DispatchID::FreightTrain(id)); + } +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct WagonEnt { + pub trans: Transform, + pub speed: Speed, + pub wagon: RailWagon, + pub itfollower: ItineraryFollower, +} + +impl GoriaDrop for WagonEnt { + fn goria_drop(self, _: WagonID, _: &mut Resources) {} +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct FreightStationEnt { + pub trans: Transform, + pub f: FreightStation, +} + +impl GoriaDrop for FreightStationEnt { + fn goria_drop(self, id: FreightStationID, res: &mut Resources) { + res.get_mut::() + .unwrap() + .remove(SoulID::FreightStation(id)); + + let mut d = res.get_mut::().unwrap(); + for (id, _) in self.f.trains { + d.free(id); + } + drop(d); + } +} + +#[derive(Inspect, Serialize, Deserialize)] +pub struct CompanyEnt { + pub trans: Transform, + pub comp: GoodsCompany, + pub workers: Workers, + pub sold: Sold, + pub bought: Bought, +} + +impl GoriaDrop for CompanyEnt { + fn goria_drop(self, id: CompanyID, res: &mut Resources) { + res.get_mut::() + .unwrap() + .remove(SoulID::GoodsCompany(id)); + } +} + +#[derive(Default, Serialize, Deserialize)] +pub struct World { + pub vehicles: SlotMap, + pub humans: SlotMap, + pub trains: SlotMap, + pub wagons: SlotMap, + pub freight_stations: SlotMap, + pub companies: SlotMap, +} + +impl World { + pub fn get(&self, id: E) -> Option<&E::Entity> { + <::Entity as Entity>::storage(self).get(id) + } + + pub fn storage(&self) -> &SlotMap { + E::storage(self) + } + + pub fn storage_id(&self, _: E) -> &SlotMap { + E::Entity::storage(self) + } + + pub fn insert(&mut self, e: E) -> E::ID { + E::storage_mut(self).insert(e) + } + + pub fn contains(&self, id: AnyEntity) -> bool { + match id { + AnyEntity::VehicleID(id) => self.storage_id(id).contains_key(id), + AnyEntity::TrainID(id) => self.storage_id(id).contains_key(id), + AnyEntity::WagonID(id) => self.storage_id(id).contains_key(id), + AnyEntity::FreightStationID(id) => self.storage_id(id).contains_key(id), + AnyEntity::CompanyID(id) => self.storage_id(id).contains_key(id), + AnyEntity::HumanID(id) => self.storage_id(id).contains_key(id), + } + } + + pub fn pos_any(&self, id: AnyEntity) -> Option { + match id { + AnyEntity::VehicleID(x) => self.pos(x), + AnyEntity::TrainID(x) => self.pos(x), + AnyEntity::WagonID(x) => self.pos(x), + AnyEntity::HumanID(x) => self.pos(x), + _ => None, + } + } + + pub fn it_any(&self, id: AnyEntity) -> Option<&Itinerary> { + match id { + AnyEntity::VehicleID(x) => Some(&self.get(x)?.it), + AnyEntity::TrainID(x) => Some(&self.get(x)?.it), + AnyEntity::HumanID(x) => Some(&self.get(x)?.it), + _ => None, + } + } + + pub fn pos(&self, id: E) -> Option { + self.get(id).map(|x| E::trans(x).position) + } + + pub fn trans(&self, id: E) -> Option { + self.get(id).map(|x| E::trans(x)) + } + + #[rustfmt::skip] + pub fn query_trans_itin(&self) -> impl Iterator + '_ { + chain(( + self.humans .iter().map(|(id, x)| (AnyEntity::HumanID(id), (&x.trans, &x.it))), + self.vehicles.iter().map(|(id, x)| (AnyEntity::VehicleID(id), (&x.trans, &x.it))), + self.trains .iter().map(|(id, x)| (AnyEntity::TrainID(id), (&x.trans, &x.it))), + )) + } + + #[rustfmt::skip] + pub fn query_selectable_pos(&self) -> impl Iterator + '_ { + chain(( + self.humans .iter().map(|(id, x)| (AnyEntity::HumanID(id), x.trans.position.xy())), + self.vehicles.iter().map(|(id, x)| (AnyEntity::VehicleID(id), x.trans.position.xy())), + self.trains .iter().map(|(id, x)| (AnyEntity::TrainID(id), x.trans.position.xy())), + self.wagons .iter().map(|(id, x)| (AnyEntity::WagonID(id), x.trans.position.xy())), + )) + } + + #[rustfmt::skip] + pub fn query_it_trans_speed( + &mut self, + ) -> impl Iterator + '_ { + chain(( + self.humans .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), + self.trains .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), + self.vehicles.values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), + )) + } + + + #[rustfmt::skip] + pub fn query_trans_speed_coll_vehicle( + &self, + ) -> impl Iterator)> { + chain(( + self.vehicles.values().filter_map(|x| { x.collider.map(|coll| (&x.trans, &x.speed, coll, Some(&x.vehicle))) }), + self.humans .values().filter_map(|x| { x.collider.map(|coll| (&x.trans, &x.speed, coll, None)) }), + )) + } + + pub fn entities(&self) -> impl Iterator + '_ { + chain(( + chain(( + self.humans.keys().map(AnyEntity::HumanID), + self.vehicles.keys().map(AnyEntity::VehicleID), + self.trains.keys().map(AnyEntity::TrainID), + self.wagons.keys().map(AnyEntity::WagonID), + )), + chain(( + self.freight_stations + .keys() + .map(AnyEntity::FreightStationID), + self.companies.keys().map(AnyEntity::CompanyID), + )), + )) + } +} + +/// A trait that describes an entity, therefore having storage within the world +pub trait Entity: 'static + Sized + Send { + type ID: EntityID; + + fn storage(w: &World) -> &SlotMap; + fn storage_mut(w: &mut World) -> &mut SlotMap; +} + +/// A trait that describes an entity id to be able to find an Entity from an ID +pub trait EntityID: 'static + slotmap::Key + Send { + type Entity: Entity; +} + +/// A trait that describes an entity having a position within the world +pub trait WorldTransform: EntityID { + fn trans(obj: &Self::Entity) -> Transform; +} + +mod macros { + #[macro_export] + macro_rules! impl_trans { + ($t:ty) => { + impl WorldTransform for $t { + fn trans(obj: &Self::Entity) -> Transform { + obj.trans + } + } + }; + } + + #[macro_export] + macro_rules! impl_entity { + ($id:ty, $obj:ty, $s:ident) => { + debug_inspect_impl!($id); + + impl Entity for $obj { + type ID = $id; + + fn storage(w: &World) -> &SlotMap { + &w.$s + } + + fn storage_mut(w: &mut World) -> &mut SlotMap { + &mut w.$s + } + } + + impl EntityID for $id { + type Entity = $obj; + } + }; + } +} + +fn chain(t: T) -> T::Iter { + t.chain() +} + +trait TupleITChain { + type Item; + type Iter: Iterator; + + fn chain(self) -> Self::Iter; +} + +impl, B: Iterator> TupleITChain for (A, B) { + type Item = Item; + type Iter = std::iter::Chain; + + fn chain(self) -> Self::Iter { + self.0.chain(self.1) + } +} + +impl, B: Iterator, C: Iterator> + TupleITChain for (A, B, C) +{ + type Item = Item; + type Iter = std::iter::Chain, C>; + + fn chain(self) -> Self::Iter { + self.0.chain(self.1).chain(self.2) + } +} + +impl< + Item, + A: Iterator, + B: Iterator, + C: Iterator, + D: Iterator, + > TupleITChain for (A, B, C, D) +{ + type Item = Item; + type Iter = std::iter::Chain, C>, D>; + + fn chain(self) -> Self::Iter { + self.0.chain(self.1).chain(self.2).chain(self.3) + } +} + +impl< + Item, + A: Iterator, + B: Iterator, + C: Iterator, + D: Iterator, + E: Iterator, + > TupleITChain for (A, B, C, D, E) +{ + type Item = Item; + type Iter = + std::iter::Chain, C>, D>, E>; + + fn chain(self) -> Self::Iter { + self.0 + .chain(self.1) + .chain(self.2) + .chain(self.3) + .chain(self.4) + } +} diff --git a/native_app/Cargo.toml b/native_app/Cargo.toml index 518bdbbe..cf122e25 100644 --- a/native_app/Cargo.toml +++ b/native_app/Cargo.toml @@ -14,7 +14,6 @@ networking = { path = "../networking", optional=true } flat_spatial = { workspace = true } log = { version = "0.4.11", features=["max_level_info", "release_max_level_info"] } inline_tweak = "1.0.6" -hecs = { workspace = true } ordered-float = { workspace = true } serde = "1.0" oddio = "0.6.2" diff --git a/native_app/src/game_loop.rs b/native_app/src/game_loop.rs index dd8aca13..1c9b14ed 100644 --- a/native_app/src/game_loop.rs +++ b/native_app/src/game_loop.rs @@ -299,7 +299,7 @@ impl State { } if let Some(e) = self.uiw.read::().0 { - if let Some(pos) = self.goria.read().unwrap().pos(e) { + if let Some(pos) = self.goria.read().unwrap().pos_any(e) { self.camera.follow(pos); } } diff --git a/native_app/src/gui/follow.rs b/native_app/src/gui/follow.rs index 2a514a53..d1d9a30b 100644 --- a/native_app/src/gui/follow.rs +++ b/native_app/src/gui/follow.rs @@ -1,5 +1,22 @@ -use hecs::Entity; +use egregoria::AnyEntity; +use egui::Ui; /// FollowEntity is a component that tells the camera to follow an entity -#[derive(Copy, Clone, Default)] -pub(crate) struct FollowEntity(pub(crate) Option); +/// Entity is defined by a function that returns the position of the entity +#[derive(Default)] +pub struct FollowEntity(pub(crate) Option); + +impl FollowEntity { + pub fn update(&mut self, ui: &mut Ui, entity: AnyEntity) { + if self.0.is_none() { + if ui.small_button("Follow").clicked() { + self.0.replace(entity); + } + return; + } + + if ui.small_button("Unfollow").clicked() { + self.0.take(); + } + } +} diff --git a/native_app/src/gui/inspect.rs b/native_app/src/gui/inspect.rs index 54a4be4b..36cc69fb 100644 --- a/native_app/src/gui/inspect.rs +++ b/native_app/src/gui/inspect.rs @@ -1,102 +1,70 @@ use crate::gui::follow::FollowEntity; use crate::uiworld::UiWorld; -use egregoria::economy::{ItemRegistry, Market, Workers}; -use egregoria::map_dynamic::{DispatchKind, Itinerary, Router}; -use egregoria::physics::{Collider, CollisionWorld, PhysicsObject, Speed}; -use egregoria::souls::desire::{BuyFood, Home, Work}; -use egregoria::souls::goods_company::GoodsCompany; -use egregoria::souls::human::HumanDecision; -use egregoria::transportation::{Location, Pedestrian, Vehicle, VehicleID}; -use egregoria::{Egregoria, SoulID}; - -use egregoria::souls::freight_station::FreightStation; -use egregoria::transportation::train::{Locomotive, LocomotiveReservation}; -use egui::{Color32, RichText, Ui}; +use egregoria::economy::{ItemRegistry, Market}; +use egregoria::transportation::Location; +use egregoria::{ + AnyEntity, CompanyEnt, Egregoria, FreightStationEnt, HumanEnt, SoulID, TrainEnt, VehicleEnt, + WagonEnt, +}; +use egui::Ui; use egui_inspect::{Inspect, InspectArgs}; -use geom::{Transform, Vec2}; -use hecs::{Component, Entity}; /// Inspect window /// Allows to inspect an entity pub(crate) struct InspectRenderer { - pub(crate) entity: Entity, + pub(crate) entity: AnyEntity, } impl InspectRenderer { - fn inspect_component>(&self, goria: &Egregoria, ui: &mut Ui) { - let c = goria.comp::(self.entity); - if let Some(x) = c { - >::render( - &x, - std::any::type_name::().split("::").last().unwrap_or(""), - ui, - &InspectArgs::default(), - ) - } - } - - fn inspect_transform(&self, goria: &Egregoria, uiw: &mut UiWorld, ui: &mut Ui) { - let c = goria.comp(self.entity); - if let Some(x) = c { - let mut t = *x; - if >::render_mut( - &mut t, - "Transform", - ui, - &InspectArgs::default(), - ) { - uiw.commands().update_transform(self.entity, t); - } - } - } - pub(crate) fn render(&mut self, uiworld: &mut UiWorld, goria: &Egregoria, ui: &mut Ui) { - let mut custom_ent = self.entity.id() as i32; + let entity = self.entity; + ui.label(format!("{:?}", self.entity)); - ui.horizontal(|ui| { - if ui.add(egui::DragValue::new(&mut custom_ent)).changed() { - if let Some(ent) = Entity::from_bits(1 << 32 | custom_ent as u64) { - if goria.world().contains(ent) { - self.entity = ent; - } - } - } - ui.label("Entity ID"); - }); + let args = InspectArgs { + indent_children: Some(false), + ..Default::default() + }; - ui.label(format!("{:?}", self.entity)); - self.inspect_transform(goria, uiworld, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); - self.inspect_component::(goria, ui); + match entity { + AnyEntity::VehicleID(x) => { + >::render(goria.get(x).unwrap(), "", ui, &args) + } + AnyEntity::TrainID(x) => { + >::render(goria.get(x).unwrap(), "", ui, &args) + } + AnyEntity::WagonID(x) => { + >::render(goria.get(x).unwrap(), "", ui, &args) + } + AnyEntity::FreightStationID(x) => { + >::render( + goria.get(x).unwrap(), + "", + ui, + &args, + ) + } + AnyEntity::CompanyID(x) => { + >::render(goria.get(x).unwrap(), "", ui, &args) + } + AnyEntity::HumanID(x) => { + >::render(goria.get(x).unwrap(), "", ui, &args) + } + } - if goria.comp::(self.entity).is_some() { - for (e, loc) in goria.world().query::<&Location>().iter() { - let loc: &Location = loc; - if loc == &Location::Vehicle(VehicleID(self.entity)) + if let AnyEntity::VehicleID(id) = entity { + for (hid, h) in goria.world().humans.iter() { + if h.location == Location::Vehicle(id) && ui - .small_button(&*format!("inspect inside vehicle: {e:?}")) + .small_button(&*format!("inspect inside vehicle: {hid:?}")) .clicked() { - self.entity = e; + self.entity = hid.into(); return; } } } + /* if let Some(coll) = goria.comp::(self.entity) { if let Some((pos, po)) = goria.read::().get(coll.0) { egui::CollapsingHeader::new("Physics Object").show(ui, |ui| { @@ -120,66 +88,62 @@ impl InspectRenderer { .color(Color32::from_rgba_unmultiplied(255, 0, 0, 255)), ); } - } + }*/ - if goria.comp::(self.entity).is_some() { - let follow = &mut uiworld.write::().0; - if follow.is_none() { - if ui.small_button("Follow").clicked() { - follow.replace(self.entity); - } - } else if ui.small_button("Unfollow").clicked() { - follow.take(); - } + { + let mut follow = uiworld.write::(); + follow.update(ui, entity); } - let market = goria.read::(); - let registry = goria.read::(); - let mut capitals = vec![]; - let mut borders = vec![]; - let mut sellorders = vec![]; - for (kind, market) in market.inner() { - let cap = unwrap_or!(market.capital(SoulID(self.entity)), continue); - capitals.push((kind, cap)); - if let Some(b) = market.buy_order(SoulID(self.entity)) { - borders.push((kind, b)); - } - if let Some(s) = market.sell_order(SoulID(self.entity)) { - sellorders.push((kind, s)); + if let Ok(soul) = SoulID::try_from(entity) { + let market = goria.read::(); + let registry = goria.read::(); + let mut capitals = vec![]; + let mut borders = vec![]; + let mut sellorders = vec![]; + for (kind, market) in market.inner() { + let cap = unwrap_or!(market.capital(soul), continue); + capitals.push((kind, cap)); + if let Some(b) = market.buy_order(soul) { + borders.push((kind, b)); + } + if let Some(s) = market.sell_order(soul) { + sellorders.push((kind, s)); + } } - } - if !capitals.is_empty() { - egui::CollapsingHeader::new("Capital").show(ui, |ui| { - ui.columns(2, |ui| { - for (kind, cap) in capitals { - ui[0].label(®istry[*kind].label); - ui[1].label(format!("{cap}")); - } + if !capitals.is_empty() { + egui::CollapsingHeader::new("Capital").show(ui, |ui| { + ui.columns(2, |ui| { + for (kind, cap) in capitals { + ui[0].label(®istry[*kind].label); + ui[1].label(format!("{cap}")); + } + }); }); - }); - } + } - if !borders.is_empty() { - egui::CollapsingHeader::new("Buy orders").show(ui, |ui| { - ui.columns(2, |ui| { - for (kind, b) in borders { - ui[0].label(®istry[*kind].label); - ui[1].label(format!("{b:#?}")); - } + if !borders.is_empty() { + egui::CollapsingHeader::new("Buy orders").show(ui, |ui| { + ui.columns(2, |ui| { + for (kind, b) in borders { + ui[0].label(®istry[*kind].label); + ui[1].label(format!("{b:#?}")); + } + }); }); - }); - } + } - if !sellorders.is_empty() { - egui::CollapsingHeader::new("Sell orders").show(ui, |ui| { - ui.columns(2, |ui| { - for (kind, b) in sellorders { - ui[0].label(®istry[*kind].label); - ui[1].label(format!("{b:#?}")); - } + if !sellorders.is_empty() { + egui::CollapsingHeader::new("Sell orders").show(ui, |ui| { + ui.columns(2, |ui| { + for (kind, b) in sellorders { + ui[0].label(®istry[*kind].label); + ui[1].label(format!("{b:#?}")); + } + }); }); - }); + } } } } diff --git a/native_app/src/gui/inspect_building.rs b/native_app/src/gui/inspect_building.rs index db9a9996..6bd9d9a8 100644 --- a/native_app/src/gui/inspect_building.rs +++ b/native_app/src/gui/inspect_building.rs @@ -1,14 +1,14 @@ use crate::uiworld::UiWorld; -use egregoria::economy::{ItemRegistry, Market, Workers}; +use egregoria::economy::{ItemRegistry, Market}; use egregoria::engine_interaction::WorldCommand; -use egregoria::Egregoria; +use egregoria::{Egregoria, SoulID}; use egui::{Context, Ui, Widget}; use crate::gui::{item_icon, InspectedEntity}; use egregoria::map::{Building, BuildingID, BuildingKind, Zone, MAX_ZONE_AREA}; use egregoria::map_dynamic::BuildingInfos; -use egregoria::souls::freight_station::{FreightStation, FreightTrainState}; -use egregoria::souls::goods_company::{GoodsCompany, GoodsCompanyRegistry, Recipe}; +use egregoria::souls::freight_station::FreightTrainState; +use egregoria::souls::goods_company::{GoodsCompanyRegistry, Recipe}; use egui_inspect::{Inspect, InspectArgs, InspectVec2Rotation}; /// Inspect a specific building, showing useful information about it @@ -82,28 +82,27 @@ fn render_house(ui: &mut Ui, uiworld: &mut UiWorld, goria: &Egregoria, b: &Build let mut inspected = uiworld.write::(); if ui.button(format!("Owner: {owner:?}")).clicked() { - inspected.e = Some(owner.0); + inspected.e = Some(owner.into()); } ui.label("Currently in the house:"); for &soul in info.inside.iter() { if ui.button(format!("{soul:?}")).clicked() { - inspected.e = Some(soul.0); + inspected.e = Some(soul.into()); } } } fn render_freightstation(ui: &mut Ui, _uiworld: &mut UiWorld, goria: &Egregoria, b: &Building) { - let Some(owner) = goria.read::().owner(b.id) else { return; }; + let Some(SoulID::FreightStation(owner)) = goria.read::().owner(b.id) else { return; }; + let Some(freight) = goria.world().get(owner) else { return; }; - let Some(freight) = goria.comp::(owner.0) else { return; }; - - ui.label(format!("Waiting cargo: {}", freight.waiting_cargo)); - ui.label(format!("Wanted cargo: {}", freight.wanted_cargo)); + ui.label(format!("Waiting cargo: {}", freight.f.waiting_cargo)); + ui.label(format!("Wanted cargo: {}", freight.f.wanted_cargo)); ui.add_space(10.0); ui.label("Trains:"); - for (tid, state) in &freight.trains { + for (tid, state) in &freight.f.trains { ui.horizontal(|ui| { ui.label(format!("{tid:?} ")); match state { @@ -124,9 +123,10 @@ fn render_freightstation(ui: &mut Ui, _uiworld: &mut UiWorld, goria: &Egregoria, fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, goria: &Egregoria, b: &Building) { let owner = goria.read::().owner(b.id); - let Some(soul) = owner else { return; }; - let Some(goods) = goria.comp::(soul.0) else { return; }; - let Some(workers) = goria.comp::(soul.0) else { return; }; + let Some(SoulID::GoodsCompany(c_id)) = owner else { return; }; + let Some(c) = goria.world().companies.get(c_id) else { return; }; + let goods = &c.comp; + let workers = &c.workers; let market = goria.read::(); let itemregistry = goria.read::(); @@ -156,7 +156,7 @@ fn render_goodscompany(ui: &mut Ui, uiworld: &mut UiWorld, goria: &Egregoria, b: let jobopening = itemregistry.id("job-opening"); for (&id, m) in market.iter() { - let Some(v) = m.capital(soul) else { continue }; + let Some(v) = m.capital(c_id.into()) else { continue }; if id == jobopening && v == 0 { continue; } diff --git a/native_app/src/gui/inspected_aura.rs b/native_app/src/gui/inspected_aura.rs index b799573d..ac2b1351 100644 --- a/native_app/src/gui/inspected_aura.rs +++ b/native_app/src/gui/inspected_aura.rs @@ -1,9 +1,9 @@ +use crate::gui::selectable::select_radius; use crate::gui::{InspectedBuilding, InspectedEntity}; use crate::rendering::immediate::ImmediateDraw; use crate::uiworld::UiWorld; -use egregoria::engine_interaction::Selectable; use egregoria::transportation::Location; -use egregoria::Egregoria; +use egregoria::{AnyEntity, Egregoria}; use geom::Color; /// InspectedAura shows the circle around the inspected entity @@ -15,23 +15,24 @@ pub(crate) fn inspected_aura(goria: &Egregoria, uiworld: &mut UiWorld) { let mut draw = uiworld.write::(); if let Some(sel) = inspected.e { - let mut pos = goria.pos(sel); + let mut pos = goria.pos_any(sel); - if let Some(loc) = goria.comp::(sel) { + if let AnyEntity::HumanID(id) = sel { + let loc = &goria.world().get(id).unwrap().location; match *loc { Location::Outside => {} - Location::Vehicle(v) => pos = goria.pos(v.0), + Location::Vehicle(v) => pos = goria.pos(v), Location::Building(b) => pos = map.buildings().get(b).map(|b| b.door_pos), } } - if let Some((pos, selectable)) = pos.zip(goria.comp::(sel)) { - draw.stroke_circle( - pos.up(0.25), - selectable.radius, - (selectable.radius * 0.01).max(0.1), - ) - .color(Color::gray(0.7)); + if let Some(pos) = pos { + let select_radius = select_radius(sel); + + if select_radius > 0.0 { + draw.stroke_circle(pos.up(0.25), select_radius, (select_radius * 0.01).max(0.1)) + .color(Color::gray(0.7)); + } } } diff --git a/native_app/src/gui/mod.rs b/native_app/src/gui/mod.rs index 0d128835..f68f4705 100644 --- a/native_app/src/gui/mod.rs +++ b/native_app/src/gui/mod.rs @@ -1,5 +1,4 @@ use common::FastMap; -use hecs::Entity; use std::borrow::Cow; use std::path::Path; @@ -9,7 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::uiworld::UiWorld; use egregoria::engine_interaction::WorldCommand; use egregoria::map::BuildingID; -use egregoria::Egregoria; +use egregoria::{AnyEntity, Egregoria}; use roadbuild::RoadBuildResource; pub(crate) mod bulldozer; @@ -89,7 +88,7 @@ pub(crate) struct InspectedBuilding { #[derive(Copy, Clone, Debug)] pub(crate) struct InspectedEntity { - pub(crate) e: Option, + pub(crate) e: Option, pub(crate) dist2: f32, pub(crate) dontclear: bool, } diff --git a/native_app/src/gui/selectable.rs b/native_app/src/gui/selectable.rs index 14941f55..948e2979 100644 --- a/native_app/src/gui/selectable.rs +++ b/native_app/src/gui/selectable.rs @@ -1,11 +1,20 @@ use crate::gui::{InspectedBuilding, InspectedEntity, Tool}; use crate::inputmap::{InputAction, InputMap}; use crate::uiworld::UiWorld; -use egregoria::engine_interaction::Selectable; use egregoria::map::ProjectFilter; -use egregoria::Egregoria; -use geom::Transform; -use std::sync::Mutex; +use egregoria::{AnyEntity, Egregoria}; +use geom::Vec2; + +pub fn select_radius(id: AnyEntity) -> f32 { + match id { + AnyEntity::VehicleID(_) => 5.0, + AnyEntity::TrainID(_) => 10.0, + AnyEntity::WagonID(_) => 10.0, + AnyEntity::FreightStationID(_) => 0.0, + AnyEntity::CompanyID(_) => 0.0, + AnyEntity::HumanID(_) => 3.0, + } +} /// Selectable allows to select entities by clicking on them #[profiling::function] @@ -19,34 +28,23 @@ pub(crate) fn selectable(goria: &Egregoria, uiworld: &mut UiWorld) { && matches!(*tool, Tool::Hand) && !inspected.dontclear { - let mut inspectcpy = *inspected; - inspectcpy.dist2 = f32::INFINITY; - let protec = Mutex::new(inspectcpy); let unproj = unwrap_ret!(inp.unprojected); - goria - .world() - .query::<(&Transform, &Selectable)>() - .iter_batched(16) - //.par_bridge() - .for_each(|chunk| { - let mut v = f32::INFINITY; - let mut ent = None; - for (e, (trans, select)) in chunk { - let dist2 = (trans.position.xy() - unproj.xy()).mag2(); - if dist2 >= select.radius * select.radius || dist2 >= v { - continue; - } - v = dist2; - ent = Some(e); - } - let mut inspected = protec.lock().unwrap(); - if inspected.dist2 >= v { - inspected.e = ent; - inspected.dist2 = v; + let w = goria.world(); + + inspected.dist2 = f32::INFINITY; + inspected.e = None; + + w.query_selectable_pos() + .for_each(|(id, pos): (AnyEntity, Vec2)| { + let dist2 = (pos - unproj.xy()).mag2(); + let rad = select_radius(id); + if dist2 >= rad * rad || dist2 >= inspected.dist2 { + return; } + inspected.dist2 = dist2; + inspected.e = Some(id); }); - *inspected = protec.into_inner().unwrap(); } if inp.just_act.contains(&InputAction::Select) diff --git a/native_app/src/gui/windows/debug.rs b/native_app/src/gui/windows/debug.rs index 712146fb..af48915c 100644 --- a/native_app/src/gui/windows/debug.rs +++ b/native_app/src/gui/windows/debug.rs @@ -4,15 +4,14 @@ use crate::game_loop::Timings; use crate::gui::InspectedEntity; use crate::network::NetworkState; use crate::uiworld::UiWorld; -use egregoria::map_dynamic::{Itinerary, ParkingManagement}; +use egregoria::map_dynamic::ParkingManagement; use egregoria::physics::CollisionWorld; use egregoria::utils::time::{GameTime, Tick, SECONDS_PER_DAY}; -use egregoria::Egregoria; +use egregoria::{Egregoria, TrainID}; use crate::inputmap::InputMap; use egregoria::map::{IntersectionID, Map, RoadSegmentKind, TraverseKind}; use egregoria::transportation::train::TrainReservations; -use egregoria::transportation::{Pedestrian, Vehicle}; use egui::Widget; use geom::{Camera, Color, LinearColor, Spline3, Vec2}; use wgpu_engine::Tesselator; @@ -170,14 +169,8 @@ pub(crate) fn debug( uiworld.commands().reset_save(); } - ui.label(format!( - "{} pedestrians", - goria.world().query::<&Pedestrian>().iter().count() - )); - ui.label(format!( - "{} vehicles", - goria.world().query::<&Vehicle>().iter().count() - )); + ui.label(format!("{} pedestrians", goria.world().humans.len())); + ui.label(format!("{} vehicles", goria.world().vehicles.len())); ui.separator(); ui.label("Game system times"); @@ -449,48 +442,36 @@ pub(crate) fn debug_trainreservations( } let selected = uiworld.read::().e?; - for (me, (itin, kin, loco, locores)) in goria - .world() - .query::<( - &Itinerary, - &egregoria::physics::Speed, - &egregoria::transportation::train::Locomotive, - &egregoria::transportation::train::LocomotiveReservation, - )>() - .iter() - { - if me != selected { - continue; - } - if let Some(travers) = itin.get_travers() { - let dist_to_next = travers - .kind - .length(map.lanes(), map.intersections()) - .unwrap_or(0.0) - - locores.cur_travers_dist; - - let stop_dist = kin.speed * kin.speed / (2.0 * loco.dec_force); - for (v, _, _, _) in egregoria::transportation::train::traverse_forward( - &map, - itin, - stop_dist + 15.0, - dist_to_next, - loco.length + 50.0, - ) { - match v { - TraverseKind::Lane(_) => {} - TraverseKind::Turn(t) => { - if map - .intersections() - .get(t.parent) - .map(|i| i.roads.len() <= 2) - .unwrap_or(true) - { - continue; - } - tess.draw_circle(map.intersections().get(t.parent)?.pos.up(3.0), 3.5); - } + let t_id: TrainID = selected.try_into().ok()?; + let t = goria.world().trains.get(t_id)?; + + let travers = t.it.get_travers()?; + let dist_to_next = travers + .kind + .length(map.lanes(), map.intersections()) + .unwrap_or(0.0) + - t.res.cur_travers_dist; + + let stop_dist = t.speed.0 * t.speed.0 / (2.0 * t.locomotive.dec_force); + for (v, _, _, _) in egregoria::transportation::train::traverse_forward( + &map, + &t.it, + stop_dist + 15.0, + dist_to_next, + t.locomotive.length + 50.0, + ) { + match v { + TraverseKind::Lane(_) => {} + TraverseKind::Turn(t) => { + if map + .intersections() + .get(t.parent) + .map(|i| i.roads.len() <= 2) + .unwrap_or(true) + { + continue; } + tess.draw_circle(map.intersections().get(t.parent)?.pos.up(3.0), 3.5); } } } @@ -505,9 +486,9 @@ pub(crate) fn debug_pathfinder( ) -> Option<()> { let map: &Map = &goria.map(); let selected = uiworld.read::().e?; - let pos = goria.pos(selected)?; + let pos = goria.pos_any(selected)?; - let itinerary = goria.comp::(selected)?; + let itinerary = goria.world().it_any(selected)?; tess.set_color(LinearColor::GREEN); tess.draw_polyline( diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index 4cb1fc9f..6f4a7fc5 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -1,8 +1,7 @@ -use egregoria::map_dynamic::Itinerary; -use egregoria::transportation::train::{RailWagon, RailWagonKind}; -use egregoria::transportation::{Location, Pedestrian, Vehicle, VehicleKind}; +use egregoria::transportation::train::RailWagonKind; +use egregoria::transportation::{Location, VehicleKind}; use egregoria::Egregoria; -use geom::{LinearColor, Transform, Vec3, V3}; +use geom::{LinearColor, Vec3, V3}; use wgpu_engine::meshload::load_mesh; use wgpu_engine::{ FrameContext, GfxContext, InstancedMeshBuilder, MeshInstance, SpriteBatchBuilder, @@ -43,14 +42,15 @@ impl InstancedRender { self.cars.instances.clear(); self.trucks.instances.clear(); self.pedestrians.instances.clear(); - for (_, (trans, v)) in goria.world().query::<(&Transform, &Vehicle)>().iter() { + for v in goria.world().vehicles.values() { + let trans = &v.trans; let instance = MeshInstance { pos: trans.position, dir: trans.dir, - tint: v.tint.into(), + tint: v.vehicle.tint.into(), }; - match v.kind { + match v.vehicle.kind { VehicleKind::Car => self.cars.instances.push(instance), VehicleKind::Truck => self.trucks.instances.push(instance), _ => {} @@ -60,14 +60,15 @@ impl InstancedRender { self.locomotives.instances.clear(); self.wagons_passenger.instances.clear(); self.wagons_freight.instances.clear(); - for (_, (trans, wagon)) in goria.world().query::<(&Transform, &RailWagon)>().iter() { + for wagon in goria.world().wagons.values() { + let trans = &wagon.trans; let instance = MeshInstance { pos: trans.position, dir: trans.dir, tint: LinearColor::WHITE, }; - match wagon.kind { + match wagon.wagon.kind { RailWagonKind::Passenger => { self.wagons_passenger.instances.push(instance); } @@ -80,40 +81,36 @@ impl InstancedRender { } } - for (_, (trans, ped, loc)) in goria - .world() - .query::<(&Transform, &Pedestrian, &Location)>() - .iter() - { - let ped: &Pedestrian = ped; - if matches!(loc, Location::Outside) { + for p in goria.world().humans.values() { + if matches!(p.location, Location::Outside) { self.pedestrians.instances.push(MeshInstance { - pos: trans.position.up(0.5 + 0.4 * ped.walk_anim.cos()), - dir: trans.dir.xy().z0(), + pos: p + .trans + .position + .up(0.5 + 0.4 * p.pedestrian.walk_anim.cos()), + dir: p.trans.dir.xy().z0(), tint: LinearColor::WHITE, }); } } self.path_not_found.clear(); - for (_, (trans, itin)) in goria.world().query::<(&Transform, &Itinerary)>().iter() { - let itin: &Itinerary = itin; - if let Some(wait) = itin.is_wait_for_reroute() { - if wait == 0 { - continue; - } + for (_, (trans, itin)) in goria.world().query_trans_itin() { + let Some(wait) = itin.is_wait_for_reroute() else { continue }; + if wait == 0 { + continue; + } - let r = wait as f32 / 200.0; - let off = 1.0 - r; + let r = wait as f32 / 200.0; + let off = 1.0 - r; - let s = 7.0; - self.path_not_found.push( - trans.position + off * 3.0 * V3::Y + 3.0 * V3::Z, - Vec3::X, - LinearColor::RED.a(r), - (s, s), - ); - } + let s = 7.0; + self.path_not_found.push( + trans.position + off * 3.0 * V3::Y + 3.0 * V3::Z, + Vec3::X, + LinearColor::RED.a(r), + (s, s), + ); } if let Some(x) = self.path_not_found.build(fctx.gfx) { diff --git a/native_app/src/uiworld.rs b/native_app/src/uiworld.rs index 61966cb5..e5d077f6 100644 --- a/native_app/src/uiworld.rs +++ b/native_app/src/uiworld.rs @@ -2,15 +2,12 @@ use crate::init::{INIT_FUNCS, SAVELOAD_FUNCS}; use egregoria::engine_interaction::{WorldCommand, WorldCommands}; use egregoria::utils::resources::{Ref, RefMut, Resources}; use egregoria::{Egregoria, EgregoriaReplayLoader}; -use hecs::{Component, DynamicBundle, QueryOne}; -use hecs::{Entity, World}; use std::any::Any; use std::sync::atomic::AtomicBool; use std::sync::Arc; #[derive(Default)] pub(crate) struct UiWorld { - pub(crate) world: World, resources: Resources, } @@ -43,20 +40,6 @@ impl UiWorld { self.read::() } - pub(crate) fn add_comp(&mut self, e: Entity, c: impl DynamicBundle) { - if self.world.insert(e, c).is_err() { - log::error!("trying to add component to entity but it doesn't exist"); - } - } - - pub(crate) fn comp(&self, e: Entity) -> Option> { - self.world.query_one::<&T>(e).ok() - } - - pub(crate) fn comp_mut(&mut self, e: Entity) -> Option<&mut T> { - self.world.query_one_mut::<&mut T>(e).ok() - } - pub(crate) fn try_write(&self) -> Option> { self.resources.get_mut().ok() } diff --git a/networking/src/client/mod.rs b/networking/src/client/mod.rs index 56f64354..ca4ea078 100644 --- a/networking/src/client/mod.rs +++ b/networking/src/client/mod.rs @@ -128,7 +128,7 @@ impl Client Result { let udp_sock = UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))) - .map_err(|e| ConnectionsError::UdpBind(e))?; + .map_err(ConnectionsError::UdpBind)?; let (udp_send_conn, udp_recv) = channel(); let (udp_send, udp_recv_conn) = channel(); - udp_sock - .connect(addr) - .map_err(|e| ConnectionsError::UdpBind(e))?; + udp_sock.connect(addr).map_err(ConnectionsError::UdpBind)?; - let tcp_stream = TcpStream::connect(addr).map_err(|e| ConnectionsError::TcpBind(e))?; + let tcp_stream = TcpStream::connect(addr).map_err(ConnectionsError::TcpBind)?; let _ = tcp_stream.set_nodelay(true); @@ -88,15 +86,14 @@ impl ConnectionClient { disconnected: Arc, ) { let udp_sock_cpy = udp_sock.try_clone().unwrap(); - std::thread::spawn(move || loop { - match udp_recv.recv() { - Ok(data) => match udp_sock_cpy.send(&data) { + std::thread::spawn(move || { + while let Ok(data) = udp_recv.recv() { + match udp_sock_cpy.send(&data) { Ok(_) => {} Err(e) => { log::error!("udp send error: {}", e); } - }, - Err(_) => break, + } } }); @@ -136,7 +133,7 @@ impl ConnectionClient { std::thread::spawn(move || loop { match tcp_recv.recv() { - Ok(data) if data.len() > 0 => { + Ok(data) if !data.is_empty() => { match tcp_stream.write_all(&(data.len() as u32).to_le_bytes()) { Ok(_) => {} Err(_) => { diff --git a/networking/src/connections.rs b/networking/src/connections.rs index 2079c3ea..120aa45a 100644 --- a/networking/src/connections.rs +++ b/networking/src/connections.rs @@ -24,6 +24,7 @@ pub(crate) struct FramedTcpReceiver { buf: Vec, } +#[allow(clippy::type_complexity)] pub struct Connections { udp_send: Sender, udp_recv: Receiver, @@ -40,13 +41,13 @@ pub enum ConnectionsError { impl Connections { pub fn new(addr: SocketAddr) -> Result { - let udp_sock = UdpSocket::bind(addr).map_err(|e| ConnectionsError::UdpBind(e))?; + let udp_sock = UdpSocket::bind(addr).map_err(ConnectionsError::UdpBind)?; let (udp_send_conn, udp_recv) = channel(); let (udp_send, udp_recv_conn) = channel(); let (tcp_conn_events_send, tcp_conn_events_recv) = channel(); - let tcp_listener = TcpListener::bind(addr).map_err(|e| ConnectionsError::TcpBind(e))?; + let tcp_listener = TcpListener::bind(addr).map_err(ConnectionsError::TcpBind)?; Self::start_process_threads( udp_sock, @@ -91,7 +92,7 @@ impl Connections { match event { TcpConnEvent::New { send, recv, addr } => { self.tcp_conns - .insert(addr.clone(), (FramedTcpReceiver::new(), recv, send)); + .insert(addr, (FramedTcpReceiver::new(), recv, send)); newconns.push(addr); } TcpConnEvent::Killed { addr } => { @@ -110,7 +111,7 @@ impl Connections { frame.recv(&v, |d| { packets.push(Packet { - addr: addr.clone(), + addr: *addr, data: d, }) }); @@ -126,15 +127,14 @@ impl Connections { tcp_conn_events_send: Sender, ) { let udp_sock_cpy = udp_sock.try_clone().unwrap(); - std::thread::spawn(move || loop { - match udp_recv.recv() { - Ok(Packet { addr, data }) => match udp_sock_cpy.send_to(&data, addr) { + std::thread::spawn(move || { + while let Ok(Packet { addr, data }) = udp_recv.recv() { + match udp_sock_cpy.send_to(&data, addr) { Ok(_) => {} Err(e) => { log::error!("udp send error: {}", e); } - }, - Err(_) => break, + } } }); @@ -183,7 +183,7 @@ impl Connections { std::thread::spawn(move || loop { match recv_from_tcp.recv() { - Ok(data) if data.len() > 0 => { + Ok(data) if !data.is_empty() => { match stream.write_all(&(data.len() as u32).to_le_bytes()) { Ok(_) => {} Err(_) => { diff --git a/networking/src/server/mod.rs b/networking/src/server/mod.rs index 19c886b9..753dd79b 100644 --- a/networking/src/server/mod.rs +++ b/networking/src/server/mod.rs @@ -192,10 +192,10 @@ impl Server { - self.worldsend.update(c, &mut self.net); + self.worldsend.update(c, &self.net); } ClientGameState::CatchingUp => { - self.catchup.update(c, &mut self.net); + self.catchup.update(c, &self.net); } _ => {} } @@ -219,7 +219,7 @@ impl Server { - self.authent.udp_connect(addr, id, &mut self.net); + self.authent.udp_connect(addr, id, &self.net); } } Some(()) @@ -285,7 +285,7 @@ impl Server