Skip to content
This repository has been archived by the owner on Jul 1, 2024. It is now read-only.

Commit

Permalink
A bit closer
Browse files Browse the repository at this point in the history
  • Loading branch information
DimitriTimoz committed Nov 19, 2023
1 parent 2bebed7 commit 7b45ce0
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 63 deletions.
96 changes: 57 additions & 39 deletions minecraft-server/src/world/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use minecraft_protocol::ids::blocks::Block;
use crate::prelude::*;
use super::*;

const MAX_LIGHT_LEVEL: u8 = 15;

#[derive(Debug, Clone)]
struct SectionLightData(Vec<u8>); // TODO(optimization): Use simd

Expand Down Expand Up @@ -38,7 +40,7 @@ impl SectionLightData {

/// Set the light level at the given position.
pub fn set(&mut self, postion: BlockPositionInChunk, level: u8) -> Result<(), ()> {
if level > 15 {
if level > MAX_LIGHT_LEVEL {
return Err(());
}

Expand All @@ -61,11 +63,11 @@ impl SectionLightData {

/// Set the light level at the given layer to the given level.
pub(super) fn set_layer(&mut self, layer: u8 , level: u8) -> Result<(), ()> {
if level > 15 {
if level > MAX_LIGHT_LEVEL {
return Err(());
}

if layer > 15 {
if layer > MAX_LIGHT_LEVEL {
return Err(());
}

Expand Down Expand Up @@ -93,32 +95,40 @@ impl EdgesLightToPropagate {
}
}

/// Push the given position and level to the correct edge.
/// If the position is not on an edge, nothing is done.
/// The position coordinate will be modified to be on the adjacent chunk
pub fn push(&mut self, position: LightPositionInChunkColumn, level: u8) {
let mut position = position;
let index = match position {
LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => 0,
LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => 1,
LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => 2,
LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => 3,
LightPositionInChunkColumn { bx: 0, y: _, bz: _ } => {
position.bx = 15;
0
},
LightPositionInChunkColumn { bx: _, y: _, bz: 0 } => {
position.bz = 15;
1
}
LightPositionInChunkColumn { bx: 15, y: _, bz: _ } => {
position.bx = 0;
2
}
LightPositionInChunkColumn { bx: _, y: _, bz: 15 } => {
position.bz = 0;
3
}
_ => return,
};
self.edges[index].push((position, level));
}

pub fn pop(&mut self) -> Option<(LightPositionInChunkColumn, u8)> {
for edge in self.edges.iter_mut() {
if let Some((position, level)) = edge.pop() {
return Some((position, level));
}
}
None
}

pub fn expand(&mut self, edges: EdgesLightToPropagate) {
for (i, edge) in edges.edges.iter().enumerate() {
self.edges[i].extend(edge.clone());
}
}


/// Get the ChunkColumnPositions of chunks that need to be propagated
pub fn chunk_positions_to_propagate(&self, from: ChunkColumnPosition) -> Vec<(ChunkColumnPosition, BinaryHeap<(LightPositionInChunkColumn, u8)>)> {
let mut result = Vec::new();
if !self.edges[0].is_empty() {
Expand Down Expand Up @@ -148,7 +158,6 @@ struct LightSystem {
pub light_mask: u64,
/// The mask of sections that don't have sky light data.
pub empty_light_mask: u64,
edge_light_to_propagate: EdgesLightToPropagate,
}

impl LightSystem {
Expand Down Expand Up @@ -205,22 +214,22 @@ impl LightSystem {
for y in 0..16 {
for i in 0..16 {
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level);
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level);
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level);
edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level);
edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level);
edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level);
}
}
} else {
// Set the part of the section
let first_offset = if section == first_section { first_secion_offset } else { 0 };
let last_offset = if section == last_section { last_section_offset } else { 15 };
let last_offset = if section == last_section { last_section_offset } else { MAX_LIGHT_LEVEL as usize };
for y in first_offset..=last_offset {
self.light_arrays[section].set_layer(y as u8, level)?;
for i in 0..16 {
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 0 }, level);
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: 15 }, level);
edges.push(LightPositionInChunkColumn { bx: i, y: section * 16 + y, bz: MAX_LIGHT_LEVEL }, level);
edges.push(LightPositionInChunkColumn { bx: 0, y: section * 16 + y, bz: i }, level);
edges.push(LightPositionInChunkColumn { bx: 15, y: section * 16 + y, bz: i }, level);
edges.push(LightPositionInChunkColumn { bx: MAX_LIGHT_LEVEL, y: section * 16 + y, bz: i }, level);
}
}
}
Expand Down Expand Up @@ -272,11 +281,10 @@ impl Light {
// TODO: Make this configurable with the world.
Self {
sky_light: LightSystem {
level: 15,
level: MAX_LIGHT_LEVEL,
light_arrays: vec![SectionLightData::new(); 24+2],
light_mask: 0,
empty_light_mask: !0,
edge_light_to_propagate: EdgesLightToPropagate::new(),
},
}
}
Expand Down Expand Up @@ -481,6 +489,17 @@ impl ChunkColumn {
to_propagate.expand(self.explore_sky_light_from_heap(&mut to_explore).map_err(|_| error!("Error while updating light"))?);
Ok(to_propagate)
}

