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
commit d7b87bd1a8
50 changed files with 669 additions and 145 deletions

View File

@ -2,6 +2,7 @@
members = [
"common",
"client",
"chat-cli",
"server",
"server-cli",
"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
use std::time::Duration;
pub mod error;
pub mod input;
// Library
use specs::Entity as EcsEntity;
// Reexports
pub use specs::Entity as EcsEntity;
pub use crate::{
error::Error,
input::Input,
};
use std::{
time::Duration,
net::SocketAddr,
};
use vek::*;
use threadpool;
// Project
use common::{comp::phys::Vel, state::State, terrain::TerrainChunk};
use specs::Builder;
use common::{
comp,
state::State,
terrain::TerrainChunk,
net::PostBox,
msg::{ClientMsg, ServerMsg},
};
use world::World;
#[derive(Debug)]
pub enum Error {
ServerShutdown,
Other(String),
}
pub struct Input {
// TODO: Use this type to manage client input
pub move_dir: Vec2<f32>,
pub enum Event {
Chat(String),
}
pub struct Client {
thread_pool: threadpool::ThreadPool,
last_ping: f64,
postbox: PostBox<ClientMsg, ServerMsg>,
tick: u64,
state: State,
player: Option<EcsEntity>,
@ -35,25 +45,35 @@ pub struct Client {
impl Client {
/// Create a new `Client`.
pub fn new() -> Self {
Self {
#[allow(dead_code)]
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_name("veloren-worker".into())
.build(),
last_ping: state.get_time(),
postbox,
tick: 0,
state: State::new(),
state,
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).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
// TODO: Get rid of this
@ -70,23 +90,41 @@ impl Client {
}
/// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.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 }
/// 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
#[allow(dead_code)]
pub fn player(&self) -> Option<EcsEntity> {
self.player
}
/// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 {
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
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
// 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
@ -99,13 +137,19 @@ impl Client {
// 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
// (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 {
// TODO: remove this
const PLAYER_VELOCITY: f32 = 100.0;
// 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)
@ -113,12 +157,49 @@ impl Client {
// Finish the tick, pass control back to the frontend (step 6)
self.tick += 1;
Ok(())
Ok(frontend_events)
}
/// Clean up the client after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) {
// Cleanup the local state
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"
[dependencies]
specs = "0.14"
specs = { version = "0.14", features = ["serde"] }
shred = "0.7"
vek = "0.9"
vek = { version = "0.9", features = ["serde"] }
dot_vox = "1.0"
threadpool = "1.7"
mio = "0.6"

View File

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

View File

@ -4,7 +4,7 @@ use vek::*;
// Pos
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Pos(pub Vec3<f32>);
impl Component for Pos {
@ -13,7 +13,7 @@ impl Component for Pos {
// Vel
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Vel(pub Vec3<f32>);
impl Component for Vel {
@ -22,7 +22,7 @@ impl Component for Vel {
// Dir
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Dir(pub Vec3<f32>);
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 comp;
pub mod figure;
pub mod msg;
pub mod state;
pub mod sys;
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
/// # Errors
/// 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 items = VecDeque::new();

View File

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

View File

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

View File

@ -3,7 +3,17 @@ use std::time::Duration;
// Library
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::*;
// 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
pub fn new_test_player(&mut self) -> EcsEntity {
self.ecs_world
@ -82,9 +105,19 @@ impl State {
.build()
}
/// Write a component
pub fn write_component<C: Component>(&mut self, e: EcsEntity, c: C) {
let _ = self.ecs_world.write_storage().insert(e, c);
/// Write a component attributed to a particular entity
pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: 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
@ -92,6 +125,11 @@ impl State {
&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
/// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes {

View File

@ -1,11 +1,6 @@
// Standard
use std::time::Duration;
// Library
use log::info;
// Project
use server::{self, Server};
use server::{Input, Event, Server};
use common::clock::Clock;
const FPS: u64 = 60;
@ -20,12 +15,21 @@ fn main() {
let mut clock = Clock::new();
// Create server
let mut server = Server::new();
let mut server = Server::new()
.expect("Failed to create server instance");
loop {
server.tick(server::Input {}, clock.get_last_delta())
let events = server.tick(Input::default(), clock.get_last_delta())
.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
server.cleanup();

View File

@ -9,3 +9,4 @@ common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" }
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
use std::time::Duration;
#![feature(drain_filter)]
// Internal
use common::state::State;
pub mod client;
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 crate::client::{
Client,
Clients,
};
#[derive(Debug)]
pub enum Error {
Other(String),
}
const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
pub struct Input {
// TODO: Use this type to manage server input
pub enum Event {
ClientConnected {
ecs_entity: EcsEntity,
},
ClientDisconnected {
ecs_entity: EcsEntity,
},
Chat {
ecs_entity: EcsEntity,
msg: String,
},
}
pub struct Server {
state: State,
world: World,
// TODO: Add "meta" state here
postoffice: PostOffice<ServerMsg, ClientMsg>,
clients: Clients,
}
impl Server {
/// Create a new `Server`.
pub fn new() -> Self {
Self {
state: State::new(),
#[allow(dead_code)]
pub fn new() -> Result<Self, Error> {
let mut state = State::new();
state.ecs_world_mut().add_resource(comp::UidAllocator::new());
Ok(Self {
state,
world: World::new(),
}
postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?,
clients: Clients::empty(),
})
}
/// Get a reference to the server's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State { &self.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 }
/// 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.
#[allow(dead_code)]
pub fn world(&self) -> &World { &self.world }
/// Get a mutable reference to the server's world.
#[allow(dead_code)]
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
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
// 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
@ -56,16 +119,126 @@ impl Server {
// 6) Send relevant state updates to all clients
// 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)
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)
Ok(())
Ok(frontend_events)
}
/// Clean up the server after a tick
#[allow(dead_code)]
pub fn cleanup(&mut self) {
// Cleanup the local state
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 {
head: Bone,
chest: Bone,
belt: Bone,
shorts: Bone,
l_hand: Bone,
bl_foot: Bone,
br_foot: Bone,
r_hand: Bone,
l_hand: Bone,
l_foot: Bone,
r_foot: Bone,
back: Bone,
@ -29,10 +29,10 @@ impl CharacterSkeleton {
Self {
head: Bone::default(),
chest: Bone::default(),
belt: Bone::default(),
shorts: Bone::default(),
l_hand: Bone::default(),
br_foot: Bone::default(),
bl_foot: Bone::default(),
r_hand: Bone::default(),
l_hand: Bone::default(),
l_foot: Bone::default(),
r_foot: Bone::default(),
back: Bone::default(),
@ -47,10 +47,10 @@ impl Skeleton for CharacterSkeleton {
[
FigureBoneData::new(self.head.compute_base_matrix()),
FigureBoneData::new(chest_mat),
FigureBoneData::new(self.belt.compute_base_matrix()),
FigureBoneData::new(self.shorts.compute_base_matrix()),
FigureBoneData::new(self.l_hand.compute_base_matrix()),
FigureBoneData::new(self.bl_foot.compute_base_matrix()),
FigureBoneData::new(self.br_foot.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.r_foot.compute_base_matrix()),
FigureBoneData::new(chest_mat * self.back.compute_base_matrix()),

View File

@ -21,30 +21,34 @@ impl Animation for RunAnimation {
time: f64,
) {
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 wavecos_slow = (time as f32 * 6.0 + PI).cos();
let wave_dip = (wave_slow.abs() - 0.5).abs();
skeleton.head.offset = Vec3::unit_z() * 13.0;
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
skeleton.head.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.head.ori = Quaternion::rotation_x(0.0);
skeleton.chest.offset = Vec3::unit_z() * 9.0;
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
skeleton.chest.offset = Vec3::new(0.0, 0.0, 0.0);
skeleton.chest.ori = Quaternion::rotation_x(0.0);
skeleton.belt.offset = Vec3::unit_z() * 7.0;
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2);
//skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
//skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1);
skeleton.shorts.offset = Vec3::unit_z() * 4.0;
skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1);
skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 0.0);
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(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.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,
// When space is pressed, start a session
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
_ => {},

View File

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

View File

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

View File

@ -7,8 +7,8 @@ use vek::*;
use dot_vox;
// Project
use common::figure::Segment;
use client::Client;
use common::{comp::phys::Pos as PosComp, figure::Segment};
// Crate
use crate::{
@ -82,15 +82,15 @@ impl Scene {
test_figure: Figure::new(
renderer,
[
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))),
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))),
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))),
Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))),
Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))),
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
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.
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
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
@ -177,15 +161,7 @@ impl Scene {
&mut self.test_figure.skeleton,
client.state().get_time(),
);
// 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_locals(renderer, FigureLocals::default()).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)
impl SessionState {
/// Create a new `SessionState`
pub fn new(renderer: &mut Renderer) -> Self {
let client = Client::new().with_test_state(); // <--- TODO: Remove this
Self {
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
let client = Client::new(([127, 0, 0, 1], 59003))?.with_test_state(); // <--- TODO: Remove this
Ok(Self {
// Create a scene for this session. The scene handles visible elements of the game world
scene: Scene::new(renderer, &client),
client,
key_state: KeyState::new(),
}
})
}
}

View File

@ -1,8 +1,5 @@
// Standard
use std::{
rc::Rc,
cell::RefCell,
};
use std::rc::Rc;
// Library
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.