Merge branch 'christof/shredded_economy3' into 'master'

Economic debugging and documentation

See merge request veloren/veloren!3309
This commit is contained in:
Marcel 2022-05-01 18:26:29 +00:00
commit e30845c68f
2 changed files with 468 additions and 152 deletions

View File

@ -57,35 +57,68 @@ impl EconStatistics {
} }
pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> { pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> {
use crate::site::economy::GoodIndex;
use std::io::Write; use std::io::Write;
write!( write!(
*f, *f,
"{}, {}, {}, {},", "{}, {}, {}, {:.1}, {},,",
site.name(), site.name(),
site.get_origin().x, site.get_origin().x,
site.get_origin().y, site.get_origin().y,
site.economy.pop site.economy.pop,
site.economy.neighbors.len(),
)?; )?;
for g in good_list() { for g in good_list() {
write!(*f, "{:?},", site.economy.values[g].unwrap_or(-1.0))?; if let Some(value) = site.economy.values[g] {
write!(*f, "{:.2},", value)?;
} else {
f.write_all(b",")?;
}
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?},", site.economy.labor_values[g].unwrap_or(-1.0))?; if let Some(labor_value) = site.economy.labor_values[g] {
write!(f, "{:.2},", labor_value)?;
} else {
f.write_all(b",")?;
}
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?},", site.economy.stocks[g])?; write!(f, "{:.1},", site.economy.stocks[g])?;
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?},", site.economy.marginal_surplus[g])?; write!(f, "{:.1},", site.economy.marginal_surplus[g])?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?},", site.economy.labors[l] * site.economy.pop)?; write!(f, "{:.1},", site.economy.labors[l] * site.economy.pop)?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?},", site.economy.productivity[l])?; write!(f, "{:.2},", site.economy.productivity[l])?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?},", site.economy.yields[l])?; write!(f, "{:.1},", site.economy.yields[l])?;
}
f.write_all(b",")?;
for l in LaborIndex::list() {
let limit = site.economy.limited_by[l];
if limit == GoodIndex::default() {
f.write_all(b",")?;
} else {
write!(f, "{:?},", limit)?;
}
}
f.write_all(b",")?;
for g in good_list() {
if site.economy.last_exports[g] >= 0.1 || site.economy.last_exports[g] <= -0.1 {
write!(f, "{:.1},", site.economy.last_exports[g])?;
} else {
f.write_all(b",")?;
}
} }
writeln!(f) writeln!(f)
} }
@ -96,28 +129,42 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i
// here // here
let mut f = if GENERATE_CSV { let mut f = if GENERATE_CSV {
let mut f = std::fs::File::create("economy.csv")?; let mut f = std::fs::File::create("economy.csv")?;
write!(f, "Site,PosX,PosY,Population,")?; write!(f, "Site,PosX,PosY,Population,Neighbors,,")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?} Value,", g)?; write!(f, "{:?} Value,", g)?;
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?} LaborVal,", g)?; write!(f, "{:?} LaborVal,", g)?;
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?} Stock,", g)?; write!(f, "{:?} Stock,", g)?;
} }
f.write_all(b",")?;
for g in good_list() { for g in good_list() {
write!(f, "{:?} Surplus,", g)?; write!(f, "{:?} Surplus,", g)?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?} Labor,", l)?; write!(f, "{:?} Labor,", l)?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?} Productivity,", l)?; write!(f, "{:?} Productivity,", l)?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() { for l in LaborIndex::list() {
write!(f, "{:?} Yields,", l)?; write!(f, "{:?} Yields,", l)?;
} }
f.write_all(b",")?;
for l in LaborIndex::list() {
write!(f, "{:?} limit,", l)?;
}
f.write_all(b",")?;
for g in good_list() {
write!(f, "{:?} trade,", g)?;
}
writeln!(f)?; writeln!(f)?;
Some(f) Some(f)
} else { } else {
@ -282,24 +329,25 @@ pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32, _vc: vergleich::C
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{sim, site::economy::GoodMap, util::seed_expan}; use crate::{sim, site::economy::GoodMap, util::seed_expan};
use common::trade::Good; use common::{store::Id, terrain::BiomeKind, trade::Good};
use rand::SeedableRng; use rand::SeedableRng;
use rand_chacha::ChaChaRng; use rand_chacha::ChaChaRng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryInto; use std::convert::TryInto;
use tracing::{info, Level}; use tracing::{info, Dispatch, Level};
use tracing_subscriber::{ use tracing_subscriber::{filter::EnvFilter, FmtSubscriber};
filter::{EnvFilter, LevelFilter},
FmtSubscriber,
};
use vek::Vec2; use vek::Vec2;
// enable info! fn execute_with_tracing(level: Level, func: fn()) {
fn init() { tracing::dispatcher::with_default(
FmtSubscriber::builder() &Dispatch::new(
.with_max_level(Level::ERROR) FmtSubscriber::builder()
.with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into())) .with_max_level(level)
.init(); .with_env_filter(EnvFilter::from_default_env())
.finish(),
),
func,
);
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -317,131 +365,375 @@ mod tests {
resources: Vec<ResourcesSetup>, resources: Vec<ResourcesSetup>,
} }
#[test] fn print_sorted(prefix: &str, mut list: Vec<(String, f32)>, threshold: f32, decimals: usize) {
fn test_economy() { print!("{}", prefix);
init(); list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Less));
let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); for i in list.iter() {
info!("init"); if i.1 >= threshold {
let seed = 59686; print!("{}={:.*} ", i.0, decimals, i.1);
let opts = sim::WorldOpts {
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
//sim::FileOpts::LoadAsset("world.map.economy_8x8".into()),
calendar: None,
};
let mut index = crate::index::Index::new(seed);
info!("Index created");
let mut sim = sim::WorldSim::generate(seed, opts, &threadpool);
info!("World loaded");
let regenerate_input = false;
if regenerate_input {
let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index);
info!("Civs created");
let mut outarr: Vec<EconomySetup> = Vec::new();
for i in index.sites.values() {
let resources: Vec<ResourcesSetup> = i
.economy
.natural_resources
.chunks_per_resource
.iter()
.map(|(good, a)| ResourcesSetup {
good: good.into(),
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 val = EconomySetup {
name: i.name().into(),
position: (i.get_origin().x, i.get_origin().y),
resources,
neighbors,
kind: match i.kind {
crate::site::SiteKind::Settlement(_)
| crate::site::SiteKind::Refactor(_)
| crate::site::SiteKind::CliffTown(_) => {
common::terrain::site::SitesKind::Settlement
},
crate::site::SiteKind::Dungeon(_) => {
common::terrain::site::SitesKind::Dungeon
},
crate::site::SiteKind::Castle(_) => {
common::terrain::site::SitesKind::Castle
},
_ => common::terrain::site::SitesKind::Void,
},
};
outarr.push(val);
}
let pretty = ron::ser::PrettyConfig::new();
if let Ok(result) = ron::ser::to_string_pretty(&outarr, pretty) {
info!("RON {}", result);
}
} else {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let ron_file = std::fs::File::open("economy_testinput2.ron")
.expect("economy_testinput2.ron not found");
let econ_testinput: Vec<EconomySetup> =
ron::de::from_reader(ron_file).expect("economy_testinput2.ron parse error");
for i in econ_testinput.iter() {
let wpos = Vec2 {
x: i.position.0,
y: i.position.1,
};
// this should be a moderate compromise between regenerating the full world and
// loading on demand using the public API. There is no way to set
// the name, do we care?
let mut settlement = match i.kind {
common::terrain::site::SitesKind::Castle => crate::site::Site::castle(
crate::site::Castle::generate(wpos, None, &mut rng),
),
common::terrain::site::SitesKind::Dungeon => crate::site::Site::dungeon(
crate::site2::Site::generate_dungeon(&crate::Land::empty(), &mut rng, wpos),
),
// common::terrain::site::SitesKind::Settlement |
_ => crate::site::Site::settlement(crate::site::Settlement::generate(
wpos, None, &mut rng,
)),
};
for g in i.resources.iter() {
//let c = sim::SimChunk::new();
//settlement.economy.add_chunk(ch, distance_squared)
// bypass the API for now
settlement.economy.natural_resources.chunks_per_resource
[g.good.try_into().unwrap_or_default()] = g.amount;
settlement.economy.natural_resources.average_yield_per_chunk
[g.good.try_into().unwrap_or_default()] = 1.0;
}
index.sites.insert(settlement);
}
// 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);
}
} }
} }
crate::sim2::simulate(&mut index, &mut sim); println!();
}
fn show_economy(sites: &common::store::Store<crate::site::Site>) {
use crate::site::economy::good_list;
for (id, site) in sites.iter() {
println!("Site id {:?} name {}", id, site.name());
//assert!(site.economy.sanity_check());
print!(" Resources: ");
for i in good_list() {
let amount = site.economy.natural_resources.chunks_per_resource[i];
if amount > 0.0 {
print!("{:?}={} ", i, amount);
}
}
println!();
println!(
" Population {:.1}, limited by {:?}",
site.economy.pop, site.economy.population_limited_by
);
let idle: f32 =
site.economy.pop * (1.0 - site.economy.labors.iter().map(|(_, a)| *a).sum::<f32>());
print_sorted(
&format!(" Professions: idle={:.1} ", idle),
site.economy
.labors
.iter()
.map(|(l, a)| (format!("{:?}", l), *a * site.economy.pop))
.collect(),
site.economy.pop * 0.05,
1,
);
print_sorted(
" Stock: ",
site.economy
.stocks
.iter()
.map(|(l, a)| (format!("{:?}", l), *a))
.collect(),
1.0,
0,
);
print_sorted(
" Values: ",
site.economy
.values
.iter()
.map(|(l, a)| {
(
format!("{:?}", l),
a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0),
)
})
.collect(),
0.1,
1,
);
print_sorted(
" Labor Values: ",
site.economy
.labor_values
.iter()
.map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0)))
.collect(),
0.1,
1,
);
print!(" Limited: ");
for (limit, prod) in site
.economy
.limited_by
.iter()
.zip(site.economy.productivity.iter())
{
if (0.01..=0.99).contains(prod.1) {
print!("{:?}:{:?}={} ", limit.0, limit.1, *prod.1);
}
}
println!();
print!(" Trade({}): ", site.economy.neighbors.len());
for (g, &amt) in site.economy.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]);
}
}
/// output the economy of the currently active world
// this expensive test is for manual inspection, not to be run automated
// recommended command: cargo test test_economy0 -- --nocapture --ignored
#[test]
#[ignore]
fn test_economy0() {
execute_with_tracing(Level::INFO, || {
let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap();
info!("init");
let seed = 59686;
let opts = sim::WorldOpts {
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
//sim::FileOpts::LoadAsset("world.map.economy_8x8".into()),
calendar: None,
};
let mut index = crate::index::Index::new(seed);
info!("Index created");
let mut sim = sim::WorldSim::generate(seed, opts, &threadpool);
info!("World loaded");
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);
});
}
/// output the economy of a small set of villages, loaded from ron
// this cheaper test is for manual inspection, not to be run automated
#[test]
#[ignore]
fn test_economy1() {
execute_with_tracing(Level::INFO, || {
let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap();
info!("init");
let seed = 59686;
let opts = sim::WorldOpts {
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
//sim::FileOpts::LoadAsset("world.map.economy_8x8".into()),
calendar: None,
};
let mut index = crate::index::Index::new(seed);
info!("Index created");
let mut sim = sim::WorldSim::generate(seed, opts, &threadpool);
info!("World loaded");
let regenerate_input = false;
if regenerate_input {
let _civs = crate::civ::Civs::generate(seed, &mut sim, &mut index);
info!("Civs created");
let mut outarr: Vec<EconomySetup> = Vec::new();
for i in index.sites.values() {
let resources: Vec<ResourcesSetup> = i
.economy
.natural_resources
.chunks_per_resource
.iter()
.map(|(good, a)| ResourcesSetup {
good: good.into(),
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 val = EconomySetup {
name: i.name().into(),
position: (i.get_origin().x, i.get_origin().y),
resources,
neighbors,
kind: match i.kind {
crate::site::SiteKind::Settlement(_)
| crate::site::SiteKind::Refactor(_)
| crate::site::SiteKind::CliffTown(_) => {
common::terrain::site::SitesKind::Settlement
},
crate::site::SiteKind::Dungeon(_) => {
common::terrain::site::SitesKind::Dungeon
},
crate::site::SiteKind::Castle(_) => {
common::terrain::site::SitesKind::Castle
},
_ => common::terrain::site::SitesKind::Void,
},
};
outarr.push(val);
}
let pretty = ron::ser::PrettyConfig::new();
if let Ok(result) = ron::ser::to_string_pretty(&outarr, pretty) {
info!("RON {}", result);
}
} else {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let ron_file = std::fs::File::open("economy_testinput2.ron")
.expect("economy_testinput2.ron not found");
let econ_testinput: Vec<EconomySetup> =
ron::de::from_reader(ron_file).expect("economy_testinput2.ron parse error");
for i in econ_testinput.iter() {
let wpos = Vec2 {
x: i.position.0,
y: i.position.1,
};
// this should be a moderate compromise between regenerating the full world and
// loading on demand using the public API. There is no way to set
// the name, do we care?
let mut settlement = match i.kind {
common::terrain::site::SitesKind::Castle => crate::site::Site::castle(
crate::site::Castle::generate(wpos, None, &mut rng),
),
common::terrain::site::SitesKind::Dungeon => {
crate::site::Site::dungeon(crate::site2::Site::generate_dungeon(
&crate::Land::empty(),
&mut rng,
wpos,
))
},
// common::terrain::site::SitesKind::Settlement |
_ => crate::site::Site::settlement(crate::site::Settlement::generate(
wpos, None, &mut rng,
)),
};
for g in i.resources.iter() {
//let c = sim::SimChunk::new();
//settlement.economy.add_chunk(ch, distance_squared)
// bypass the API for now
settlement.economy.natural_resources.chunks_per_resource
[g.good.try_into().unwrap_or_default()] = g.amount;
settlement.economy.natural_resources.average_yield_per_chunk
[g.good.try_into().unwrap_or_default()] = 1.0;
}
index.sites.insert(settlement);
}
// 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);
}
}
}
crate::sim2::simulate(&mut index, &mut sim);
show_economy(&index.sites);
});
}
struct Simenv {
index: crate::index::Index,
rng: ChaChaRng,
targets: hashbrown::HashMap<Id<crate::site::Site>, f32>,
}
#[test]
/// test whether a site in moderate climate can survive on its own
fn test_economy_moderate_standalone() {
fn add_settlement(
env: &mut Simenv,
// index: &mut crate::index::Index,
// rng: &mut ChaCha20Rng,
_name: &str,
target: f32,
resources: &[(Good, f32)],
) -> Id<crate::site::Site> {
let wpos = Vec2 { x: 42, y: 42 };
let mut settlement = crate::site::Site::settlement(crate::site::Settlement::generate(
wpos,
None,
&mut env.rng,
//Some(name),
));
for (good, amount) in resources.iter() {
settlement.economy.natural_resources.chunks_per_resource
[(*good).try_into().unwrap_or_default()] = *amount;
settlement.economy.natural_resources.average_yield_per_chunk
[(*good).try_into().unwrap_or_default()] = 1.0;
}
let id = env.index.sites.insert(settlement);
env.targets.insert(id, target);
id
}
execute_with_tracing(Level::ERROR, || {
let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap();
info!("init");
let seed = 59686;
let opts = sim::WorldOpts {
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
calendar: Default::default(),
};
let index = crate::index::Index::new(seed);
info!("Index created");
let mut sim = sim::WorldSim::generate(seed, opts, &threadpool);
info!("World loaded");
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let mut env = Simenv {
index,
rng,
targets: hashbrown::HashMap::new(),
};
add_settlement(&mut env, "Forest", 5000.0, &[(
Good::Terrain(BiomeKind::Forest),
100.0_f32,
)]);
add_settlement(&mut env, "Grass", 900.0, &[(
Good::Terrain(BiomeKind::Grassland),
100.0_f32,
)]);
add_settlement(&mut env, "Mountain", 3.0, &[(
Good::Terrain(BiomeKind::Mountain),
100.0_f32,
)]);
// add_settlement(&mut env, "Desert", 19.0, &[(
// Good::Terrain(BiomeKind::Desert),
// 100.0_f32,
// )]);
// add_settlement(&mut index, &mut rng, &[
// (Good::Terrain(BiomeKind::Jungle), 100.0_f32),
// ]);
// add_settlement(&mut index, &mut rng, &[
// (Good::Terrain(BiomeKind::Snowland), 100.0_f32),
// ]);
add_settlement(&mut env, "GrFoMo", 12000.0, &[
(Good::Terrain(BiomeKind::Grassland), 100.0_f32),
(Good::Terrain(BiomeKind::Forest), 100.0_f32),
(Good::Terrain(BiomeKind::Mountain), 10.0_f32),
]);
// add_settlement(&mut env, "Mountain", 19.0, &[
// (Good::Terrain(BiomeKind::Mountain), 100.0_f32),
// // (Good::CaveAccess, 100.0_f32),
// ]);
// connect to neighbors (one way)
for i in 1..(env.index.sites.ids().count() as u64 - 1) {
let previous = env.index.sites.recreate_id(i - 1);
let center = env.index.sites.recreate_id(i);
center.zip(previous).map(|(center, previous)| {
env.index.sites[center]
.economy
.add_neighbor(previous, i as usize);
env.index.sites[previous]
.economy
.add_neighbor(center, i as usize);
});
}
crate::sim2::simulate(&mut env.index, &mut sim);
show_economy(&env.index.sites);
// check population (shrinks if economy gets broken)
for (id, site) in env.index.sites.iter() {
assert!(site.economy.pop >= env.targets[&id]);
}
});
} }
} }

