dynamis-broadphase 0.7.0

Spatial grid entries and coarse collision pair generation
Documentation
use super::streams::{BroadphaseDemand, BroadphaseStreams};
use dynamis_abi::{
    COUNTER_ENTRIES, COUNTER_PAIRS, COUNTER_SPILLOVER_ENTRIES, COUNTER_SPILLOVER_PAIRS, Counters,
    ENTRY_CELLS_PER_PARTICLE, MAX_CELLS_PER_COLLIDER,
};
use dynamis_domain::{MIN_SLOTS, STREAM_FLOOR, StreamWatch, product};

const STREAM_DENSITY_PAIRS: u32 = 16;
const PLAN_COOLDOWN: u32 = 10;

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BroadphaseCapacity {
    pub entries: u32,
    pub pairs: u32,
}

#[derive(Clone, Copy, Debug)]
pub struct BroadphaseInputs {
    pub colliders: u32,
    pub particles: u32,
    pub pending_commands: bool,
}

pub struct Capacity {
    entries: StreamWatch,
    pairs: StreamWatch,
    cooldown: u32,
}

impl Default for Capacity {
    fn default() -> Self {
        Self::new()
    }
}

impl Capacity {
    pub const fn new() -> Self {
        Self {
            entries: StreamWatch::IDLE,
            pairs: StreamWatch::IDLE,
            cooldown: 0,
        }
    }

    pub fn plan(
        &mut self,
        measured: &Counters,
        inputs: &BroadphaseInputs,
        current: &BroadphaseStreams,
    ) -> (BroadphaseDemand, bool) {
        let open = self.cooldown == 0;
        self.pairs.observe(
            measured[COUNTER_PAIRS],
            measured[COUNTER_SPILLOVER_PAIRS] > 0,
            current.pair_major.slots(),
        );
        self.entries.observe(
            measured[COUNTER_ENTRIES],
            measured[COUNTER_SPILLOVER_ENTRIES] > 0,
            current.entry_keys.slots(),
        );
        let pressured = self.pairs.pressured() || self.entries.pressured();
        let idle = open
            && !pressured
            && self.pairs.is_idle()
            && self.entries.is_idle()
            && !inputs.pending_commands;
        if pressured {
            self.entries.settle(false);
            self.pairs.settle(false);
        } else if idle {
            self.entries.settle(true);
            self.pairs.settle(true);
            self.cooldown = PLAN_COOLDOWN;
        } else {
            self.cooldown = self.cooldown.saturating_sub(1);
        }
        let entry_budget = product(
            inputs.colliders,
            MAX_CELLS_PER_COLLIDER,
            "grid collider entry",
        )
        .checked_add(product(
            inputs.particles,
            ENTRY_CELLS_PER_PARTICLE,
            "grid particle entry",
        ))
        .unwrap_or_else(|| panic!("grid entry capacity exceeds the device index space"));
        let pair_budget = product(inputs.colliders, STREAM_DENSITY_PAIRS, "pair");
        let entries = if idle {
            self.entries
                .released(current.entry_keys.slots(), entry_budget)
        } else {
            self.entries
                .widened(current.entry_keys.slots(), entry_budget)
        };
        let pairs = if idle {
            self.pairs.released(current.pair_major.slots(), pair_budget)
        } else {
            self.pairs.widened(current.pair_major.slots(), pair_budget)
        };
        let sort = entries.max(pairs).max(MIN_SLOTS);
        (
            BroadphaseDemand {
                entries,
                pairs,
                sort,
            },
            idle,
        )
    }

    pub fn floor() -> BroadphaseDemand {
        BroadphaseDemand {
            entries: STREAM_FLOOR,
            pairs: STREAM_FLOOR,
            sort: STREAM_FLOOR,
        }
    }
}