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 state = ApiState::<()> {
45 setup: None,
46 update: None,
47 data: None,
48 clears_draw_pools: false,
49 frame_limit: None,
50 frames_rendered: 0,
51 };
52 let mut shell =
53 pump_shell_new(Box::new(state)).expect("failed to create the engine event loop");
54 shell.context.world.resources.window.title = window.title;
55 shell.context.world.resources.window.initial_size = window.size;
56
57 let mut pumps_without_window = 0;
58 while !pump_shell_ready(&shell) {
59 if !pump_frame(&mut shell) {
60 break;
61 }
62 let window_exists = shell.context.world.resources.window.handle.is_some();
63 if window_exists && shell.context.initialized && shell.context.renderer.is_none() {
64 panic!("failed to create the renderer, see the log for the error");
65 }
66 if !window_exists {
67 pumps_without_window += 1;
68 if pumps_without_window > 10000 {
69 panic!("failed to create the window, see the log for the error");
70 }
71 }
72 }
73
74 let mut world = World::default();
75 std::mem::swap(&mut world, &mut shell.context.world);
76
77 schedule_remove(
78 &mut world.resources.schedules.frame,
79 system_names::RESET_MOUSE,
80 );
81 schedule_remove(
82 &mut world.resources.schedules.frame,
83 system_names::RESET_KEYBOARD,
84 );
85 schedule_remove(
86 &mut world.resources.schedules.frame,
87 system_names::RESET_TOUCH,
88 );
89
90 let frame_limit = crate::runner::frame_limit_from_environment();
91
92 App {
93 world,
94 frame_limit,
95 frames_rendered: 0,
96 shell,
97 }
98}
99
100pub fn render_image(
114 width: u32,
115 height: u32,
116 path: impl Into<std::path::PathBuf>,
117 setup: impl FnOnce(&mut World),
118) {
119 let state = ApiState::<()> {
120 setup: None,
121 update: None,
122 data: None,
123 clears_draw_pools: false,
124 frame_limit: None,
125 frames_rendered: 0,
126 };
127 let mut shell =
128 pump_shell_new(Box::new(state)).expect("failed to create the engine event loop");
129 shell.context.world.resources.window.start_hidden = true;
130 shell.context.world.resources.window.initial_size = Some((width, height));
131
132 while !pump_shell_ready(&shell) {
133 if !pump_frame(&mut shell) {
134 return;
135 }
136 }
137
138 setup(&mut shell.context.world);
139
140 for _ in 0..30 {
141 tick_hidden(&mut shell);
142 }
143 crate::environment::screenshot(&mut shell.context.world, path.into());
144 for _ in 0..10 {
145 tick_hidden(&mut shell);
146 }
147}
148
149fn tick_hidden(shell: &mut PumpShell) {
150 let context = &mut shell.context;
151 let Some(renderer) = context.renderer.as_mut() else {
152 return;
153 };
154 if let Some(next_state) = tick_offscreen(&mut context.world, context.state.as_mut(), renderer) {
155 context.state = next_state;
156 }
157}
158
159pub fn frame(app: &mut App) -> bool {
171 if let Some(limit) = app.frame_limit
172 && app.frames_rendered >= limit
173 {
174 return false;
175 }
176
177 nightshade::ecs::input::systems::reset_mouse_system(&mut app.world);
178 nightshade::ecs::input::systems::reset_keyboard_system(&mut app.world);
179 nightshade::ecs::input::systems::reset_touch_system(&mut app.world);
180
181 std::mem::swap(&mut app.world, &mut app.shell.context.world);
182 let alive = pump_frame(&mut app.shell);
183 std::mem::swap(&mut app.world, &mut app.shell.context.world);
184
185 crate::draw::clear_draw_pools(&mut app.world);
186 app.frames_rendered += 1;
187
188 alive
189}