nightshade 0.14.2

A cross-platform data-oriented game engine.
Documentation
//! Developer console for runtime debugging and commands.
//!
//! An in-game terminal for executing commands, inspecting entities, and tweaking settings:
//!
//! - [`ShellState`]: Console state (visibility, input, history, commands)
//! - [`Command`]: Plain-data record describing a command (name, usage, function pointer)
//! - [`CommandRegistry`]: Built-in and app-specific command storage
//! - [`shell_retained_ui`]: Render console with retained UI overlay
//!
//! # Opening the Console
//!
//! Toggle with `Alt+C`. Close with `Escape` or `Alt+C` again.
//!
//! # Basic Setup
//!
//! ```ignore
//! struct MyGame {
//!     shell: ShellState<()>,
//! }
//!
//! impl State for MyGame {
//!     fn on_start(&mut self, world: &mut World) {
//!         self.shell.register_builtin_commands();
//!     }
//!
//!     fn on_key_input(&mut self, world: &mut World, key: KeyCode, state: KeyState) {
//!         if self.shell.visible {
//!             self.shell.handle_key(key, state == KeyState::Pressed);
//!         }
//!     }
//!
//!     fn on_char_input(&mut self, world: &mut World, ch: char) {
//!         if self.shell.visible && ch.is_ascii_graphic() || ch == ' ' {
//!             self.shell.input_buffer.push(ch);
//!         }
//!     }
//!
//!     fn run_systems(&mut self, world: &mut World) {
//!         self.shell.update_animation(world.resources.window.timing.delta_time);
//!         shell_retained_ui(&mut self.shell, world);
//!     }
//! }
//! ```
//!
//! # Creating Custom Commands
//!
//! Define a plain function and register it as a [`Command`]:
//!
//! ```ignore
//! fn debug_command(args: &[&str], world: &mut World, _ctx: &mut ()) -> String {
//!     if args.is_empty() {
//!         return format!("Debug: {}", world.resources.graphics.debug_mode);
//!     }
//!     match args[0] {
//!         "on" => world.resources.graphics.debug_mode = true,
//!         "off" => world.resources.graphics.debug_mode = false,
//!         _ => return "Usage: debug [on|off]".to_string(),
//!     }
//!     "OK".to_string()
//! }
//!
//! shell.registry.register(Command {
//!     name: "debug",
//!     description: "Toggle debug mode",
//!     usage: "debug [on|off]",
//!     execute: debug_command,
//! });
//! ```
//!
//! # Using Context for State
//!
//! Pass game-specific state via the generic `C` type:
//!
//! ```ignore
//! struct GameContext {
//!     score: i32,
//! }
//!
//! fn score_command(args: &[&str], _world: &mut World, ctx: &mut GameContext) -> String {
//!     if let Some(value) = args.first() {
//!         ctx.score = value.parse().unwrap_or(0);
//!     }
//!     format!("Score: {}", ctx.score)
//! }
//!
//! let mut shell: ShellState<GameContext> = ShellState::new(GameContext { score: 0 });
//! shell.registry.register(Command {
//!     name: "score",
//!     description: "Get or set score",
//!     usage: "score [value]",
//!     execute: score_command,
//! });
//! ```
//!
//! # Built-in Commands
//!
//! Register with [`ShellState::register_builtin_commands`]:
//!
//! | Command | Description |
//! |---------|-------------|
//! | `help [cmd]` | List commands or show command help |
//! | `clear` | Clear console output |
//! | `spawn_cube <x> <y> <z>` | Spawn a cube at position |
//! | `spawn_sphere <x> <y> <z>` | Spawn a sphere at position |
//! | `spawn_cylinder <x> <y> <z>` | Spawn a cylinder at position |
//! | `spawn_cone <x> <y> <z>` | Spawn a cone at position |
//! | `spawn_torus <x> <y> <z>` | Spawn a torus at position |
//! | `spawn_plane <x> <y> <z>` | Spawn a plane at position |
//! | `grid [on\|off]` | Toggle grid visibility |
//! | `atmosphere <type>` | Set atmosphere (none, sky, cloudy, space, nebula, sunset) |
//! | `fov [degrees]` | Get or set camera FOV |
//! | `timescale [value]` | Get or set time speed (1.0 = normal) |
//! | `fps` | Show current framerate |
//! | `pos` | Show camera position |
//! | `tp <x> <y> <z>` | Teleport camera to position |
//! | `unlit [on\|off]` | Toggle unlit rendering |
//! | `bloom [on\|off\|intensity]` | Toggle or set bloom |
//! | `rotate <entity> <x> <y> <z>` | Rotate entity by degrees |
//! | `scale <entity> <factor>` | Scale entity uniformly |
//! | `move <entity> <x> <y> <z>` | Move entity to position |
//! | `delete <entity>` | Delete an entity |
//! | `inspect <entity>` | Show entity information |
//! | `quit` | Exit the application |
//!
//! # Entity References
//!
//! Entities are referenced as `entity_<id>_<generation>`:
//!
//! ```ignore
//! > spawn_cube 0 1 0
//! Spawned cube at (0, 1, 0)
//!
//! > inspect entity_5_0
//! Entity: entity_5_0
//! Position: (0.00, 1.00, 0.00)
//! Scale: (1.00, 1.00, 1.00)
//!
//! > rotate entity_5_0 0 45 0
//! Rotated entity_5_0 by (0, 45, 0) degrees
//! ```
//!
//! # Inserting Entity References
//!
//! Insert entities from picking results:
//!
//! ```ignore
//! if let Some(result) = pick_closest_entity(world, &ray, &options) {
//!     shell.insert_entity(result.entity);
//! }
//! ```
//!
//! # Console Features
//!
//! - Command history: `Up`/`Down` arrows cycle through history
//! - Resizable: Drag the bottom edge to resize
//! - Scrollable: Output area scrolls automatically
//! - Smooth animation: Slides in/out when toggling
//!
//! [`ShellState`]: ShellState
//! [`Command`]: Command
//! [`CommandRegistry`]: CommandRegistry
//! [`shell_retained_ui`]: shell_retained_ui

