mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'master' into 'master'
Added multi-character rendering, integrated Sphynx, network overhaul See merge request veloren/veloren!19 Former-commit-id: df34b5df61bea22193b465e883d563a56632174c
This commit is contained in:
commit
07698f46ba
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "veloren-chat-cli"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
use std::time::Duration;
|
||||
use log::info;
|
||||
use client::{Input, Client, Event};
|
||||
use common::clock::Clock;
|
||||
use common::{
|
||||
comp,
|
||||
clock::Clock,
|
||||
};
|
||||
|
||||
const FPS: u64 = 60;
|
||||
|
||||
@ -15,7 +18,7 @@ fn main() {
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Create client
|
||||
let mut client = Client::new(([127, 0, 0, 1], 59003))
|
||||
let mut client = Client::new(([127, 0, 0, 1], 59003), comp::Player::new("test".to_string()), None, 300)
|
||||
.expect("Failed to create client instance");
|
||||
|
||||
client.send_chat("Hello!".to_string());
|
||||
|
@ -1,12 +1,11 @@
|
||||
[package]
|
||||
name = "veloren-client"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
world = { package = "veloren-world", path = "../world" }
|
||||
|
||||
specs = "0.14"
|
||||
vek = "0.9"
|
||||
|
@ -3,6 +3,7 @@ use common::net::PostError;
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Network(PostError),
|
||||
ServerWentMad,
|
||||
ServerTimeout,
|
||||
ServerShutdown,
|
||||
Other(String),
|
||||
@ -10,9 +11,6 @@ pub enum Error {
|
||||
|
||||
impl From<PostError> for Error {
|
||||
fn from(err: PostError) -> Self {
|
||||
match err {
|
||||
PostError::Disconnect => Error::ServerShutdown,
|
||||
err => Error::Network(err),
|
||||
}
|
||||
Error::Network(err)
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,10 @@ pub use crate::{
|
||||
use std::{
|
||||
time::Duration,
|
||||
net::SocketAddr,
|
||||
collections::HashSet,
|
||||
};
|
||||
use vek::*;
|
||||
use threadpool;
|
||||
use threadpool::ThreadPool;
|
||||
use specs::Builder;
|
||||
use common::{
|
||||
comp,
|
||||
@ -24,7 +25,6 @@ use common::{
|
||||
net::PostBox,
|
||||
msg::{ClientMsg, ServerMsg},
|
||||
};
|
||||
use world::World;
|
||||
|
||||
const SERVER_TIMEOUT: f64 = 5.0; // Seconds
|
||||
|
||||
@ -33,7 +33,7 @@ pub enum Event {
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
thread_pool: threadpool::ThreadPool,
|
||||
thread_pool: ThreadPool,
|
||||
|
||||
last_ping: f64,
|
||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
@ -41,19 +41,38 @@ pub struct Client {
|
||||
tick: u64,
|
||||
state: State,
|
||||
player: Option<EcsEntity>,
|
||||
view_distance: u64,
|
||||
|
||||
// Testing
|
||||
world: World,
|
||||
pub chunk: Option<TerrainChunk>,
|
||||
pending_chunks: HashSet<Vec3<i32>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new `Client`.
|
||||
#[allow(dead_code)]
|
||||
pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
|
||||
let state = State::new();
|
||||
pub fn new<A: Into<SocketAddr>>(
|
||||
addr: A,
|
||||
player: comp::Player,
|
||||
character: Option<comp::Character>,
|
||||
view_distance: u64,
|
||||
) -> Result<Self, Error> {
|
||||
|
||||
let mut postbox = PostBox::to_server(addr)?;
|
||||
let mut postbox = PostBox::to(addr)?;
|
||||
|
||||
// Send connection request
|
||||
postbox.send_message(ClientMsg::Connect {
|
||||
player,
|
||||
character,
|
||||
});
|
||||
|
||||
// Wait for handshake from server
|
||||
let (state, player) = match postbox.next_message() {
|
||||
Some(ServerMsg::Handshake { ecs_state, player_entity }) => {
|
||||
let mut state = State::from_state_package(ecs_state);
|
||||
let player_entity = state.ecs().entity_from_uid(player_entity);
|
||||
(state, player_entity)
|
||||
},
|
||||
_ => return Err(Error::ServerWentMad),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
thread_pool: threadpool::Builder::new()
|
||||
@ -65,11 +84,10 @@ impl Client {
|
||||
|
||||
tick: 0,
|
||||
state,
|
||||
player: None,
|
||||
player,
|
||||
view_distance,
|
||||
|
||||
// Testing
|
||||
world: World::new(),
|
||||
chunk: None,
|
||||
pending_chunks: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -79,18 +97,6 @@ impl Client {
|
||||
#[allow(dead_code)]
|
||||
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<i32>) {
|
||||
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.
|
||||
#[allow(dead_code)]
|
||||
pub fn state(&self) -> &State { &self.state }
|
||||
@ -114,7 +120,7 @@ impl Client {
|
||||
/// Send a chat message to the server
|
||||
#[allow(dead_code)]
|
||||
pub fn send_chat(&mut self, msg: String) {
|
||||
self.postbox.send(ClientMsg::Chat(msg))
|
||||
self.postbox.send_message(ClientMsg::Chat(msg))
|
||||
}
|
||||
|
||||
/// Execute a single client tick, handle input and update the game state by the given duration
|
||||
@ -138,12 +144,19 @@ impl Client {
|
||||
// Handle new messages from the server
|
||||
frontend_events.append(&mut self.handle_new_messages()?);
|
||||
|
||||
self.state.terrain().iter().for_each(|(k, _)| {
|
||||
println!("Chunk at {:?}", k);
|
||||
});
|
||||
|
||||
// Step 1
|
||||
if let Some(ecs_entity) = self.player {
|
||||
// TODO: remove this
|
||||
const PLAYER_VELOCITY: f32 = 100.0;
|
||||
// TODO: Set acceleration instead
|
||||
self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY)));
|
||||
self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY) * 0.1));
|
||||
if input.move_dir.magnitude() > 0.01 {
|
||||
self.state.write_component(ecs_entity, comp::phys::Dir(input.move_dir.normalized().into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Tick the client's LocalState (step 3)
|
||||
@ -157,12 +170,31 @@ impl Client {
|
||||
self.state.read_storage().get(ecs_entity).cloned(),
|
||||
) {
|
||||
(Some(pos), Some(vel), Some(dir)) => {
|
||||
self.postbox.send(ClientMsg::PlayerPhysics { pos, vel, dir });
|
||||
self.postbox.send_message(ClientMsg::PlayerPhysics { pos, vel, dir });
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Request chunks from the server
|
||||
if let Some(player_entity) = self.player {
|
||||
if let Some(pos) = self.state.read_storage::<comp::phys::Pos>().get(player_entity) {
|
||||
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
|
||||
|
||||
for i in chunk_pos.x - 0..chunk_pos.x + 1 {
|
||||
for j in chunk_pos.y - 0..chunk_pos.y + 1 {
|
||||
for k in 0..3 {
|
||||
let key = chunk_pos + Vec3::new(i, j, k);
|
||||
if self.state.terrain().get_key(key).is_none() && !self.pending_chunks.contains(&key) {
|
||||
self.postbox.send_message(ClientMsg::TerrainChunkRequest { key });
|
||||
self.pending_chunks.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the tick, pass control back to the frontend (step 6)
|
||||
self.tick += 1;
|
||||
Ok(frontend_events)
|
||||
@ -187,28 +219,16 @@ impl Client {
|
||||
|
||||
for msg in new_msgs {
|
||||
match msg {
|
||||
ServerMsg::Handshake { .. } => return Err(Error::ServerWentMad),
|
||||
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
||||
ServerMsg::Ping => self.postbox.send(ClientMsg::Pong),
|
||||
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
|
||||
ServerMsg::Pong => {},
|
||||
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
|
||||
ServerMsg::SetPlayerEntity(uid) => {
|
||||
let ecs_entity = self.state
|
||||
.get_entity(uid)
|
||||
.unwrap_or_else(|| self.state.build_uid_entity_with_uid(uid).build());
|
||||
|
||||
self.player = Some(ecs_entity);
|
||||
},
|
||||
ServerMsg::EntityPhysics { uid, pos, vel, dir } => {
|
||||
let ecs_entity = self.state
|
||||
.get_entity(uid)
|
||||
.unwrap_or_else(|| self.state.build_uid_entity_with_uid(uid).build());
|
||||
|
||||
self.state.write_component(ecs_entity, pos);
|
||||
self.state.write_component(ecs_entity, vel);
|
||||
self.state.write_component(ecs_entity, dir);
|
||||
},
|
||||
ServerMsg::EntityDeleted(uid) => {
|
||||
self.state.delete_entity(uid);
|
||||
ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here!
|
||||
ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package),
|
||||
ServerMsg::TerrainChunkUpdate { key, chunk } => {
|
||||
self.state.insert_chunk(key, *chunk);
|
||||
self.pending_chunks.remove(&key);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -218,7 +238,7 @@ impl Client {
|
||||
return Err(Error::ServerTimeout);
|
||||
} else if self.state.get_time() - self.last_ping > SERVER_TIMEOUT * 0.5 {
|
||||
// Try pinging the server if the timeout is nearing
|
||||
self.postbox.send(ClientMsg::Ping);
|
||||
self.postbox.send_message(ClientMsg::Ping);
|
||||
}
|
||||
|
||||
Ok(frontend_events)
|
||||
@ -227,6 +247,6 @@ impl Client {
|
||||
|
||||
impl Drop for Client {
|
||||
fn drop(&mut self) {
|
||||
self.postbox.send(ClientMsg::Disconnect);
|
||||
self.postbox.send_message(ClientMsg::Disconnect);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
[package]
|
||||
name = "veloren-common"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mckol363@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"] }
|
||||
|
||||
specs = { version = "0.14", features = ["serde"] }
|
||||
shred = { version = "0.7", features = ["nightly"] }
|
||||
vek = { version = "0.9", features = ["serde"] }
|
||||
@ -16,4 +18,3 @@ serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
bincode = "1.0"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.3"
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Library
|
||||
use specs::{Component, VecStorage};
|
||||
use specs::{Component, VecStorage, FlaggedStorage};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
enum Race {
|
||||
pub enum Race {
|
||||
Danari,
|
||||
Dwarf,
|
||||
Elf,
|
||||
@ -13,7 +12,7 @@ enum Race {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Gender {
|
||||
pub enum Gender {
|
||||
Female,
|
||||
Male,
|
||||
Unspecified,
|
||||
@ -30,6 +29,21 @@ pub struct Character {
|
||||
feet: (),
|
||||
}
|
||||
|
||||
impl Component for Character {
|
||||
type Storage = VecStorage<Self>;
|
||||
impl Character {
|
||||
// TODO: Remove this
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
race: Race::Human,
|
||||
gender: Gender::Unspecified,
|
||||
head: (),
|
||||
chest: (),
|
||||
belt: (),
|
||||
arms: (),
|
||||
feet: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Character {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
@ -1,20 +1,7 @@
|
||||
pub mod character;
|
||||
pub mod player;
|
||||
pub mod phys;
|
||||
pub mod uid;
|
||||
pub mod util;
|
||||
|
||||
// Reexports
|
||||
pub use uid::{Uid, UidAllocator};
|
||||
|
||||
use specs::World as EcsWorld;
|
||||
|
||||
pub fn register_local_components(ecs_world: &mut EcsWorld) {
|
||||
ecs_world.register::<Uid>();
|
||||
ecs_world.add_resource(UidAllocator::new());
|
||||
|
||||
ecs_world.register::<util::New>();
|
||||
|
||||
ecs_world.register::<phys::Pos>();
|
||||
ecs_world.register::<phys::Vel>();
|
||||
ecs_world.register::<phys::Dir>();
|
||||
ecs_world.register::<phys::UpdateKind>();
|
||||
}
|
||||
pub use character::Character;
|
||||
pub use player::Player;
|
||||
|
@ -1,5 +1,4 @@
|
||||
// Library
|
||||
use specs::{Component, VecStorage};
|
||||
use specs::{Component, VecStorage, FlaggedStorage};
|
||||
use vek::*;
|
||||
|
||||
// Pos
|
||||
@ -8,7 +7,7 @@ use vek::*;
|
||||
pub struct Pos(pub Vec3<f32>);
|
||||
|
||||
impl Component for Pos {
|
||||
type Storage = VecStorage<Self>;
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
||||
// Vel
|
||||
@ -17,7 +16,7 @@ impl Component for Pos {
|
||||
pub struct Vel(pub Vec3<f32>);
|
||||
|
||||
impl Component for Vel {
|
||||
type Storage = VecStorage<Self>;
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
||||
// Dir
|
||||
@ -26,17 +25,5 @@ impl Component for Vel {
|
||||
pub struct Dir(pub Vec3<f32>);
|
||||
|
||||
impl Component for Dir {
|
||||
type Storage = VecStorage<Self>;
|
||||
}
|
||||
|
||||
// UpdateKind
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum UpdateKind {
|
||||
Passive,
|
||||
Force,
|
||||
}
|
||||
|
||||
impl Component for UpdateKind {
|
||||
type Storage = VecStorage<Self>;
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
18
common/src/comp/player.rs
Normal file
18
common/src/comp/player.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use specs::{Component, VecStorage, FlaggedStorage};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Player {
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(alias: String) -> Self {
|
||||
Self {
|
||||
alias,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Player {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
u64,
|
||||
fmt,
|
||||
};
|
||||
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 fmt::Display for Uid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", 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();
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// Library
|
||||
use specs::{Component, NullStorage};
|
||||
use vek::*;
|
||||
|
||||
// Pos
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct New;
|
||||
|
||||
impl Component for New {
|
||||
type Storage = NullStorage<Self>;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
#![feature(euclidean_division, duration_float, try_from, trait_alias)]
|
||||
#![feature(euclidean_division, duration_float, trait_alias, bind_by_move_pattern_guards)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
@ -1,17 +1,22 @@
|
||||
use crate::comp::{
|
||||
Uid,
|
||||
phys,
|
||||
};
|
||||
use vek::*;
|
||||
use crate::comp;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum ClientMsg {
|
||||
Connect {
|
||||
player: comp::Player,
|
||||
character: Option<comp::Character>,
|
||||
},
|
||||
Ping,
|
||||
Pong,
|
||||
Chat(String),
|
||||
PlayerPhysics {
|
||||
pos: phys::Pos,
|
||||
vel: phys::Vel,
|
||||
dir: phys::Dir,
|
||||
pos: comp::phys::Pos,
|
||||
vel: comp::phys::Vel,
|
||||
dir: comp::phys::Dir,
|
||||
},
|
||||
TerrainChunkRequest {
|
||||
key: Vec3<i32>,
|
||||
},
|
||||
Disconnect,
|
||||
}
|
||||
|
29
common/src/msg/ecs_packet.rs
Normal file
29
common/src/msg/ecs_packet.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::marker::PhantomData;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use crate::comp;
|
||||
|
||||
// Automatically derive From<T> for Packet for each variant Packet::T(T)
|
||||
sphynx::sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsPacket {
|
||||
Pos(comp::phys::Pos),
|
||||
Vel(comp::phys::Vel),
|
||||
Dir(comp::phys::Dir),
|
||||
Character(comp::Character),
|
||||
Player(comp::Player),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for Phantom for each variant Phantom::T(PhantomData<T>)
|
||||
sphynx::sum_type! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum EcsPhantom {
|
||||
Pos(PhantomData<comp::phys::Pos>),
|
||||
Vel(PhantomData<comp::phys::Vel>),
|
||||
Dir(PhantomData<comp::phys::Dir>),
|
||||
Character(PhantomData<comp::Character>),
|
||||
Player(PhantomData<comp::Player>),
|
||||
}
|
||||
}
|
||||
impl sphynx::Packet for EcsPacket {
|
||||
type Phantom = EcsPhantom;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
pub mod ecs_packet;
|
||||
pub mod server;
|
||||
pub mod client;
|
||||
|
||||
// Reexports
|
||||
pub use server::ServerMsg;
|
||||
pub use client::ClientMsg;
|
||||
pub use self::server::ServerMsg;
|
||||
pub use self::client::ClientMsg;
|
||||
pub use self::ecs_packet::EcsPacket;
|
||||
|
@ -1,20 +1,21 @@
|
||||
use crate::comp::{
|
||||
Uid,
|
||||
phys,
|
||||
};
|
||||
use vek::*;
|
||||
use crate::terrain::TerrainChunk;
|
||||
use super::EcsPacket;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum ServerMsg {
|
||||
Handshake {
|
||||
ecs_state: sphynx::StatePackage<EcsPacket>,
|
||||
player_entity: u64,
|
||||
},
|
||||
Shutdown,
|
||||
Ping,
|
||||
Pong,
|
||||
Chat(String),
|
||||
SetPlayerEntity(Uid),
|
||||
EntityPhysics {
|
||||
uid: Uid,
|
||||
pos: phys::Pos,
|
||||
vel: phys::Vel,
|
||||
dir: phys::Dir,
|
||||
SetPlayerEntity(u64),
|
||||
EcsSync(sphynx::SyncPackage<EcsPacket>),
|
||||
TerrainChunkUpdate {
|
||||
key: Vec3<i32>,
|
||||
chunk: Box<TerrainChunk>,
|
||||
},
|
||||
EntityDeleted(Uid),
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
pub mod data;
|
||||
pub mod post;
|
||||
//pub mod post;
|
||||
pub mod post2;
|
||||
|
||||
pub use post2 as post;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
|
@ -23,6 +23,7 @@ use mio_extras::channel::{
|
||||
Sender,
|
||||
};
|
||||
use bincode;
|
||||
use middleman::Middleman;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@ -56,14 +57,15 @@ impl<T> From<mio_extras::channel::SendError<T>> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PostSend = 'static + serde::Serialize + Send + fmt::Debug;
|
||||
pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + fmt::Debug;
|
||||
pub trait PostSend = 'static + serde::Serialize + Send + middleman::Message;
|
||||
pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + middleman::Message;
|
||||
|
||||
const TCP_TOK: Token = Token(0);
|
||||
const CTRL_TOK: Token = Token(1);
|
||||
const POSTBOX_TOK: Token = Token(2);
|
||||
const SEND_TOK: Token = Token(3);
|
||||
const RECV_TOK: Token = Token(4);
|
||||
const MIDDLEMAN_TOK: Token = Token(5);
|
||||
|
||||
const MAX_MSG_BYTES: usize = 1 << 20;
|
||||
|
||||
@ -218,7 +220,7 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> {
|
||||
let (recv_tx, recv_rx) = channel();
|
||||
|
||||
let worker_poll = Poll::new()?;
|
||||
worker_poll.register(&tcp_stream, TCP_TOK, Ready::readable(), PollOpt::edge())?;
|
||||
worker_poll.register(&tcp_stream, TCP_TOK, Ready::readable() | Ready::writable(), PollOpt::edge())?;
|
||||
worker_poll.register(&ctrl_rx, CTRL_TOK, Ready::readable(), PollOpt::edge())?;
|
||||
worker_poll.register(&send_rx, SEND_TOK, Ready::readable(), PollOpt::edge())?;
|
||||
|
||||
@ -251,6 +253,45 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> {
|
||||
let _ = self.send_tx.send(data);
|
||||
}
|
||||
|
||||
// TODO: This method is super messy
|
||||
pub fn next_message(&mut self) -> Option<R> {
|
||||
if self.err.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut events = Events::with_capacity(10);
|
||||
if let Err(err) = self.poll.poll(
|
||||
&mut events,
|
||||
Some(Duration::new(0, 0)),
|
||||
) {
|
||||
self.err = Some(err.into());
|
||||
return None;
|
||||
}
|
||||
|
||||
for event in events {
|
||||
match event.token() {
|
||||
// Keep reading new messages from the channel
|
||||
RECV_TOK => loop {
|
||||
match self.recv_rx.try_recv() {
|
||||
Ok(Ok(msg)) => return Some(msg),
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(err) => {
|
||||
self.err = Some(err.into());
|
||||
return None;
|
||||
},
|
||||
Ok(Err(err)) => {
|
||||
self.err = Some(err);
|
||||
return None;
|
||||
},
|
||||
}
|
||||
},
|
||||
tok => panic!("Unexpected event token '{:?}'", tok),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> {
|
||||
let mut msgs = VecDeque::new();
|
||||
|
||||
@ -306,12 +347,38 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
send_rx: Receiver<S>,
|
||||
recv_tx: Sender<Result<R, Error>>,
|
||||
) -> Result<(), Error> {
|
||||
fn try_tcp_send(tcp_stream: &mut TcpStream, chunks: &mut VecDeque<Vec<u8>>) -> Result<(), Error> {
|
||||
loop {
|
||||
let chunk = match chunks.pop_front() {
|
||||
Some(chunk) => chunk,
|
||||
None => break,
|
||||
};
|
||||
|
||||
match tcp_stream.write_all(&chunk) {
|
||||
Ok(()) => {},
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
|
||||
chunks.push_front(chunk);
|
||||
break;
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
return Err(err.into());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum RecvState {
|
||||
ReadHead(Vec<u8>),
|
||||
ReadBody(usize, Vec<u8>),
|
||||
}
|
||||
|
||||
let mut recv_state = RecvState::ReadHead(Vec::with_capacity(8));
|
||||
let mut recv_state = RecvState::ReadHead(Vec::new());
|
||||
let mut chunks = VecDeque::new();
|
||||
|
||||
//let mut recv_state = RecvState::ReadHead(Vec::with_capacity(8));
|
||||
let mut events = Events::with_capacity(64);
|
||||
|
||||
'work: loop {
|
||||
@ -344,18 +411,23 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
},
|
||||
};
|
||||
|
||||
let mut packet = msg_bytes
|
||||
let mut bytes = msg_bytes
|
||||
.len()
|
||||
.to_le_bytes()
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
packet.append(&mut msg_bytes);
|
||||
bytes.append(&mut msg_bytes);
|
||||
|
||||
match tcp_stream.write_all(&packet) {
|
||||
Ok(()) => {},
|
||||
bytes
|
||||
.chunks(1024)
|
||||
.map(|chunk| chunk.to_vec())
|
||||
.for_each(|chunk| chunks.push_back(chunk));
|
||||
|
||||
match try_tcp_send(&mut tcp_stream, &mut chunks) {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
recv_tx.send(Err(err.into()))?;
|
||||
break 'work;
|
||||
return Err(Error::Network);
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -363,7 +435,9 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
Err(err) => Err(err)?,
|
||||
}
|
||||
},
|
||||
TCP_TOK => loop {
|
||||
TCP_TOK => {
|
||||
loop {
|
||||
// Check TCP error
|
||||
match tcp_stream.take_error() {
|
||||
Ok(None) => {},
|
||||
Ok(Some(err)) => {
|
||||
@ -379,6 +453,7 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
RecvState::ReadHead(head) => if head.len() == 8 {
|
||||
let len = usize::from_le_bytes(<[u8; 8]>::try_from(head.as_slice()).unwrap());
|
||||
if len > MAX_MSG_BYTES {
|
||||
println!("TOO BIG! {:x}", len);
|
||||
recv_tx.send(Err(Error::InvalidMsg))?;
|
||||
break 'work;
|
||||
} else if len == 0 {
|
||||
@ -393,6 +468,7 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
} else {
|
||||
let mut b = [0; 1];
|
||||
match tcp_stream.read(&mut b) {
|
||||
Ok(0) => {},
|
||||
Ok(_) => head.push(b[0]),
|
||||
Err(_) => break,
|
||||
}
|
||||
@ -420,30 +496,44 @@ fn postbox_worker<S: PostSend, R: PostRecv>(
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Now, try sending TCP stuff
|
||||
match try_tcp_send(&mut tcp_stream, &mut chunks) {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
recv_tx.send(Err(err.into()))?;
|
||||
return Err(Error::Network);
|
||||
},
|
||||
}
|
||||
},
|
||||
tok => panic!("Unexpected event token '{:?}'", tok),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tcp_stream.shutdown(Shutdown::Both)?;
|
||||
//tcp_stream.shutdown(Shutdown::Both)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TESTS
|
||||
|
||||
/*
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TestMsg<T>(T);
|
||||
|
||||
#[test]
|
||||
fn connect() {
|
||||
let srv_addr = ([127, 0, 0, 1], 12345);
|
||||
|
||||
let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap();
|
||||
let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap();
|
||||
|
||||
// We should start off with 0 incoming connections
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
assert_eq!(postoffice.new_connections().len(), 0);
|
||||
assert_eq!(postoffice.error(), None);
|
||||
|
||||
let postbox = PostBox::<f32, u32>::to_server(srv_addr).unwrap();
|
||||
let postbox = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap();
|
||||
|
||||
// Now a postbox has been created, we should have 1 new
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
@ -457,21 +547,21 @@ fn connect_fail() {
|
||||
let listen_addr = ([0; 4], 12345);
|
||||
let connect_addr = ([127, 0, 0, 1], 12212);
|
||||
|
||||
let mut postoffice = PostOffice::<u32, f32>::bind(listen_addr).unwrap();
|
||||
let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(listen_addr).unwrap();
|
||||
|
||||
// We should start off with 0 incoming connections
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
assert_eq!(postoffice.new_connections().len(), 0);
|
||||
assert_eq!(postoffice.error(), None);
|
||||
|
||||
assert!(PostBox::<f32, u32>::to_server(connect_addr).is_err());
|
||||
assert!(PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(connect_addr).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_count() {
|
||||
let srv_addr = ([127, 0, 0, 1], 12346);
|
||||
|
||||
let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap();
|
||||
let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap();
|
||||
let mut postboxes = Vec::new();
|
||||
|
||||
// We should start off with 0 incoming connections
|
||||
@ -480,7 +570,7 @@ fn connection_count() {
|
||||
assert_eq!(postoffice.error(), None);
|
||||
|
||||
for _ in 0..5 {
|
||||
postboxes.push(PostBox::<f32, u32>::to_server(srv_addr).unwrap());
|
||||
postboxes.push(PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap());
|
||||
}
|
||||
|
||||
// 5 postboxes created, we should have 5
|
||||
@ -494,10 +584,10 @@ fn connection_count() {
|
||||
fn disconnect() {
|
||||
let srv_addr = ([127, 0, 0, 1], 12347);
|
||||
|
||||
let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap();
|
||||
let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap();
|
||||
|
||||
let mut server_postbox = {
|
||||
let mut client_postbox = PostBox::<f32, u32>::to_server(srv_addr).unwrap();
|
||||
let mut client_postbox = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
let mut incoming = postoffice.new_connections();
|
||||
@ -519,52 +609,53 @@ fn disconnect() {
|
||||
fn client_to_server() {
|
||||
let srv_addr = ([127, 0, 0, 1], 12348);
|
||||
|
||||
let mut po = PostOffice::<u32, f32>::bind(srv_addr).unwrap();
|
||||
let mut po = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap();
|
||||
|
||||
let mut client_pb = PostBox::<f32, u32>::to_server(srv_addr).unwrap();
|
||||
let mut client_pb = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
|
||||
let mut server_pb = po.new_connections().next().unwrap();
|
||||
|
||||
client_pb.send(1337.0);
|
||||
client_pb.send(9821.0);
|
||||
client_pb.send(-3.2);
|
||||
client_pb.send(17.0);
|
||||
client_pb.send(TestMsg(1337.0));
|
||||
client_pb.send(TestMsg(9821.0));
|
||||
client_pb.send(TestMsg(-3.2));
|
||||
client_pb.send(TestMsg(17.0));
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
|
||||
let mut incoming_msgs = server_pb.new_messages();
|
||||
assert_eq!(incoming_msgs.len(), 4);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 1337.0);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 9821.0);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), -3.2);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 17.0);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(1337.0));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(9821.0));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(-3.2));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(17.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_to_client() {
|
||||
let srv_addr = ([127, 0, 0, 1], 12349);
|
||||
|
||||
let mut po = PostOffice::<u32, f32>::bind(srv_addr).unwrap();
|
||||
let mut po = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap();
|
||||
|
||||
let mut client_pb = PostBox::<f32, u32>::to_server(srv_addr).unwrap();
|
||||
let mut client_pb = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap();
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
|
||||
let mut server_pb = po.new_connections().next().unwrap();
|
||||
|
||||
server_pb.send(1337);
|
||||
server_pb.send(9821);
|
||||
server_pb.send(39999999);
|
||||
server_pb.send(17);
|
||||
server_pb.send(TestMsg(1337));
|
||||
server_pb.send(TestMsg(9821));
|
||||
server_pb.send(TestMsg(39999999));
|
||||
server_pb.send(TestMsg(17));
|
||||
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
|
||||
let mut incoming_msgs = client_pb.new_messages();
|
||||
assert_eq!(incoming_msgs.len(), 4);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 1337);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 9821);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 39999999);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), 17);
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(1337));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(9821));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(39999999));
|
||||
assert_eq!(incoming_msgs.next().unwrap(), TestMsg(17));
|
||||
}
|
||||
*/
|
||||
|
404
common/src/net/post2.rs
Normal file
404
common/src/net/post2.rs
Normal file
@ -0,0 +1,404 @@
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
net::{TcpListener, TcpStream, SocketAddr, Shutdown},
|
||||
time::{Instant, Duration},
|
||||
marker::PhantomData,
|
||||
sync::{mpsc, Arc, atomic::{AtomicBool, Ordering}},
|
||||
thread,
|
||||
collections::VecDeque,
|
||||
convert::TryFrom,
|
||||
};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Error {
|
||||
Io, //Io(io::Error),
|
||||
Bincode, //Bincode(bincode::Error),
|
||||
ChannelFailure,
|
||||
InvalidMessage,
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Error::Io //(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::Error> for Error {
|
||||
fn from(err: bincode::Error) -> Self {
|
||||
Error::Bincode //(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpsc::TryRecvError> for Error {
|
||||
fn from(error: mpsc::TryRecvError) -> Self {
|
||||
Error::ChannelFailure
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PostMsg = Serialize + DeserializeOwned + 'static + Send;
|
||||
|
||||
const MAX_MSG_SIZE: usize = 1 << 20;
|
||||
|
||||
pub struct PostOffice<S: PostMsg, R: PostMsg> {
|
||||
listener: TcpListener,
|
||||
error: Option<Error>,
|
||||
phantom: PhantomData<(S, R)>,
|
||||
}
|
||||
|
||||
impl<S: PostMsg, R: PostMsg> PostOffice<S, R> {
|
||||
pub fn bind<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
|
||||
let mut listener = TcpListener::bind(addr.into())?;
|
||||
listener.set_nonblocking(true)?;
|
||||
|
||||
Ok(Self {
|
||||
listener,
|
||||
error: None,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn error(&self) -> Option<Error> {
|
||||
self.error.clone()
|
||||
}
|
||||
|
||||
pub fn new_postboxes(&mut self) -> impl ExactSizeIterator<Item=PostBox<S, R>> {
|
||||
let mut new = Vec::new();
|
||||
|
||||
if self.error.is_some() {
|
||||
return new.into_iter();
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.listener.accept() {
|
||||
Ok((stream, sock)) => new.push(PostBox::from_stream(stream).unwrap()),
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
||||
Err(e) => {
|
||||
self.error = Some(e.into());
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
new.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PostBox<S: PostMsg, R: PostMsg> {
|
||||
send_tx: mpsc::Sender<S>,
|
||||
recv_rx: mpsc::Receiver<Result<R, Error>>,
|
||||
worker: Option<thread::JoinHandle<()>>,
|
||||
running: Arc<AtomicBool>,
|
||||
error: Option<Error>,
|
||||
}
|
||||
|
||||
impl<S: PostMsg, R: PostMsg> PostBox<S, R> {
|
||||
pub fn to<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
|
||||
Self::from_stream(TcpStream::connect(addr.into())?)
|
||||
}
|
||||
|
||||
fn from_stream(stream: TcpStream) -> Result<Self, Error> {
|
||||
stream.set_nonblocking(true)?;
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let worker_running = running.clone();
|
||||
|
||||
let (send_tx, send_rx) = mpsc::channel();
|
||||
let (recv_tx, recv_rx) = mpsc::channel();
|
||||
|
||||
let worker = thread::spawn(move || Self::worker(stream, send_rx, recv_tx, worker_running));
|
||||
|
||||
Ok(Self {
|
||||
send_tx,
|
||||
recv_rx,
|
||||
worker: Some(worker),
|
||||
running,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn error(&self) -> Option<Error> {
|
||||
self.error.clone()
|
||||
}
|
||||
|
||||
pub fn send_message(&mut self, msg: S) {
|
||||
let _ = self.send_tx.send(msg);
|
||||
}
|
||||
|
||||
pub fn next_message(&mut self) -> Option<R> {
|
||||
if self.error.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.recv_rx.recv().ok()? {
|
||||
Ok(msg) => Some(msg),
|
||||
Err(e) => {
|
||||
self.error = Some(e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> {
|
||||
let mut new = Vec::new();
|
||||
|
||||
if self.error.is_some() {
|
||||
return new.into_iter();
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.recv_rx.try_recv() {
|
||||
Ok(Ok(msg)) => new.push(msg),
|
||||
Err(mpsc::TryRecvError::Empty) => break,
|
||||
Err(e) => {
|
||||
self.error = Some(e.into());
|
||||
break;
|
||||
},
|
||||
Ok(Err(e)) => {
|
||||
self.error = Some(e);
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
new.into_iter()
|
||||
}
|
||||
|
||||
fn worker(mut stream: TcpStream, send_rx: mpsc::Receiver<S>, recv_tx: mpsc::Sender<Result<R, Error>>, running: Arc<AtomicBool>) {
|
||||
let mut outgoing_chunks = VecDeque::new();
|
||||
let mut incoming_buf = Vec::new();
|
||||
|
||||
'work: while running.load(Ordering::Relaxed) {
|
||||
// Get stream errors
|
||||
match stream.take_error() {
|
||||
Ok(Some(e)) | Err(e) => {
|
||||
recv_tx.send(Err(e.into())).unwrap();
|
||||
break 'work;
|
||||
},
|
||||
Ok(None) => {},
|
||||
}
|
||||
|
||||
// Try getting messages from the send channel
|
||||
for _ in 0..10 {
|
||||
match send_rx.try_recv() {
|
||||
Ok(send_msg) => {
|
||||
// Serialize message
|
||||
let mut msg_bytes = bincode::serialize(&send_msg).unwrap();
|
||||
|
||||
// Assemble into packet
|
||||
let mut packet_bytes = msg_bytes
|
||||
.len()
|
||||
.to_le_bytes()
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
packet_bytes.append(&mut msg_bytes);
|
||||
|
||||
// Split packet into chunks
|
||||
packet_bytes
|
||||
.chunks(4096)
|
||||
.map(|chunk| chunk.to_vec())
|
||||
.for_each(|chunk| outgoing_chunks.push_back(chunk))
|
||||
},
|
||||
Err(mpsc::TryRecvError::Empty) => break,
|
||||
// Worker error
|
||||
Err(e) => {
|
||||
let _ = recv_tx.send(Err(e.into()));
|
||||
break 'work;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Try sending bytes through the TCP stream
|
||||
for _ in 0..10 {
|
||||
//println!("HERE! Outgoing len: {}", outgoing_chunks.len());
|
||||
match outgoing_chunks.pop_front() {
|
||||
Some(chunk) => match stream.write_all(&chunk) {
|
||||
Ok(()) => {},
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
// Return chunk to the queue to try again later
|
||||
outgoing_chunks.push_front(chunk);
|
||||
break;
|
||||
},
|
||||
// Worker error
|
||||
Err(e) => {
|
||||
recv_tx.send(Err(e.into())).unwrap();
|
||||
break 'work;
|
||||
},
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Try receiving bytes from the TCP stream
|
||||
for _ in 0..10 {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
match stream.read(&mut buf) {
|
||||
Ok(n) => incoming_buf.extend_from_slice(&buf[0..n]),
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
||||
// Worker error
|
||||
Err(e) => {
|
||||
recv_tx.send(Err(e.into())).unwrap();
|
||||
break 'work;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Try turning bytes into messages
|
||||
for _ in 0..10 {
|
||||
match incoming_buf.get(0..8) {
|
||||
Some(len_bytes) => {
|
||||
let len = usize::from_le_bytes(<[u8; 8]>::try_from(len_bytes).unwrap()); // Can't fail
|
||||
|
||||
if len > MAX_MSG_SIZE {
|
||||
recv_tx.send(Err(Error::InvalidMessage)).unwrap();
|
||||
break 'work;
|
||||
} else if incoming_buf.len() >= len + 8 {
|
||||
let deserialize_result = bincode::deserialize(&incoming_buf[8..len + 8]);
|
||||
|
||||
if let Err(e) = &deserialize_result {
|
||||
println!("DESERIALIZE ERROR: {:?}", e);
|
||||
}
|
||||
|
||||
recv_tx.send(deserialize_result.map_err(|e| e.into()));
|
||||
incoming_buf = incoming_buf.split_off(len + 8);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
stream.shutdown(Shutdown::Both);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PostMsg, R: PostMsg> Drop for PostBox<S, R> {
|
||||
fn drop(&mut self) {
|
||||
self.running.store(false, Ordering::Relaxed);
|
||||
self.worker.take().map(|handle| handle.join());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_postoffice<S: PostMsg, R: PostMsg>(id: u16) -> Result<(PostOffice<S, R>, SocketAddr), Error> {
|
||||
let sock = ([0; 4], 12345 + id).into();
|
||||
Ok((PostOffice::bind(sock)?, sock))
|
||||
}
|
||||
|
||||
fn loop_for<F: FnMut()>(duration: Duration, mut f: F) {
|
||||
let start = Instant::now();
|
||||
while start.elapsed() < duration {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect() {
|
||||
let (mut postoffice, sock) = create_postoffice::<(), ()>(0).unwrap();
|
||||
|
||||
let _client0 = PostBox::<(), ()>::to(sock).unwrap();
|
||||
let _client1 = PostBox::<(), ()>::to(sock).unwrap();
|
||||
let _client2 = PostBox::<(), ()>::to(sock).unwrap();
|
||||
|
||||
let mut new_clients = 0;
|
||||
loop_for(Duration::from_millis(250), || {
|
||||
new_clients += postoffice.new_postboxes().count();
|
||||
});
|
||||
|
||||
assert_eq!(new_clients, 3);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn disconnect() {
|
||||
let (mut postoffice, sock) = create_postoffice::<(), ()>(1).unwrap();
|
||||
|
||||
let mut client = PostBox::<i32, ()>::to(sock).unwrap();
|
||||
loop_for(Duration::from_millis(250), || ());
|
||||
let mut server = postoffice.new_postboxes().unwrap().next().unwrap();
|
||||
|
||||
drop(client);
|
||||
loop_for(Duration::from_millis(300), || ());
|
||||
|
||||
assert!(server.new_messages().is_err());
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn send_recv() {
|
||||
let (mut postoffice, sock) = create_postoffice::<(), i32>(2).unwrap();
|
||||
let test_msgs = vec![1, 1337, 42, -48];
|
||||
|
||||
let mut client = PostBox::<i32, ()>::to(sock).unwrap();
|
||||
loop_for(Duration::from_millis(250), || ());
|
||||
let mut server = postoffice.new_postboxes().next().unwrap();
|
||||
|
||||
for msg in &test_msgs {
|
||||
client.send_message(msg.clone());
|
||||
}
|
||||
|
||||
let mut recv_msgs = Vec::new();
|
||||
loop_for(Duration::from_millis(250), || server
|
||||
.new_messages()
|
||||
.for_each(|msg| recv_msgs.push(msg)));
|
||||
|
||||
assert_eq!(test_msgs, recv_msgs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_huge() {
|
||||
let (mut postoffice, sock) = create_postoffice::<(), Vec<i32>>(3).unwrap();
|
||||
let test_msgs: Vec<Vec<i32>> = (0..5).map(|i| (0..100000).map(|j| i * 2 + j).collect()).collect();
|
||||
|
||||
let mut client = PostBox::<Vec<i32>, ()>::to(sock).unwrap();
|
||||
loop_for(Duration::from_millis(250), || ());
|
||||
let mut server = postoffice.new_postboxes().next().unwrap();
|
||||
|
||||
for msg in &test_msgs {
|
||||
client.send_message(msg.clone());
|
||||
}
|
||||
|
||||
let mut recv_msgs = Vec::new();
|
||||
loop_for(Duration::from_millis(3000), || server
|
||||
.new_messages()
|
||||
.for_each(|msg| recv_msgs.push(msg)));
|
||||
|
||||
assert_eq!(test_msgs.len(), recv_msgs.len());
|
||||
assert!(test_msgs == recv_msgs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_both() {
|
||||
let (mut postoffice, sock) = create_postoffice::<u32, u32>(4).unwrap();
|
||||
let test_msgs = vec![1, 1337, 42, -48];
|
||||
|
||||
let mut client = PostBox::<u32, u32>::to(sock).unwrap();
|
||||
loop_for(Duration::from_millis(250), || ());
|
||||
let mut server = postoffice.new_postboxes().next().unwrap();
|
||||
|
||||
let test_msgs = vec![
|
||||
(0xDEADBEAD, 0xBEEEEEEF),
|
||||
(0x1BADB002, 0xBAADF00D),
|
||||
(0xBAADA555, 0xC0DED00D),
|
||||
(0xCAFEBABE, 0xDEADC0DE),
|
||||
];
|
||||
|
||||
for (to, from) in test_msgs {
|
||||
client.send_message(to);
|
||||
server.send_message(from);
|
||||
|
||||
loop_for(Duration::from_millis(250), || ());
|
||||
|
||||
assert_eq!(client.new_messages().next().unwrap(), from);
|
||||
assert_eq!(server.new_messages().next().unwrap(), to);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
time::Duration,
|
||||
collections::HashSet,
|
||||
};
|
||||
use shred::{Fetch, FetchMut};
|
||||
use specs::{
|
||||
Builder,
|
||||
@ -6,18 +9,22 @@ use specs::{
|
||||
DispatcherBuilder,
|
||||
EntityBuilder as EcsEntityBuilder,
|
||||
Entity as EcsEntity,
|
||||
World as EcsWorld,
|
||||
storage::{
|
||||
Storage as EcsStorage,
|
||||
MaskedStorage as EcsMaskedStorage,
|
||||
},
|
||||
saveload::{MarkedBuilder, MarkerAllocator},
|
||||
};
|
||||
use sphynx;
|
||||
use vek::*;
|
||||
use crate::{
|
||||
comp,
|
||||
sys,
|
||||
terrain::TerrainMap,
|
||||
terrain::{
|
||||
TerrainMap,
|
||||
TerrainChunk,
|
||||
},
|
||||
msg::EcsPacket,
|
||||
};
|
||||
|
||||
/// How much faster should an in-game day be compared to a real day?
|
||||
@ -35,17 +42,17 @@ struct Time(f64);
|
||||
pub struct DeltaTime(pub f64);
|
||||
|
||||
pub struct Changes {
|
||||
pub new_chunks: Vec<Vec3<i32>>,
|
||||
pub changed_chunks: Vec<Vec3<i32>>,
|
||||
pub removed_chunks: Vec<Vec3<i32>>,
|
||||
pub new_chunks: HashSet<Vec3<i32>>,
|
||||
pub changed_chunks: HashSet<Vec3<i32>>,
|
||||
pub removed_chunks: HashSet<Vec3<i32>>,
|
||||
}
|
||||
|
||||
impl Changes {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
new_chunks: vec![],
|
||||
changed_chunks: vec![],
|
||||
removed_chunks: vec![],
|
||||
new_chunks: HashSet::new(),
|
||||
changed_chunks: HashSet::new(),
|
||||
removed_chunks: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,95 +66,74 @@ impl Changes {
|
||||
/// 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,
|
||||
ecs: sphynx::World<EcsPacket>,
|
||||
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(DeltaTime(0.0));
|
||||
ecs_world.add_resource(TerrainMap::new());
|
||||
|
||||
// Register common components with the state
|
||||
comp::register_local_components(&mut ecs_world);
|
||||
|
||||
Self {
|
||||
ecs_world,
|
||||
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
|
||||
changes: Changes::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `State` from an ECS state package
|
||||
pub fn from_state_package(state_package: sphynx::StatePackage<EcsPacket>) -> Self {
|
||||
Self {
|
||||
ecs: sphynx::World::from_state_package(specs::World::new(), Self::setup_sphynx_world, state_package),
|
||||
changes: Changes::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new Sphynx ECS world
|
||||
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsPacket>) {
|
||||
// Register synced components
|
||||
ecs.register_synced::<comp::phys::Pos>();
|
||||
ecs.register_synced::<comp::phys::Vel>();
|
||||
ecs.register_synced::<comp::phys::Dir>();
|
||||
ecs.register_synced::<comp::Character>();
|
||||
ecs.register_synced::<comp::Player>();
|
||||
|
||||
// Register resources used by the ECS
|
||||
ecs.internal_mut().add_resource(TimeOfDay(0.0));
|
||||
ecs.internal_mut().add_resource(Time(0.0));
|
||||
ecs.internal_mut().add_resource(DeltaTime(0.0));
|
||||
ecs.internal_mut().add_resource(TerrainMap::new());
|
||||
}
|
||||
|
||||
/// 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.ecs.internal_mut().register::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a new entity with a generated UID
|
||||
pub fn build_uid_entity(&mut self) -> EcsEntityBuilder {
|
||||
self.ecs_world.create_entity()
|
||||
.with(comp::util::New)
|
||||
.marked::<comp::Uid>()
|
||||
}
|
||||
|
||||
/// Build an entity with a specific UID
|
||||
pub fn build_uid_entity_with_uid(&mut self, uid: comp::Uid) -> EcsEntityBuilder {
|
||||
let builder = self.build_uid_entity();
|
||||
|
||||
builder.world
|
||||
.write_resource::<comp::UidAllocator>()
|
||||
.allocate(builder.entity, Some(uid.into()));
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
/// Get an entity from its UID, if it exists
|
||||
pub fn get_entity(&self, uid: comp::Uid) -> Option<EcsEntity> {
|
||||
// Find the ECS entity from its UID
|
||||
self.ecs_world
|
||||
.read_resource::<comp::UidAllocator>()
|
||||
.retrieve_entity_internal(uid.into())
|
||||
}
|
||||
|
||||
/// Delete an entity from the state's ECS, if it exists
|
||||
pub fn delete_entity(&mut self, uid: comp::Uid) {
|
||||
// Find the ECS entity from its UID
|
||||
let ecs_entity = self.ecs_world
|
||||
.read_resource::<comp::UidAllocator>()
|
||||
.retrieve_entity_internal(uid.into());
|
||||
|
||||
// Delete the ECS entity, if it exists
|
||||
if let Some(ecs_entity) = ecs_entity {
|
||||
let _ = self.ecs_world.delete_entity(ecs_entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
let _ = self.ecs.internal_mut().write_storage().insert(entity, comp);
|
||||
}
|
||||
|
||||
/// Read a component attributed to a particular entity
|
||||
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
|
||||
self.ecs.internal().read_storage().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>()
|
||||
self.ecs.internal().read_storage::<C>()
|
||||
}
|
||||
|
||||
/// Get a reference to the internal ECS world
|
||||
pub fn ecs_world(&self) -> &EcsWorld {
|
||||
&self.ecs_world
|
||||
pub fn ecs(&self) -> &sphynx::World<EcsPacket> {
|
||||
&self.ecs
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the internal ECS world
|
||||
pub fn ecs_world_mut(&mut self) -> &mut EcsWorld {
|
||||
&mut self.ecs_world
|
||||
pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsPacket> {
|
||||
&mut self.ecs
|
||||
}
|
||||
|
||||
/// Get a reference to the `Changes` structure of the state. This contains
|
||||
@ -156,54 +142,57 @@ impl State {
|
||||
&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::<TimeOfDay>().0
|
||||
self.ecs.internal().read_resource::<TimeOfDay>().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::<Time>().0
|
||||
self.ecs.internal().read_resource::<Time>().0
|
||||
}
|
||||
|
||||
/// Get a reference to this state's terrain.
|
||||
pub fn terrain(&self) -> Fetch<TerrainMap> {
|
||||
self.ecs_world.read_resource::<TerrainMap>()
|
||||
self.ecs
|
||||
.internal()
|
||||
.read_resource::<TerrainMap>()
|
||||
}
|
||||
|
||||
// TODO: Get rid of this since it shouldn't be needed
|
||||
pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> {
|
||||
self.ecs_world.write_resource::<TerrainMap>()
|
||||
/// Insert the provided chunk into this state's terrain.
|
||||
pub fn insert_chunk(&mut self, key: Vec3<i32>, chunk: TerrainChunk) {
|
||||
if self.ecs
|
||||
.internal_mut()
|
||||
.write_resource::<TerrainMap>()
|
||||
.insert(key, chunk)
|
||||
.is_some()
|
||||
{
|
||||
self.changes.changed_chunks.insert(key);
|
||||
} else {
|
||||
self.changes.new_chunks.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a single tick, simulating the game state by the given duration.
|
||||
pub fn tick(&mut self, dt: Duration) {
|
||||
// First, wipe all temporary marker components
|
||||
self.ecs_world.write_storage::<comp::util::New>().clear();
|
||||
|
||||
// Change the time accordingly
|
||||
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
||||
self.ecs_world.write_resource::<Time>().0 += dt.as_secs_f64();
|
||||
self.ecs.internal_mut().write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
||||
self.ecs.internal_mut().write_resource::<Time>().0 += dt.as_secs_f64();
|
||||
|
||||
// Run systems to update the world
|
||||
self.ecs_world.write_resource::<DeltaTime>().0 = dt.as_secs_f64();
|
||||
self.ecs.internal_mut().write_resource::<DeltaTime>().0 = dt.as_secs_f64();
|
||||
|
||||
// Create and run dispatcher for ecs systems
|
||||
let mut dispatch_builder = DispatcherBuilder::new();
|
||||
sys::add_local_systems(&mut dispatch_builder);
|
||||
// This dispatches all the systems in parallel
|
||||
dispatch_builder.build().dispatch(&self.ecs_world.res);
|
||||
dispatch_builder.build().dispatch(&self.ecs.internal_mut().res);
|
||||
|
||||
self.ecs_world.maintain();
|
||||
self.ecs.internal_mut().maintain();
|
||||
}
|
||||
|
||||
/// Clean up the state after a tick
|
||||
|
@ -1,3 +1,6 @@
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum BiomeKind {
|
||||
Void,
|
||||
Grassland,
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Library
|
||||
use vek::*;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
// Crate
|
||||
use crate::vol::Vox;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Block {
|
||||
kind: u8,
|
||||
color: [u8; 3],
|
||||
|
@ -7,10 +7,8 @@ pub use self::{
|
||||
biome::BiomeKind,
|
||||
};
|
||||
|
||||
// Library
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use crate::{
|
||||
vol::VolSize,
|
||||
volumes::{
|
||||
@ -21,6 +19,7 @@ use crate::{
|
||||
|
||||
// TerrainChunkSize
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TerrainChunkSize;
|
||||
|
||||
impl VolSize for TerrainChunkSize {
|
||||
@ -29,6 +28,7 @@ impl VolSize for TerrainChunkSize {
|
||||
|
||||
// TerrainChunkMeta
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TerrainChunkMeta {
|
||||
biome: BiomeKind,
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use std::marker::PhantomData;
|
||||
|
||||
// Library
|
||||
use vek::*;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
// Local
|
||||
use crate::vol::{
|
||||
@ -23,6 +24,7 @@ pub enum ChunkErr {
|
||||
// V = Voxel
|
||||
// S = Size (replace when const generics are a thing)
|
||||
// M = Metadata
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Chunk<V: Vox, S: VolSize, M> {
|
||||
vox: Vec<V>,
|
||||
meta: M,
|
||||
|
@ -126,7 +126,37 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
|
||||
self.chunks.insert(key, chunk)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> {
|
||||
self.chunks.remove(key)
|
||||
pub fn get_key(&self, key: Vec3<i32>) -> Option<&Chunk<V, S, M>> {
|
||||
self.chunks.get(&key)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: Vec3<i32>) -> Option<Chunk<V, S, M>> {
|
||||
self.chunks.remove(&key)
|
||||
}
|
||||
|
||||
pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> {
|
||||
key * S::SIZE.map(|e| e as i32)
|
||||
}
|
||||
|
||||
pub fn pos_key(&self, pos: Vec3<i32>) -> Vec3<i32> {
|
||||
Self::chunk_key(pos)
|
||||
}
|
||||
|
||||
pub fn iter<'a>(&'a self) -> ChunkIter<'a, V, S, M> {
|
||||
ChunkIter {
|
||||
iter: self.chunks.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChunkIter<'a, V: Vox, S: VolSize, M> {
|
||||
iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Chunk<V, S, M>>,
|
||||
}
|
||||
|
||||
impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkIter<'a, V, S, M> {
|
||||
type Item = (Vec3<i32>, &'a Chunk<V, S, M>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|(k, c)| (*k, c))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "veloren-server-cli"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -24,9 +24,9 @@ fn main() {
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
Event::ClientConnected { ecs_entity } => info!("Client connected!"),
|
||||
Event::ClientDisconnected { ecs_entity } => info!("Client disconnected!"),
|
||||
Event::Chat { ecs_entity, msg } => info!("[Client] {}", msg),
|
||||
Event::ClientConnected { entity } => info!("Client connected!"),
|
||||
Event::ClientDisconnected { entity } => info!("Client disconnected!"),
|
||||
Event::Chat { entity, msg } => info!("[Client] {}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "veloren-server"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" }
|
||||
|
||||
specs = "0.14"
|
||||
vek = "0.9"
|
||||
threadpool = "1.7"
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use specs::Entity as EcsEntity;
|
||||
use common::{
|
||||
comp,
|
||||
@ -6,41 +7,61 @@ use common::{
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ClientState {
|
||||
Connecting,
|
||||
Connected,
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
pub ecs_entity: EcsEntity,
|
||||
pub state: ClientState,
|
||||
pub postbox: PostBox<ServerMsg, ClientMsg>,
|
||||
pub last_ping: f64,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn notify(&mut self, msg: ServerMsg) {
|
||||
self.postbox.send_message(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Clients {
|
||||
clients: Vec<Client>,
|
||||
clients: HashMap<EcsEntity, Client>,
|
||||
}
|
||||
|
||||
impl Clients {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
clients: Vec::new(),
|
||||
clients: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, client: Client) {
|
||||
self.clients.push(client);
|
||||
pub fn add(&mut self, entity: EcsEntity, client: Client) {
|
||||
self.clients.insert(entity, client);
|
||||
}
|
||||
|
||||
pub fn remove_if<F: FnMut(&mut Client) -> bool>(&mut self, f: F) {
|
||||
self.clients.drain_filter(f);
|
||||
pub fn remove_if<F: FnMut(EcsEntity, &mut Client) -> bool>(&mut self, mut f: F) {
|
||||
self.clients.retain(|entity, client| !f(*entity, client));
|
||||
}
|
||||
|
||||
pub fn notify_all(&mut self, msg: ServerMsg) {
|
||||
for client in &mut self.clients {
|
||||
client.postbox.send(msg.clone());
|
||||
pub fn notify(&mut self, entity: EcsEntity, msg: ServerMsg) {
|
||||
if let Some(client) = self.clients.get_mut(&entity) {
|
||||
client.notify(msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_all_except(&mut self, ecs_entity: EcsEntity, msg: ServerMsg) {
|
||||
for client in &mut self.clients {
|
||||
if client.ecs_entity != ecs_entity {
|
||||
client.postbox.send(msg.clone());
|
||||
pub fn notify_connected(&mut self, msg: ServerMsg) {
|
||||
for client in self.clients.values_mut() {
|
||||
if client.state == ClientState::Connected {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_connected_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
|
||||
for (entity, client) in self.clients.iter_mut() {
|
||||
if client.state == ClientState::Connected && *entity != except_entity {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ pub use crate::{
|
||||
use std::{
|
||||
time::Duration,
|
||||
net::SocketAddr,
|
||||
sync::mpsc,
|
||||
collections::HashSet,
|
||||
};
|
||||
use specs::{
|
||||
Entity as EcsEntity,
|
||||
@ -22,14 +24,17 @@ use specs::{
|
||||
saveload::MarkedBuilder,
|
||||
};
|
||||
use vek::*;
|
||||
use threadpool::ThreadPool;
|
||||
use common::{
|
||||
comp,
|
||||
state::State,
|
||||
net::PostOffice,
|
||||
msg::{ServerMsg, ClientMsg},
|
||||
terrain::TerrainChunk,
|
||||
};
|
||||
use world::World;
|
||||
use crate::client::{
|
||||
ClientState,
|
||||
Client,
|
||||
Clients,
|
||||
};
|
||||
@ -38,13 +43,13 @@ const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
|
||||
|
||||
pub enum Event {
|
||||
ClientConnected {
|
||||
ecs_entity: EcsEntity,
|
||||
entity: EcsEntity,
|
||||
},
|
||||
ClientDisconnected {
|
||||
ecs_entity: EcsEntity,
|
||||
entity: EcsEntity,
|
||||
},
|
||||
Chat {
|
||||
ecs_entity: EcsEntity,
|
||||
entity: EcsEntity,
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
@ -55,18 +60,32 @@ pub struct Server {
|
||||
|
||||
postoffice: PostOffice<ServerMsg, ClientMsg>,
|
||||
clients: Clients,
|
||||
|
||||
thread_pool: ThreadPool,
|
||||
chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>,
|
||||
chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>,
|
||||
pending_chunks: HashSet<Vec3<i32>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Create a new `Server`.
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let (chunk_tx, chunk_rx) = mpsc::channel();
|
||||
|
||||
Ok(Self {
|
||||
state: State::new(),
|
||||
world: World::new(),
|
||||
|
||||
postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?,
|
||||
clients: Clients::empty(),
|
||||
|
||||
thread_pool: threadpool::Builder::new()
|
||||
.thread_name("veloren-worker".into())
|
||||
.build(),
|
||||
chunk_tx,
|
||||
chunk_rx,
|
||||
pending_chunks: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -77,15 +96,6 @@ impl Server {
|
||||
#[allow(dead_code)]
|
||||
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
|
||||
|
||||
/// Build a new player with a generated UID
|
||||
pub fn build_player(&mut self) -> EcsEntityBuilder {
|
||||
self.state.build_uid_entity()
|
||||
.with(comp::phys::Pos(Vec3::zero()))
|
||||
.with(comp::phys::Vel(Vec3::zero()))
|
||||
.with(comp::phys::Dir(Vec3::unit_y()))
|
||||
.with(comp::phys::UpdateKind::Passive)
|
||||
}
|
||||
|
||||
/// Get a reference to the server's world.
|
||||
#[allow(dead_code)]
|
||||
pub fn world(&self) -> &World { &self.world }
|
||||
@ -127,6 +137,29 @@ impl Server {
|
||||
// Tick the client's LocalState (step 3)
|
||||
self.state.tick(dt);
|
||||
|
||||
// Fetch any generated `TerrainChunk`s and insert them into the terrain
|
||||
// Also, send the chunk data to anybody that is close by
|
||||
for (key, chunk) in self.chunk_rx.try_iter() {
|
||||
// Send the chunk to all nearby players
|
||||
for (entity, player, pos) in (
|
||||
&self.state.ecs().internal().entities(),
|
||||
&self.state.ecs().internal().read_storage::<comp::Player>(),
|
||||
&self.state.ecs().internal().read_storage::<comp::phys::Pos>(),
|
||||
).join() {
|
||||
// TODO: Distance check
|
||||
// if self.state.terrain().key_pos(key)
|
||||
|
||||
/*
|
||||
self.clients.notify(entity, ServerMsg::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Box::new(chunk.clone()),
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
self.state.insert_chunk(key, chunk);
|
||||
}
|
||||
|
||||
// Synchronise clients with the new state of the world
|
||||
self.sync_clients();
|
||||
|
||||
@ -145,24 +178,20 @@ impl Server {
|
||||
fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> {
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
for mut postbox in self.postoffice.new_connections() {
|
||||
let ecs_entity = self.build_player()
|
||||
// When the player is first created, force a physics notification to everyone
|
||||
// including themselves.
|
||||
.with(comp::phys::UpdateKind::Force)
|
||||
for mut postbox in self.postoffice.new_postboxes() {
|
||||
let entity = self.state
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.build();
|
||||
let uid = self.state.read_storage().get(ecs_entity).cloned().unwrap();
|
||||
|
||||
postbox.send(ServerMsg::SetPlayerEntity(uid));
|
||||
|
||||
self.clients.add(Client {
|
||||
ecs_entity,
|
||||
self.clients.add(entity, Client {
|
||||
state: ClientState::Connecting,
|
||||
postbox,
|
||||
last_ping: self.state.get_time(),
|
||||
});
|
||||
|
||||
frontend_events.push(Event::ClientConnected {
|
||||
ecs_entity,
|
||||
entity,
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,9 +205,10 @@ impl Server {
|
||||
let state = &mut self.state;
|
||||
let mut new_chat_msgs = Vec::new();
|
||||
let mut disconnected_clients = Vec::new();
|
||||
let mut requested_chunks = Vec::new();
|
||||
|
||||
self.clients.remove_if(|client| {
|
||||
let mut disconnected = false;
|
||||
self.clients.remove_if(|entity, client| {
|
||||
let mut disconnect = false;
|
||||
let new_msgs = client.postbox.new_messages();
|
||||
|
||||
// Update client ping
|
||||
@ -187,30 +217,66 @@ impl Server {
|
||||
|
||||
// Process incoming messages
|
||||
for msg in new_msgs {
|
||||
match msg {
|
||||
ClientMsg::Ping => client.postbox.send(ServerMsg::Pong),
|
||||
ClientMsg::Pong => {},
|
||||
ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)),
|
||||
ClientMsg::PlayerPhysics { pos, vel, dir } => {
|
||||
state.write_component(client.ecs_entity, pos);
|
||||
state.write_component(client.ecs_entity, vel);
|
||||
state.write_component(client.ecs_entity, dir);
|
||||
match client.state {
|
||||
ClientState::Connecting => match msg {
|
||||
ClientMsg::Connect { player, character } => {
|
||||
|
||||
// Write client components
|
||||
state.write_component(entity, player);
|
||||
state.write_component(entity, comp::phys::Pos(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
|
||||
if let Some(character) = character {
|
||||
state.write_component(entity, character);
|
||||
}
|
||||
|
||||
client.state = ClientState::Connected;
|
||||
|
||||
// Return a handshake with the state of the current world
|
||||
client.notify(ServerMsg::Handshake {
|
||||
ecs_state: state.ecs().gen_state_package(),
|
||||
player_entity: state
|
||||
.ecs()
|
||||
.uid_from_entity(entity)
|
||||
.unwrap()
|
||||
.into(),
|
||||
});
|
||||
},
|
||||
_ => disconnect = true,
|
||||
},
|
||||
ClientState::Connected => match msg {
|
||||
ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected
|
||||
ClientMsg::Disconnect => disconnect = true,
|
||||
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
|
||||
ClientMsg::Pong => {},
|
||||
ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)),
|
||||
ClientMsg::PlayerPhysics { pos, vel, dir } => {
|
||||
state.write_component(entity, pos);
|
||||
state.write_component(entity, vel);
|
||||
state.write_component(entity, dir);
|
||||
},
|
||||
ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) {
|
||||
Some(chunk) => {}, /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Box::new(chunk.clone()),
|
||||
}),*/
|
||||
None => requested_chunks.push(key),
|
||||
},
|
||||
},
|
||||
ClientMsg::Disconnect => disconnected = true,
|
||||
}
|
||||
}
|
||||
} else if
|
||||
state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
|
||||
client.postbox.error().is_some() // Postbox error
|
||||
{
|
||||
disconnected = true;
|
||||
disconnect = true;
|
||||
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 {
|
||||
// Try pinging the client if the timeout is nearing
|
||||
client.postbox.send(ServerMsg::Ping);
|
||||
client.postbox.send_message(ServerMsg::Ping);
|
||||
}
|
||||
|
||||
if disconnected {
|
||||
disconnected_clients.push(client.ecs_entity);
|
||||
if disconnect {
|
||||
disconnected_clients.push(entity);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -218,24 +284,35 @@ impl Server {
|
||||
});
|
||||
|
||||
// Handle new chat messages
|
||||
for (ecs_entity, msg) in new_chat_msgs {
|
||||
self.clients.notify_all(ServerMsg::Chat(msg.clone()));
|
||||
for (entity, msg) in new_chat_msgs {
|
||||
self.clients.notify_connected(ServerMsg::Chat(match state
|
||||
.ecs()
|
||||
.internal()
|
||||
.read_storage::<comp::Player>()
|
||||
.get(entity)
|
||||
{
|
||||
Some(player) => format!("[{}] {}", &player.alias, msg),
|
||||
None => format!("[<anon>] {}", msg),
|
||||
}));
|
||||
|
||||
frontend_events.push(Event::Chat {
|
||||
ecs_entity,
|
||||
entity,
|
||||
msg,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle client disconnects
|
||||
for ecs_entity in disconnected_clients {
|
||||
self.clients.notify_all(ServerMsg::EntityDeleted(state.read_storage().get(ecs_entity).cloned().unwrap()));
|
||||
for entity in disconnected_clients {
|
||||
state.ecs_mut().delete_entity_synced(entity);
|
||||
|
||||
frontend_events.push(Event::ClientDisconnected {
|
||||
ecs_entity,
|
||||
entity,
|
||||
});
|
||||
}
|
||||
|
||||
state.ecs_world_mut().delete_entity(ecs_entity);
|
||||
// Generate requested chunks
|
||||
for key in requested_chunks {
|
||||
self.generate_chunk(key);
|
||||
}
|
||||
|
||||
Ok(frontend_events)
|
||||
@ -243,36 +320,19 @@ impl Server {
|
||||
|
||||
/// Sync client states with the most up to date information
|
||||
fn sync_clients(&mut self) {
|
||||
for (entity, &uid, &pos, &vel, &dir, update_kind) in (
|
||||
&self.state.ecs_world().entities(),
|
||||
&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>(),
|
||||
&mut self.state.ecs_world().write_storage::<comp::phys::UpdateKind>(),
|
||||
).join() {
|
||||
let msg = ServerMsg::EntityPhysics {
|
||||
uid,
|
||||
pos,
|
||||
vel,
|
||||
dir,
|
||||
};
|
||||
|
||||
// Sometimes we need to force updated (i.e: teleporting players). This involves sending
|
||||
// everyone, including the player themselves, of their new physics information.
|
||||
match update_kind {
|
||||
comp::phys::UpdateKind::Force => self.clients.notify_all(msg),
|
||||
comp::phys::UpdateKind::Passive => self.clients.notify_all_except(entity, msg),
|
||||
self.clients.notify_connected(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
|
||||
}
|
||||
|
||||
// Now that the update has occured, default to a passive update
|
||||
*update_kind = comp::phys::UpdateKind::Passive;
|
||||
pub fn generate_chunk(&mut self, key: Vec3<i32>) {
|
||||
if self.pending_chunks.insert(key) {
|
||||
let chunk_tx = self.chunk_tx.clone();
|
||||
self.thread_pool.execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.clients.notify_all(ServerMsg::Shutdown);
|
||||
self.clients.notify_connected(ServerMsg::Shutdown);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "voxygen"
|
||||
version = "0.1.0"
|
||||
name = "veloren-voxygen"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -24,27 +24,28 @@ impl Animation for RunAnimation {
|
||||
let wave_slow = (time as f32 * 6.0 + PI).sin();
|
||||
let wave_dip = (wave_slow.abs() - 0.5).abs();
|
||||
|
||||
skeleton.head.offset = Vec3::unit_z() * 13.0;
|
||||
skeleton.head.offset = Vec3::unit_z() * 13.0 / 11.0;
|
||||
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
|
||||
|
||||
skeleton.chest.offset = Vec3::unit_z() * 9.0;
|
||||
skeleton.chest.offset = Vec3::unit_z() * 9.0 / 11.0;
|
||||
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
|
||||
|
||||
skeleton.belt.offset = Vec3::unit_z() * 7.0;
|
||||
skeleton.belt.offset = Vec3::unit_z() * 7.0 / 11.0;
|
||||
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2);
|
||||
|
||||
skeleton.shorts.offset = Vec3::unit_z() * 4.0;
|
||||
skeleton.shorts.offset = Vec3::unit_z() * 4.0 / 11.0;
|
||||
skeleton.shorts.ori = Quaternion::rotation_z(wave * 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.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0) / 11.0;
|
||||
skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0) / 11.0;
|
||||
|
||||
skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0);
|
||||
skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0) / 11.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.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0) / 11.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);
|
||||
skeleton.back.scale = Vec3::one();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use crate::render::FigureBoneData;
|
||||
pub struct Bone {
|
||||
pub offset: Vec3<f32>,
|
||||
pub ori: Quaternion<f32>,
|
||||
pub scale: Vec3<f32>,
|
||||
}
|
||||
|
||||
impl Bone {
|
||||
@ -17,15 +18,16 @@ impl Bone {
|
||||
Self {
|
||||
offset: Vec3::zero(),
|
||||
ori: Quaternion::identity(),
|
||||
scale: Vec3::broadcast(1.0 / 11.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_base_matrix(&self) -> Mat4<f32> {
|
||||
Mat4::<f32>::translation_3d(self.offset) * Mat4::from(self.ori)
|
||||
Mat4::<f32>::translation_3d(self.offset) * Mat4::scaling_3d(self.scale) * Mat4::from(self.ori)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Skeleton {
|
||||
pub trait Skeleton: Send + Sync + 'static {
|
||||
fn compute_matrices(&self) -> [FigureBoneData; 16];
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,10 @@ use crate::{
|
||||
GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::clock::Clock;
|
||||
use common::{
|
||||
comp,
|
||||
clock::Clock,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||
use vek::*;
|
||||
@ -55,7 +58,7 @@ impl PlayState for MainMenuState {
|
||||
|
||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||
|
||||
// Maintain the UI
|
||||
// Maintain the UI (TODO: Maybe clean this up a little to avoid rightward drift?)
|
||||
for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) {
|
||||
match event {
|
||||
MainMenuEvent::LoginAttempt{ username, server_address } => {
|
||||
@ -67,12 +70,12 @@ impl PlayState for MainMenuState {
|
||||
Ok(mut socket_adders) => {
|
||||
while let Some(socket_addr) = socket_adders.next() {
|
||||
// TODO: handle error
|
||||
match Client::new(socket_addr) {
|
||||
match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test()), 300) {
|
||||
Ok(client) => {
|
||||
return PlayStateResult::Push(
|
||||
Box::new(CharSelectionState::new(
|
||||
&mut global_state.window,
|
||||
std::rc::Rc::new(std::cell::RefCell::new(client.with_test_state())) // <--- TODO: Remove this
|
||||
std::rc::Rc::new(std::cell::RefCell::new(client)) // <--- TODO: Remove this
|
||||
))
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,14 @@
|
||||
use specs::{Component, VecStorage};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
f32,
|
||||
};
|
||||
use specs::{Entity as EcsEntity, Component, VecStorage, Join};
|
||||
use vek::*;
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp,
|
||||
figure::Segment,
|
||||
};
|
||||
use crate::{
|
||||
Error,
|
||||
render::{
|
||||
@ -12,42 +21,50 @@ use crate::{
|
||||
FigureBoneData,
|
||||
FigureLocals,
|
||||
},
|
||||
anim::Skeleton,
|
||||
anim::{
|
||||
Animation,
|
||||
Skeleton,
|
||||
character::{
|
||||
CharacterSkeleton,
|
||||
RunAnimation,
|
||||
},
|
||||
},
|
||||
mesh::Meshable,
|
||||
};
|
||||
|
||||
pub struct Figure<S: Skeleton> {
|
||||
// GPU data
|
||||
model: Model<FigurePipeline>,
|
||||
bone_consts: Consts<FigureBoneData>,
|
||||
locals: Consts<FigureLocals>,
|
||||
|
||||
// CPU data
|
||||
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
|
||||
pub skeleton: S,
|
||||
pub struct Figures {
|
||||
test_model: Model<FigurePipeline>,
|
||||
states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
|
||||
}
|
||||
|
||||
impl<S: Skeleton> Figure<S> {
|
||||
pub fn new(
|
||||
renderer: &mut Renderer,
|
||||
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
|
||||
skeleton: S,
|
||||
) -> Result<Self, Error> {
|
||||
let mut this = Self {
|
||||
model: renderer.create_model(&Mesh::new())?,
|
||||
bone_consts: renderer.create_consts(&skeleton.compute_matrices())?,
|
||||
locals: renderer.create_consts(&[FigureLocals::default()])?,
|
||||
|
||||
bone_meshes,
|
||||
skeleton,
|
||||
};
|
||||
this.update_model(renderer)?;
|
||||
Ok(this)
|
||||
impl Figures {
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
// TODO: Make a proper asset loading system
|
||||
fn load_segment(filename: &'static str) -> Segment {
|
||||
Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/voxel/").to_string() + filename)).unwrap())
|
||||
}
|
||||
|
||||
pub fn update_model(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
|
||||
let mut mesh = Mesh::new();
|
||||
let bone_meshes = [
|
||||
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -6.5, -6.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))),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
];
|
||||
|
||||
self.bone_meshes
|
||||
let mut mesh = Mesh::new();
|
||||
bone_meshes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
|
||||
@ -55,39 +72,69 @@ impl<S: Skeleton> Figure<S> {
|
||||
mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8))
|
||||
});
|
||||
|
||||
self.model = renderer.create_model(&mesh)?;
|
||||
Ok(())
|
||||
Self {
|
||||
test_model: renderer.create_model(&mesh).unwrap(),
|
||||
states: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_skeleton(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
|
||||
renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())?;
|
||||
Ok(())
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) {
|
||||
let time = client.state().get_time();
|
||||
let ecs = client.state_mut().ecs_mut().internal_mut();
|
||||
for (entity, pos, dir, character) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::phys::Pos>(),
|
||||
&ecs.read_storage::<comp::phys::Dir>(),
|
||||
&ecs.read_storage::<comp::Character>(),
|
||||
).join() {
|
||||
let state = self.states
|
||||
.entry(entity)
|
||||
.or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new()));
|
||||
|
||||
RunAnimation::update_skeleton(&mut state.skeleton, time);
|
||||
|
||||
state.update(renderer, pos.0, dir.0);
|
||||
}
|
||||
|
||||
pub fn update_locals(&mut self, renderer: &mut Renderer, locals: FigureLocals) -> Result<(), Error> {
|
||||
renderer.update_consts(&mut self.locals, &[locals])?;
|
||||
Ok(())
|
||||
self.states.retain(|entity, _| ecs.entities().is_alive(*entity));
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
|
||||
pub fn render(&self, renderer: &mut Renderer, client: &Client, globals: &Consts<Globals>) {
|
||||
for state in self.states.values() {
|
||||
renderer.render_figure(
|
||||
&self.model,
|
||||
&self.test_model,
|
||||
globals,
|
||||
&self.locals,
|
||||
&self.bone_consts,
|
||||
&state.locals,
|
||||
&state.bone_consts,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Figure<S: Skeleton> {
|
||||
pub struct FigureState<S: Skeleton> {
|
||||
bone_consts: Consts<FigureBoneData>,
|
||||
locals: Consts<FigureLocals>,
|
||||
skeleton: S,
|
||||
}
|
||||
|
||||
impl<S: Skeleton> Component for Figure<S> {
|
||||
type Storage = VecStorage<Self>;
|
||||
impl<S: Skeleton> FigureState<S> {
|
||||
pub fn new(renderer: &mut Renderer, skeleton: S) -> Self {
|
||||
Self {
|
||||
bone_consts: renderer.create_consts(&skeleton.compute_matrices()).unwrap(),
|
||||
locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(),
|
||||
skeleton,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, renderer: &mut Renderer, pos: Vec3<f32>, dir: Vec3<f32>) {
|
||||
let mat =
|
||||
Mat4::<f32>::identity() *
|
||||
Mat4::translation_3d(pos) *
|
||||
Mat4::rotation_z(dir.y.atan2(dir.x) + f32::consts::PI / 2.0);
|
||||
|
||||
let locals = FigureLocals::new(mat);
|
||||
renderer.update_consts(&mut self.locals, &[locals]).unwrap();
|
||||
|
||||
renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()).unwrap();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -29,7 +29,7 @@ use crate::{
|
||||
};
|
||||
use self::{
|
||||
camera::Camera,
|
||||
figure::Figure,
|
||||
figure::Figures,
|
||||
terrain::Terrain,
|
||||
};
|
||||
|
||||
@ -47,13 +47,7 @@ pub struct Scene {
|
||||
|
||||
skybox: Skybox,
|
||||
terrain: Terrain,
|
||||
|
||||
test_figure: Figure<CharacterSkeleton>,
|
||||
}
|
||||
|
||||
// TODO: Make a proper asset loading system
|
||||
fn load_segment(filename: &'static str) -> Segment {
|
||||
Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/voxel/").to_string() + filename)).unwrap())
|
||||
figures: Figures,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
@ -74,30 +68,7 @@ impl Scene {
|
||||
.unwrap(),
|
||||
},
|
||||
terrain: Terrain::new(),
|
||||
|
||||
test_figure: Figure::new(
|
||||
renderer,
|
||||
[
|
||||
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -6.5, -6.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))),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
],
|
||||
CharacterSkeleton::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
figures: Figures::new(renderer),
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,13 +104,14 @@ 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) {
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) {
|
||||
// Get player position
|
||||
let player_pos = client
|
||||
.player()
|
||||
.and_then(|ent| client
|
||||
.state()
|
||||
.ecs_world()
|
||||
.ecs()
|
||||
.internal()
|
||||
.read_storage::<comp::phys::Pos>()
|
||||
.get(ent)
|
||||
.map(|pos| pos.0)
|
||||
@ -164,24 +136,13 @@ impl Scene {
|
||||
)])
|
||||
.expect("Failed to update global constants");
|
||||
|
||||
// Maintain the terrain
|
||||
// Maintain the terrain and figures
|
||||
self.terrain.maintain(renderer, client);
|
||||
|
||||
// TODO: Don't do this here
|
||||
RunAnimation::update_skeleton(
|
||||
&mut self.test_figure.skeleton,
|
||||
client.state().get_time(),
|
||||
);
|
||||
|
||||
// Calculate player model matrix
|
||||
let model_mat = Mat4::<f32>::translation_3d(player_pos);
|
||||
|
||||
self.test_figure.update_locals(renderer, FigureLocals::new(model_mat)).unwrap();
|
||||
self.test_figure.update_skeleton(renderer).unwrap();
|
||||
self.figures.maintain(renderer, client);
|
||||
}
|
||||
|
||||
/// Render the scene using the provided `Renderer`
|
||||
pub fn render_to(&self, renderer: &mut Renderer) {
|
||||
pub fn render(&self, renderer: &mut Renderer, client: &Client) {
|
||||
// Render the skybox first (it appears over everything else so must be rendered first)
|
||||
renderer.render_skybox(
|
||||
&self.skybox.model,
|
||||
@ -189,10 +150,8 @@ impl Scene {
|
||||
&self.skybox.locals,
|
||||
);
|
||||
|
||||
// Render terrain
|
||||
// Render terrain and figures
|
||||
self.terrain.render(renderer, &self.globals);
|
||||
|
||||
// Render the test figure
|
||||
self.test_figure.render(renderer, &self.globals);
|
||||
self.figures.render(renderer, client, &self.globals);
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ impl SessionState {
|
||||
renderer.clear(BG_COLOR);
|
||||
|
||||
// Render the screen using the global renderer
|
||||
self.scene.render_to(renderer);
|
||||
self.scene.render(renderer, &self.client.borrow());
|
||||
// Draw the UI to the screen
|
||||
self.hud.render(renderer);
|
||||
|
||||
@ -105,6 +105,7 @@ impl PlayState for SessionState {
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Load a few chunks TODO: Remove this
|
||||
/*
|
||||
for x in -6..7 {
|
||||
for y in -6..7 {
|
||||
for z in -1..2 {
|
||||
@ -112,6 +113,7 @@ impl PlayState for SessionState {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Game loop
|
||||
loop {
|
||||
@ -155,7 +157,7 @@ impl PlayState for SessionState {
|
||||
.expect("Failed to tick the scene");
|
||||
|
||||
// Maintain the scene
|
||||
self.scene.maintain(global_state.window.renderer_mut(), &self.client.borrow());
|
||||
self.scene.maintain(global_state.window.renderer_mut(), &mut self.client.borrow_mut());
|
||||
// Maintain the UI
|
||||
for event in self.hud.maintain(global_state.window.renderer_mut()) {
|
||||
match event {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "veloren-world"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -24,7 +24,7 @@ impl World {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn generate_chunk(&self, chunk_pos: Vec3<i32>) -> TerrainChunk {
|
||||
pub fn generate_chunk(chunk_pos: Vec3<i32>) -> TerrainChunk {
|
||||
// TODO: This is all test code, remove/improve this later
|
||||
|
||||
let mut chunk = TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void());
|
||||
|
Loading…
Reference in New Issue
Block a user