veloren_rtsim/rule/
cleanup.rs

1use crate::{RtState, Rule, RuleError, event::OnTick};
2use rand::prelude::*;
3use rand_chacha::ChaChaRng;
4
5/// Prevent performing cleanup for every NPC every tick
6const NPC_SENTIMENT_TICK_SKIP: u64 = 30;
7const NPC_CLEANUP_TICK_SKIP: u64 = 100;
8const FACTION_CLEANUP_TICK_SKIP: u64 = 30;
9const SITE_CLEANUP_TICK_SKIP: u64 = 30;
10
11/// A rule that cleans up data structures in rtsim: removing old reports,
12/// irrelevant sentiments, etc.
13///
14/// Also performs sentiment decay (although this should be moved elsewhere)
15pub struct CleanUp;
16
17impl Rule for CleanUp {
18    fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
19        rtstate.bind::<Self, OnTick>(|ctx| {
20            let data = &mut *ctx.state.data_mut();
21            let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
22
23            // TODO: Use `.into_par_iter()` for these by implementing rayon traits in upstream slotmap.
24
25            // Decay NPC sentiments
26            data.npcs
27                .iter_mut()
28                // Only cleanup NPCs every few ticks
29                .filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_SENTIMENT_TICK_SKIP == 0)
30                .for_each(|(_, npc)| npc.sentiments.decay(&mut rng, ctx.event.dt * NPC_SENTIMENT_TICK_SKIP as f32));
31
32            // Remove dead NPCs
33            // TODO: Don't do this every tick, find a sensible way to gradually remove dead NPCs after they've been
34            // forgotten
35            data.npcs
36                .retain(|npc_id, npc| if npc.is_dead() {
37                    // Remove NPC from home population
38                    if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
39                        home.population.remove(&npc_id);
40                    }
41                    tracing::debug!(?npc_id, "Cleaning up dead NPC");
42                    false
43                } else {
44                    true
45                });
46
47            // Clean up site population.
48            data.sites.iter_mut()
49                .filter(|(_, site)| (site.seed as u64 + ctx.event.tick) % SITE_CLEANUP_TICK_SKIP == 0)
50                .for_each(|(id, site)| {
51                site.population.retain(|npc_id| {
52                    data.npcs.get(*npc_id).is_some_and(|npc| npc.home == Some(id))
53                });
54            });
55
56            // Clean up entities
57            data.npcs
58                .iter_mut()
59                .filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_CLEANUP_TICK_SKIP == 0)
60                .for_each(|(_, npc)| npc.cleanup(&data.reports));
61
62            // Clean up factions
63            data.factions
64                .iter_mut()
65                .filter(|(_, faction)| (faction.seed as u64 + ctx.event.tick) % FACTION_CLEANUP_TICK_SKIP == 0)
66                .for_each(|(_, faction)| faction.cleanup());
67
68            // Clean up sites
69            data.sites
70                .iter_mut()
71                .filter(|(_, site)| (site.seed as u64 + ctx.event.tick) % SITE_CLEANUP_TICK_SKIP == 0)
72                .for_each(|(_, site)| site.cleanup(&data.reports));
73
74            // Clean up old reports
75            data.reports.cleanup(data.time_of_day);
76        });
77
78        Ok(Self)
79    }
80}