steel_core/entity/damage/
history.rs1use std::mem;
2use std::sync::{Arc, Weak};
3
4use rustc_hash::FxHashMap;
5use steel_utils::locks::SyncMutex;
6
7use crate::entity::{EntityBase, EntityGeneration};
8use crate::level_data::GameTime;
9
10use super::DamageSource;
11
12mod binding;
13mod source;
14
15pub(crate) use binding::DamageHistoryBinding;
16use binding::DamageHistoryVictim;
17pub use source::RecentDamageSource;
18use source::WeakDamageSource;
19
20const MAX_DAMAGE_SOURCE_AGE_TICKS: i64 = 40;
22
23struct DamageRecord {
24 victim_lifetime: Weak<DamageHistoryVictim>,
25 owner: Weak<()>,
27 source: WeakDamageSource,
28 retained: Option<DamageSource>,
29 timestamp: i64,
30 clock: Arc<GameTime>,
31}
32
33impl DamageRecord {
34 fn is_expired(&self) -> bool {
35 self.victim_lifetime.strong_count() == 0
36 || self.clock.ticks().wrapping_sub(self.timestamp) > MAX_DAMAGE_SOURCE_AGE_TICKS
37 }
38}
39
40#[derive(Default)]
47pub struct DamageHistory {
48 records: SyncMutex<FxHashMap<EntityGeneration, DamageRecord>>,
49}
50
51impl DamageHistory {
52 pub(crate) fn record(
53 self: &Arc<Self>,
54 victim: &EntityBase,
55 source: &DamageSource,
56 clock: &Arc<GameTime>,
57 ) {
58 victim
59 .damage_history()
60 .record(self, victim.generation(), source, clock);
61 }
62
63 pub(crate) fn last_damage_source(
64 &self,
65 victim: EntityGeneration,
66 ) -> Option<RecentDamageSource> {
67 let (source, expired) = {
68 let mut records = self.records.lock();
69 if records.get(&victim).is_some_and(DamageRecord::is_expired) {
70 (None, records.remove(&victim))
71 } else {
72 (
73 records.get(&victim).map(|record| record.source.resolve()),
74 None,
75 )
76 }
77 };
78 drop(expired);
79 source
80 }
81
82 pub(crate) fn clear_victim(&self, victim: EntityGeneration) {
83 let removed = self.records.lock().remove(&victim);
84 drop(removed);
85 }
86
87 pub(crate) fn expire(&self) {
90 let (expired, released) = {
92 let mut records = self.records.lock();
93 let expired: Vec<_> = records
94 .extract_if(|_, record| record.is_expired())
95 .map(|(_, record)| record)
96 .collect();
97 let released: Vec<_> = records
98 .values_mut()
99 .filter(|record| record.owner.strong_count() == 0)
100 .filter_map(|record| record.retained.take())
101 .collect();
102 (expired, released)
103 };
104 drop((expired, released));
105 }
106
107 pub(crate) fn clear(&self) {
109 let records = mem::take(&mut *self.records.lock());
110 drop(records);
111 }
112}
113
114#[cfg(test)]
115mod tests;