View File

@ -88,6 +88,7 @@ lazy_static! {
pub struct Economy { pub struct Economy {
/// Population /// Population
pub pop: f32, pub pop: f32,
pub population_limited_by: GoodIndex,
/// Total available amount of each good /// Total available amount of each good
pub stocks: GoodMap<f32>, pub stocks: GoodMap<f32>,
@ -97,22 +98,30 @@ pub struct Economy {
pub marginal_surplus: GoodMap<f32>, pub marginal_surplus: GoodMap<f32>,
/// amount of wares not needed by the economy (helps with trade planning) /// amount of wares not needed by the economy (helps with trade planning)
pub unconsumed_stock: GoodMap<f32>, pub 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 // For some goods, such a goods without any supply, it doesn't make sense to talk about value
pub values: GoodMap<Option<f32>>, pub values: GoodMap<Option<f32>>,
/// amount of goods exported/imported during the last cycle
pub last_exports: GoodMap<f32>, pub last_exports: GoodMap<f32>,
pub active_exports: GoodMap<f32>, // unfinished trade (amount unconfirmed) pub active_exports: GoodMap<f32>, // unfinished trade (amount unconfirmed)
//pub export_targets: GoodMap<f32>, //pub export_targets: GoodMap<f32>,
/// amount of labor that went into a good, [1 man cycle=1.0]
pub labor_values: GoodMap<Option<f32>>, pub labor_values: GoodMap<Option<f32>>,
// this assumes a single source, replace with LaborMap?
pub material_costs: GoodMap<f32>, pub material_costs: GoodMap<f32>,
// Proportion of individuals dedicated to an industry /// Proportion of individuals dedicated to an industry (sums to roughly 1.0)
pub labors: LaborMap<f32>, pub labors: LaborMap<f32>,
// Per worker, per year, of their output good // Per worker, per year, of their output good
pub yields: LaborMap<f32>, pub yields: LaborMap<f32>,
/// [0.0..1.0]
pub productivity: LaborMap<f32>, pub productivity: LaborMap<f32>,
/// Missing raw material which limits production
pub limited_by: LaborMap<GoodIndex>,
pub natural_resources: NaturalResources, pub natural_resources: NaturalResources,
// usize is distance /// Neighboring sites to trade with
pub neighbors: Vec<NeighborInformation>, pub neighbors: Vec<NeighborInformation>,
/// outgoing trade, per provider /// outgoing trade, per provider
@ -126,6 +135,7 @@ impl Default for Economy {
let coin_index: GoodIndex = GoodIndex::try_from(Coin).unwrap_or_default(); let coin_index: GoodIndex = GoodIndex::try_from(Coin).unwrap_or_default();
Self { Self {
pop: 32.0, pop: 32.0,
population_limited_by: GoodIndex::default(),
stocks: GoodMap::from_list(&[(coin_index, Economy::STARTING_COIN)], 100.0), stocks: GoodMap::from_list(&[(coin_index, Economy::STARTING_COIN)], 100.0),
surplus: Default::default(), surplus: Default::default(),
@ -140,6 +150,7 @@ impl Default for Economy {
labors: LaborMap::from_default(0.01), labors: LaborMap::from_default(0.01),
yields: LaborMap::from_default(1.0), yields: LaborMap::from_default(1.0),
productivity: LaborMap::from_default(1.0), productivity: LaborMap::from_default(1.0),
limited_by: LaborMap::from_default(GoodIndex::default()),
natural_resources: Default::default(), natural_resources: Default::default(),
neighbors: Default::default(), neighbors: Default::default(),
@ -928,7 +939,7 @@ impl Economy {
// available then we only need to consume 2/3rds // available then we only need to consume 2/3rds
// of other ingredients and leave the rest in stock // of other ingredients and leave the rest in stock
// In effect, this is the productivity // In effect, this is the productivity
let labor_productivity = orders let (labor_productivity, limited_by) = orders
.iter() .iter()
.map(|(good, amount)| { .map(|(good, amount)| {
// What quantity is this order requesting? // What quantity is this order requesting?
@ -936,13 +947,20 @@ impl Economy {
assert!(stocks_before[*good] >= 0.0); assert!(stocks_before[*good] >= 0.0);
assert!(demand[*good] >= 0.0); assert!(demand[*good] >= 0.0);
// What proportion of this order is the economy able to satisfy? // What proportion of this order is the economy able to satisfy?
(stocks_before[*good] / demand[*good]).min(1.0) ((stocks_before[*good] / demand[*good]).min(1.0), *good)
}) })
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Less)) .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Less))
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("Industry {:?} requires at least one input order", labor) panic!("Industry {:?} requires at least one input order", labor)
}); });
assert!(labor_productivity >= 0.0); assert!(labor_productivity >= 0.0);
if let Some(labor) = labor {
self.limited_by[*labor] = if labor_productivity >= 1.0 {
GoodIndex::default()
} else {
limited_by
};
}
let mut total_materials_cost = 0.0; let mut total_materials_cost = 0.0;
for (good, amount) in orders { for (good, amount) in orders {
@ -1069,7 +1087,8 @@ impl Economy {
// Births/deaths // Births/deaths
const NATURAL_BIRTH_RATE: f32 = 0.05; const NATURAL_BIRTH_RATE: f32 = 0.05;
const DEATH_RATE: f32 = 0.005; const DEATH_RATE: f32 = 0.005;
let birth_rate = if self.surplus[*FOOD_INDEX] > 0.0 { let population_growth = self.surplus[*FOOD_INDEX] > 0.0;
let birth_rate = if population_growth {
NATURAL_BIRTH_RATE NATURAL_BIRTH_RATE
} else { } else {
0.0 0.0
@ -1078,6 +1097,11 @@ impl Economy {
"pop", "pop",
dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE), dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE),
); );
self.population_limited_by = if population_growth {
GoodIndex::default()
} else {
*FOOD_INDEX
};
// calculate the new unclaimed stock // calculate the new unclaimed stock
//let next_orders = self.get_orders(); //let next_orders = self.get_orders();