Skip to main content

Crate nightshade_api

Crate nightshade_api 

Source
Expand description

§nightshade-api

A procedural high level API over the nightshade engine. Write a full 3d scene or a small game as straight-line code with free functions and plain data. No trait to implement, no callbacks to wire up, no ECS knowledge required to get started.

A spinning cube:

use nightshade_api::prelude::*;

fn main() {
    let mut app = open();
    let cube = spawn_cube(&mut app.world, vec3(0.0, 0.5, 0.0));
    while frame(&mut app) {
        let step = delta_time(&app.world);
        rotate(&mut app.world, cube, Vec3::y(), step);
    }
}

Add to Cargo.toml:

nightshade-api = "0.50"

§What you get for free

prelude::open gives you a window with a sky, a sun with shadows, a reference grid, an orbit camera focused on the origin, prototype textures like "checkerboard", and escape to exit. Every program starts from a lit, navigable scene. Override any of it with one call: prelude::set_background, prelude::show_grid, prelude::fly_camera, prelude::set_sun.

§The two entry points

Own the loop (native only). Setup is ordinary code before the loop, state is ordinary locals across loop iterations:

let mut app = open();
let mut score = 0;
while frame(&mut app) {
    score += 1;
}

Or hand the engine the loop with run, which also works on wasm. Setup returns your state, the update closure receives it back every frame:

run(
    |world| spawn_cube(world, vec3(0.0, 0.5, 0.0)),
    |world, cube| {
        let step = delta_time(world);
        rotate(world, *cube, Vec3::y(), step);
    },
)
.unwrap();

run returns a Result, so a real main returns it. For several per-frame jobs, the run! macro takes any number of update systems:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    run!(setup, handle_input, move_player, check_collisions)
}

For immediate-mode UI, enable the egui feature, call enable_egui once in setup, then draw from your update closure by pulling the frame’s context with egui_context. No extra closure: it composes with run and run! as is. egui is re-exported from the prelude.

run(
    |world| { enable_egui(world); spawn_cube(world, vec3(0.0, 0.5, 0.0)) },
    |world, cube| {
        rotate(world, *cube, Vec3::y(), delta_time(world));
        if let Some(ctx) = egui_context(world) {
            egui::Window::new("Inspector").show(&ctx, |ui| ui.label(format!("{cube:?}")));
        }
    },
)
.unwrap();

§Vocabulary

Two verbs carry the lifetime rules. spawn_ is retained: the thing exists until you prelude::despawn it. draw_ is immediate: visible for exactly one frame, redraw it every frame you want it on screen.

§Reading the scene back

The setters have readers, which is what a tool that edits a scene rather than just building one needs. prelude::describe_entity gathers an entity’s whole editable state, prelude::color and friends read one field, prelude::scene_tree and prelude::children walk the hierarchy, prelude::list_materials reads the shared material registry, and prelude::bounds_of with prelude::frame_entities measure and frame a selection. prelude::save_scene and prelude::load_scene round-trip the whole world to bytes. Components are added, removed, and snapshotted for undo by prelude::ComponentKind, the surface the standalone editor is built on.

§Commands

Every call also has a data form. prelude::Command is a serde enum with one variant per function, prelude::submit_command runs one, and prelude::submit_commands runs a batch where a later command can name an entity an earlier one produced with prelude::Ref::Result, so one batch builds and wires up a scene. The enum is the wire format a binding targets: build Command values, read prelude::CommandReply back, with the json schema from prelude::command_schema. The free functions stay the real implementations and the dispatch forwards to them. See the commands example and docs/COMMAND_API.md.

§Dropping down to the engine

Every function here takes the real engine prelude::World and bottoms out in normal nightshade calls. Nothing is hidden behind a wrapper type, so when a program outgrows the facade you replace one call site at a time. The full engine is re-exported at nightshade, one path away:

use nightshade_api::nightshade::prelude::*;

§Two worlds

A game that outgrows plain entities can keep its own ECS world beside the engine’s. Declare a second world with the freecs::ecs! macro and let the engine World own rendering and transforms while your world holds movement, behavior, and rules as plain components and systems. Link the two by storing the engine Entity in an EngineEntity component on each game entity, then once a frame push your game state into the engine with ordinary facade calls like set_position. Both freecs and EngineEntity come from the prelude, so use nightshade_api::prelude::* is enough to define and drive the second world. The engine side stays plain data and remains the same command surface every other call uses, while the game side is ordinary Rust. The dual_world example builds the whole pattern end to end.

§Web apps

For a browser app the engine runs in a web worker on an OffscreenCanvas and a Leptos page drives it from the main thread. Both halves of that seam are features of this crate: offscreen is the worker side (offscreen::run_offscreen owns the canvas, the world, the render loop, and the page conversation), and leptos is the page side (the web module, an engine-free Leptos component library with a worker-backed EngineViewport, usable with default-features = false). The shared wire types are in wire. The template_leptos and template_api_leptos directories are the working references.

§Examples

The examples/ directory is the tour. Run one with just run-example solar_system from the repo root, or cargo run -r -p nightshade-api --example solar_system. Every example also runs in the browser with just run-example-wasm solar_system, which serves it through trunk.

Re-exports§

pub use nightshade;

Modules§

editor
Types the standalone editor shares across its processes (the page, the engine worker, and the desktop bridge), behind the protocol feature. The agent surface is gated separately behind protocol-agent.
prelude
Everything in one import.
web
Leptos (CSR) components for building the page around an engine viewport, plus a general set of themed UI patterns: panels, forms, menus, tables, trees, a command palette, keybindings, a code editor, toasts, a status bar, drag and drop, and a resizable app shell, all driven from one set of CSS custom properties.
wire
Leptos-free wire types spoken between a page and its render worker, compiled under either the leptos or the offscreen feature. Pixel quantities are physical surface pixels (CSS pixels times the device pixel ratio), origin at the canvas top-left. Application-defined messages ride the Custom variants as JSON values.

Macros§

run
Runs a program from a setup expression and one or more per-frame update expressions, the variadic form of run. Setup runs once and returns your state; each update runs every frame, in the order given, and receives that state. Returns the same Result as run, so main can return it.