Merge branch 'master' into 'master'

client-server impl

See merge request veloren/fresh!10

Former-commit-id: 7389a98fec540371097b183503e47dbbdbf836df
This commit is contained in:
Joshua Barretto
2019-03-04 20:33:01 +00:00
50 changed files with 669 additions and 145 deletions

View File

@ -2,6 +2,7 @@
members = [ members = [
"common", "common",
"client", "client",
"chat-cli",
"server", "server",
"server-cli", "server-cli",
"voxygen", "voxygen",

1
assets/voxygen/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tmp/

3
chat-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

12
chat-cli/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "veloren-chat-cli"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
client = { package = "veloren-client", path = "../client" }
common = { package = "veloren-common", path = "../common" }
log = "0.4"
pretty_env_logger = "0.3"

37
chat-cli/src/main.rs Normal file
View File

@ -0,0 +1,37 @@
use std::time::Duration;
use log::info;
use client::{Input, Client, Event};
use common::clock::Clock;
const FPS: u64 = 60;
fn main() {
// Init logging
pretty_env_logger::init();
info!("Starting chat-cli...");
// Set up an fps clock
let mut clock = Clock::new();
// Create client
let mut client = Client::new(([127, 0, 0, 1], 59003))
.expect("Failed to create client instance");
loop {
let events = client.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick client");
for event in events {
match event {
Event::Chat(msg) => println!("[chat] {}", msg),
}
}
// Clean up the server after a tick
client.cleanup();
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
}
}

17
client/src/error.rs Normal file
View File

@ -0,0 +1,17 @@
use common::net::PostError;
#[derive(Debug)]
pub enum Error {
Network(PostError),
ServerShutdown,
Other(String),
}
impl From<PostError> for Error {
fn from(err: PostError) -> Self {
match err {
PostError::Disconnected => Error::ServerShutdown,
err => Error::Network(err),
}
}
}

14
client/src/input.rs Normal file
View File

@ -0,0 +1,14 @@
use vek::*;
pub struct Input {
// TODO: Use this type to manage client input
pub move_dir: Vec2<f32>,
}
impl Default for Input {
fn default() -> Self {
Input {
move_dir: Vec2::zero(),
}
}
}

View File

@ -1,29 +1,39 @@
// Standard pub mod error;
use std::time::Duration; pub mod input;
// Library // Reexports
use specs::Entity as EcsEntity; pub use specs::Entity as EcsEntity;
pub use crate::{
error::Error,
input::Input,
};
use std::{
time::Duration,
net::SocketAddr,
};
use vek::*; use vek::*;
use threadpool; use threadpool;
use specs::Builder;
// Project use common::{
use common::{comp::phys::Vel, state::State, terrain::TerrainChunk}; comp,
state::State,
terrain::TerrainChunk,
net::PostBox,
msg::{ClientMsg, ServerMsg},
};
use world::World; use world::World;
#[derive(Debug)] pub enum Event {
pub enum Error { Chat(String),
ServerShutdown,
Other(String),
}
pub struct Input {
// TODO: Use this type to manage client input
pub move_dir: Vec2<f32>,
} }
pub struct Client { pub struct Client {
thread_pool: threadpool::ThreadPool, thread_pool: threadpool::ThreadPool,
last_ping: f64,
postbox: PostBox<ClientMsg, ServerMsg>,
tick: u64, tick: u64,
state: State, state: State,
player: Option<EcsEntity>, player: Option<EcsEntity>,
@ -35,25 +45,35 @@ pub struct Client {
impl Client { impl Client {
/// Create a new `Client`. /// Create a new `Client`.
pub fn new() -> Self { #[allow(dead_code)]
Self { pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
let state = State::new();
let mut postbox = PostBox::to_server(addr)?;
postbox.send(ClientMsg::Chat(String::from("Hello, world!")));
Ok(Self {
thread_pool: threadpool::Builder::new() thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into()) .thread_name("veloren-worker".into())
.build(), .build(),
last_ping: state.get_time(),
postbox,
tick: 0, tick: 0,
state: State::new(), state,
player: None, player: None,
// Testing // Testing
world: World::new(), world: World::new(),
chunk: None, chunk: None,
} })
} }
/// Get a reference to the client's worker thread pool. This pool should be used for any /// 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 /// computationally expensive operations that run outside of the main thread (i.e: threads that
/// block on I/O operations are exempt). /// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool } pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
// TODO: Get rid of this // TODO: Get rid of this
@ -70,23 +90,41 @@ impl Client {
} }
/// Get a reference to the client's game state. /// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.state } pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state. /// Get a mutable reference to the client's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { &mut self.state } pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get an entity from its UID, creating it if it does not exists
pub fn get_or_create_entity(&mut self, uid: u64) -> EcsEntity {
self.state.ecs_world_mut().create_entity()
.with(comp::Uid(uid))
.build()
}
/// Get the player entity /// Get the player entity
#[allow(dead_code)]
pub fn player(&self) -> Option<EcsEntity> { pub fn player(&self) -> Option<EcsEntity> {
self.player self.player
} }
/// Get the current tick number. /// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 { pub fn get_tick(&self) -> u64 {
self.tick self.tick
} }
/// Send a chat message to the server
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) -> Result<(), Error> {
Ok(self.postbox.send(ClientMsg::Chat(msg))?)
}
/// Execute a single client tick, handle input and update the game state by the given duration /// 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<(), Error> { #[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are // 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 // 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 // the core developers before making significant changes to this code. Here is the
@ -99,13 +137,19 @@ impl Client {
// 4) Go through the terrain update queue and apply all changes to the terrain // 4) Go through the terrain update queue and apply all changes to the terrain
// 5) Finish the tick, passing control of the main thread back to the frontend // 5) Finish the tick, passing control of the main thread back to the frontend
// (step 1) // Build up a list of events for this frame, to be passed to the frontend
let mut frontend_events = Vec::new();
// Handle new messages from the server
frontend_events.append(&mut self.handle_new_messages()?);
// Step 3
if let Some(p) = self.player { if let Some(p) = self.player {
// TODO: remove this // TODO: remove this
const PLAYER_VELOCITY: f32 = 100.0; const PLAYER_VELOCITY: f32 = 100.0;
// TODO: Set acceleration instead // TODO: Set acceleration instead
self.state.write_component(p, Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); self.state.write_component(p, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY)));
} }
// Tick the client's LocalState (step 3) // Tick the client's LocalState (step 3)
@ -113,12 +157,49 @@ impl Client {
// Finish the tick, pass control back to the frontend (step 6) // Finish the tick, pass control back to the frontend (step 6)
self.tick += 1; self.tick += 1;
Ok(()) Ok(frontend_events)
} }
/// Clean up the client after a tick /// Clean up the client after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// Cleanup the local state // Cleanup the local state
self.state.cleanup(); self.state.cleanup();
} }
/// Handle new server messages
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
// Step 1
let new_msgs = self.postbox.new_messages();
if new_msgs.len() > 0 {
self.last_ping = self.state.get_time();
for msg in new_msgs {
println!("Received message");
match msg {
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
ServerMsg::EntityPhysics { uid, pos, vel, dir } => {
let ecs_entity = self.get_or_create_entity(uid);
self.state.write_component(ecs_entity, pos);
self.state.write_component(ecs_entity, vel);
self.state.write_component(ecs_entity, dir);
},
}
}
} else if let Some(err) = self.postbox.status() {
return Err(err.into());
}
Ok(frontend_events)
}
}
impl Drop for Client {
fn drop(&mut self) {
self.postbox.send(ClientMsg::Disconnect).unwrap();
}
} }