mod commands;
mod retained_ui;
mod state;

use crate::ecs::world::{Entity, World};

pub use self::commands::builtin_commands;
pub use self::retained_ui::*;
pub use self::state::*;

pub fn format_entity(entity: Entity) -> String {
    format!("entity_{}_{}", entity.id, entity.generation)
}

pub fn parse_entity(s: &str) -> Option<Entity> {
    let parts: Vec<&str> = s.split('_').collect();
    if parts.len() != 3 || parts[0] != "entity" {
        return None;
    }
    let id: u32 = parts[1].parse().ok()?;
    let generation: u32 = parts[2].parse().ok()?;
    Some(Entity { id, generation })
}

pub type CommandFn<C> = fn(&[&str], &mut World, &mut C) -> String;

pub struct Command<C> {
    pub name: &'static str,
    pub description: &'static str,
    pub usage: &'static str,
    pub execute: CommandFn<C>,
}

impl<C> Copy for Command<C> {}

impl<C> Clone for Command<C> {
    fn clone(&self) -> Self {
        *self
    }
}

pub struct CommandRegistry<C> {
    pub builtin: Vec<Command<C>>,
    pub app: Vec<Command<C>>,
}

impl<C> Default for CommandRegistry<C> {
    fn default() -> Self {
        Self::new()
    }
}

impl<C> CommandRegistry<C> {
    pub fn new() -> Self {
        Self {
            builtin: Vec::new(),
            app: Vec::new(),
        }
    }

    pub fn register(&mut self, command: Command<C>) {
        debug_assert!(
            !self.contains(command.name),
            "shell command \"{}\" already registered",
            command.name
        );
        self.app.push(command);
    }

    pub fn register_builtin(&mut self, command: Command<C>) {
        debug_assert!(
            !self.contains(command.name),
            "shell command \"{}\" already registered",
            command.name
        );
        self.builtin.push(command);
    }

    pub fn get(&self, name: &str) -> Option<&Command<C>> {
        self.builtin
            .iter()
            .chain(self.app.iter())
            .find(|command| command.name == name)
    }

    pub fn contains(&self, name: &str) -> bool {
        self.get(name).is_some()
    }

    pub fn builtin_command_names(&self) -> Vec<&str> {
        let mut names: Vec<&str> = self.builtin.iter().map(|command| command.name).collect();
        names.sort();
        names
    }

    pub fn app_command_names(&self) -> Vec<&str> {
        let mut names: Vec<&str> = self.app.iter().map(|command| command.name).collect();
        names.sort();
        names
    }
}

pub struct OutputLine {
    pub text: String,
    pub is_command: bool,
}