Added entity simulation to rtsim, reification, assimilation

This commit is contained in:
Joshua Barretto 2020-11-11 23:59:09 +00:00
parent 2b5e09e32e
commit 99a881f349
16 changed files with 250 additions and 57 deletions

2
Cargo.lock generated
View File

@ -5402,10 +5402,12 @@ dependencies = [
"portpicker", "portpicker",
"prometheus", "prometheus",
"rand 0.7.3", "rand 0.7.3",
"rand_chacha 0.2.2",
"ron", "ron",
"scan_fmt", "scan_fmt",
"serde", "serde",
"serde_json", "serde_json",
"slab",
"specs", "specs",
"specs-idvs", "specs-idvs",
"tiny_http", "tiny_http",

View File

@ -179,12 +179,12 @@ void main() {
// float reflected_light_point = /*length*/(diffuse_light_point.r) + f_light * point_shadow; // 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; // 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)); 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); 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 = 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; tgt_color = color;
} }

View File

@ -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::{ use comp::{
item::{Item, Reagent}, item::{Item, Reagent},
Ori, Pos, Ori, Pos,
@ -96,6 +103,7 @@ pub enum ServerEvent {
ExitIngame { ExitIngame {
entity: EcsEntity, entity: EcsEntity,
}, },
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
CreateNpc { CreateNpc {
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
@ -107,6 +115,7 @@ pub enum ServerEvent {
scale: comp::Scale, scale: comp::Scale,
home_chunk: Option<comp::HomeChunk>, home_chunk: Option<comp::HomeChunk>,
drop_item: Option<Item>, drop_item: Option<Item>,
rtsim_entity: Option<RtSimEntity>,
}, },
CreateWaypoint(Vec3<f32>), CreateWaypoint(Vec3<f32>),
ClientDisconnect(EcsEntity), ClientDisconnect(EcsEntity),

View File

@ -40,6 +40,7 @@ pub mod path;
pub mod ray; pub mod ray;
pub mod recipe; pub mod recipe;
pub mod region; pub mod region;
pub mod rtsim;
pub mod spiral; pub mod spiral;
pub mod state; pub mod state;
pub mod states; pub mod states;

15
common/src/rtsim.rs Normal file
View File

@ -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<Self>;
}

View File

@ -18,7 +18,10 @@ pub use self::{
use roots::find_roots_cubic; use roots::find_roots_cubic;
use serde::{Deserialize, Serialize}; 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::*; use vek::*;
// TerrainChunkSize // TerrainChunkSize
@ -106,6 +109,27 @@ impl TerrainChunkMeta {
pub type TerrainChunk = chonk::Chonk<Block, TerrainChunkSize, TerrainChunkMeta>; pub type TerrainChunk = chonk::Chonk<Block, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainGrid = VolGrid2d<TerrainChunk>; pub type TerrainGrid = VolGrid2d<TerrainChunk>;
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<i32>) -> Vec3<i32> {
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. // Terrain helper functions used across multiple crates.
/// Computes the position Vec2 of a SimChunk from an index, where the index was /// Computes the position Vec2 of a SimChunk from an index, where the index was

View File

@ -43,3 +43,5 @@ libsqlite3-sys = { version = "0.18", features = ["bundled"] }
diesel = { version = "1.4.3", features = ["sqlite"] } diesel = { version = "1.4.3", features = ["sqlite"] }
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
dotenv = "0.15.0" dotenv = "0.15.0"
slab = "0.4"
rand_chacha = "0.2"

View File

@ -7,6 +7,7 @@ use common::{
}, },
outcome::Outcome, outcome::Outcome,
util::Dir, util::Dir,
rtsim::RtSimEntity,
}; };
use comp::group; use comp::group;
use specs::{Builder, Entity as EcsEntity, WorldExt}; use specs::{Builder, Entity as EcsEntity, WorldExt};
@ -50,6 +51,7 @@ pub fn handle_create_npc(
scale: Scale, scale: Scale,
drop_item: Option<Item>, drop_item: Option<Item>,
home_chunk: Option<HomeChunk>, home_chunk: Option<HomeChunk>,
rtsim_entity: Option<RtSimEntity>,
) { ) {
let group = match alignment { let group = match alignment {
Alignment::Wild => None, Alignment::Wild => None,
@ -90,6 +92,12 @@ pub fn handle_create_npc(
entity entity
}; };
let entity = if let Some(rtsim_entity) = rtsim_entity {
entity.with(rtsim_entity)
} else {
entity
};
entity.build(); entity.build();
} }

View File

@ -2,6 +2,7 @@ use crate::{
client::Client, client::Client,
comp::{biped_large, quadruped_medium, quadruped_small, PhysicsState}, comp::{biped_large, quadruped_medium, quadruped_small, PhysicsState},
Server, SpawnPoint, StateExt, Server, SpawnPoint, StateExt,
rtsim::RtSim,
}; };
use common::{ use common::{
assets::Asset, assets::Asset,
@ -20,6 +21,7 @@ use common::{
terrain::{Block, TerrainGrid}, terrain::{Block, TerrainGrid},
vol::ReadVol, vol::ReadVol,
Damage, DamageSource, Explosion, GroupTarget, RadiusEffect, Damage, DamageSource, Explosion, GroupTarget, RadiusEffect,
rtsim::RtSimEntity,
}; };
use comp::item::Reagent; use comp::item::Reagent;
use rand::prelude::*; 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() .ecs()
.write_storage::<Client>() .write_storage::<Client>()
.get_mut(entity) .get_mut(entity)
@ -339,6 +341,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
.ecs() .ecs()
.write_storage::<comp::CharacterState>() .write_storage::<comp::CharacterState>()
.insert(entity, comp::CharacterState::default()); .insert(entity, comp::CharacterState::default());
false
} else if state.ecs().read_storage::<comp::Agent>().contains(entity) { } else if state.ecs().read_storage::<comp::Agent>().contains(entity) {
use specs::Builder; use specs::Builder;
@ -452,10 +456,16 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
) )
} }
let _ = state true
.delete_entity_recorded(entity)
.map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity"));
} else { } else {
true
};
if should_delete {
if let Some(rtsim_entity) = state.ecs().read_storage::<RtSimEntity>().get(entity).copied() {
state.ecs().write_resource::<RtSim>().destroy_entity(rtsim_entity.0);
}
let _ = state let _ = state
.delete_entity_recorded(entity) .delete_entity_recorded(entity)
.map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity")); .map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity"));