View File

@ -5,9 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc
edition = "2018" edition = "2018"
[dependencies] [dependencies]
specs = "0.14" specs = { version = "0.14", features = ["serde"] }
shred = "0.7" shred = "0.7"
vek = "0.9" vek = { version = "0.9", features = ["serde"] }
dot_vox = "1.0" dot_vox = "1.0"
threadpool = "1.7" threadpool = "1.7"
mio = "0.6" mio = "0.6"

View File

@ -1,9 +1,14 @@
pub mod phys; pub mod phys;
pub mod uid;
// Reexports
pub use uid::{Uid, UidAllocator};
// External
use specs::World as EcsWorld; use specs::World as EcsWorld;
pub fn register_local_components(ecs_world: &mut EcsWorld) { pub fn register_local_components(ecs_world: &mut EcsWorld) {
ecs_world.register::<Uid>();
ecs_world.register::<phys::Pos>(); ecs_world.register::<phys::Pos>();
ecs_world.register::<phys::Vel>(); ecs_world.register::<phys::Vel>();
ecs_world.register::<phys::Dir>(); ecs_world.register::<phys::Dir>();

View File

@ -4,7 +4,7 @@ use vek::*;
// Pos // Pos
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Pos(pub Vec3<f32>); pub struct Pos(pub Vec3<f32>);
impl Component for Pos { impl Component for Pos {
@ -13,7 +13,7 @@ impl Component for Pos {
// Vel // Vel
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Vel(pub Vec3<f32>); pub struct Vel(pub Vec3<f32>);
impl Component for Vel { impl Component for Vel {
@ -22,7 +22,7 @@ impl Component for Vel {
// Dir // Dir
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Dir(pub Vec3<f32>); pub struct Dir(pub Vec3<f32>);
impl Component for Dir { impl Component for Dir {

77
common/src/comp/uid.rs Normal file
View File

@ -0,0 +1,77 @@
use std::{
collections::HashMap,
ops::Range,
u64,
};
use specs::{
saveload::{Marker, MarkerAllocator},
world::EntitiesRes,
Component,
VecStorage,
Entity,
Join,
ReadStorage,
};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Uid(pub u64);
impl Into<u64> for Uid {
fn into(self) -> u64 {
self.0
}
}
impl Component for Uid {
type Storage = VecStorage<Self>;
}
impl Marker for Uid {
type Identifier = u64;
type Allocator = UidAllocator;
fn id(&self) -> u64 { self.0 }
fn update(&mut self, update: Self) {
assert_eq!(self.0, update.0);
}
}
pub struct UidAllocator {
pub(crate) range: Range<u64>,
pub(crate) mapping: HashMap<u64, Entity>,
}
impl UidAllocator {
pub fn new() -> Self {
Self {
range: 0..u64::MAX,
mapping: HashMap::new(),
}
}
}
impl MarkerAllocator<Uid> for UidAllocator {
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
let id = id.unwrap_or_else(|| {
self.range.next().expect("
Id range must be effectively endless.
Somehow, you ran this program for longer than the lifetime of the universe.
It's probably time to stop playing and prepare for your imminent extinction.
")
});
self.mapping.insert(id, entity);
Uid(id)
}
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
self.mapping.get(&id).cloned()
}
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
self.mapping = (&*entities, storage)
.join()
.map(|(e, m)| (m.id(), e))
.collect();
}
}

View File

@ -6,6 +6,7 @@ extern crate serde_derive;
pub mod clock; pub mod clock;
pub mod comp; pub mod comp;
pub mod figure; pub mod figure;
pub mod msg;
pub mod state; pub mod state;
pub mod sys; pub mod sys;
pub mod terrain; pub mod terrain;

5
common/src/msg/client.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ClientMsg {
Chat(String),
Disconnect,
}

6
common/src/msg/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod server;
pub mod client;
// Reexports
pub use server::ServerMsg;
pub use client::ClientMsg;

13
common/src/msg/server.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::comp::phys;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ServerMsg {
Shutdown,
Chat(String),
EntityPhysics {
uid: u64,
pos: phys::Pos,
vel: phys::Vel,
dir: phys::Dir,
},
}

View File

@ -112,7 +112,7 @@ where
/// Non-blocking receiver method returning an iterator over already received and deserialized objects /// Non-blocking receiver method returning an iterator over already received and deserialized objects
/// # Errors /// # Errors
/// If the other side disconnects PostBox won't realize that until you try to send something /// If the other side disconnects PostBox won't realize that until you try to send something
pub fn recv_iter(&mut self) -> impl Iterator<Item = R> { pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item = R> {
let mut events = Events::with_capacity(4096); let mut events = Events::with_capacity(4096);
let mut items = VecDeque::new(); let mut items = VecDeque::new();

View File

@ -79,7 +79,7 @@ where
/// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es /// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es
pub fn new_connections( pub fn new_connections(
&mut self, &mut self,
) -> impl Iterator<Item = PostBox<S, R>> { ) -> impl ExactSizeIterator<Item = PostBox<S, R>> {
let mut events = Events::with_capacity(256); let mut events = Events::with_capacity(256);
let mut conns = VecDeque::new(); let mut conns = VecDeque::new();

View File

@ -25,8 +25,8 @@ fn basic_run() {
scon.send(String::from("foo")).unwrap(); scon.send(String::from("foo")).unwrap();
client.send(String::from("bar")).unwrap(); client.send(String::from("bar")).unwrap();
std::thread::sleep(std::time::Duration::from_millis(10)); std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!("foo", client.recv_iter().next().unwrap()); assert_eq!("foo", client.new_messages().next().unwrap());
assert_eq!("bar", scon.recv_iter().next().unwrap()); assert_eq!("bar", scon.new_messages().next().unwrap());
} }
#[test] #[test]
@ -40,7 +40,7 @@ fn huge_size_header() {
std::thread::sleep(std::time::Duration::from_millis(10)); std::thread::sleep(std::time::Duration::from_millis(10));
client.write(&[0xffu8; 64]).unwrap(); client.write(&[0xffu8; 64]).unwrap();
std::thread::sleep(std::time::Duration::from_millis(10)); std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(scon.recv_iter().next(), None); assert_eq!(scon.new_messages().next(), None);
} }
#[test] #[test]
@ -66,7 +66,7 @@ fn disconnect() {
thread::sleep(Duration::from_millis(10)); thread::sleep(Duration::from_millis(10));
match to_client.recv_iter().next() { match to_client.new_messages().next() {
None => {}, None => {},
_ => panic!("Unexpected message!"), _ => panic!("Unexpected message!"),
} }

View File

@ -3,7 +3,17 @@ use std::time::Duration;
// Library // Library
use shred::{Fetch, FetchMut}; use shred::{Fetch, FetchMut};
use specs::{Builder, Component, DispatcherBuilder, Entity as EcsEntity, World as EcsWorld}; use specs::{
Builder,
Component,
DispatcherBuilder,
Entity as EcsEntity,
World as EcsWorld,
storage::{
Storage as EcsStorage,
MaskedStorage as EcsMaskedStorage,
},
};
use vek::*; use vek::*;
// Crate // Crate
@ -72,6 +82,19 @@ impl State {
} }
} }
/// Register a component with the state's ECS
pub fn with_component<T: Component>(mut self) -> Self
where <T as Component>::Storage: Default
{
self.ecs_world.register::<T>();
self
}
/// Delete an entity from the state's ECS, if it exists
pub fn delete_entity(&mut self, entity: EcsEntity) {
let _ = self.ecs_world.delete_entity(entity);
}
// TODO: Get rid of this // TODO: Get rid of this
pub fn new_test_player(&mut self) -> EcsEntity { pub fn new_test_player(&mut self) -> EcsEntity {
self.ecs_world self.ecs_world
@ -82,9 +105,19 @@ impl State {
.build() .build()
} }
/// Write a component /// Write a component attributed to a particular entity
pub fn write_component<C: Component>(&mut self, e: EcsEntity, c: C) { pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: C) {
let _ = self.ecs_world.write_storage().insert(e, c); let _ = self.ecs_world.write_storage().insert(entity, comp);
}
/// Read a clone of a component attributed to a particular entity
pub fn read_component<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
self.ecs_world.read_storage::<C>().get(entity).cloned()
}
/// Get a read-only reference to the storage of a particular component type
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
self.ecs_world.read_storage::<C>()
} }
/// Get a reference to the internal ECS world /// Get a reference to the internal ECS world
@ -92,6 +125,11 @@ impl State {
&self.ecs_world &self.ecs_world
} }
/// Get a mutable reference to the internal ECS world
pub fn ecs_world_mut(&mut self) -> &mut EcsWorld {
&mut self.ecs_world
}
/// Get a reference to the `Changes` structure of the state. This contains /// Get a reference to the `Changes` structure of the state. This contains
/// information about state that has changed since the last game tick. /// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes { pub fn changes(&self) -> &Changes {

View File

@ -1,11 +1,6 @@
// Standard
use std::time::Duration; use std::time::Duration;
// Library
use log::info; use log::info;
use server::{Input, Event, Server};
// Project
use server::{self, Server};
use common::clock::Clock; use common::clock::Clock;
const FPS: u64 = 60; const FPS: u64 = 60;
@ -20,12 +15,21 @@ fn main() {
let mut clock = Clock::new(); let mut clock = Clock::new();
// Create server // Create server
let mut server = Server::new(); let mut server = Server::new()
.expect("Failed to create server instance");
loop { loop {
server.tick(server::Input {}, clock.get_last_delta()) let events = server.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick server"); .expect("Failed to tick server");
for event in events {
match event {
Event::ClientConnected { ecs_entity } => println!("Client connected!"),
Event::ClientDisconnected { ecs_entity } => println!("Client disconnected!"),
Event::Chat { msg, .. } => println!("[chat] {}", msg),
}
}
// Clean up the server after a tick // Clean up the server after a tick
server.cleanup(); server.cleanup();

View File

@ -9,3 +9,4 @@ common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" } world = { package = "veloren-world", path = "../world" }
specs = "0.14" specs = "0.14"
vek = "0.9"

40
server/src/client.rs Normal file
View File

@ -0,0 +1,40 @@
use specs::Entity as EcsEntity;
use common::{
msg::{ServerMsg, ClientMsg},
net::PostBox,
};
use crate::Error;
pub struct Client {
pub ecs_entity: EcsEntity,
pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64,
}
pub struct Clients {
clients: Vec<Client>,
}
impl Clients {
pub fn empty() -> Self {
Self {
clients: Vec::new(),
}
}
pub fn add(&mut self, client: Client) {
self.clients.push(client);
}
pub fn remove_if<F: FnMut(&mut Client) -> bool>(&mut self, f: F) {
self.clients.drain_filter(f);
}
pub fn notify_all(&mut self, msg: ServerMsg) {
for client in &mut self.clients {
// Consume any errors, deal with them later
let _ = client.postbox.send(msg.clone());
println!("Sending message...");
}
}
}

13
server/src/error.rs Normal file
View File

@ -0,0 +1,13 @@
use common::net::PostError;
#[derive(Debug)]
pub enum Error {
Network(PostError),
Other(String),
}
impl From<PostError> for Error {
fn from(err: PostError) -> Self {
Error::Network(err)
}
}

9
server/src/input.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Input {
// TODO: Use this type to manage server input
}
impl Default for Input {
fn default() -> Self {
Input {}
}
}

View File

@ -1,47 +1,110 @@
// Standard #![feature(drain_filter)]
use std::time::Duration;
// Internal pub mod client;
use common::state::State; pub mod error;
pub mod input;
// Reexports
pub use crate::{
error::Error,
input::Input,
};
use std::{
time::Duration,
net::SocketAddr,
};
use specs::{
Entity as EcsEntity,
world::EntityBuilder as EcsEntityBuilder,
Builder,
join::Join,
saveload::MarkedBuilder,
};
use vek::*;
use common::{
comp,
state::State,
net::PostOffice,
msg::{ServerMsg, ClientMsg},
};
use world::World; use world::World;
use crate::client::{
Client,
Clients,
};
#[derive(Debug)] const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
pub enum Error {
Other(String),
}
pub struct Input { pub enum Event {
// TODO: Use this type to manage server input ClientConnected {
ecs_entity: EcsEntity,
},
ClientDisconnected {
ecs_entity: EcsEntity,
},
Chat {
ecs_entity: EcsEntity,
msg: String,
},
} }
pub struct Server { pub struct Server {
state: State, state: State,
world: World, world: World,
// TODO: Add "meta" state here postoffice: PostOffice<ServerMsg, ClientMsg>,
clients: Clients,
} }
impl Server { impl Server {
/// Create a new `Server`. /// Create a new `Server`.
pub fn new() -> Self { #[allow(dead_code)]
Self { pub fn new() -> Result<Self, Error> {
state: State::new(), let mut state = State::new();
state.ecs_world_mut().add_resource(comp::UidAllocator::new());
Ok(Self {
state,
world: World::new(), world: World::new(),
}
postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?,
clients: Clients::empty(),
})
} }
/// Get a reference to the server's game state. /// Get a reference to the server's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.state } pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the server's game state. /// Get a mutable reference to the server's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { &mut self.state } pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Build a new entity with a generated UID
pub fn build_entity(&mut self) -> EcsEntityBuilder {
self.state.ecs_world_mut().create_entity()
.marked::<comp::Uid>()
}
/// Build a new player with a generated UID
pub fn build_player(&mut self) -> EcsEntityBuilder {
self.build_entity()
.with(comp::phys::Pos(Vec3::zero()))
.with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y()))
}
/// Get a reference to the server's world. /// Get a reference to the server's world.
#[allow(dead_code)]
pub fn world(&self) -> &World { &self.world } pub fn world(&self) -> &World { &self.world }
/// Get a mutable reference to the server's world. /// Get a mutable reference to the server's world.
#[allow(dead_code)]
pub fn world_mut(&mut self) -> &mut World { &mut self.world } pub fn world_mut(&mut self) -> &mut World { &mut self.world }
/// Execute a single server tick, handle input and update the game state by the given duration /// Execute a single server tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> { #[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most server-side things are // This tick function is the centre of the Veloren universe. Most server-side things are
// managed from here, and as such it's important that it stays organised. Please consult // 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 // the core developers before making significant changes to this code. Here is the
@ -56,16 +119,126 @@ impl Server {
// 6) Send relevant state updates to all clients // 6) Send relevant state updates to all clients
// 7) Finish the tick, passing control of the main thread back to the frontend // 7) Finish the tick, passing control of the main thread back to the frontend
// Build up a list of events for this frame, to be passed to the frontend
let mut frontend_events = Vec::new();
// If networking has problems, handle them
if let Some(err) = self.postoffice.status() {
return Err(err.into());
}
// Handle new client connections (step 2)
frontend_events.append(&mut self.handle_new_connections()?);
// Handle new messages from clients
frontend_events.append(&mut self.handle_new_messages()?);
// Tick the client's LocalState (step 3) // Tick the client's LocalState (step 3)
self.state.tick(dt); self.state.tick(dt);
// Synchronise clients with the new state of the world
self.sync_clients();
// Finish the tick, pass control back to the frontend (step 6) // Finish the tick, pass control back to the frontend (step 6)
Ok(()) Ok(frontend_events)
} }
/// Clean up the server after a tick /// Clean up the server after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
// Cleanup the local state // Cleanup the local state
self.state.cleanup(); self.state.cleanup();
} }
/// Handle new client connections
fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
for postbox in self.postoffice.new_connections() {
let ecs_entity = self.build_player()
.build();
frontend_events.push(Event::ClientConnected {
ecs_entity,
});
self.clients.add(Client {
ecs_entity,
postbox,
last_ping: self.state.get_time(),
});
}
Ok(frontend_events)
}
/// Handle new client messages
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
let state = &mut self.state;
let mut new_chat_msgs = Vec::new();
self.clients.remove_if(|client| {
let mut disconnected = false;
let new_msgs = client.postbox.new_messages();
// Update client ping
if new_msgs.len() > 0 {
client.last_ping = state.get_time();
// Process incoming messages
for msg in new_msgs {
match msg {
ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)),
ClientMsg::Disconnect => disconnected = true,
}
}
} else if
state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
client.postbox.status().is_some() // Postbox eror
{
disconnected = true;
}
if disconnected {
state.delete_entity(client.ecs_entity);
frontend_events.push(Event::ClientDisconnected {
ecs_entity: client.ecs_entity,
});
true
} else {
false
}
});
// Handle new chat messages
for (ecs_entity, msg) in new_chat_msgs {
self.clients.notify_all(ServerMsg::Chat(msg.clone()));
frontend_events.push(Event::Chat {
ecs_entity,
msg,
});
}
Ok(frontend_events)
}
/// Sync client states with the most up to date information
fn sync_clients(&mut self) {
for (&uid, &pos, &vel, &dir) in (
&self.state.ecs_world().read_storage::<comp::Uid>(),
&self.state.ecs_world().read_storage::<comp::phys::Pos>(),
&self.state.ecs_world().read_storage::<comp::phys::Vel>(),
&self.state.ecs_world().read_storage::<comp::phys::Dir>(),
).join() {
self.clients.notify_all(ServerMsg::EntityPhysics {
uid: uid.into(),
pos,
vel,
dir,
});
}
}
} }

