mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sharp/deterministic-civsim' into 'master'
Make civsim and sites deterministic. See merge request veloren/veloren!1009
This commit is contained in:
commit
786665eac4
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved camera aiming
|
- Improved camera aiming
|
||||||
|
- Made civsim, sites, etc. deterministic from the same seed.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4993,6 +4993,7 @@ dependencies = [
|
|||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dot_vox",
|
"dot_vox",
|
||||||
"find_folder",
|
"find_folder",
|
||||||
|
"fxhash",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"image",
|
"image",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@ -5115,6 +5116,7 @@ dependencies = [
|
|||||||
"arr_macro",
|
"arr_macro",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
"fxhash",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"image",
|
"image",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -13,6 +13,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
|
|||||||
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
|
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
|
||||||
vek = { version = "0.10.0", features = ["serde"] }
|
vek = { version = "0.10.0", features = ["serde"] }
|
||||||
dot_vox = "4.0.0"
|
dot_vox = "4.0.0"
|
||||||
|
fxhash = "0.2.1"
|
||||||
image = "0.22.3"
|
image = "0.22.3"
|
||||||
mio = "0.6.19"
|
mio = "0.6.19"
|
||||||
mio-extras = "2.0.5"
|
mio-extras = "2.0.5"
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
use crate::path::Path;
|
use crate::path::Path;
|
||||||
use core::cmp::Ordering::Equal;
|
use core::{
|
||||||
|
cmp::Ordering::{self, Equal},
|
||||||
|
f32, fmt,
|
||||||
|
hash::{BuildHasher, Hash},
|
||||||
|
};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use std::{cmp::Ordering, collections::BinaryHeap, f32, hash::Hash};
|
use std::collections::BinaryHeap;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct PathEntry<S> {
|
pub struct PathEntry<S> {
|
||||||
@ -43,33 +47,62 @@ impl<T> PathResult<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Astar<S: Clone + Eq + Hash> {
|
pub struct Astar<S, Hasher> {
|
||||||
iter: usize,
|
iter: usize,
|
||||||
max_iters: usize,
|
max_iters: usize,
|
||||||
potential_nodes: BinaryHeap<PathEntry<S>>,
|
potential_nodes: BinaryHeap<PathEntry<S>>,
|
||||||
came_from: HashMap<S, S>,
|
came_from: HashMap<S, S, Hasher>,
|
||||||
cheapest_scores: HashMap<S, f32>,
|
cheapest_scores: HashMap<S, f32, Hasher>,
|
||||||
final_scores: HashMap<S, f32>,
|
final_scores: HashMap<S, f32, Hasher>,
|
||||||
visited: HashSet<S>,
|
visited: HashSet<S, Hasher>,
|
||||||
cheapest_node: Option<S>,
|
cheapest_node: Option<S>,
|
||||||
cheapest_cost: Option<f32>,
|
cheapest_cost: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Clone + Eq + Hash> Astar<S> {
|
/// NOTE: Must manually derive since Hasher doesn't implement it.
|
||||||
pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32) -> Self {
|
impl<S: Clone + Eq + Hash + fmt::Debug, H: BuildHasher> fmt::Debug for Astar<S, H> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Astar")
|
||||||
|
.field("iter", &self.iter)
|
||||||
|
.field("max_iters", &self.max_iters)
|
||||||
|
.field("potential_nodes", &self.potential_nodes)
|
||||||
|
.field("came_from", &self.came_from)
|
||||||
|
.field("cheapest_scores", &self.cheapest_scores)
|
||||||
|
.field("final_scores", &self.final_scores)
|
||||||
|
.field("visited", &self.visited)
|
||||||
|
.field("cheapest_node", &self.cheapest_node)
|
||||||
|
.field("cheapest_cost", &self.cheapest_cost)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||||
|
pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32, hasher: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_iters,
|
max_iters,
|
||||||
iter: 0,
|
iter: 0,
|
||||||
potential_nodes: std::iter::once(PathEntry {
|
potential_nodes: core::iter::once(PathEntry {
|
||||||
cost: 0.0,
|
cost: 0.0,
|
||||||
node: start.clone(),
|
node: start.clone(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
came_from: HashMap::default(),
|
came_from: HashMap::with_hasher(hasher.clone()),
|
||||||
cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(),
|
cheapest_scores: {
|
||||||
final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(),
|
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||||
visited: std::iter::once(start).collect(),
|
h.extend(core::iter::once((start.clone(), 0.0)));
|
||||||
|
h
|
||||||
|
},
|
||||||
|
final_scores: {
|
||||||
|
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||||
|
h.extend(core::iter::once((start.clone(), heuristic(&start))));
|
||||||
|
h
|
||||||
|
},
|
||||||
|
visited: {
|
||||||
|
let mut s = HashSet::with_capacity_and_hasher(1, hasher);
|
||||||
|
s.extend(core::iter::once(start));
|
||||||
|
s
|
||||||
|
},
|
||||||
cheapest_node: None,
|
cheapest_node: None,
|
||||||
cheapest_cost: None,
|
cheapest_cost: None,
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
terrain::Block,
|
terrain::Block,
|
||||||
vol::{BaseVol, ReadVol},
|
vol::{BaseVol, ReadVol},
|
||||||
};
|
};
|
||||||
|
use hashbrown::hash_map::DefaultHashBuilder;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -92,7 +93,11 @@ impl Route {
|
|||||||
pub struct Chaser {
|
pub struct Chaser {
|
||||||
last_search_tgt: Option<Vec3<f32>>,
|
last_search_tgt: Option<Vec3<f32>>,
|
||||||
route: Route,
|
route: Route,
|
||||||
astar: Option<Astar<Vec3<i32>>>,
|
/// We use this hasher (AAHasher) because:
|
||||||
|
/// (1) we care about DDOS attacks (ruling out FxHash);
|
||||||
|
/// (2) we don't care about determinism across computers (we can use
|
||||||
|
/// AAHash).
|
||||||
|
astar: Option<Astar<Vec3<i32>, DefaultHashBuilder>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chaser {
|
impl Chaser {
|
||||||
@ -147,7 +152,7 @@ impl Chaser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn find_path<V>(
|
fn find_path<V>(
|
||||||
astar: &mut Option<Astar<Vec3<i32>>>,
|
astar: &mut Option<Astar<Vec3<i32>, DefaultHashBuilder>>,
|
||||||
vol: &V,
|
vol: &V,
|
||||||
startf: Vec3<f32>,
|
startf: Vec3<f32>,
|
||||||
endf: Vec3<f32>,
|
endf: Vec3<f32>,
|
||||||
@ -263,7 +268,12 @@ where
|
|||||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||||
|
|
||||||
let mut new_astar = match astar.take() {
|
let mut new_astar = match astar.take() {
|
||||||
None => Astar::new(20_000, start, heuristic.clone()),
|
None => Astar::new(
|
||||||
|
20_000,
|
||||||
|
start,
|
||||||
|
heuristic.clone(),
|
||||||
|
DefaultHashBuilder::default(),
|
||||||
|
),
|
||||||
Some(astar) => astar,
|
Some(astar) => astar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@ use std::{
|
|||||||
ops::{Index, IndexMut},
|
ops::{Index, IndexMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Id<T>(usize, PhantomData<T>);
|
// NOTE: We use u64 to make sure we are consistent across all machines. We
|
||||||
|
// assume that usize fits into 8 bytes.
|
||||||
|
pub struct Id<T>(u64, PhantomData<T>);
|
||||||
|
|
||||||
impl<T> Id<T> {
|
impl<T> Id<T> {
|
||||||
pub fn id(&self) -> usize { self.0 }
|
pub fn id(&self) -> u64 { self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Copy for Id<T> {}
|
impl<T> Copy for Id<T> {}
|
||||||
@ -37,12 +39,19 @@ impl<T> Default for Store<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Store<T> {
|
impl<T> Store<T> {
|
||||||
pub fn get(&self, id: Id<T>) -> &T { self.items.get(id.0).unwrap() }
|
pub fn get(&self, id: Id<T>) -> &T {
|
||||||
|
// NOTE: Safe conversion, because it came from usize.
|
||||||
|
self.items.get(id.0 as usize).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: Id<T>) -> &mut T { self.items.get_mut(id.0).unwrap() }
|
pub fn get_mut(&mut self, id: Id<T>) -> &mut T {
|
||||||
|
// NOTE: Safe conversion, because it came from usize.
|
||||||
|
self.items.get_mut(id.0 as usize).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
|
pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
|
||||||
(0..self.items.len()).map(|i| Id(i, PhantomData))
|
// NOTE: Assumes usize fits into 8 bytes.
|
||||||
|
(0..self.items.len() as u64).map(|i| Id(i, PhantomData))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||||
@ -53,11 +62,13 @@ impl<T> Store<T> {
|
|||||||
self.items
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, item)| (Id(i, PhantomData), item))
|
// NOTE: Assumes usize fits into 8 bytes.
|
||||||
|
.map(|(i, item)| (Id(i as u64, PhantomData), item))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, item: T) -> Id<T> {
|
pub fn insert(&mut self, item: T) -> Id<T> {
|
||||||
let id = Id(self.items.len(), PhantomData);
|
// NOTE: Assumes usize fits into 8 bytes.
|
||||||
|
let id = Id(self.items.len() as u64, PhantomData);
|
||||||
self.items.push(item);
|
self.items.push(item);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ edition = "2018"
|
|||||||
bincode = "1.2.0"
|
bincode = "1.2.0"
|
||||||
common = { package = "veloren-common", path = "../common" }
|
common = { package = "veloren-common", path = "../common" }
|
||||||
bitvec = "0.17.4"
|
bitvec = "0.17.4"
|
||||||
|
fxhash = "0.2.1"
|
||||||
image = "0.22.3"
|
image = "0.22.3"
|
||||||
itertools = "0.8.2"
|
itertools = "0.8.2"
|
||||||
vek = "0.10.0"
|
vek = "0.10.0"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
mod econ;
|
mod econ;
|
||||||
|
|
||||||
|
use self::{Occupation::*, Stock::*};
|
||||||
use crate::{
|
use crate::{
|
||||||
sim::WorldSim,
|
sim::WorldSim,
|
||||||
site::{Dungeon, Settlement, Site as WorldSite},
|
site::{Dungeon, Settlement, Site as WorldSite},
|
||||||
@ -15,10 +16,15 @@ use common::{
|
|||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
hash::{BuildHasherDefault, Hash},
|
||||||
|
ops::Range,
|
||||||
|
};
|
||||||
|
use fxhash::{FxHasher32, FxHasher64};
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use std::{fmt, hash::Hash, ops::Range};
|
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
const INITIAL_CIV_COUNT: usize = (crate::sim::WORLD_SIZE.x * crate::sim::WORLD_SIZE.y * 3) / 65536; //48 at default scale
|
const INITIAL_CIV_COUNT: usize = (crate::sim::WORLD_SIZE.x * crate::sim::WORLD_SIZE.y * 3) / 65536; //48 at default scale
|
||||||
@ -29,7 +35,15 @@ pub struct Civs {
|
|||||||
places: Store<Place>,
|
places: Store<Place>,
|
||||||
|
|
||||||
tracks: Store<Track>,
|
tracks: Store<Track>,
|
||||||
track_map: HashMap<Id<Site>, HashMap<Id<Site>, Id<Track>>>,
|
/// We use this hasher (FxHasher64) because
|
||||||
|
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
track_map: HashMap<
|
||||||
|
Id<Site>,
|
||||||
|
HashMap<Id<Site>, Id<Track>, BuildHasherDefault<FxHasher64>>,
|
||||||
|
BuildHasherDefault<FxHasher64>,
|
||||||
|
>,
|
||||||
|
|
||||||
sites: Store<Site>,
|
sites: Store<Site>,
|
||||||
}
|
}
|
||||||
@ -235,7 +249,16 @@ impl Civs {
|
|||||||
let transition =
|
let transition =
|
||||||
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
||||||
let satisfied = |p: &Id<Site>| *p == b;
|
let satisfied = |p: &Id<Site>| *p == b;
|
||||||
let mut astar = Astar::new(100, a, heuristic);
|
// We use this hasher (FxHasher64) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
let mut astar = Astar::new(
|
||||||
|
100,
|
||||||
|
a,
|
||||||
|
heuristic,
|
||||||
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
|
);
|
||||||
astar
|
astar
|
||||||
.poll(100, heuristic, neighbors, transition, satisfied)
|
.poll(100, heuristic, neighbors, transition, satisfied)
|
||||||
.into_path()
|
.into_path()
|
||||||
@ -281,8 +304,12 @@ impl Civs {
|
|||||||
loc: Vec2<i32>,
|
loc: Vec2<i32>,
|
||||||
area: Range<usize>,
|
area: Range<usize>,
|
||||||
) -> Option<Id<Place>> {
|
) -> Option<Id<Place>> {
|
||||||
let mut dead = HashSet::new();
|
// We use this hasher (FxHasher64) because
|
||||||
let mut alive = HashSet::new();
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
let mut dead = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
|
||||||
|
let mut alive = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
|
||||||
alive.insert(loc);
|
alive.insert(loc);
|
||||||
|
|
||||||
// Fill the surrounding area
|
// Fill the surrounding area
|
||||||
@ -495,7 +522,16 @@ fn find_path(
|
|||||||
let transition =
|
let transition =
|
||||||
|a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
|a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
||||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||||
let mut astar = Astar::new(20000, a, heuristic);
|
// We use this hasher (FxHasher64) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
let mut astar = Astar::new(
|
||||||
|
20000,
|
||||||
|
a,
|
||||||
|
heuristic,
|
||||||
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
|
);
|
||||||
astar
|
astar
|
||||||
.poll(20000, heuristic, neighbors, transition, satisfied)
|
.poll(20000, heuristic, neighbors, transition, satisfied)
|
||||||
.into_path()
|
.into_path()
|
||||||
@ -688,13 +724,13 @@ impl fmt::Display for Site {
|
|||||||
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
|
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
|
||||||
writeln!(f, "Stocks")?;
|
writeln!(f, "Stocks")?;
|
||||||
for (stock, q) in self.stocks.iter() {
|
for (stock, q) in self.stocks.iter() {
|
||||||
writeln!(f, "- {}: {}", stock, q.floor())?;
|
writeln!(f, "- {:?}: {}", stock, q.floor())?;
|
||||||
}
|
}
|
||||||
writeln!(f, "Values")?;
|
writeln!(f, "Values")?;
|
||||||
for stock in TRADE_STOCKS.iter() {
|
for stock in TRADE_STOCKS.iter() {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"- {}: {}",
|
"- {:?}: {}",
|
||||||
stock,
|
stock,
|
||||||
self.values[*stock]
|
self.values[*stock]
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
@ -703,11 +739,16 @@ impl fmt::Display for Site {
|
|||||||
}
|
}
|
||||||
writeln!(f, "Laborers")?;
|
writeln!(f, "Laborers")?;
|
||||||
for (labor, n) in self.labors.iter() {
|
for (labor, n) in self.labors.iter() {
|
||||||
writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?;
|
writeln!(
|
||||||
|
f,
|
||||||
|
"- {:?}: {}",
|
||||||
|
labor,
|
||||||
|
(*n * self.population).floor() as u32
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
writeln!(f, "Export targets")?;
|
writeln!(f, "Export targets")?;
|
||||||
for (stock, n) in self.export_targets.iter() {
|
for (stock, n) in self.export_targets.iter() {
|
||||||
writeln!(f, "- {}: {}", stock, n)?;
|
writeln!(f, "- {:?}: {}", stock, n)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -723,43 +764,50 @@ pub enum SiteKind {
|
|||||||
impl Site {
|
impl Site {
|
||||||
pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) {
|
pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) {
|
||||||
// Insert natural resources into the economy
|
// Insert natural resources into the economy
|
||||||
if self.stocks[FISH] < nat_res.river {
|
if self.stocks[Fish] < nat_res.river {
|
||||||
self.stocks[FISH] = nat_res.river;
|
self.stocks[Fish] = nat_res.river;
|
||||||
}
|
}
|
||||||
if self.stocks[WHEAT] < nat_res.farmland {
|
if self.stocks[Wheat] < nat_res.farmland {
|
||||||
self.stocks[WHEAT] = nat_res.farmland;
|
self.stocks[Wheat] = nat_res.farmland;
|
||||||
}
|
}
|
||||||
if self.stocks[LOGS] < nat_res.wood {
|
if self.stocks[Logs] < nat_res.wood {
|
||||||
self.stocks[LOGS] = nat_res.wood;
|
self.stocks[Logs] = nat_res.wood;
|
||||||
}
|
}
|
||||||
if self.stocks[GAME] < nat_res.wood {
|
if self.stocks[Game] < nat_res.wood {
|
||||||
self.stocks[GAME] = nat_res.wood;
|
self.stocks[Game] = nat_res.wood;
|
||||||
}
|
}
|
||||||
if self.stocks[ROCK] < nat_res.rock {
|
if self.stocks[Rock] < nat_res.rock {
|
||||||
self.stocks[ROCK] = nat_res.rock;
|
self.stocks[Rock] = nat_res.rock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We use this hasher (FxHasher32) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
|
||||||
let orders = vec![
|
let orders = vec![
|
||||||
(None, vec![(FOOD, 0.5)]),
|
(None, vec![(Food, 0.5)]),
|
||||||
(Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]),
|
(Some(Cook), vec![(Flour, 16.0), (Meat, 4.0), (Wood, 3.0)]),
|
||||||
(Some(LUMBERJACK), vec![(LOGS, 4.5)]),
|
(Some(Lumberjack), vec![(Logs, 4.5)]),
|
||||||
(Some(MINER), vec![(ROCK, 7.5)]),
|
(Some(Miner), vec![(Rock, 7.5)]),
|
||||||
(Some(FISHER), vec![(FISH, 4.0)]),
|
(Some(Fisher), vec![(Fish, 4.0)]),
|
||||||
(Some(HUNTER), vec![(GAME, 4.0)]),
|
(Some(Hunter), vec![(Game, 4.0)]),
|
||||||
(Some(FARMER), vec![(WHEAT, 4.0)]),
|
(Some(Farmer), vec![(Wheat, 4.0)]),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
|
.collect::<HashMap<_, Vec<(Stock, f32)>, BuildHasherDefault<FxHasher32>>>();
|
||||||
|
|
||||||
// Per labourer, per year
|
// Per labourer, per year
|
||||||
let production = Stocks::from_list(&[
|
let production = MapVec::from_list(
|
||||||
(FARMER, (FLOUR, 2.0)),
|
&[
|
||||||
(LUMBERJACK, (WOOD, 1.5)),
|
(Farmer, (Flour, 2.0)),
|
||||||
(MINER, (STONE, 0.6)),
|
(Lumberjack, (Wood, 1.5)),
|
||||||
(FISHER, (MEAT, 3.0)),
|
(Miner, (Stone, 0.6)),
|
||||||
(HUNTER, (MEAT, 0.25)),
|
(Fisher, (Meat, 3.0)),
|
||||||
(COOK, (FOOD, 20.0)),
|
(Hunter, (Meat, 0.25)),
|
||||||
]);
|
(Cook, (Food, 20.0)),
|
||||||
|
],
|
||||||
|
(Rock, 0.0),
|
||||||
|
);
|
||||||
|
|
||||||
let mut demand = Stocks::from_default(0.0);
|
let mut demand = Stocks::from_default(0.0);
|
||||||
for (labor, orders) in &orders {
|
for (labor, orders) in &orders {
|
||||||
@ -881,7 +929,7 @@ impl Site {
|
|||||||
// Births/deaths
|
// Births/deaths
|
||||||
const NATURAL_BIRTH_RATE: f32 = 0.15;
|
const NATURAL_BIRTH_RATE: f32 = 0.15;
|
||||||
const DEATH_RATE: f32 = 0.05;
|
const DEATH_RATE: f32 = 0.05;
|
||||||
let birth_rate = if self.surplus[FOOD] > 0.0 {
|
let birth_rate = if self.surplus[Food] > 0.0 {
|
||||||
NATURAL_BIRTH_RATE
|
NATURAL_BIRTH_RATE
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
@ -890,26 +938,33 @@ impl Site {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Occupation = &'static str;
|
#[repr(u8)]
|
||||||
const FARMER: Occupation = "farmer";
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
const LUMBERJACK: Occupation = "lumberjack";
|
enum Occupation {
|
||||||
const MINER: Occupation = "miner";
|
Farmer = 0,
|
||||||
const FISHER: Occupation = "fisher";
|
Lumberjack = 1,
|
||||||
const HUNTER: Occupation = "hunter";
|
Miner = 2,
|
||||||
const COOK: Occupation = "cook";
|
Fisher = 3,
|
||||||
|
Hunter = 4,
|
||||||
|
Cook = 5,
|
||||||
|
}
|
||||||
|
|
||||||
type Stock = &'static str;
|
#[repr(u8)]
|
||||||
const WHEAT: Stock = "wheat";
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
const FLOUR: Stock = "flour";
|
pub enum Stock {
|
||||||
const MEAT: Stock = "meat";
|
Wheat = 0,
|
||||||
const FISH: Stock = "fish";
|
Flour = 1,
|
||||||
const GAME: Stock = "game";
|
Meat = 2,
|
||||||
const FOOD: Stock = "food";
|
Fish = 3,
|
||||||
const LOGS: Stock = "logs";
|
Game = 4,
|
||||||
const WOOD: Stock = "wood";
|
Food = 5,
|
||||||
const ROCK: Stock = "rock";
|
Logs = 6,
|
||||||
const STONE: Stock = "stone";
|
Wood = 7,
|
||||||
const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE];
|
Rock = 8,
|
||||||
|
Stone = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRADE_STOCKS: [Stock; 5] = [Flour, Meat, Food, Wood, Stone];
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct TradeState {
|
struct TradeState {
|
||||||
@ -934,21 +989,35 @@ impl Default for TradeState {
|
|||||||
|
|
||||||
pub type Stocks<T> = MapVec<Stock, T>;
|
pub type Stocks<T> = MapVec<Stock, T>;
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MapVec<K, T> {
|
pub struct MapVec<K, T> {
|
||||||
entries: HashMap<K, T>,
|
/// We use this hasher (FxHasher32) because
|
||||||
|
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
/// (3) we have 1-byte keys (for which FxHash is supposedly fastest).
|
||||||
|
entries: HashMap<K, T, BuildHasherDefault<FxHasher32>>,
|
||||||
default: T,
|
default: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
|
/// Need manual implementation of Default since K doesn't need that bound.
|
||||||
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>) -> Self
|
impl<K, T: Default> Default for MapVec<K, T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: Default::default(),
|
||||||
|
default: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Copy + Eq + Hash, T: Clone> MapVec<K, T> {
|
||||||
|
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>, default: T) -> Self
|
||||||
where
|
where
|
||||||
K: 'a,
|
K: 'a,
|
||||||
T: 'a,
|
T: 'a,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
entries: i.into_iter().cloned().collect(),
|
entries: i.into_iter().cloned().collect(),
|
||||||
default: T::default(),
|
default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,12 +1055,12 @@ impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::Index<K> for MapVec<K, T> {
|
impl<K: Copy + Eq + Hash, T: Clone> std::ops::Index<K> for MapVec<K, T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
|
|
||||||
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
fn index(&self, entry: K) -> &Self::Output { self.get(entry) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Copy + Eq + Hash, T: Default + Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
impl<K: Copy + Eq + Hash, T: Clone> std::ops::IndexMut<K> for MapVec<K, T> {
|
||||||
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) }
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use core::hash::BuildHasherDefault;
|
||||||
|
use fxhash::FxHasher64;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use rand::{seq::SliceRandom, Rng};
|
use rand::{seq::SliceRandom, Rng};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -7,7 +9,11 @@ pub struct Location {
|
|||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) center: Vec2<i32>,
|
pub(crate) center: Vec2<i32>,
|
||||||
pub(crate) kingdom: Option<Kingdom>,
|
pub(crate) kingdom: Option<Kingdom>,
|
||||||
pub(crate) neighbours: HashSet<usize>,
|
// We use this hasher (FxHasher64) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
pub(crate) neighbours: HashSet<u64, BuildHasherDefault<FxHasher64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
impl Location {
|
||||||
|
@ -1353,14 +1353,15 @@ impl WorldSim {
|
|||||||
.map(|l| l.center)
|
.map(|l| l.center)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
// NOTE: We assume that usize is 8 or fewer bytes.
|
||||||
(0..locations.len()).for_each(|i| {
|
(0..locations.len()).for_each(|i| {
|
||||||
let pos = locations[i].center.map(|e| e as i64);
|
let pos = locations[i].center.map(|e| e as i64);
|
||||||
|
|
||||||
loc_clone.sort_by_key(|(_, l)| l.map(|e| e as i64).distance_squared(pos));
|
loc_clone.sort_by_key(|(_, l)| l.map(|e| e as i64).distance_squared(pos));
|
||||||
|
|
||||||
loc_clone.iter().skip(1).take(2).for_each(|(j, _)| {
|
loc_clone.iter().skip(1).take(2).for_each(|(j, _)| {
|
||||||
locations[i].neighbours.insert(*j);
|
locations[i].neighbours.insert(*j as u64);
|
||||||
locations[*j].neighbours.insert(i);
|
locations[*j].neighbours.insert(i as u64);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,9 +16,11 @@ use common::{
|
|||||||
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
||||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||||
};
|
};
|
||||||
|
use core::{f32, hash::BuildHasherDefault};
|
||||||
|
use fxhash::FxHasher64;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{f32, sync::Arc};
|
use std::sync::Arc;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
impl WorldSim {
|
impl WorldSim {
|
||||||
@ -381,7 +383,16 @@ impl Floor {
|
|||||||
_ => 100000.0,
|
_ => 100000.0,
|
||||||
};
|
};
|
||||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||||
let mut astar = Astar::new(20000, a, heuristic);
|
// We use this hasher (FxHasher64) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we don't care about determinism across computers (we could use AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
let mut astar = Astar::new(
|
||||||
|
20000,
|
||||||
|
a,
|
||||||
|
heuristic,
|
||||||
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
|
);
|
||||||
let path = astar
|
let path = astar
|
||||||
.poll(
|
.poll(
|
||||||
FLOOR_SIZE.product() as usize + 1,
|
FLOOR_SIZE.product() as usize + 1,
|
||||||
|
@ -18,9 +18,10 @@ use common::{
|
|||||||
terrain::{Block, BlockKind, TerrainChunkSize},
|
terrain::{Block, BlockKind, TerrainChunkSize},
|
||||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||||
};
|
};
|
||||||
|
use fxhash::FxHasher64;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{collections::VecDeque, f32};
|
use std::{collections::VecDeque, f32, hash::BuildHasherDefault};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -967,7 +968,11 @@ pub struct Sample<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Land {
|
pub struct Land {
|
||||||
tiles: HashMap<Vec2<i32>, Tile>,
|
/// We use this hasher (FxHasher64) because
|
||||||
|
/// (1) we need determinism across computers (ruling out AAHash);
|
||||||
|
/// (2) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
tiles: HashMap<Vec2<i32>, Tile, BuildHasherDefault<FxHasher64>>,
|
||||||
plots: Store<Plot>,
|
plots: Store<Plot>,
|
||||||
sampler_warp: StructureGen2d,
|
sampler_warp: StructureGen2d,
|
||||||
hazard: Id<Plot>,
|
hazard: Id<Plot>,
|
||||||
@ -978,7 +983,7 @@ impl Land {
|
|||||||
let mut plots = Store::default();
|
let mut plots = Store::default();
|
||||||
let hazard = plots.insert(Plot::Hazard);
|
let hazard = plots.insert(Plot::Hazard);
|
||||||
Self {
|
Self {
|
||||||
tiles: HashMap::new(),
|
tiles: HashMap::default(),
|
||||||
plots,
|
plots,
|
||||||
sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5),
|
sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5),
|
||||||
hazard,
|
hazard,
|
||||||
@ -1089,21 +1094,38 @@ impl Land {
|
|||||||
|from: &Vec2<i32>, to: &Vec2<i32>| path_cost_fn(self.tile_at(*from), self.tile_at(*to));
|
|from: &Vec2<i32>, to: &Vec2<i32>| path_cost_fn(self.tile_at(*from), self.tile_at(*to));
|
||||||
let satisfied = |pos: &Vec2<i32>| *pos == dest;
|
let satisfied = |pos: &Vec2<i32>| *pos == dest;
|
||||||
|
|
||||||
Astar::new(250, origin, heuristic)
|
// We use this hasher (FxHasher64) because
|
||||||
.poll(250, heuristic, neighbors, transition, satisfied)
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
.into_path()
|
// (2) we don't care about determinism across computers (we could use AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
Astar::new(
|
||||||
|
250,
|
||||||
|
origin,
|
||||||
|
heuristic,
|
||||||
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
|
)
|
||||||
|
.poll(250, heuristic, neighbors, transition, satisfied)
|
||||||
|
.into_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We use this hasher (FxHasher64) because
|
||||||
|
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
fn grow_from(
|
fn grow_from(
|
||||||
&self,
|
&self,
|
||||||
start: Vec2<i32>,
|
start: Vec2<i32>,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
_rng: &mut impl Rng,
|
_rng: &mut impl Rng,
|
||||||
mut match_fn: impl FnMut(Option<&Plot>) -> bool,
|
mut match_fn: impl FnMut(Option<&Plot>) -> bool,
|
||||||
) -> HashSet<Vec2<i32>> {
|
) -> HashSet<Vec2<i32>, BuildHasherDefault<FxHasher64>> {
|
||||||
let mut open = VecDeque::new();
|
let mut open = VecDeque::new();
|
||||||
open.push_back(start);
|
open.push_back(start);
|
||||||
let mut closed = HashSet::new();
|
// We use this hasher (FxHasher64) because
|
||||||
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
|
// (2) we care about determinism across computers (ruling out AAHash);
|
||||||
|
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||||
|
let mut closed = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
|
||||||
|
|
||||||
while open.len() + closed.len() < max_size {
|
while open.len() + closed.len() < max_size {
|
||||||
let next_pos = if let Some(next_pos) = open.pop_front() {
|
let next_pos = if let Some(next_pos) = open.pop_front() {
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
pub struct HashCache<K: Hash + Eq + Clone, V> {
|
|
||||||
capacity: usize,
|
|
||||||
map: HashMap<K, (usize, V)>,
|
|
||||||
counter: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Hash + Eq + Clone, V> Default for HashCache<K, V> {
|
|
||||||
fn default() -> Self { Self::with_capacity(1024) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Hash + Eq + Clone, V> HashCache<K, V> {
|
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
capacity,
|
|
||||||
map: HashMap::with_capacity(1024),
|
|
||||||
counter: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maintain(&mut self) {
|
|
||||||
const CACHE_BLOAT_RATE: usize = 2;
|
|
||||||
|
|
||||||
if self.map.len() > self.capacity * CACHE_BLOAT_RATE {
|
|
||||||
let (capacity, counter) = (self.capacity, self.counter);
|
|
||||||
self.map.retain(|_, (c, _)| *c + capacity > counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<F: FnOnce(K) -> V>(&mut self, key: K, f: F) -> &V {
|
|
||||||
self.maintain();
|
|
||||||
|
|
||||||
let counter = &mut self.counter;
|
|
||||||
&self
|
|
||||||
.map
|
|
||||||
.entry(key.clone())
|
|
||||||
.or_insert_with(|| {
|
|
||||||
*counter += 1;
|
|
||||||
(*counter, f(key))
|
|
||||||
})
|
|
||||||
.1
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
pub mod fast_noise;
|
pub mod fast_noise;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
pub mod hash_cache;
|
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod seed_expan;
|
pub mod seed_expan;
|
||||||
@ -12,7 +11,6 @@ pub mod unit_chooser;
|
|||||||
pub use self::{
|
pub use self::{
|
||||||
fast_noise::FastNoise,
|
fast_noise::FastNoise,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
hash_cache::HashCache,
|
|
||||||
random::{RandomField, RandomPerm},
|
random::{RandomField, RandomPerm},
|
||||||
sampler::{Sampler, SamplerMut},
|
sampler::{Sampler, SamplerMut},
|
||||||
small_cache::SmallCache,
|
small_cache::SmallCache,
|
||||||
|
Loading…
Reference in New Issue
Block a user