Pumpkin/pumpkin-world/src/chunk_system/generation_cache.rs
2026-07-03 18:51:47 +02:00

408 lines
14 KiB
Rust

use super::chunk_state::{Chunk, StagedChunkEnum};
use crate::ProtoChunk;
use crate::chunk::ChunkHeightmapType;
use crate::generation::generator;
use crate::generation::height_limit::HeightLimitView;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::{BlockAccessor, WorldPortalExt};
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::biome::Biome;
use pumpkin_data::block_properties::is_air;
use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::{Block, BlockState, BlockStateId};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::HeightMap;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use tracing::debug;
pub struct Cache {
pub x: i32,
pub z: i32,
pub size: i32,
pub chunks: Vec<Chunk>,
}
impl HeightLimitView for Cache {
fn height(&self) -> u16 {
let mid = ((self.size * self.size) >> 1) as usize;
match &self.chunks[mid] {
Chunk::Proto(chunk) => chunk.height(),
Chunk::Level(_) => panic!(),
}
}
fn bottom_y(&self) -> i8 {
let mid = ((self.size * self.size) >> 1) as usize;
match &self.chunks[mid] {
Chunk::Proto(chunk) => chunk.bottom_y(),
Chunk::Level(_) => panic!(),
}
}
}
impl BlockAccessor for Cache {
fn get_block(&self, position: &BlockPos) -> &'static Block {
GenerationCache::get_block_state(self, &position.0).to_block()
}
fn get_block_state(&self, position: &BlockPos) -> &'static BlockState {
GenerationCache::get_block_state(self, &position.0).to_state()
}
fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId {
GenerationCache::get_block_state(self, &position.0)
}
fn get_block_and_state(&self, position: &BlockPos) -> (&'static Block, &'static BlockState) {
let id = GenerationCache::get_block_state(self, &position.0);
BlockState::from_id_with_block(id)
}
}
impl GenerationCache for Cache {
fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut ProtoChunk> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
(dx >= 0 && dx < self.size && dz >= 0 && dz < self.size)
.then(|| self.chunks[(dx * self.size + dz) as usize].get_proto_chunk_mut())
}
fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size {
return None;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Proto(chunk) => Some(chunk),
Chunk::Level(_) => None,
}
}
fn try_get_proto_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size {
return None;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Proto(chunk) => Some(chunk),
Chunk::Level(_) => None,
}
}
fn get_center_chunk(&self) -> &ProtoChunk {
let mid = ((self.size * self.size) >> 1) as usize;
self.chunks[mid].get_proto_chunk()
}
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk {
let mid = ((self.size * self.size) >> 1) as usize;
self.chunks[mid].get_proto_chunk_mut()
}
fn get_fluid_and_fluid_state(&self, pos: &Vector3<i32>) -> (Fluid, FluidState) {
let id = GenerationCache::get_block_state(self, pos);
let Some(fluid) = Fluid::from_state_id(id) else {
let block = Block::from_state_id(id);
if let Some(properties) = block.properties(id) {
for (name, value) in properties.to_props() {
if name == "waterlogged" {
if value == "true" {
let fluid = Fluid::FLOWING_WATER;
let state = fluid.states[0].clone();
return (fluid, state);
}
break;
}
}
}
let fluid = Fluid::EMPTY;
let state = fluid.states[0].clone();
return (fluid, state);
};
//let state = fluid.get_state(id);
let state = fluid.states[0].clone();
(fluid.clone(), state)
}
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.z;
// debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here
debug!(
"illegal get_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.z, self.size
);
return BlockStateId::AIR;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => data
.section
.get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize)
.unwrap_or(BlockStateId::AIR),
Chunk::Proto(data) => data.get_block_state(pos),
}
}
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.z;
// debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here
debug!(
"illegal set_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.z, self.size
);
return;
}
match &mut self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => {
data.set_block_absolute_y(
(pos.x & 15) as usize,
pos.y,
(pos.z & 15) as usize,
block_state.id,
);
}
Chunk::Proto(data) => {
data.set_block_state(pos.x, pos.y, pos.z, block_state);
}
}
}
fn add_block_entity(&mut self, pos: &Vector3<i32>, nbt: NbtCompound) {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.z;
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
debug!(
"illegal add_block_entity {pos:?} cache pos ({}, {}) size {}",
self.x, self.z, self.size
);
return;
}
match &mut self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(_) => {
debug!("add_block_entity on non-proto chunk at {pos:?}");
}
Chunk::Proto(data) => {
data.add_block_entity(nbt);
}
}
}
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
match heightmap {
HeightMap::WorldSurfaceWg | HeightMap::WorldSurface => {
self.top_block_height_exclusive(x, z)
}
HeightMap::OceanFloorWg | HeightMap::OceanFloor => {
self.ocean_floor_height_exclusive(x, z)
}
HeightMap::MotionBlocking => self.top_motion_blocking_block_height_exclusive(x, z),
HeightMap::MotionBlockingNoLeaves => {
self.top_motion_blocking_block_no_leaves_height_exclusive(x, z)
}
}
}
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let heightmap = data.heightmap.lock().unwrap();
let min_y = data.section.min_y;
heightmap.get(ChunkHeightmapType::MotionBlocking, x, z, min_y)
}
Chunk::Proto(data) => data.top_motion_blocking_block_height_exclusive(x, z),
}
}
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let heightmap = data.heightmap.lock().unwrap();
let min_y = data.section.min_y;
heightmap.get(ChunkHeightmapType::MotionBlockingNoLeaves, x, z, min_y)
}
Chunk::Proto(data) => data.top_motion_blocking_block_no_leaves_height_exclusive(x, z),
}
}
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.z;
debug_assert!(dx < self.size && dy < self.size);
debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
let heightmap = data.heightmap.lock().unwrap();
let min_y = data.section.min_y;
heightmap.get(ChunkHeightmapType::WorldSurface, x, z, min_y) // can we return this?
}
Chunk::Proto(data) => data.top_block_height_exclusive(x, z),
}
}
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.z;
if dx < 0 || dy < 0 || dx >= self.size || dy >= self.size {
return 0;
}
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(_data) => {
0 // todo missing
}
Chunk::Proto(data) => data.ocean_floor_height_exclusive(x, z),
}
}
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome {
let dx = (x >> 4) - self.x;
let dy = (z >> 4) - self.z;
let (dx, dy) = if dx < 0 || dy < 0 || dx >= self.size || dy >= self.size {
// Position is outside the cache — fall back to the centre chunk's biome
let mid = self.size / 2;
(mid, mid)
} else {
(dx, dy)
};
match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => {
// Could this happen?
Biome::from_id(
data.section
.get_rough_biome_absolute_y((x & 15) as usize, y, (z & 15) as usize)
.unwrap_or(0),
)
.unwrap()
}
Chunk::Proto(data) => data.get_terrain_gen_biome(x, y, z),
}
}
fn get_blending_data(
&self,
chunk_x: i32,
chunk_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size {
return None;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Proto(chunk) => chunk.blending_data.as_ref(),
Chunk::Level(data) => data.blending_data.as_ref(),
}
}
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
is_air(GenerationCache::get_block_state(self, local_pos))
}
}
impl Cache {
#[must_use]
pub fn new(x: i32, z: i32, size: i32) -> Self {
Self {
x,
z,
size,
chunks: Vec::with_capacity((size * size) as usize),
}
}
pub fn advance(
&mut self,
stage: StagedChunkEnum,
generator: &generator::VanillaGenerator,
block_registry: &dyn WorldPortalExt,
lighting_config: &LightingEngineConfig,
) {
let mid = ((self.size * self.size) >> 1) as usize;
match stage {
StagedChunkEnum::Empty => panic!("empty stage"),
StagedChunkEnum::StructureStart => {
self.chunks[mid]
.get_proto_chunk_mut()
.set_structure_starts(generator);
}
StagedChunkEnum::StructureReferences => {
self.chunks[mid]
.get_proto_chunk_mut()
.set_structure_references(generator);
}
StagedChunkEnum::Biomes => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_biomes(generator),
StagedChunkEnum::Noise => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_noise(generator),
StagedChunkEnum::Surface => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_surface(generator),
StagedChunkEnum::Carvers => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_carvers(generator),
StagedChunkEnum::Features => {
ProtoChunk::generate_features_and_structure(
self,
block_registry,
&generator.random_config,
);
}
StagedChunkEnum::Lighting => {
let mut engine = crate::lighting::LightEngine::new();
engine.initialize_light(self, lighting_config);
// Only set stage to Lighting if it wasn't already at Lighting or higher
// (initialize_light may short-circuit for already-lit chunks)
let chunk = self.chunks[mid].get_proto_chunk_mut();
if chunk.stage < StagedChunkEnum::Lighting {
chunk.stage = StagedChunkEnum::Lighting;
}
// Engine's internal state is cleared by initialize_light() and will be dropped here
drop(engine);
}
StagedChunkEnum::Spawn => {
ProtoChunk::spawn_mobs(self, block_registry);
}
StagedChunkEnum::Full => {
let chunk = self.chunks[mid].get_proto_chunk_mut();
debug_assert_eq!(chunk.stage, StagedChunkEnum::Spawn);
chunk.stage = StagedChunkEnum::Full;
self.chunks[mid].upgrade_to_level_chunk(&generator.dimension, lighting_config);
}
StagedChunkEnum::None => {}
}
}
}