Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion feather/base/src/anvil/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ impl RegionHandle {
Ok((chunk, level.entities.clone(), level.block_entities.clone()))
}

/// Checks if the specified chunk position is generated in this region.
/// # Panics
/// Panics if the specified chunk position is not within this
/// region file.
pub fn check_chunk_existence(&self, pos: ChunkPosition) -> bool {
self.header.location_for_chunk(pos).exists()
}

/// Saves the given chunk to this region file. The header will be updated
/// accordingly and saved as well.
///
Expand Down Expand Up @@ -387,7 +395,7 @@ fn read_section_into_chunk(section: &mut LevelSection, chunk: &mut Chunk) -> Res

let chunk_section = ChunkSection::new(blocks, light);

chunk.set_section_at(section.y as isize, Some(chunk_section));
chunk.set_section_at_raw(section.y as isize, Some(chunk_section));

Ok(())
}
Expand Down
26 changes: 17 additions & 9 deletions feather/base/src/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,20 +254,25 @@ impl Chunk {
}

/// Gets the chunk section at index `y`.
pub fn section(&self, y: usize) -> Option<&ChunkSection> {
self.sections.get(y)?.as_ref()
pub fn section(&self, y: isize) -> Option<&ChunkSection> {
self.sections.get((y + 1) as usize)?.as_ref()
}

/// Mutably gets the chunk section at index `y`.
pub fn section_mut(&mut self, y: usize) -> Option<&mut ChunkSection> {
self.sections.get_mut(y)?.as_mut()
pub fn section_mut(&mut self, y: isize) -> Option<&mut ChunkSection> {
self.sections.get_mut((y + 1) as usize)?.as_mut()
}

/// Sets the section at index `y`.
pub fn set_section_at(&mut self, y: isize, section: Option<ChunkSection>) {
self.sections[(y + 1) as usize] = section;
}

/// Directly sets the section at index `y` without offseting it. Useful when loading from region files
pub fn set_section_at_raw(&mut self, y: isize, section: Option<ChunkSection>) {
self.sections[y as usize] = section;
}

/// Gets the sections of this chunk.
pub fn sections(&self) -> &[Option<ChunkSection>] {
&self.sections
Expand Down Expand Up @@ -419,7 +424,7 @@ mod tests {

chunk.set_block_at(0, 0, 0, BlockId::andesite());
assert_eq!(chunk.block_at(0, 0, 0).unwrap(), BlockId::andesite());
assert!(chunk.section(1).is_some());
assert!(chunk.section(0).is_some());
}

#[test]
Expand Down Expand Up @@ -472,7 +477,7 @@ mod tests {
assert_eq!(chunk.block_at(x, (section * 16) + y, z), Some(block));
if counter != 0 {
assert!(
chunk.section(section + 1).is_some(),
chunk.section(section as isize).is_some(),
"Section {} failed",
section
);
Expand All @@ -485,14 +490,17 @@ mod tests {

// Go through again to be sure
for section in 0..16 {
assert!(chunk.section(section + 1).is_some());
assert!(chunk.section(section).is_some());
let mut counter = 0;
for x in 0..16 {
for y in 0..16 {
for z in 0..16 {
let block = BlockId::from_vanilla_id(counter);
assert_eq!(chunk.block_at(x, (section * 16) + y, z), Some(block));
assert!(chunk.section(section + 1).is_some());
assert_eq!(
chunk.block_at(x, (section as usize * 16) + y, z),
Some(block)
);
assert!(chunk.section(section).is_some());
counter += 1;
}
}
Expand Down
134 changes: 134 additions & 0 deletions feather/base/src/chunk_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};

use crate::Chunk;
use anyhow::bail;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};

pub type ChunkHandle = Arc<ChunkLock>;
/// A wrapper around a RwLock. Cannot be locked for writing when unloaded.
/// This structure exists so that a chunk can be read from even after being unloaded without accidentaly writing to it.
#[derive(Debug)]
pub struct ChunkLock {
loaded: AtomicBool,
lock: RwLock<Chunk>,
}
impl ChunkLock {
pub fn new(chunk: Chunk, loaded: bool) -> Self {
Self {
loaded: AtomicBool::new(loaded),
lock: RwLock::new(chunk),
}
}
/// Returns whether the chunk is loaded.
pub fn is_loaded(&self) -> bool {
self.loaded.load(Ordering::SeqCst)
}
/// Attempts to set the chunk as unloaded. Returns an error if the chunk is locked as writable.
pub fn set_unloaded(&self) -> anyhow::Result<()> {
if self.loaded.swap(false, Ordering::SeqCst) {
// FIXME: Decide what to do when unloading an unloaded chunk
}
if self.lock.try_read().is_none() {
// Locking fails when someone else already owns a write lock
bail!("Cannot unload chunk because it is locked as writable!")
}
Ok(())
}
/// Sets the chunk as loaded and returns the previous state.
pub fn set_loaded(&self) -> bool {
self.loaded.swap(true, Ordering::SeqCst)
}

/// Locks this chunk with read acccess. Doesn't block.
/// Returns None if the chunk is unloaded or locked for writing, Some otherwise.
pub fn try_read(&self) -> Option<RwLockReadGuard<Chunk>> {
self.lock.try_read()
}

/// Locks this chunk with read acccess, blocking the current thread until it can be acquired.
/// Returns None if the chunk is unloaded, Some otherwise.
pub fn read(&self) -> RwLockReadGuard<Chunk> {
self.lock.read()
}
/// Locks this chunk with exclusive write acccess. Doesn't block.
/// Returns None if the chunk is unloaded or locked already, Some otherwise.
pub fn try_write(&self) -> Option<RwLockWriteGuard<Chunk>> {
if self.is_loaded() {
self.lock.try_write()
} else {
None
}
}
/// Locks this chunk with exclusive write acccess, blocking the current thread until it can be acquired.
/// Returns None if the chunk is unloaded, Some otherwise.
pub fn write(&self) -> Option<RwLockWriteGuard<Chunk>> {
if self.is_loaded() {
Some(self.lock.write())
} else {
None
}
}

pub fn is_locked(&self) -> bool {
self.lock.is_locked()
}
}

#[cfg(test)]
mod tests {
use std::{
thread::{sleep, spawn, JoinHandle},
time::Duration,
};

use libcraft_core::ChunkPosition;

use super::*;
fn empty_lock(x: i32, z: i32, loaded: bool) -> ChunkLock {
ChunkLock::new(Chunk::new(ChunkPosition::new(x, z)), loaded)
}
#[test]
fn normal_function() {
let lock = empty_lock(0, 0, true);
for _ in 0..100 {
// It should be possible to lock in any way
if rand::random::<bool>() {
let _guard = lock.try_read().unwrap();
} else {
let _guard = lock.try_write().unwrap();
}
}
}
#[test]
fn cannot_write_unloaded() {
let lock = empty_lock(0, 0, false);
assert!(lock.try_write().is_none())
}
#[test]
fn can_read_unloaded() {
let lock = empty_lock(0, 0, false);
assert!(lock.try_read().is_some())
}
#[test]
fn multithreaded() {
let lock = Arc::new(empty_lock(0, 0, true));
let mut handles: Vec<JoinHandle<()>> = vec![];
for _ in 0..20 {
let l = lock.clone();
handles.push(spawn(move || {
while let Some(guard) = l.write() {
sleep(Duration::from_millis(10));
drop(guard)
}
}))
}
sleep(Duration::from_millis(1000));
lock.set_unloaded().unwrap_or(()); // Discard error
for h in handles {
h.join().unwrap() // Wait for all threads to stop
}
}
}
2 changes: 2 additions & 0 deletions feather/base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ use serde::{Deserialize, Serialize};

pub mod anvil;
pub mod chunk;
pub mod chunk_lock;
pub mod inventory;
pub mod metadata;

pub use blocks::*;
pub use chunk::{Chunk, ChunkSection, CHUNK_HEIGHT, CHUNK_WIDTH};
pub use chunk_lock::*;
pub use generated::{Area, Biome, EntityKind, Inventory, Item, ItemStack};
pub use libcraft_blocks::{BlockKind, BlockState};
pub use libcraft_core::{position, vec3, BlockPosition, ChunkPosition, Gamemode, Position, Vec3d};
Expand Down
1 change: 1 addition & 0 deletions feather/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ uuid = { version = "0.8", features = [ "v4" ] }
libcraft-core = { path = "../../libcraft/core" }
rayon = "1.5"
worldgen = { path = "../worldgen", package = "feather-worldgen" }
rand = "0.8"
Loading