Superflat layer generation (#24) by aramperes · Pull Request #108 · feather-rs/feather · GitHub
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.

78 changes: 78 additions & 0 deletions core/src/save/level.rs
1 change: 1 addition & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = ["caelunshun <caelunshun@gmail.com>"]
edition = "2018"

[dependencies]
feather-blocks = { path = "../blocks" }
feather-core = { path = "../core" }
feather-item-block = { path = "../item_block" }
mio = "0.6.19"
Expand Down
22 changes: 11 additions & 11 deletions server/src/chunk_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//! Also handles unloading chunks when unused.
use crossbeam::channel::{Receiver, Sender};
use shrev::{EventChannel, ReaderId};
use specs::{Component, DispatcherBuilder, Entity, Read, ReadStorage, System, World, Write};
use specs::{
Component, DispatcherBuilder, Entity, Read, ReadExpect, ReadStorage, System, World, Write,
};
use std::sync::atomic::{AtomicU32, Ordering};

use feather_core::world::{ChunkMap, ChunkPosition};
Expand All @@ -13,26 +15,22 @@ use rayon::prelude::*;

use crate::entity::EntityDestroyEvent;
use crate::systems::{CHUNK_HOLD_REMOVE, CHUNK_LOAD, CHUNK_OPTIMIZE, CHUNK_UNLOAD};
use crate::worldgen::WorldGenerator;
use crate::{chunkworker, current_time_in_millis, TickCount, TPS};
use hashbrown::HashSet;
use multimap::MultiMap;
use specs::storage::BTreeStorage;
use std::collections::VecDeque;
use std::sync::Arc;

/// A handle for interacting with the chunk
/// worker thread.
#[derive(Debug, Clone)]
pub struct ChunkWorkerHandle {
sender: Sender<chunkworker::Request>,
receiver: Receiver<chunkworker::Reply>,
}

impl Default for ChunkWorkerHandle {
fn default() -> Self {
let (sender, receiver) = chunkworker::start("world");
Self { sender, receiver }
}
}

/// Event which is triggered when a chunk is loaded.
#[derive(Debug, Clone, Copy)]
pub struct ChunkLoadEvent {
Expand All @@ -53,7 +51,7 @@ impl<'a> System<'a> for ChunkLoadSystem {
Write<'a, ChunkMap>,
Write<'a, EventChannel<ChunkLoadEvent>>,
Write<'a, EventChannel<ChunkLoadFailEvent>>,
Read<'a, ChunkWorkerHandle>,
ReadExpect<'a, ChunkWorkerHandle>,
);

fn run(&mut self, data: Self::SystemData) {
Expand Down Expand Up @@ -83,9 +81,11 @@ impl<'a> System<'a> for ChunkLoadSystem {
fn setup(&mut self, world: &mut World) {
use specs::prelude::SystemData;

let generator = world.fetch_mut::<Arc<dyn WorldGenerator>>().clone();

info!("Starting chunk worker thread");
let handle = chunkworker::start("world");
world.insert(handle);
let (sender, receiver) = chunkworker::start("world", generator);
world.insert(ChunkWorkerHandle { sender, receiver });

Self::SystemData::setup(world);
}
Expand Down
69 changes: 59 additions & 10 deletions server/src/chunkworker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
//! the chunk back over a channel. If the chunk did not exist,
//! it will notify the server thread of the incident.
//! In response, the server thread should generate the chunk.
use crate::worldgen::WorldGenerator;
use crossbeam::channel::{Receiver, Sender};
use feather_core::region;
use feather_core::region::{RegionHandle, RegionPosition};
use feather_core::world::chunk::Chunk;
use feather_core::world::ChunkPosition;
use hashbrown::HashMap;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

pub type Reply = (ChunkPosition, Result<Chunk, Error>);
Expand Down Expand Up @@ -72,12 +74,18 @@ struct ChunkWorker {

/// A map of currently open region files
open_regions: HashMap<RegionPosition, RegionFile>,

/// World generator for new chunks.
world_generator: Arc<dyn WorldGenerator>,
}

/// Starts a chunk worker on a new thread.
/// The returned channels can be used
/// to communicate with the worker.
pub fn start(world_dir: &str) -> (Sender<Request>, Receiver<Reply>) {
pub fn start(
world_dir: &str,
world_gen: Arc<dyn WorldGenerator>,
Comment thread
aramperes marked this conversation as resolved.
) -> (Sender<Request>, Receiver<Reply>) {
let (request_tx, request_rx) = crossbeam::channel::unbounded();
let (reply_tx, reply_rx) = crossbeam::channel::unbounded();

Expand All @@ -86,6 +94,7 @@ pub fn start(world_dir: &str) -> (Sender<Request>, Receiver<Reply>) {
sender: reply_tx,
receiver: request_rx,
open_regions: HashMap::new(),
world_generator: world_gen,
};

// Without changing the stack size,
Expand All @@ -107,8 +116,9 @@ fn run(mut worker: ChunkWorker) {
match request {
Request::ShutDown => break,
Request::LoadChunk(pos) => {
let reply = load_chunk(&mut worker, pos);
worker.sender.send(reply).unwrap();
if let Some(reply) = load_chunk(&mut worker, pos) {
worker.sender.send(reply).unwrap();
}
}
}
}
Expand All @@ -117,13 +127,19 @@ fn run(mut worker: ChunkWorker) {
}

/// Attempts to load the chunk at the specified position.
fn load_chunk(worker: &mut ChunkWorker, pos: ChunkPosition) -> Reply {
fn load_chunk(worker: &mut ChunkWorker, pos: ChunkPosition) -> Option<Reply> {
let rpos = RegionPosition::from_chunk(pos);
if !is_region_loaded(worker, pos) {
// Need to load region into memory
let handle = region::load_region(&worker.dir, rpos);
if handle.is_err() {
return (pos, Err(Error::LoadError(handle.err().unwrap())));
// TODO: Create a new region file before generating chunk
schedule_generate_new_chunk(
&Arc::from(worker.sender.clone()),
pos,
&worker.world_generator,
);
return None;
}

let handle = handle.unwrap();
Expand All @@ -145,21 +161,54 @@ fn load_chunk(worker: &mut ChunkWorker, pos: ChunkPosition) -> Reply {
let file = worker.open_regions.get_mut(&rpos).unwrap();
let handle = &mut file.handle;

load_chunk_from_handle(pos, handle)
load_chunk_from_handle(
pos,
handle,
&Arc::from(worker.sender.clone()),
&worker.world_generator,
)
}

fn load_chunk_from_handle(pos: ChunkPosition, handle: &mut RegionHandle) -> Reply {
fn load_chunk_from_handle(
pos: ChunkPosition,
handle: &mut RegionHandle,
sender: &Arc<Sender<Reply>>,
generator: &Arc<dyn WorldGenerator>,
) -> Option<Reply> {
let result = handle.load_chunk(pos);

match result {
Ok(chunk) => (pos, Ok(chunk)),
Ok(chunk) => Some((pos, Ok(chunk))),
Err(e) => match e {
region::Error::ChunkNotExist => (pos, Err(Error::ChunkNotExist)),
err => (pos, Err(Error::LoadError(err))),
region::Error::ChunkNotExist => {
schedule_generate_new_chunk(sender, pos, generator);
None
}
err => Some((pos, Err(Error::LoadError(err)))),
},
}
}

/// Generates a new chunk asynchronously,
/// sending the result to the provided Sender.
fn schedule_generate_new_chunk(
sender: &Arc<Sender<Reply>>,
pos: ChunkPosition,
generator: &Arc<dyn WorldGenerator>,
) {
let sender = sender.clone();
let generator = Arc::clone(generator);
rayon::spawn(move || {
sender.send(generate_new_chunk(pos, &generator)).unwrap();
});
}

/// Generates a new chunk synchronously,
/// returning a Reply to send to a Sender.
fn generate_new_chunk(pos: ChunkPosition, generator: &Arc<dyn WorldGenerator>) -> Reply {
(pos, Ok(generator.generate_chunk(pos)))
}

/// Returns whether the given chunk's region
/// is already loaded.
fn is_region_loaded(worker: &ChunkWorker, chunk_pos: ChunkPosition) -> bool {
Expand Down
6 changes: 3 additions & 3 deletions server/src/joinhandler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use std::sync::Arc;

use shrev::EventChannel;
use specs::{
Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System,
Write, WriteStorage,
Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadExpect, ReadStorage,
System, Write, WriteStorage,
};

use feather_core::level::LevelData;
Expand Down Expand Up @@ -75,7 +75,7 @@ impl<'a> System<'a> for JoinHandlerSystem {
Write<'a, EventChannel<PlayerJoinEvent>>,
Write<'a, EventChannel<EntitySpawnEvent>>,
Write<'a, EventChannel<InventoryUpdateEvent>>,
Read<'a, ChunkWorkerHandle>,
ReadExpect<'a, ChunkWorkerHandle>,
Entities<'a>,
Read<'a, LazyUpdate>,
Read<'a, Arc<Config>>,
Expand Down
13 changes: 12 additions & 1 deletion server/src/main.rs
Loading