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}