use nightshade::prelude::*;
pub(crate) const RESERVED_PREFIX: &str = "api::";
pub(crate) const CAMERA_NAME_PREFIX: &str = "api::camera::";
pub(crate) const CAMERA_ORBIT: &str = "api::camera::orbit";
pub(crate) const CAMERA_FLY: &str = "api::camera::fly";
#[cfg(feature = "physics")]
pub(crate) const CAMERA_FIRST_PERSON: &str = "api::camera::first_person";
pub(crate) const CAMERA_FIXED: &str = "api::camera::fixed";
#[cfg(feature = "physics")]
pub(crate) const PLAYER_NAME: &str = "api::player";
pub(crate) const SUN_NAME: &str = "api::sun";
pub(crate) const DRAW_MATERIAL: &str = "api::draw";
pub(crate) const DRAW_CUBE_POOL: &str = "api::draw::cube";
pub(crate) const DRAW_SPHERE_POOL: &str = "api::draw::sphere";
pub(crate) const DRAW_LINES_POOL: &str = "api::draw::lines";
pub(crate) const MATERIAL_PREFIX: &str = "api::material::";
type SetupFunction<Data> = Box<dyn FnOnce(&mut World) -> Data>;
type UpdateFunction<Data> = Box<dyn FnMut(&mut World, &mut Data)>;
pub(crate) fn register_named(world: &mut World, name: &str, entity: Entity) {
world
.resources
.entities
.names
.insert(name.to_string(), entity);
}
pub(crate) fn lookup_named(world: &mut World, name: &str) -> Option<Entity> {
let cached = world
.resources
.entities
.names
.get(name)
.copied()
.filter(|&entity| world.core.get_name(entity).is_some());
if cached.is_some() {
return cached;
}
let found = nightshade::ecs::world::commands::find_entity_by_name(world, name)?;
register_named(world, name, found);
Some(found)
}
pub(crate) struct ApiState<Data> {
pub(crate) setup: Option<SetupFunction<Data>>,
pub(crate) update: Option<UpdateFunction<Data>>,
pub(crate) data: Option<Data>,
pub(crate) clears_draw_pools: bool,
pub(crate) frame_limit: Option<u32>,
pub(crate) frames_rendered: u32,
}
pub(crate) fn frame_limit_from_environment() -> Option<u32> {
std::env::var("NIGHTSHADE_API_FRAMES")
.ok()
.and_then(|value| value.parse().ok())
}
impl<Data: 'static> State for ApiState<Data> {
fn initialize(&mut self, world: &mut World) {
apply_defaults(world);
if let Some(setup) = self.setup.take() {
self.data = Some(setup(world));
}
}
fn run_systems(&mut self, world: &mut World) {
if let Some(limit) = self.frame_limit {
self.frames_rendered += 1;
if self.frames_rendered >= limit {
world.resources.window.should_exit = true;
}
}
escape_key_exit_system(world);
run_camera_systems(world);
if self.clears_draw_pools {
crate::draw::clear_draw_pools(world);
}
if let (Some(update), Some(data)) = (self.update.as_mut(), self.data.as_mut()) {
update(world, data);
}
}
}
fn apply_defaults(world: &mut World) {
world.resources.render_settings.atmosphere = Atmosphere::Sky;
world.resources.debug_draw.show_grid = true;
#[cfg(feature = "physics")]
{
world.resources.physics.enabled = true;
}
let sun = spawn_sun(world);
world.core.set_name(sun, Name(SUN_NAME.to_string()));
register_named(world, SUN_NAME, sun);
load_procedural_textures(world);
crate::draw::initialize_draw_pools(world);
crate::camera::orbit_camera(world, Vec3::zeros(), 8.0);
}
fn run_camera_systems(world: &mut World) {
let Some(camera) = world.resources.active_camera else {
return;
};
let drives_controllers = world
.core
.get_name(camera)
.is_some_and(|name| name.0 == CAMERA_ORBIT || name.0 == CAMERA_FLY);
if drives_controllers {
camera_controllers_system(world);
}
#[cfg(feature = "physics")]
{
let drives_character = world
.core
.get_name(camera)
.is_some_and(|name| name.0 == CAMERA_FIRST_PERSON);
if drives_character {
first_person_camera_look_system(world);
}
}
}
pub fn run<Data: 'static>(
setup: impl FnOnce(&mut World) -> Data + 'static,
update: impl FnMut(&mut World, &mut Data) + 'static,
) -> Result<(), Box<dyn std::error::Error>> {
launch(ApiState {
setup: Some(Box::new(setup)),
update: Some(Box::new(update)),
data: None,
clears_draw_pools: true,
frame_limit: frame_limit_from_environment(),
frames_rendered: 0,
})
}
pub fn run_scene(
setup: impl FnOnce(&mut World) + 'static,
) -> Result<(), Box<dyn std::error::Error>> {
run(setup, |_, _| {})
}