#[cfg(feature = "plugins")]
use crate::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")]
use crate::plugin::PluginMgr;
use crate::{BuildArea, NoDurabilityArea};
#[cfg(feature = "plugins")]
use common::uid::IdMaps;
use common::{
calendar::Calendar,
comp,
event::{EventBus, LocalEvent},
link::Is,
mounting::{Mount, Rider, VolumeRider, VolumeRiders},
outcome::Outcome,
resources::{
DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings,
ProgramTime, Time, TimeOfDay, TimeScale,
},
shared_server_config::ServerConstants,
slowjob::SlowJobPool,
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
tether,
time::DayPeriod,
trade::Trades,
vol::{ReadVol, WriteVol},
weather::{Weather, WeatherGrid},
};
use common_base::{prof_span, span};
use common_ecs::{PhysicsMetrics, SysMetrics};
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
use core::{convert::identity, time::Duration};
use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder};
use specs::{
prelude::Resource,
shred::{Fetch, FetchMut, SendDispatcher},
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::{sync::Arc, time::Instant};
use timer_queue::TimerQueue;
use vek::*;
const MAX_DELTA_TIME: f32 = 1.0;
const SECONDS_TO_MILLISECONDS: f64 = 1000.0;
#[derive(Default)]
pub struct BlockChange {
blocks: HashMap<Vec3<i32>, Block>,
}
impl BlockChange {
pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
if !self.blocks.contains_key(&pos) {
self.blocks.insert(pos, block);
Some(())
} else {
None
}
}
pub fn can_set_block(&self, pos: Vec3<i32>) -> bool { !self.blocks.contains_key(&pos) }
pub fn clear(&mut self) { self.blocks.clear(); }
}
#[derive(Default)]
pub struct ScheduledBlockChange {
changes: TimerQueue<HashMap<Vec3<i32>, Block>>,
outcomes: TimerQueue<HashMap<Vec3<i32>, Block>>,
last_poll_time: u64,
}
impl ScheduledBlockChange {
pub fn set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
let timer = self.changes.insert(
(replace_time * SECONDS_TO_MILLISECONDS) as u64,
HashMap::new(),
);
self.changes.get_mut(timer).insert(pos, block);
}
pub fn outcome_set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
let outcome_timer = self.outcomes.insert(
(replace_time * SECONDS_TO_MILLISECONDS) as u64,
HashMap::new(),
);
self.outcomes.get_mut(outcome_timer).insert(pos, block);
}
}
#[derive(Default)]
pub struct TerrainChanges {
pub new_chunks: HashSet<Vec2<i32>>,
pub modified_chunks: HashSet<Vec2<i32>>,
pub removed_chunks: HashSet<Vec2<i32>>,
pub modified_blocks: HashMap<Vec3<i32>, Block>,
}
impl TerrainChanges {
pub fn clear(&mut self) {
self.new_chunks.clear();
self.modified_chunks.clear();
self.removed_chunks.clear();
}
}
#[derive(Clone)]
pub struct BlockDiff {
pub wpos: Vec3<i32>,
pub old: Block,
pub new: Block,
}
pub struct State {
ecs: specs::World,
thread_pool: Arc<ThreadPool>,
dispatcher: SendDispatcher<'static>,
}
pub type Pools = Arc<ThreadPool>;
impl State {
pub fn pools(game_mode: GameMode) -> Pools {
let thread_name_infix = match game_mode {
GameMode::Server => "s",
GameMode::Client => "c",
GameMode::Singleplayer => "sp",
};
Arc::new(
ThreadPoolBuilder::new()
.num_threads(num_cpus::get().max(common::consts::MIN_RECOMMENDED_RAYON_THREADS))
.thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
.build()
.unwrap(),
)
}
pub fn client(
pools: Pools,
map_size_lg: MapSizeLg,
default_chunk: Arc<TerrainChunk>,
add_systems: impl Fn(&mut DispatcherBuilder),
#[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
) -> Self {
Self::new(
GameMode::Client,
pools,
map_size_lg,
default_chunk,
add_systems,
#[cfg(feature = "plugins")]
plugin_mgr,
)
}
pub fn server(
pools: Pools,
map_size_lg: MapSizeLg,
default_chunk: Arc<TerrainChunk>,
add_systems: impl Fn(&mut DispatcherBuilder),
#[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
) -> Self {
Self::new(
GameMode::Server,
pools,
map_size_lg,
default_chunk,
add_systems,
#[cfg(feature = "plugins")]
plugin_mgr,
)
}
pub fn new(
game_mode: GameMode,
pools: Pools,
map_size_lg: MapSizeLg,
default_chunk: Arc<TerrainChunk>,
add_systems: impl Fn(&mut DispatcherBuilder),
#[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
) -> Self {
prof_span!(guard, "create dispatcher");
let mut dispatch_builder =
DispatcherBuilder::<'static, 'static>::new().with_pool(Arc::clone(&pools));
add_systems(&mut dispatch_builder);
let dispatcher = dispatch_builder
.build()
.try_into_sendable()
.unwrap_or_else(|_| panic!("Thread local systems not allowed"));
drop(guard);
Self {
ecs: Self::setup_ecs_world(
game_mode,
Arc::clone(&pools),
map_size_lg,
default_chunk,
#[cfg(feature = "plugins")]
plugin_mgr,
),
thread_pool: pools,
dispatcher,
}
}
fn setup_ecs_world(
game_mode: GameMode,
thread_pool: Arc<ThreadPool>,
map_size_lg: MapSizeLg,
default_chunk: Arc<TerrainChunk>,
#[cfg(feature = "plugins")] mut plugin_mgr: PluginMgr,
) -> specs::World {
prof_span!("State::setup_ecs_world");
let mut ecs = specs::World::new();
ecs.register_sync_marker();
ecs.register::<comp::Body>();
ecs.register::<comp::Hardcore>();
ecs.register::<comp::body::parts::Heads>();
ecs.register::<comp::Player>();
ecs.register::<comp::Stats>();
ecs.register::<comp::SkillSet>();
ecs.register::<comp::ActiveAbilities>();
ecs.register::<comp::Buffs>();
ecs.register::<comp::Auras>();
ecs.register::<comp::EnteredAuras>();
ecs.register::<comp::Energy>();
ecs.register::<comp::Combo>();
ecs.register::<comp::Health>();
ecs.register::<comp::Poise>();
ecs.register::<comp::CanBuild>();
ecs.register::<comp::LightEmitter>();
ecs.register::<comp::PickupItem>();
ecs.register::<comp::Scale>();
ecs.register::<Is<Mount>>();
ecs.register::<Is<Rider>>();
ecs.register::<Is<VolumeRider>>();
ecs.register::<Is<tether::Leader>>();
ecs.register::<Is<tether::Follower>>();
ecs.register::<comp::Mass>();
ecs.register::<comp::Density>();
ecs.register::<comp::Collider>();
ecs.register::<comp::Sticky>();
ecs.register::<comp::Immovable>();
ecs.register::<comp::CharacterState>();
ecs.register::<comp::CharacterActivity>();
ecs.register::<comp::Object>();
ecs.register::<comp::Group>();
ecs.register::<comp::Shockwave>();
ecs.register::<comp::ShockwaveHitEntities>();
ecs.register::<comp::Beam>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::LootOwner>();
ecs.register::<comp::Admin>();
ecs.register::<comp::Stance>();
ecs.register::<comp::Teleporting>();
ecs.register::<comp::Controller>();
ecs.register::<comp::PhysicsState>();
ecs.register::<comp::Pos>();
ecs.register::<comp::Vel>();
ecs.register::<comp::Ori>();
ecs.register::<comp::Inventory>();
ecs.register::<comp::PreviousPhysCache>();
ecs.register::<comp::PosVelOriDefer>();
ecs.register::<comp::LightAnimation>();
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Agent>();
ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Waypoint>();
ecs.register::<comp::MapMarker>();
ecs.register::<comp::Projectile>();
ecs.register::<comp::Melee>();
ecs.register::<comp::ItemDrops>();
ecs.register::<comp::ChatMode>();
ecs.register::<comp::Faction>();
ecs.register::<comp::invite::Invite>();
ecs.register::<comp::invite::PendingInvites>();
ecs.register::<VolumeRiders>();
ecs.register::<common::combat::DeathEffects>();
ecs.insert(TimeOfDay(0.0));
ecs.insert(Calendar::default());
ecs.insert(WeatherGrid::new(Vec2::zero()));
ecs.insert(Time(0.0));
ecs.insert(ProgramTime(0.0));
ecs.insert(TimeScale(1.0));
ecs.insert(DeltaTime(0.0));
ecs.insert(PlayerEntity(None));
ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
ecs.insert(BlockChange::default());
ecs.insert(ScheduledBlockChange::default());
ecs.insert(crate::special_areas::AreasContainer::<BuildArea>::default());
ecs.insert(crate::special_areas::AreasContainer::<NoDurabilityArea>::default());
ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(game_mode);
ecs.insert(EventBus::<Outcome>::default());
ecs.insert(common::CachedSpatialGrid::default());
ecs.insert(EntitiesDiedLastTick::default());
let num_cpu = num_cpus::get() as u64;
let slow_limit = (num_cpu / 2 + num_cpu / 4).max(1);
tracing::trace!(?slow_limit, "Slow Thread limit");
ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
ecs.insert(comp::group::GroupManager::default());
ecs.insert(SysMetrics::default());
ecs.insert(PhysicsMetrics::default());
ecs.insert(Trades::default());
ecs.insert(PlayerPhysicsSettings::default());
ecs.insert(VolumeRiders::default());
#[cfg(feature = "plugins")]
ecs.insert({
let ecs_world = EcsWorld {
entities: &ecs.entities(),
health: ecs.read_component().into(),
uid: ecs.read_component().into(),
id_maps: &ecs.read_resource::<IdMaps>().into(),
player: ecs.read_component().into(),
};
if let Err(e) = plugin_mgr.load_event(&ecs_world, game_mode) {
tracing::debug!(?e, "Failed to run plugin init");
tracing::info!("Plugins disabled, enable debug logging for more information.");
PluginMgr::default()
} else {
plugin_mgr
}
});
ecs
}
#[must_use]
pub fn with_component<T: Component>(mut self) -> Self
where
<T as Component>::Storage: Default,
{
self.ecs.register::<T>();
self
}
pub fn write_component_ignore_entity_dead<C: Component>(
&mut self,
entity: EcsEntity,
comp: C,
) -> Option<C> {
self.ecs
.write_storage()
.insert(entity, comp)
.ok()
.and_then(identity)
}
pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
self.ecs.write_storage().remove(entity)
}
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).cloned()
}
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).copied()
}
pub fn emit_event_now<E>(&self, event: E)
where
EventBus<E>: Resource,
{
self.ecs.write_resource::<EventBus<E>>().emit_now(event)
}
pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
self.ecs.get_mut::<R>().expect(
"Tried to fetch an invalid resource even though all our resources should be known at \
compile time.",
)
}
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
self.ecs.read_storage::<C>()
}
pub fn ecs(&self) -> &specs::World { &self.ecs }
pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
pub fn thread_pool(&self) -> &Arc<ThreadPool> { &self.thread_pool }
pub fn terrain_changes(&self) -> Fetch<TerrainChanges> { self.ecs.read_resource() }
pub fn weather_grid(&self) -> Fetch<WeatherGrid> { self.ecs.read_resource() }
pub fn weather_grid_mut(&mut self) -> FetchMut<WeatherGrid> { self.ecs.write_resource() }
pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
self.weather_grid().get_interpolated(pos)
}
pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
self.weather_grid().get_max_near(pos)
}
pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
pub fn terrain(&self) -> Fetch<TerrainGrid> { self.ecs.read_resource() }
pub fn slow_job_pool(&self) -> Fetch<SlowJobPool> { self.ecs.read_resource() }
pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() }
pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
self.terrain().get(pos).ok().copied()
}
pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
self.ecs.write_resource::<BlockChange>().set(pos, block);
}
pub fn schedule_set_block(
&self,
pos: Vec3<i32>,
block: Block,
sprite_block: Block,
replace_time: f64,
) {
self.ecs
.write_resource::<ScheduledBlockChange>()
.set(pos, block, replace_time);
self.ecs
.write_resource::<ScheduledBlockChange>()
.outcome_set(pos, sprite_block, replace_time);
}
pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
self.ecs.read_resource::<BlockChange>().can_set_block(pos)
}
pub fn clear_terrain(&mut self) -> usize {
let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
self.terrain_mut()
.drain()
.map(|(key, _)| {
removed_chunks.insert(key);
})
.count()
}
pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: Arc<TerrainChunk>) {
if self
.ecs
.write_resource::<TerrainGrid>()
.insert(key, chunk)
.is_some()
{
self.ecs
.write_resource::<TerrainChanges>()
.modified_chunks
.insert(key);
} else {
self.ecs
.write_resource::<TerrainChanges>()
.new_chunks
.insert(key);
}
}
pub fn remove_chunk(&mut self, key: Vec2<i32>) -> bool {
if self
.ecs
.write_resource::<TerrainGrid>()
.remove(key)
.is_some()
{
self.ecs
.write_resource::<TerrainChanges>()
.removed_chunks
.insert(key);
true
} else {
false
}
}
pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
self.apply_terrain_changes_internal(false, block_update);
}
fn apply_terrain_changes_internal(
&self,
during_tick: bool,
mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
) {
span!(
_guard,
"apply_terrain_changes",
"State::apply_terrain_changes"
);
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
let mut modified_blocks =
std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
let mut scheduled_changes = self.ecs.write_resource::<ScheduledBlockChange>();
let current_time: f64 = self.ecs.read_resource::<Time>().0 * SECONDS_TO_MILLISECONDS;
let current_time = current_time as u64;
if scheduled_changes.last_poll_time < current_time {
scheduled_changes.last_poll_time = current_time;
while let Some(changes) = scheduled_changes.changes.poll(current_time) {
modified_blocks.extend(changes.iter());
}
let outcome = self.ecs.read_resource::<EventBus<Outcome>>();
while let Some(outcomes) = scheduled_changes.outcomes.poll(current_time) {
for (pos, block) in outcomes.into_iter() {
if let Some(sprite) = block.get_sprite() {
outcome.emit_now(Outcome::SpriteDelete { pos, sprite });
}
}
}
}
let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
modified_blocks.retain(|wpos, new| {
let res = terrain.map(*wpos, |old| {
updated_blocks.push(BlockDiff {
wpos: *wpos,
old,
new: *new,
});
*new
});
if let (&Ok(old), true) = (&res, during_tick) {
*new = old;
}
res.is_ok()
});
if !updated_blocks.is_empty() {
block_update(&self.ecs, updated_blocks);
}
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
}
pub fn tick(
&mut self,
dt: Duration,
update_terrain: bool,
mut metrics: Option<&mut StateTickMetrics>,
server_constants: &ServerConstants,
block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
) {
span!(_guard, "tick", "State::tick");
macro_rules! section_span {
($guard:ident, $label:literal) => {
span!(span_guard, $label);
let metrics_guard = metrics.as_mut().map(|m| MetricsGuard::new($label, m));
let $guard = (span_guard, metrics_guard);
};
}
let time_scale = self.ecs.read_resource::<TimeScale>().0;
self.ecs.write_resource::<TimeOfDay>().0 +=
dt.as_secs_f64() * server_constants.day_cycle_coefficient * time_scale;
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64() * time_scale;
self.ecs.write_resource::<ProgramTime>().0 += dt.as_secs_f64();
self.ecs.write_resource::<DeltaTime>().0 =
(dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
section_span!(guard, "run systems");
self.dispatcher.dispatch(&self.ecs);
drop(guard);
self.maintain_ecs();
if update_terrain {
self.apply_terrain_changes_internal(true, block_update);
}
section_span!(guard, "process local events");
let outcomes = self.ecs.read_resource::<EventBus<Outcome>>();
let mut outcomes_emitter = outcomes.emitter();
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
for event in events {
let mut velocities = self.ecs.write_storage::<comp::Vel>();
let physics = self.ecs.read_storage::<comp::PhysicsState>();
match event {
LocalEvent::Jump(entity, impulse) => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
}
},
LocalEvent::ApplyImpulse { entity, impulse } => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0 = impulse;
}
},
LocalEvent::Boost {
entity,
vel: extra_vel,
} => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0 += extra_vel;
}
},
LocalEvent::CreateOutcome(outcome) => {
outcomes_emitter.emit(outcome);
},
}
}
drop(guard);
}
pub fn maintain_ecs(&mut self) {
span!(_guard, "maintain ecs");
self.ecs.maintain();
}
pub fn cleanup(&mut self) {
span!(_guard, "cleanup", "State::cleanup");
self.ecs.write_resource::<TerrainChanges>().clear();
}
}
#[derive(Default)]
pub struct StateTickMetrics {
pub timings: Vec<(&'static str, Duration)>,
}
impl StateTickMetrics {
fn add(&mut self, label: &'static str, dur: Duration) {
debug_assert!(
self.timings.iter().all(|(l, _)| *l != label),
"Duplicate label in state tick metrics {label}"
);
self.timings.push((label, dur));
}
}
struct MetricsGuard<'a> {
start: Instant,
label: &'static str,
metrics: &'a mut StateTickMetrics,
}
impl<'a> MetricsGuard<'a> {
fn new(label: &'static str, metrics: &'a mut StateTickMetrics) -> Self {
Self {
start: Instant::now(),
label,
metrics,
}
}
}
impl Drop for MetricsGuard<'_> {
fn drop(&mut self) { self.metrics.add(self.label, self.start.elapsed()); }
}