View File

@ -15,10 +15,10 @@ use super::{
pub struct CharacterSkeleton { pub struct CharacterSkeleton {
head: Bone, head: Bone,
chest: Bone, chest: Bone,
belt: Bone, bl_foot: Bone,
shorts: Bone, br_foot: Bone,
l_hand: Bone,
r_hand: Bone, r_hand: Bone,
l_hand: Bone,
l_foot: Bone, l_foot: Bone,
r_foot: Bone, r_foot: Bone,
back: Bone, back: Bone,
@ -29,10 +29,10 @@ impl CharacterSkeleton {
Self { Self {
head: Bone::default(), head: Bone::default(),
chest: Bone::default(), chest: Bone::default(),
belt: Bone::default(), br_foot: Bone::default(),
shorts: Bone::default(), bl_foot: Bone::default(),
l_hand: Bone::default(),
r_hand: Bone::default(), r_hand: Bone::default(),
l_hand: Bone::default(),
l_foot: Bone::default(), l_foot: Bone::default(),
r_foot: Bone::default(), r_foot: Bone::default(),
back: Bone::default(), back: Bone::default(),
@ -47,10 +47,10 @@ impl Skeleton for CharacterSkeleton {
[ [
FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.head.compute_base_matrix()),
FigureBoneData::new(chest_mat), FigureBoneData::new(chest_mat),
FigureBoneData::new(self.belt.compute_base_matrix()), FigureBoneData::new(self.bl_foot.compute_base_matrix()),
FigureBoneData::new(self.shorts.compute_base_matrix()), FigureBoneData::new(self.br_foot.compute_base_matrix()),
FigureBoneData::new(self.l_hand.compute_base_matrix()),
FigureBoneData::new(self.r_hand.compute_base_matrix()), FigureBoneData::new(self.r_hand.compute_base_matrix()),
FigureBoneData::new(self.l_hand.compute_base_matrix()),
FigureBoneData::new(self.l_foot.compute_base_matrix()), FigureBoneData::new(self.l_foot.compute_base_matrix()),
FigureBoneData::new(self.r_foot.compute_base_matrix()), FigureBoneData::new(self.r_foot.compute_base_matrix()),
FigureBoneData::new(chest_mat * self.back.compute_base_matrix()), FigureBoneData::new(chest_mat * self.back.compute_base_matrix()),

View File

@ -21,30 +21,34 @@ impl Animation for RunAnimation {
time: f64, time: f64,
) { ) {
let wave = (time as f32 * 12.0).sin(); let wave = (time as f32 * 12.0).sin();
let wavecos = (time as f32 * 12.0).cos();
let wave_slow = (time as f32 * 6.0 + PI).sin(); let wave_slow = (time as f32 * 6.0 + PI).sin();
let wavecos_slow = (time as f32 * 6.0 + PI).cos();
let wave_dip = (wave_slow.abs() - 0.5).abs(); let wave_dip = (wave_slow.abs() - 0.5).abs();
skeleton.head.offset = Vec3::unit_z() * 13.0; skeleton.head.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); skeleton.head.ori = Quaternion::rotation_x(0.0);
skeleton.chest.offset = Vec3::unit_z() * 9.0; skeleton.chest.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); skeleton.chest.ori = Quaternion::rotation_x(0.0);
skeleton.belt.offset = Vec3::unit_z() * 7.0; //skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2); //skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1);
skeleton.shorts.offset = Vec3::unit_z() * 4.0; skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1); skeleton.bl_foot.ori = Quaternion::rotation_x(wave_slow * 2.0);
//skeleton.bl_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
//skeleton.bl_foot.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0); //skeleton.r_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0); //skeleton.r_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
skeleton.l_hand.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.l_hand.ori = Quaternion::rotation_x(wave_slow * 2.0);
//skeleton.l_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
//skeleton.l_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0);
skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0);
skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0);
skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0);
skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0);
skeleton.back.ori = Quaternion::rotation_y(2.5);
} }
} }

