mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! Actions: what a player can do, and the controls bound to each one.
//!
//! A game names its own actions — `Jump`, `Walk`, `Aim` — in up to three
//! vocabularies, one per kind: a button is pressed, released or down, an
//! axis reads one number, an axis2 reads two. [`InputActions`] holds the
//! three, and [`Game::InputActions`](crate::Game::InputActions) names the
//! set; [`NoInputActions`] is the set of a game that reads no input, and
//! [`Key`] is the prototype set that binds each key to itself. Every query
//! — `down`, `pressed`, `released`, `axis`, `axis2` — takes an action of
//! that set and no other.
//!
//! Each action declares its own default bindings, which may hold a key, a
//! mouse button, a pad button, a stick or a composite of them. An axis or
//! axis2 binding holds a deadzone —
//! [`AxisBinding::DEFAULT_DEADZONE`](crate::AxisBinding::DEFAULT_DEADZONE)
//! for a pad or joystick control, and zero for the rest — and a scale.
//! A [`PointerDelta`] lane, a [`WheelDelta`] lane and
//! [`Axis2Binding::pointer`](crate::Axis2Binding::pointer) read how far
//! they moved through that scale and nothing clamps what they read; every
//! other binding reads in its kind's own range.
//!
//! A game that rebinds reads [`bindings`](crate::FrameContext::bindings) for
//! what an action is bound to now, and shows each binding's text.
//! [`actuated_button`](crate::FrameContext::actuated_button),
//! [`actuated_axis`](crate::FrameContext::actuated_axis) and
//! [`actuated_axis2`](crate::FrameContext::actuated_axis2) then report
//! whatever the player just moved, which
//! [`rebind`](crate::FrameContext::rebind) takes. The engine keeps every
//! rebind in the platform's own store, under a name made from the title
//! [`Config::new`](crate::Config::new) was given.
//!
//! [`Cursor`] is what the pointer itself is drawn as, the closed set
//! [`set_cursor`](crate::FrameContext::set_cursor) takes for one frame; a
//! frame that sets none draws [`Cursor::Arrow`]. Where the UI sets a cursor
//! of its own, the UI's is drawn instead. [`Cursor::Held`] draws no pointer
//! and holds it in place, leaving a [`PointerDelta`] binding its
//! movement to read and [`pointer`](crate::FrameContext::pointer) the place
//! it was held at; the frame after it sets another cursor releases it.

pub use action::{
    InputAction, InputActions, InputAxis2Action, InputAxisAction, InputButtonAction,
    NoInputActions, NoInputAxes, NoInputAxes2, NoInputButtons,
};
pub use binding::{
    Axis2Binding, AxisBinding, ButtonAxis, ButtonAxis2, ButtonBinding, JoystickControl, Key,
    MouseButton, Pad, PadAxis, PointerDelta, Stick, WheelDelta,
};
pub use cursor::Cursor;

#[cfg(feature = "offscreen")]
pub use state::Switch;

pub(crate) use pad::Pads;
pub(crate) use state::{Controls, Devices, WheelRate};

use core::num::NonZeroU32;

use crate::math::Vec2;
use state::Ticks;
use table::{Rebound, Table};

/// The game thread's own input: every action's bindings, whether a rebind
/// changed one since the last flush, the [`Controls`] every query reads
/// from, and what the ticks read of them.
pub(crate) struct Queries {
    table: Table,
    controls: Controls,
    ticks: Ticks,
    dirty: bool,
}

impl Queries {
    /// The queries a run starts with: the game's actions at their declared
    /// bindings, with whatever the store `kept` of an earlier run over
    /// them, and controls reading nothing.
    pub(crate) fn new<A: InputActions>(kept: Option<&str>) -> Self {
        Self {
            table: Table::new::<A>(kept),
            controls: Controls::default(),
            ticks: Ticks::default(),
            dirty: false,
        }
    }

    /// Takes `controls` as what every query reads from, and folds them
    /// into what the ticks read.
    pub(crate) fn take(&mut self, controls: Controls) {
        self.ticks.fold(&controls);
        self.controls = controls;
    }