pub(super) fn update_from_edge(&mut self, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Result<(), ()> {
for (position, level) in to_propagate {
let block = Block::from(self.get_block(position.clone().into()));
if block.is_transparent() {
self.light.sky_light.set_level(position.clone(), level.saturating_sub(block.light_absorption()))?;
self.update_light_as_block_changed_at(position.into())?;
}
}
Ok(())
}
}

#[cfg(test)]
Expand All @@ -491,17 +510,17 @@ mod tests {
fn test_section_light_data() {
let mut data = SectionLightData::new();

data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 15).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 15);
data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, MAX_LIGHT_LEVEL).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), MAX_LIGHT_LEVEL);

data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }, 0).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 0 }).unwrap(), 0);

data.set(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }, 1).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 0, bz: 1 }).unwrap(), 1);

data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, 15).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }).unwrap(), 15);
data.set(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }, MAX_LIGHT_LEVEL).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 0, by: 1, bz: 1 }).unwrap(), MAX_LIGHT_LEVEL);

data.set(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }, 1).unwrap();
assert_eq!(data.get(BlockPositionInChunk { bx: 1, by: 1, bz: 1 }).unwrap(), 1);
Expand All @@ -512,41 +531,40 @@ mod tests {
// Manual layer
for z in 0..16 {
for x in 0..16 {
data.set(BlockPositionInChunk { bx: x, by: 0, bz: z }, 15).unwrap();
data.set(BlockPositionInChunk { bx: x, by: 0, bz: z }, MAX_LIGHT_LEVEL).unwrap();
}
}

for z in 0..16 {
for x in 0..16 {
assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z);
assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 0, bz: z }).unwrap(), MAX_LIGHT_LEVEL, "x: {}, z: {}", x, z);
}
}

// Test layer
data.set_layer(1, 15).unwrap();
data.set_layer(1, MAX_LIGHT_LEVEL).unwrap();
for x in 0..16 {
for z in 0..16 {
assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), 15, "x: {}, z: {}", x, z);
assert_eq!(data.get(BlockPositionInChunk { bx: x, by: 1, bz: z }).unwrap(), MAX_LIGHT_LEVEL, "x: {}, z: {}", x, z);
}
}
}

#[test]
fn test_set_region() {
let mut sky_light = LightSystem {
level: 15,
level: MAX_LIGHT_LEVEL,
light_arrays: vec![SectionLightData::new(); 16+2],
light_mask: 0,
empty_light_mask: !0,
edge_light_to_propagate: EdgesLightToPropagate::new(),
};

sky_light.set_region(1, 33, 15).unwrap();
sky_light.set_region(1, 33, MAX_LIGHT_LEVEL).unwrap();

// Test in
assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), 15);
assert_eq!(sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 1, by: 15, bz: 8 }).unwrap(), 15);
assert_eq!(sky_light.light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), 15);
assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 0, by: 1, bz: 7 }).unwrap(), MAX_LIGHT_LEVEL);
assert_eq!(sky_light.light_arrays[1].get(BlockPositionInChunk { bx: 1, by: MAX_LIGHT_LEVEL, bz: 8 }).unwrap(), MAX_LIGHT_LEVEL);
assert_eq!(sky_light.light_arrays[2].get(BlockPositionInChunk { bx: 3, by: 0, bz: 0 }).unwrap(), MAX_LIGHT_LEVEL);

// Test out
assert_eq!(sky_light.light_arrays[0].get(BlockPositionInChunk { bx: 4, by: 0, bz: 2 }).unwrap(), 0);
Expand Down
62 changes: 38 additions & 24 deletions minecraft-server/src/world/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ impl ChunkColumn {
}
current_height
}

