feo_oop_engine/
lib.rs

1//! [![github]](https://github.com/littleTitan/feo-oop-engine) [![crates-io]](https://crates.io/crates/feo-oop-engine) [![docs-rs]](crate)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
6//!
7//! Feo OOP Engine is an object oriented game engine.
8//! > procedural macros: [feo-oop-engine-proc-macros](https://docs.rs/feo-oop-engine-proc-macros)
9//! ## Compatibility
10//!
11//! |  OS     | Compatible |
12//! | :-----: | :--------: |
13//! | Windows | Issue id:1 |
14//! | Linux   | Yes        |
15//! | OSX     | Yes        |
16//!
17//! See issue [#1](/../../issues/1) for Windows
18//! ## Description
19//! The FeO OOP engine is a library I created to help me learn about 3D engines. This 
20//! project is composed of two parts. [feo-math](https://github.com/littleTitan/feo-math), 
21//! the math boilerplate; and the feo-oop-engine. The engine is built on the 
22//! [vulkano](https://vulkano.rs) framework. This program is designed to facilitate 3D 
23//! application development. Please note however that this program has its own unique 
24//! workflow. Features of the engine include [scripts](###scripts), object oriented 
25//! programming (or OOP for short), textures, materials, lights, game objects, and obj
26//! and mtl processing.
27//! 
28//! ## Example
29//! 
30//! ### Building the Scene
31//! 
32//! First create a new scene.
33//! ```no_run
34//! use feo_oop_engine::scene::Scene;
35//! 
36//! let scene = Scene::new(None);
37//! ```
38//! This is where all of your game-objects will directly or indirectly exist on. 
39//!
40//! ### Initialize the Engine With the Scene
41//! To create an engine use the `FeoEngine::init(scene, specify_hardware)`. This will create a feo_engine object.
42//! ```no_run
43//! use feo_oop_engine::FeoEngine;
44//! # let scene = feo_oop_engine::scene::Scene::new(None);
45//! let mut engine = FeoEngine::init(scene, Some(1));
46//! ```
47//!
48//! ### Build Objects
49//! To build objects use the `::new()` constructor for the object you wish to build. You might want to build a light and a camera to be able to see the scene.
50//! ```no_run
51//! use feo_oop_engine::scene::game_object::obj::Obj;
52//! # let scene = feo_oop_engine::scene::Scene::new(None);
53//! # let mut engine = feo_oop_engine::FeoEngine::init(scene, Some(1));
54//! let obj = Obj::from_obj(
55//!    Some("cube"), 
56//!    "assets/standard-assets/models/shapes/cube.obj",
57//!    None,
58//!    None,
59//!    None,
60//!    None,
61//!    true,
62//!    engine.globals.clone(),
63//!    None
64//! );
65//! ```
66//!
67//! ### Pass Objects to Scene
68//! Use the `add_child()` function to add the object you created to the scene within the engine.
69//! ```no_run
70//! use feo_oop_engine::registration::relation::Parent;
71//! # let scene = feo_oop_engine::scene::Scene::new(None);
72//! # let mut engine = feo_oop_engine::FeoEngine::init(scene, Some(1));
73//! # let obj = feo_oop_engine::scene::game_object::obj::Obj::from_obj( Some("cube"), 
74//! #    "assets/standard-assets/models/shapes/cube.obj", None, None, None, None,
75//! #    true, engine.globals.clone(), None );
76//! engine.scene.write().unwrap().add_child(obj.unwrap());
77//! ```
78//!
79//! ### Running the Engine
80//! When all the game_objects have been created you can use the run() function of feo_engine to start the engine.
81//! ```ignore
82//! engine.run()
83//! ```
84#[macro_use] extern crate lazy_static;
85
86#[macro_use] extern crate feo_oop_engine_proc_macros;
87
88pub mod scene;
89pub mod components;
90pub mod scripting;
91pub mod event;
92pub mod graphics;
93pub mod registration;
94
95pub mod shaders;
96pub mod macros;
97
98pub(crate) mod term_ui;
99
100use {
101    self::{
102        graphics::frame_system::FrameSystem,
103        event::UserEvent,
104        scripting::globals::EngineGlobals,
105        scene::Scene,
106        components::texture::Texture,
107        registration::id::IDSystem
108    },
109    std::{
110        sync::{
111            Arc,
112            RwLock,
113        },
114        any::Any,
115    },
116    vulkano::{
117        device::{
118            Device, 
119            DeviceExtensions, 
120            Queue
121        },
122        image::{
123            view::ImageView,
124            ImageUsage
125        }, 
126        instance::{
127            Instance,
128        },
129        swapchain::{
130            self, 
131            AcquireError, 
132            ColorSpace, 
133            FullscreenExclusive, 
134            PresentMode, 
135            Surface, 
136            SurfaceTransform, 
137            Swapchain, 
138            SwapchainCreationError
139        }, 
140        sync::{
141            self, 
142            FlushError, 
143            GpuFuture
144        }
145    },
146    vulkano_win::VkSurfaceBuild,
147    winit::{
148        dpi::PhysicalSize, 
149        event::{
150            Event, 
151            WindowEvent
152        }, 
153        event_loop::{
154            ControlFlow, 
155            EventLoop
156        },
157        platform::run_return::EventLoopExtRunReturn, 
158        window::{
159            Window, 
160            WindowBuilder
161        }
162    }
163};
164
165/// The Engine
166pub struct FeoEngine {
167    event_loop: EventLoop<UserEvent<Arc<dyn Any + 'static + Send + Sync>>>,
168    surface: Arc<Surface<Window>>,
169    queue: Arc<Queue>,
170
171    pub scene: Arc<RwLock<Scene>>,
172
173    pub id_system: IDSystem,
174
175    pub globals: EngineGlobals
176}
177
178impl FeoEngine {
179    /// Initialize a FeoEngine.
180    /// 
181    /// # Arguments
182    /// * `scene` - The scene in which GameObjects exist.
183    /// * `index` - The optional device id. The default (None) displays available devices and prompts for the selection of one. 
184    /// # Examples
185    /// ```no_run
186    /// # use feo_oop_engine::FeoEngine;
187    /// # let scene = feo_oop_engine::scene::Scene::new(None);
188    /// FeoEngine::init(scene, Some(1)); // Uses the device with id of 1
189    /// ```
190    pub fn init(scene: Arc<RwLock<Scene>>, index: Option<usize>) -> FeoEngine {
191        // Vulkano Instance
192        let instance = Instance::new(None, &vulkano_win::required_extensions(), None).unwrap();
193
194        // Physical Device
195        let physical = term_ui::prompt_physical_device(&instance, index);
196
197        // event loop
198        let event_loop = EventLoop::<UserEvent<Arc<dyn Any + Send + Sync>>>::with_user_event();
199
200        // surface
201        let surface = {
202            let mut builder = WindowBuilder::new();
203            builder.window.inner_size = Some(PhysicalSize::new(1024_u32, 512_u32).into());
204            builder.build_vk_surface(&event_loop, instance.clone()).unwrap()
205        };
206
207        // get access to the device and get graphics queue
208        let (_device, queue) = {
209            let queue_family = physical
210                .queue_families()
211                .find(|&q| q.supports_graphics() && surface.is_supported(q).unwrap_or(false))
212                .unwrap();
213
214            let device_ext = DeviceExtensions {
215                khr_swapchain: true,
216                khr_storage_buffer_storage_class: true,
217                ..DeviceExtensions::none()
218            };
219
220            let features = physical.supported_features();
221            // TODO assertions
222            
223            let (device, mut queues) = Device::new(
224                physical,
225                features,
226                &device_ext,
227                [(queue_family, 0.5)].iter().cloned(),
228            ).unwrap();
229
230            (device, queues.next().unwrap())
231        };
232
233        Texture::default(queue.clone());
234
235        let id_system = IDSystem::default();
236        
237        FeoEngine {
238            globals: EngineGlobals{ // todo fix
239                queue: queue.clone(),
240                surface: surface.clone(),
241                scene: scene.clone(),
242                event_loop_proxy: Arc::new(futures::lock::Mutex::new(event_loop.create_proxy())),
243                id_system: id_system.clone()
244            },
245
246            //instance,
247            event_loop,
248            surface,
249            queue,
250
251            scene,
252
253            id_system,
254        }
255    }
256
257    /// Allows the engine to commence excecution.
258    pub fn run(&mut self) {
259        // get swapchain and images
260        let dimensions: [u32; 2] = self.surface.window().inner_size().into();
261        let (mut swapchain, _) = {
262            let caps = self.surface.capabilities(self.queue.device().physical_device()).unwrap();
263            let format = caps.supported_formats[0].0;
264            let alpha = caps.supported_composite_alpha.iter().next().unwrap();
265
266            let (swapchain, images) = Swapchain::new(
267                self.queue.device().clone(),
268                self.surface.clone(),
269                caps.min_image_count,
270                format,
271                dimensions,
272                1,
273                ImageUsage::color_attachment(),
274                &self.queue,
275                SurfaceTransform::Identity,
276                alpha,
277                PresentMode::Fifo,
278                FullscreenExclusive::Default,
279                true,
280                ColorSpace::SrgbNonLinear,
281            ).unwrap();
282
283            let images = images
284                .into_iter()
285                .map(|image| ImageView::new(image).unwrap())
286                .collect::<Vec<_>>();
287            
288            (swapchain, images)
289        };
290
291        // Deferred system
292        let mut frame_system = FrameSystem::new(self.queue.clone(), swapchain.format(), dimensions);
293        
294        // Frame Future
295        let mut previous_frame_end = Some(sync::now(self.queue.device().clone()).boxed());
296
297        // Event Loop proxy
298        let proxy = self.event_loop.create_proxy();
299        
300        // Self pointer
301        let local_self: *mut Self = self;
302
303        self.event_loop.run_return(move | mut event, _, control_flow| {
304            // a mutable reference to self
305            let local_self = unsafe {&mut *local_self};
306    
307            // Deal with Event Redundancy
308            while let Event::UserEvent(UserEvent::WinitEvent(inner_event)) = event {
309                event = match inner_event {
310                    Event::UserEvent(boxed_user_event) => Event::UserEvent(*boxed_user_event),
311                    Event::NewEvents(start_case) => Event::NewEvents(start_case),
312                    Event::WindowEvent { window_id, event } => Event::WindowEvent { window_id, event },
313                    Event::DeviceEvent { device_id, event } => Event::DeviceEvent { device_id, event },
314                    Event::Suspended => Event::Suspended,
315                    Event::Resumed => Event::Resumed,
316                    Event::MainEventsCleared => Event::MainEventsCleared,
317                    Event::RedrawRequested(window_id) => Event::RedrawRequested(window_id),
318                    Event::RedrawEventsCleared => Event::RedrawEventsCleared,
319                    Event::LoopDestroyed => Event::LoopDestroyed,
320                };
321            }
322
323            // Static Event
324            let event: Event<'static, UserEvent<Arc<dyn Any + Send + Sync>>> = event.to_static().unwrap();
325            
326            // Executor for Object event handlers
327            let h_executor = {
328                let (executor, spawner) = scripting::new_executor_and_spawner(local_self.globals.clone());
329                local_self.scene.read().unwrap().spawn_script_handlers(spawner, event.clone());
330                executor
331            };
332            
333            match event {
334                Event::WindowEvent {
335                    event: WindowEvent::CloseRequested,
336                    ..
337                } => {
338                    *control_flow = ControlFlow::Exit;
339                },
340                Event::UserEvent( UserEvent::RebuildSwapchain ) | 
341                Event::WindowEvent {
342                    event: WindowEvent::Resized(_),
343                    ..
344                } => { 
345                    // resizing is slower however because no dynamic viewports are used rendering is faster
346                    let dimensions: [u32; 2] = local_self.surface.window().inner_size().into();
347
348                    let (new_swapchain, new_images) = 
349                        match swapchain.recreate_with_dimensions(dimensions) {
350                            Ok(r) => r,
351                            Err(SwapchainCreationError::UnsupportedDimensions) => return,
352                            Err(e) => panic!("Failed to recreate swapchain: {:?}", e),
353                        };
354
355                    let new_images = new_images
356                        .into_iter()
357                        .map(|image| ImageView::new(image).unwrap())
358                        .collect::<Vec<_>>();
359                        
360                    swapchain = new_swapchain;
361
362                    frame_system.rebuild_dims(&new_images[..]);
363                },
364                Event::RedrawEventsCleared => {
365                    // Clear buffer pool
366                    previous_frame_end.as_mut().unwrap().cleanup_finished();
367
368                    // Generate Executor and Spawner for scripts
369                    let (executor, spawner) = scripting::new_executor_and_spawner(local_self.globals.clone());
370                    local_self.scene.read().unwrap().spawn_script_cores(spawner);
371
372                    // Get the next image
373                    let (image_num, suboptimal, acquire_future) =
374                        match swapchain::acquire_next_image(swapchain.clone(), None) {
375                            Ok(r) => r,
376                            Err(AcquireError::OutOfDate) => {
377                                proxy.send_event(UserEvent::RebuildSwapchain).unwrap();
378                                return;
379                            }
380                            Err(e) => panic!("Failed to acquire next image: {:?}", e),
381                        };
382                    
383                    // rebuild swapchain if suboptimal
384                    if suboptimal { proxy.send_event(UserEvent::RebuildSwapchain).unwrap(); }
385                    
386                    // Run scripts to completion
387                    executor.run(local_self.scene.clone()); // TODO: merge future with other future and start scripts mvd
388                    
389                    let future = local_self.scene.read().unwrap()
390                        .render(local_self.scene.clone(), &mut frame_system, image_num, acquire_future, &mut previous_frame_end)
391                        .then_swapchain_present(local_self.queue.clone(), swapchain.clone(), image_num)
392                        .then_signal_fence_and_flush();
393
394                    match future {
395                        Ok(future) => {
396                            previous_frame_end = Some(future.boxed());
397                        },
398                        Err(FlushError::OutOfDate) => {
399                            proxy.send_event(UserEvent::RebuildSwapchain).unwrap();
400                            previous_frame_end = Some(sync::now(local_self.queue.device().clone()).boxed());
401                        },
402                        Err(e) => {
403                            println!("Failed to flush future: {:?}", e);
404                            previous_frame_end = Some(sync::now(local_self.queue.device().clone()).boxed());
405                        }
406                    }
407                    
408                },
409                _ => {},
410            }
411            
412            // Force event handlers to Completion
413            h_executor.run(local_self.scene.clone());
414        });
415    }
416}