mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
edffd576d4
commit
6d3ea3172c
@ -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: [],
|
||||
),
|
||||
]
|
@ -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: [
|
||||
(
|
||||
|
@ -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,
|
||||
|
@ -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); }
|
@ -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 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]);
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user