pub(super) fn get_highest_block(&self) -> u32 {
self.heightmap.max_height.unwrap_or(0)
}
Expand Down Expand Up @@ -448,7 +448,7 @@ impl WorldMap {
pub async fn get_block(&self, position: BlockPosition) -> BlockWithState {
async fn inner_get_block(s: &WorldMap, position: BlockPosition) -> Option<BlockWithState> {
let chunk_position = position.chunk();
let position_in_chunk_column = position.in_chunk_column();
let position_in_chunk_column: BlockPositionInChunkColumn = position.in_chunk_column();
let chunk_column_position = chunk_position.chunk_column();
let shard = chunk_column_position.shard(s.shard_count);

Expand Down Expand Up @@ -485,7 +485,7 @@ impl WorldMap {
}

pub async fn set_block(&self, position: BlockPosition, block: BlockWithState) {
async fn inner_get_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> {
async fn inner_set_block(s: &WorldMap, position: BlockPosition, block: BlockWithState) -> Option<(EdgesLightToPropagate, ChunkColumnPosition)> {
let chunk_position = position.chunk();
let position_in_chunk_column = position.in_chunk_column();
let chunk_column_position = chunk_position.chunk_column();
Expand All @@ -495,36 +495,42 @@ impl WorldMap {
let chunk_column = shard.get_mut(&chunk_column_position)?;
chunk_column.set_block(position_in_chunk_column.clone(), block);
chunk_column.update_light_as_block_changed_at(position_in_chunk_column).ok().map(|to_propagate| (to_propagate, chunk_column_position))

}
let to_propagate = inner_get_block(self, position, block).await;

let to_propagate = inner_set_block(self, position, block).await;
if let Some(to_propagate) = to_propagate {
let (to_propagate, from) = to_propagate;
let to_popagate = to_propagate.chunk_positions_to_propagate(from);
for (chunk_column_position, to_propagate) in to_popagate {
self.update_light_from_edge(self, chunk_column_position, to_propagate).await;
self.update_light_from_edge(chunk_column_position, to_propagate).await;
}
}
}

pub async fn get_skylight(&self, position: BlockPosition) -> u8 {
let chunk_position = position.chunk();
let position_in_chunk_column = position.in_chunk_column();
let chunk_column_position = chunk_position.chunk_column();
let shard = chunk_column_position.shard(self.shard_count);

let shard = self.shards[shard].read().await;
let chunk_column = shard.get(&chunk_column_position).unwrap();
chunk_column.light.get_skylight_level(position_in_chunk_column.into())
async fn inner_get_skylight(s: &WorldMap, position: BlockPosition) -> Option<u8> {
let chunk_position = position.chunk();
let chunk_column_position = chunk_position.chunk_column();
let shard = chunk_column_position.shard(s.shard_count);

let shard = s.shards[shard].read().await;
let chunk_column = shard.get(&chunk_column_position)?;
let level = chunk_column.get_skylight(position.in_chunk_column());
Some(level)
}
inner_get_skylight(self, position).await.unwrap_or(0)
}

async fn update_light_from_edge(&self, s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) {
let shard = chunk_column_position.shard(self.shard_count);

let mut shard = s.shards[shard].write().await;
let chunk_column = shard.get_mut(&chunk_column_position);
async fn update_light_from_edge(&self, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) {
async fn inner_get_skylight(s: &WorldMap, chunk_column_position: ChunkColumnPosition, to_propagate: BinaryHeap<(LightPositionInChunkColumn, u8)>) -> Option<()> {
let shard = chunk_column_position.shard(s.shard_count);


let mut shard = s.shards[shard].write().await;
let chunk_column = shard.get_mut(&chunk_column_position)?;
chunk_column.update_from_edge(to_propagate).ok()?;
Some(())
}
inner_get_skylight(self, chunk_column_position, to_propagate).await;
}

pub async fn load(&self, position: ChunkColumnPosition) {
Expand Down Expand Up @@ -743,10 +749,9 @@ mod tests {

#[tokio::test]
async fn test_sky_light_flat_chunk() {
let world = WorldMap::new(10);
let world = WorldMap::new(100);
world.load(ChunkColumnPosition { cx: 0, cz: 0 }).await;


// Check that the sky light is equal to the light level above the grass and on the top of the world.
for x in 0..16 {
for z in 0..16 {
Expand All @@ -768,15 +773,24 @@ mod tests {
assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14);
world.set_block(BlockPosition { x: 0, y: -50, z: 1 }, BlockWithState::Air).await;
assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: 1}).await, 14);

// test on chunk border
world.load(ChunkColumnPosition { cx: 1, cz: 0 }).await;
world.load(ChunkColumnPosition { cx: 0, cz: 1 }).await;
world.load(ChunkColumnPosition { cx: 1, cz: 1 }).await;
world.load(ChunkColumnPosition { cx: -1, cz: -1 }).await;

assert_ne!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14);
world.set_block(BlockPosition { x: 0, y: -50, z: -1 }, BlockWithState::Air).await;
assert_eq!(world.get_skylight(BlockPosition { x: 0, y: -50, z: -1}).await, 14);
}


#[test]
fn benchmark_get_block() {

let start_time = std::time::Instant::now();
for _ in 0..441 {
let mut column = ChunkColumn::flat();
let _column = ChunkColumn::flat();
}

let elapsed: Duration = start_time.elapsed();
Expand Down

0 comments on commit 7b45ce0

Please sign in to comment.