View File

@ -46,7 +46,7 @@ impl PlayState for TitleState {
Event::Close => return PlayStateResult::Shutdown, Event::Close => return PlayStateResult::Shutdown,
// When space is pressed, start a session // When space is pressed, start a session
Event::Char(' ') => return PlayStateResult::Push( Event::Char(' ') => return PlayStateResult::Push(
Box::new(SessionState::new(global_state.window.renderer_mut())), Box::new(SessionState::new(global_state.window.renderer_mut()).unwrap()), // TODO: Handle this error
), ),
// Ignore all other events // Ignore all other events
_ => {}, _ => {},

View File

@ -11,15 +11,12 @@ use gfx::{
}; };
// Local // Local
use super::{ use super::super::{
Globals, Pipeline,
super::{ TgtColorFmt,
Pipeline, TgtDepthFmt,
TgtColorFmt, Mesh,
TgtDepthFmt, Quad,
Mesh,
Quad,
},
}; };
gfx_defines! { gfx_defines! {

View File

@ -4,7 +4,7 @@ use std::marker::PhantomData;
// Library // Library
use gfx::{ use gfx::{
self, self,
traits::{Factory, FactoryExt}, traits::Factory,
}; };
use image::{ use image::{
DynamicImage, DynamicImage,
@ -14,7 +14,6 @@ use image::{
// Local // Local
use super::{ use super::{
RenderError, RenderError,
mesh::Mesh,
Pipeline, Pipeline,
gfx_backend, gfx_backend,
}; };

View File

@ -7,8 +7,8 @@ use vek::*;
use dot_vox; use dot_vox;
// Project // Project
use common::figure::Segment;
use client::Client; use client::Client;
use common::{comp::phys::Pos as PosComp, figure::Segment};
// Crate // Crate
use crate::{ use crate::{
@ -82,15 +82,15 @@ impl Scene {
test_figure: Figure::new( test_figure: Figure::new(
renderer, renderer,
[ [
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))), Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))),
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))), Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), None,
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), None,
Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))), None,
None, None,
None, None,
None, None,
@ -138,22 +138,6 @@ impl Scene {
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick. /// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
// Get player position
let player_pos = match client.player() {
Some(entity) => {
client
.state()
.ecs_world()
.read_storage::<PosComp>()
.get(entity)
.expect("There was no position component on the player entity!")
.0
}
None => Vec3::default(),
};
// Alter camera position to match player
self.camera.set_focus_pos(player_pos);
// Compute camera matrices // Compute camera matrices
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
@ -177,15 +161,7 @@ impl Scene {
&mut self.test_figure.skeleton, &mut self.test_figure.skeleton,
client.state().get_time(), client.state().get_time(),
); );
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
// Calculate entity model matrix
let model_mat = Mat4::<f32>::translation_3d(player_pos);
//* Mat4::rotation_z(PI - entity.look_dir().x)
//* Mat4::rotation_x(entity.look_dir().y);
self.test_figure
.update_locals(renderer, FigureLocals::new(model_mat))
.unwrap();
self.test_figure.update_skeleton(renderer).unwrap(); self.test_figure.update_skeleton(renderer).unwrap();
} }

View File

@ -34,14 +34,14 @@ pub struct SessionState {
/// Represents an active game session (i.e: one that is being played) /// Represents an active game session (i.e: one that is being played)
impl SessionState { impl SessionState {
/// Create a new `SessionState` /// Create a new `SessionState`
pub fn new(renderer: &mut Renderer) -> Self { pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
let client = Client::new().with_test_state(); // <--- TODO: Remove this let client = Client::new(([127, 0, 0, 1], 59003))?.with_test_state(); // <--- TODO: Remove this
Self { Ok(Self {
// Create a scene for this session. The scene handles visible elements of the game world // Create a scene for this session. The scene handles visible elements of the game world
scene: Scene::new(renderer, &client), scene: Scene::new(renderer, &client),
client, client,
key_state: KeyState::new(), key_state: KeyState::new(),
} })
} }
} }

View File

@ -1,8 +1,5 @@
// Standard // Standard
use std::{ use std::rc::Rc;
rc::Rc,
cell::RefCell,
};
// Library // Library
use image::DynamicImage; use image::DynamicImage;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.