1use crate::runner::ApiState;
4use nightshade::prelude::*;
5use nightshade::run::pump::{PumpShell, pump_frame, pump_shell_new, pump_shell_ready};
6
7pub struct Window {
9 pub title: String,
10 pub size: Option<(u32, u32)>,
11}
12
13impl Default for Window {
14 fn default() -> Self {
15 Self {
16 title: "nightshade".to_string(),
17 size: None,
18 }
19 }
20}
21
22pub struct App {
29 pub world: World,
30 pub frame_limit: Option<u32>,
31 frames_rendered: u32,
32 shell: PumpShell,
33}
34
35pub fn open() -> App {
39 open_with(Window::default())
40}
41
42pub fn open_with(window: Window) -> App {
44 let mut stages = crate::runner::build_api_frame_stages();
45 nightshade::app::remove_frame_system(
46 &mut stages,
47 nightshade::ecs::input::systems::reset_mouse_system,
48 );
49 nightshade::app::remove_frame_system(
50 &mut stages,
51 nightshade::ecs::input::systems::reset_keyboard_system,
52 );
53 nightshade::app::remove_frame_system(
54 &mut stages,
55 nightshade::ecs::input::systems::reset_touch_system,
56 );
57 let state = ApiState::<()> {
58 setup: None,
59 update: None,
60 data: None,
61 clears_draw_pools: false,
62 frame_limit: None,
63 frames_rendered: 0,
64 stages,
65 };
66 let mut shell =
67 pump_shell_new(Box::new(state)).expect("failed to create the engine event loop");
68 shell
69 .context
70 .world
71 .res_mut::<nightshade::ecs::window::resources::Window>()
72 .title = window.title;
73 shell
74 .context
75 .world
76 .res_mut::<nightshade::ecs::window::resources::Window>()
77 .initial_size = window.size;
78
79 let mut pumps_without_window = 0;
80 while !pump_shell_ready(&shell) {
81 if !pump_frame(&mut shell) {
82 break;
83 }
84 let window_exists = shell
85 .context
86 .world
87 .res::<nightshade::ecs::window::resources::Window>()
88 .handle
89 .is_some();
90 if window_exists && shell.context.initialized && shell.context.renderer.is_none() {
91 panic!("failed to create the renderer, see the log for the error");
92 }
93 if !window_exists {
94 pumps_without_window += 1;
95 if pumps_without_window > 10000 {
96 panic!("failed to create the window, see the log for the error");
97 }
98 }
99 }
100
101 let mut world = World::default();
102 std::mem::swap(&mut world, &mut shell.context.world);
103
104 let frame_limit = crate::runner::frame_limit_from_environment();
105
106 App {
107 world,
108 frame_limit,
109 frames_rendered: 0,
110 shell,
111 }
112}
113
114pub fn render_image(
128 width: u32,
129 height: u32,
130 path: impl Into<std::path::PathBuf>,
131 setup: impl FnOnce(&mut World),
132) {
133 let state = ApiState::<()> {
134 setup: None,
135 update: None,
136 data: None,
137 clears_draw_pools: false,
138 frame_limit: None,
139 frames_rendered: 0,
140 stages: crate::runner::build_api_frame_stages(),
141 };
142 let mut shell =
143 pump_shell_new(Box::new(state)).expect("failed to create the engine event loop");
144 shell
145 .context
146 .world
147 .res_mut::<nightshade::ecs::window::resources::Window>()
148 .start_hidden = true;
149 shell
150 .context
151 .world
152 .res_mut::<nightshade::ecs::window::resources::Window>()
153 .initial_size = Some((width, height));
154
155 while !pump_shell_ready(&shell) {
156 if !pump_frame(&mut shell) {
157 return;
158 }
159 }
160
161 setup(&mut shell.context.world);
162
163 for _ in 0..30 {
164 tick_hidden(&mut shell);
165 }
166 crate::environment::screenshot(&mut shell.context.world, path.into());
167 for _ in 0..10 {
168 tick_hidden(&mut shell);
169 }
170}
171
172fn tick_hidden(shell: &mut PumpShell) {
173 let context = &mut shell.context;
174 let Some(renderer) = context.renderer.as_mut() else {
175 return;
176 };
177 if let Some(next_state) = tick_offscreen(&mut context.world, context.state.as_mut(), renderer) {
178 context.state = next_state;
179 }
180}
181
182pub fn frame(app: &mut App) -> bool {
194 if let Some(limit) = app.frame_limit
195 && app.frames_rendered >= limit
196 {
197 return false;
198 }
199
200 nightshade::ecs::input::systems::reset_mouse_system(&mut app.world);
201 nightshade::ecs::input::systems::reset_keyboard_system(&mut app.world);
202 nightshade::ecs::input::systems::reset_touch_system(&mut app.world);
203
204 std::mem::swap(&mut app.world, &mut app.shell.context.world);
205 let alive = pump_frame(&mut app.shell);
206 std::mem::swap(&mut app.world, &mut app.shell.context.world);
207
208 crate::draw::clear_draw_pools(&mut app.world);
209 app.frames_rendered += 1;
210
211 alive
212}