2019-11-04 00:57:36 +00:00
use super ::{
2019-11-29 06:04:37 +00:00
sentinel ::{ DeletedEntities , ReadTrackers , TrackedComps , TrackedResources } ,
2019-11-04 00:57:36 +00:00
SysTimer ,
} ;
2019-10-15 04:06:14 +00:00
use crate ::{
client ::{ Client , RegionSubscription } ,
Tick ,
} ;
use common ::{
comp ::{ CharacterState , ForceUpdate , Inventory , InventoryUpdate , Last , Ori , Pos , Vel } ,
msg ::ServerMsg ,
region ::{ Event as RegionEvent , RegionMap } ,
2019-11-24 20:12:03 +00:00
sync ::Uid ,
2019-10-15 04:06:14 +00:00
} ;
use specs ::{
2019-10-20 07:20:21 +00:00
Entities , Entity as EcsEntity , Join , Read , ReadExpect , ReadStorage , System , Write , WriteStorage ,
2019-10-15 04:06:14 +00:00
} ;
/// This system will send physics updates to the client
pub struct Sys ;
impl < ' a > System < ' a > for Sys {
type SystemData = (
Entities < ' a > ,
Read < ' a , Tick > ,
ReadExpect < ' a , RegionMap > ,
2019-10-20 07:20:21 +00:00
Write < ' a , SysTimer < Self > > ,
2019-10-15 04:06:14 +00:00
ReadStorage < ' a , Uid > ,
ReadStorage < ' a , Pos > ,
ReadStorage < ' a , Vel > ,
ReadStorage < ' a , Ori > ,
ReadStorage < ' a , CharacterState > ,
ReadStorage < ' a , Inventory > ,
ReadStorage < ' a , RegionSubscription > ,
WriteStorage < ' a , Last < Pos > > ,
WriteStorage < ' a , Last < Vel > > ,
WriteStorage < ' a , Last < Ori > > ,
WriteStorage < ' a , Last < CharacterState > > ,
WriteStorage < ' a , Client > ,
WriteStorage < ' a , ForceUpdate > ,
WriteStorage < ' a , InventoryUpdate > ,
2019-11-29 06:04:37 +00:00
Write < ' a , DeletedEntities > ,
2019-11-04 00:57:36 +00:00
TrackedComps < ' a > ,
ReadTrackers < ' a > ,
TrackedResources < ' a > ,
2019-10-15 04:06:14 +00:00
) ;
fn run (
& mut self ,
(
entities ,
tick ,
region_map ,
2019-10-20 07:20:21 +00:00
mut timer ,
2019-10-15 04:06:14 +00:00
uids ,
positions ,
velocities ,
orientations ,
character_states ,
inventories ,
subscriptions ,
mut last_pos ,
mut last_vel ,
mut last_ori ,
mut last_character_state ,
mut clients ,
mut force_updates ,
mut inventory_updates ,
2019-11-29 06:04:37 +00:00
mut deleted_entities ,
2019-11-04 00:57:36 +00:00
tracked_comps ,
2019-11-29 06:04:37 +00:00
trackers ,
2019-11-04 00:57:36 +00:00
tracked_resources ,
2019-10-15 04:06:14 +00:00
) : Self ::SystemData ,
) {
2019-10-20 07:20:21 +00:00
timer . start ( ) ;
2019-10-15 04:06:14 +00:00
let tick = tick . 0 ;
// To send entity updates
// 1. Iterate through regions
// 2. Iterate through region subscribers (ie clients)
// - Collect a list of entity ids for clients who are subscribed to this region (hash calc to check each)
// 3. Iterate through events from that region
2019-11-24 20:12:03 +00:00
// - For each entity entered event, iterate through the client list and check if they are subscribed to the source (hash calc per subscribed client per entity event), if not subscribed to the source send a entity creation message to that client
// - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity event)
2019-10-15 04:06:14 +00:00
// 4. Iterate through entities in that region
// 5. Inform clients of the component changes for that entity
// - Throttle update rate base on distance to each client
// Sync physics
// via iterating through regions
for ( key , region ) in region_map . iter ( ) {
2019-11-29 06:04:37 +00:00
// Assemble subscriber list for this region by iterating through clients and checking
// if they are subscribed to this region
2019-10-15 04:06:14 +00:00
let mut subscribers = ( & mut clients , & entities , & subscriptions , & positions )
. join ( )
. filter_map ( | ( client , entity , subscription , pos ) | {
if client . is_ingame ( ) & & subscription . regions . contains ( & key ) {
Some ( ( client , & subscription . regions , entity , * pos ) )
} else {
None
}
} )
. collect ::< Vec < _ > > ( ) ;
for event in region . events ( ) {
match event {
2019-10-25 05:35:15 +00:00
RegionEvent ::Entered ( id , maybe_key ) = > {
2019-11-29 06:04:37 +00:00
// Don't process newly created entities here (redundant network messages)
if trackers . uid . inserted ( ) . contains ( * id ) {
continue ;
}
2019-10-25 05:35:15 +00:00
let entity = entities . entity ( * id ) ;
if let Some ( ( uid , pos , vel , ori , character_state ) ) =
uids . get ( entity ) . and_then ( | uid | {
positions . get ( entity ) . map ( | pos | {
(
uid ,
pos ,
velocities . get ( entity ) ,
orientations . get ( entity ) ,
character_states . get ( entity ) ,
)
} )
} )
{
2019-11-04 00:57:36 +00:00
let create_msg = ServerMsg ::CreateEntity (
tracked_comps . create_entity_package ( entity ) ,
) ;
2019-10-26 02:00:30 +00:00
for ( client , regions , client_entity , _ ) in & mut subscribers {
2019-10-25 05:35:15 +00:00
if maybe_key
. as_ref ( )
. map ( | key | ! regions . contains ( key ) )
. unwrap_or ( true )
2019-11-04 00:57:36 +00:00
// Client doesn't need to know about itself
2019-10-26 02:00:30 +00:00
& & * client_entity ! = entity
2019-10-25 05:35:15 +00:00
{
2019-11-04 00:57:36 +00:00
client . notify ( create_msg . clone ( ) ) ;
2019-10-25 05:35:15 +00:00
send_initial_unsynced_components (
client ,
uid ,
pos ,
vel ,
ori ,
character_state ,
) ;
}
}
}
}
2019-10-15 04:06:14 +00:00
RegionEvent ::Left ( id , maybe_key ) = > {
// Lookup UID for entity
if let Some ( & uid ) = uids . get ( entities . entity ( * id ) ) {
for ( client , regions , _ , _ ) in & mut subscribers {
2019-10-25 05:35:15 +00:00
if maybe_key
2019-10-15 04:06:14 +00:00
. as_ref ( )
2019-10-25 05:35:15 +00:00
. map ( | key | ! regions . contains ( key ) )
. unwrap_or ( true )
2019-10-15 04:06:14 +00:00
{
client . notify ( ServerMsg ::DeleteEntity ( uid . into ( ) ) ) ;
}
}
}
}
}
}
2019-11-04 00:57:36 +00:00
// Sync tracked components
2019-11-29 06:04:37 +00:00
// Get deleted entities in this region from DeletedEntities
2019-11-04 00:57:36 +00:00
let sync_msg = ServerMsg ::EcsSync (
2019-11-29 06:04:37 +00:00
trackers . create_sync_package (
& tracked_comps ,
region . entities ( ) ,
deleted_entities
. take_deleted_in_region ( key )
. unwrap_or_else ( | | Vec ::new ( ) ) ,
) ,
2019-11-04 00:57:36 +00:00
) ;
for ( client , _ , _ , _ ) in & mut subscribers {
client . notify ( sync_msg . clone ( ) ) ;
}
2019-10-15 04:06:14 +00:00
2019-11-04 00:57:36 +00:00
let mut send_msg = | msg : ServerMsg ,
entity : EcsEntity ,
pos : Pos ,
force_update : Option < & ForceUpdate > ,
throttle : bool | {
for ( client , _ , client_entity , client_pos ) in & mut subscribers {
2019-11-29 06:04:37 +00:00
let update = if client_entity = = & entity {
// Don't send client physics updates about itself unless force update is set
force_update . is_some ( )
2019-11-04 00:57:36 +00:00
} else if ! throttle {
// Update rate not thottled by distance
true
} else {
// Throttle update rate based on distance to client
let distance_sq = client_pos . 0. distance_squared ( pos . 0 ) ;
// More entities farther away so checks start there
if distance_sq > 300.0 f32 . powi ( 2 ) {
( tick + entity . id ( ) as u64 ) % 32 = = 0
} else if distance_sq > 250.0 f32 . powi ( 2 ) {
( tick + entity . id ( ) as u64 ) % 16 = = 0
} else if distance_sq > 200.0 f32 . powi ( 2 ) {
( tick + entity . id ( ) as u64 ) % 8 = = 0
} else if distance_sq > 150.0 f32 . powi ( 2 ) {
( tick + entity . id ( ) as u64 ) % 4 = = 0
} else if distance_sq > 100.0 f32 . powi ( 2 ) {
( tick + entity . id ( ) as u64 ) % 2 = = 0
} else {
true // Closer than 100 blocks
2019-10-15 04:06:14 +00:00
}
2019-11-04 00:57:36 +00:00
} ;
if update {
client . notify ( msg . clone ( ) ) ;
2019-10-15 04:06:14 +00:00
}
2019-11-04 00:57:36 +00:00
}
} ;
2019-10-15 04:06:14 +00:00
2019-11-04 00:57:36 +00:00
// Sync physics components
2019-10-15 04:06:14 +00:00
for ( _ , entity , & uid , & pos , maybe_vel , maybe_ori , character_state , force_update ) in (
region . entities ( ) ,
& entities ,
& uids ,
& positions ,
velocities . maybe ( ) ,
orientations . maybe ( ) ,
character_states . maybe ( ) ,
force_updates . maybe ( ) ,
)
. join ( )
{
2019-10-25 05:35:15 +00:00
// TODO: An entity that stoppped moving on a tick that it wasn't sent to the player
// will never have it's position updated
2019-10-15 04:06:14 +00:00
if last_pos . get ( entity ) . map ( | & l | l . 0 ! = pos ) . unwrap_or ( true ) {
let _ = last_pos . insert ( entity , Last ( pos ) ) ;
send_msg (
ServerMsg ::EntityPos {
entity : uid . into ( ) ,
pos ,
} ,
entity ,
pos ,
force_update ,
true ,
) ;
}
if let Some ( & vel ) = maybe_vel {
if last_vel . get ( entity ) . map ( | & l | l . 0 ! = vel ) . unwrap_or ( true ) {
let _ = last_vel . insert ( entity , Last ( vel ) ) ;
send_msg (
ServerMsg ::EntityVel {
entity : uid . into ( ) ,
vel ,
} ,
entity ,
pos ,
force_update ,
true ,
) ;
}
}
if let Some ( & ori ) = maybe_ori {
if last_ori . get ( entity ) . map ( | & l | l . 0 ! = ori ) . unwrap_or ( true ) {
let _ = last_ori . insert ( entity , Last ( ori ) ) ;
send_msg (
ServerMsg ::EntityOri {
entity : uid . into ( ) ,
ori ,
} ,
entity ,
pos ,
force_update ,
true ,
) ;
}
}
if let Some ( & character_state ) = character_state {
if last_character_state
. get ( entity )
. map ( | & l | ! character_state . is_same_state ( & l . 0 ) )
. unwrap_or ( true )
{
let _ = last_character_state . insert ( entity , Last ( character_state ) ) ;
send_msg (
ServerMsg ::EntityCharacterState {
entity : uid . into ( ) ,
character_state ,
} ,
entity ,
pos ,
force_update ,
false ,
) ;
}
}
}
}
2019-11-29 06:04:37 +00:00
// Handle entity deletion in regions that don't exist in RegionMap (theoretically none)
for ( region_key , deleted ) in deleted_entities . take_remaining_deleted ( ) {
for client in
( & mut clients , & subscriptions )
. join ( )
. filter_map ( | ( client , subscription ) | {
if client . is_ingame ( ) & & subscription . regions . contains ( & region_key ) {
Some ( client )
} else {
None
}
} )
{
for uid in & deleted {
client . notify ( ServerMsg ::DeleteEntity ( * uid ) ) ;
}
}
}
2019-11-04 00:57:36 +00:00
// TODO: Sync clients that don't have a position?
2019-10-15 04:06:14 +00:00
// Sync inventories
for ( client , inventory , _ ) in ( & mut clients , & inventories , & inventory_updates ) . join ( ) {
client . notify ( ServerMsg ::InventoryUpdate ( inventory . clone ( ) ) ) ;
}
// Remove all force flags.
force_updates . clear ( ) ;
inventory_updates . clear ( ) ;
2019-10-20 07:20:21 +00:00
2019-11-04 00:57:36 +00:00
// Sync resources
// TODO: doesn't really belong in this system
let res_msg = ServerMsg ::EcsResSync ( tracked_resources . create_res_sync_package ( ) ) ;
for client in ( & mut clients ) . join ( ) {
client . notify ( res_msg . clone ( ) ) ;
}
2019-10-20 07:20:21 +00:00
timer . end ( ) ;
2019-10-15 04:06:14 +00:00
}
}
2019-10-25 05:35:15 +00:00
pub fn send_initial_unsynced_components (
client : & mut Client ,
uid : & Uid ,
pos : & Pos ,
vel : Option < & Vel > ,
ori : Option < & Ori > ,
character_state : Option < & CharacterState > ,
) {
let entity = ( * uid ) . into ( ) ;
client . notify ( ServerMsg ::EntityPos { entity , pos : * pos } ) ;
if let Some ( & vel ) = vel {
client . notify ( ServerMsg ::EntityVel { entity , vel } ) ;
}
if let Some ( & ori ) = ori {
client . notify ( ServerMsg ::EntityOri { entity , ori } ) ;
}
if let Some ( & character_state ) = character_state {
client . notify ( ServerMsg ::EntityCharacterState {
entity ,
character_state ,
} ) ;
}
}