1#[cfg(feature = "plugins")]
2use crate::plugin::PluginMgr;
3#[cfg(feature = "plugins")]
4use crate::plugin::memory_manager::EcsWorld;
5use crate::{BattleModeChangeArea, 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::{
43 sync::{
44 Arc,
45 atomic::{AtomicBool, Ordering},
46 },
47 time::Instant,
48};
49use timer_queue::TimerQueue;
50use vek::*;
51
52const MAX_DELTA_TIME: f32 = 1.0;
58const SECONDS_TO_MILLISECONDS: f64 = 1000.0;
60
61#[derive(Default)]
62pub struct BlockChange {
63 blocks: HashMap<Vec3<i32>, Block>,
64}
65
66impl BlockChange {
67 pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
68
69 pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
70 if !self.blocks.contains_key(&pos) {
71 self.blocks.insert(pos, block);
72 Some(())
73 } else {
74 None
75 }
76 }
77
78 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool { !self.blocks.contains_key(&pos) }
81
82 pub fn clear(&mut self) { self.blocks.clear(); }
83}
84
85#[derive(Default)]
86pub struct ScheduledBlockChange {
87 changes: TimerQueue<HashMap<Vec3<i32>, Block>>,
88 outcomes: TimerQueue<HashMap<Vec3<i32>, Block>>,
89 last_poll_time: u64,
90}
91impl ScheduledBlockChange {
92 pub fn set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
93 let timer = self.changes.insert(
94 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
95 HashMap::new(),
96 );
97 self.changes.get_mut(timer).insert(pos, block);
98 }
99
100 pub fn outcome_set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
101 let outcome_timer = self.outcomes.insert(
102 (replace_time * SECONDS_TO_MILLISECONDS) as u64,
103 HashMap::new(),
104 );
105 self.outcomes.get_mut(outcome_timer).insert(pos, block);
106 }
107}
108
109#[derive(Default)]
110pub struct TerrainChanges {
111 pub new_chunks: HashSet<Vec2<i32>>,
112 pub modified_chunks: HashSet<Vec2<i32>>,
113 pub removed_chunks: HashSet<Vec2<i32>>,
114 pub modified_blocks: HashMap<Vec3<i32>, Block>,
115}
116
117impl TerrainChanges {
118 pub fn clear(&mut self) {
119 self.new_chunks.clear();
120 self.modified_chunks.clear();
121 self.removed_chunks.clear();
122 }
123}
124
125#[derive(Clone)]
126pub struct BlockDiff {
127 pub wpos: Vec3<i32>,
128 pub old: Block,
129 pub new: Block,
130}
131
132pub struct State {
136 ecs: specs::World,
137 thread_pool: Arc<ThreadPool>,
139 dispatcher: SendDispatcher<'static>,
140}
141
142pub type Pools = Arc<ThreadPool>;
143
144impl State {
145 pub fn pools(game_mode: GameMode) -> Pools {
146 let (thread_name_infix, is_main_task) = match game_mode {
147 GameMode::Server => ("s", true),
148 GameMode::Client => ("c", true),
149 GameMode::Singleplayer => ("sp", false),
152 };
153
154 let is_first_error = Arc::new(AtomicBool::new(true));
155 let set_priority = move || {
156 use thread_priority::*;
157 let priority = if is_main_task {
158 ThreadPriority::Crossplatform(TryFrom::try_from(50).unwrap())
160 } else {
161 ThreadPriority::Min
162 };
163 let res = cfg_select! {
164 target_os = "linux" => std::thread::current().set_priority_and_policy(
165 ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin),
166 priority,
167 ),
168 _ => std::thread::current().set_priority(priority),
169 };
170 if let Err(err) = res
171 && is_first_error.swap(false, Ordering::Relaxed)
172 {
173 tracing::warn!(
174 "Unable to set priority/schedule policy for dispatcher pool thread: {err}"
175 );
176 }
177 };
178
179 Arc::new(
180 ThreadPoolBuilder::new()
181 .num_threads(num_cpus::get().max(common::consts::MIN_RECOMMENDED_RAYON_THREADS))
182 .thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
183 .spawn_handler(|thread| {
184 let mut b = std::thread::Builder::new();
185 if let Some(name) = thread.name() {
186 b = b.name(name.to_owned());
187 }
188 if let Some(stack_size) = thread.stack_size() {
189 b = b.stack_size(stack_size);
190 }
191 let set_priority = set_priority.clone();
192 b.spawn(move || {
193 set_priority();
194 thread.run()
195 })?;
196 Ok(())
197 })
198 .build()
199 .unwrap(),
200 )
201 }
202
203 pub fn client(
205 pools: Pools,
206 map_size_lg: MapSizeLg,
207 default_chunk: Arc<TerrainChunk>,
208 add_systems: impl Fn(&mut DispatcherBuilder),
209 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
210 ) -> Self {
211 Self::new(
212 GameMode::Client,
213 pools,
214 map_size_lg,
215 default_chunk,
216 add_systems,
217 #[cfg(feature = "plugins")]
218 plugin_mgr,
219 )
220 }
221
222 pub fn server(
224 pools: Pools,
225 map_size_lg: MapSizeLg,
226 default_chunk: Arc<TerrainChunk>,
227 add_systems: impl Fn(&mut DispatcherBuilder),
228 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
229 ) -> Self {
230 Self::new(
231 GameMode::Server,
232 pools,
233 map_size_lg,
234 default_chunk,
235 add_systems,
236 #[cfg(feature = "plugins")]
237 plugin_mgr,
238 )
239 }
240
241 pub fn new(
242 game_mode: GameMode,
243 pools: Pools,
244 map_size_lg: MapSizeLg,
245 default_chunk: Arc<TerrainChunk>,
246 add_systems: impl Fn(&mut DispatcherBuilder),
247 #[cfg(feature = "plugins")] plugin_mgr: PluginMgr,
248 ) -> Self {
249 prof_span!(guard, "create dispatcher");
250 let mut dispatch_builder =
251 DispatcherBuilder::<'static, 'static>::new().with_pool(Arc::clone(&pools));
252 add_systems(&mut dispatch_builder);
254 let dispatcher = dispatch_builder
255 .build()
256 .try_into_sendable()
257 .unwrap_or_else(|_| panic!("Thread local systems not allowed"));
258 drop(guard);
259
260 Self {
261 ecs: Self::setup_ecs_world(
262 game_mode,
263 Arc::clone(&pools),
264 map_size_lg,
265 default_chunk,
266 #[cfg(feature = "plugins")]
267 plugin_mgr,
268 ),
269 thread_pool: pools,
270 dispatcher,
271 }
272 }
273
274 fn setup_ecs_world(
278 game_mode: GameMode,
279 thread_pool: Arc<ThreadPool>,
280 map_size_lg: MapSizeLg,
281 default_chunk: Arc<TerrainChunk>,
282 #[cfg(feature = "plugins")] mut plugin_mgr: PluginMgr,
283 ) -> specs::World {
284 prof_span!("State::setup_ecs_world");
285 let mut ecs = specs::World::new();
286 ecs.register_sync_marker();
288 ecs.register::<comp::Body>();
290 ecs.register::<comp::Hardcore>();
291 ecs.register::<comp::body::parts::Heads>();
292 ecs.register::<comp::Player>();
293 ecs.register::<comp::Stats>();
294 ecs.register::<comp::SkillSet>();
295 ecs.register::<comp::ActiveAbilities>();
296 ecs.register::<comp::Buffs>();
297 ecs.register::<comp::Auras>();
298 ecs.register::<comp::EnteredAuras>();
299 ecs.register::<comp::Energy>();
300 ecs.register::<comp::Combo>();
301 ecs.register::<comp::Health>();
302 ecs.register::<comp::Poise>();
303 ecs.register::<comp::CanBuild>();
304 ecs.register::<comp::LightEmitter>();
305 ecs.register::<comp::PickupItem>();
306 ecs.register::<comp::ThrownItem>();
307 ecs.register::<comp::Scale>();
308 ecs.register::<Is<Mount>>();
309 ecs.register::<Is<Rider>>();
310 ecs.register::<Is<VolumeRider>>();
311 ecs.register::<Is<tether::Leader>>();
312 ecs.register::<Is<tether::Follower>>();
313 ecs.register::<Is<interaction::Interactor>>();
314 ecs.register::<interaction::Interactors>();
315 ecs.register::<comp::Mass>();
316 ecs.register::<comp::Density>();
317 ecs.register::<comp::Collider>();
318 ecs.register::<comp::Sticky>();
319 ecs.register::<comp::Immovable>();
320 ecs.register::<comp::CharacterState>();
321 ecs.register::<comp::CharacterActivity>();
322 ecs.register::<comp::Object>();
323 ecs.register::<comp::Group>();
324 ecs.register::<comp::Shockwave>();
325 ecs.register::<comp::ShockwaveHitEntities>();
326 ecs.register::<comp::projectile::ProjectileHitEntities>();
327 ecs.register::<comp::Beam>();
328 ecs.register::<comp::Arcing>();
329 ecs.register::<comp::Pool>();
330 ecs.register::<comp::Alignment>();
331 ecs.register::<comp::LootOwner>();
332 ecs.register::<comp::Admin>();
333 ecs.register::<comp::Stance>();
334 ecs.register::<comp::Teleporting>();
335 ecs.register::<comp::GizmoSubscriber>();
336 ecs.register::<comp::FrontendMarker>();
337
338 ecs.register::<comp::Controller>();
340
341 ecs.register::<comp::PhysicsState>();
343
344 ecs.register::<comp::Pos>();
346 ecs.register::<comp::Vel>();
347 ecs.register::<comp::Ori>();
348 ecs.register::<comp::Inventory>();
349
350 ecs.register::<comp::PreviousPhysCache>();
352 ecs.register::<comp::PosVelOriDefer>();
353
354 ecs.register::<comp::LightAnimation>();
357 ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
358 ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
359 ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
360
361 ecs.register::<comp::Last<comp::Pos>>();
364 ecs.register::<comp::Last<comp::Vel>>();
365 ecs.register::<comp::Last<comp::Ori>>();
366 ecs.register::<comp::Agent>();
367 ecs.register::<comp::WaypointArea>();
368 ecs.register::<comp::ForceUpdate>();
369 ecs.register::<comp::InventoryUpdateBuffer>();
370 ecs.register::<comp::Waypoint>();
371 ecs.register::<comp::MapMarker>();
372 ecs.register::<comp::Projectile>();
373 ecs.register::<comp::Melee>();
374 ecs.register::<comp::ItemDrops>();
375 ecs.register::<comp::ChatMode>();
376 ecs.register::<comp::Faction>();
377 ecs.register::<comp::invite::Invite>();
378 ecs.register::<comp::invite::PendingInvites>();
379 ecs.register::<VolumeRiders>();
380 ecs.register::<common::combat::DeathEffects>();
381 ecs.register::<common::combat::RiderEffects>();
382 ecs.register::<comp::SpectatingEntity>();
383
384 ecs.insert(TimeOfDay(0.0));
386 ecs.insert(Calendar::default());
387 ecs.insert(WeatherGrid::new(Vec2::zero()));
388 ecs.insert(Time(0.0));
389 ecs.insert(ProgramTime(0.0));
390 ecs.insert(TimeScale(1.0));
391
392 ecs.insert(DeltaTime(0.0));
394 ecs.insert(PlayerEntity(None));
395 ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
396 ecs.insert(BlockChange::default());
397 ecs.insert(ScheduledBlockChange::default());
398 ecs.insert(crate::special_areas::AreasContainer::<BuildArea>::default());
399 ecs.insert(crate::special_areas::AreasContainer::<NoDurabilityArea>::default());
400 ecs.insert(crate::special_areas::AreasContainer::<BattleModeChangeArea>::default());
401 ecs.insert(TerrainChanges::default());
402 ecs.insert(EventBus::<LocalEvent>::default());
403 ecs.insert(game_mode);
404 ecs.insert(EventBus::<Outcome>::default());
405 ecs.insert(common::CachedSpatialGrid::default());
406 ecs.insert(EntitiesDiedLastTick::default());
407 ecs.insert(RtsimGizmos::default());
408
409 let num_cpu = num_cpus::get() as u64;
410 let slow_limit = (num_cpu / 2 + num_cpu / 4).max(1);
411 tracing::trace!(?slow_limit, "Slow Thread limit");
412 ecs.insert(SlowJobPool::new(slow_limit, 10_000, thread_pool));
413
414 ecs.insert(comp::group::GroupManager::default());
416 ecs.insert(SysMetrics::default());
417 ecs.insert(PhysicsMetrics::default());
418 ecs.insert(Trades::default());
419 ecs.insert(PlayerPhysicsSettings::default());
420 ecs.insert(VolumeRiders::default());
421
422 #[cfg(feature = "plugins")]
424 ecs.insert({
425 let ecs_world = EcsWorld {
426 entities: &ecs.entities(),
427 health: ecs.read_component().into(),
428 uid: ecs.read_component().into(),
429 id_maps: &ecs.read_resource::<IdMaps>().into(),
430 player: ecs.read_component().into(),
431 };
432 if let Err(e) = plugin_mgr.load_event(&ecs_world, game_mode) {
433 tracing::debug!(?e, "Failed to run plugin init");
434 tracing::info!("Plugins disabled, enable debug logging for more information.");
435 PluginMgr::default()
436 } else {
437 plugin_mgr
438 }
439 });
440
441 ecs
442 }
443
444 #[must_use]
446 pub fn with_component<T: Component>(mut self) -> Self
447 where
448 <T as Component>::Storage: Default,
449 {
450 self.ecs.register::<T>();
451 self
452 }
453
454 pub fn write_component_ignore_entity_dead<C: Component>(
463 &mut self,
464 entity: EcsEntity,
465 comp: C,
466 ) -> Option<C> {
467 self.ecs
468 .write_storage()
469 .insert(entity, comp)
470 .ok()
471 .and_then(identity)
472 }
473
474 pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
476 self.ecs.write_storage().remove(entity)
477 }
478
479 pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
481 self.ecs.read_storage().get(entity).cloned()
482 }
483
484 pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
486 self.ecs.read_storage().get(entity).copied()
487 }
488
489 pub fn emit_event_now<E>(&self, event: E)
492 where
493 EventBus<E>: Resource,
494 {
495 self.ecs.write_resource::<EventBus<E>>().emit_now(event)
496 }
497
498 pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
503 self.ecs.get_mut::<R>().expect(
504 "Tried to fetch an invalid resource even though all our resources should be known at \
505 compile time.",
506 )
507 }
508
509 pub fn read_storage<C: Component>(&self) -> EcsStorage<'_, C, Fetch<'_, EcsMaskedStorage<C>>> {
511 self.ecs.read_storage::<C>()
512 }
513
514 pub fn ecs(&self) -> &specs::World { &self.ecs }
516
517 pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
519
520 pub fn thread_pool(&self) -> &Arc<ThreadPool> { &self.thread_pool }
521
522 pub fn terrain_changes(&self) -> Fetch<'_, TerrainChanges> { self.ecs.read_resource() }
526
527 pub fn weather_grid(&self) -> Fetch<'_, WeatherGrid> { self.ecs.read_resource() }
529
530 pub fn weather_grid_mut(&mut self) -> FetchMut<'_, WeatherGrid> { self.ecs.write_resource() }
532
533 pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
535 self.weather_grid().get_interpolated(pos)
536 }
537
538 pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
540 self.weather_grid().get_max_near(pos)
541 }
542
543 pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
548
549 pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
551
552 pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
556
557 pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
561
562 pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
564
565 pub fn terrain(&self) -> Fetch<'_, TerrainGrid> { self.ecs.read_resource() }
567
568 pub fn slow_job_pool(&self) -> Fetch<'_, SlowJobPool> { self.ecs.read_resource() }
570
571 pub fn terrain_mut(&self) -> FetchMut<'_, TerrainGrid> { self.ecs.write_resource() }
573
574 pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
576 self.terrain().get(pos).ok().copied()
577 }
578
579 pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
581 self.ecs.write_resource::<BlockChange>().set(pos, block);
582 }
583
584 pub fn schedule_set_block(
587 &self,
588 pos: Vec3<i32>,
589 block: Block,
590 sprite_block: Block,
591 replace_time: f64,
592 ) {
593 self.ecs
594 .write_resource::<ScheduledBlockChange>()
595 .set(pos, block, replace_time);
596 self.ecs
597 .write_resource::<ScheduledBlockChange>()
598 .outcome_set(pos, sprite_block, replace_time);
599 }
600
601 pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
604 self.ecs.read_resource::<BlockChange>().can_set_block(pos)
605 }
606
607 pub fn clear_terrain(&mut self) -> usize {
609 let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
610
611 self.terrain_mut()
612 .drain()
613 .map(|(key, _)| {
614 removed_chunks.insert(key);
615 })
616 .count()
617 }
618
619 pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: Arc<TerrainChunk>) {
621 if self
622 .ecs
623 .write_resource::<TerrainGrid>()
624 .insert(key, chunk)
625 .is_some()
626 {
627 self.ecs
628 .write_resource::<TerrainChanges>()
629 .modified_chunks
630 .insert(key);
631 } else {
632 self.ecs
633 .write_resource::<TerrainChanges>()
634 .new_chunks
635 .insert(key);
636 }
637 }
638
639 pub fn remove_chunk(&mut self, key: Vec2<i32>) -> bool {
642 if self
643 .ecs
644 .write_resource::<TerrainGrid>()
645 .remove(key)
646 .is_some()
647 {
648 self.ecs
649 .write_resource::<TerrainChanges>()
650 .removed_chunks
651 .insert(key);
652
653 true
654 } else {
655 false
656 }
657 }
658
659 pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
661 self.apply_terrain_changes_internal(false, block_update);
662 }
663
664 fn apply_terrain_changes_internal(
672 &self,
673 during_tick: bool,
674 mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
675 ) {
676 span!(
677 _guard,
678 "apply_terrain_changes",
679 "State::apply_terrain_changes"
680 );
681 let mut terrain = self.ecs.write_resource::<TerrainGrid>();
682 let mut modified_blocks =
683 std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
684
685 let mut scheduled_changes = self.ecs.write_resource::<ScheduledBlockChange>();
686 let current_time: f64 = self.ecs.read_resource::<Time>().0 * SECONDS_TO_MILLISECONDS;
687 let current_time = current_time as u64;
688 if scheduled_changes.last_poll_time < current_time {
693 scheduled_changes.last_poll_time = current_time;
694 while let Some(changes) = scheduled_changes.changes.poll(current_time) {
695 modified_blocks.extend(changes.iter());
696 }
697 let outcome = self.ecs.read_resource::<EventBus<Outcome>>();
698 while let Some(outcomes) = scheduled_changes.outcomes.poll(current_time) {
699 for (pos, block) in outcomes.into_iter() {
700 if let Some(sprite) = block.get_sprite() {
701 outcome.emit_now(Outcome::SpriteDelete { pos, sprite });
702 }
703 }
704 }
705 }
706 let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
709
710 let mut block_updates = HashSet::<Vec3<i32>>::default();
712
713 modified_blocks.retain(|wpos, new| {
714 let res = terrain.map(*wpos, |old| {
715 updated_blocks.push(BlockDiff {
716 wpos: *wpos,
717 old,
718 new: *new,
719 });
720 *new
721 });
722
723 if let (&Ok(old), true) = (&res, during_tick) {
724 *new = old;
729 }
730
731 if let (&Ok(old), false) = (&res, during_tick) {
732 let h = old
733 .get_sprite()
734 .and_then(|s| s.solid_height())
735 .unwrap_or(1.0)
736 .max(
737 new.get_sprite()
738 .and_then(|s| s.solid_height())
739 .unwrap_or(1.0),
740 )
741 .ceil() as i32;
742
743 block_updates.extend((-1..=h + 1).map(|z| wpos + Vec3::unit_z() * z).chain(
744 (0..=h).flat_map(|z| {
745 Dir2::ALL
746 .iter()
747 .map(move |d| wpos + Vec3::unit_z() * z + d.to_vec2())
748 }),
749 ));
750 };
751
752 res.is_ok()
753 });
754
755 if !updated_blocks.is_empty() {
756 block_update(&self.ecs, updated_blocks);
757 }
758
759 if !during_tick {
765 prof_span!(_guard, "Indirectly modified sprites");
766
767 let indirectly_modified = block_updates
770 .into_iter()
771 .filter_map(|wpos| {
773 let block = terrain.get(wpos).ok()?;
774 Some((wpos, block.get_sprite()?.adjecency_requirement()?, block))
775 })
776 .filter(|(wpos, adjecency_requirement, block)| {
778 let rot_mat = block.rotation_mat();
779 let find_solid = |adj: Vec3<i32>| {
781 let wpos = wpos + adj;
782
783 let res = terrain.get(wpos).copied().unwrap_or(Block::empty());
784
785 let not_above = adj.z <= 0 || adj.x != 0 || adj.y != 0;
788
789 if not_above && !res.is_solid() {
790 for z in 1..=Block::MAX_HEIGHT.ceil() as i32 {
792 if let Ok(block) = terrain.get(wpos - Vec3::unit_z() * z)
793 && let Some(sprite) = block.get_sprite()
794 && let Some(h) = sprite.solid_height()
795 && h.ceil() as i32 > z
796 {
797 return *block;
798 }
799 }
800 }
801
802 res
803 };
804
805 let rel_solid = |adj: Vec3<i32>| find_solid(rot_mat * adj);
808
809 let valid = match adjecency_requirement {
810 SpriteAdjecencyRequirement::AllSolid(v) => {
811 v.iter().all(|v| rel_solid(*v).is_solid())
812 },
813 SpriteAdjecencyRequirement::AnySolid(v) => {
814 v.iter().any(|v| rel_solid(*v).is_solid())
815 },
816 };
817
818 !valid
819 })
820 .map(|(wpos, _, block)| (wpos, block))
821 .collect::<Vec<_>>();
822
823 let bonk_event_bus = self.ecs.write_resource::<EventBus<BonkEvent>>();
825 let mut bonk_emitter = bonk_event_bus.emitter();
826
827 let mut block_change = self.ecs.write_resource::<BlockChange>();
828
829 for (wpos, block) in indirectly_modified {
830 if block.is_bonkable() {
831 bonk_emitter.emit(BonkEvent {
832 pos: wpos.as_::<f32>() + 0.5,
833 owner: None,
835 target: None,
836 });
837 } else {
838 block_change.blocks.insert(wpos, block.into_vacant());
839 }
840 }
841 }
842
843 self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
844 }
845
846 pub fn tick(
848 &mut self,
849 dt: Duration,
850 update_terrain: bool,
851 mut metrics: Option<&mut StateTickMetrics>,
852 server_constants: &ServerConstants,
853 block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
854 ) {
855 span!(_guard, "tick", "State::tick");
856
857 macro_rules! section_span {
859 ($guard:ident, $label:literal) => {
860 span!(span_guard, $label);
861 let metrics_guard = metrics.as_mut().map(|m| MetricsGuard::new($label, m));
862 let $guard = (span_guard, metrics_guard);
863 };
864 }
865
866 let time_scale = self.ecs.read_resource::<TimeScale>().0;
868 self.ecs.write_resource::<TimeOfDay>().0 +=
869 dt.as_secs_f64() * server_constants.day_cycle_coefficient * time_scale;
870 self.ecs.write_resource::<Time>().0 += dt.as_secs_f64() * time_scale;
871 self.ecs.write_resource::<ProgramTime>().0 += dt.as_secs_f64();
872
873 self.ecs.write_resource::<DeltaTime>().0 =
877 (dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
878
879 section_span!(guard, "run systems");
880 self.dispatcher.dispatch(&self.ecs);
882 drop(guard);
883
884 self.maintain_ecs();
885
886 if update_terrain {
887 self.apply_terrain_changes_internal(true, block_update);
888 }
889
890 section_span!(guard, "process local events");
892
893 let outcomes = self.ecs.read_resource::<EventBus<Outcome>>();
894 let mut outcomes_emitter = outcomes.emitter();
895
896 let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
897 for event in events {
898 let mut velocities = self.ecs.write_storage::<comp::Vel>();
899 let physics = self.ecs.read_storage::<comp::PhysicsState>();
900 match event {
901 LocalEvent::Jump(entity, impulse) => {
902 if let Some(vel) = velocities.get_mut(entity) {
903 vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
904 }
905 },
906 LocalEvent::ApplyImpulse { entity, impulse } => {
907 if let Some(vel) = velocities.get_mut(entity) {
908 vel.0 = impulse;
909 }
910 },
911 LocalEvent::Boost {
912 entity,
913 vel: extra_vel,
914 } => {
915 if let Some(vel) = velocities.get_mut(entity) {
916 vel.0 += extra_vel;
917 }
918 },
919 LocalEvent::CreateOutcome(outcome) => {
920 outcomes_emitter.emit(outcome);
921 },
922 }
923 }
924 drop(guard);
925 }
926
927 pub fn maintain_ecs(&mut self) {
928 span!(_guard, "maintain ecs");
929 self.ecs.maintain();
930 }
931
932 pub fn cleanup(&mut self) {
934 span!(_guard, "cleanup", "State::cleanup");
935 self.ecs.write_resource::<TerrainChanges>().clear();
937 }
938}
939
940#[derive(Default)]
942pub struct StateTickMetrics {
943 pub timings: Vec<(&'static str, Duration)>,
944}
945
946impl StateTickMetrics {
947 fn add(&mut self, label: &'static str, dur: Duration) {
948 debug_assert!(
950 self.timings.iter().all(|(l, _)| *l != label),
951 "Duplicate label in state tick metrics {label}"
952 );
953 self.timings.push((label, dur));
954 }
955}
956
957struct MetricsGuard<'a> {
958 start: Instant,
959 label: &'static str,
960 metrics: &'a mut StateTickMetrics,
961}
962
963impl<'a> MetricsGuard<'a> {
964 fn new(label: &'static str, metrics: &'a mut StateTickMetrics) -> Self {
965 Self {
966 start: Instant::now(),
967 label,
968 metrics,
969 }
970 }
971}
972
973impl Drop for MetricsGuard<'_> {
974 fn drop(&mut self) { self.metrics.add(self.label, self.start.elapsed()); }
975}