Skip to main content

dynamis_broadphase/
capacity.rs

1use super::streams::{BroadphaseDemand, BroadphaseStreams};
2use dynamis_abi::{
3    COUNTER_ENTRIES, COUNTER_PAIRS, COUNTER_SPILLOVER_ENTRIES, COUNTER_SPILLOVER_PAIRS, Counters,
4    ENTRY_CELLS_PER_PARTICLE, MAX_CELLS_PER_COLLIDER,
5};
6use dynamis_domain::{MIN_SLOTS, STREAM_FLOOR, StreamWatch, product};
7
8const STREAM_DENSITY_PAIRS: u32 = 16;
9const PLAN_COOLDOWN: u32 = 10;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub struct BroadphaseCapacity {
13    pub entries: u32,
14    pub pairs: u32,
15}
16
17#[derive(Clone, Copy, Debug)]
18pub struct BroadphaseInputs {
19    pub colliders: u32,
20    pub particles: u32,
21    pub pending_commands: bool,
22}
23
24pub struct Capacity {
25    entries: StreamWatch,
26    pairs: StreamWatch,
27    cooldown: u32,
28}
29
30impl Default for Capacity {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl Capacity {
37    pub const fn new() -> Self {
38        Self {
39            entries: StreamWatch::IDLE,
40            pairs: StreamWatch::IDLE,
41            cooldown: 0,
42        }
43    }
44
45    pub fn plan(
46        &mut self,
47        measured: &Counters,
48        inputs: &BroadphaseInputs,
49        current: &BroadphaseStreams,
50    ) -> (BroadphaseDemand, bool) {
51        let open = self.cooldown == 0;
52        self.pairs.observe(
53            measured[COUNTER_PAIRS],
54            measured[COUNTER_SPILLOVER_PAIRS] > 0,
55            current.pair_major.slots(),
56        );
57        self.entries.observe(
58            measured[COUNTER_ENTRIES],
59            measured[COUNTER_SPILLOVER_ENTRIES] > 0,
60            current.entry_keys.slots(),
61        );
62        let pressured = self.pairs.pressured() || self.entries.pressured();
63        let idle = open
64            && !pressured
65            && self.pairs.is_idle()
66            && self.entries.is_idle()
67            && !inputs.pending_commands;
68        if pressured {
69            self.entries.settle(false);
70            self.pairs.settle(false);
71        } else if idle {
72            self.entries.settle(true);
73            self.pairs.settle(true);
74            self.cooldown = PLAN_COOLDOWN;
75        } else {
76            self.cooldown = self.cooldown.saturating_sub(1);
77        }
78        let entry_budget = product(
79            inputs.colliders,
80            MAX_CELLS_PER_COLLIDER,
81            "grid collider entry",
82        )
83        .checked_add(product(
84            inputs.particles,
85            ENTRY_CELLS_PER_PARTICLE,
86            "grid particle entry",
87        ))
88        .unwrap_or_else(|| panic!("grid entry capacity exceeds the device index space"));
89        let pair_budget = product(inputs.colliders, STREAM_DENSITY_PAIRS, "pair");
90        let entries = if idle {
91            self.entries
92                .released(current.entry_keys.slots(), entry_budget)
93        } else {
94            self.entries
95                .widened(current.entry_keys.slots(), entry_budget)
96        };
97        let pairs = if idle {
98            self.pairs.released(current.pair_major.slots(), pair_budget)
99        } else {
100            self.pairs.widened(current.pair_major.slots(), pair_budget)
101        };
102        let sort = entries.max(pairs).max(MIN_SLOTS);
103        (
104            BroadphaseDemand {
105                entries,
106                pairs,
107                sort,
108            },
109            idle,
110        )
111    }
112
113    pub fn floor() -> BroadphaseDemand {
114        BroadphaseDemand {
115            entries: STREAM_FLOOR,
116            pairs: STREAM_FLOOR,
117            sort: STREAM_FLOOR,
118        }
119    }
120}