    /// Runs `tick` `count` times, on the edges since the last frame whose
    /// ticks ran.
    ///
    /// Each call reads the same edges; no later tick reads them again.
    pub(crate) fn ticks(&mut self, count: NonZeroU32, mut tick: impl FnMut(&Ticking<'_>)) {
        let ticking = Ticking { queries: self };
        for _ in 0..count.get() {
            tick(&ticking);
        }
    }

    pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
        self.controls.frame().down(self.table.resolve(action))
    }

    pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
        self.controls.frame().pressed(self.table.resolve(action))
    }

    pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
        self.controls.frame().released(self.table.resolve(action))
    }

    pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
        match self.pressed(action) {
            true => self.controls.clicks(self.table.resolve(action)),
            false => 0,
        }
    }

    pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
        self.controls.frame().axis(self.table.resolve(action))
    }

    pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
        self.controls.frame().axis2(self.table.resolve(action))
    }

    pub(crate) fn pointer(&self) -> Vec2 {
        self.controls.frame().pointer()
    }

    pub(crate) fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding> {
        self.table.resolve(action).to_vec()
    }

    /// Binds `action` to `bindings` and marks the store to be written where
    /// that is not what the action already read through.
    pub(crate) fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>) {
        match self.table.rebind(action, bindings) {
            Rebound::Changed => self.dirty = true,
            Rebound::Unchanged => {}
        }
    }

    /// The bindings as the store keeps them where a rebind changed one
    /// since the last call, for the display thread to write, and nothing at
    /// all where none did.
    pub(crate) fn flush(&mut self) -> Option<String> {
        core::mem::take(&mut self.dirty).then(|| self.table.written())
    }

    pub(crate) fn actuated_button(&self) -> Option<ButtonBinding> {
        self.controls.frame().actuated_button()
    }

    pub(crate) fn actuated_axis(&self) -> Option<AxisBinding> {
        self.controls.frame().actuated_axis()
    }

    pub(crate) fn actuated_axis2(&self) -> Option<Axis2Binding> {
        self.controls.frame().actuated_axis2()
    }

    /// Whether `action` went down since the last frame whose ticks ran.
    /// Only [`Ticking`] calls this.
    fn pressed_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
        self.ticks.snapshot().pressed(self.table.resolve(action))
    }

    /// Whether `action` came up since the last frame whose ticks ran. Only
    /// [`Ticking`] calls this.
    fn released_over_ticks<A: InputButtonAction>(&self, action: A) -> bool {
        self.ticks.snapshot().released(self.table.resolve(action))
    }

    /// Presses in a row the press these ticks read is the last of. Only
    /// [`Ticking`] calls this.
    fn clicks_over_ticks<A: InputButtonAction>(&self, action: A) -> u32 {
        match self.pressed_over_ticks(action) {
            true => self.controls.clicks(self.table.resolve(action)),
            false => 0,
        }
    }

    /// Ends the ticks of one frame, so no later tick reads their edges.
    /// Only dropping a [`Ticking`] calls this.
    fn ticked(&mut self) {
        self.ticks.ticked();
    }
}

/// The input the ticks of one frame read. [`Queries::ticks`] is the only
/// source of one.
///
/// Dropping it ends those ticks, so no later tick reads an edge these ticks
/// already read.
pub(crate) struct Ticking<'a> {
    queries: &'a mut Queries,
}

impl Ticking<'_> {
    pub(crate) fn down<A: InputButtonAction>(&self, action: A) -> bool {
        self.queries.down(action)
    }

    pub(crate) fn pressed<A: InputButtonAction>(&self, action: A) -> bool {
        self.queries.pressed_over_ticks(action)
    }

    pub(crate) fn released<A: InputButtonAction>(&self, action: A) -> bool {
        self.queries.released_over_ticks(action)
    }

    pub(crate) fn clicks<A: InputButtonAction>(&self, action: A) -> u32 {
        self.queries.clicks_over_ticks(action)
    }

    pub(crate) fn axis<A: InputAxisAction>(&self, action: A) -> f32 {
        self.queries.axis(action)
    }

    pub(crate) fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2 {
        self.queries.axis2(action)
    }

    pub(crate) fn pointer(&self) -> Vec2 {
        self.queries.pointer()
    }
}

