Merge branch 'sharp/deterministic-civsim' into 'master'

Make civsim and sites deterministic.

See merge request veloren/veloren!1009
This commit is contained in:
Joshua Barretto 2020-05-26 19:22:48 +00:00
commit 786665eac4
14 changed files with 269 additions and 148 deletions

View File

@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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