reaction 0.2.0

Universal low-latency input handling for game engines
Documentation
use crate::mapping::action::InputAction;
use crate::mapping::resolver::InputMap;
use crate::state::advanced::AdvancedInputState;
use crate::types::GamepadId;

#[derive(Clone)]
pub struct InputContext {
    pub priority: i32,
    pub mappings: InputMap,
    pub active: bool,
    pub blocks_lower_priority: bool,
    pub blocks_equal_priority: bool, // ✅ NEW: blocking logic
}

impl InputContext {
    pub fn new(priority: i32, mappings: InputMap) -> Self {
        Self {
            priority,
            mappings,
            active: true,
            blocks_lower_priority: false,
            blocks_equal_priority: false,
        }
    }

    pub fn with_blocking(mut self, lower: bool, equal: bool) -> Self {
        self.blocks_lower_priority = lower;
        self.blocks_equal_priority = equal;
        self
    }
}

pub struct ContextStack {
    contexts: Vec<InputContext>,
    allow_multiple_handlers: bool, // ✅ NEW: sum vs first-handler
}

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

impl ContextStack {
    pub fn new() -> Self {
        Self {
            contexts: Vec::new(),
            allow_multiple_handlers: false,
        }
    }

    pub fn set_allow_multiple_handlers(&mut self, allow: bool) {
        self.allow_multiple_handlers = allow;
    }

    pub fn push(&mut self, context: InputContext) {
        self.contexts.push(context);
        self.sort();
    }

    fn sort(&mut self) {
        // High priority first
        self.contexts.sort_by(|a, b| b.priority.cmp(&a.priority));
    }

    pub fn resolve(
        &self,
        action: InputAction,
        input: &AdvancedInputState,
        gamepad_id: Option<GamepadId>,
    ) -> f32 {
        if self.allow_multiple_handlers {
            // ✅ Sum all active contexts' values
            let mut total = 0.0;
            let mut blocked_by_higher = false;
            let mut last_priority = i32::MAX;

            for context in &self.contexts {
                if !context.active {
                    continue;
                }

                if blocked_by_higher {
                    if context.priority < last_priority {
                        // Priority is strictly lower than the blocking context
                        break;
                    }
                    if context.priority == last_priority && context.blocks_equal_priority {
                        // Priority is equal and blocking
                        continue;
                    }
                }

                let val = context.mappings.resolve(action, input, gamepad_id);
                total += val;

                if context.blocks_lower_priority || context.blocks_equal_priority {
                    blocked_by_higher = true;
                    last_priority = context.priority;
                }
            }
            total.clamp(-1.0, 1.0)
        } else {
            // ✅ Original: first active handler wins
            for context in &self.contexts {
                if !context.active {
                    continue;
                }

                let val = context.mappings.resolve(action, input, gamepad_id);
                if val.abs() > 0.0 {
                    return val;
                }

                if context.blocks_lower_priority {
                    break;
                }
            }
            0.0
        }
    }
}