veloren_server/sys/
sentinel.rs

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