veloren_server/sys/
sentinel.rs

1use common::{
2    comp::{
3        Ori, Pos, SpectatingEntity, Vel,
4        item::{MaterialStatManifest, tool::AbilityMap},
5    },
6    uid::Uid,
7};
8use common_ecs::{Job, Origin, Phase, System};
9use common_net::{
10    msg::EcsCompPacket,
11    sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, NetSync, SyncFrom, UpdateTracker},
12};
13use hashbrown::HashMap;
14use specs::{Entity as EcsEntity, Join, ReadExpect, ReadStorage, SystemData, WriteExpect, shred};
15use vek::*;
16
17/// Always watching
18/// This system will monitor specific components for insertion, removal, and
19/// modification
20#[derive(Default)]
21pub struct Sys;
22impl<'a> System<'a> for Sys {
23    type SystemData = (TrackedStorages<'a>, WriteExpect<'a, UpdateTrackers>);
24
25    const NAME: &'static str = "sentinel";
26    const ORIGIN: Origin = Origin::Server;
27    const PHASE: Phase = Phase::Create;
28
29    fn run(_job: &mut Job<Self>, (storages, mut trackers): Self::SystemData) {
30        trackers.record_changes(&storages);
31    }
32}
33
34/// Holds state like modified bitsets, modification event readers
35macro_rules! trackers {
36    // Every place where we have `$( /* ... */ )*` will be repeated for each synced component.
37    ($($component_name:ident: $component_type:ident,)*) => {
38        #[derive(SystemData)]
39        pub struct TrackedStorages<'a> {
40            // Uids are tracked to detect created entities that should be synced over the network.
41            // Additionally we need access to the uids when generating packets to send to the clients.
42            pub uid: ReadStorage<'a, Uid>,
43            pub spectating_entity: ReadStorage<'a, SpectatingEntity>,
44            $(pub $component_name: ReadStorage<'a, $component_type>,)*
45            // TODO: these may be used to duplicate items when we attempt to remove
46            // cloning them.
47            pub _ability_map: ReadExpect<'a, AbilityMap>,
48            pub _msm: ReadExpect<'a, MaterialStatManifest>,
49        }
50
51        impl TrackedStorages<'_> {
52            /// Create a package containing all the synced components for this entity. This is
53            /// used to initialized the entity's representation on the client (e.g. used when a new
54            /// entity is within the area synced to the client).
55            ///
56            /// Note: This is only for components that are synced to the client for all entities.
57            pub fn create_entity_package(
58                &self,
59                entity: EcsEntity,
60                pos: Option<Pos>,
61                vel: Option<Vel>,
62                ori: Option<Ori>,
63            ) -> Option<EntityPackage<EcsCompPacket>> {
64                let uid = self.uid.get(entity).copied()?;
65                Some(self.create_entity_package_with_uid(entity, uid, pos, vel, ori))
66            }
67
68            /// See [`Self::create_entity_package`].
69            ///
70            /// NOTE: Only if you're certain you know the UID for the entity, and it hasn't
71            /// changed!
72            pub fn create_entity_package_with_uid(
73                &self,
74                entity: EcsEntity,
75                uid: Uid,
76                pos: Option<Pos>,
77                vel: Option<Vel>,
78                ori: Option<Ori>,
79            ) -> EntityPackage<EcsCompPacket> {
80                let mut comps = Vec::new();
81                // NOTE: we could potentially include a bitmap indicating which components are present instead of tagging
82                // components with the type in order to save bandwidth
83                //
84                // if the number of optional components sent is less than 1/8 of the number of component types then
85                // then the suggested approach would no longer be favorable
86                $(
87                    // Only add components that are synced from any entity.
88                    if matches!(
89                        <$component_type as NetSync>::SYNC_FROM,
90                        SyncFrom::AnyEntity,
91                    ) {
92                        self
93                            .$component_name
94                            .get(entity)
95                            // TODO: should duplicate rather than clone the item
96                            //
97                            // NetClone trait?
98                            //
99                            //.map(|item| item.duplicate(&self.ability_map, &self.msm))
100                            .cloned()
101                            .map(|c| comps.push(c.into()));
102                    }
103                )*
104                // Add untracked comps
105                pos.map(|c| comps.push(c.into()));
106                vel.map(|c| comps.push(c.into()));
107                ori.map(|c| comps.push(c.into()));
108
109                EntityPackage { uid, comps }
110            }
111
112            /// Create sync package for switching a client to another entity specifically to
113            /// remove/add components that are only synced for the client's entity.
114            pub fn create_sync_from_client_entity_switch(
115                &self,
116                old_uid: Uid,
117                new_uid: Uid,
118                new_entity: specs::Entity,
119            ) -> CompSyncPackage<EcsCompPacket> {
120                let mut comp_sync_package = CompSyncPackage::new();
121
122                $(
123                    if matches!(
124                        <$component_type as NetSync>::SYNC_FROM,
125                        SyncFrom::ClientEntity,
126                    ) {
127                        comp_sync_package.comp_removed::<$component_type>(old_uid);
128                        if let Some(comp) = self.$component_name.get(new_entity).cloned() {
129                            comp_sync_package.comp_inserted(new_uid, comp);
130                        }
131                    }
132                )*
133
134                comp_sync_package
135            }
136        }
137
138        /// Contains an [`UpdateTracker`] for every synced component (that uses this method of
139        /// change detection).
140        ///
141        /// This should be inserted into the ecs as a Resource
142        pub struct UpdateTrackers {
143            pub uid: UpdateTracker<Uid>,
144            spectating_entity: UpdateTracker<SpectatingEntity>,
145            $($component_name: UpdateTracker<$component_type>,)*
146        }
147
148        impl UpdateTrackers {
149            /// Constructs the update trackers and inserts it into the world as a resource.
150            ///
151            /// Components that will be synced must already be registered.
152            pub fn register(world: &mut specs::World) {
153                let trackers = UpdateTrackers {
154                    uid: UpdateTracker::<Uid>::new(&world),
155                    spectating_entity: UpdateTracker::<SpectatingEntity>::new(&world),
156                    $($component_name: UpdateTracker::<$component_type>::new(&world),)*
157                };
158
159                world.insert(trackers);
160                // TODO: if we held copies of components for doing diffing, the components that hold that data could be registered here
161            }
162
163            /// Records updates to components that are provided from the tracked storages as a series of events into bitsets
164            /// that can later be joined on.
165            fn record_changes(&mut self, comps: &TrackedStorages) {
166                self.uid.record_changes(&comps.uid);
167                self.spectating_entity.record_changes(&comps.spectating_entity);
168                $(
169                    self.$component_name.record_changes(&comps.$component_name);
170                )*
171
172                // Enable for logging of counts of component update events.
173                const LOG_COUNTS: bool = false;
174                // Plotting counts via tracy. Env var provided to toggle on so there's no need to
175                // recompile if you are already have a tracy build.
176                let plot_counts = common_base::TRACY_ENABLED && matches!(std::env::var("PLOT_UPDATE_COUNTS").as_deref(), Ok("1"));
177
178                macro_rules! log_counts {
179                    ($comp:ident, $name:expr) => {
180                        if LOG_COUNTS || plot_counts {
181                            let tracker = &self.$comp;
182                            let inserted = tracker.inserted().into_iter().count();
183                            let modified = tracker.modified().into_iter().count();
184                            let removed = tracker.removed().into_iter().count();
185
186                            if plot_counts {
187                                let sum = inserted + modified + removed;
188                                common_base::plot!(concat!($name, "updates"), sum as f64);
189                            }
190
191                            if LOG_COUNTS {
192                                tracing::warn!("{:6} insertions detected for    {}", inserted, $name);
193                                tracing::warn!("{:6} modifications detected for {}", modified, $name);
194                                tracing::warn!("{:6} deletions detected for     {}", removed, $name);
195                            }
196                        }
197                    };
198                }
199                $(log_counts!($component_name, concat!(stringify!($component_name), 's'));)*
200            }
201
202            /// Create a [`EntitySyncPackage`] and a [`CompSyncPackage`] to provide updates
203            /// for the set entities specified by the provided filter (e.g. for a region).
204            ///
205            /// A deleted entities must be externally constructed and provided here.
206            pub fn create_sync_packages(
207                &self,
208                comps: &TrackedStorages,
209                filter: impl Join + Copy,
210                deleted_entities: Vec<Uid>,
211            ) -> (EntitySyncPackage, CompSyncPackage<EcsCompPacket>) {
212                let entity_sync_package =
213                    EntitySyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities);
214                let mut comp_sync_package = CompSyncPackage::new();
215
216                $(
217                    if matches!(
218                        <$component_type as NetSync>::SYNC_FROM,
219                        SyncFrom::AnyEntity,
220                    ) {
221                        comp_sync_package.add_component_updates(
222                            &comps.uid,
223                            &self.$component_name,
224                            &comps.$component_name,
225                            filter,
226                        );
227                    }
228                )*
229
230                (entity_sync_package, comp_sync_package)
231            }
232
233
234            /// Create sync package for components that are only synced for the client's entity.
235            ///
236            /// This can optionally include components that are synced "for any entity" for cases
237            /// where other mechanisms don't sync those components.
238            pub fn create_sync_from_client_package(
239                &self,
240                comps: &TrackedStorages,
241                entity: specs::Entity,
242                include_all_comps: bool,
243            ) -> CompSyncPackage<EcsCompPacket> {
244                // TODO: this type repeats the entity uid for each component but
245                // we know they will all be the same here, using it for now for
246                // convenience but it could help to make a specific type for this
247                // later.
248                let mut comp_sync_package = CompSyncPackage::new();
249
250                let uid = match comps.uid.get(entity) {
251                    Some(uid) => (*uid).into(),
252                    // Return empty package if we can't get uid for this entity
253                    None => return comp_sync_package,
254                };
255
256                $(
257                    if match <$component_type as NetSync>::SYNC_FROM {
258                        SyncFrom::ClientEntity | SyncFrom::ClientSpectatorEntity => true,
259                        SyncFrom::AnyEntity => include_all_comps,
260                    } {
261                        comp_sync_package.add_component_update(
262                            &self.$component_name,
263                            &comps.$component_name,
264                            uid,
265                            entity,
266                            false,
267                        );
268                    }
269                )*
270
271                comp_sync_package
272            }
273
274            pub fn create_sync_from_spectated_entity_package(
275                &self,
276                comps: &TrackedStorages,
277                for_entity: specs::Entity,
278                spectated_entity: specs::Entity,
279            ) -> CompSyncPackage<EcsCompPacket> {
280                let mut comp_sync_package = CompSyncPackage::new();
281
282                let force_sync = self.spectating_entity.modified().contains(for_entity.id())
283                || self.spectating_entity.inserted().contains(for_entity.id());
284
285                let uid = match comps.uid.get(spectated_entity) {
286                    Some(uid) => (*uid).into(),
287                    // Return empty package if we can't get uid for this entity
288                    None => return comp_sync_package,
289                };
290
291                $(
292                    if match <$component_type as NetSync>::SYNC_FROM {
293                        SyncFrom::ClientEntity | SyncFrom::AnyEntity => false,
294                        SyncFrom::ClientSpectatorEntity => true,
295                    } {
296                        comp_sync_package.add_component_update(
297                            &self.$component_name,
298                            &comps.$component_name,
299                            uid,
300                            spectated_entity,
301                            // Force sync if we have changed what entity we're spectating.
302                            force_sync,
303                        );
304                    }
305                )*
306
307                comp_sync_package
308            }
309        }
310    }
311}
312
313// Import all the component types so they will be available when expanding the
314// macro below.
315use common_net::synced_components::*;
316// Pass `trackers!` macro to this "x macro" which will invoke it with a list
317// of components. This will declare the types defined in the macro above.
318common_net::synced_components!(trackers);
319
320/// Deleted entities grouped by region.
321///
322/// Note, this is primarily for syncing purposes and can include the Uid of
323/// clients that have left "in-game" to return to the character screen (even
324/// though there is still an entity with this Uid!).
325pub struct DeletedEntities {
326    map: HashMap<Vec2<i32>, Vec<Uid>>,
327}
328
329impl Default for DeletedEntities {
330    fn default() -> Self {
331        Self {
332            map: HashMap::new(),
333        }
334    }
335}
336
337impl DeletedEntities {
338    pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2<i32>) {
339        self.map.entry(region_key).or_default().push(uid);
340    }
341
342    pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Vec<Uid> {
343        self.map.remove(&key).unwrap_or_default()
344    }
345
346    pub fn get_deleted_in_region(&self, key: Vec2<i32>) -> &[Uid] {
347        self.map.get(&key).map_or(&[], |v| v.as_slice())
348    }
349
350    pub fn take_remaining_deleted(&mut self) -> impl Iterator<Item = (Vec2<i32>, Vec<Uid>)> + '_ {
351        self.map.drain()
352    }
353}