Make sure location hint calculation is only done once.

Location hint calculation is expensive and should not be done
in a hot loop.

This is a fixup for 1572a5a759.
This commit is contained in:
Tormod G. Hellen 2023-05-29 17:37:09 +02:00
parent e71eee0cd2
commit 1d0329cfa7
No known key found for this signature in database
GPG Key ID: 13F76F45CB334E5B
2 changed files with 113 additions and 38 deletions

View File

@ -107,20 +107,22 @@ impl ProximitySpec {
}
}
struct ProximityRequirements {
struct ProximityRequirementsBuilder {
all_of: Vec<ProximitySpec>,
any_of: Vec<ProximitySpec>,
}
impl ProximityRequirements {
pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
let all_of_compliance = self.all_of.iter().all(|spec| spec.satisfied_by(site));
let any_of_compliance =
self.any_of.is_empty() || self.any_of.iter().any(|spec| spec.satisfied_by(site));
all_of_compliance && any_of_compliance
impl ProximityRequirementsBuilder {
pub fn finalize(self, world_dims: &Aabr<i32>) -> ProximityRequirements {
let location_hint = self.location_hint(world_dims);
ProximityRequirements {
all_of: self.all_of,
any_of: self.any_of,
location_hint,
}
}
pub fn location_hint(&self, world_dims: &Aabr<i32>) -> Aabr<i32> {
fn location_hint(&self, world_dims: &Aabr<i32>) -> Aabr<i32> {
let bounding_box_of_point = |point: Vec2<i32>, max_distance: i32| Aabr {
min: Vec2 {
x: point.x - max_distance,
@ -162,7 +164,7 @@ impl ProximityRequirements {
}
pub fn new() -> Self {
ProximityRequirements {
Self {
all_of: Vec::new(),
any_of: Vec::new(),
}
@ -189,6 +191,25 @@ impl ProximityRequirements {
}
}
struct ProximityRequirements {
all_of: Vec<ProximitySpec>,
any_of: Vec<ProximitySpec>,
location_hint: Aabr<i32>,
}
impl ProximityRequirements {
pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
if self.location_hint.contains_point(site) {
let all_of_compliance = self.all_of.iter().all(|spec| spec.satisfied_by(site));
let any_of_compliance =
self.any_of.is_empty() || self.any_of.iter().any(|spec| spec.satisfied_by(site));
all_of_compliance && any_of_compliance
} else {
false
}
}
}
impl<'a, R: Rng> GenCtx<'a, R> {
pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> {
let mut entropy = self.rng.gen::<[u8; 32]>();
@ -235,15 +256,17 @@ impl Civs {
info!(?initial_civ_count, "all civilisations created");
prof_span!(guard, "find locations and establish sites");
let world_dims = ctx.sim.get_aabr();
for _ in 0..initial_civ_count * 3 {
attempt(5, || {
let (loc, kind) = match ctx.rng.gen_range(0..64) {
0..=5 => (
find_site_loc(
&mut ctx,
&ProximityRequirements::new()
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.castle_enemies(), 40)
.close_to_one_of(this.towns(), 20),
.close_to_one_of(this.towns(), 20)
.finalize(&world_dims),
SiteKind::Castle,
)?,
SiteKind::Castle,
@ -253,8 +276,9 @@ impl Civs {
(
find_site_loc(
&mut ctx,
&ProximityRequirements::new()
.avoid_all_of(this.tree_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.tree_enemies(), 40)
.finalize(&world_dims),
SiteKind::GiantTree,
)?,
SiteKind::GiantTree,
@ -263,8 +287,9 @@ impl Civs {
(
find_site_loc(
&mut ctx,
&ProximityRequirements::new()
.avoid_all_of(this.tree_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.tree_enemies(), 40)
.finalize(&world_dims),
SiteKind::Tree,
)?,
SiteKind::Tree,
@ -274,7 +299,9 @@ impl Civs {
32..=37 => (
find_site_loc(
&mut ctx,
&ProximityRequirements::new().avoid_all_of(this.gnarling_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.gnarling_enemies(), 40)
.finalize(&world_dims),
SiteKind::Gnarling,
)?,
SiteKind::Gnarling,
@ -283,8 +310,9 @@ impl Civs {
38..=43 => (
find_site_loc(
&mut ctx,
&ProximityRequirements::new()
.avoid_all_of(this.chapel_site_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.chapel_site_enemies(), 40)
.finalize(&world_dims),
SiteKind::ChapelSite,
)?,
SiteKind::ChapelSite,
@ -292,7 +320,9 @@ impl Civs {
44..=49 => (
find_site_loc(
&mut ctx,
&ProximityRequirements::new().avoid_all_of(this.gnarling_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.gnarling_enemies(), 40)
.finalize(&world_dims),
SiteKind::Adlet,
)?,
SiteKind::Adlet,
@ -300,7 +330,9 @@ impl Civs {
_ => (
find_site_loc(
&mut ctx,
&ProximityRequirements::new().avoid_all_of(this.dungeon_enemies(), 40),
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.dungeon_enemies(), 40)
.finalize(&world_dims),
SiteKind::Dungeon,
)?,
SiteKind::Dungeon,
@ -763,9 +795,11 @@ impl Civs {
13..=18 => SiteKind::SavannahPit,
_ => SiteKind::Refactor,
};
let world_dims = ctx.sim.get_aabr();
let site = attempt(100, || {
let avoid_town_enemies =
ProximityRequirements::new().avoid_all_of(self.town_enemies(), 60);
let avoid_town_enemies = ProximityRequirementsBuilder::new()
.avoid_all_of(self.town_enemies(), 60)
.finalize(&world_dims);
let loc = find_site_loc(ctx, &avoid_town_enemies, kind)?;
Some(self.establish_site(ctx, loc, |place| Site {
kind,
@ -1502,15 +1536,8 @@ fn find_site_loc(
prof_span!("find_site_loc");
const MAX_ATTEMPTS: usize = 10000;
let mut loc = None;
let location_hint = proximity_reqs.location_hint;
for _ in 0..MAX_ATTEMPTS {
let world_dims = Aabr {
min: Vec2 { x: 0, y: 0 },
max: Vec2 {
x: ctx.sim.get_size().x as i32,
y: ctx.sim.get_size().y as i32,
},
};
let location_hint = proximity_reqs.location_hint(&world_dims);
let test_loc = loc.unwrap_or_else(|| {
Vec2::new(
ctx.rng.gen_range(location_hint.min.x..location_hint.max.x),
@ -1868,22 +1895,51 @@ mod tests {
#[test]
fn empty_proximity_requirements() {
let reqs = ProximityRequirements::new();
let world_dims = Aabr {
min: Vec2 { x: 0, y: 0 },
max: Vec2 {
x: 200_i32,
y: 200_i32,
},
};
let reqs = ProximityRequirementsBuilder::new().finalize(&world_dims);
assert!(reqs.satisfied_by(Vec2 { x: 0, y: 0 }));
}
#[test]
fn avoid_proximity_requirements() {
let reqs =
ProximityRequirements::new().avoid_all_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10);
let world_dims = Aabr {
min: Vec2 {
x: -200_i32,
y: -200_i32,
},
max: Vec2 {
x: 200_i32,
y: 200_i32,
},
};
let reqs = ProximityRequirementsBuilder::new()
.avoid_all_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10)
.finalize(&world_dims);
assert!(reqs.satisfied_by(Vec2 { x: 8, y: -8 }));
assert!(!reqs.satisfied_by(Vec2 { x: -1, y: 1 }));
}
#[test]
fn near_proximity_requirements() {
let reqs =
ProximityRequirements::new().close_to_one_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10);
let world_dims = Aabr {
min: Vec2 {
x: -200_i32,
y: -200_i32,
},
max: Vec2 {
x: 200_i32,
y: 200_i32,
},
};
let reqs = ProximityRequirementsBuilder::new()
.close_to_one_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10)
.finalize(&world_dims);
assert!(reqs.satisfied_by(Vec2 { x: 1, y: -1 }));
assert!(!reqs.satisfied_by(Vec2 { x: -8, y: 8 }));
}
@ -1891,16 +1947,24 @@ mod tests {
#[test]
fn complex_proximity_requirements() {
let a_site = Vec2 { x: 572, y: 724 };
let reqs = ProximityRequirements::new()
let world_dims = Aabr {
min: Vec2 { x: 0, y: 0 },
max: Vec2 {
x: 1000_i32,
y: 1000_i32,
},
};
let reqs = ProximityRequirementsBuilder::new()
.close_to_one_of(vec![a_site].into_iter(), 60)
.avoid_all_of(vec![a_site].into_iter(), 40);
.avoid_all_of(vec![a_site].into_iter(), 40)
.finalize(&world_dims);
assert!(reqs.satisfied_by(Vec2 { x: 572, y: 774 }));
assert!(!reqs.satisfied_by(a_site));
}
#[test]
fn location_hint() {
let reqs = ProximityRequirements::new().close_to_one_of(
let reqs = ProximityRequirementsBuilder::new().close_to_one_of(
vec![Vec2 { x: 1, y: 0 }, Vec2 { x: 13, y: 12 }].into_iter(),
10,
);

View File

@ -1581,6 +1581,17 @@ impl WorldSim {
pub fn get_size(&self) -> Vec2<u32> { self.map_size_lg().chunks().map(u32::from) }
pub fn get_aabr(&self) -> Aabr<i32> {
let size = self.get_size();
Aabr {
min: Vec2 { x: 0, y: 0 },
max: Vec2 {
x: size.x as i32,
y: size.y as i32,
},
}
}
pub fn generate_oob_chunk(&self) -> TerrainChunk {
TerrainChunk::water(CONFIG.sea_level as i32)
}