View File

@ -118,9 +118,10 @@ impl Server {
scale, scale,
home_chunk, home_chunk,
drop_item, drop_item,
rtsim_entity,
} => handle_create_npc( } => handle_create_npc(
self, pos, stats, health, loadout, body, agent, alignment, scale, drop_item, 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::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
ServerEvent::ClientDisconnect(entity) => { ServerEvent::ClientDisconnect(entity) => {

View File

@ -44,6 +44,7 @@ use crate::{
presence::{Presence, RegionSubscription}, presence::{Presence, RegionSubscription},
state_ext::StateExt, state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedComps}, sys::sentinel::{DeletedEntities, TrackedComps},
rtsim::RtSim,
}; };
use common::{ use common::{
assets::Asset, assets::Asset,
@ -58,7 +59,8 @@ use common::{
state::{State, TimeOfDay}, state::{State, TimeOfDay},
sync::WorldSyncExt, sync::WorldSyncExt,
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
vol::RectVolSize, vol::{ReadVol, RectVolSize},
rtsim::RtSimEntity,
}; };
use futures_executor::block_on; use futures_executor::block_on;
use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics}; use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics};
@ -321,6 +323,10 @@ impl Server {
// set the spawn point we calculated above // set the spawn point we calculated above
state.ecs_mut().insert(SpawnPoint(spawn_point)); 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. // Set starting time for the server.
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time; state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;
@ -359,7 +365,7 @@ impl Server {
let this = Self { let this = Self {
state, state,
world: Arc::new(world), world,
index, index,
map, map,
@ -550,6 +556,11 @@ impl Server {
}; };
for entity in to_delete { 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::<RtSimEntity>().get(entity).copied() {
self.state.ecs().write_resource::<RtSim>().assimilate_entity(rtsim_entity.0);
}
if let Err(e) = self.state.delete_entity_recorded(entity) { if let Err(e) = self.state.delete_entity_recorded(entity) {
error!(?e, "Failed to delete agent outside the terrain"); error!(?e, "Failed to delete agent outside the terrain");
} }

View File

@ -10,7 +10,7 @@ impl<'a> System<'a> for Sys {
); );
fn run(&mut self, (server_event_bus, mut rtsim): Self::SystemData) { 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 // TODO
} }
} }

