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}