1use nightshade::prelude::*;
4
5#[cfg(any(feature = "picking", feature = "navmesh"))]
6pub(crate) const RESERVED_PREFIX: &str = "api::";
7pub(crate) const CAMERA_NAME_PREFIX: &str = "api::camera::";
8pub(crate) const CAMERA_ORBIT: &str = "api::camera::orbit";
9pub(crate) const CAMERA_FLY: &str = "api::camera::fly";
10#[cfg(feature = "physics")]
11pub(crate) const CAMERA_FIRST_PERSON: &str = "api::camera::first_person";
12pub(crate) const CAMERA_FIXED: &str = "api::camera::fixed";
13#[cfg(feature = "physics")]
14pub(crate) const PLAYER_NAME: &str = "api::player";
15pub(crate) const SUN_NAME: &str = "api::sun";
16pub(crate) const DRAW_MATERIAL: &str = "api::draw";
17pub(crate) const DRAW_CUBE_POOL: &str = "api::draw::cube";
18pub(crate) const DRAW_SPHERE_POOL: &str = "api::draw::sphere";
19pub(crate) const DRAW_CYLINDER_POOL: &str = "api::draw::cylinder";
20pub(crate) const DRAW_CONE_POOL: &str = "api::draw::cone";
21pub(crate) const DRAW_TORUS_POOL: &str = "api::draw::torus";
22pub(crate) const DRAW_LINES_POOL: &str = "api::draw::lines";
23pub(crate) const MATERIAL_PREFIX: &str = "api::material::";
24pub(crate) const UI_ROOT_NAME: &str = "api::ui::root";
25
26type SetupFunction<Data> = Box<dyn FnOnce(&mut World) -> Data>;
27type UpdateFunction<Data> = Box<dyn FnMut(&mut World, &mut Data)>;
28
29pub(crate) fn register_named(world: &mut World, name: &str, entity: Entity) {
30 world
31 .res_mut::<nightshade::ecs::entity_registry::EntityRegistry>()
32 .names
33 .insert(name.to_string(), entity);
34}
35
36pub(crate) fn lookup_named(world: &mut World, name: &str) -> Option<Entity> {
37 let cached = world
38 .res::<nightshade::ecs::entity_registry::EntityRegistry>()
39 .names
40 .get(name)
41 .copied()
42 .filter(|&entity| {
43 world
44 .get::<nightshade::ecs::primitives::Name>(entity)
45 .is_some()
46 });
47 if cached.is_some() {
48 return cached;
49 }
50 let found = nightshade::ecs::world::commands::find_entity_by_name(world, name)?;
51 register_named(world, name, found);
52 Some(found)
53}
54
55pub(crate) struct ApiState<Data> {
56 pub(crate) setup: Option<SetupFunction<Data>>,
57 pub(crate) update: Option<UpdateFunction<Data>>,
58 pub(crate) data: Option<Data>,
59 pub(crate) clears_draw_pools: bool,
60 pub(crate) frame_limit: Option<u32>,
61 pub(crate) frames_rendered: u32,
62 pub(crate) stages: freecs::Stages<World>,
67}
68
69pub(crate) fn build_api_frame_stages() -> freecs::Stages<World> {
74 let mut stages = nightshade::app::build_default_frame_stages();
75 nightshade::plugins::render::register_frame_systems(&mut stages);
76 nightshade::plugins::ui::register_frame_systems(&mut stages);
77 nightshade::plugins::cutscene::register_frame_systems(&mut stages);
78 #[cfg(feature = "physics")]
79 nightshade::plugins::physics::register_frame_systems(&mut stages);
80 #[cfg(feature = "navmesh")]
81 nightshade::plugins::navmesh::register_frame_systems(&mut stages);
82 #[cfg(feature = "audio")]
83 nightshade::plugins::audio::register_frame_systems(&mut stages);
84 stages
85}
86
87pub(crate) fn frame_limit_from_environment() -> Option<u32> {
88 std::env::var("NIGHTSHADE_API_FRAMES")
89 .ok()
90 .and_then(|value| value.parse().ok())
91}
92
93impl<Data: 'static> State for ApiState<Data> {
94 fn initialize(&mut self, world: &mut World) {
95 apply_defaults(world);
96 if let Some(setup) = self.setup.take() {
97 self.data = Some(setup(world));
98 }
99 }
100
101 fn run_systems(&mut self, world: &mut World) {
102 if let Some(limit) = self.frame_limit {
103 self.frames_rendered += 1;
104 if self.frames_rendered >= limit {
105 world
106 .res_mut::<nightshade::ecs::window::resources::Window>()
107 .should_exit = true;
108 }
109 }
110 #[cfg(all(feature = "audio", target_arch = "wasm32"))]
111 nightshade::plugins::audio::systems::lazy_initialize_audio_system(world);
112 #[cfg(feature = "gamepad")]
113 {
114 nightshade::plugins::gamepad::gamepad_input_system(world);
115 let gamepad_events = world
116 .res::<nightshade::plugins::gamepad::Gamepad>()
117 .events
118 .clone();
119 for event in gamepad_events {
120 world
121 .res_mut::<nightshade::ecs::input::resources::Input>()
122 .events
123 .push(nightshade::ecs::input::events::AppEvent::Gamepad(event));
124 }
125 }
126 escape_key_exit_system(world);
127 run_camera_systems(world);
128 if self.clears_draw_pools {
129 crate::draw::clear_draw_pools(world);
130 }
131 if let (Some(update), Some(data)) = (self.update.as_mut(), self.data.as_mut()) {
132 update(world, data);
133 }
134 self.stages.run(world);
135 }
136}
137
138fn apply_defaults(world: &mut World) {
139 world
140 .res_mut::<nightshade::render::config::RenderSettings>()
141 .atmosphere = Atmosphere::Sky;
142 world
143 .res_mut::<nightshade::render::config::DebugDraw>()
144 .show_grid = false;
145 nightshade::plugins::ui::install(world);
146 #[cfg(feature = "physics")]
147 nightshade::plugins::physics::install(world);
148 #[cfg(all(feature = "audio", not(target_arch = "wasm32")))]
149 nightshade::plugins::audio::systems::initialize_audio_system(world);
150 let sun = spawn_sun(world);
151 world.set(sun, Name(SUN_NAME.to_string()));
152 register_named(world, SUN_NAME, sun);
153 load_procedural_textures(world);
154 crate::draw::initialize_draw_pools(world);
155 crate::camera::orbit_camera(world, Vec3::zeros(), 8.0);
156}
157
158fn run_camera_systems(world: &mut World) {
159 let Some(camera) = world
160 .res::<nightshade::ecs::camera::resources::ActiveCamera>()
161 .0
162 else {
163 return;
164 };
165 let drives_controllers = world
166 .get::<nightshade::ecs::primitives::Name>(camera)
167 .is_some_and(|name| name.0 == CAMERA_ORBIT || name.0 == CAMERA_FLY);
168 if drives_controllers {
169 camera_controllers_system(world);
170 }
171 #[cfg(feature = "physics")]
172 {
173 let drives_character = world
174 .get::<nightshade::ecs::primitives::Name>(camera)
175 .is_some_and(|name| name.0 == CAMERA_FIRST_PERSON);
176 if drives_character {
177 first_person_camera_look_system(world);
178 }
179 }
180}
181
182pub fn run<Data: 'static>(
202 setup: impl FnOnce(&mut World) -> Data + 'static,
203 update: impl FnMut(&mut World, &mut Data) + 'static,
204) -> Result<(), Box<dyn std::error::Error>> {
205 let log_guards = nightshade::plugins::log::initialize_logging(
206 "nightshade",
207 &nightshade::plugins::log::LogConfig::default(),
208 );
209 let result = launch(ApiState {
210 setup: Some(Box::new(setup)),
211 update: Some(Box::new(update)),
212 data: None,
213 clears_draw_pools: true,
214 frame_limit: frame_limit_from_environment(),
215 frames_rendered: 0,
216 stages: build_api_frame_stages(),
217 });
218 drop(log_guards);
219 result
220}
221
222pub fn run_scene(
227 setup: impl FnOnce(&mut World) + 'static,
228) -> Result<(), Box<dyn std::error::Error>> {
229 run(setup, |_, _| {})
230}
231
232pub fn systems<Data, const N: usize>(
238 updates: [fn(&mut World, &mut Data); N],
239) -> impl FnMut(&mut World, &mut Data) {
240 move |world, data| {
241 for &update in &updates {
242 update(world, data);
243 }
244 }
245}
246
247#[macro_export]
271macro_rules! run {
272 ($setup:expr, $($update:expr),+ $(,)?) => {
273 $crate::prelude::run($setup, $crate::prelude::systems([$($update),+]))
274 };
275}