Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Superflat layer generation (#24) #108

Merged
merged 17 commits into from
Sep 7, 2019
Merged
Show file tree
Hide file tree
Changes from 13 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Implements level.dat file loading.

use feather_items::Item;
use serde::Deserialize;
use std::collections::HashMap;
use std::io::Read;

/// Root level tag
Expand Down Expand Up @@ -66,6 +69,11 @@ pub struct LevelData {

#[serde(rename = "Version")]
pub version: LevelVersion,

#[serde(rename = "generatorName")]
pub generator_name: String,
#[serde(rename = "generatorOptions")]
pub generator_options: Option<SuperflatGeneratorOptions>,
}

/// Represents level version data.
Expand All @@ -77,6 +85,74 @@ pub struct LevelVersion {
name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuperflatGeneratorOptions {
pub structures: HashMap<String, nbt::Value>,
pub layers: Vec<SuperflatLayer>,
pub biome: String,
}

impl Default for SuperflatGeneratorOptions {
fn default() -> Self {
let mut default_structures = HashMap::new();
default_structures.insert(
String::from("village"),
nbt::Value::Compound(HashMap::new()),
);

// Default superflat world layers
Self {
structures: default_structures,
layers: vec![
SuperflatLayer {
block: Item::Bedrock.identifier().to_string(),
height: 1,
},
SuperflatLayer {
block: Item::Dirt.identifier().to_string(),
height: 2,
},
SuperflatLayer {
block: Item::GrassBlock.identifier().to_string(),
height: 1,
},
],
biome: String::from("minecraft:plains"),
}
}
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct SuperflatLayer {
pub block: String,
aramperes marked this conversation as resolved.
Show resolved Hide resolved
pub height: u8,
}

/// The type of world generator for a level.
#[derive(Debug, PartialEq)]
pub enum LevelGeneratorType {
Default,
Flat,
LargeBiomes,
Amplified,
Buffet,
Debug,
}

impl LevelData {
pub fn generator_type(&self) -> LevelGeneratorType {
match self.generator_name.to_lowercase().as_str() {
"default" => LevelGeneratorType::Default,
"flat" => LevelGeneratorType::Flat,
"largeBiomes" => LevelGeneratorType::LargeBiomes,
"amplified" => LevelGeneratorType::Amplified,
"buffet" => LevelGeneratorType::Buffet,
"debug_all_block_states" => LevelGeneratorType::Debug,
_ => LevelGeneratorType::Default,
}
}
}

/// Deserializes a level.dat file from the given reader.
pub fn deserialize_level_file<R: Read>(reader: R) -> Result<LevelData, nbt::Error> {
match nbt::from_gzip_reader::<_, Root>(reader) {
Expand Down Expand Up @@ -113,5 +189,7 @@ mod tests {
assert_eq!(level.spawn_z, 0);
assert!(level.thundering);
assert_eq!(level.thunder_time, 5252);
assert_eq!(level.generator_name, "default");
assert!(level.generator_options.is_none());
}
}
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
51 changes: 41 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>,
aramperes marked this conversation as resolved.
Show resolved Hide 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,13 @@ 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())));
return Some((pos, Err(Error::LoadError(handle.err().unwrap()))));
}

let handle = handle.unwrap();
Expand All @@ -145,21 +155,42 @@ 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 => {
let sender = Arc::clone(sender);
aramperes marked this conversation as resolved.
Show resolved Hide resolved
let generator = Arc::clone(generator);
rayon::spawn(move || {
sender.send(generate_new_chunk(pos, &generator)).unwrap();
});
None
}
err => Some((pos, Err(Error::LoadError(err)))),
},
}
}

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
23 changes: 22 additions & 1 deletion server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern crate feather_codegen;
extern crate bitflags;
#[macro_use]
extern crate feather_core;
extern crate feather_blocks;

extern crate nalgebra_glm as glm;

Expand All @@ -44,9 +45,10 @@ use crate::network::send_packet_to_player;
use crate::player::PlayerDisconnectEvent;
use crate::systems::{BROADCASTER, ITEM_SPAWN, JOIN_HANDLER, NETWORK, SPAWNER};
use crate::util::Util;
use crate::worldgen::{EmptyWorldGenerator, SuperflatWorldGenerator, WorldGenerator};
use backtrace::Backtrace;
use feather_core::level;
use feather_core::level::LevelData;
use feather_core::level::{LevelData, LevelGeneratorType};
use shrev::EventChannel;
use std::fs::File;
use std::io::{Read, Write};
Expand All @@ -71,6 +73,7 @@ pub mod systems;
#[cfg(test)]
pub mod testframework;
pub mod time;
pub mod worldgen;

pub const TPS: u64 = 20;
pub const PROTOCOL_VERSION: u32 = 404;
Expand Down Expand Up @@ -210,13 +213,31 @@ fn init_world<'a, 'b>(
ioman: io::NetworkIoManager,
level: LevelData,
) -> (World, Dispatcher<'a, 'b>) {
{
let level = level.clone();
debug!("Level type: {}", level.generator_name);
if level.generator_type() == LevelGeneratorType::Flat {
debug!(
"Superflat generator options: {:?}",
level.generator_options.unwrap_or_default()
);
}
}
let mut world = World::new();
time::init_time(&mut world, &level);
world.insert(config);
world.insert(player_count);
world.insert(ioman);
world.insert(TickCount::default());

let generator: Arc<dyn WorldGenerator> = match level.generator_type() {
LevelGeneratorType::Flat => Arc::new(SuperflatWorldGenerator {
options: level.clone().generator_options.unwrap_or_default(),
}),
_ => Arc::new(EmptyWorldGenerator {}),
};
world.insert(level);
world.insert(generator);

let mut dispatcher = DispatcherBuilder::new();

Expand Down
Loading