fantasy_craft/core/
app.rs1use hecs::World;
2use macroquad::prelude::*;
3use futures::{FutureExt, future::BoxFuture};
4use crate::core::context::Context;
5use crate::core::event::EventBus;
6use crate::core::schedule::{Schedule, Stage};
7use crate::core::asset_server::AssetServer;
8use crate::core::plugins::Plugin;
9use crate::core::time::DeltaTime;
10use crate::core::web_context::WebContext;
11use crate::graphics::splash_screen::{SplashScreenData, animate_splash_screen, despawn_splash_screen, setup_splash_screen};
12use crate::input::manager::InputManager;
13use crate::prelude::{Spritesheet, System};
14use crate::gui::resources::PreviousMousePosition;
15use crate::scene::scene_loader::SceneLoader;
16
17pub struct App {
18 pub context: Context,
19 pub schedule: Schedule,
20 pub scene_loader: SceneLoader,
21 pub window_conf: Conf,
22 pub scene_path: Option<String>,
23 pub assets_file: Option<String>,
24 show_splash_screen: bool,
25 splash_screen_logo: String,
26 splash_screen_background_color: Color,
27 binding_path: Option<String>
28}
29
30impl App {
31 pub fn new(conf: Conf) -> Self {
32 let world = World::new();
33 let asset_server = AssetServer::new();
34
35 App {
36 context: Context::new(
37 world,
38 asset_server
39 ),
40 schedule: Schedule::new(),
41 scene_loader: SceneLoader::new(),
42 window_conf: conf,
43 scene_path: None,
44 show_splash_screen: true,
45 assets_file: None,
46 splash_screen_logo: "resources/textures/logo_engine.png".to_string(),
47 splash_screen_background_color: Color::new(1.0, 0.980392157, 0.960784314, 1.0),
48 binding_path: None
49 }
50 }
51
52 pub fn with_splash_screen_enabled(&mut self, enabled: bool) -> &mut Self {
53 self.show_splash_screen = enabled;
54 self
55 }
56
57 pub fn with_splash_screen_logo(&mut self, path: &str) -> &mut Self {
58 self.splash_screen_logo = path.to_string();
59 self
60 }
61
62 pub fn with_splash_screen_background_color(&mut self, color: Color) -> &mut Self {
63 self.splash_screen_background_color = color;
64 self
65 }
66
67 pub fn with_scene_path(&mut self, scene_path: String) -> &mut Self {
68 self.scene_path = Some(scene_path);
69 self
70 }
71
72 pub fn with_assets_file(&mut self, file_path: String) -> &mut Self {
73 self.assets_file = Some(file_path);
74 self
75 }
76
77 pub fn with_binding_file(&mut self, file_path: String) -> &mut Self {
78 self.binding_path = Some(file_path);
79 self
80 }
81
82 pub fn add_system(&mut self, stage: Stage, system: System) -> &mut Self {
83 self.schedule.add_system(stage, system);
84 self
85 }
86
87 pub fn add_plugin<T: Plugin>(&mut self, plugin: T) -> &mut Self {
88 plugin.build(self);
89 self
90 }
91
92 fn resolve_path(base: &str, path: &str) -> String {
93 if path.starts_with("http") {
94 return path.to_string();
95 }
96 format!("{}{}", base, path)
97 }
98
99 pub async fn run(mut self) {
100 let base_url = WebContext::get_base_url();
102 info!("App: Resolved Base URL: {}", base_url);
103
104 let resolved_splash_path = Self::resolve_path(&base_url, &self.splash_screen_logo);
106
107 let resolved_assets_file = self.assets_file.as_ref().map(|p| Self::resolve_path(&base_url, p));
109 let resolved_scene_path = self.scene_path.as_ref().map(|p| Self::resolve_path(&base_url, p));
110 let resolved_binding_path = self.binding_path.as_ref().map(|p| Self::resolve_path(&base_url, p));
111
112 const SPLASH_MIN_DURATION: f64 = 3.0;
113
114 let mut maybe_asset_server: Option<AssetServer> = None;
115
116 if self.show_splash_screen {
117 let splash_texture = match load_texture(&resolved_splash_path).await {
118 Ok(tex) => tex,
119 Err(e) => {
120 error!("Failed to load splash logo at: {}. Error: {}", resolved_splash_path, e);
121 let img = Image::gen_image_color(1, 1, MAGENTA);
123 Texture2D::from_image(&img)
124 }
125 };
126
127 self.context.asset_server.add_spritesheet(
129 "splash_screen_logo".to_string(),
130 Spritesheet::new(
131 splash_texture,
132 1024.0,
133 1024.0,
134 ),
135 );
136
137 self.context.insert_resource(SplashScreenData {
138 background_color: self.splash_screen_background_color
139 });
140
141 setup_splash_screen(&mut self.context);
142 let start_time = get_time();
143
144 let mut loading_asset_server = AssetServer::new();
146
147 let asset_path_for_future = resolved_assets_file.clone();
148
149 let mut load_future: BoxFuture<'static, (AssetServer, Result<(), Box<dyn std::error::Error>>)> =
150 Box::pin(async move {
151 let result = if let Some(path) = asset_path_for_future {
152 loading_asset_server.load_assets_from_file(&path).await
153 } else {
154 Ok(())
155 };
156 (loading_asset_server, result)
157 });
158
159 let mut assets_loaded = false;
160
161 loop {
163 let dt = self.context.resource_mut::<DeltaTime>();
164 dt.0 = get_frame_time();
165 clear_background(self.splash_screen_background_color);
166
167 animate_splash_screen(&mut self.context);
169 self.schedule.run_stage(Stage::Render, &mut self.context);
170 self.schedule.run_stage(Stage::PostRender, &mut self.context);
171 set_default_camera();
172 self.schedule.run_stage(Stage::GuiRender, &mut self.context);
173
174 next_frame().await;
175
176 let elapsed = get_time() - start_time;
177 let duration_done = elapsed >= SPLASH_MIN_DURATION;
178
179 if !assets_loaded {
180 if let Some((loaded_server, result)) = load_future.as_mut().now_or_never() {
181 if let Err(e) = result {
183 error!("Failed to load assets from JSON file: {}", e);
184 }
185 maybe_asset_server = Some(loaded_server);
186 assets_loaded = true;
187 }
188 }
189
190 if assets_loaded && duration_done {
191 break;
192 }
193 }
194
195 despawn_splash_screen(&mut self.context);
196 } else {
197 let mut asset_server = AssetServer::new();
199
200 if let Some(path) = &resolved_assets_file {
201 if let Err(e) = asset_server.load_assets_from_file(path).await {
202 error!("Failed to load assets from JSON file: {}", e);
203 }
204 }
205
206 maybe_asset_server = Some(asset_server);
207 }
208
209 if let Some(loaded_server) = maybe_asset_server {
211 self.context.asset_server.merge(loaded_server);
212 self.context.asset_server.finalize_textures().await;
213 }
214
215 self.context.asset_server.prepare_loaded_tiledmap().await;
216
217 if let Some(scene_path) = resolved_scene_path {
218 self.scene_loader.load_scene_from_file(&scene_path, &mut self.context).await.unwrap();
219 }
220
221 if let Some(binding_path) = resolved_binding_path {
222 info!("App: Loading bindings from: {}", binding_path);
223
224 match load_string(&binding_path).await {
226 Ok(content) => {
227 if let Some(input_manager) = self.context.get_resource_mut::<InputManager>() {
228 input_manager.load_from_string(&content);
230 }
231 },
232 Err(e) => {
233 error!("App: Failed to load binding file '{}': {}", binding_path, e);
234 }
235 }
236 }
237
238 self.schedule.run_stage(Stage::StartUp, &mut self.context);
240
241 loop {
242 let dt = self.context.resource_mut::<DeltaTime>();
243 dt.0 = get_frame_time();
244 clear_background(LIGHTGRAY);
245
246 self.schedule.run_stage(Stage::Update, &mut self.context);
247 self.schedule.run_stage(Stage::PostUpdate, &mut self.context);
248 self.schedule.run_stage(Stage::Render, &mut self.context);
249 self.schedule.run_stage(Stage::PostRender, &mut self.context);
250
251 set_default_camera();
252 self.schedule.run_stage(Stage::GuiRender, &mut self.context);
253
254 if let Some(event_bus) = self.context.get_resource_mut::<EventBus>() {
255 event_bus.clear();
256 }
257
258 if let Some(prev_mouse_pos) = self.context.get_resource_mut::<PreviousMousePosition>() {
259 prev_mouse_pos.0 = mouse_position().into();
260 }
261
262 next_frame().await;
263 }
264 }
265}