Skip to main content

steel_core/entity/damage/history/
binding.rs

1use std::{
2    ptr,
3    sync::{Arc, Weak},
4};
5
6use steel_utils::locks::SyncMutex;
7
8use super::{DamageHistory, DamageRecord, DamageSource, RecentDamageSource, WeakDamageSource};
9use crate::entity::EntityGeneration;
10use crate::level_data::GameTime;
11
12/// Serializes history writes with removal and gameplay ownership changes.
13#[derive(Default)]
14pub(crate) struct DamageHistoryBinding {
15    state: SyncMutex<BindingState>,
16}
17
18#[derive(Default)]
19struct BindingState {
20    victim: Option<Arc<DamageHistoryVictim>>,
21    owner: Weak<()>,
22    removed: bool,
23}
24
25/// Owned only by the entity's binding; records observe its lifetime weakly.
26pub(super) struct DamageHistoryVictim {
27    generation: EntityGeneration,
28    history: Weak<DamageHistory>,
29}
30
31impl DamageHistoryBinding {
32    /// The marker must live in a gameplay owner, never in the entity itself.
33    pub(crate) fn retain_owner(&self) -> Arc<()> {
34        let mut state = self.state.lock();
35        if let Some(owner) = state.owner.upgrade() {
36            return owner;
37        }
38        let owner = Arc::new(());
39        state.owner = Arc::downgrade(&owner);
40        owner
41    }
42
43    pub(super) fn record(
44        &self,
45        history: &Arc<DamageHistory>,
46        generation: EntityGeneration,
47        source: &DamageSource,
48        clock: &Arc<GameTime>,
49    ) {
50        let descriptor = WeakDamageSource::new(source);
51        let replaced = {
52            let mut state = self.state.lock();
53            let victim_lifetime = match &state.victim {
54                Some(bound) if ptr::eq(bound.history.as_ptr(), Arc::as_ptr(history)) => {
55                    Arc::downgrade(bound)
56                }
57                _ => {
58                    let bound = Arc::new(DamageHistoryVictim {
59                        generation,
60                        history: Arc::downgrade(history),
61                    });
62                    let lifetime = Arc::downgrade(&bound);
63                    state.victim = Some(bound);
64                    lifetime
65                }
66            };
67            let record = DamageRecord {
68                victim_lifetime,
69                owner: Weak::clone(&state.owner),
70                retained: (!state.removed && state.owner.strong_count() > 0)
71                    .then(|| source.clone()),
72                source: descriptor,
73                timestamp: clock.ticks(),
74                clock: Arc::clone(clock),
75            };
76            history.records.lock().insert(generation, record)
77        };
78        // Dropping sources can destroy entities; release both locks first.
79        drop(replaced);
80    }
81
82    /// Both the source and the upgraded history can own entities. Drop them outside
83    /// the entity lifecycle lock, including when this is the last history handle.
84    pub(crate) fn release_on_removal(&self) -> Option<(Arc<DamageHistory>, Option<DamageSource>)> {
85        let mut state = self.state.lock();
86        state.removed = true;
87        let victim = state.victim.as_ref()?;
88        let history = victim.history.upgrade()?;
89        let released = history
90            .records
91            .lock()
92            .get_mut(&victim.generation)
93            .and_then(|record| record.retained.take());
94        Some((history, released))
95    }
96
97    pub(crate) fn clear_removed(&self) {
98        self.state.lock().removed = false;
99    }
100
101    pub(crate) fn last_damage_source(&self) -> Option<RecentDamageSource> {
102        let (history, victim) = self.bound_history()?;
103        history.last_damage_source(victim)
104    }
105
106    /// Clears transient history when a target domain's state is restored.
107    pub(crate) fn clear(&self) {
108        if let Some((history, victim)) = self.bound_history() {
109            history.clear_victim(victim);
110        }
111    }
112
113    fn bound_history(&self) -> Option<(Arc<DamageHistory>, EntityGeneration)> {
114        let state = self.state.lock();
115        let bound = state.victim.as_ref()?;
116        Some((bound.history.upgrade()?, bound.generation))
117    }
118}