diff --git a/world/economy_testinput.ron b/world/economy_testinput.ron deleted file mode 100644 index 83f6cc62e9..0000000000 --- a/world/economy_testinput.ron +++ /dev/null @@ -1,220 +0,0 @@ -[ - ( - name: "Credge", - position: (4176, 4080), - kind: Settlement, - neighbors: [ - (2, 112), - (1, 146), - (6, 98), - (9, 145), - ], - resources: [ - ( - good: Terrain(Lake), - amount: 4, - ), - ( - good: Terrain(Mountain), - amount: 52, - ), - ( - good: Terrain(Grassland), - amount: 7073, - ), - ( - good: Terrain(Ocean), - amount: 4929, - ), - ( - good: Terrain(Forest), - amount: 6360, - ), - ], - ), - ( - name: "Etodren", - position: (1200, 2928), - kind: Settlement, - neighbors: [ - (2, 258), - (0, 146), - (6, 60), - (9, 158), - ], - resources: [ - ( - good: Terrain(Mountain), - amount: 288, - ), - ( - good: Terrain(Grassland), - amount: 4129, - ), - ( - good: Terrain(Desert), - amount: 230, - ), - ( - good: Terrain(Ocean), - amount: 2923, - ), - ( - good: Terrain(Forest), - amount: 3139, - ), - ], - ), - ( - name: "Twige", - position: (2000, 7632), - kind: Settlement, - neighbors: [ - (0, 112), - (1, 258), - (6, 109), - (9, 32), - ], - resources: [ - ( - good: Terrain(Lake), - amount: 1, - ), - ( - good: Terrain(Mountain), - amount: 231, - ), - ( - good: Terrain(Grassland), - amount: 3308, - ), - ( - good: Terrain(Desert), - amount: 1695, - ), - ( - good: Terrain(Ocean), - amount: 487, - ), - ( - good: Terrain(Forest), - amount: 1338, - ), - ], - ), - ( - name: "Pleed Dungeon", - position: (6922, 1034), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Fred Lair", - position: (3786, 2250), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Frer Dungeon", - position: (6602, 2250), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Inige Castle", - position: (1360, 4304), - kind: Castle, - neighbors: [ - (0, 98), - (1, 60), - (2, 109), - (9, 99), - ], - resources: [ - ( - good: Terrain(Mountain), - amount: 424, - ), - ( - good: Terrain(Grassland), - amount: 958, - ), - ( - good: Terrain(Desert), - amount: 1285, - ), - ( - good: Terrain(Ocean), - amount: 669, - ), - ( - good: Terrain(Forest), - amount: 1781, - ), - ], - ), - ( - name: "Estedock Catacombs", - position: (4650, 330), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Oreefey Lair", - position: (1578, 3754), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Lasnast Keep", - position: (1136, 6832), - kind: Castle, - neighbors: [ - (0, 145), - (2, 32), - (1, 158), - (6, 99), - ], - resources: [ - ( - good: Terrain(Mountain), - amount: 352, - ), - ( - good: Terrain(Grassland), - amount: 887, - ), - ( - good: Terrain(Desert), - amount: 2606, - ), - ( - good: Terrain(Ocean), - amount: 286, - ), - ( - good: Terrain(Forest), - amount: 679, - ), - ], - ), - ( - name: "Oren Lair", - position: (6730, 2506), - kind: Dungeon, - neighbors: [], - resources: [], - ), - ( - name: "Ween Crib", - position: (2250, 8010), - kind: Dungeon, - neighbors: [], - resources: [], - ), -] \ No newline at end of file diff --git a/world/economy_testinput2.ron b/world/economy_testinput2.ron index 16e10df778..fca72a297e 100644 --- a/world/economy_testinput2.ron +++ b/world/economy_testinput2.ron @@ -4,8 +4,8 @@ position: (1, 1), kind: Settlement, neighbors: [ - (1, 10), - (2, 10), + 1, + 2, ], resources: [ ( @@ -19,7 +19,7 @@ position: (10, 10), kind: Settlement, neighbors: [ - (0, 10), + 0, ], resources: [ ( @@ -33,7 +33,7 @@ position: (20, 10), kind: Settlement, neighbors: [ - (0, 10), + 0, ], resources: [ ( diff --git a/world/examples/economy_tree.rs b/world/examples/economy_tree.rs index 949be12e6d..8273bcceac 100644 --- a/world/examples/economy_tree.rs +++ b/world/examples/economy_tree.rs @@ -1,6 +1,6 @@ use common::trade::Good; use std::io::Write; -use veloren_world::site::economy::{self, good_list, Economy}; +use veloren_world::site::economy::{GraphInfo, Labor}; //use regex::Regex::replace_all; fn good_name(g: Good) -> String { @@ -9,20 +9,18 @@ fn good_name(g: Good) -> String { res.replace(')', "_") } -fn labor_name(l: economy::Labor) -> String { +fn labor_name(l: Labor) -> String { let res = format!("{:?}", l); res.replace(' ', "_") } fn main() -> Result<(), std::io::Error> { - let eco = Economy::default(); - let o = eco.get_orders(); - let p = eco.get_productivity(); + let eco = GraphInfo::default(); let mut f = std::fs::File::create("economy.gv")?; writeln!(f, "digraph economy {{")?; - for i in good_list() { - let color = if economy::direct_use_goods().contains(&i) { + for i in eco.good_list() { + let color = if !eco.can_store(&i) { "green" } else { "orange" @@ -33,44 +31,42 @@ fn main() -> Result<(), std::io::Error> { writeln!(f)?; writeln!(f, "// Professions")?; writeln!(f, "Everyone [shape=doubleoctagon];")?; - for i in economy::Labor::list() { + for i in eco.labor_list() { writeln!(f, "{:?} [shape=box];", labor_name(i))?; } writeln!(f)?; writeln!(f, "// Orders")?; + let o = eco.get_orders(); for i in o.iter() { for j in i.1.iter() { - if i.0.is_some() { - let style = if matches!(j.0.into(), Good::Tools) - || matches!(j.0.into(), Good::Armor) - || matches!(j.0.into(), Good::Potions) - { - ", style=dashed, color=orange" - } else { - "" - }; - writeln!( - f, - "{:?} -> {:?} [label=\"{:.1}\"{}];", - good_name(j.0.into()), - labor_name(i.0.unwrap()), - j.1, - style - )?; + let style = if matches!(j.0.into(), Good::Tools | Good::Armor | Good::Potions) { + ", style=dashed, color=orange" } else { - writeln!( - f, - "{:?} -> Everyone [label=\"{:.1}\"];", - good_name(j.0.into()), - j.1 - )?; - } + "" + }; + writeln!( + f, + "{:?} -> {:?} [label=\"{:.1}\"{}];", + good_name(j.0.into()), + labor_name(i.0), + j.1, + style + )?; } } + for j in eco.get_orders_everyone().iter() { + writeln!( + f, + "{:?} -> Everyone [label=\"{:.1}\"];", + good_name(j.0.into()), + j.1 + )?; + } writeln!(f)?; writeln!(f, "// Products")?; + let p = eco.get_production(); for i in p.iter() { writeln!( f, diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index e69de29bb2..ea339e100a 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -0,0 +1,3 @@ +use crate::{sim::WorldSim, site::economy::simulate_economy, Index}; + +pub fn simulate(index: &mut Index, _world: &mut WorldSim) { simulate_economy(index); } diff --git a/world/src/site/economy/context.rs b/world/src/site/economy/context.rs index 2306c68a3f..196b174623 100644 --- a/world/src/site/economy/context.rs +++ b/world/src/site/economy/context.rs @@ -1,11 +1,8 @@ +/// this contains global housekeeping info during simulation use crate::{ - sim::WorldSim, site::{ - economy::{ - good_list, vergleich, LaborIndex, COIN_INDEX, DAYS_PER_MONTH, DAYS_PER_YEAR, - INTER_SITE_TRADE, - }, - Site, SiteKind, + economy::{self, Economy, DAYS_PER_MONTH, DAYS_PER_YEAR, INTER_SITE_TRADE}, + SiteKind, }, Index, }; @@ -14,43 +11,42 @@ use tracing::{debug, info}; // this is an empty replacement for https://github.com/cpetig/vergleich // which can be used to compare values acros runs -pub mod vergleich { - pub struct Error {} - impl Error { - pub fn to_string(&self) -> &'static str { "" } - } - pub struct ProgramRun {} - impl ProgramRun { - pub fn new(_: &str) -> Result { Ok(Self {}) } +// pub mod vergleich { +// pub struct Error {} +// impl Error { +// pub fn to_string(&self) -> &'static str { "" } +// } +// pub struct ProgramRun {} +// impl ProgramRun { +// pub fn new(_: &str) -> Result { Ok(Self {}) } - pub fn set_epsilon(&mut self, _: f32) {} +// pub fn set_epsilon(&mut self, _: f32) {} - pub fn context(&mut self, _: &str) -> Context { Context {} } +// pub fn context(&mut self, _: &str) -> Context { Context {} } - //pub fn value(&mut self, _: &str, val: f32) -> f32 { val } - } - pub struct Context {} - impl Context { - #[must_use] - pub fn context(&mut self, _: &str) -> Context { Context {} } +// //pub fn value(&mut self, _: &str, val: f32) -> f32 { val } +// } +// pub struct Context {} +// impl Context { +// #[must_use] +// pub fn context(&mut self, _: &str) -> Context { Context {} } - pub fn value(&mut self, _: &str, val: f32) -> f32 { val } +// pub fn value(&mut self, _: &str, val: f32) -> f32 { val } - pub fn dummy() -> Self { Context {} } - } -} +// pub fn dummy() -> Self { Context {} } +// } +// } const TICK_PERIOD: f32 = 3.0 * DAYS_PER_MONTH; // 3 months const HISTORY_DAYS: f32 = 500.0 * DAYS_PER_YEAR; // 500 years - /// Statistics collector (min, max, avg) #[derive(Debug)] struct EconStatistics { - pub count: u32, - pub sum: f32, - pub min: f32, - pub max: f32, + count: u32, + sum: f32, + min: f32, + max: f32, } impl Default for EconStatistics { @@ -83,122 +79,145 @@ impl EconStatistics { fn valid(&self) -> bool { self.min.is_finite() } } -fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::io::Error> { - // ... - if let Some(f) = f.as_mut() { - writeln!(f)?; - for site in index.sites.ids() { - let site = index.sites.get(site); - csv_entry(f, site)?; - } +pub struct Environment { + csv_file: Option, + // context: vergleich::ProgramRun, +} + +impl Environment { + pub fn new() -> Result { + // let mut context = vergleich::ProgramRun::new("economy_compare.sqlite") + // .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, + // e.to_string()))?; context.set_epsilon(0.1); + let csv_file = Economy::csv_open(); + Ok(Self { + csv_file, /* context */ + }) } - { - let mut castles = EconStatistics::default(); - let mut towns = EconStatistics::default(); - let mut dungeons = EconStatistics::default(); - let giant_trees = EconStatistics::default(); - for site in index.sites.ids() { - let site = &index.sites[site]; - match site.kind { - SiteKind::Settlement(_) | SiteKind::Refactor(_) | SiteKind::CliffTown(_) => { - towns += site.economy.pop - }, - SiteKind::Dungeon(_) => dungeons += site.economy.pop, - SiteKind::Castle(_) => castles += site.economy.pop, - SiteKind::Tree(_) => (), - SiteKind::GiantTree(_) => (), - SiteKind::Gnarling(_) => {}, + fn iteration(&mut self, _: i32) {} + + fn end(mut self, index: &Index) { + if let Some(f) = self.csv_file.as_mut() { + use std::io::Write; + let err = writeln!(f); + if err.is_ok() { + for site in index.sites.ids() { + let site = index.sites.get(site); + if Economy::csv_entry(f, site).is_err() { + break; + } + } + } + self.csv_file.take(); + } + + { + let mut castles = EconStatistics::default(); + let mut towns = EconStatistics::default(); + let mut dungeons = EconStatistics::default(); + for site in index.sites.ids() { + let site = &index.sites[site]; + match site.kind { + SiteKind::Settlement(_) | SiteKind::Refactor(_) | SiteKind::CliffTown(_) => { + towns += site.economy.pop + }, + SiteKind::Dungeon(_) => dungeons += site.economy.pop, + SiteKind::Castle(_) => castles += site.economy.pop, + SiteKind::Tree(_) => (), + SiteKind::GiantTree(_) => (), + SiteKind::Gnarling(_) => {}, + } + } + if towns.valid() { + info!( + "Towns {:.0}-{:.0} avg {:.0} inhabitants", + towns.min, + towns.max, + towns.sum / (towns.count as f32) + ); + } + if castles.valid() { + info!( + "Castles {:.0}-{:.0} avg {:.0}", + castles.min, + castles.max, + castles.sum / (castles.count as f32) + ); + } + if dungeons.valid() { + info!( + "Dungeons {:.0}-{:.0} avg {:.0}", + dungeons.min, + dungeons.max, + dungeons.sum / (dungeons.count as f32) + ); } } - if towns.valid() { - info!( - "Towns {:.0}-{:.0} avg {:.0} inhabitants", - towns.min, - towns.max, - towns.sum / (towns.count as f32) - ); - } - if castles.valid() { - info!( - "Castles {:.0}-{:.0} avg {:.0}", - castles.min, - castles.max, - castles.sum / (castles.count as f32) - ); - } - if dungeons.valid() { - info!( - "Dungeons {:.0}-{:.0} avg {:.0}", - dungeons.min, - dungeons.max, - dungeons.sum / (dungeons.count as f32) - ); - } - if giant_trees.valid() { - info!( - "Giant Trees {:.0}-{:.0} avg {:.0}", - giant_trees.min, - giant_trees.max, - giant_trees.sum / (giant_trees.count as f32) - ) - } - check_money(index); } - if let Some(f) = f.as_mut() { - if i % 5 == 0 { + fn csv_tick(&mut self, index: &Index) { + if let Some(f) = self.csv_file.as_mut() { if let Some(site) = index .sites .values() .find(|s| !matches!(s.kind, SiteKind::Dungeon(_))) { - csv_entry(f, site)?; + Economy::csv_entry(f, site).unwrap_or_else(|_| { + self.csv_file.take(); + }); } } } +} + +fn simulate_return(index: &mut Index) -> Result<(), std::io::Error> { + let mut env = economy::Environment::new()?; tracing::info!("economy simulation start"); - let mut vr = vergleich::ProgramRun::new("economy_compare.sqlite") - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - vr.set_epsilon(0.1); for i in 0..(HISTORY_DAYS / TICK_PERIOD) as i32 { if (index.time / DAYS_PER_YEAR) as i32 % 50 == 0 && (index.time % DAYS_PER_YEAR) as i32 == 0 { debug!("Year {}", (index.time / DAYS_PER_YEAR) as i32); } - - tick(index, world, TICK_PERIOD, vr.context(&i.to_string())); - + env.iteration(i); + tick(index, TICK_PERIOD, &mut env); + if i % 5 == 0 { + env.csv_tick(index); + } } tracing::info!("economy simulation end"); + env.end(index); + // csv_footer(f, index); + + Ok(()) } -pub fn simulate(index: &mut Index, world: &mut WorldSim) { - simulate_return(index, world) +pub fn simulate_economy(index: &mut Index) { + simulate_return(index) .unwrap_or_else(|err| info!("I/O error in simulate (economy.csv not writable?): {}", err)); } -fn check_money(index: &mut Index) { - let mut sum_stock: f32 = 0.0; - for site in index.sites.values() { - sum_stock += site.economy.stocks[*COIN_INDEX]; - } - let mut sum_del: f32 = 0.0; - for v in index.trade.deliveries.values() { - for del in v.iter() { - sum_del += del.amount[*COIN_INDEX]; - } - } - info!( - "Coin amount {} + {} = {}", - sum_stock, - sum_del, - sum_stock + sum_del - ); -} +// fn check_money(index: &Index) { +// let mut sum_stock: f32 = 0.0; +// for site in index.sites.values() { +// sum_stock += site.economy.stocks[*COIN_INDEX]; +// } +// let mut sum_del: f32 = 0.0; +// for v in index.trade.deliveries.values() { +// for del in v.iter() { +// sum_del += del.amount[*COIN_INDEX]; +// } +// } +// info!( +// "Coin amount {} + {} = {}", +// sum_stock, +// sum_del, +// sum_stock + sum_del +// ); +// } -pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32, _vc: vergleich::Context) { +fn tick(index: &mut Index, dt: f32, _env: &mut Environment) { if INTER_SITE_TRADE { // move deliverables to recipient cities for (id, deliv) in index.trade.deliveries.drain() { @@ -207,7 +226,7 @@ pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32, _vc: vergleich::C } index.sites.par_iter_mut().for_each(|(site_id, site)| { if site.do_economic_simulation() { - site.economy.tick(site_id, dt, vergleich::Context::dummy()); + site.economy.tick(site_id, dt); // helpful for debugging but not compatible with parallel execution // vc.context(&site_id.id().to_string())); } @@ -241,8 +260,9 @@ pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32, _vc: vergleich::C #[cfg(test)] mod tests { - use crate::{sim, site::economy::GoodMap, util::seed_expan}; + use crate::{sim, util::seed_expan}; use common::{store::Id, terrain::BiomeKind, trade::Good}; + use hashbrown::HashMap; use rand::SeedableRng; use rand_chacha::ChaChaRng; use serde::{Deserialize, Serialize}; @@ -274,14 +294,20 @@ mod tests { name: String, position: (i32, i32), kind: common::terrain::site::SitesKind, - neighbors: Vec<(u64, usize)>, // id, travel_distance + neighbors: Vec, // id resources: Vec, } - fn show_economy(sites: &common::store::Store) { - use crate::site::economy::good_list; + fn show_economy( + sites: &common::store::Store, + names: &Option, String>>, + ) { for (id, site) in sites.iter() { - println!("Site id {:?} name {}", id, site.name()); + let name = names.as_ref().map_or(site.name().into(), |map| { + map.get(&id).cloned().unwrap_or(site.name().into()) + }); + println!("Site id {:?} name {}", id.id(), name); + site.economy.print_details(); } } @@ -308,7 +334,7 @@ mod tests { let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); info!("Civs created"); crate::sim2::simulate(&mut index, &mut sim); - show_economy(&index.sites); + show_economy(&index.sites, &None); }); } @@ -331,6 +357,7 @@ mod tests { info!("Index created"); let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); info!("World loaded"); + let mut names = None; let regenerate_input = false; if regenerate_input { let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index); @@ -347,12 +374,7 @@ mod tests { amount: *a * i.economy.natural_resources.average_yield_per_chunk[good], }) .collect(); - let neighbors = i - .economy - .neighbors - .iter() - .map(|j| (j.id.id(), j.travel_distance)) - .collect(); + let neighbors = i.economy.neighbors.iter().map(|j| j.id.id()).collect(); let val = EconomySetup { name: i.name().into(), position: (i.get_origin().x, i.get_origin().y), @@ -385,6 +407,7 @@ mod tests { .expect("economy_testinput2.ron not found"); let econ_testinput: Vec = ron::de::from_reader(ron_file).expect("economy_testinput2.ron parse error"); + names = Some(HashMap::new()); for i in econ_testinput.iter() { let wpos = Vec2 { x: i.position.0, @@ -418,43 +441,32 @@ mod tests { settlement.economy.natural_resources.average_yield_per_chunk [g.good.try_into().unwrap_or_default()] = 1.0; } - index.sites.insert(settlement); + let id = index.sites.insert(settlement); + names.as_mut().map(|map| map.insert(id, i.name.clone())); } // we can't add these in the first loop as neighbors will refer to later sites // (which aren't valid in the first loop) - for (i, e) in econ_testinput.iter().enumerate() { - if let Some(id) = index.sites.recreate_id(i as u64) { - let mut neighbors: Vec = e - .neighbors - .iter() - .flat_map(|(nid, dist)| { - index.sites.recreate_id(*nid).map(|i| (i, dist)) - }) - .map(|(nid, dist)| crate::site::economy::NeighborInformation { - id: nid, - travel_distance: *dist, - last_values: GoodMap::from_default(0.0), - last_supplies: GoodMap::from_default(0.0), - }) - .collect(); - index - .sites - .get_mut(id) - .economy - .neighbors - .append(&mut neighbors); + for (id, econ) in econ_testinput.iter().enumerate() { + if let Some(id) = index.sites.recreate_id(id as u64) { + for nid in econ.neighbors.iter() { + if let Some(nid) = index.sites.recreate_id(*nid) { + let town = &mut index.sites.get_mut(id).economy; + town.add_neighbor(nid, 0); + } + } } } } crate::sim2::simulate(&mut index, &mut sim); - show_economy(&index.sites); + show_economy(&index.sites, &names); }); } struct Simenv { index: crate::index::Index, rng: ChaChaRng, - targets: hashbrown::HashMap, f32>, + targets: HashMap, f32>, + names: HashMap, String>, } #[test] @@ -462,9 +474,7 @@ mod tests { fn test_economy_moderate_standalone() { fn add_settlement( env: &mut Simenv, - // index: &mut crate::index::Index, - // rng: &mut ChaCha20Rng, - _name: &str, + name: &str, target: f32, resources: &[(Good, f32)], ) -> Id { @@ -473,7 +483,6 @@ mod tests { wpos, None, &mut env.rng, - //Some(name), )); for (good, amount) in resources.iter() { settlement.economy.natural_resources.chunks_per_resource @@ -483,6 +492,7 @@ mod tests { } let id = env.index.sites.insert(settlement); env.targets.insert(id, target); + env.names.insert(id, name.into()); id } @@ -503,13 +513,14 @@ mod tests { let mut env = Simenv { index, rng, - targets: hashbrown::HashMap::new(), + targets: HashMap::new(), + names: HashMap::new(), }; add_settlement(&mut env, "Forest", 5000.0, &[( Good::Terrain(BiomeKind::Forest), 100.0_f32, )]); - add_settlement(&mut env, "Grass", 900.0, &[( + add_settlement(&mut env, "Grass", 880.0, &[( Good::Terrain(BiomeKind::Grassland), 100.0_f32, )]); @@ -550,7 +561,7 @@ mod tests { }); } crate::sim2::simulate(&mut env.index, &mut sim); - show_economy(&env.index.sites); + show_economy(&env.index.sites, &Some(env.names)); // check population (shrinks if economy gets broken) for (id, site) in env.index.sites.iter() { assert!(site.economy.pop >= env.targets[&id]); diff --git a/world/src/site/economy/map_types.rs b/world/src/site/economy/map_types.rs index 28e8263d28..fd8810e8ba 100644 --- a/world/src/site/economy/map_types.rs +++ b/world/src/site/economy/map_types.rs @@ -194,7 +194,7 @@ pub struct LaborMap { data: Vec, } -impl Default for LaborMap { +impl Default for LaborMap { fn default() -> Self { LaborMap { data: std::iter::repeat(V::default()).take(*LABOR_COUNT).collect(), @@ -233,6 +233,13 @@ impl LaborMap { .enumerate() .map(|(idx, v)| (LaborIndex::from_usize(idx), v)) } + + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + (&mut self.data) + .iter_mut() + .enumerate() + .map(|(idx, v)| (LaborIndex::from_usize(idx), v)) + } } impl LaborMap { @@ -347,6 +354,12 @@ impl Labor { pub fn is_everyone(&self) -> bool { self.0 == DUMMY_LABOR.0 } + pub fn orders_everyone() -> Vec<(GoodIndex, f32)> { + LABOR + .get(DUMMY_LABOR.0 as usize) + .map_or(Vec::new(), |l| l.orders.clone()) + } + pub fn orders(&self) -> Vec<(GoodIndex, f32)> { LABOR .get(self.0 as usize) diff --git a/world/src/site/economy/mod.rs b/world/src/site/economy/mod.rs index 8d3e3a9edf..350542a935 100644 --- a/world/src/site/economy/mod.rs +++ b/world/src/site/economy/mod.rs @@ -1,3 +1,6 @@ +/// This file contains a single economy +/// and functions to simulate it +use crate::world_msg::EconomyInfo; use crate::{ sim::SimChunk, site::Site, @@ -8,51 +11,56 @@ use common::{ terrain::BiomeKind, trade::{Good, SitePrices}, }; +use hashbrown::HashMap; use lazy_static::lazy_static; use std::{cmp::Ordering::Less, convert::TryFrom}; use tracing::{debug, info, trace, warn}; use Good::*; mod map_types; -pub use map_types::{GoodIndex, GoodMap, Labor, LaborIndex, LaborMap, NaturalResources}; +pub use map_types::Labor; +use map_types::{GoodIndex, GoodMap, LaborIndex, LaborMap, NaturalResources}; +mod context; +pub use context::simulate_economy; +use context::Environment; -pub const INTER_SITE_TRADE: bool = true; -pub const DAYS_PER_MONTH: f32 = 30.0; -pub const DAYS_PER_YEAR: f32 = 12.0 * DAYS_PER_MONTH; +const INTER_SITE_TRADE: bool = true; +const DAYS_PER_MONTH: f32 = 30.0; +const DAYS_PER_YEAR: f32 = 12.0 * DAYS_PER_MONTH; const GENERATE_CSV: bool = false; #[derive(Debug)] pub struct TradeOrder { - pub customer: Id, - pub amount: GoodMap, // positive for orders, negative for exchange + customer: Id, + amount: GoodMap, // positive for orders, negative for exchange } #[derive(Debug)] pub struct TradeDelivery { - pub supplier: Id, - pub amount: GoodMap, // positive for orders, negative for exchange - pub prices: GoodMap, // at the time of interaction - pub supply: GoodMap, // maximum amount available, at the time of interaction + supplier: Id, + amount: GoodMap, // positive for orders, negative for exchange + prices: GoodMap, // at the time of interaction + supply: GoodMap, // maximum amount available, at the time of interaction } #[derive(Debug, Default)] pub struct TradeInformation { - pub orders: DHashMap, Vec>, // per provider - pub deliveries: DHashMap, Vec>, // per receiver + orders: DHashMap, Vec>, // per provider + deliveries: DHashMap, Vec>, // per receiver } #[derive(Debug)] pub struct NeighborInformation { - pub id: Id, - pub travel_distance: usize, + id: Id, + //travel_distance: usize, // remembered from last interaction - pub last_values: GoodMap, - pub last_supplies: GoodMap, + last_values: GoodMap, + last_supplies: GoodMap, } lazy_static! { - pub static ref COIN_INDEX: GoodIndex = Coin.try_into().unwrap_or_default(); + static ref COIN_INDEX: GoodIndex = Coin.try_into().unwrap_or_default(); static ref FOOD_INDEX: GoodIndex = Good::Food.try_into().unwrap_or_default(); static ref TRANSPORTATION_INDEX: GoodIndex = Transportation.try_into().unwrap_or_default(); } @@ -60,47 +68,47 @@ lazy_static! { #[derive(Debug)] pub struct Economy { /// Population - pub pop: f32, - pub population_limited_by: GoodIndex, + pop: f32, + population_limited_by: GoodIndex, /// Total available amount of each good - pub stocks: GoodMap, + stocks: GoodMap, /// Surplus stock compared to demand orders - pub surplus: GoodMap, + surplus: GoodMap, /// change rate (derivative) of stock in the current situation - pub marginal_surplus: GoodMap, + marginal_surplus: GoodMap, /// amount of wares not needed by the economy (helps with trade planning) - pub unconsumed_stock: GoodMap, + unconsumed_stock: GoodMap, /// Local availability of a good, 4.0 = starved, 2.0 = balanced, 0.1 = /// extra, NULL = way too much // For some goods, such a goods without any supply, it doesn't make sense to talk about value - pub values: GoodMap>, + values: GoodMap>, /// amount of goods exported/imported during the last cycle - pub last_exports: GoodMap, - pub active_exports: GoodMap, // unfinished trade (amount unconfirmed) + last_exports: GoodMap, + active_exports: GoodMap, // unfinished trade (amount unconfirmed) //pub export_targets: GoodMap, /// amount of labor that went into a good, [1 man cycle=1.0] - pub labor_values: GoodMap>, + labor_values: GoodMap>, // this assumes a single source, replace with LaborMap? - pub material_costs: GoodMap, + material_costs: GoodMap, /// Proportion of individuals dedicated to an industry (sums to roughly 1.0) - pub labors: LaborMap, + labors: LaborMap, // Per worker, per year, of their output good - pub yields: LaborMap, + yields: LaborMap, /// [0.0..1.0] - pub productivity: LaborMap, + productivity: LaborMap, /// Missing raw material which limits production - pub limited_by: LaborMap, + limited_by: LaborMap, - pub natural_resources: NaturalResources, + natural_resources: NaturalResources, /// Neighboring sites to trade with - pub neighbors: Vec, + neighbors: Vec, /// outgoing trade, per provider - pub orders: DHashMap, Vec>, + orders: DHashMap, Vec>, /// incoming trade - only towards this site - pub deliveries: Vec, + deliveries: Vec, } impl Default for Economy { @@ -136,10 +144,58 @@ impl Default for Economy { } impl Economy { - pub const MINIMUM_PRICE: f32 = 0.1; - pub const STARTING_COIN: f32 = 1000.0; + const MINIMUM_PRICE: f32 = 0.1; + const STARTING_COIN: f32 = 1000.0; const _NATURAL_RESOURCE_SCALE: f32 = 1.0 / 9.0; + pub fn population(&self) -> f32 { self.pop } + + pub fn get_available_stock(&self) -> HashMap { + self.unconsumed_stock + .iter() + .map(|(g, a)| (g.into(), *a)) + .collect() + } + + pub fn get_information(&self, id: Id) -> EconomyInfo { + EconomyInfo { + id: id.id(), + population: self.pop.floor() as u32, + stock: self + .stocks + .iter() + .map(|(g, a)| (Good::from(g), *a)) + .collect(), + labor_values: self + .labor_values + .iter() + .filter_map(|(g, a)| a.map(|a| (Good::from(g), a))) + .collect(), + values: self + .values + .iter() + .filter_map(|(g, a)| a.map(|a| (Good::from(g), a))) + .collect(), + labors: self.labors.iter().map(|(_, a)| (*a)).collect(), + last_exports: self + .last_exports + .iter() + .map(|(g, a)| (Good::from(g), *a)) + .collect(), + resources: self + .natural_resources + .chunks_per_resource + .iter() + .map(|(g, a)| { + ( + Good::from(g), + ((*a) as f32) * self.natural_resources.average_yield_per_chunk[g], + ) + }) + .collect(), + } + } + pub fn cache_economy(&mut self) { for g in good_list() { let amount: f32 = self @@ -161,13 +217,22 @@ impl Economy { } } - pub fn get_orders(&self) -> DHashMap, Vec<(GoodIndex, f32)>> { - Labor::list_full() - .map(|l| (if l.is_everyone() { None } else { Some(l) }, l.orders())) - .collect() + /// orders per profession (excluding everyone) + fn get_orders(&self) -> &'static LaborMap> { + lazy_static! { + static ref ORDERS: LaborMap> = { + let mut res = LaborMap::default(); + res.iter_mut().for_each(|(i, e)| *e = i.orders()); + res + }; + } + &ORDERS } - pub fn get_productivity(&self) -> LaborMap<(GoodIndex, f32)> { + /// resources consumed by everyone (no matter which profession) + fn get_orders_everyone(&self) -> Vec<(GoodIndex, f32)> { Labor::orders_everyone() } + + fn get_production(&self) -> LaborMap<(GoodIndex, f32)> { // cache the site independent part of production lazy_static! { static ref PRODUCTS: LaborMap<(GoodIndex, f32)> = LaborMap::from_iter( @@ -187,7 +252,7 @@ impl Economy { }) } - pub fn replenish(&mut self, _time: f32) { + fn replenish(&mut self, _time: f32) { for (good, &ch) in self.natural_resources.chunks_per_resource.iter() { let per_year = self.natural_resources.average_yield_per_chunk[good] * ch; self.stocks[good] = self.stocks[good].max(per_year); @@ -229,11 +294,10 @@ impl Economy { } } - pub fn add_neighbor(&mut self, id: Id, distance: usize) { + pub fn add_neighbor(&mut self, id: Id, _distance: usize) { self.neighbors.push(NeighborInformation { id, - travel_distance: distance, - + //travel_distance: distance, last_values: GoodMap::from_default(Economy::MINIMUM_PRICE), last_supplies: Default::default(), }); @@ -511,17 +575,17 @@ impl Economy { let internal_orders = self.get_orders(); let mut next_demand = GoodMap::from_default(0.0); - for (labor, orders) in &internal_orders { - let workers = if let Some(labor) = labor { - self.labors[*labor] - } else { - 1.0 - } * self.pop; + for (labor, orders) in internal_orders.iter() { + let workers = self.labors[labor] * self.pop; for (good, amount) in orders { next_demand[*good] += *amount * workers; assert!(next_demand[*good] >= 0.0); } } + for (good, amount) in self.get_orders_everyone().iter() { + next_demand[*good] += *amount * self.pop; + assert!(next_demand[*good] >= 0.0); + } //info!("Trade {} {}", site.id(), orders.len()); let mut total_orders: GoodMap = GoodMap::from_default(0.0); for i in orders.iter() { @@ -717,72 +781,64 @@ impl Economy { /// product becomes available through a mechanism such as trade, an /// entire arm of the economy may materialise to take advantage of this. - pub fn tick( - &mut self, - //deliveries: Option<&mut Vec>, - site_id: Id, - dt: f32, - mut vc: vergleich::Context, - ) { + pub fn tick(&mut self, site_id: Id, dt: f32) { // collect goods from trading if INTER_SITE_TRADE { - // if let Some(deliveries) = deliveries { self.collect_deliveries(); - // } } let orders = self.get_orders(); - let productivity = self.get_productivity(); + let production = self.get_production(); - for i in productivity.iter() { - vc.context("productivity") - .value(&std::format!("{:?}{:?}", i.0, Good::from(i.1.0)), i.1.1); - } + // for i in production.iter() { + // vc.context("production") + // .value(&std::format!("{:?}{:?}", i.0, Good::from(i.1.0)), i.1.1); + // } let mut demand = GoodMap::from_default(0.0); - for (labor, orders) in &orders { - let workers = if let Some(labor) = labor { - self.labors[*labor] - } else { - 1.0 - } * self.pop; + for (labor, orders) in orders.iter() { + let workers = self.labors[labor] * self.pop; for (good, amount) in orders { demand[*good] += *amount * workers; } } + for (good, amount) in self.get_orders_everyone().iter() { + demand[*good] += *amount * self.pop; + } if INTER_SITE_TRADE { demand[*COIN_INDEX] += Economy::STARTING_COIN; // if we spend coin value increases } // which labor is the merchant - let merchant_labor = productivity + let merchant_labor = production .iter() .find(|(_, v)| v.0 == *TRANSPORTATION_INDEX) - .map(|(l, _)| l); + .map(|(l, _)| l) + .unwrap_or_default(); let mut supply = self.stocks; //GoodMap::from_default(0.0); - for (labor, goodvec) in productivity.iter() { + for (labor, goodvec) in production.iter() { //for (output_good, _) in goodvec.iter() { //info!("{} supply{:?}+={}", site_id.id(), Good::from(goodvec.0), // self.yields[labor] * self.labors[labor] * self.pop); supply[goodvec.0] += self.yields[labor] * self.labors[labor] * self.pop; - vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0), labor)) - .value("yields", self.yields[labor]); - vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0), labor)) - .value("labors", self.labors[labor]); + // vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0), + // labor)) .value("yields", self.yields[labor]); + // vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0), + // labor)) .value("labors", self.labors[labor]); //} } - for i in supply.iter() { - vc.context("supply") - .value(&std::format!("{:?}", Good::from(i.0)), *i.1); - } + // for i in supply.iter() { + // vc.context("supply") + // .value(&std::format!("{:?}", Good::from(i.0)), *i.1); + // } let stocks = &self.stocks; - for i in stocks.iter() { - vc.context("stocks") - .value(&std::format!("{:?}", Good::from(i.0)), *i.1); - } + // for i in stocks.iter() { + // vc.context("stocks") + // .value(&std::format!("{:?}", Good::from(i.0)), *i.1); + // } self.surplus = demand.map(|g, demand| supply[g] + stocks[g] - demand); self.marginal_surplus = demand.map(|g, demand| supply[g] - demand); @@ -793,18 +849,14 @@ impl Economy { // this is in line with the other professions) let transportation_capacity = self.stocks[*TRANSPORTATION_INDEX]; let trade = if INTER_SITE_TRADE { - let trade = self.plan_trade_for_site( - &site_id, - transportation_capacity, - // external_orders, - &mut potential_trade, - ); + let trade = + self.plan_trade_for_site(&site_id, transportation_capacity, &mut potential_trade); self.active_exports = GoodMap::from_iter(trade.iter().map(|(g, a)| (g, -*a)), 0.0); // TODO: check for availability? // add the wares to sell to demand and the goods to buy to supply for (g, a) in trade.iter() { - vc.context("trade") - .value(&std::format!("{:?}", Good::from(g)), *a); + // vc.context("trade") + // .value(&std::format!("{:?}", Good::from(g)), *a); if *a > 0.0 { supply[g] += *a; assert!(supply[g] >= 0.0); @@ -832,16 +884,17 @@ impl Economy { *surplus }; // Value rationalisation - let goodname = std::format!("{:?}", Good::from(good)); - vc.context("old_surplus").value(&goodname, old_surplus); - vc.context("demand").value(&goodname, demand[good]); + // let goodname = std::format!("{:?}", Good::from(good)); + // vc.context("old_surplus").value(&goodname, old_surplus); + // vc.context("demand").value(&goodname, demand[good]); let val = 2.0f32.powf(1.0 - old_surplus / demand[good]); let smooth = 0.8; values[good] = if val > 0.001 && val < 1000.0 { - Some(vc.context("values").value( - &goodname, + Some( + // vc.context("values").value( + // &goodname, smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val, - )) + ) } else { None }; @@ -858,10 +911,10 @@ impl Economy { // summing favors merchants too much (as they will provide multiple // goods, so we use max instead) let labor_ratios: LaborMap = LaborMap::from_iter( - productivity.iter().map(|(labor, goodvec)| { + production.iter().map(|(labor, goodvec)| { ( labor, - if Some(labor) == merchant_labor { + if labor == merchant_labor { all_trade_goods .iter() .chain(std::iter::once(&goodvec.0)) @@ -879,15 +932,15 @@ impl Economy { trace!(?labor_ratios); let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::().max(0.01); - let mut labor_context = vc.context("labor"); - productivity.iter().for_each(|(labor, _)| { + //let mut labor_context = vc.context("labor"); + production.iter().for_each(|(labor, _)| { let smooth = 0.8; - self.labors[labor] = labor_context.value( - &format!("{:?}", labor), + self.labors[labor] = + // labor_context.value( + // &format!("{:?}", labor), smooth * self.labors[labor] + (1.0 - smooth) - * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum), - ); + * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum); assert!(self.labors[labor] >= 0.0); }); @@ -905,13 +958,9 @@ impl Economy { // TODO: trade let mut total_outputs = GoodMap::::default(); for (labor, orders) in orders.iter() { - let workers = if let Some(labor) = labor { - self.labors[*labor] - } else { - 1.0 - } * self.pop; + let workers = self.labors[labor] * self.pop; assert!(workers >= 0.0); - let is_merchant = merchant_labor == *labor; + let is_merchant = merchant_labor == labor; // For each order, we try to find the minimum satisfaction rate - this limits // how much we can produce! For example, if we need 0.25 fish and @@ -934,13 +983,11 @@ impl Economy { panic!("Industry {:?} requires at least one input order", labor) }); assert!(labor_productivity >= 0.0); - if let Some(labor) = labor { - self.limited_by[*labor] = if labor_productivity >= 1.0 { - GoodIndex::default() - } else { - limited_by - }; - } + self.limited_by[labor] = if labor_productivity >= 1.0 { + GoodIndex::default() + } else { + limited_by + }; let mut total_materials_cost = 0.0; for (good, amount) in orders { @@ -1012,38 +1059,39 @@ impl Economy { } // Industries produce things - if let Some(labor) = labor { - let work_products = &productivity[*labor]; - //let workers = self.labors[*labor] * self.pop; - //let final_rate = rate; - //let yield_per_worker = labor_productivity; - self.yields[*labor] = labor_productivity * work_products.1; - self.productivity[*labor] = labor_productivity; - //let total_product_rate: f32 = work_products.iter().map(|(_, r)| *r).sum(); - let (stock, rate) = work_products; - let total_output = labor_productivity * *rate * workers; - assert!(total_output >= 0.0); - self.stocks[*stock] += total_output; - produced_goods[*stock] += total_output; + let work_products = &production[labor]; + self.yields[labor] = labor_productivity * work_products.1; + self.productivity[labor] = labor_productivity; + let (stock, rate) = work_products; + let total_output = labor_productivity * *rate * workers; + assert!(total_output >= 0.0); + self.stocks[*stock] += total_output; + produced_goods[*stock] += total_output; - let produced_amount: f32 = produced_goods.iter().map(|(_, a)| *a).sum(); - for (stock, amount) in produced_goods.iter() { - let cost_weight = amount / produced_amount.max(0.001); - // Materials cost per unit - // TODO: How to handle this reasonably for multiple producers (collect upper and - // lower term separately) - self.material_costs[stock] = - total_materials_cost / amount.max(0.001) * cost_weight; - // Labor costs - let wages = 1.0; - let total_labor_cost = workers * wages; + let produced_amount: f32 = produced_goods.iter().map(|(_, a)| *a).sum(); + for (stock, amount) in produced_goods.iter() { + let cost_weight = amount / produced_amount.max(0.001); + // Materials cost per unit + // TODO: How to handle this reasonably for multiple producers (collect upper and + // lower term separately) + self.material_costs[stock] = total_materials_cost / amount.max(0.001) * cost_weight; + // Labor costs + let wages = 1.0; + let total_labor_cost = workers * wages; - total_labor_values[stock] += - (total_materials_cost + total_labor_cost) * cost_weight; - total_outputs[stock] += amount; - } + total_labor_values[stock] += + (total_materials_cost + total_labor_cost) * cost_weight; + total_outputs[stock] += amount; } } + // consume goods needed by everyone + for &(good, amount) in self.get_orders_everyone().iter() { + let needed = amount * self.pop; + let available = stocks_before[good]; + self.stocks[good] = (self.stocks[good] - needed.min(available)).max(0.0); + //info!("Ev {:.1} {:?} {} - {:.1} {:.1}", self.pop, good, + // self.stocks[good], needed, available); + } // Update labour values per unit self.labor_values = total_labor_values.map(|stock, tlv| { @@ -1073,10 +1121,10 @@ impl Economy { } else { 0.0 }; - self.pop += vc.value( - "pop", - dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE), - ); + self.pop += //vc.value( + //"pop", + dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE); + //); self.population_limited_by = if population_growth { GoodIndex::default() } else { @@ -1088,22 +1136,23 @@ impl Economy { // orders are static let mut next_demand = GoodMap::from_default(0.0); for (labor, orders) in orders.iter() { - let workers = if let Some(labor) = labor { - self.labors[*labor] - } else { - 1.0 - } * self.pop; + let workers = self.labors[labor] * self.pop; for (good, amount) in orders { next_demand[*good] += *amount * workers; assert!(next_demand[*good] >= 0.0); } } - let mut us = vc.context("unconsumed"); + for (good, amount) in self.get_orders_everyone().iter() { + next_demand[*good] += *amount * self.pop; + assert!(next_demand[*good] >= 0.0); + } + //let mut us = vc.context("unconsumed"); self.unconsumed_stock = GoodMap::from_iter( self.stocks.iter().map(|(g, a)| { ( g, - us.value(&format!("{:?}", Good::from(g)), *a - next_demand[g]), + //us.value(&format!("{:?}", Good::from(g)), + *a - next_demand[g], ) }), 0.0, @@ -1111,7 +1160,6 @@ impl Economy { } pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> { - use crate::site::economy::GoodIndex; use std::io::Write; write!( *f, @@ -1215,32 +1263,43 @@ impl Economy { for g in good_list() { write!(f, "{:?} trade,", g)?; } - writeln!(f)?; - Ok(()) + writeln!(f) + } - let mut f = if GENERATE_CSV { - let mut f = std::fs::File::create("economy.csv")?; - Some(f) + pub fn csv_open() -> Option { + if GENERATE_CSV { + let mut f = std::fs::File::create("economy.csv").ok()?; + if Self::csv_header(&mut f).is_err() { + None + } else { + Some(f) + } } else { None } } - - fn print_sorted(prefix: &str, mut list: Vec<(String, f32)>, threshold: f32, decimals: usize) { - print!("{}", prefix); - list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); - for i in list.iter() { - if i.1 >= threshold { - print!("{}={:.*} ", i.0, decimals, i.1); - } - } - println!(); - } - fn show_economy(sites: &common::store::Store) { + #[cfg(test)] + fn print_details(&self) { + fn print_sorted( + prefix: &str, + mut list: Vec<(String, f32)>, + threshold: f32, + decimals: usize, + ) { + print!("{}", prefix); + list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less)); + for i in list.iter() { + if i.1 >= threshold { + print!("{}={:.*} ", i.0, decimals, i.1); + } + } + println!(); + } + print!(" Resources: "); for i in good_list() { - let amount = site.economy.natural_resources.chunks_per_resource[i]; + let amount = self.natural_resources.chunks_per_resource[i]; if amount > 0.0 { print!("{:?}={} ", i, amount); } @@ -1248,24 +1307,21 @@ impl Economy { println!(); println!( " Population {:.1}, limited by {:?}", - site.economy.pop, site.economy.population_limited_by + self.pop, self.population_limited_by ); - let idle: f32 = - site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::()); + let idle: f32 = self.pop * (1.0 - self.labors.iter().map(|(_, a)| *a).sum::()); print_sorted( &format!(" Professions: idle={:.1} ", idle), - site.economy - .labors + self.labors .iter() - .map(|(l, a)| (format!("{:?}", l), *a * site.economy.pop)) + .map(|(l, a)| (format!("{:?}", l), *a * self.pop)) .collect(), - site.economy.pop * 0.05, + self.pop * 0.05, 1, ); print_sorted( " Stock: ", - site.economy - .stocks + self.stocks .iter() .map(|(l, a)| (format!("{:?}", l), *a)) .collect(), @@ -1274,8 +1330,7 @@ impl Economy { ); print_sorted( " Values: ", - site.economy - .values + self.values .iter() .map(|(l, a)| { ( @@ -1289,8 +1344,7 @@ impl Economy { ); print_sorted( " Labor Values: ", - site.economy - .labor_values + self.labor_values .iter() .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0))) .collect(), @@ -1298,36 +1352,28 @@ impl Economy { 1, ); print!(" Limited: "); - for (limit, prod) in site - .economy - .limited_by - .iter() - .zip(site.economy.productivity.iter()) - { + for (limit, prod) in self.limited_by.iter().zip(self.productivity.iter()) { if (0.01..=0.99).contains(prod.1) { - print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1); + print!("{:?}:{:?}={:.2} ", limit.0, limit.1, *prod.1); } } println!(); - print!(" Trade({}): ", site.economy.neighbors.len()); - for (g, &amt) in site.economy.active_exports.iter() { + print!(" Trade({}): ", self.neighbors.len()); + for (g, &amt) in self.active_exports.iter() { if !(-0.1..=0.1).contains(&amt) { print!("{:?}={:.2} ", g, amt); } } println!(); - // check population (shrinks if economy gets broken) - // assert!(site.economy.pop >= env.targets[&id]); - } } -pub fn good_list() -> impl Iterator { +fn good_list() -> impl Iterator { (0..GoodIndex::LENGTH).map(GoodIndex::from_usize) } // cache in GoodMap ? -pub fn transportation_effort(g: GoodIndex) -> f32 { +fn transportation_effort(g: GoodIndex) -> f32 { match Good::from(g) { Terrain(_) | Territory(_) | RoadSecurity => 0.0, Coin => 0.01, @@ -1340,7 +1386,7 @@ pub fn transportation_effort(g: GoodIndex) -> f32 { } } -pub fn decay_rate(g: GoodIndex) -> f32 { +fn decay_rate(g: GoodIndex) -> f32 { match Good::from(g) { Food => 0.2, Flour => 0.1, @@ -1351,7 +1397,7 @@ pub fn decay_rate(g: GoodIndex) -> f32 { } /** you can't accumulate or save these options/resources for later */ -pub fn direct_use_goods() -> &'static [GoodIndex] { +fn direct_use_goods() -> &'static [GoodIndex] { lazy_static! { static ref DIRECT_USE: [GoodIndex; 13] = [ GoodIndex::try_from(Transportation).unwrap_or_default(), @@ -1371,3 +1417,29 @@ pub fn direct_use_goods() -> &'static [GoodIndex] { } &*DIRECT_USE } + +pub struct GraphInfo { + dummy: Economy, +} + +impl GraphInfo { + pub fn get_orders(&self) -> &'static LaborMap> { self.dummy.get_orders() } + + pub fn get_orders_everyone(&self) -> Vec<(GoodIndex, f32)> { self.dummy.get_orders_everyone() } + + pub fn get_production(&self) -> LaborMap<(GoodIndex, f32)> { self.dummy.get_production() } + + pub fn good_list(&self) -> impl Iterator { good_list() } + + pub fn labor_list(&self) -> impl Iterator { Labor::list() } + + pub fn can_store(&self, g: &GoodIndex) -> bool { direct_use_goods().contains(g) } +} + +impl Default for GraphInfo { + fn default() -> Self { + Self { + dummy: Economy::default(), + } + } +} diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 0c445feaff..dd8a06bf87 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -187,12 +187,7 @@ impl Site { SiteKind::Settlement(_) | SiteKind::Refactor(_) | SiteKind::CliffTown(_) => { Some(common::trade::SiteInformation { id: site_id, - unconsumed_stock: self - .economy - .unconsumed_stock - .iter() - .map(|(g, a)| (g.into(), *a)) - .collect(), + unconsumed_stock: self.economy.get_available_stock(), }) }, _ => None,