diff --git a/Cargo.lock b/Cargo.lock index 63248ec3..887d983f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2861,6 +2861,7 @@ dependencies = [ "flat_spatial", "geom", "inline_tweak", + "itertools", "lazy_static", "log", "ordered-float", diff --git a/common/src/iter.rs b/common/src/iter.rs new file mode 100644 index 00000000..03379090 --- /dev/null +++ b/common/src/iter.rs @@ -0,0 +1,68 @@ +pub fn chain(t: T) -> T::Iter { + t.chain() +} + +pub 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/common/src/lib.rs b/common/src/lib.rs index 54791958..e36aec8f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,6 +4,7 @@ mod chunkid; pub mod error; mod hash; pub mod history; +pub mod iter; pub mod logger; pub mod macros; pub mod rand; diff --git a/simulation/Cargo.toml b/simulation/Cargo.toml index c56c329b..d665eab5 100644 --- a/simulation/Cargo.toml +++ b/simulation/Cargo.toml @@ -10,21 +10,22 @@ edition = "2021" ordered-float = { workspace = true } serde = { version = "1.0", features = ["derive"] } log = "0.4.11" -egui-inspect = { path = "../egui-inspect"} -flat_spatial = { workspace = true, features=["serde"] } +egui-inspect = { path = "../egui-inspect"} +flat_spatial = { workspace = true, features=["serde"] } geom = { path = "../geom" } common = { path = "../common" } prototypes = { path = "../prototypes" } -slotmapd = { version = "1.0", default-features = false, features = ["serde", "unstable"] } +slotmapd = { version = "1.0", default-features = false, features = ["serde", "unstable"] } rayon = "1.6" -profiling = { version = "1.0.5", default-features = false } -inline_tweak = { version = "1.0.9", features = ["release_tweak"] } +profiling = { version = "1.0.5", default-features = false } +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 = { workspace = true } -bitflags = "2.4.1" +bitflags = "2.4.1" +itertools = "0.12.0" [dev-dependencies] easybench = "1.1.0" diff --git a/simulation/src/map/electricity.rs b/simulation/src/map/electricity.rs new file mode 100644 index 00000000..410718f2 --- /dev/null +++ b/simulation/src/map/electricity.rs @@ -0,0 +1,296 @@ +use crate::map::{BuildingID, Buildings, IntersectionID, Intersections, Map, RoadID, Roads}; +use std::collections::{BTreeMap, BTreeSet}; + +/// A network object is an object that can be connected to an electricity network +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum NetworkObjectID { + Building(BuildingID), + Intersection(IntersectionID), + Road(RoadID), +} + +impl From for NetworkObjectID { + fn from(v: BuildingID) -> Self { + Self::Building(v) + } +} + +impl From for NetworkObjectID { + fn from(v: IntersectionID) -> Self { + Self::Intersection(v) + } +} + +impl From for NetworkObjectID { + fn from(v: RoadID) -> Self { + Self::Road(v) + } +} + +/// The id of a network is the id of its lowest object. This is necessary to keep everything +/// deterministic even though we don't serialize the electricity cache +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ElectricityNetworkID(NetworkObjectID); + +/// A network is a set of sources and sinks that is connected together +pub struct ElectricityNetwork { + pub id: ElectricityNetworkID, + + /// The sources/sinks of the network must be buildings. For efficient iteration, + /// we store them separately from the road graph + pub buildings: BTreeSet, + + /// The objects of the networks + pub objects: BTreeSet, +} + +/// The electricity cache is a cache of all the electricity networks in the map +/// It maintains a mapping from network objects to network ids that are connected to each other +/// It does not store the actual graph of connections (!) which is provided by the map +/// only the connected components and their objects +#[derive(Default)] +pub struct ElectricityCache { + pub networks: BTreeMap, + + /// The network that each intersection is connected to + pub ids: BTreeMap, +} + +impl ElectricityCache { + /// Add a new network object. Must be called before adding or removing edges + pub fn add_object(&mut self, object_id: impl Into) { + let object_id = object_id.into(); + self.add_object_inner(object_id); + } + fn add_object_inner(&mut self, object_id: NetworkObjectID) { + let network_id = ElectricityNetworkID(object_id); + let network = ElectricityNetwork { + id: network_id, + buildings: match object_id { + NetworkObjectID::Building(b) => BTreeSet::from([b]), + _ => BTreeSet::new(), + }, + objects: BTreeSet::from([object_id]), + }; + + self.networks.insert(network_id, network); + self.ids.insert(object_id, network_id); + } + + /// Add an edge between two network objects (symmetric) + pub fn add_edge(&mut self, src: impl Into, dst: impl Into) { + let src = src.into(); + let dst = dst.into(); + self.add_edge_inner(&src, &dst); + } + + fn add_edge_inner(&mut self, src: &NetworkObjectID, dst: &NetworkObjectID) { + let Some(src) = self.ids.get(&src) else { + log::error!("electricity add_edge src {:?} not found", src); + return; + }; + let Some(dst) = self.ids.get(&dst) else { + log::error!("electricity add_edge dst {:?} not found", dst); + return; + }; + self.merge(*src, *dst); + } + + pub fn remove_edge( + &mut self, + roads: &Roads, + buildings: &Buildings, + intersections: &Intersections, + src: impl Into, + dst: impl Into, + ) { + let src = src.into(); + let dst = dst.into(); + self.remove_edge_inner(roads, buildings, intersections, &src, &dst); + } + + fn remove_edge_inner( + &mut self, + roads: &Roads, + buildings: &Buildings, + intersections: &Intersections, + src: &NetworkObjectID, + dst: &NetworkObjectID, + ) { + let Some(src_net) = self.ids.get(&src) else { + log::error!("electricity remove_edge src {:?} not found", src); + return; + }; + let Some(dst_net) = self.ids.get(&dst) else { + log::error!("electricity remove_edge dst {:?} not found", dst); + return; + }; + debug_assert!(src_net == dst_net); + if Self::path_exists(roads, buildings, intersections, *src, *dst) { + return; + } + self.split(*src_net, *src, *dst); + } + + fn path_exists( + roads: &Roads, + buildings: &Buildings, + intersections: &Intersections, + src: NetworkObjectID, + dst: NetworkObjectID, + ) -> bool { + for v in pathfinding::directed::bfs::bfs_reach(src, |n| { + Self::electricity_edges(roads, buildings, intersections, *n) + }) { + if v == dst { + return true; + } + } + false + } + + /// Gets the network id of a network object + /// Note that network ids change all the time, so this should not be kept as state + pub fn net_id(&self, object_id: NetworkObjectID) -> Option { + self.ids.get(&object_id).copied() + } + + pub fn networks(&self) -> impl Iterator { + self.networks.values() + } + + /// Build the electricity cache from a map. Should give the same result as the current cache in the map + pub fn build(map: &Map) -> ElectricityCache { + let mut e = ElectricityCache::default(); + + for b in map.buildings.keys() { + e.add_object(NetworkObjectID::Building(b)); + } + + for i in map.intersections.keys() { + e.add_object(NetworkObjectID::Intersection(i)); + } + + for r in map.roads.keys() { + e.add_object(NetworkObjectID::Road(r)); + } + + for n_id in common::iter::chain(( + map.buildings.keys().map(NetworkObjectID::Building), + map.intersections.keys().map(NetworkObjectID::Intersection), + map.roads.keys().map(NetworkObjectID::Road), + )) { + for neighbor in + Self::electricity_edges(&map.roads, &map.buildings, &map.intersections, n_id) + { + e.add_edge(n_id, neighbor); + } + } + + e + } + + /// Iterate over the edges of a network object + /// + /// Buildings -> 1 road + /// Intersections -> n roads + /// Roads -> 2 intersections + n buildings + fn electricity_edges<'a>( + roads: &'a Roads, + buildings: &'a Buildings, + intersections: &'a Intersections, + obj: NetworkObjectID, + ) -> impl Iterator + 'a { + use itertools::Either::{Left, Right}; + match obj { + NetworkObjectID::Building(b) => { + let Some(b) = buildings.get(b) else { + return Left(Left(None.into_iter())); + }; + let Some(r) = b.connected_road else { + return Left(Left(None.into_iter())); + }; + Left(Right(Some(NetworkObjectID::Road(r)).into_iter())) + } + NetworkObjectID::Intersection(i) => { + let Some(i) = intersections.get(i) else { + return Left(Left(None.into_iter())); + }; + Right(Left(i.roads.iter().map(|v| NetworkObjectID::Road(*v)))) + } + NetworkObjectID::Road(r) => { + let Some(r) = roads.get(r) else { + return Left(Left(None.into_iter())); + }; + Right(Right(common::iter::chain(( + Some(NetworkObjectID::Intersection(r.src)).into_iter(), + Some(NetworkObjectID::Intersection(r.dst)).into_iter(), + r.connected_buildings + .iter() + .map(|v| NetworkObjectID::Building(*v)), + )))) + } + } + } + + /// Merge two networks together, the smallest one is removed + /// + /// The strategy is: + /// - Merge objects/sources/sinks into the biggest existing network to avoid allocations + /// - Find out the network_id of the new network (smallest object within it) + /// - Update the network_id of all objects in the new network if needed + /// + fn merge(&mut self, mut a: ElectricityNetworkID, mut b: ElectricityNetworkID) { + if a == b { + return; + } + + if self.networks[&a].objects.len() > self.networks[&b].objects.len() { + std::mem::swap(&mut a, &mut b); + } + + let src = self.networks.remove(&a).unwrap(); + let dst = self.networks.get(&b).unwrap(); + + let new_id = ElectricityNetworkID( + *dst.objects + .first() + .unwrap() + .min(src.objects.first().unwrap()), + ); + + // We will need to relabel all objects in src in any case + for id in src.objects.iter() { + self.ids.insert(*id, new_id); + } + + // network_id changed, we need to relabel dst objects too + // and re-insert it into the networks btree + if new_id != dst.id { + for id in dst.objects.iter() { + self.ids.insert(*id, new_id); + } + + let dst_id = dst.id; // cannot inline cuz of borrow checker + let mut dst = self.networks.remove(&dst_id).unwrap(); + dst.id = new_id; + self.networks.insert(dst.id, dst); + } + + // finally, merge src into dst + let dst = self.networks.get_mut(&new_id).unwrap(); + dst.buildings.extend(src.buildings); + dst.objects.extend(src.objects); + } + + /// Split a network into two networks + /// The two given ids are hints of elements in disjoint connected components + fn split( + &mut self, + network_id: ElectricityNetworkID, + id1: NetworkObjectID, + id2: NetworkObjectID, + ) { + todo!() + } +} diff --git a/simulation/src/map/map.rs b/simulation/src/map/map.rs index 09535960..3f0b069b 100644 --- a/simulation/src/map/map.rs +++ b/simulation/src/map/map.rs @@ -1,3 +1,4 @@ +use crate::map::electricity::ElectricityCache; use crate::map::height_override::find_overrides; use crate::map::serializing::SerializedMap; use crate::map::{ @@ -35,6 +36,7 @@ pub struct Map { pub(crate) spatial_map: SpatialMap, pub(crate) external_train_stations: Vec, + pub electricity: ElectricityCache, pub environment: Environment, pub parking: ParkingSpots, pub subscribers: MapSubscribers, @@ -63,6 +65,7 @@ impl Map { environment: Environment::default(), spatial_map: SpatialMap::default(), external_train_stations: Default::default(), + electricity: Default::default(), override_suscriber: subscribers.subscribe(UpdateType::Road | UpdateType::Building), subscribers, } diff --git a/simulation/src/map/mod.rs b/simulation/src/map/mod.rs index 4546350e..ab7b01de 100644 --- a/simulation/src/map/mod.rs +++ b/simulation/src/map/mod.rs @@ -28,6 +28,7 @@ pub mod procgen { } mod change_detection; +mod electricity; mod height_override; mod light_policy; #[allow(clippy::module_inception)] diff --git a/simulation/src/world.rs b/simulation/src/world.rs index 756cbd5f..83aef320 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -14,6 +14,7 @@ use crate::transportation::{ use crate::utils::par_command_buffer::SimDrop; use crate::utils::resources::Resources; use crate::{impl_entity, impl_trans, SoulID}; +use common::iter::chain; use derive_more::{From, TryInto}; use geom::{Transform, Vec2, Vec3}; use serde::Deserialize; @@ -356,75 +357,6 @@ mod macros { } } -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) - } -} - impl Display for AnyEntity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self {