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}