1use nightshade::prelude::*;
4
5pub(crate) const RESERVED_PREFIX: &str = "api::";
6pub(crate) const CAMERA_NAME_PREFIX: &str = "api::camera::";
7pub(crate) const CAMERA_ORBIT: &str = "api::camera::orbit";
8pub(crate) const CAMERA_FLY: &str = "api::camera::fly";
9#[cfg(feature = "physics")]
10pub(crate) const CAMERA_FIRST_PERSON: &str = "api::camera::first_person";
11pub(crate) const CAMERA_FIXED: &str = "api::camera::fixed";
12#[cfg(feature = "physics")]
13pub(crate) const PLAYER_NAME: &str = "api::player";
14pub(crate) const SUN_NAME: &str = "api::sun";
15pub(crate) const DRAW_MATERIAL: &str = "api::draw";
16pub(crate) const DRAW_CUBE_POOL: &str = "api::draw::cube";
17pub(crate) const DRAW_SPHERE_POOL: &str = "api::draw::sphere";
18pub(crate) const DRAW_LINES_POOL: &str = "api::draw::lines";
19pub(crate) const MATERIAL_PREFIX: &str = "api::material::";
20pub(crate) const UI_ROOT_NAME: &str = "api::ui::root";
21
22type SetupFunction<Data> = Box<dyn FnOnce(&mut World) -> Data>;
23type UpdateFunction<Data> = Box<dyn FnMut(&mut World, &mut Data)>;
24
25pub(crate) fn register_named(world: &mut World, name: &str, entity: Entity) {
26 world
27 .resources
28 .entities
29 .names
30 .insert(name.to_string(), entity);
31}
32
33pub(crate) fn lookup_named(world: &mut World, name: &str) -> Option<Entity> {
34 let cached = world
35 .resources
36 .entities
37 .names
38 .get(name)
39 .copied()
40 .filter(|&entity| world.core.get_name(entity).is_some());
41 if cached.is_some() {
42 return cached;
43 }
44 let found = nightshade::ecs::world::commands::find_entity_by_name(world, name)?;
45 register_named(world, name, found);
46 Some(found)
47}
48
49pub(crate) struct ApiState<Data> {
50 pub(crate) setup: Option<SetupFunction<Data>>,
51 pub(crate) update: Option<UpdateFunction<Data>>,
52 pub(crate) data: Option<Data>,
53 pub(crate) clears_draw_pools: bool,
54 pub(crate) frame_limit: Option<u32>,
55 pub(crate) frames_rendered: u32,
56}
57
58pub(crate) fn frame_limit_from_environment() -> Option<u32> {
59 std::env::var("NIGHTSHADE_API_FRAMES")
60 .ok()
61 .and_then(|value| value.parse().ok())
62}
63
64impl<Data: 'static> State for ApiState<Data> {
65 fn initialize(&mut self, world: &mut World) {
66 apply_defaults(world);
67 if let Some(setup) = self.setup.take() {
68 self.data = Some(setup(world));
69 }
70 }
71
72 fn run_systems(&mut self, world: &mut World) {
73 if let Some(limit) = self.frame_limit {
74 self.frames_rendered += 1;
75 if self.frames_rendered >= limit {
76 world.resources.window.should_exit = true;
77 }
78 }
79 escape_key_exit_system(world);
80 run_camera_systems(world);
81 if self.clears_draw_pools {
82 crate::draw::clear_draw_pools(world);
83 }
84 if let (Some(update), Some(data)) = (self.update.as_mut(), self.data.as_mut()) {
85 update(world, data);
86 }
87 }
88}
89
90fn apply_defaults(world: &mut World) {
91 world.resources.render_settings.atmosphere = Atmosphere::Sky;
92 world.resources.debug_draw.show_grid = true;
93 world.resources.user_interface.enabled = true;
94 world.resources.retained_ui.enabled = true;
95 #[cfg(feature = "physics")]
96 {
97 world.resources.physics.enabled = true;
98 }
99 let sun = spawn_sun(world);
100 world.core.set_name(sun, Name(SUN_NAME.to_string()));
101 register_named(world, SUN_NAME, sun);
102 load_procedural_textures(world);
103 crate::draw::initialize_draw_pools(world);
104 crate::camera::orbit_camera(world, Vec3::zeros(), 8.0);
105}
106
107fn run_camera_systems(world: &mut World) {
108 let Some(camera) = world.resources.active_camera else {
109 return;
110 };
111 let drives_controllers = world
112 .core
113 .get_name(camera)
114 .is_some_and(|name| name.0 == CAMERA_ORBIT || name.0 == CAMERA_FLY);
115 if drives_controllers {
116 camera_controllers_system(world);
117 }
118 #[cfg(feature = "physics")]
119 {
120 let drives_character = world
121 .core
122 .get_name(camera)
123 .is_some_and(|name| name.0 == CAMERA_FIRST_PERSON);
124 if drives_character {
125 first_person_camera_look_system(world);
126 }
127 }
128}
129
130pub fn run<Data: 'static>(
150 setup: impl FnOnce(&mut World) -> Data + 'static,
151 update: impl FnMut(&mut World, &mut Data) + 'static,
152) -> Result<(), Box<dyn std::error::Error>> {
153 launch(ApiState {
154 setup: Some(Box::new(setup)),
155 update: Some(Box::new(update)),
156 data: None,
157 clears_draw_pools: true,
158 frame_limit: frame_limit_from_environment(),
159 frames_rendered: 0,
160 })
161}
162
163pub fn run_scene(
168 setup: impl FnOnce(&mut World) + 'static,
169) -> Result<(), Box<dyn std::error::Error>> {
170 run(setup, |_, _| {})
171}
172
173pub fn systems<Data, const N: usize>(
179 updates: [fn(&mut World, &mut Data); N],
180) -> impl FnMut(&mut World, &mut Data) {
181 move |world, data| {
182 for &update in &updates {
183 update(world, data);
184 }
185 }
186}
187
188#[macro_export]
212macro_rules! run {
213 ($setup:expr, $($update:expr),+ $(,)?) => {
214 $crate::prelude::run($setup, $crate::prelude::systems([$($update),+]))
215 };
216}