separate orders by professions and everyone

cleanup and fix
strictly this population growth logic is wrong, but identical to the existing one
standardize on production, remove more dead code
fix example + rustfmt
separate csv logic from tick (intention is to move it into economy)
remove the format call from economy (inactive debugging code)
remove more formatting
rustfmt
small clippy fix
Reduce precision on output
move csv code
move rest of sim2 mod into economy (context)
remove more unused parts
keep things a bit more coherent
remove pub
make more functions and fields private
remove outdated input, fix other, print names, move output to proper abstraction
remove dead code
This commit is contained in:
Christof Petig 2022-06-01 21:09:31 +02:00
parent edffd576d4
commit 6d3ea3172c
8 changed files with 505 additions and 635 deletions

View File

@ -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: [],
),
]

View File

@ -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: [
(

View File

@ -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,19 +31,16 @@ 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)
{
let style = if matches!(j.0.into(), Good::Tools | Good::Armor | Good::Potions) {
", style=dashed, color=orange"
} else {
""
@ -54,11 +49,13 @@ fn main() -> Result<(), std::io::Error> {
f,
"{:?} -> {:?} [label=\"{:.1}\"{}];",
good_name(j.0.into()),
labor_name(i.0.unwrap()),
labor_name(i.0),
j.1,
style
)?;
} else {
}
}
for j in eco.get_orders_everyone().iter() {
writeln!(
f,
"{:?} -> Everyone [label=\"{:.1}\"];",
@ -66,11 +63,10 @@ fn main() -> Result<(), std::io::Error> {
j.1
)?;
}
}
}
writeln!(f)?;
writeln!(f, "// Products")?;
let p = eco.get_production();
for i in p.iter() {
writeln!(
f,

View File

@ -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); }

View File

