From 99a881f3492deca4a060b35c82d72431392f1fdd Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 11 Nov 2020 23:59:09 +0000 Subject: [PATCH] Added entity simulation to rtsim, reification, assimilation --- Cargo.lock | 2 + assets/voxygen/shaders/fluid-frag/cheap.glsl | 4 +- common/src/event.rs | 11 +- common/src/lib.rs | 1 + common/src/rtsim.rs | 15 +++ common/src/terrain/mod.rs | 26 ++++- server/Cargo.toml | 2 + server/src/events/entity_creation.rs | 8 ++ server/src/events/entity_manipulation.rs | 18 +++- server/src/events/mod.rs | 3 +- server/src/lib.rs | 15 ++- server/src/rtsim/load_chunks.rs | 2 +- server/src/rtsim/mod.rs | 102 ++++++++++++++----- server/src/rtsim/tick.rs | 76 ++++++++++++++ server/src/rtsim/unload_chunks.rs | 21 +--- server/src/sys/terrain.rs | 1 + 16 files changed, 250 insertions(+), 57 deletions(-) create mode 100644 common/src/rtsim.rs create mode 100644 server/src/rtsim/tick.rs diff --git a/Cargo.lock b/Cargo.lock index a53854a3ea..591e2f7ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5402,10 +5402,12 @@ dependencies = [ "portpicker", "prometheus", "rand 0.7.3", + "rand_chacha 0.2.2", "ron", "scan_fmt", "serde", "serde_json", + "slab", "specs", "specs-idvs", "tiny_http", diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index 5130760f3d..91828bf179 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -179,12 +179,12 @@ void main() { // float reflected_light_point = /*length*/(diffuse_light_point.r) + f_light * point_shadow; // reflected_light += k_d * (diffuse_light_point + f_light * point_shadow * shade_frac) + specular_light_point; - float passthrough = /*pow(*/dot(cam_norm, -cam_to_frag/*view_dir*/)/*, 0.5)*/; + float passthrough = clamp(dot(cam_norm, -cam_to_frag) * 1.0 - 0.2, 0, 1); float min_refl = min(emitted_light.r, min(emitted_light.g, emitted_light.b)); vec3 surf_color = illuminate(max_light, view_dir, water_color * /* fog_color * */emitted_light, /*surf_color * */water_color * reflected_light); // vec4 color = vec4(surf_color, passthrough * 1.0 / (1.0 + min_refl));// * (1.0 - /*log(1.0 + cam_attenuation)*//*cam_attenuation*/1.0 / (2.0 - log_cam))); - vec4 color = mix(vec4(surf_color, 1.0), vec4(surf_color, 1.0 / (1.0 + /*diffuse_light*//*(f_light * point_shadow + point_light)*//*4.0 * reflected_light_point*/min_refl/* * 0.25*/)), passthrough); + vec4 color = vec4(surf_color, (1.0 - passthrough) * 1.0 / (1.0 + min_refl)); tgt_color = color; } diff --git a/common/src/event.rs b/common/src/event.rs index 5d5c9a4444..a47e9950ee 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,4 +1,11 @@ -use crate::{character::CharacterId, comp, sync::Uid, util::Dir, Explosion}; +use crate::{ + character::CharacterId, + sync::Uid, + util::Dir, + comp, + Explosion, + rtsim::RtSimEntity, +}; use comp::{ item::{Item, Reagent}, Ori, Pos, @@ -96,6 +103,7 @@ pub enum ServerEvent { ExitIngame { entity: EcsEntity, }, + // TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type? CreateNpc { pos: comp::Pos, stats: comp::Stats, @@ -107,6 +115,7 @@ pub enum ServerEvent { scale: comp::Scale, home_chunk: Option, drop_item: Option, + rtsim_entity: Option, }, CreateWaypoint(Vec3), ClientDisconnect(EcsEntity), diff --git a/common/src/lib.rs b/common/src/lib.rs index 25ea1b3669..b7ba8f0e4b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -40,6 +40,7 @@ pub mod path; pub mod ray; pub mod recipe; pub mod region; +pub mod rtsim; pub mod spiral; pub mod state; pub mod states; diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs new file mode 100644 index 0000000000..9c4dd056e8 --- /dev/null +++ b/common/src/rtsim.rs @@ -0,0 +1,15 @@ +// We'd like to not have this file in `common`, but sadly there are +// things in `common` that require it (currently, `ServerEvent`). When +// possible, this should be moved to the `rtsim` module in `server`. + +use specs_idvs::IdvStorage; +use specs::Component; + +pub type RtSimId = usize; + +#[derive(Copy, Clone, Debug)] +pub struct RtSimEntity(pub RtSimId); + +impl Component for RtSimEntity { + type Storage = IdvStorage; +} diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 4832a3b53e..878eb9648b 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -18,7 +18,10 @@ pub use self::{ use roots::find_roots_cubic; use serde::{Deserialize, Serialize}; -use crate::{vol::RectVolSize, volumes::vol_grid_2d::VolGrid2d}; +use crate::{ + vol::{RectVolSize, ReadVol}, + volumes::vol_grid_2d::VolGrid2d, +}; use vek::*; // TerrainChunkSize @@ -106,6 +109,27 @@ impl TerrainChunkMeta { pub type TerrainChunk = chonk::Chonk; pub type TerrainGrid = VolGrid2d; +impl TerrainGrid { + /// Find a location suitable for spawning an entity near the given + /// position (but in the same chunk). + pub fn find_space(&self, pos: Vec3) -> Vec3 { + let mut z_diff = 0; + for _ in 0..128 { + let test_pos = pos + Vec3::unit_z() * z_diff; + if (0..2) + .all(|z| self + .get(test_pos + Vec3::unit_z() * z) + .map(|b| !b.is_solid()) + .unwrap_or(true)) + { + return test_pos; + } + z_diff = -z_diff + if z_diff <= 0 { 1 } else { 0 }; + } + pos + } +} + // Terrain helper functions used across multiple crates. /// Computes the position Vec2 of a SimChunk from an index, where the index was diff --git a/server/Cargo.toml b/server/Cargo.toml index 3a7172e43f..1b7f53d7e5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -43,3 +43,5 @@ libsqlite3-sys = { version = "0.18", features = ["bundled"] } diesel = { version = "1.4.3", features = ["sqlite"] } diesel_migrations = "1.4.0" dotenv = "0.15.0" +slab = "0.4" +rand_chacha = "0.2" diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index e5cd9e7bfb..b02ca5629b 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -7,6 +7,7 @@ use common::{ }, outcome::Outcome, util::Dir, + rtsim::RtSimEntity, }; use comp::group; use specs::{Builder, Entity as EcsEntity, WorldExt}; @@ -50,6 +51,7 @@ pub fn handle_create_npc( scale: Scale, drop_item: Option, home_chunk: Option, + rtsim_entity: Option, ) { let group = match alignment { Alignment::Wild => None, @@ -90,6 +92,12 @@ pub fn handle_create_npc( entity }; + let entity = if let Some(rtsim_entity) = rtsim_entity { + entity.with(rtsim_entity) + } else { + entity + }; + entity.build(); } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8981f8414b..3bc0049c1e 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -2,6 +2,7 @@ use crate::{ client::Client, comp::{biped_large, quadruped_medium, quadruped_small, PhysicsState}, Server, SpawnPoint, StateExt, + rtsim::RtSim, }; use common::{ assets::Asset, @@ -20,6 +21,7 @@ use common::{ terrain::{Block, TerrainGrid}, vol::ReadVol, Damage, DamageSource, Explosion, GroupTarget, RadiusEffect, + rtsim::RtSimEntity, }; use comp::item::Reagent; use rand::prelude::*; @@ -308,7 +310,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc } })(); - if state + let should_delete = if state .ecs() .write_storage::() .get_mut(entity) @@ -339,6 +341,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc .ecs() .write_storage::() .insert(entity, comp::CharacterState::default()); + + false } else if state.ecs().read_storage::().contains(entity) { use specs::Builder; @@ -452,10 +456,16 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc ) } - let _ = state - .delete_entity_recorded(entity) - .map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity")); + true } else { + true + }; + + if should_delete { + if let Some(rtsim_entity) = state.ecs().read_storage::().get(entity).copied() { + state.ecs().write_resource::().destroy_entity(rtsim_entity.0); + } + let _ = state .delete_entity_recorded(entity) .map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity")); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index afac60a788..e4ea598457 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -118,9 +118,10 @@ impl Server { scale, home_chunk, drop_item, + rtsim_entity, } => handle_create_npc( self, pos, stats, health, loadout, body, agent, alignment, scale, drop_item, - home_chunk, + home_chunk, rtsim_entity, ), ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos), ServerEvent::ClientDisconnect(entity) => { diff --git a/server/src/lib.rs b/server/src/lib.rs index 31033de097..ff684b88ba 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -44,6 +44,7 @@ use crate::{ presence::{Presence, RegionSubscription}, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, + rtsim::RtSim, }; use common::{ assets::Asset, @@ -58,7 +59,8 @@ use common::{ state::{State, TimeOfDay}, sync::WorldSyncExt, terrain::TerrainChunkSize, - vol::RectVolSize, + vol::{ReadVol, RectVolSize}, + rtsim::RtSimEntity, }; use futures_executor::block_on; use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics}; @@ -321,6 +323,10 @@ impl Server { // set the spawn point we calculated above state.ecs_mut().insert(SpawnPoint(spawn_point)); + // Insert the world into the ECS (todo: Maybe not an Arc?) + let world = Arc::new(world); + state.ecs_mut().insert(world.clone()); + // Set starting time for the server. state.ecs_mut().write_resource::().0 = settings.start_time; @@ -359,7 +365,7 @@ impl Server { let this = Self { state, - world: Arc::new(world), + world, index, map, @@ -550,6 +556,11 @@ impl Server { }; for entity in to_delete { + // Assimilate entities that are part of the real-time world simulation + if let Some(rtsim_entity) = self.state.ecs().read_storage::().get(entity).copied() { + self.state.ecs().write_resource::().assimilate_entity(rtsim_entity.0); + } + if let Err(e) = self.state.delete_entity_recorded(entity) { error!(?e, "Failed to delete agent outside the terrain"); } diff --git a/server/src/rtsim/load_chunks.rs b/server/src/rtsim/load_chunks.rs index 98d236b062..0007142874 100644 --- a/server/src/rtsim/load_chunks.rs +++ b/server/src/rtsim/load_chunks.rs @@ -10,7 +10,7 @@ impl<'a> System<'a> for Sys { ); fn run(&mut self, (server_event_bus, mut rtsim): Self::SystemData) { - for chunk in std::mem::take(&mut rtsim.chunks_to_load) { + for chunk in std::mem::take(&mut rtsim.world.chunks_to_load) { // TODO } } diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index e307a10fd6..2cde2b8b0f 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -1,55 +1,86 @@ mod load_chunks; mod unload_chunks; +mod tick; use vek::*; use world::util::Grid; -use common::state::State; -use specs::{DispatcherBuilder, Component, WorldExt}; +use common::{ + state::State, + terrain::TerrainChunk, + rtsim::{RtSimEntity, RtSimId}, + vol::RectRasterableVol, +}; +use specs::{DispatcherBuilder, WorldExt}; use specs_idvs::IdvStorage; - -type EntityId = u64; +use slab::Slab; +use rand::prelude::*; pub struct RtSim { - chunks: Grid, - chunks_to_load: Vec>, - chunks_to_unload: Vec>, + world: RtWorld, + entities: Slab, } impl RtSim { pub fn new(world_chunk_size: Vec2) -> Self { Self { - chunks: Grid::populate_from(world_chunk_size.map(|e| e as i32), |_| Chunk { - is_loaded: false, - }), - chunks_to_load: Vec::new(), - chunks_to_unload: Vec::new(), + world: RtWorld { + chunks: Grid::populate_from(world_chunk_size.map(|e| e as i32), |_| Chunk { + is_loaded: false, + }), + chunks_to_load: Vec::new(), + chunks_to_unload: Vec::new(), + }, + entities: Slab::new(), } } pub fn hook_load_chunk(&mut self, key: Vec2) { - if let Some(chunk) = self.chunks.get_mut(key) { + if let Some(chunk) = self.world.chunks.get_mut(key) { if !chunk.is_loaded { chunk.is_loaded = true; - self.chunks_to_load.push(key); + self.world.chunks_to_load.push(key); } } } pub fn hook_unload_chunk(&mut self, key: Vec2) { - if let Some(chunk) = self.chunks.get_mut(key) { + if let Some(chunk) = self.world.chunks.get_mut(key) { if chunk.is_loaded { chunk.is_loaded = false; - self.chunks_to_unload.push(key); + self.world.chunks_to_unload.push(key); } } } - pub fn assimilate_entity(&mut self, entity: EntityId) { - // TODO + pub fn assimilate_entity(&mut self, entity: RtSimId) { + tracing::info!("Assimilated rtsim entity {}", entity); + self.entities.get_mut(entity).map(|e| e.is_loaded = false); } - pub fn update_entity(&mut self, entity: EntityId, pos: Vec3) { - // TODO + pub fn reify_entity(&mut self, entity: RtSimId) { + tracing::info!("Reified rtsim entity {}", entity); + self.entities.get_mut(entity).map(|e| e.is_loaded = true); + } + + pub fn update_entity(&mut self, entity: RtSimId, pos: Vec3) { + self.entities.get_mut(entity).map(|e| e.pos = pos); + } + + pub fn destroy_entity(&mut self, entity: RtSimId) { + tracing::info!("Destroyed rtsim entity {}", entity); + self.entities.remove(entity); + } +} + +pub struct RtWorld { + chunks: Grid, + chunks_to_load: Vec>, + chunks_to_unload: Vec>, +} + +impl RtWorld { + pub fn chunk_at(&self, pos: Vec2) -> Option<&Chunk> { + self.chunks.get(pos.map2(TerrainChunk::RECT_SIZE, |e, sz| (e.floor() as i32).div_euclid(sz as i32))) } } @@ -57,22 +88,41 @@ pub struct Chunk { is_loaded: bool, } -pub struct RtSimEntity(EntityId); - -impl Component for RtSimEntity { - type Storage = IdvStorage; +pub struct Entity { + is_loaded: bool, + pos: Vec3, + seed: u32, } const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys"; const UNLOAD_CHUNK_SYS: &str = "rtsim_unload_chunk_sys"; +const TICK_SYS: &str = "rtsim_tick_sys"; pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { - dispatch_builder.add(load_chunks::Sys, LOAD_CHUNK_SYS, &[]); dispatch_builder.add(unload_chunks::Sys, UNLOAD_CHUNK_SYS, &[]); + dispatch_builder.add(load_chunks::Sys, LOAD_CHUNK_SYS, &[UNLOAD_CHUNK_SYS]); + dispatch_builder.add(tick::Sys, TICK_SYS, &[LOAD_CHUNK_SYS, UNLOAD_CHUNK_SYS]); } pub fn init(state: &mut State, world: &world::World) { - state.ecs_mut().insert(RtSim::new(world.sim().get_size())); + let mut rtsim = RtSim::new(world.sim().get_size()); + + for _ in 0..10 { + let pos = Vec2::new( + thread_rng().gen_range(0, rtsim.world.chunks.size().x * TerrainChunk::RECT_SIZE.x as i32), + thread_rng().gen_range(0, rtsim.world.chunks.size().y * TerrainChunk::RECT_SIZE.y as i32), + ); + + let id = rtsim.entities.insert(Entity { + is_loaded: false, + pos: Vec3::from(pos.map(|e| e as f32)), + seed: thread_rng().gen(), + }); + + tracing::info!("Spawned rtsim NPC {} at {:?}", id, pos); + } + + state.ecs_mut().insert(rtsim); state.ecs_mut().register::(); tracing::info!("Initiated real-time world simulation"); } diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs new file mode 100644 index 0000000000..65e26706ea --- /dev/null +++ b/server/src/rtsim/tick.rs @@ -0,0 +1,76 @@ +use super::*; +use common::{ + event::{EventBus, ServerEvent}, + terrain::TerrainGrid, + comp, +}; +use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, ReadExpect}; +use rand_chacha::ChaChaRng; +use std::sync::Arc; + +pub struct Sys; +impl<'a> System<'a> for Sys { + type SystemData = ( + Read<'a, EventBus>, + WriteExpect<'a, RtSim>, + ReadExpect<'a, TerrainGrid>, + ReadExpect<'a, Arc>, + ); + + fn run( + &mut self, + ( + server_event_bus, + mut rtsim, + terrain, + world, + ): Self::SystemData, + ) { + let rtsim = &mut *rtsim; + + // TODO: don't update all of them each tick + let mut to_reify = Vec::new(); + for (id, entity) in rtsim.entities.iter_mut() { + if entity.is_loaded { + continue; + } else if rtsim.world.chunk_at(entity.pos.xy()).map(|c| c.is_loaded).unwrap_or(false) { + to_reify.push(id); + } + + if let Some(chunk) = world.sim().get_wpos(entity.pos.xy().map(|e| e.floor() as i32)) { + entity.pos.z = chunk.alt; + } + } + + let mut server_emitter = server_event_bus.emitter(); + for id in to_reify { + rtsim.reify_entity(id); + let entity = &rtsim.entities[id]; + let mut rng = ChaChaRng::from_seed([ + entity.seed.to_le_bytes()[0], + entity.seed.to_le_bytes()[1], + entity.seed.to_le_bytes()[2], + entity.seed.to_le_bytes()[3], + 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]); + let species = *(&comp::humanoid::ALL_SPECIES).choose(&mut rng).unwrap(); + let body = comp::humanoid::Body::random_with(&mut rng, &species).into(); + server_emitter.emit(ServerEvent::CreateNpc { + pos: comp::Pos(terrain.find_space(entity.pos.map(|e| e.floor() as i32)).map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)), + stats: comp::Stats::new("Rtsim Entity".to_string(), body), + health: comp::Health::new(body, 10), + loadout: comp::Loadout::default(), + body, + agent: None, + alignment: comp::Alignment::Npc, + scale: comp::Scale(1.0), + drop_item: None, + home_chunk: None, + rtsim_entity: Some(RtSimEntity(id)), + }); + } + } +} diff --git a/server/src/rtsim/unload_chunks.rs b/server/src/rtsim/unload_chunks.rs index 8aee4b0818..7b7fd6dcff 100644 --- a/server/src/rtsim/unload_chunks.rs +++ b/server/src/rtsim/unload_chunks.rs @@ -28,27 +28,10 @@ impl<'a> System<'a> for Sys { positions, ): Self::SystemData, ) { - let chunks = std::mem::take(&mut rtsim.chunks_to_unload); - - for (entity, rtsim_entity, pos) in ( - &entities, - &rtsim_entities, - &positions, - ).join() { - let key = terrain_grid.pos_key(pos.0.map(|e| e.floor() as i32)); - - if terrain_grid.get_key(key).is_some() { - break; - } else if chunks.contains(&key) { - // Assimilate the entity back into the simulation - rtsim.assimilate_entity(rtsim_entity.0); - } - - rtsim.update_entity(rtsim_entity.0, pos.0); - } + let chunks = std::mem::take(&mut rtsim.world.chunks_to_unload); for chunk in chunks { - + // TODO } } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 7764d220f8..f42716bf43 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -198,6 +198,7 @@ impl<'a> System<'a> for Sys { scale: comp::Scale(scale), home_chunk: Some(comp::HomeChunk(key)), drop_item: entity.loot_drop, + rtsim_entity: None, }) } }