pub mod destination;
pub mod etd;
pub mod look;
pub mod nearest_car;
pub mod reposition;
pub mod scan;
pub use destination::{AssignedCar, DestinationDispatch};
pub use etd::EtdDispatch;
pub use look::LookDispatch;
pub use nearest_car::NearestCarDispatch;
pub use scan::ScanDispatch;
use serde::{Deserialize, Serialize};
use crate::entity::EntityId;
use crate::ids::GroupId;
use crate::world::World;
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct RiderInfo {
pub id: EntityId,
pub destination: Option<EntityId>,
pub weight: f64,
pub wait_ticks: u64,
}
#[derive(Debug, Clone, Default)]
pub struct DispatchManifest {
pub waiting_at_stop: BTreeMap<EntityId, Vec<RiderInfo>>,
pub riding_to_stop: BTreeMap<EntityId, Vec<RiderInfo>>,
pub resident_count_at_stop: BTreeMap<EntityId, usize>,
}
impl DispatchManifest {
#[must_use]
pub fn waiting_count_at(&self, stop: EntityId) -> usize {
self.waiting_at_stop.get(&stop).map_or(0, Vec::len)
}
#[must_use]
pub fn total_weight_at(&self, stop: EntityId) -> f64 {
self.waiting_at_stop
.get(&stop)
.map_or(0.0, |riders| riders.iter().map(|r| r.weight).sum())
}
#[must_use]
pub fn riding_count_to(&self, stop: EntityId) -> usize {
self.riding_to_stop.get(&stop).map_or(0, Vec::len)
}
#[must_use]
pub fn has_demand(&self, stop: EntityId) -> bool {
self.waiting_count_at(stop) > 0 || self.riding_count_to(stop) > 0
}
#[must_use]
pub fn resident_count_at(&self, stop: EntityId) -> usize {
self.resident_count_at_stop.get(&stop).copied().unwrap_or(0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BuiltinStrategy {
Scan,
Look,
NearestCar,
Etd,
Destination,
Custom(String),
}
impl std::fmt::Display for BuiltinStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Scan => write!(f, "Scan"),
Self::Look => write!(f, "Look"),
Self::NearestCar => write!(f, "NearestCar"),
Self::Etd => write!(f, "Etd"),
Self::Destination => write!(f, "Destination"),
Self::Custom(name) => write!(f, "Custom({name})"),
}
}
}
impl BuiltinStrategy {
#[must_use]
pub fn instantiate(&self) -> Option<Box<dyn DispatchStrategy>> {
match self {
Self::Scan => Some(Box::new(scan::ScanDispatch::new())),
Self::Look => Some(Box::new(look::LookDispatch::new())),
Self::NearestCar => Some(Box::new(nearest_car::NearestCarDispatch::new())),
Self::Etd => Some(Box::new(etd::EtdDispatch::new())),
Self::Destination => Some(Box::new(destination::DestinationDispatch::new())),
Self::Custom(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum DispatchDecision {
GoToStop(EntityId),
Idle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineInfo {
entity: EntityId,
elevators: Vec<EntityId>,
serves: Vec<EntityId>,
}
impl LineInfo {
#[must_use]
pub const fn new(entity: EntityId, elevators: Vec<EntityId>, serves: Vec<EntityId>) -> Self {
Self {
entity,
elevators,
serves,
}
}
#[must_use]
pub const fn entity(&self) -> EntityId {
self.entity
}
#[must_use]
pub fn elevators(&self) -> &[EntityId] {
&self.elevators
}
#[must_use]
pub fn serves(&self) -> &[EntityId] {
&self.serves
}
pub(crate) const fn set_entity(&mut self, entity: EntityId) {
self.entity = entity;
}
pub(crate) const fn elevators_mut(&mut self) -> &mut Vec<EntityId> {
&mut self.elevators
}
pub(crate) const fn serves_mut(&mut self) -> &mut Vec<EntityId> {
&mut self.serves
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElevatorGroup {
id: GroupId,
name: String,
lines: Vec<LineInfo>,
elevator_entities: Vec<EntityId>,
stop_entities: Vec<EntityId>,
}
impl ElevatorGroup {
#[must_use]
pub fn new(id: GroupId, name: String, lines: Vec<LineInfo>) -> Self {
let mut group = Self {
id,
name,
lines,
elevator_entities: Vec::new(),
stop_entities: Vec::new(),
};
group.rebuild_caches();
group
}
#[must_use]
pub const fn id(&self) -> GroupId {
self.id
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn lines(&self) -> &[LineInfo] {
&self.lines
}
pub const fn lines_mut(&mut self) -> &mut Vec<LineInfo> {
&mut self.lines
}
#[must_use]
pub fn elevator_entities(&self) -> &[EntityId] {
&self.elevator_entities
}
#[must_use]
pub fn stop_entities(&self) -> &[EntityId] {
&self.stop_entities
}
pub(crate) fn push_stop(&mut self, stop: EntityId) {
if !self.stop_entities.contains(&stop) {
self.stop_entities.push(stop);
}
}
pub(crate) fn push_elevator(&mut self, elevator: EntityId) {
if !self.elevator_entities.contains(&elevator) {
self.elevator_entities.push(elevator);
}
}
pub fn rebuild_caches(&mut self) {
self.elevator_entities = self
.lines
.iter()
.flat_map(|li| li.elevators.iter().copied())
.collect();
let mut stops: Vec<EntityId> = self
.lines
.iter()
.flat_map(|li| li.serves.iter().copied())
.collect();
stops.sort_unstable();
stops.dedup();
self.stop_entities = stops;
}
}
pub trait DispatchStrategy: Send + Sync {
fn pre_dispatch(
&mut self,
_group: &ElevatorGroup,
_manifest: &DispatchManifest,
_world: &mut World,
) {
}
fn decide(
&mut self,
elevator: EntityId,
elevator_position: f64,
group: &ElevatorGroup,
manifest: &DispatchManifest,
world: &World,
) -> DispatchDecision;
fn decide_all(
&mut self,
elevators: &[(EntityId, f64)], group: &ElevatorGroup,
manifest: &DispatchManifest,
world: &World,
) -> Vec<(EntityId, DispatchDecision)> {
elevators
.iter()
.map(|(eid, pos)| (*eid, self.decide(*eid, *pos, group, manifest, world)))
.collect()
}
fn notify_removed(&mut self, _elevator: EntityId) {}
}
pub trait RepositionStrategy: Send + Sync {
fn reposition(
&mut self,
idle_elevators: &[(EntityId, f64)],
stop_positions: &[(EntityId, f64)],
group: &ElevatorGroup,
world: &World,
) -> Vec<(EntityId, EntityId)>;
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BuiltinReposition {
SpreadEvenly,
ReturnToLobby,
DemandWeighted,
NearestIdle,
Custom(String),
}
impl std::fmt::Display for BuiltinReposition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SpreadEvenly => write!(f, "SpreadEvenly"),
Self::ReturnToLobby => write!(f, "ReturnToLobby"),
Self::DemandWeighted => write!(f, "DemandWeighted"),
Self::NearestIdle => write!(f, "NearestIdle"),
Self::Custom(name) => write!(f, "Custom({name})"),
}
}
}
impl BuiltinReposition {
#[must_use]
pub fn instantiate(&self) -> Option<Box<dyn RepositionStrategy>> {
match self {
Self::SpreadEvenly => Some(Box::new(reposition::SpreadEvenly)),
Self::ReturnToLobby => Some(Box::new(reposition::ReturnToLobby::new())),
Self::DemandWeighted => Some(Box::new(reposition::DemandWeighted)),
Self::NearestIdle => Some(Box::new(reposition::NearestIdle)),
Self::Custom(_) => None,
}
}
}