veloren_common_state/
state.rs

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
46/// At what point should we stop speeding up physics to compensate for lag? If
47/// we speed physics up too fast, we'd skip important physics events like
48/// collisions. This constant determines the upper limit. If delta time exceeds
49/// this value, the game's physics will begin to produce time lag. Ideally, we'd
50/// avoid such a situation.
51const MAX_DELTA_TIME: f32 = 1.0;
52/// convert seconds to milliseconds to use in TimerQueue
53const 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    /// Check if the block at given position `pos` has already been modified
73    /// this tick.
74    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
126/// A type used to represent game state stored on both the client and the
127/// server. This includes things like entity components, terrain data, and
128/// global states like weather, time of day, etc.
129pub struct State {
130    ecs: specs::World,
131    // Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
132    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    /// Create a new `State` in client mode.
156    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    /// Create a new `State` in server mode.
175    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        // TODO: Consider alternative ways to do this
205        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    /// Creates ecs world and registers all the common components and resources
227    // TODO: Split up registering into server and client (e.g. move
228    // EventBus<ServerEvent> to the server)
229    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        // Uids for sync
239        ecs.register_sync_marker();
240        // Register server -> all clients synced components.
241        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        // Register components send from clients -> server
291        ecs.register::<comp::Controller>();
292
293        // Register components send directly from server -> all but one client
294        ecs.register::<comp::PhysicsState>();
295
296        // Register components synced from client -> server -> all other clients
297        ecs.register::<comp::Pos>();
298        ecs.register::<comp::Vel>();
299        ecs.register::<comp::Ori>();
300        ecs.register::<comp::Inventory>();
301
302        // Register common unsynced components
303        ecs.register::<comp::PreviousPhysCache>();
304        ecs.register::<comp::PosVelOriDefer>();
305
306        // Register client-local components
307        // TODO: only register on the client
308        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        // Register server-local components
314        // TODO: only register on the server
315        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        // Register synced resources used by the ECS.
337        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        // Register unsynced resources used by the ECS.
345        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        // TODO: only register on the server
366        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        // Load plugins from asset directory
374        #[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    /// Register a component with the state's ECS.
396    #[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    /// Write a component attributed to a particular entity, ignoring errors.
406    ///
407    /// This should be used *only* when we can guarantee that the rest of the
408    /// code does not rely on the insert having succeeded (meaning the
409    /// entity is no longer alive!).
410    ///
411    /// Returns None if the entity was dead or there was no previous entry for
412    /// this component; otherwise, returns Some(old_component).
413    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    /// Delete a component attributed to a particular entity.
426    pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
427        self.ecs.write_storage().remove(entity)
428    }
429
430    /// Read a component attributed to a particular entity.
431    pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
432        self.ecs.read_storage().get(entity).cloned()
433    }
434
435    /// Read a component attributed to a particular entity.
436    pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
437        self.ecs.read_storage().get(entity).copied()
438    }
439
440    /// # Panics
441    /// Panics if `EventBus<E>` is borrowed
442    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    /// Given mutable access to the resource R, assuming the resource
450    /// component exists (this is already the behavior of functions like `fetch`
451    /// and `write_component_ignore_entity_dead`).  Since all of our resources
452    /// are generated up front, any failure here is definitely a code bug.
453    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    /// Get a read-only reference to the storage of a particular component type.
461    pub fn read_storage<C: Component>(&self) -> EcsStorage<'_, C, Fetch<'_, EcsMaskedStorage<C>>> {
462        self.ecs.read_storage::<C>()
463    }
464
465    /// Get a reference to the internal ECS world.
466    pub fn ecs(&self) -> &specs::World { &self.ecs }
467
468    /// Get a mutable reference to the internal ECS world.
469    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    /// Get a reference to the `TerrainChanges` structure of the state. This
474    /// contains information about terrain state that has changed since the
475    /// last game tick.
476    pub fn terrain_changes(&self) -> Fetch<'_, TerrainChanges> { self.ecs.read_resource() }
477
478    /// Get a reference the current in-game weather grid.
479    pub fn weather_grid(&self) -> Fetch<'_, WeatherGrid> { self.ecs.read_resource() }
480
481    /// Get a mutable reference the current in-game weather grid.
482    pub fn weather_grid_mut(&mut self) -> FetchMut<'_, WeatherGrid> { self.ecs.write_resource() }
483
484    /// Get the current weather at a position in worldspace.
485    pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
486        self.weather_grid().get_interpolated(pos)
487    }
488
489    /// Get the max weather near a position in worldspace.
490    pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
491        self.weather_grid().get_max_near(pos)
492    }
493
494    /// Get the current in-game time of day.
495    ///
496    /// Note that this should not be used for physics, animations or other such
497    /// localised timings.
498    pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
499
500    /// Get the current in-game day period (period of the day/night cycle)
501    pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
502
503    /// Get the current in-game time.
504    ///
505    /// Note that this does not correspond to the time of day.
506    pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
507
508    /// Get the current true in-game time, unaffected by time_scale.
509    ///
510    /// Note that this does not correspond to the time of day.
511    pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
512
513    /// Get the current delta time.
514    pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
515
516    /// Get a reference to this state's terrain.
517    pub fn terrain(&self) -> Fetch<'_, TerrainGrid> { self.ecs.read_resource() }
518
519    /// Get a reference to this state's terrain.
520    pub fn slow_job_pool(&self) -> Fetch<'_, SlowJobPool> { self.ecs.read_resource() }
521
522    /// Get a writable reference to this state's terrain.
523    pub fn terrain_mut(&self) -> FetchMut<'_, TerrainGrid> { self.ecs.write_resource() }
524
525    /// Get a block in this state's terrain.
526    pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
527        self.terrain().get(pos).ok().copied()
528    }
529
530    /// Set a block in this state's terrain.
531    pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
532        self.ecs.write_resource::<BlockChange>().set(pos, block);
533    }
534
535    /// Set a block in this state's terrain (used to delete temporary summoned
536    /// sprites after a timeout).
537    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    /// Check if the block at given position `pos` has already been modified
553    /// this tick.
554    pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
555        self.ecs.read_resource::<BlockChange>().can_set_block(pos)
556    }
557
558    /// Removes every chunk of the terrain.
559    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    /// Insert the provided chunk into this state's terrain.
571    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    /// Remove the chunk with the given key from this state's terrain, if it
591    /// exists.
592    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    // Apply terrain changes
611    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    /// `during_tick` is true if and only if this is called from within
616    /// [State::tick].
617    ///
618    /// This only happens if [State::tick] is asked to update terrain itself
619    /// (using `update_terrain: true`).  [State::tick] is called from within
620    /// both the client and the server ticks, right after handling terrain
621    /// messages; currently, client sets it to true and server to false.
622    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        // This is important as the poll function has a debug assert that the new poll
640        // is at a more recent time than the old poll. As Time is synced between server
641        // and client, there is a chance that client dt can get slightly ahead of a
642        // server update, so we do not want to panic in that scenario.
643        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        // Apply block modifications
658        // Only include in `TerrainChanges` if successful
659        let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
660
661        // All positions that should recieve a block update.
662        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                // NOTE: If the changes are applied during the tick, we push the *old* value as
676                // the modified block (since it otherwise can't be recovered after the tick).
677                // Otherwise, the changes will be applied after the tick, so we push the *new*
678                // value.
679                *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        // Only do block updates not during the tick since that's when actual
711        // terrain changes are applied.
712        //
713        // Clients will get these changes since they're just normal block updates
714        // next tick.
715        if !during_tick {
716            prof_span!(_guard, "Indirectly modified sprites");
717
718            // Collects all blocks that are neighbors with a modified block,
719            // where the `adjecency_requirement` is no longer upheld.
720            let indirectly_modified = block_updates
721                .into_iter()
722                // Filter for blocks that have an adjecency requirement.
723                .filter_map(|wpos| {
724                    let block = terrain.get(wpos).ok()?;
725                    Some((wpos, block.get_sprite()?.adjecency_requirement()?, block))
726                })
727                // Check if said adjecency requirement is upheld.
728                .filter(|(wpos, adjecency_requirement, block)| {
729                    let rot_mat = block.rotation_mat();
730                    // Tries to find a solid block for the given adjecent block.
731                    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                        // Don't check for sprites if we're checking for a block
737                        // directly above.
738                        let not_above = adj.z <= 0 || adj.x != 0 || adj.y != 0;
739
740                        if not_above && !res.is_solid() {
741                            // Sprites can be taller than 1 block.
742                            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                    // Same as `find_solid` but first rotates with the sprites rotation
757                    // and mirroring.
758                    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            // If the sprite is bonkable, bonk it.
775            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                        // TODO: Pass who destroyed the block?
785                        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    /// Execute a single tick, simulating the game state by the given duration.
798    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        // Timing code for server metrics
809        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        // Change the time accordingly.
818        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        // Update delta time.
825        // Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
826        // important physics events.
827        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        // This dispatches all the systems in parallel.
832        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        // Process local events
842        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    /// Clean up the state after a tick.
884    pub fn cleanup(&mut self) {
885        span!(_guard, "cleanup", "State::cleanup");
886        // Clean up data structures from the last tick.
887        self.ecs.write_resource::<TerrainChanges>().clear();
888    }
889}
890
891// Timing code for server metrics
892#[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        // Check for duplicates!
900        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}