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
|
||||
|
||||
- Improved camera aiming
|
||||
- Made civsim, sites, etc. deterministic from the same seed.
|
||||
|
||||
### Removed
|
||||
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4993,6 +4993,7 @@ dependencies = [
|
||||
"crossbeam",
|
||||
"dot_vox",
|
||||
"find_folder",
|
||||
"fxhash",
|
||||
"hashbrown",
|
||||
"image",
|
||||
"indexmap",
|
||||
@ -5115,6 +5116,7 @@ dependencies = [
|
||||
"arr_macro",
|
||||
"bincode",
|
||||
"bitvec",
|
||||
"fxhash",
|
||||
"hashbrown",
|
||||
"image",
|
||||
"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"] }
|
||||
vek = { version = "0.10.0", features = ["serde"] }
|
||||
dot_vox = "4.0.0"
|
||||
fxhash = "0.2.1"
|
||||
image = "0.22.3"
|
||||
mio = "0.6.19"
|
||||
mio-extras = "2.0.5"
|
||||
|
@ -1,7 +1,11 @@
|
||||
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 std::{cmp::Ordering, collections::BinaryHeap, f32, hash::Hash};
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathEntry<S> {
|
||||
@ -43,33 +47,62 @@ impl<T> PathResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Astar<S: Clone + Eq + Hash> {
|
||||
#[derive(Clone)]
|
||||
pub struct Astar<S, Hasher> {
|
||||
iter: usize,
|
||||
max_iters: usize,
|
||||
potential_nodes: BinaryHeap<PathEntry<S>>,
|
||||
came_from: HashMap<S, S>,
|
||||
cheapest_scores: HashMap<S, f32>,
|
||||
final_scores: HashMap<S, f32>,
|
||||
visited: HashSet<S>,
|
||||
came_from: HashMap<S, S, Hasher>,
|
||||
cheapest_scores: HashMap<S, f32, Hasher>,
|
||||
final_scores: HashMap<S, f32, Hasher>,
|
||||
visited: HashSet<S, Hasher>,
|
||||
cheapest_node: Option<S>,
|
||||
cheapest_cost: Option<f32>,
|
||||
}
|
||||
|
||||
impl<S: Clone + Eq + Hash> Astar<S> {
|
||||
pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32) -> Self {
|
||||
/// NOTE: Must manually derive since Hasher doesn't implement it.
|
||||
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 {
|
||||
max_iters,
|
||||
iter: 0,
|
||||
potential_nodes: std::iter::once(PathEntry {
|
||||
potential_nodes: core::iter::once(PathEntry {
|
||||
cost: 0.0,
|
||||
node: start.clone(),
|
||||
})
|
||||
.collect(),
|
||||
came_from: HashMap::default(),
|
||||
cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(),
|
||||
final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(),
|
||||
visited: std::iter::once(start).collect(),
|
||||
came_from: HashMap::with_hasher(hasher.clone()),
|
||||
cheapest_scores: {
|
||||
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||
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_cost: None,
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::{
|
||||
terrain::Block,
|
||||
vol::{BaseVol, ReadVol},
|
||||
};
|
||||
use hashbrown::hash_map::DefaultHashBuilder;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::iter::FromIterator;
|
||||
use vek::*;
|
||||
@ -92,7 +93,11 @@ impl Route {
|
||||
pub struct Chaser {
|
||||
last_search_tgt: Option<Vec3<f32>>,
|
||||
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 {
|
||||
@ -147,7 +152,7 @@ impl Chaser {
|
||||
}
|
||||
|
||||
fn find_path<V>(
|
||||
astar: &mut Option<Astar<Vec3<i32>>>,
|
||||
astar: &mut Option<Astar<Vec3<i32>, DefaultHashBuilder>>,
|
||||
vol: &V,
|
||||
startf: Vec3<f32>,
|
||||
endf: Vec3<f32>,
|
||||
@ -263,7 +268,12 @@ where
|
||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -5,10 +5,12 @@ use std::{
|
||||
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> {
|
||||
pub fn id(&self) -> usize { self.0 }
|
||||
pub fn id(&self) -> u64 { self.0 }
|
||||
}
|
||||
|
||||
impl<T> Copy for Id<T> {}
|
||||
@ -37,12 +39,19 @@ impl<T> Default for 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>> {
|
||||
(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() }
|
||||
@ -53,11 +62,13 @@ impl<T> Store<T> {
|
||||
self.items
|
||||
.iter()
|
||||
.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> {
|
||||
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);
|
||||
id
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ edition = "2018"
|
||||
bincode = "1.2.0"
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
bitvec = "0.17.4"
|
||||
fxhash = "0.2.1"
|
||||
image = "0.22.3"
|
||||
itertools = "0.8.2"
|
||||
vek = "0.10.0"
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
mod econ;
|
||||
|
||||
use self::{Occupation::*, Stock::*};
|
||||
use crate::{
|
||||
sim::WorldSim,
|
||||
site::{Dungeon, Settlement, Site as WorldSite},
|
||||
@ -15,10 +16,15 @@ use common::{
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use core::{
|
||||
fmt,
|
||||
hash::{BuildHasherDefault, Hash},
|
||||
ops::Range,
|
||||
};
|
||||
use fxhash::{FxHasher32, FxHasher64};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use std::{fmt, hash::Hash, ops::Range};
|
||||
use vek::*;
|
||||
|
||||
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>,
|
||||
|
||||
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>,
|
||||
}
|
||||
@ -235,7 +249,16 @@ impl Civs {
|
||||
let transition =
|
||||
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
||||
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
|
||||
.poll(100, heuristic, neighbors, transition, satisfied)
|
||||
.into_path()
|
||||
@ -281,8 +304,12 @@ impl Civs {
|
||||
loc: Vec2<i32>,
|
||||
area: Range<usize>,
|
||||
) -> Option<Id<Place>> {
|
||||
let mut dead = HashSet::new();
|
||||
let mut alive = 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 dead = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
|
||||
let mut alive = HashSet::with_hasher(BuildHasherDefault::<FxHasher64>::default());
|
||||
alive.insert(loc);
|
||||
|
||||
// Fill the surrounding area
|
||||
@ -495,7 +522,16 @@ fn find_path(
|
||||
let transition =
|
||||
|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 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
|
||||
.poll(20000, heuristic, neighbors, transition, satisfied)
|
||||
.into_path()
|
||||
@ -688,13 +724,13 @@ impl fmt::Display for Site {
|
||||
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
|
||||
writeln!(f, "Stocks")?;
|
||||
for (stock, q) in self.stocks.iter() {
|
||||
writeln!(f, "- {}: {}", stock, q.floor())?;
|
||||
writeln!(f, "- {:?}: {}", stock, q.floor())?;
|
||||
}
|
||||
writeln!(f, "Values")?;
|
||||
for stock in TRADE_STOCKS.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
"- {}: {}",
|
||||
"- {:?}: {}",
|
||||
stock,
|
||||
self.values[*stock]
|
||||
.map(|x| x.to_string())
|
||||
@ -703,11 +739,16 @@ impl fmt::Display for Site {
|
||||
}
|
||||
writeln!(f, "Laborers")?;
|
||||
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")?;
|
||||
for (stock, n) in self.export_targets.iter() {
|
||||
writeln!(f, "- {}: {}", stock, n)?;
|
||||
writeln!(f, "- {:?}: {}", stock, n)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -723,43 +764,50 @@ pub enum SiteKind {
|
||||
impl Site {
|
||||
pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) {
|
||||
// Insert natural resources into the economy
|
||||
if self.stocks[FISH] < nat_res.river {
|
||||
self.stocks[FISH] = nat_res.river;
|
||||
if self.stocks[Fish] < nat_res.river {
|
||||
self.stocks[Fish] = nat_res.river;
|
||||
}
|
||||
if self.stocks[WHEAT] < nat_res.farmland {
|
||||
self.stocks[WHEAT] = nat_res.farmland;
|
||||
if self.stocks[Wheat] < nat_res.farmland {
|
||||
self.stocks[Wheat] = nat_res.farmland;
|
||||
}
|
||||
if self.stocks[LOGS] < nat_res.wood {
|
||||
self.stocks[LOGS] = nat_res.wood;
|
||||
if self.stocks[Logs] < nat_res.wood {
|
||||
self.stocks[Logs] = nat_res.wood;
|
||||
}
|
||||
if self.stocks[GAME] < nat_res.wood {
|
||||
self.stocks[GAME] = nat_res.wood;
|
||||
if self.stocks[Game] < nat_res.wood {
|
||||
self.stocks[Game] = nat_res.wood;
|
||||
}
|
||||
if self.stocks[ROCK] < nat_res.rock {
|
||||
self.stocks[ROCK] = nat_res.rock;
|
||||
if 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![
|
||||
(None, vec![(FOOD, 0.5)]),
|
||||
(Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]),
|
||||
(Some(LUMBERJACK), vec![(LOGS, 4.5)]),
|
||||
(Some(MINER), vec![(ROCK, 7.5)]),
|
||||
(Some(FISHER), vec![(FISH, 4.0)]),
|
||||
(Some(HUNTER), vec![(GAME, 4.0)]),
|
||||
(Some(FARMER), vec![(WHEAT, 4.0)]),
|
||||
(None, vec![(Food, 0.5)]),
|
||||
(Some(Cook), vec![(Flour, 16.0), (Meat, 4.0), (Wood, 3.0)]),
|
||||
(Some(Lumberjack), vec![(Logs, 4.5)]),
|
||||
(Some(Miner), vec![(Rock, 7.5)]),
|
||||
(Some(Fisher), vec![(Fish, 4.0)]),
|
||||
(Some(Hunter), vec![(Game, 4.0)]),
|
||||
(Some(Farmer), vec![(Wheat, 4.0)]),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, Vec<(Stock, f32)>>>();
|
||||
.collect::<HashMap<_, Vec<(Stock, f32)>, BuildHasherDefault<FxHasher32>>>();
|
||||
|
||||
// Per labourer, per year
|
||||
let production = Stocks::from_list(&[
|
||||
(FARMER, (FLOUR, 2.0)),
|
||||
(LUMBERJACK, (WOOD, 1.5)),
|
||||
(MINER, (STONE, 0.6)),
|
||||
(FISHER, (MEAT, 3.0)),
|
||||
(HUNTER, (MEAT, 0.25)),
|
||||
(COOK, (FOOD, 20.0)),
|
||||
]);
|
||||
let production = MapVec::from_list(
|
||||
&[
|
||||
(Farmer, (Flour, 2.0)),
|
||||
(Lumberjack, (Wood, 1.5)),
|
||||
(Miner, (Stone, 0.6)),
|
||||
(Fisher, (Meat, 3.0)),
|
||||
(Hunter, (Meat, 0.25)),
|
||||
(Cook, (Food, 20.0)),
|
||||
],
|
||||
(Rock, 0.0),
|
||||
);
|
||||
|
||||
let mut demand = Stocks::from_default(0.0);
|
||||
for (labor, orders) in &orders {
|
||||
@ -881,7 +929,7 @@ impl Site {
|
||||
// Births/deaths
|
||||
const NATURAL_BIRTH_RATE: f32 = 0.15;
|
||||
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
|
||||
} else {
|
||||
0.0
|
||||
@ -890,26 +938,33 @@ impl Site {
|
||||
}
|
||||
}
|
||||
|
||||
type Occupation = &'static str;
|
||||
const FARMER: Occupation = "farmer";
|
||||
const LUMBERJACK: Occupation = "lumberjack";
|
||||
const MINER: Occupation = "miner";
|
||||
const FISHER: Occupation = "fisher";
|
||||
const HUNTER: Occupation = "hunter";
|
||||
const COOK: Occupation = "cook";
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
enum Occupation {
|
||||
Farmer = 0,
|
||||
Lumberjack = 1,
|
||||
Miner = 2,
|
||||
Fisher = 3,
|
||||
Hunter = 4,
|
||||
Cook = 5,
|
||||
}
|
||||
|
||||
type Stock = &'static str;
|
||||
const WHEAT: Stock = "wheat";
|
||||
const FLOUR: Stock = "flour";
|
||||
const MEAT: Stock = "meat";
|
||||
const FISH: Stock = "fish";
|
||||
const GAME: Stock = "game";
|
||||
const FOOD: Stock = "food";
|
||||
const LOGS: Stock = "logs";
|
||||
const WOOD: Stock = "wood";
|
||||
const ROCK: Stock = "rock";
|
||||
const STONE: Stock = "stone";
|
||||
const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE];
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Stock {
|
||||
Wheat = 0,
|
||||
Flour = 1,
|
||||
Meat = 2,
|
||||
Fish = 3,
|
||||
Game = 4,
|
||||
Food = 5,
|
||||
Logs = 6,
|
||||
Wood = 7,
|
||||
Rock = 8,
|
||||
Stone = 9,
|
||||
}
|
||||
|
||||
const TRADE_STOCKS: [Stock; 5] = [Flour, Meat, Food, Wood, Stone];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TradeState {
|
||||
@ -934,21 +989,35 @@ impl Default for TradeState {
|
||||
|
||||
pub type Stocks<T> = MapVec<Stock, T>;
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl<K: Copy + Eq + Hash, T: Default + Clone> MapVec<K, T> {
|
||||
pub fn from_list<'a>(i: impl IntoIterator<Item = &'a (K, T)>) -> Self
|
||||
/// Need manual implementation of Default since K doesn't need that bound.
|
||||
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
|
||||
K: 'a,
|
||||
T: 'a,
|
||||
{
|
||||
Self {
|
||||
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;
|
||||
|
||||
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) }
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use core::hash::BuildHasherDefault;
|
||||
use fxhash::FxHasher64;
|
||||
use hashbrown::HashSet;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use vek::*;
|
||||
@ -7,7 +9,11 @@ pub struct Location {
|
||||
pub(crate) name: String,
|
||||
pub(crate) center: Vec2<i32>,
|
||||
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 {
|
||||
|
@ -1353,14 +1353,15 @@ impl WorldSim {
|
||||
.map(|l| l.center)
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>();
|
||||
// NOTE: We assume that usize is 8 or fewer bytes.
|
||||
(0..locations.len()).for_each(|i| {
|
||||
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.iter().skip(1).take(2).for_each(|(j, _)| {
|
||||
locations[i].neighbours.insert(*j);
|
||||
locations[*j].neighbours.insert(i);
|
||||
locations[i].neighbours.insert(*j as u64);
|
||||
locations[*j].neighbours.insert(i as u64);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -16,9 +16,11 @@ use common::{
|
||||
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||
};
|
||||
use core::{f32, hash::BuildHasherDefault};
|
||||
use fxhash::FxHasher64;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::prelude::*;
|
||||
use std::{f32, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
impl WorldSim {
|
||||
@ -381,7 +383,16 @@ impl Floor {
|
||||
_ => 100000.0,
|
||||
};
|
||||
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
|
||||
.poll(
|
||||
FLOOR_SIZE.product() as usize + 1,
|
||||
|
@ -18,9 +18,10 @@ use common::{
|
||||
terrain::{Block, BlockKind, TerrainChunkSize},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||
};
|
||||
use fxhash::FxHasher64;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rand::prelude::*;
|
||||
use std::{collections::VecDeque, f32};
|
||||
use std::{collections::VecDeque, f32, hash::BuildHasherDefault};
|
||||
use vek::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -967,7 +968,11 @@ pub struct Sample<'a> {
|
||||
}
|
||||
|
||||
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>,
|
||||
sampler_warp: StructureGen2d,
|
||||
hazard: Id<Plot>,
|
||||
@ -978,7 +983,7 @@ impl Land {
|
||||
let mut plots = Store::default();
|
||||
let hazard = plots.insert(Plot::Hazard);
|
||||
Self {
|
||||
tiles: HashMap::new(),
|
||||
tiles: HashMap::default(),
|
||||
plots,
|
||||
sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5),
|
||||
hazard,
|
||||
@ -1089,21 +1094,38 @@ impl Land {
|
||||
|from: &Vec2<i32>, to: &Vec2<i32>| path_cost_fn(self.tile_at(*from), self.tile_at(*to));
|
||||
let satisfied = |pos: &Vec2<i32>| *pos == dest;
|
||||
|
||||
Astar::new(250, origin, heuristic)
|
||||
.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 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(
|
||||
&self,
|
||||
start: Vec2<i32>,
|
||||
max_size: usize,
|
||||
_rng: &mut impl Rng,
|
||||
mut match_fn: impl FnMut(Option<&Plot>) -> bool,
|
||||
) -> HashSet<Vec2<i32>> {
|
||||
) -> HashSet<Vec2<i32>, BuildHasherDefault<FxHasher64>> {
|
||||
let mut open = VecDeque::new();
|
||||
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 {
|
||||
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 grid;
|
||||
pub mod hash_cache;
|
||||
pub mod random;
|
||||
pub mod sampler;
|
||||
pub mod seed_expan;
|
||||
@ -12,7 +11,6 @@ pub mod unit_chooser;
|
||||
pub use self::{
|
||||
fast_noise::FastNoise,
|
||||
grid::Grid,
|
||||
hash_cache::HashCache,
|
||||
random::{RandomField, RandomPerm},
|
||||
sampler::{Sampler, SamplerMut},
|
||||
small_cache::SmallCache,
|
||||
|
Loading…
Reference in New Issue
Block a user