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::{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
45/// At what point should we stop speeding up physics to compensate for lag? If
46/// we speed physics up too fast, we'd skip important physics events like
47/// collisions. This constant determines the upper limit. If delta time exceeds
48/// this value, the game's physics will begin to produce time lag. Ideally, we'd
49/// avoid such a situation.
50const MAX_DELTA_TIME: f32 = 1.0;
51/// convert seconds to milliseconds to use in TimerQueue
52const 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    /// Check if the block at given position `pos` has already been modified
72    /// this tick.
73    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
125/// A type used to represent game state stored on both the client and the
126/// server. This includes things like entity components, terrain data, and
127/// global states like weather, time of day, etc.
128pub struct State {
129    ecs: specs::World,
130    // Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
131    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    /// Create a new `State` in client mode.
155    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    /// Create a new `State` in server mode.
174    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        // TODO: Consider alternative ways to do this
204        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    /// Creates ecs world and registers all the common components and resources
226    // TODO: Split up registering into server and client (e.g. move
227    // EventBus<ServerEvent> to the server)
228    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        // Uids for sync
238        ecs.register_sync_marker();
239        // Register server -> all clients synced components.
240        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        // Register components send from clients -> server
289        ecs.register::<comp::Controller>();
290
291        // Register components send directly from server -> all but one client
292        ecs.register::<comp::PhysicsState>();
293
294        // Register components synced from client -> server -> all other clients
295        ecs.register::<comp::Pos>();
296        ecs.register::<comp::Vel>();
297        ecs.register::<comp::Ori>();
298        ecs.register::<comp::Inventory>();
299
300        // Register common unsynced components
301        ecs.register::<comp::PreviousPhysCache>();
302        ecs.register::<comp::PosVelOriDefer>();
303
304        // Register client-local components
305        // TODO: only register on the client
306        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        // Register server-local components
312        // TODO: only register on the server
313        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        // Register synced resources used by the ECS.
335        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        // Register unsynced resources used by the ECS.
343        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        // TODO: only register on the server
364        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        // Load plugins from asset directory
372        #[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    /// Register a component with the state's ECS.
394    #[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    /// Write a component attributed to a particular entity, ignoring errors.
404    ///
405    /// This should be used *only* when we can guarantee that the rest of the
406    /// code does not rely on the insert having succeeded (meaning the
407    /// entity is no longer alive!).
408    ///
409    /// Returns None if the entity was dead or there was no previous entry for
410    /// this component; otherwise, returns Some(old_component).
411    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    /// Delete a component attributed to a particular entity.
424    pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
425        self.ecs.write_storage().remove(entity)
426    }
427
428    /// Read a component attributed to a particular entity.
429    pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
430        self.ecs.read_storage().get(entity).cloned()
431    }
432
433    /// Read a component attributed to a particular entity.
434    pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
435        self.ecs.read_storage().get(entity).copied()
436    }
437
438    /// # Panics
439    /// Panics if `EventBus<E>` is borrowed
440    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    /// Given mutable access to the resource R, assuming the resource
448    /// component exists (this is already the behavior of functions like `fetch`
449    /// and `write_component_ignore_entity_dead`).  Since all of our resources
450    /// are generated up front, any failure here is definitely a code bug.
451    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    /// Get a read-only reference to the storage of a particular component type.
459    pub fn read_storage<C: Component>(&self) -> EcsStorage<'_, C, Fetch<'_, EcsMaskedStorage<C>>> {
460        self.ecs.read_storage::<C>()
461    }
462
463    /// Get a reference to the internal ECS world.
464    pub fn ecs(&self) -> &specs::World { &self.ecs }
465
466    /// Get a mutable reference to the internal ECS world.
467    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    /// Get a reference to the `TerrainChanges` structure of the state. This
472    /// contains information about terrain state that has changed since the
473    /// last game tick.
474    pub fn terrain_changes(&self) -> Fetch<'_, TerrainChanges> { self.ecs.read_resource() }
475
476    /// Get a reference the current in-game weather grid.
477    pub fn weather_grid(&self) -> Fetch<'_, WeatherGrid> { self.ecs.read_resource() }
478
479    /// Get a mutable reference the current in-game weather grid.
480    pub fn weather_grid_mut(&mut self) -> FetchMut<'_, WeatherGrid> { self.ecs.write_resource() }
481
482    /// Get the current weather at a position in worldspace.
483    pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
484        self.weather_grid().get_interpolated(pos)
485    }
486
487    /// Get the max weather near a position in worldspace.
488    pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
489        self.weather_grid().get_max_near(pos)
490    }
491
492    /// Get the current in-game time of day.
493    ///
494    /// Note that this should not be used for physics, animations or other such
495    /// localised timings.
496    pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
497
498    /// Get the current in-game day period (period of the day/night cycle)
499    pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
500
501    /// Get the current in-game time.
502    ///
503    /// Note that this does not correspond to the time of day.
504    pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
505
506    /// Get the current true in-game time, unaffected by time_scale.
507    ///
508    /// Note that this does not correspond to the time of day.
509    pub fn get_program_time(&self) -> f64 { self.ecs.read_resource::<ProgramTime>().0 }
510
511    /// Get the current delta time.
512    pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
513
514    /// Get a reference to this state's terrain.
515    pub fn terrain(&self) -> Fetch<'_, TerrainGrid> { self.ecs.read_resource() }
516
517    /// Get a reference to this state's terrain.
518    pub fn slow_job_pool(&self) -> Fetch<'_, SlowJobPool> { self.ecs.read_resource() }
519
520    /// Get a writable reference to this state's terrain.
521    pub fn terrain_mut(&self) -> FetchMut<'_, TerrainGrid> { self.ecs.write_resource() }
522
523    /// Get a block in this state's terrain.
524    pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
525        self.terrain().get(pos).ok().copied()
526    }
527
528    /// Set a block in this state's terrain.
529    pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
530        self.ecs.write_resource::<BlockChange>().set(pos, block);
531    }
532
533    /// Set a block in this state's terrain (used to delete temporary summoned
534    /// sprites after a timeout).
535    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    /// Check if the block at given position `pos` has already been modified
551    /// this tick.
552    pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
553        self.ecs.read_resource::<BlockChange>().can_set_block(pos)
554    }
555
556    /// Removes every chunk of the terrain.
557    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    /// Insert the provided chunk into this state's terrain.
569    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    /// Remove the chunk with the given key from this state's terrain, if it
589    /// exists.
590    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    // Apply terrain changes
609    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    /// `during_tick` is true if and only if this is called from within
614    /// [State::tick].
615    ///
616    /// This only happens if [State::tick] is asked to update terrain itself
617    /// (using `update_terrain: true`).  [State::tick] is called from within
618    /// both the client and the server ticks, right after handling terrain
619    /// messages; currently, client sets it to true and server to false.
620    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        // This is important as the poll function has a debug assert that the new poll
638        // is at a more recent time than the old poll. As Time is synced between server
639        // and client, there is a chance that client dt can get slightly ahead of a
640        // server update, so we do not want to panic in that scenario.
641        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        // Apply block modifications
656        // Only include in `TerrainChanges` if successful
657        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                // NOTE: If the changes are applied during the tick, we push the *old* value as
669                // the modified block (since it otherwise can't be recovered after the tick).
670                // Otherwise, the changes will be applied after the tick, so we push the *new*
671                // value.
672                *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    /// Execute a single tick, simulating the game state by the given duration.
685    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        // Timing code for server metrics
696        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        // Change the time accordingly.
705        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        // Update delta time.
712        // Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
713        // important physics events.
714        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        // This dispatches all the systems in parallel.
719        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        // Process local events
729        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    /// Clean up the state after a tick.
771    pub fn cleanup(&mut self) {
772        span!(_guard, "cleanup", "State::cleanup");
773        // Clean up data structures from the last tick.
774        self.ecs.write_resource::<TerrainChanges>().clear();
775    }
776}
777
778// Timing code for server metrics
779#[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        // Check for duplicates!
787        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}