impl Drop for Ticking<'_> {
    fn drop(&mut self) {
        self.queries.ticked();
    }
}

mod action;
mod binding;
mod cursor;
mod pad;
mod state;
mod table;

#[cfg(test)]
mod tests {
    use core::time::Duration;

    use super::*;
    use winit::event::{DeviceId, ElementState, WindowEvent};

    /// The interval these tests count a double click by.
    const DOUBLE_CLICK: Duration = Duration::from_millis(400);

    /// More than one tick in a frame, so a test can check what each tick
    /// reads.
    const TICKS: NonZeroU32 = match NonZeroU32::new(3) {
        Some(ticks) => ticks,
        None => NonZeroU32::MIN,
    };

    /// A run reading through the keyboard as its own vocabulary, keeping
    /// nothing between runs: the devices of its display thread and the
    /// queries of its game thread.
    fn run() -> (Devices, Queries) {
        (
            Devices::new(Pads::silent(), DOUBLE_CLICK),
            Queries::new::<Key>(None),
        )
    }

    /// The primary mouse button pressed, which is the one control a test can
    /// press: `winit` keeps a keyboard event's platform field private, so no
    /// other crate builds one.
    fn click() -> WindowEvent {
        WindowEvent::MouseInput {
            device_id: DeviceId::dummy(),
            state: ElementState::Pressed,
            button: winit::event::MouseButton::Left,
        }
    }

    /// What each of the `count` ticks of one frame reads for `action`.
    fn pressed_per_tick(queries: &mut Queries, count: NonZeroU32, action: Key) -> Vec<bool> {
        let mut read = Vec::new();
        queries.ticks(count, |ticking| read.push(ticking.pressed(action)));
        read
    }

    #[test]
    fn only_a_rebind_that_changed_something_leaves_the_store_to_write() {
        let (_, mut queries) = run();

        queries.rebind(Key::Space, vec![Key::Space.into()]);
        assert!(!queries.dirty, "what it read through already is no change");

        queries.rebind(Key::Space, vec![Key::Enter.into()]);
        assert!(queries.dirty);
        assert!(
            queries.flush().is_some(),
            "and the text to write is handed out"
        );

        assert!(
            queries.flush().is_none(),
            "so the store is written once, not per call"
        );
        assert_eq!(queries.bindings(Key::Space), vec![Key::Enter.into()]);
    }

    #[test]
    fn the_queries_answer_from_the_controls_alone_and_count_clicks_across_them() {
        let (mut devices, mut queries) = run();
        queries.rebind(Key::Space, vec![MouseButton::Left.into()]);
        devices.see(&click());
        queries.take(devices.sample(Duration::ZERO));
        let first = (queries.pressed(Key::Space), queries.clicks(Key::Space));

        devices.press(MouseButton::Left.into(), false);
        devices.sample(Duration::ZERO);
        devices.press(MouseButton::Left.into(), true);
        queries.take(devices.sample(Duration::from_millis(100)));

        assert_eq!(
            (queries.pressed(Key::Space), queries.clicks(Key::Space)),
            (first.0, first.1 + 1),
            "the same press read through the controls, one click further on"
        );
        assert!(
            queries.pressed_over_ticks(Key::Space),
            "and the ticks' edge crosses in the same controls"
        );
    }

    #[test]
    fn the_controls_are_plain_data_that_cross_between_threads() {
        fn crosses<T: Send + Clone>() {}
        crosses::<Controls>();
    }

    #[test]
    fn the_ticks_of_one_frame_read_an_edge_and_the_ticks_after_them_read_none() {
        let (mut devices, mut queries) = run();
        queries.rebind(Key::Space, vec![MouseButton::Left.into()]);

        devices.see(&click());
        queries.take(devices.sample(Duration::ZERO));
        queries.take(devices.sample(Duration::ZERO));

        assert_eq!(
            pressed_per_tick(&mut queries, TICKS, Key::Space),
            [true; 3],
            "every tick of the frame that reads the press reads it"
        );

        queries.take(devices.sample(Duration::ZERO));
        assert_eq!(
            pressed_per_tick(&mut queries, TICKS, Key::Space),
            [false; 3],
            "and the ticks that follow read it no more"
        );
    }
}