View File

@ -1,55 +1,86 @@
mod load_chunks; mod load_chunks;
mod unload_chunks; mod unload_chunks;
mod tick;
use vek::*; use vek::*;
use world::util::Grid; use world::util::Grid;
use common::state::State; use common::{
use specs::{DispatcherBuilder, Component, WorldExt}; state::State,
terrain::TerrainChunk,
rtsim::{RtSimEntity, RtSimId},
vol::RectRasterableVol,
};
use specs::{DispatcherBuilder, WorldExt};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use slab::Slab;
type EntityId = u64; use rand::prelude::*;
pub struct RtSim { pub struct RtSim {
chunks: Grid<Chunk>, world: RtWorld,
chunks_to_load: Vec<Vec2<i32>>, entities: Slab<Entity>,
chunks_to_unload: Vec<Vec2<i32>>,
} }
impl RtSim { impl RtSim {
pub fn new(world_chunk_size: Vec2<u32>) -> Self { pub fn new(world_chunk_size: Vec2<u32>) -> Self {
Self { Self {
chunks: Grid::populate_from(world_chunk_size.map(|e| e as i32), |_| Chunk { world: RtWorld {
is_loaded: false, 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(), chunks_to_load: Vec::new(),
chunks_to_unload: Vec::new(),
},
entities: Slab::new(),
} }
} }
pub fn hook_load_chunk(&mut self, key: Vec2<i32>) { pub fn hook_load_chunk(&mut self, key: Vec2<i32>) {
if let Some(chunk) = self.chunks.get_mut(key) { if let Some(chunk) = self.world.chunks.get_mut(key) {
if !chunk.is_loaded { if !chunk.is_loaded {
chunk.is_loaded = true; 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<i32>) { pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
if let Some(chunk) = self.chunks.get_mut(key) { if let Some(chunk) = self.world.chunks.get_mut(key) {
if chunk.is_loaded { if chunk.is_loaded {
chunk.is_loaded = false; 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) { pub fn assimilate_entity(&mut self, entity: RtSimId) {
// TODO 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<f32>) { pub fn reify_entity(&mut self, entity: RtSimId) {
// TODO 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<f32>) {
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<Chunk>,
chunks_to_load: Vec<Vec2<i32>>,
chunks_to_unload: Vec<Vec2<i32>>,
}
impl RtWorld {
pub fn chunk_at(&self, pos: Vec2<f32>) -> 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, is_loaded: bool,
} }
pub struct RtSimEntity(EntityId); pub struct Entity {
is_loaded: bool,
impl Component for RtSimEntity { pos: Vec3<f32>,
type Storage = IdvStorage<Self>; seed: u32,
} }
const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys"; const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys";
const UNLOAD_CHUNK_SYS: &str = "rtsim_unload_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) { 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(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) { 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::<RtSimEntity>(); state.ecs_mut().register::<RtSimEntity>();
tracing::info!("Initiated real-time world simulation"); tracing::info!("Initiated real-time world simulation");
} }

76
server/src/rtsim/tick.rs Normal file
View File

@ -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<ServerEvent>>,
WriteExpect<'a, RtSim>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Arc<world::World>>,
);
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)),
});
}
}
}

View File

@ -28,27 +28,10 @@ impl<'a> System<'a> for Sys {
positions, positions,
): Self::SystemData, ): Self::SystemData,
) { ) {
let chunks = std::mem::take(&mut rtsim.chunks_to_unload); let chunks = std::mem::take(&mut rtsim.world.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);
}
for chunk in chunks { for chunk in chunks {
// TODO
} }
} }
} }

View File

@ -198,6 +198,7 @@ impl<'a> System<'a> for Sys {
scale: comp::Scale(scale), scale: comp::Scale(scale),
home_chunk: Some(comp::HomeChunk(key)), home_chunk: Some(comp::HomeChunk(key)),
drop_item: entity.loot_drop, drop_item: entity.loot_drop,
rtsim_entity: None,
}) })
} }
} }