use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
pub type RouteId = String;
pub type InventoryEntityId = String;
pub type ItemId = String;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Route {
pub id: RouteId,
pub source_id: InventoryEntityId,
pub destination_id: InventoryEntityId,
pub transporter: Transporter,
#[serde(skip)]
pub runtime: RouteRuntime,
pub metadata: RouteMetadata,
}
impl Route {
pub fn new(
id: impl Into<String>,
source_id: impl Into<String>,
destination_id: impl Into<String>,
transporter: Transporter,
) -> Self {
Self {
id: id.into(),
source_id: source_id.into(),
destination_id: destination_id.into(),
transporter,
runtime: RouteRuntime::new(),
metadata: RouteMetadata::default(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Transporter {
pub throughput: u32,
pub cooldown: f32,
pub item_filter: Option<Vec<ItemId>>,
pub pull_limit: Option<u32>,
pub priority: u8,
pub distance: f32,
pub status: TransporterStatus,
}
impl Transporter {
pub fn new(throughput: u32, cooldown: f32) -> Self {
Self {
throughput,
cooldown,
item_filter: None,
pull_limit: None,
priority: 0,
distance: 1.0,
status: TransporterStatus::Active,
}
}
pub fn with_filter(mut self, items: Vec<impl Into<String>>) -> Self {
self.item_filter = Some(items.into_iter().map(|s| s.into()).collect());
self
}
pub fn with_priority(mut self, priority: u8) -> Self {
self.priority = priority;
self
}
pub fn with_pull_limit(mut self, limit: u32) -> Self {
self.pull_limit = Some(limit);
self
}
pub fn with_distance(mut self, distance: f32) -> Self {
self.distance = distance;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TransporterStatus {
Active,
SourceEmpty,
DestinationFull,
Blocked,
Disabled,
}
#[derive(Clone, Debug)]
pub struct RouteRuntime {
pub next_execution: Option<Instant>,
pub last_transfer: Option<Instant>,
pub failure_count: u32,
}
impl RouteRuntime {
pub fn new() -> Self {
Self {
next_execution: Some(Instant::now()), last_transfer: None,
failure_count: 0,
}
}
pub fn schedule_next(&mut self, cooldown: Duration) {
self.next_execution = Some(Instant::now() + cooldown);
self.failure_count = 0; }
pub fn mark_failed(&mut self, base_cooldown: Duration) {
self.failure_count += 1;
let backoff = base_cooldown * 2u32.pow(self.failure_count.min(5)); self.next_execution = Some(Instant::now() + backoff);
}
pub fn is_ready(&self) -> bool {
match self.next_execution {
Some(time) => Instant::now() >= time,
None => false,
}
}
}
impl Default for RouteRuntime {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RouteMetadata {
pub name: String,
pub description: String,
pub total_transferred: u64,
pub created_at: Option<std::time::SystemTime>,
pub tags: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct TransferResult {
pub route_id: RouteId,
pub item_id: ItemId,
pub amount: u32,
pub success: bool,
pub reason: Option<String>,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct LogisticsMetrics {
pub total_routes: usize,
pub active_routes: usize,
pub transfers_this_update: usize,
pub total_transfers: u64,
pub total_items_moved: u64,
pub routes_blocked: usize,
pub routes_starved: usize,
pub last_update_duration_us: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_route_creation() {
let route = Route::new("test_route", "source", "dest", Transporter::new(10, 1.0));
assert_eq!(route.id, "test_route");
assert_eq!(route.source_id, "source");
assert_eq!(route.destination_id, "dest");
assert_eq!(route.transporter.throughput, 10);
}
#[test]
fn test_transporter_builder() {
let transporter = Transporter::new(5, 2.0)
.with_filter(vec!["iron", "gold"])
.with_priority(10)
.with_pull_limit(100)
.with_distance(5.0);
assert_eq!(transporter.throughput, 5);
assert_eq!(transporter.cooldown, 2.0);
assert_eq!(transporter.item_filter.unwrap().len(), 2);
assert_eq!(transporter.priority, 10);
assert_eq!(transporter.pull_limit, Some(100));
assert_eq!(transporter.distance, 5.0);
}
#[test]
fn test_route_runtime_schedule() {
let mut runtime = RouteRuntime::new();
assert!(runtime.is_ready());
runtime.schedule_next(Duration::from_secs(1));
assert!(!runtime.is_ready()); assert_eq!(runtime.failure_count, 0);
}
#[test]
fn test_route_runtime_exponential_backoff() {
let mut runtime = RouteRuntime::new();
runtime.mark_failed(Duration::from_secs(1));
assert_eq!(runtime.failure_count, 1);
runtime.mark_failed(Duration::from_secs(1));
assert_eq!(runtime.failure_count, 2);
runtime.mark_failed(Duration::from_secs(1));
assert_eq!(runtime.failure_count, 3);
}
}