Added networked entity updates, player entity allocation

Former-commit-id: b81d6e38a74f846d64c092d1422dc402f369100c
This commit is contained in:
Joshua Barretto 2019-03-05 00:00:11 +00:00
parent bb93f29522
commit a8b0039898
13 changed files with 172 additions and 97 deletions

View File

@ -19,8 +19,13 @@ fn main() {
.expect("Failed to create client instance");
loop {
let events = client.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick client");
let events = match client.tick(Input::default(), clock.get_last_delta()) {
Ok(events) => events,
Err(err) => {
println!("Error: {:?}", err);
break;
},
};
for event in events {
match event {

View File

@ -14,9 +14,12 @@ use std::{
};
use vek::*;
use threadpool;
use specs::Builder;
use specs::{
Builder,
saveload::MarkerAllocator,
};
use common::{
comp,
comp::{self, Uid},
state::State,
terrain::TerrainChunk,
net::PostBox,
@ -36,7 +39,7 @@ pub struct Client {
tick: u64,
state: State,
player: Option<EcsEntity>,
player: Option<Uid>,
// Testing
world: World,
@ -80,7 +83,6 @@ impl Client {
// TODO: Get rid of this
pub fn with_test_state(mut self) -> Self {
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
self.player = Some(self.state.new_test_player());
self
}
@ -99,15 +101,32 @@ impl Client {
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()
pub fn get_or_create_entity(&mut self, uid: Uid) -> EcsEntity {
// Find the ECS entity from its UID
let ecs_entity = self.state().ecs_world()
.read_resource::<comp::UidAllocator>()
.retrieve_entity_internal(uid.into());
// Return the entity or create it
if let Some(ecs_entity) = ecs_entity {
ecs_entity
} else {
let ecs_entity = self.state.ecs_world_mut().create_entity()
.build();
// Allocate it the specific UID given
self.state
.ecs_world_mut()
.write_resource::<comp::UidAllocator>()
.allocate(ecs_entity, Some(uid.into()));
ecs_entity
}
}
/// Get the player entity
#[allow(dead_code)]
pub fn player(&self) -> Option<EcsEntity> {
pub fn player(&self) -> Option<Uid> {
self.player
}
@ -145,12 +164,12 @@ impl Client {
frontend_events.append(&mut self.handle_new_messages()?);
// Step 3
if let Some(p) = self.player {
if let Some(ecs_entity) = self.player.and_then(|uid| self.state().get_entity(uid)) {
// TODO: remove this
const PLAYER_VELOCITY: f32 = 100.0;
// TODO: Set acceleration instead
self.state.write_component(p, 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)));
}
// Tick the client's LocalState (step 3)
@ -182,12 +201,16 @@ impl Client {
match msg {
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
ServerMsg::SetPlayerEntity(uid) => self.player = Some(uid),
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);
},
ServerMsg::EntityDeleted(uid) => {
self.state.delete_entity(uid);
},
}
}
} else if let Some(err) = self.postbox.status() {

View File

@ -6,7 +6,7 @@ edition = "2018"
[dependencies]
specs = { version = "0.14", features = ["serde"] }
shred = "0.7"
shred = { version = "0.7", features = ["nightly"] }
vek = { version = "0.9", features = ["serde"] }
dot_vox = "1.0"
threadpool = "1.7"

View File

@ -8,8 +8,10 @@ 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::<phys::Pos>();
ecs_world.register::<phys::Vel>();
ecs_world.register::<phys::Dir>();
ecs_world.register::<phys::UpdateKind>();
}

View File

@ -28,3 +28,14 @@ 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>;
}

View File

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

View File

@ -90,6 +90,14 @@ impl State {
self
}
/// 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
@ -103,16 +111,6 @@ impl State {
}
}
// TODO: Get rid of this
pub fn new_test_player(&mut self) -> EcsEntity {
self.ecs_world
.create_entity()
.with(comp::phys::Pos(Vec3::default()))
.with(comp::phys::Vel(Vec3::default()))
.with(comp::phys::Dir(Vec3::default()))
.build()
}
/// 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);

View File

@ -3,7 +3,7 @@ use log::info;
use server::{Input, Event, Server};
use common::clock::Clock;
const FPS: u64 = 60;
const TPS: u64 = 20;
fn main() {
// Init logging
@ -24,9 +24,9 @@ fn main() {
for event in events {
match event {
Event::ClientConnected { uid } => println!("Client {} connected!", uid),
Event::ClientDisconnected { uid } => println!("Client {} disconnected!", uid),
Event::Chat { uid, msg } => println!("[Client {}] {}", uid, msg),
Event::ClientConnected { uid } => info!("Client {} connected!", uid),
Event::ClientDisconnected { uid } => info!("Client {} disconnected!", uid),
Event::Chat { uid, msg } => info!("[Client {}] {}", uid, msg),
}
}
@ -34,6 +34,6 @@ fn main() {
server.cleanup();
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
clock.tick(Duration::from_millis(1000 / TPS));
}
}

View File

@ -35,7 +35,6 @@ impl Clients {
for client in &mut self.clients {
// Consume any errors, deal with them later
let _ = client.postbox.send(msg.clone());
println!("Sending message...");
}
}
@ -44,7 +43,6 @@ impl Clients {
if client.uid != uid {
// Consume any errors, deal with them later
let _ = client.postbox.send(msg.clone());
println!("Sending message...");
}
}
}

View File

@ -61,12 +61,8 @@ impl Server {
/// Create a new `Server`.
#[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,
state: State::new(),
world: World::new(),
postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?,
@ -93,6 +89,9 @@ impl Server {
.with(comp::phys::Pos(Vec3::zero()))
.with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y()))
// When the player is first created, force a physics notification to everyone
// including themselves.
.with(comp::phys::UpdateKind::Force)
}
/// Get a reference to the server's world.
@ -154,19 +153,21 @@ impl Server {
fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> {
let mut frontend_events = Vec::new();
for postbox in self.postoffice.new_connections() {
for mut postbox in self.postoffice.new_connections() {
let ecs_entity = self.build_player().build();
let uid = self.state.read_component(ecs_entity).unwrap();
frontend_events.push(Event::ClientConnected {
uid,
});
let _ = postbox.send(ServerMsg::SetPlayerEntity(uid));
self.clients.add(Client {
uid,
postbox,
last_ping: self.state.get_time(),
});
frontend_events.push(Event::ClientConnected {
uid,
});
}
Ok(frontend_events)
@ -178,6 +179,7 @@ impl Server {
let state = &mut self.state;
let mut new_chat_msgs = Vec::new();
let mut disconnected_clients = Vec::new();
self.clients.remove_if(|client| {
let mut disconnected = false;
@ -202,10 +204,7 @@ impl Server {
}
if disconnected {
state.delete_entity(client.uid);
frontend_events.push(Event::ClientDisconnected {
uid: client.uid,
});
disconnected_clients.push(client.uid);
true
} else {
false
@ -222,23 +221,45 @@ impl Server {
});
}
// Handle client disconnects
for uid in disconnected_clients {
self.clients.notify_all(ServerMsg::EntityDeleted(uid));
frontend_events.push(Event::ClientDisconnected {
uid,
});
state.delete_entity(uid);
}
Ok(frontend_events)
}
/// Sync client states with the most up to date information
fn sync_clients(&mut self) {
for (&uid, &pos, &vel, &dir) in (
for (&uid, &pos, &vel, &dir, update_kind) 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>(),
&mut self.state.ecs_world().write_storage::<comp::phys::UpdateKind>(),
).join() {
self.clients.notify_all_except(uid, ServerMsg::EntityPhysics {
uid: uid.into(),
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(uid, msg),
}
// Now that the update has occured, default to a passive update
*update_kind = comp::phys::UpdateKind::Passive;
}
}
}

View File

@ -15,10 +15,10 @@ use super::{
pub struct CharacterSkeleton {
head: Bone,
chest: Bone,
bl_foot: Bone,
br_foot: Bone,
r_hand: Bone,
belt: Bone,
shorts: Bone,
l_hand: Bone,
r_hand: Bone,
l_foot: Bone,
r_foot: Bone,
back: Bone,
@ -29,10 +29,10 @@ impl CharacterSkeleton {
Self {
head: Bone::default(),
chest: Bone::default(),
br_foot: Bone::default(),
bl_foot: Bone::default(),
r_hand: Bone::default(),
belt: Bone::default(),
shorts: Bone::default(),
l_hand: Bone::default(),
r_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.bl_foot.compute_base_matrix()),
FigureBoneData::new(self.br_foot.compute_base_matrix()),
FigureBoneData::new(self.r_hand.compute_base_matrix()),
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.r_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,34 +21,30 @@ 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::new(0.0, 0.0, 0.0);
skeleton.head.ori = Quaternion::rotation_x(0.0);
skeleton.head.offset = Vec3::unit_z() * 13.0;
skeleton.head.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.chest.offset = Vec3::unit_z() * 9.0;
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
//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.belt.offset = Vec3::unit_z() * 7.0;
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2);
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.shorts.offset = Vec3::unit_z() * 4.0;
skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1);
//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_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_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

@ -2,15 +2,13 @@ pub mod camera;
pub mod figure;
pub mod terrain;
// Library
use vek::*;
use dot_vox;
// Project
use common::figure::Segment;
use common::{
comp,
figure::Segment,
};
use client::Client;
// Crate
use crate::{
render::{
Consts,
@ -29,8 +27,6 @@ use crate::{
character::{CharacterSkeleton, RunAnimation},
},
};
// Local
use self::{
camera::Camera,
figure::Figure,
@ -82,15 +78,15 @@ impl Scene {
test_figure: Figure::new(
renderer,
[
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,
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))),
None,
None,
None,
@ -138,6 +134,22 @@ 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().and_then(|uid| client.state().get_entity(uid)) {
Some(ecs_entity) => {
client
.state()
.ecs_world()
.read_storage::<comp::phys::Pos>()
.get(ecs_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();
@ -161,7 +173,11 @@ impl Scene {
&mut self.test_figure.skeleton,
client.state().get_time(),
);
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
// 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();
}