@ -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<Self, Error> { 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<Self, Error> { 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,21 +79,43 @@ 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)?;
pub struct Environment {
csv_file: Option<std::fs::File>,
// context: vergleich::ProgramRun,
}
impl Environment {
pub fn new() -> Result<Self, std::io::Error> {
// 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 */
})
}
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);
csv_entry(f, 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();
let giant_trees = EconStatistics::default();
for site in index.sites.ids() {
let site = &index.sites[site];
match site.kind {
@ -135,70 +153,71 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i
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<u64>, // id
resources: Vec<ResourcesSetup>,
}
fn show_economy(sites: &common::store::Store<crate::site::Site>) {
use crate::site::economy::good_list;
fn show_economy(
sites: &common::store::Store<crate::site::Site>,
names: &Option<HashMap<Id<crate::site::Site>, 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<EconomySetup> =
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<crate::site::economy::NeighborInformation> = 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<Id<crate::site::Site>, f32>,
targets: HashMap<Id<crate::site::Site>, f32>,
names: HashMap<Id<crate::site::Site>, 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<crate::site::Site> {
@ -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]);

View File

@ -194,7 +194,7 @@ pub struct LaborMap<V> {
data: Vec<V>,
}
impl<V: Default + Copy> Default for LaborMap<V> {
impl<V: Default + Clone> Default for LaborMap<V> {
fn default() -> Self {
LaborMap {
data: std::iter::repeat(V::default()).take(*LABOR_COUNT).collect(),
@ -233,6 +233,13 @@ impl<V> LaborMap<V> {
.enumerate()
.map(|(idx, v)| (LaborIndex::from_usize(idx), v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (LaborIndex, &mut V)> + '_ {
(&mut self.data)
.iter_mut()
.enumerate()
.map(|(idx, v)| (LaborIndex::from_usize(idx), v))
}
}
impl<V: Copy + Default> LaborMap<V> {
@ -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)

View File

@ -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<Site>,
pub amount: GoodMap<f32>, // positive for orders, negative for exchange
customer: Id<Site>,
amount: GoodMap<f32>, // positive for orders, negative for exchange
}
#[derive(Debug)]
pub struct TradeDelivery {
pub supplier: Id<Site>,
pub amount: GoodMap<f32>, // positive for orders, negative for exchange
pub prices: GoodMap<f32>, // at the time of interaction
pub supply: GoodMap<f32>, // maximum amount available, at the time of interaction
supplier: Id<Site>,
amount: GoodMap<f32>, // positive for orders, negative for exchange
prices: GoodMap<f32>, // at the time of interaction
supply: GoodMap<f32>, // maximum amount available, at the time of interaction
}
#[derive(Debug, Default)]
pub struct TradeInformation {
pub orders: DHashMap<Id<Site>, Vec<TradeOrder>>, // per provider
pub deliveries: DHashMap<Id<Site>, Vec<TradeDelivery>>, // per receiver
orders: DHashMap<Id<Site>, Vec<TradeOrder>>, // per provider
deliveries: DHashMap<Id<Site>, Vec<TradeDelivery>>, // per receiver
}
#[derive(Debug)]
pub struct NeighborInformation {
pub id: Id<Site>,
pub travel_distance: usize,
id: Id<Site>,
//travel_distance: usize,
// remembered from last interaction
pub last_values: GoodMap<f32>,
pub last_supplies: GoodMap<f32>,
last_values: GoodMap<f32>,
last_supplies: GoodMap<f32>,
}
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<f32>,
stocks: GoodMap<f32>,
/// Surplus stock compared to demand orders
pub surplus: GoodMap<f32>,
surplus: GoodMap<f32>,
/// change rate (derivative) of stock in the current situation
pub marginal_surplus: GoodMap<f32>,
marginal_surplus: GoodMap<f32>,
/// amount of wares not needed by the economy (helps with trade planning)
pub unconsumed_stock: GoodMap<f32>,
unconsumed_stock: GoodMap<f32>,
/// 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<Option<f32>>,
values: GoodMap<Option<f32>>,
/// amount of goods exported/imported during the last cycle
pub last_exports: GoodMap<f32>,
pub active_exports: GoodMap<f32>, // unfinished trade (amount unconfirmed)
last_exports: GoodMap<f32>,
active_exports: GoodMap<f32>, // unfinished trade (amount unconfirmed)
//pub export_targets: GoodMap<f32>,
/// amount of labor that went into a good, [1 man cycle=1.0]
pub labor_values: GoodMap<Option<f32>>,
labor_values: GoodMap<Option<f32>>,
// this assumes a single source, replace with LaborMap?
pub material_costs: GoodMap<f32>,
material_costs: GoodMap<f32>,
/// Proportion of individuals dedicated to an industry (sums to roughly 1.0)
pub labors: LaborMap<f32>,
labors: LaborMap<f32>,
// Per worker, per year, of their output good
pub yields: LaborMap<f32>,
yields: LaborMap<f32>,
/// [0.0..1.0]
pub productivity: LaborMap<f32>,
productivity: LaborMap<f32>,
/// Missing raw material which limits production
pub limited_by: LaborMap<GoodIndex>,
limited_by: LaborMap<GoodIndex>,
pub natural_resources: NaturalResources,
natural_resources: NaturalResources,
/// Neighboring sites to trade with
pub neighbors: Vec<NeighborInformation>,
neighbors: Vec<NeighborInformation>,
/// outgoing trade, per provider
pub orders: DHashMap<Id<Site>, Vec<TradeOrder>>,
orders: DHashMap<Id<Site>, Vec<TradeOrder>>,
/// incoming trade - only towards this site
pub deliveries: Vec<TradeDelivery>,
deliveries: Vec<TradeDelivery>,
}
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<Good, f32> {
self.unconsumed_stock
.iter()
.map(|(g, a)| (g.into(), *a))
.collect()
}
pub fn get_information(&self, id: Id<Site>) -> 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<Option<LaborIndex>, 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<Vec<(GoodIndex, f32)>> {
lazy_static! {
static ref ORDERS: LaborMap<Vec<(GoodIndex, f32)>> = {
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<Site>, distance: usize) {
pub fn add_neighbor(&mut self, id: Id<Site>, _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<f32> = 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<TradeDelivery>>,
site_id: Id<Site>,
dt: f32,
mut vc: vergleich::Context,
) {
pub fn tick(&mut self, site_id: Id<Site>, 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<f32> = 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::<f32>().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::<f32>::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 {
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,14 +1059,9 @@ 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 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);
@ -1032,8 +1074,7 @@ impl Economy {
// 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;
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;
@ -1043,6 +1084,13 @@ impl Economy {
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
@ -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,18 +1263,30 @@ 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")?;
pub fn csv_open() -> Option<std::fs::File> {
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) {
#[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() {
@ -1237,10 +1297,9 @@ impl Economy {
println!();
}
fn show_economy(sites: &common::store::Store<crate::site::Site>) {
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::<f32>());
let idle: f32 = self.pop * (1.0 - self.labors.iter().map(|(_, a)| *a).sum::<f32>());
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<Item = GoodIndex> {
fn good_list() -> impl Iterator<Item = GoodIndex> {
(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<Vec<(GoodIndex, f32)>> { 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<Item = GoodIndex> { good_list() }
pub fn labor_list(&self) -> impl Iterator<Item = Labor> { 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(),
}
}
}

View File

@ -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,