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::{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},
23 tether,
24 time::DayPeriod,
25 trade::Trades,
26 vol::{ReadVol, WriteVol},
27 weather::{Weather, WeatherGrid},
28};
29use common_base::{prof_span, span};
30use common_ecs::{PhysicsMetrics, SysMetrics};
31use common_net::sync::{WorldSyncExt, interpolation as sync_interp};
32use core::{convert::identity, time::Duration};
33use hashbrown::{HashMap, HashSet};
34use rayon::{ThreadPool, ThreadPoolBuilder};
35use specs::{
36 Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
37 prelude::Resource,
38 shred::{Fetch, FetchMut, SendDispatcher},
39 storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
40};
41use std::{sync::Arc, time::Instant};
42use timer_queue::TimerQueue;
43use vek::*;
44
45const MAX_DELTA_TIME: f32 = 1.0;
51const SECONDS_TO_MILLISECONDS: f64 = 1000.0;
53
54#[derive(Default)]
55pub struct BlockChange {
56 blocks: HashMap<Vec3<i32>, Block>,
57}
58
59impl BlockChange {
60 pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
61
62 pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
63 if !self.blocks.contains_key(&pos) {
64 self.blocks.insert(pos, block);
65 Some(())
66 } else {
67 None
68 }
69 }
70
71 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool { !self.blocks.contains_key(&pos) }
74
75 pub fn clear(&mut self) { self.blocks.clear(); }
76}
77
78#[derive(Default)]
79pub struct ScheduledBlockChange {
80 changes: TimerQueue<HashMap<Vec3<i32>, Block>>,
81 outcomes: TimerQueue<HashMap<Vec3<i32>, Block>>,
82 last_poll_time: u64,
83}
84impl ScheduledBlockChange {
85 pub fn set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
86 let timer = self.changes.insert(
87 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
88 HashMap::new(),
89 );
90 self.changes.get_mut(timer).insert(pos, block);
91 }
92
93 pub fn outcome_set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
94 let outcome_timer = self.outcomes.insert(
95 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
96 HashMap::new(),
97 );
98 self.outcomes.get_mut(outcome_timer).insert(pos, block);
99 }
100}
101
102#[derive(Default)]
103pub struct TerrainChanges {
104 pub new_chunks: HashSet<Vec2<i32>>,
105 pub modified_chunks: HashSet<Vec2<i32>>,
106 pub removed_chunks: HashSet<Vec2<i32>>,
107 pub modified_blocks: HashMap<Vec3<i32>, Block>,
108}
109
110impl TerrainChanges {
111 pub fn clear(&mut self) {
112 self.new_chunks.clear();
113 self.modified_chunks.clear();
114 self.removed_chunks.clear();
115 }
116}
117
118#[derive(Clone)]
119pub struct BlockDiff {
120 pub wpos: Vec3<i32>,
121 pub old: Block,
122 pub new: Block,
123}
124
125pub struct State {
129 ecs: specs::World,
130 thread_pool: Arc<ThreadPool>,
132 dispatcher: SendDispatcher<'static>,
133}
134
135pub type Pools = Arc<ThreadPool>;
136
137impl State {
138 pub fn pools(game_mode: GameMode) -> Pools {
139 let thread_name_infix = match game_mode {
140 GameMode::Server => "s",
141 GameMode::Client => "c",
142 GameMode::Singleplayer => "sp",
143 };
144
145 Arc::new(
146 ThreadPoolBuilder::new()
147 .num_threads(num_cpus::get().max(common::consts::MIN_RECOMMENDED_RAYON_THREADS))
148 .thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
149 .build()
150 .unwrap(),
151 )
152 }
153
154 pub fn client(
156 pools: Pools,
157 map_size_lg: MapSizeLg,
158 default_chunk: Arc<TerrainChunk>,
159 add_systems: impl Fn(&mut DispatcherBuilder),
160 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
161 ) -> Self {
162 Self::new(
163 GameMode::Client,
164 pools,
165 map_size_lg,
166 default_chunk,
167 add_systems,
168 #[cfg(feature = "plugins")]
169 plugin_mgr,
170 )
171 }
172
173 pub fn server(
175 pools: Pools,
176 map_size_lg: MapSizeLg,
177 default_chunk: Arc<TerrainChunk>,
178 add_systems: impl Fn(&mut DispatcherBuilder),
179 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
180 ) -> Self {
181 Self::new(
182 GameMode::Server,
183 pools,
184 map_size_lg,
185 default_chunk,
186 add_systems,
187 #[cfg(feature = "plugins")]
188 plugin_mgr,
189 )
190 }
191
192 pub fn new(
193 game_mode: GameMode,
194 pools: Pools,
195 map_size_lg: MapSizeLg,
196 default_chunk: Arc<TerrainChunk>,
197 add_systems: impl Fn(&mut DispatcherBuilder),
198 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
199 ) -> Self {
200 prof_span!(guard, "create dispatcher");
201 let mut dispatch_builder =
202 DispatcherBuilder::<'static, 'static>::new().with_pool(Arc::clone(&pools));
203 add_systems(&mut dispatch_builder);
205 let dispatcher = dispatch_builder
206 .build()
207 .try_into_sendable()
208 .unwrap_or_else(|_| panic!("Thread local systems not allowed"));
209 drop(guard);
210
211 Self {
212 ecs: Self::setup_ecs_world(
213 game_mode,
214 Arc::clone(&pools),
215 map_size_lg,
216 default_chunk,
217 #[cfg(feature = "plugins")]
218 plugin_mgr,
219 ),
220 thread_pool: pools,
221 dispatcher,
222 }
223 }
224
225 fn setup_ecs_world(
229 game_mode: GameMode,
230 thread_pool: Arc<ThreadPool>,
231 map_size_lg: MapSizeLg,
232 default_chunk: Arc<TerrainChunk>,
233 #[cfg(feature = "plugins")] mut plugin_mgr: PluginMgr,
234 ) -> specs::World {
235 prof_span!("State::setup_ecs_world");
236 let mut ecs = specs::World::new();
237 ecs.register_sync_marker();
239 ecs.register::<comp::Body>();
241 ecs.register::<comp::Hardcore>();
242 ecs.register::<comp::body::parts::Heads>();
243 ecs.register::<comp::Player>();
244 ecs.register::<comp::Stats>();
245 ecs.register::<comp::SkillSet>();
246 ecs.register::<comp::ActiveAbilities>();
247 ecs.register::<comp::Buffs>();
248 ecs.register::<comp::Auras>();
249 ecs.register::<comp::EnteredAuras>();
250 ecs.register::<comp::Energy>();
251 ecs.register::<comp::Combo>();
252 ecs.register::<comp::Health>();
253 ecs.register::<comp::Poise>();
254 ecs.register::<comp::CanBuild>();
255 ecs.register::<comp::LightEmitter>();
256 ecs.register::<comp::PickupItem>();
257 ecs.register::<comp::ThrownItem>();
258 ecs.register::<comp::Scale>();
259 ecs.register::<Is<Mount>>();
260 ecs.register::<Is<Rider>>();
261 ecs.register::<Is<VolumeRider>>();
262 ecs.register::<Is<tether::Leader>>();
263 ecs.register::<Is<tether::Follower>>();
264 ecs.register::<Is<interaction::Interactor>>();
265 ecs.register::<interaction::Interactors>();
266 ecs.register::<comp::Mass>();
267 ecs.register::<comp::Density>();
268 ecs.register::<comp::Collider>();
269 ecs.register::<comp::Sticky>();
270 ecs.register::<comp::Immovable>();
271 ecs.register::<comp::CharacterState>();
272 ecs.register::<comp::CharacterActivity>();
273 ecs.register::<comp::Object>();
274 ecs.register::<comp::Group>();
275 ecs.register::<comp::Shockwave>();
276 ecs.register::<comp::ShockwaveHitEntities>();
277 ecs.register::<comp::projectile::ProjectileHitEntities>();
278 ecs.register::<comp::Beam>();
279 ecs.register::<comp::Arcing>();
280 ecs.register::<comp::Alignment>();
281 ecs.register::<comp::LootOwner>();
282 ecs.register::<comp::Admin>();
283 ecs.register::<comp::Stance>();
284 ecs.register::<comp::Teleporting>();
285 ecs.register::<comp::GizmoSubscriber>();
286 ecs.register::<comp::FrontendMarker>();
287
288 ecs.register::<comp::Controller>();
290
291 ecs.register::<comp::PhysicsState>();
293
294 ecs.register::<comp::Pos>();
296 ecs.register::<comp::Vel>();
297 ecs.register::<comp::Ori>();
298 ecs.register::<comp::Inventory>();
299
300 ecs.register::<comp::PreviousPhysCache>();
302 ecs.register::<comp::PosVelOriDefer>();
303
304 ecs.register::<comp::LightAnimation>();
307 ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
308 ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
309 ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
310
311 ecs.register::<comp::Last<comp::Pos>>();
314 ecs.register::<comp::Last<comp::Vel>>();
315 ecs.register::<comp::Last<comp::Ori>>();
316 ecs.register::<comp::Agent>();
317 ecs.register::<comp::WaypointArea>();
318 ecs.register::<comp::ForceUpdate>();
319 ecs.register::<comp::InventoryUpdate>();
320 ecs.register::<comp::Waypoint>();
321 ecs.register::<comp::MapMarker>();
322 ecs.register::<comp::Projectile>();
323 ecs.register::<comp::Melee>();
324 ecs.register::<comp::ItemDrops>();
325 ecs.register::<comp::ChatMode>();
326 ecs.register::<comp::Faction>();
327 ecs.register::<comp::invite::Invite>();
328 ecs.register::<comp::invite::PendingInvites>();
329 ecs.register::<VolumeRiders>();
330 ecs.register::<common::combat::DeathEffects>();
331 ecs.register::<common::combat::RiderEffects>();
332 ecs.register::<comp::SpectatingEntity>();
333
334 ecs.insert(TimeOfDay(0.0));
336 ecs.insert(Calendar::default());
337 ecs.insert(WeatherGrid::new(Vec2::zero()));
338 ecs.insert(Time(0.0));
339 ecs.insert(ProgramTime(0.0));
340 ecs.insert(TimeScale(1.0));
341
342 ecs.insert(DeltaTime(0.0));
344 ecs.insert(PlayerEntity(None));
345 ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
346 ecs.insert(BlockChange::default());
347 ecs.insert(ScheduledBlockChange::default());
348 ecs.insert(crate::special_areas::AreasContainer::<BuildArea>::default());
349 ecs.insert(crate::special_areas::AreasContainer::<NoDurabilityArea>::default());
350 ecs.insert(TerrainChanges::default());
351 ecs.insert(EventBus::<LocalEvent>::default());
352 ecs.insert(game_mode);
353 ecs.insert(EventBus::<Outcome>::default());
354 ecs.insert(common::CachedSpatialGrid::default());
355 ecs.insert(EntitiesDiedLastTick::default());
356 ecs.insert(RtsimGizmos::default());
357
358 let num_cpu = num_cpus::get() as u64;
359 let slow_limit = (num_cpu / 2 + num_cpu / 4).max(1);
360 tracing::trace!(?slow_limit, "Slow Thread limit");
361 ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
362
363 ecs.insert(comp::group::GroupManager::default());
365 ecs.insert(SysMetrics::default());
366 ecs.insert(PhysicsMetrics::default());
367 ecs.insert(Trades::default());
368 ecs.insert(PlayerPhysicsSettings::default());
369 ecs.insert(VolumeRiders::default());
370
371 #[cfg(feature = "plugins")]
373 ecs.insert({
374 let ecs_world = EcsWorld {
375 entities: &ecs.entities(),
376 health: ecs.read_component().into(),
377 uid: ecs.read_component().into(),
378 id_maps: &ecs.read_resource::<IdMaps>().into(),
379 player: ecs.read_component().into(),
380 };
381 if let Err(e) = plugin_mgr.load_event(&ecs_world, game_mode) {
382 tracing::debug!(?e, "Failed to run plugin init");
383 tracing::info!("Plugins disabled, enable debug logging for more information.");
384 PluginMgr::default()
385 } else {
386 plugin_mgr
387 }
388 });
389
390 ecs
391 }
392
393 #[must_use]
395 pub fn with_component<T: Component>(mut self) -> Self
396 where
397 <T as Component>::Storage: Default,
398 {
399 self.ecs.register::<T>();
400 self
401 }
402
403 pub fn write_component_ignore_entity_dead<C: Component>(
412 &mut self,
413 entity: EcsEntity,
414 comp: C,
415 ) -> Option<C> {
416 self.ecs
417 .write_storage()
418 .insert(entity, comp)
419 .ok()
420 .and_then(identity)
421 }
422
423 pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
425 self.ecs.write_storage().remove(entity)
426 }
427
428 pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
430 self.ecs.read_storage().get(entity).cloned()
431 }
432
433 pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
435 self.ecs.read_storage().get(entity).copied()
436 }
437
438 pub fn emit_event_now<E>(&self, event: E)
441 where
442 EventBus<E>: Resource,
443 {
444 self.ecs.write_resource::<EventBus<E>>().emit_now(event)
445 }
446
447 pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
452 self.ecs.get_mut::<R>().expect(
453 "Tried to fetch an invalid resource even though all our resources should be known at \
454 compile time.",
455 )
456 }
457
458 pub fn read_storage<C: Component>(&self) -> EcsStorage<'_, C, Fetch<'_, EcsMaskedStorage<C>>> {
460 self.ecs.read_storage::<C>()
461 }
462
463 pub fn ecs(&self) -> &specs::World { &self.ecs }
465
466 pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
468
469 pub fn thread_pool(&self) -> &Arc<ThreadPool> { &self.thread_pool }
470
471 pub fn terrain_changes(&self) -> Fetch<'_, TerrainChanges> { self.ecs.read_resource() }
475
476 pub fn weather_grid(&self) -> Fetch<'_, WeatherGrid> { self.ecs.read_resource() }
478
479 pub fn weather_grid_mut(&mut self) -> FetchMut<'_, WeatherGrid> { self.ecs.write_resource() }
481
482 pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
484 self.weather_grid().get_interpolated(pos)
485 }
486
487 pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
489 self.weather_grid().get_max_near(pos)
490 }
491
492 pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
497
498 pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
500
501 pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
505
506 pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
510
511 pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
513
514 pub fn terrain(&self) -> Fetch<'_, TerrainGrid> { self.ecs.read_resource() }
516
517 pub fn slow_job_pool(&self) -> Fetch<'_, SlowJobPool> { self.ecs.read_resource() }
519
520 pub fn terrain_mut(&self) -> FetchMut<'_, TerrainGrid> { self.ecs.write_resource() }
522
523 pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
525 self.terrain().get(pos).ok().copied()
526 }
527
528 pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
530 self.ecs.write_resource::<BlockChange>().set(pos, block);
531 }
532
533 pub fn schedule_set_block(
536 &self,
537 pos: Vec3<i32>,
538 block: Block,
539 sprite_block: Block,
540 replace_time: f64,
541 ) {
542 self.ecs
543 .write_resource::<ScheduledBlockChange>()
544 .set(pos, block, replace_time);
545 self.ecs
546 .write_resource::<ScheduledBlockChange>()
547 .outcome_set(pos, sprite_block, replace_time);
548 }
549
550 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
553 self.ecs.read_resource::<BlockChange>().can_set_block(pos)
554 }
555
556 pub fn clear_terrain(&mut self) -> usize {
558 let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
559
560 self.terrain_mut()
561 .drain()
562 .map(|(key, _)| {
563 removed_chunks.insert(key);
564 })
565 .count()
566 }
567
568 pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: Arc<TerrainChunk>) {
570 if self
571 .ecs
572 .write_resource::<TerrainGrid>()
573 .insert(key, chunk)
574 .is_some()
575 {
576 self.ecs
577 .write_resource::<TerrainChanges>()
578 .modified_chunks
579 .insert(key);
580 } else {
581 self.ecs
582 .write_resource::<TerrainChanges>()
583 .new_chunks
584 .insert(key);
585 }
586 }
587
588 pub fn remove_chunk(&mut self, key: Vec2<i32>) -> bool {
591 if self
592 .ecs
593 .write_resource::<TerrainGrid>()
594 .remove(key)
595 .is_some()
596 {
597 self.ecs
598 .write_resource::<TerrainChanges>()
599 .removed_chunks
600 .insert(key);
601
602 true
603 } else {
604 false
605 }
606 }
607
608 pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
610 self.apply_terrain_changes_internal(false, block_update);
611 }
612
613 fn apply_terrain_changes_internal(
621 &self,
622 during_tick: bool,
623 mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
624 ) {
625 span!(
626 _guard,
627 "apply_terrain_changes",
628 "State::apply_terrain_changes"
629 );
630 let mut terrain = self.ecs.write_resource::<TerrainGrid>();
631 let mut modified_blocks =
632 std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
633
634 let mut scheduled_changes = self.ecs.write_resource::<ScheduledBlockChange>();
635 let current_time: f64 = self.ecs.read_resource::<Time>().0 * SECONDS_TO_MILLISECONDS;
636 let current_time = current_time as u64;
637 if scheduled_changes.last_poll_time < current_time {
642 scheduled_changes.last_poll_time = current_time;
643 while let Some(changes) = scheduled_changes.changes.poll(current_time) {
644 modified_blocks.extend(changes.iter());
645 }
646 let outcome = self.ecs.read_resource::<EventBus<Outcome>>();
647 while let Some(outcomes) = scheduled_changes.outcomes.poll(current_time) {
648 for (pos, block) in outcomes.into_iter() {
649 if let Some(sprite) = block.get_sprite() {
650 outcome.emit_now(Outcome::SpriteDelete { pos, sprite });
651 }
652 }
653 }
654 }
655 let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
658 modified_blocks.retain(|wpos, new| {
659 let res = terrain.map(*wpos, |old| {
660 updated_blocks.push(BlockDiff {
661 wpos: *wpos,
662 old,
663 new: *new,
664 });
665 *new
666 });
667 if let (&Ok(old), true) = (&res, during_tick) {
668 *new = old;
673 }
674 res.is_ok()
675 });
676
677 if !updated_blocks.is_empty() {
678 block_update(&self.ecs, updated_blocks);
679 }
680
681 self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
682 }
683
684 pub fn tick(
686 &mut self,
687 dt: Duration,
688 update_terrain: bool,
689 mut metrics: Option<&mut StateTickMetrics>,
690 server_constants: &ServerConstants,
691 block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
692 ) {
693 span!(_guard, "tick", "State::tick");
694
695 macro_rules! section_span {
697 ($guard:ident, $label:literal) => {
698 span!(span_guard, $label);
699 let metrics_guard = metrics.as_mut().map(|m| MetricsGuard::new($label, m));
700 let $guard = (span_guard, metrics_guard);
701 };
702 }
703
704 let time_scale = self.ecs.read_resource::<TimeScale>().0;
706 self.ecs.write_resource::<TimeOfDay>().0 +=
707 dt.as_secs_f64() * server_constants.day_cycle_coefficient * time_scale;
708 self.ecs.write_resource::<Time>().0 += dt.as_secs_f64() * time_scale;
709 self.ecs.write_resource::<ProgramTime>().0 += dt.as_secs_f64();
710
711 self.ecs.write_resource::<DeltaTime>().0 =
715 (dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
716
717 section_span!(guard, "run systems");
718 self.dispatcher.dispatch(&self.ecs);
720 drop(guard);
721
722 self.maintain_ecs();
723
724 if update_terrain {
725 self.apply_terrain_changes_internal(true, block_update);
726 }
727
728 section_span!(guard, "process local events");
730
731 let outcomes = self.ecs.read_resource::<EventBus<Outcome>>();
732 let mut outcomes_emitter = outcomes.emitter();
733
734 let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
735 for event in events {
736 let mut velocities = self.ecs.write_storage::<comp::Vel>();
737 let physics = self.ecs.read_storage::<comp::PhysicsState>();
738 match event {
739 LocalEvent::Jump(entity, impulse) => {
740 if let Some(vel) = velocities.get_mut(entity) {
741 vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
742 }
743 },
744 LocalEvent::ApplyImpulse { entity, impulse } => {
745 if let Some(vel) = velocities.get_mut(entity) {
746 vel.0 = impulse;
747 }
748 },
749 LocalEvent::Boost {
750 entity,
751 vel: extra_vel,
752 } => {
753 if let Some(vel) = velocities.get_mut(entity) {
754 vel.0 += extra_vel;
755 }
756 },
757 LocalEvent::CreateOutcome(outcome) => {
758 outcomes_emitter.emit(outcome);
759 },
760 }
761 }
762 drop(guard);
763 }
764
765 pub fn maintain_ecs(&mut self) {
766 span!(_guard, "maintain ecs");
767 self.ecs.maintain();
768 }
769
770 pub fn cleanup(&mut self) {
772 span!(_guard, "cleanup", "State::cleanup");
773 self.ecs.write_resource::<TerrainChanges>().clear();
775 }
776}
777
778#[derive(Default)]
780pub struct StateTickMetrics {
781 pub timings: Vec<(&'static str, Duration)>,
782}
783
784impl StateTickMetrics {
785 fn add(&mut self, label: &'static str, dur: Duration) {
786 debug_assert!(
788 self.timings.iter().all(|(l, _)| *l != label),
789 "Duplicate label in state tick metrics {label}"
790 );
791 self.timings.push((label, dur));
792 }
793}
794
795struct MetricsGuard<'a> {
796 start: Instant,
797 label: &'static str,
798 metrics: &'a mut StateTickMetrics,
799}
800
801impl<'a> MetricsGuard<'a> {
802 fn new(label: &'static str, metrics: &'a mut StateTickMetrics) -> Self {
803 Self {
804 start: Instant::now(),
805 label,
806 metrics,
807 }
808 }
809}
810
811impl Drop for MetricsGuard<'_> {
812 fn drop(&mut self) { self.metrics.add(self.label, self.start.elapsed()); }
813}