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 .resources
32 .entities
33 .names
34 .insert(name.to_string(), entity);
35}
36
37pub(crate) fn lookup_named(world: &mut World, name: &str) -> Option<Entity> {
38 let cached = world
39 .resources
40 .entities
41 .names
42 .get(name)
43 .copied()
44 .filter(|&entity| {
45 world
46 .get::<nightshade::ecs::primitives::Name>(entity)
47 .is_some()
48 });
49 if cached.is_some() {
50 return cached;
51 }
52 let found = nightshade::ecs::world::commands::find_entity_by_name(world, name)?;
53 register_named(world, name, found);
54 Some(found)
55}
56
57pub(crate) struct ApiState<Data> {
58 pub(crate) setup: Option<SetupFunction<Data>>,
59 pub(crate) update: Option<UpdateFunction<Data>>,
60 pub(crate) data: Option<Data>,
61 pub(crate) clears_draw_pools: bool,
62 pub(crate) frame_limit: Option<u32>,
63 pub(crate) frames_rendered: u32,
64}
65
66pub(crate) fn frame_limit_from_environment() -> Option<u32> {
67 std::env::var("NIGHTSHADE_API_FRAMES")
68 .ok()
69 .and_then(|value| value.parse().ok())
70}
71
72impl<Data: 'static> State for ApiState<Data> {
73 fn initialize(&mut self, world: &mut World) {
74 apply_defaults(world);
75 if let Some(setup) = self.setup.take() {
76 self.data = Some(setup(world));
77 }
78 }
79
80 fn run_systems(&mut self, world: &mut World) {
81 if let Some(limit) = self.frame_limit {
82 self.frames_rendered += 1;
83 if self.frames_rendered >= limit {
84 world.resources.window.should_exit = true;
85 }
86 }
87 #[cfg(all(feature = "audio", target_arch = "wasm32"))]
88 nightshade::plugins::audio::systems::lazy_initialize_audio_system(world);
89 #[cfg(feature = "gamepad")]
90 {
91 nightshade::plugins::gamepad::gamepad_input_system(world);
92 let gamepad_events = world.resources.input.gamepad.events.clone();
93 for event in gamepad_events {
94 world
95 .resources
96 .input
97 .events
98 .push(nightshade::ecs::input::events::AppEvent::Gamepad(event));
99 }
100 }
101 escape_key_exit_system(world);
102 run_camera_systems(world);
103 if self.clears_draw_pools {
104 crate::draw::clear_draw_pools(world);
105 }
106 if let (Some(update), Some(data)) = (self.update.as_mut(), self.data.as_mut()) {
107 update(world, data);
108 }
109 }
110}
111
112fn apply_defaults(world: &mut World) {
113 world.resources.render_settings.atmosphere = Atmosphere::Sky;
114 world.resources.debug_draw.show_grid = false;
115 nightshade::plugins::render::install(world);
116 nightshade::plugins::ui::install(world);
117 nightshade::plugins::cutscene::install(world);
118 #[cfg(feature = "physics")]
119 nightshade::plugins::physics::install(world);
120 #[cfg(feature = "navmesh")]
121 nightshade::plugins::navmesh::install(world);
122 #[cfg(feature = "audio")]
123 {
124 #[cfg(not(target_arch = "wasm32"))]
125 nightshade::plugins::audio::systems::initialize_audio_system(world);
126 nightshade::plugins::audio::install(world);
127 }
128 let sun = spawn_sun(world);
129 world.set(sun, Name(SUN_NAME.to_string()));
130 register_named(world, SUN_NAME, sun);
131 load_procedural_textures(world);
132 crate::draw::initialize_draw_pools(world);
133 crate::camera::orbit_camera(world, Vec3::zeros(), 8.0);
134}
135
136fn run_camera_systems(world: &mut World) {
137 let Some(camera) = world.resources.active_camera else {
138 return;
139 };
140 let drives_controllers = world
141 .get::<nightshade::ecs::primitives::Name>(camera)
142 .is_some_and(|name| name.0 == CAMERA_ORBIT || name.0 == CAMERA_FLY);
143 if drives_controllers {
144 camera_controllers_system(world);
145 }
146 #[cfg(feature = "physics")]
147 {
148 let drives_character = world
149 .get::<nightshade::ecs::primitives::Name>(camera)
150 .is_some_and(|name| name.0 == CAMERA_FIRST_PERSON);
151 if drives_character {
152 first_person_camera_look_system(world);
153 }
154 }
155}
156
157pub fn run<Data: 'static>(
177 setup: impl FnOnce(&mut World) -> Data + 'static,
178 update: impl FnMut(&mut World, &mut Data) + 'static,
179) -> Result<(), Box<dyn std::error::Error>> {
180 let log_guards = nightshade::plugins::log::initialize_logging(
181 "nightshade",
182 &nightshade::plugins::log::LogConfig::default(),
183 );
184 let result = launch(ApiState {
185 setup: Some(Box::new(setup)),
186 update: Some(Box::new(update)),
187 data: None,
188 clears_draw_pools: true,
189 frame_limit: frame_limit_from_environment(),
190 frames_rendered: 0,
191 });
192 drop(log_guards);
193 result
194}
195
196pub fn run_scene(
201 setup: impl FnOnce(&mut World) + 'static,
202) -> Result<(), Box<dyn std::error::Error>> {
203 run(setup, |_, _| {})
204}
205
206pub fn systems<Data, const N: usize>(
212 updates: [fn(&mut World, &mut Data); N],
213) -> impl FnMut(&mut World, &mut Data) {
214 move |world, data| {
215 for &update in &updates {
216 update(world, data);
217 }
218 }
219}
220
221#[macro_export]
245macro_rules! run {
246 ($setup:expr, $($update:expr),+ $(,)?) => {
247 $crate::prelude::run($setup, $crate::prelude::systems([$($update),+]))
248 };
249}