steel_core/entity/ai/
sensing.rs1use rustc_hash::FxHashSet;
4
5#[derive(Debug, Default)]
7pub(crate) struct Sensing {
8 seen: FxHashSet<i32>,
9 unseen: FxHashSet<i32>,
10}
11
12impl Sensing {
13 #[must_use]
15 pub(crate) fn new() -> Self {
16 Self::default()
17 }
18
19 pub(crate) fn tick(&mut self) {
21 self.seen.clear();
22 self.unseen.clear();
23 }
24
25 pub(crate) fn has_line_of_sight(
27 &mut self,
28 target_id: i32,
29 test: impl FnOnce() -> bool,
30 ) -> bool {
31 if self.seen.contains(&target_id) {
32 return true;
33 }
34 if self.unseen.contains(&target_id) {
35 return false;
36 }
37
38 let has_line_of_sight = test();
39 if has_line_of_sight {
40 self.seen.insert(target_id);
41 } else {
42 self.unseen.insert(target_id);
43 }
44 has_line_of_sight
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use std::sync::atomic::{AtomicUsize, Ordering};
51
52 use super::*;
53
54 #[test]
55 fn sensing_caches_seen_targets_until_tick() {
56 let calls = AtomicUsize::new(0);
57 let mut sensing = Sensing::new();
58
59 assert!(sensing.has_line_of_sight(1, || {
60 calls.fetch_add(1, Ordering::Relaxed);
61 true
62 }));
63 assert!(sensing.has_line_of_sight(1, || {
64 calls.fetch_add(1, Ordering::Relaxed);
65 false
66 }));
67 assert_eq!(calls.load(Ordering::Relaxed), 1);
68
69 sensing.tick();
70 assert!(!sensing.has_line_of_sight(1, || {
71 calls.fetch_add(1, Ordering::Relaxed);
72 false
73 }));
74 assert_eq!(calls.load(Ordering::Relaxed), 2);
75 }
76
77 #[test]
78 fn sensing_caches_unseen_targets_until_tick() {
79 let calls = AtomicUsize::new(0);
80 let mut sensing = Sensing::new();
81
82 assert!(!sensing.has_line_of_sight(1, || {
83 calls.fetch_add(1, Ordering::Relaxed);
84 false
85 }));
86 assert!(!sensing.has_line_of_sight(1, || {
87 calls.fetch_add(1, Ordering::Relaxed);
88 true
89 }));
90
91 assert_eq!(calls.load(Ordering::Relaxed), 1);
92 }
93}