fennel_engine/
app.rs

1use std::{
2    fs,
3    sync::{Arc, Mutex},
4};
5
6use fennel_core::{
7    Window,
8    events::{KeyboardEvent, WindowEventHandler},
9    graphics::WindowConfig,
10};
11use serde::{Deserialize, Serialize};
12use specs::{Builder, Dispatcher, DispatcherBuilder, WorldExt};
13
14use crate::{
15    ecs::{
16        input::InputSystem,
17        scene::SceneSystem,
18        sprite::{HostPtr, RenderingSystem, Sprite, SpriteFactory},
19    },
20    events::KeyEvents,
21    registry::ComponentRegistry,
22    scenes::{ActiveScene, Scene},
23};
24
25/// The application struct which contains [`fennel_core::Window`], [`specs::World`] and `specs`
26/// `Dispatcher`
27pub struct App {
28    /// Responsible for GFX and audio
29    pub window: fennel_core::Window,
30    /// ECS world
31    pub world: specs::World,
32    /// ECS dispatcher
33    pub dispatcher: Dispatcher<'static, 'static>,
34    /// Application scenes
35    pub scenes: Vec<Scene>,
36    /// Registry of component factories for scene drawing
37    pub component_registry: ComponentRegistry,
38    /// Current active scene
39    pub active_scene: ActiveScene
40}
41
42/// Builder for [`App`]
43#[derive(Default, Debug)]
44pub struct AppBuilder {
45    name: &'static str,
46    dimensions: (u32, u32),
47    config: &'static str,
48    window_config: WindowConfig,
49}
50
51/// Application config defined by user
52#[derive(Deserialize, Serialize, Debug)]
53struct Config {
54    /// Path to assets directory
55    assets_path: String,
56    /// Path to scenes directory
57    scenes_path: String,
58    /// First scene to display
59    initial_scene: String,
60}
61
62unsafe impl Send for App {}
63unsafe impl Sync for App {}
64
65#[async_trait::async_trait]
66impl WindowEventHandler for App {
67    fn update(&self, _window: &mut Window) -> anyhow::Result<()> {
68        Ok(())
69    }
70
71    fn draw(&mut self, window: &mut Window) -> anyhow::Result<()> {
72        self.frame_tick();
73        window.graphics.canvas.present();
74        Ok(())
75    }
76
77    fn key_down_event(&self, _window: &mut Window, event: KeyboardEvent) -> anyhow::Result<()> {
78        println!("{:?}", event.keycode);
79        Ok(())
80    }
81}
82
83impl App {
84    /// Runs the event loop, must be called only once, UB otherwise
85    pub async fn run(mut self) -> anyhow::Result<()> {
86        // you know what? fuck you and your borrow checker.
87        // i'm 100% sure this app is single-threaded and its 11 pm
88        // at the moment so i'm not gonna solve this shit in some
89        // safe way
90        // as long this works and doesn't SEGFAULTs i'll keep it
91        let ptr: *mut App = &mut self as *mut App;
92        fennel_core::events::run(&mut self.window, unsafe { &mut *ptr as &mut App }).await;
93        Ok(())
94    }
95
96    /// Evaluate systems
97    pub fn frame_tick(&mut self) {
98        let host_ptr = HostPtr(self as *mut App);
99        self.world.insert(host_ptr);
100        self.dispatcher.dispatch(&self.world);
101        self.world.maintain();
102        self.world.remove::<HostPtr>();
103    }
104}
105
106impl AppBuilder {
107    /// Create a new [`AppBuilder`]
108    pub fn new() -> AppBuilder {
109        AppBuilder {
110            name: "",
111            dimensions: (100, 100),
112            config: "",
113            window_config: WindowConfig {
114                resizable: false,
115                fullscreen: false,
116                centered: false,
117            },
118        }
119    }
120
121    /// Set the window name
122    pub fn name(mut self, name: &'static str) -> AppBuilder {
123        self.name = name;
124        self
125    }
126
127    /// Set the window dimensions
128    pub fn dimensions(mut self, dimensions: (u32, u32)) -> AppBuilder {
129        self.dimensions = dimensions;
130        self
131    }
132
133    /// Set the application config
134    pub fn config(mut self, path: &'static str) -> AppBuilder {
135        self.config = path;
136        self
137    }
138
139    /// Builds an [`App`]
140    pub fn build(self) -> anyhow::Result<App> {
141        let resource_manager = Arc::new(Mutex::new(fennel_core::resources::ResourceManager::new()));
142        let config_reader = fs::read(self.config)?;
143        let config: Config = toml::from_slice(&config_reader)?;
144        let graphics = fennel_core::graphics::Graphics::new(
145            self.name.to_string(),
146            self.dimensions,
147            resource_manager.clone(),
148            |graphics| {
149                resource_manager.lock().unwrap().load_dir(config.assets_path.clone().into(), graphics).unwrap();
150            },
151            self.window_config
152        );
153        let window = fennel_core::Window::new(
154            graphics.expect("failed to initialize graphics"),
155            resource_manager,
156        );
157        let mut component_registry = ComponentRegistry::new();
158        let mut world = specs::World::new();
159        let mut dispatcher = DispatcherBuilder::new()
160            .with_thread_local(RenderingSystem)
161            .with(SceneSystem, "scene_system", &[])
162            .with(InputSystem, "input_system", &[])
163            .build();
164        let mut scenes: Vec<Scene> = vec![];
165
166        component_registry.register("sprite", Box::new(SpriteFactory));
167        world.register::<Scene>();
168        world.register::<Sprite>();
169        world.insert(KeyEvents::default());
170
171        for entry in fs::read_dir(config.scenes_path).expect("meow") {
172            let scene_reader = fs::read(entry.unwrap().path()).expect("meow");
173            let scene: Scene = ron::de::from_bytes(&scene_reader)?; 
174            world.create_entity().with(scene.clone()).build();
175            scenes.push(scene.clone());
176        }
177
178        dispatcher.setup(&mut world);
179
180        Ok(App {
181            window,
182            world,
183            dispatcher,
184            scenes,
185            component_registry,
186            // assuming the initial scene name is `main`
187            active_scene: ActiveScene { name: String::from("main"), loaded: false }
188        })
189    }
190}