mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Natural spawning: a start block's section palette answers its conductor test
NaturalSpawner.spawnCategoryForPosition first reads the block at a random height of each chunk, for each category, every tick, and stops on a redstone conductor. That read was a cache miss in a random section of every chunk around every player. When the section's palette holds only conductors (stone and ores) or none (air), the answer is the same for every cell, so it comes from the palette (16 entries at most, each asked twice at most); any other section reads the cell as before. A palette entry no cell uses any more only sends it back to the read. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6d3ed570fb
commit
a91f4a7999
3 files changed
+68
-1
No files matched your search
@@ -426,6 +426,29 @@ impl World {
|
||||
}
|
||||
false
|
||||
}
|
||||
/// What `matches` says of the block at (x, y, z) when its section's
|
||||
/// palette alone tells: every entry matches, or none does. `None` when
|
||||
/// only the cell can tell (a mixed or long palette, or no section there):
|
||||
/// the caller reads [`Self::block`] then, and gets the same answer.
|
||||
pub fn palette_answer(
|
||||
&self,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
mut matches: impl FnMut(BlockStateId) -> bool,
|
||||
) -> Option<bool> {
|
||||
let chunk = self.chunks.get(&(x >> 4, z >> 4))?;
|
||||
let blocks = &chunk.sections[chunk.section_index(y)?].blocks;
|
||||
// Each entry is asked twice at most: past 16 of them, reading the
|
||||
// cell costs less.
|
||||
if blocks.is_global() || blocks.palette_len() > 16 {
|
||||
return None;
|
||||
}
|
||||
if !blocks.maybe_has(|v| matches(BlockStateId(v))) {
|
||||
return Some(false);
|
||||
}
|
||||
(!blocks.maybe_has(|v| !matches(BlockStateId(v)))).then_some(true)
|
||||
}
|
||||
/// The block entities to tick this tick, in order (`BlockEntities::tickers`).
|
||||
pub fn block_entity_tickers(
|
||||
&mut self,
|
||||
@@ -613,6 +636,32 @@ mod tests {
|
||||
/// A box misses a state only where no section it reaches lists it, and
|
||||
/// unloaded columns or heights past the world count as their air.
|
||||
#[test]
|
||||
fn a_palette_answer_is_the_block_s_own() {
|
||||
let mut world = World::new(BlockStateId(0), 0);
|
||||
let mut chunk = Chunk::empty(0, 0);
|
||||
chunk.set(3, 70, 3, 9);
|
||||
world.chunks.insert((0, 0), Arc::new(chunk));
|
||||
let nine = |s: BlockStateId| s.0 == 9;
|
||||
let any = |s: BlockStateId| s.0 == 9 || s.0 == 0;
|
||||
for y in [-100, -1, 0, 50, 64, 70, 79, 80, 400] {
|
||||
for (x, z) in [(3, 3), (0, 0), (15, 15), (20, 3), (-1, -1)] {
|
||||
for matches in [&nine as &dyn Fn(BlockStateId) -> bool, &any] {
|
||||
if let Some(answer) = world.palette_answer(x, y, z, matches) {
|
||||
assert_eq!(answer, matches(world.block(x, y, z)), "({x}, {y}, {z})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// An air section answers for all its cells; the section holding 9
|
||||
// answers only where every entry agrees.
|
||||
assert_eq!(world.palette_answer(1, 50, 1, nine), Some(false));
|
||||
assert_eq!(world.palette_answer(1, 70, 1, nine), None);
|
||||
assert_eq!(world.palette_answer(1, 70, 1, any), Some(true));
|
||||
// No chunk, or no section: the caller reads the block.
|
||||
assert_eq!(world.palette_answer(20, 70, 3, nine), None);
|
||||
assert_eq!(world.palette_answer(1, 400, 1, nine), None);
|
||||
}
|
||||
#[test]
|
||||
fn may_hold_reads_the_palettes_of_the_reached_sections() {
|
||||
let mut world = World::new(BlockStateId(0), 0);
|
||||
let mut chunk = Chunk::empty(0, 0);
|
||||
|
||||
@@ -447,6 +447,19 @@ impl SpawnLevel for GameLevel<'_, '_> {
|
||||
}
|
||||
}
|
||||
impl NaturalLevel for GameLevel<'_, '_> {
|
||||
/// From the section's palette when it answers for every block (a section
|
||||
/// of stone and ores, or of air): reading the cell itself is a cache miss
|
||||
/// in a random section of every chunk, each tick.
|
||||
fn redstone_conductor(&self, x: i32, y: i32, z: i32) -> bool {
|
||||
let data = self.w.data;
|
||||
let conducts = |s: BlockStateId| {
|
||||
data.state(s).flags & horizon_client::mobs::data::REDSTONE_CONDUCTOR != 0
|
||||
};
|
||||
self.w
|
||||
.world
|
||||
.palette_answer(x, y, z, conducts)
|
||||
.unwrap_or_else(|| conducts(self.state(x, y, z)))
|
||||
}
|
||||
fn spawn_hooks(&self) -> bool {
|
||||
!horizon_client::mod_api::mods().is_empty()
|
||||
}
|
||||
|
||||
@@ -301,6 +301,11 @@ fn spawns_far_from_player(s: Species, category: Category) -> bool {
|
||||
pub type MobType = (Species, f32, f32, Category);
|
||||
|
||||
pub trait NaturalLevel: SpawnLevel {
|
||||
/// `BlockState.isRedstoneConductor` of the block at (x, y, z): the start
|
||||
/// of a spawn attempt, read for every chunk and category each tick.
|
||||
fn redstone_conductor(&self, x: i32, y: i32, z: i32) -> bool {
|
||||
self.mob_data().state(self.state(x, y, z)).flags & super::data::REDSTONE_CONDUCTOR != 0
|
||||
}
|
||||
/// Whether server mods hear `potential_spawns`.
|
||||
fn spawn_hooks(&self) -> bool {
|
||||
false
|
||||
@@ -1092,7 +1097,7 @@ pub fn spawn_category_for_position<L: NaturalLevel, R: RandomSource>(
|
||||
trace: &mut dyn FnMut(Event),
|
||||
) {
|
||||
let [x0, y, z0] = start;
|
||||
if l.mob_data().state(l.state(x0, y, z0)).flags & super::data::REDSTONE_CONDUCTOR != 0 {
|
||||
if l.redstone_conductor(x0, y, z0) {
|
||||
return;
|
||||
}
|
||||
let mut spawned: Vec<Aabb> = Vec::new();
|
||||
|
||||
Reference in new issue
Block a user