diff --git a/Cargo.toml b/Cargo.toml index ddf12b4e37..f15ce50095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ members = [ "common", "client", "server", + "server-cli", + "voxygen", + "world", ] [profile.dev] diff --git a/client/Cargo.toml b/client/Cargo.toml index 97b351b96f..ac82be29e1 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Joshua Barretto "] edition = "2018" [dependencies] -common = { path = "../common" } +common = { package = "veloren-common", path = "../common" } +world = { package = "veloren-world", path = "../world" } specs = "0.14" +vek = "0.9" +threadpool = "1.7" diff --git a/client/src/lib.rs b/client/src/lib.rs index 627d7cc1ac..5a90e14ff8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,10 +1,20 @@ // Standard use std::time::Duration; -// Internal -use common::state::State; +// Library +use specs::Entity as EcsEntity; +use vek::*; +use threadpool; -pub enum ClientErr { +// Project +use common::{ + state::State, + terrain::TerrainChunk, +}; +use world::World; + +#[derive(Debug)] +pub enum Error { ServerShutdown, Other(String), } @@ -14,20 +24,65 @@ pub struct Input { } pub struct Client { - state: State, + thread_pool: threadpool::ThreadPool, - // TODO: Add "meta" state here + tick: u64, + state: State, + player: Option, + + // Testing + world: World, + pub chunk: Option, } impl Client { + /// Create a new `Client`. pub fn new() -> Self { Self { + thread_pool: threadpool::Builder::new() + .thread_name("veloren-worker".into()) + .build(), + + tick: 0, state: State::new(), + player: None, + + // Testing + world: World::new(), + chunk: None, } } + /// Get a reference to the client's worker thread pool. This pool should be used for any + /// computationally expensive operations that run outside of the main thread (i.e: threads that + /// block on I/O operations are exempt). + pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool } + + // TODO: Get rid of this + pub fn with_test_state(mut self) -> Self { + self.chunk = Some(self.world.generate_chunk(Vec3::zero())); + self + } + + // TODO: Get rid of this + pub fn load_chunk(&mut self, pos: Vec3) { + self.state.terrain_mut().insert(pos, self.world.generate_chunk(pos)); + self.state.changes_mut().new_chunks.push(pos); + } + + /// Get a reference to the client's game state. + pub fn state(&self) -> &State { &self.state } + + /// Get a mutable reference to the client's game state. + pub fn state_mut(&mut self) -> &mut State { &mut self.state } + + /// Get the current tick number. + pub fn get_tick(&self) -> u64 { + self.tick + } + /// Execute a single client tick, handle input and update the game state by the given duration - pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> { + pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> { // This tick function is the centre of the Veloren universe. Most client-side things are // managed from here, and as such it's important that it stays organised. Please consult // the core developers before making significant changes to this code. Here is the @@ -44,6 +99,13 @@ impl Client { self.state.tick(dt); // Finish the tick, pass control back to the frontend (step 6) + self.tick += 1; Ok(()) } + + /// Clean up the client after a tick + pub fn cleanup(&mut self) { + // Cleanup the local state + self.state.cleanup(); + } } diff --git a/common/Cargo.toml b/common/Cargo.toml index 65a6d13a8d..0975cf220f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,9 +1,12 @@ [package] -name = "common" +name = "veloren-common" version = "0.1.0" authors = ["Joshua Barretto "] edition = "2018" [dependencies] specs = "0.14" +shred = "0.7" vek = "0.9" +dot_vox = "1.0" +threadpool = "1.7" diff --git a/common/src/clock.rs b/common/src/clock.rs new file mode 100644 index 0000000000..437fe77c57 --- /dev/null +++ b/common/src/clock.rs @@ -0,0 +1,50 @@ +// Standard +use std::{ + thread, + time::{Duration, SystemTime}, +}; + +const CLOCK_SMOOTHING: f64 = 0.9; + +pub struct Clock { + last_sys_time: SystemTime, + last_delta: Option, + running_tps_average: f64, +} + +impl Clock { + pub fn new() -> Self { + Self { + last_sys_time: SystemTime::now(), + last_delta: None, + running_tps_average: 0.0, + } + } + + pub fn get_tps(&self) -> f64 { self.running_tps_average } + + pub fn get_last_delta(&self) -> Duration { self.last_delta.unwrap_or(Duration::new(0, 0)) } + + pub fn get_avg_delta(&self) -> Duration { Duration::from_float_secs(self.running_tps_average) } + + pub fn tick(&mut self, tgt: Duration) { + let delta = SystemTime::now() + .duration_since(self.last_sys_time) + .expect("Time went backwards!"); + + // Attempt to sleep to fill the gap + if let Some(sleep_dur) = tgt.checked_sub(delta) { + thread::sleep(sleep_dur); + } + + let delta = SystemTime::now() + .duration_since(self.last_sys_time) + .expect("Time went backwards!"); + + self.last_sys_time = SystemTime::now(); + self.last_delta = Some(delta); + self.running_tps_average = + CLOCK_SMOOTHING * self.running_tps_average + + (1.0 - CLOCK_SMOOTHING) * delta.as_float_secs(); + } +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index cc7bec16b9..c7bb5a311f 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,7 +1,7 @@ pub mod phys; // External -use specs::{World as EcsWorld, Builder}; +use specs::World as EcsWorld; pub fn register_local_components(ecs_world: &mut EcsWorld) { ecs_world.register::(); diff --git a/common/src/figure/cell.rs b/common/src/figure/cell.rs new file mode 100644 index 0000000000..2d445dcf3d --- /dev/null +++ b/common/src/figure/cell.rs @@ -0,0 +1,38 @@ +// Library +use vek::*; + +// Crate +use crate::vol::Vox; + +/// A type representing a single voxel in a figure +#[derive(Copy, Clone, Debug)] +pub enum Cell { + Filled([u8; 3]), + Empty, +} + +impl Cell { + pub fn new(rgb: Rgb) -> Self { + Cell::Filled(rgb.into_array()) + } + + pub fn get_color(&self) -> Option> { + match self { + Cell::Filled(col) => Some(Rgb::from(*col)), + Cell::Empty => None, + } + } +} + +impl Vox for Cell { + fn empty() -> Self { + Cell::Empty + } + + fn is_empty(&self) -> bool { + match self { + Cell::Filled(_) => false, + Cell::Empty => true, + } + } +} diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs new file mode 100644 index 0000000000..4bde150427 --- /dev/null +++ b/common/src/figure/mod.rs @@ -0,0 +1,55 @@ +pub mod cell; + +// Library +use vek::*; +use dot_vox::DotVoxData; + +// Crate +use crate::{ + vol::{Vox, WriteVol}, + volumes::dyna::Dyna, +}; + +// Local +use self::cell::Cell; + +/// A type representing a volume that may be part of an animated figure. +/// +/// Figures are used to represent things like characters, NPCs, mobs, etc. +pub type Segment = Dyna; + +impl From for Segment { + fn from(dot_vox_data: DotVoxData) -> Self { + if let Some(model) = dot_vox_data.models.get(0) { + let palette = dot_vox_data + .palette + .iter() + .map(|col| Rgba::from(col.to_ne_bytes()).into()) + .collect::>(); + + let mut segment = Segment::filled( + Vec3::new( + model.size.x, + model.size.y, + model.size.z, + ), + Cell::empty(), + (), + ); + + for voxel in &model.voxels { + if let Some(&color) = palette.get(voxel.i as usize) { + // TODO: Maybe don't ignore this error? + let _ = segment.set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| e as i32), + Cell::new(color), + ); + } + } + + segment + } else { + Segment::filled(Vec3::zero(), Cell::empty(), ()) + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index e1ec001dc1..4d33fd7988 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,7 +1,10 @@ -#![feature(euclidean_division)] +#![feature(euclidean_division, duration_float)] +pub mod clock; pub mod comp; +pub mod figure; pub mod state; pub mod terrain; +pub mod util; pub mod volumes; pub mod vol; diff --git a/common/src/state.rs b/common/src/state.rs index 5842498ec3..a4d974ee57 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -1,39 +1,119 @@ // Standard use std::time::Duration; -// External +// Library use specs::World as EcsWorld; +use shred::{Fetch, FetchMut}; +use vek::*; // Crate use crate::{ comp, terrain::TerrainMap, - vol::VolSize, }; -// A type used to represent game state stored on both the client and the server. This includes -// things like entity components, terrain data, and global state like weather, time of day, etc. +/// How much faster should an in-game day be compared to a real day? +// TODO: Don't hard-code this +const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0; + +/// A resource to store the time of day +struct TimeOfDay(f64); + +/// A resource to store the tick (i.e: physics) time +struct Time(f64); + +pub struct Changes { + pub new_chunks: Vec>, + pub changed_chunks: Vec>, + pub removed_chunks: Vec>, +} + +impl Changes { + pub fn default() -> Self { + Self { + new_chunks: vec![], + changed_chunks: vec![], + removed_chunks: vec![], + } + } + + pub fn cleanup(&mut self) { + self.new_chunks.clear(); + self.changed_chunks.clear(); + self.removed_chunks.clear(); + } +} + +/// A type used to represent game state stored on both the client and the server. This includes +/// things like entity components, terrain data, and global state like weather, time of day, etc. pub struct State { ecs_world: EcsWorld, - terrain_map: TerrainMap, - time: f64, + changes: Changes, } impl State { + /// Create a new `State`. pub fn new() -> Self { let mut ecs_world = EcsWorld::new(); + // Register resources used by the ECS + ecs_world.add_resource(TimeOfDay(0.0)); + ecs_world.add_resource(Time(0.0)); + ecs_world.add_resource(TerrainMap::new()); + + // Register common components with the state comp::register_local_components(&mut ecs_world); Self { ecs_world, - terrain_map: TerrainMap::new(), - time: 0.0, + changes: Changes::default(), } } - // Execute a single tick, simulating the game state by the given duration + /// Get a reference to the internal ECS world + pub fn ecs_world(&self) -> &EcsWorld { &self.ecs_world } + + /// Get a reference to the `Changes` structure of the state. This contains + /// information about state that has changed since the last game tick. + pub fn changes(&self) -> &Changes { &self.changes } + + // TODO: Get rid of this since it shouldn't be needed + pub fn changes_mut(&mut self) -> &mut Changes { &mut self.changes } + + /// Get the current in-game time of day. + /// + /// Note that this should not be used for physics, animations or other such localised timings. + pub fn get_time_of_day(&self) -> f64 { + self.ecs_world.read_resource::().0 + } + + /// Get the current in-game time. + /// + /// Note that this does not correspond to the time of day. + pub fn get_time(&self) -> f64 { + self.ecs_world.read_resource::