1#[cfg(feature = "plugins")]
2use crate::plugin::PluginMgr;
3#[cfg(feature = "plugins")]
4use crate::plugin::memory_manager::EcsWorld;
5use crate::{BuildArea, NoDurabilityArea};
6#[cfg(feature = "plugins")]
7use common::uid::IdMaps;
8use common::{
9 calendar::Calendar,
10 comp::{self, gizmos::RtsimGizmos},
11 event::{BonkEvent, EventBus, LocalEvent},
12 interaction,
13 link::Is,
14 mounting::{Mount, Rider, VolumeRider, VolumeRiders},
15 outcome::Outcome,
16 resources::{
17 DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings,
18 ProgramTime, Time, TimeOfDay, TimeScale,
19 },
20 shared_server_config::ServerConstants,
21 slowjob::SlowJobPool,
22 terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid, sprite::SpriteAdjecencyRequirement},
23 tether,
24 time::DayPeriod,
25 trade::Trades,
26 util::Dir2,
27 vol::{ReadVol, WriteVol},
28 weather::{Weather, WeatherGrid},
29};
30use common_base::{prof_span, span};
31use common_ecs::{PhysicsMetrics, SysMetrics};
32use common_net::sync::{WorldSyncExt, interpolation as sync_interp};
33use core::{convert::identity, time::Duration};
34use hashbrown::{HashMap, HashSet};
35use rayon::{ThreadPool, ThreadPoolBuilder};
36use specs::{
37 Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
38 prelude::Resource,
39 shred::{Fetch, FetchMut, SendDispatcher},
40 storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
41};
42use std::{sync::Arc, time::Instant};
43use timer_queue::TimerQueue;
44use vek::*;
45
46const MAX_DELTA_TIME: f32 = 1.0;
52const SECONDS_TO_MILLISECONDS: f64 = 1000.0;
54
55#[derive(Default)]
56pub struct BlockChange {
57 blocks: HashMap<Vec3<i32>, Block>,
58}
59
60impl BlockChange {
61 pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
62
63 pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
64 if !self.blocks.contains_key(&pos) {
65 self.blocks.insert(pos, block);
66 Some(())
67 } else {
68 None
69 }
70 }
71
72 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool { !self.blocks.contains_key(&pos) }
75
76 pub fn clear(&mut self) { self.blocks.clear(); }
77}
78
79#[derive(Default)]
80pub struct ScheduledBlockChange {
81 changes: TimerQueue<HashMap<Vec3<i32>, Block>>,
82 outcomes: TimerQueue<HashMap<Vec3<i32>, Block>>,
83 last_poll_time: u64,
84}
85impl ScheduledBlockChange {
86 pub fn set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
87 let timer = self.changes.insert(
88 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
89 HashMap::new(),
90 );
91 self.changes.get_mut(timer).insert(pos, block);
92 }
93
94 pub fn outcome_set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
95 let outcome_timer = self.outcomes.insert(
96 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
97 HashMap::new(),
98 );
99 self.outcomes.get_mut(outcome_timer).insert(pos, block);
100 }
101}
102
103#[derive(Default)]
104pub struct TerrainChanges {
105 pub new_chunks: HashSet<Vec2<i32>>,
106 pub modified_chunks: HashSet<Vec2<i32>>,
107 pub removed_chunks: HashSet<Vec2<i32>>,
108 pub modified_blocks: HashMap<Vec3<i32>, Block>,
109}
110
111impl TerrainChanges {
112 pub fn clear(&mut self) {
113 self.new_chunks.clear();
114 self.modified_chunks.clear();
115 self.removed_chunks.clear();
116 }
117}
118
119#[derive(Clone)]
120pub struct BlockDiff {
121 pub wpos: Vec3<i32>,
122 pub old: Block,
123 pub new: Block,
124}
125
126pub struct State {
130 ecs: specs::World,
131 thread_pool: Arc<ThreadPool>,
133 dispatcher: SendDispatcher<'static>,
134}
135
136pub type Pools = Arc<ThreadPool>;
137
138impl State {
139 pub fn pools(game_mode: GameMode) -> Pools {
140 let thread_name_infix = match game_mode {
141 GameMode::Server => "s",
142 GameMode::Client => "c",
143 GameMode::Singleplayer => "sp",
144 };
145
146 Arc::new(
147 ThreadPoolBuilder::new()
148 .num_threads(num_cpus::get().max(common::consts::MIN_RECOMMENDED_RAYON_THREADS))
149 .thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
150 .build()
151 .unwrap(),
152 )
153 }
154
155 pub fn client(
157 pools: Pools,
158 map_size_lg: MapSizeLg,
159 default_chunk: Arc<TerrainChunk>,
160 add_systems: impl Fn(&mut DispatcherBuilder),
161 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
162 ) -> Self {
163 Self::new(
164 GameMode::Client,
165 pools,
166 map_size_lg,
167 default_chunk,
168 add_systems,
169 #[cfg(feature = "plugins")]
170 plugin_mgr,
171 )
172 }
173
174 pub fn server(
176 pools: Pools,
177 map_size_lg: MapSizeLg,
178 default_chunk: Arc<TerrainChunk>,
179 add_systems: impl Fn(&mut DispatcherBuilder),
180 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
181 ) -> Self {
182 Self::new(
183 GameMode::Server,
184 pools,
185 map_size_lg,
186 default_chunk,
187 add_systems,
188 #[cfg(feature = "plugins")]
189 plugin_mgr,
190 )
191 }
192
193 pub fn new(
194 game_mode: GameMode,
195 pools: Pools,
196 map_size_lg: MapSizeLg,
197 default_chunk: Arc<TerrainChunk>,
198 add_systems: impl Fn(&mut DispatcherBuilder),
199 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
200 ) -> Self {
201 prof_span!(guard, "create dispatcher");
202 let mut dispatch_builder =
203 DispatcherBuilder::<'static, 'static>::new().with_pool(Arc::clone(&pools));
204 add_systems(&mut dispatch_builder);
206 let dispatcher = dispatch_builder
207 .build()
208 .try_into_sendable()
209 .unwrap_or_else(|_| panic!("Thread local systems not allowed"));
210 drop(guard);
211
212 Self {
213 ecs: Self::setup_ecs_world(
214 game_mode,
215 Arc::clone(&pools),
216 map_size_lg,
217 default_chunk,
218 #[cfg(feature = "plugins")]
219 plugin_mgr,
220 ),
221 thread_pool: pools,
222 dispatcher,
223 }
224 }
225
226 fn setup_ecs_world(
230 game_mode: GameMode,
231 thread_pool: Arc<ThreadPool>,
232 map_size_lg: MapSizeLg,
233 default_chunk: Arc<TerrainChunk>,
234 #[cfg(feature = "plugins")] mut plugin_mgr: PluginMgr,
235 ) -> specs::World {
236 prof_span!("State::setup_ecs_world");
237 let mut ecs = specs::World::new();
238 ecs.register_sync_marker();
240 ecs.register::<comp::Body>();
242 ecs.register::<comp::Hardcore>();
243 ecs.register::<comp::body::parts::Heads>();
244 ecs.register::<comp::Player>();
245 ecs.register::<comp::Stats>();
246 ecs.register::<comp::SkillSet>();
247 ecs.register::<comp::ActiveAbilities>();
248 ecs.register::<comp::Buffs>();
249 ecs.register::<comp::Auras>();
250 ecs.register::<comp::EnteredAuras>();
251 ecs.register::<comp::Energy>();
252 ecs.register::<comp::Combo>();
253 ecs.register::<comp::Health>();
254 ecs.register::<comp::Poise>();
255 ecs.register::<comp::CanBuild>();
256 ecs.register::<comp::LightEmitter>();
257 ecs.register::<comp::PickupItem>();
258 ecs.register::<comp::ThrownItem>();
259 ecs.register::<comp::Scale>();
260 ecs.register::<Is<Mount>>();
261 ecs.register::<Is<Rider>>();
262 ecs.register::<Is<VolumeRider>>();
263 ecs.register::<Is<tether::Leader>>();
264 ecs.register::<Is<tether::Follower>>();
265 ecs.register::<Is<interaction::Interactor>>();
266 ecs.register::<interaction::Interactors>();
267 ecs.register::<comp::Mass>();
268 ecs.register::<comp::Density>();
269 ecs.register::<comp::Collider>();
270 ecs.register::<comp::Sticky>();
271 ecs.register::<comp::Immovable>();
272 ecs.register::<comp::CharacterState>();
273 ecs.register::<comp::CharacterActivity>();
274 ecs.register::<comp::Object>();
275 ecs.register::<comp::Group>();
276 ecs.register::<comp::Shockwave>();
277 ecs.register::<comp::ShockwaveHitEntities>();
278 ecs.register::<comp::projectile::ProjectileHitEntities>();
279 ecs.register::<comp::Beam>();
280 ecs.register::<comp::Arcing>();
281 ecs.register::<comp::Pool>();
282 ecs.register::<comp::Alignment>();
283 ecs.register::<comp::LootOwner>();
284 ecs.register::<comp::Admin>();
285 ecs.register::<comp::Stance>();
286 ecs.register::<comp::Teleporting>();
287 ecs.register::<comp::GizmoSubscriber>();
288 ecs.register::<comp::FrontendMarker>();
289
290 ecs.register::<comp::Controller>();
292
293 ecs.register::<comp::PhysicsState>();
295
296 ecs.register::<comp::Pos>();
298 ecs.register::<comp::Vel>();
299 ecs.register::<comp::Ori>();
300 ecs.register::<comp::Inventory>();
301
302 ecs.register::<comp::PreviousPhysCache>();
304 ecs.register::<comp::PosVelOriDefer>();
305
306 ecs.register::<comp::LightAnimation>();
309 ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
310 ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
311 ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
312
313 ecs.register::<comp::Last<comp::Pos>>();
316 ecs.register::<comp::Last<comp::Vel>>();
317 ecs.register::<comp::Last<comp::Ori>>();
318 ecs.register::<comp::Agent>();
319 ecs.register::<comp::WaypointArea>();
320 ecs.register::<comp::ForceUpdate>();
321 ecs.register::<comp::InventoryUpdateBuffer>();
322 ecs.register::<comp::Waypoint>();
323 ecs.register::<comp::MapMarker>();
324 ecs.register::<comp::Projectile>();
325 ecs.register::<comp::Melee>();
326 ecs.register::<comp::ItemDrops>();
327 ecs.register::<comp::ChatMode>();
328 ecs.register::<comp::Faction>();
329 ecs.register::<comp::invite::Invite>();
330 ecs.register::<comp::invite::PendingInvites>();
331 ecs.register::<VolumeRiders>();
332 ecs.register::<common::combat::DeathEffects>();
333 ecs.register::<common::combat::RiderEffects>();
334 ecs.register::<comp::SpectatingEntity>();
335
336 ecs.insert(TimeOfDay(0.0));
338 ecs.insert(Calendar::default());
339 ecs.insert(WeatherGrid::new(Vec2::zero()));
340 ecs.insert(Time(0.0));
341 ecs.insert(ProgramTime(0.0));
342 ecs.insert(TimeScale(1.0));
343
344 ecs.insert(DeltaTime(0.0));
346 ecs.insert(PlayerEntity(None));
347 ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
348 ecs.insert(BlockChange::default());
349 ecs.insert(ScheduledBlockChange::default());
350 ecs.insert(crate::special_areas::AreasContainer::<BuildArea>::default());
351 ecs.insert(crate::special_areas::AreasContainer::<NoDurabilityArea>::default());
352 ecs.insert(TerrainChanges::default());
353 ecs.insert(EventBus::<LocalEvent>::default());
354 ecs.insert(game_mode);
355 ecs.insert(EventBus::<Outcome>::default());
356 ecs.insert(common::CachedSpatialGrid::default());
357 ecs.insert(EntitiesDiedLastTick::default());
358 ecs.insert(RtsimGizmos::default());
359
360 let num_cpu = num_cpus::get() as u64;
361 let slow_limit = (num_cpu / 2 + num_cpu / 4).max(1);
362 tracing::trace!(?slow_limit, "Slow Thread limit");
363 ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
364
365 ecs.insert(comp::group::GroupManager::default());
367 ecs.insert(SysMetrics::default());
368 ecs.insert(PhysicsMetrics::default());
369 ecs.insert(Trades::default());
370 ecs.insert(PlayerPhysicsSettings::default());
371 ecs.insert(VolumeRiders::default());
372
373 #[cfg(feature = "plugins")]
375 ecs.insert({
376 let ecs_world = EcsWorld {
377 entities: &ecs.entities(),
378 health: ecs.read_component().into(),
379 uid: ecs.read_component().into(),
380 id_maps: &ecs.read_resource::<IdMaps>().into(),
381 player: ecs.read_component().into(),
382 };
383 if let Err(e) = plugin_mgr.load_event(&ecs_world, game_mode) {
384 tracing::debug!(?e, "Failed to run plugin init");
385 tracing::info!("Plugins disabled, enable debug logging for more information.");
386 PluginMgr::default()
387 } else {
388 plugin_mgr
389 }
390 });
391
392 ecs
393 }
394
395 #[must_use]
397 pub fn with_component<T: Component>(mut self) -> Self
398 where
399 <T as Component>::Storage: Default,
400 {
401 self.ecs.register::<T>();
402 self
403 }
404
405 pub fn write_component_ignore_entity_dead<C: Component>(
414 &mut self,
415 entity: EcsEntity,
416 comp: C,
417 ) -> Option<C> {
418 self.ecs
419 .write_storage()
420 .insert(entity, comp)
421 .ok()
422 .and_then(identity)
423 }
424
425 pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
427 self.ecs.write_storage().remove(entity)
428 }
429
430 pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
432 self.ecs.read_storage().get(entity).cloned()
433 }
434
435 pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
437 self.ecs.read_storage().get(entity).copied()
438 }
439
440 pub fn emit_event_now<E>(&self, event: E)
443 where
444 EventBus<E>: Resource,
445 {
446 self.ecs.write_resource::<EventBus<E>>().emit_now(event)
447 }
448
449 pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
454 self.ecs.get_mut::<R>().expect(
455 "Tried to fetch an invalid resource even though all our resources should be known at \
456 compile time.",
457 )
458 }
459
460 pub fn read_storage<C: Component>(&self) -> EcsStorage<'_, C, Fetch<'_, EcsMaskedStorage<C>>> {
462 self.ecs.read_storage::<C>()
463 }
464
465 pub fn ecs(&self) -> &specs::World { &self.ecs }
467
468 pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
470
471 pub fn thread_pool(&self) -> &Arc<ThreadPool> { &self.thread_pool }
472
473 pub fn terrain_changes(&self) -> Fetch<'_, TerrainChanges> { self.ecs.read_resource() }
477
478 pub fn weather_grid(&self) -> Fetch<'_, WeatherGrid> { self.ecs.read_resource() }
480
481 pub fn weather_grid_mut(&mut self) -> FetchMut<'_, WeatherGrid> { self.ecs.write_resource() }
483
484 pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
486 self.weather_grid().get_interpolated(pos)
487 }
488
489 pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
491 self.weather_grid().get_max_near(pos)
492 }
493
494 pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
499
500 pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
502
503 pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
507
508 pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
512
513 pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
515
516 pub fn terrain(&self) -> Fetch<'_, TerrainGrid> { self.ecs.read_resource() }
518
519 pub fn slow_job_pool(&self) -> Fetch<'_, SlowJobPool> { self.ecs.read_resource() }
521
522 pub fn terrain_mut(&self) -> FetchMut<'_, TerrainGrid> { self.ecs.write_resource() }
524
525 pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
527 self.terrain().get(pos).ok().copied()
528 }
529
530 pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
532 self.ecs.write_resource::<BlockChange>().set(pos, block);
533 }
534
535 pub fn schedule_set_block(
538 &self,
539 pos: Vec3<i32>,
540 block: Block,
541 sprite_block: Block,
542 replace_time: f64,
543 ) {
544 self.ecs
545 .write_resource::<ScheduledBlockChange>()
546 .set(pos, block, replace_time);
547 self.ecs
548 .write_resource::<ScheduledBlockChange>()
549 .outcome_set(pos, sprite_block, replace_time);
550 }
551
552 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
555 self.ecs.read_resource::<BlockChange>().can_set_block(pos)
556 }
557
558 pub fn clear_terrain(&mut self) -> usize {
560 let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
561
562 self.terrain_mut()
563 .drain()
564 .map(|(key, _)| {
565 removed_chunks.insert(key);
566 })
567 .count()
568 }
569
570 pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: Arc<TerrainChunk>) {
572 if self
573 .ecs
574 .write_resource::<TerrainGrid>()
575 .insert(key, chunk)
576 .is_some()
577 {
578 self.ecs
579 .write_resource::<TerrainChanges>()
580 .modified_chunks
581 .insert(key);
582 } else {
583 self.ecs
584 .write_resource::<TerrainChanges>()
585 .new_chunks
586 .insert(key);
587 }
588 }
589
590 pub fn remove_chunk(&mut self, key: Vec2<i32>) -> bool {
593 if self
594 .ecs
595 .write_resource::<TerrainGrid>()
596 .remove(key)
597 .is_some()
598 {
599 self.ecs
600 .write_resource::<TerrainChanges>()
601 .removed_chunks
602 .insert(key);
603
604 true
605 } else {
606 false
607 }
608 }
609
610 pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
612 self.apply_terrain_changes_internal(false, block_update);
613 }
614
615 fn apply_terrain_changes_internal(
623 &self,
624 during_tick: bool,
625 mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
626 ) {
627 span!(
628 _guard,
629 "apply_terrain_changes",
630 "State::apply_terrain_changes"
631 );
632 let mut terrain = self.ecs.write_resource::<TerrainGrid>();
633 let mut modified_blocks =
634 std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
635
636 let mut scheduled_changes = self.ecs.write_resource::<ScheduledBlockChange>();
637 let current_time: f64 = self.ecs.read_resource::<Time>().0 * SECONDS_TO_MILLISECONDS;
638 let current_time = current_time as u64;
639 if scheduled_changes.last_poll_time < current_time {
644 scheduled_changes.last_poll_time = current_time;
645 while let Some(changes) = scheduled_changes.changes.poll(current_time) {
646 modified_blocks.extend(changes.iter());
647 }
648 let outcome = self.ecs.read_resource::<EventBus<Outcome>>();
649 while let Some(outcomes) = scheduled_changes.outcomes.poll(current_time) {
650 for (pos, block) in outcomes.into_iter() {
651 if let Some(sprite) = block.get_sprite() {
652 outcome.emit_now(Outcome::SpriteDelete { pos, sprite });
653 }
654 }
655 }
656 }
657 let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
660
661 let mut block_updates = HashSet::<Vec3<i32>>::default();
663
664 modified_blocks.retain(|wpos, new| {
665 let res = terrain.map(*wpos, |old| {
666 updated_blocks.push(BlockDiff {
667 wpos: *wpos,
668 old,
669 new: *new,
670 });
671 *new
672 });
673
674 if let (&Ok(old), true) = (&res, during_tick) {
675 *new = old;
680 }
681
682 if let (&Ok(old), false) = (&res, during_tick) {
683 let h = old
684 .get_sprite()
685 .and_then(|s| s.solid_height())
686 .unwrap_or(1.0)
687 .max(
688 new.get_sprite()
689 .and_then(|s| s.solid_height())
690 .unwrap_or(1.0),
691 )
692 .ceil() as i32;
693
694 block_updates.extend((-1..=h + 1).map(|z| wpos + Vec3::unit_z() * z).chain(
695 (0..=h).flat_map(|z| {
696 Dir2::ALL
697 .iter()
698 .map(move |d| wpos + Vec3::unit_z() * z + d.to_vec2())
699 }),
700 ));
701 };
702
703 res.is_ok()
704 });
705
706 if !updated_blocks.is_empty() {
707 block_update(&self.ecs, updated_blocks);
708 }
709
710 if !during_tick {
716 prof_span!(_guard, "Indirectly modified sprites");
717
718 let indirectly_modified = block_updates
721 .into_iter()
722 .filter_map(|wpos| {
724 let block = terrain.get(wpos).ok()?;
725 Some((wpos, block.get_sprite()?.adjecency_requirement()?, block))
726 })
727 .filter(|(wpos, adjecency_requirement, block)| {
729 let rot_mat = block.rotation_mat();
730 let find_solid = |adj: Vec3<i32>| {
732 let wpos = wpos + adj;
733
734 let res = terrain.get(wpos).copied().unwrap_or(Block::empty());
735
736 let not_above = adj.z <= 0 || adj.x != 0 || adj.y != 0;
739
740 if not_above && !res.is_solid() {
741 for z in 1..=Block::MAX_HEIGHT.ceil() as i32 {
743 if let Ok(block) = terrain.get(wpos - Vec3::unit_z() * z)
744 && let Some(sprite) = block.get_sprite()
745 && let Some(h) = sprite.solid_height()
746 && h.ceil() as i32 > z
747 {
748 return *block;
749 }
750 }
751 }
752
753 res
754 };
755
756 let rel_solid = |adj: Vec3<i32>| find_solid(rot_mat * adj);
759
760 let valid = match adjecency_requirement {
761 SpriteAdjecencyRequirement::AllSolid(v) => {
762 v.iter().all(|v| rel_solid(*v).is_solid())
763 },
764 SpriteAdjecencyRequirement::AnySolid(v) => {
765 v.iter().any(|v| rel_solid(*v).is_solid())
766 },
767 };
768
769 !valid
770 })
771 .map(|(wpos, _, block)| (wpos, block))
772 .collect::<Vec<_>>();
773
774 let bonk_event_bus = self.ecs.write_resource::<EventBus<BonkEvent>>();
776 let mut bonk_emitter = bonk_event_bus.emitter();
777
778 let mut block_change = self.ecs.write_resource::<BlockChange>();
779
780 for (wpos, block) in indirectly_modified {
781 if block.is_bonkable() {
782 bonk_emitter.emit(BonkEvent {
783 pos: wpos.as_::<f32>() + 0.5,
784 owner: None,
786 target: None,
787 });
788 } else {
789 block_change.blocks.insert(wpos, block.into_vacant());
790 }
791 }
792 }
793
794 self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
795 }
796
797 pub fn tick(
799 &mut self,
800 dt: Duration,
801 update_terrain: bool,
802 mut metrics: Option<&mut StateTickMetrics>,
803 server_constants: &ServerConstants,
804 block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
805 ) {
806 span!(_guard, "tick", "State::tick");
807
808 macro_rules! section_span {
810 ($guard:ident, $label:literal) => {
811 span!(span_guard, $label);
812 let metrics_guard = metrics.as_mut().map(|m| MetricsGuard::new($label, m));
813 let $guard = (span_guard, metrics_guard);
814 };
815 }
816
817 let time_scale = self.ecs.read_resource::<TimeScale>().0;
819 self.ecs.write_resource::<TimeOfDay>().0 +=
820 dt.as_secs_f64() * server_constants.day_cycle_coefficient * time_scale;
821 self.ecs.write_resource::<Time>().0 += dt.as_secs_f64() * time_scale;
822 self.ecs.write_resource::<ProgramTime>().0 += dt.as_secs_f64();
823
824 self.ecs.write_resource::<DeltaTime>().0 =
828 (dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
829
830 section_span!(guard, "run systems");
831 self.dispatcher.dispatch(&self.ecs);
833 drop(guard);
834
835 self.maintain_ecs();
836
837 if update_terrain {
838 self.apply_terrain_changes_internal(true, block_update);
839 }
840
841 section_span!(guard, "process local events");
843
844 let outcomes = self.ecs.read_resource::<EventBus<Outcome>>();
845 let mut outcomes_emitter = outcomes.emitter();
846
847 let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
848 for event in events {
849 let mut velocities = self.ecs.write_storage::<comp::Vel>();
850 let physics = self.ecs.read_storage::<comp::PhysicsState>();
851 match event {
852 LocalEvent::Jump(entity, impulse) => {
853 if let Some(vel) = velocities.get_mut(entity) {
854 vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
855 }
856 },
857 LocalEvent::ApplyImpulse { entity, impulse } => {
858 if let Some(vel) = velocities.get_mut(entity) {
859 vel.0 = impulse;
860 }
861 },
862 LocalEvent::Boost {
863 entity,
864 vel: extra_vel,
865 } => {
866 if let Some(vel) = velocities.get_mut(entity) {
867 vel.0 += extra_vel;
868 }
869 },
870 LocalEvent::CreateOutcome(outcome) => {
871 outcomes_emitter.emit(outcome);
872 },
873 }
874 }
875 drop(guard);
876 }
877
878 pub fn maintain_ecs(&mut self) {
879 span!(_guard, "maintain ecs");
880 self.ecs.maintain();
881 }
882
883 pub fn cleanup(&mut self) {
885 span!(_guard, "cleanup", "State::cleanup");
886 self.ecs.write_resource::<TerrainChanges>().clear();
888 }
889}
890
891#[derive(Default)]
893pub struct StateTickMetrics {
894 pub timings: Vec<(&'static str, Duration)>,
895}
896
897impl StateTickMetrics {
898 fn add(&mut self, label: &'static str, dur: Duration) {
899 debug_assert!(
901 self.timings.iter().all(|(l, _)| *l != label),
902 "Duplicate label in state tick metrics {label}"
903 );
904 self.timings.push((label, dur));
905 }
906}
907
908struct MetricsGuard<'a> {
909 start: Instant,
910 label: &'static str,
911 metrics: &'a mut StateTickMetrics,
912}
913
914impl<'a> MetricsGuard<'a> {
915 fn new(label: &'static str, metrics: &'a mut StateTickMetrics) -> Self {
916 Self {
917 start: Instant::now(),
918 label,
919 metrics,
920 }
921 }
922}
923
924impl Drop for MetricsGuard<'_> {
925 fn drop(&mut self) { self.metrics.add(self.label, self.start.elapsed()); }
926}