1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
use crate::{event::OnTick, RtState, Rule, RuleError};
use rand::prelude::*;
use rand_chacha::ChaChaRng;

/// Prevent performing cleanup for every NPC every tick
const NPC_SENTIMENT_TICK_SKIP: u64 = 30;
const NPC_CLEANUP_TICK_SKIP: u64 = 100;
const FACTION_CLEANUP_TICK_SKIP: u64 = 30;
const SITE_CLEANUP_TICK_SKIP: u64 = 30;

/// A rule that cleans up data structures in rtsim: removing old reports,
/// irrelevant sentiments, etc.
///
/// Also performs sentiment decay (although this should be moved elsewhere)
pub struct CleanUp;

impl Rule for CleanUp {
    fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
        rtstate.bind::<Self, OnTick>(|ctx| {
            let data = &mut *ctx.state.data_mut();
            let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());

            // TODO: Use `.into_par_iter()` for these by implementing rayon traits in upstream slotmap.

            // Decay NPC sentiments
            data.npcs
                .iter_mut()
                // Only cleanup NPCs every few ticks
                .filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_SENTIMENT_TICK_SKIP == 0)
                .for_each(|(_, npc)| npc.sentiments.decay(&mut rng, ctx.event.dt * NPC_SENTIMENT_TICK_SKIP as f32));

            // Remove dead NPCs
            // TODO: Don't do this every tick, find a sensible way to gradually remove dead NPCs after they've been
            // forgotten
            data.npcs
                .retain(|npc_id, npc| if npc.is_dead() {
                    // Remove NPC from home population
                    if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
                        home.population.remove(&npc_id);
                    }
                    false
                } else {
                    true
                });

            // Clean up entities
            data.npcs
                .iter_mut()
                .filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_CLEANUP_TICK_SKIP == 0)
                .for_each(|(_, npc)| npc.cleanup(&data.reports));

            // Clean up factions
            data.factions
                .iter_mut()
                .filter(|(_, faction)| (faction.seed as u64 + ctx.event.tick) % FACTION_CLEANUP_TICK_SKIP == 0)
                .for_each(|(_, faction)| faction.cleanup());

            // Clean up sites
            data.sites
                .iter_mut()
                .filter(|(_, site)| (site.seed as u64 + ctx.event.tick) % SITE_CLEANUP_TICK_SKIP == 0)
                .for_each(|(_, site)| site.cleanup(&data.reports));

            // Clean up old reports
            data.reports.cleanup(data.time_of_day);
        });

        Ok(Self)
    }
}