diff --git a/Cargo.lock b/Cargo.lock
index da7454f..9e91469 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -199,6 +199,36 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
+[[package]]
+name = "bevy_app"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0ac033a388b8699d241499a43783a09e6a3bab2430f1297c6bd4974095efb3f"
+dependencies = [
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_reflect",
+ "bevy_tasks",
+ "bevy_utils",
+ "console_error_panic_hook",
+ "ctrlc",
+ "derive_more",
+ "downcast-rs",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "bevy_derive"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d94761ce947b0a2402fd949fe1e7a5b1535293130ba4cd9893be6295d4680a"
+dependencies = [
+ "bevy_macro_utils",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "bevy_ecs"
 version = "0.15.3"
@@ -462,6 +492,16 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "const-random"
 version = "0.1.18"
@@ -543,6 +583,16 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
 
+[[package]]
+name = "ctrlc"
+version = "3.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
+dependencies = [
+ "nix",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "cursor-icon"
 version = "1.1.0"
@@ -993,6 +1043,18 @@ dependencies = [
  "jni-sys",
 ]
 
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -1533,6 +1595,7 @@ name = "rust_vulkan_test"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "bevy_app",
  "bevy_ecs",
  "env_logger",
  "glam",
diff --git a/Cargo.toml b/Cargo.toml
index a84f681..4542e45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,8 +17,9 @@ vulkano-shaders = "0.35"
 glam = { version = "0.30" }
 
 # ECS
-bevy_ecs = "0.15.3"
+bevy_ecs = "0.15"
+bevy_app = "0.15"
 
 # Log and tracing
 log = "0.4"
-env_logger = "0.11.5"
+env_logger = "0.11"
diff --git a/src/core/app/app.rs b/src/core/app/app.rs
deleted file mode 100644
index 3821bfc..0000000
--- a/src/core/app/app.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use std::error::Error;
-
-use bevy_ecs::world::World;
-
-pub enum AppExit {
-    Success,
-    Error(Box<dyn Error>),
-}
-
-pub type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
-
-#[derive(Debug, thiserror::Error)]
-pub enum AppError {
-    #[error("Runner is not set")]
-    RunnerNotSet,
-    #[error("Runner returned an error : {0}")]
-    RunnerError(Box<dyn Error>),
-}
-
-pub struct App {
-    world: World,
-    runner: Option<RunnerFn>,
-}
-
-impl Default for App {
-    fn default() -> Self {
-        Self {
-            world: World::new(),
-            runner: None,
-        }
-    }
-}
-
-impl App {
-    pub fn world_mut(&mut self) -> &mut World {
-        &mut self.world
-    }
-
-    pub fn world(&self) -> &World {
-        &self.world
-    }
-
-    pub fn run(mut self) -> Result<(), AppError> {
-        match self.runner.take() {
-            Some(runner) => match runner(self) {
-                AppExit::Success => Ok(()),
-                AppExit::Error(e) => Err(AppError::RunnerError(e)),
-            },
-            None => Err(AppError::RunnerNotSet),
-        }
-    }
-
-    pub fn set_runner(&mut self, runner: RunnerFn) {
-        self.runner = Some(runner);
-    }
-}
diff --git a/src/core/app/mod.rs b/src/core/app/mod.rs
deleted file mode 100644
index c6c8a20..0000000
--- a/src/core/app/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-mod app;
-pub use app::*;
diff --git a/src/core/mod.rs b/src/core/mod.rs
index abaeeae..db2061f 100644
--- a/src/core/mod.rs
+++ b/src/core/mod.rs
@@ -1,4 +1,3 @@
-pub mod app;
 pub mod camera;
 pub mod render;
 pub mod vulkan;
diff --git a/src/core/vulkan/mod.rs b/src/core/vulkan/mod.rs
index fe22fb3..29fe224 100644
--- a/src/core/vulkan/mod.rs
+++ b/src/core/vulkan/mod.rs
@@ -1,7 +1,7 @@
 use vulkan_context::VulkanContext;
 use window_render_context::WindowRenderContext;
 
-use super::app::App;
+use bevy_app::App;
 
 mod utils;
 mod vulkan_context;
diff --git a/src/core/vulkan/vulkan_context.rs b/src/core/vulkan/vulkan_context.rs
index f59d9a3..b8be6ca 100644
--- a/src/core/vulkan/vulkan_context.rs
+++ b/src/core/vulkan/vulkan_context.rs
@@ -1,5 +1,6 @@
 use std::{any::Any, sync::Arc};
 
+use bevy_app::App;
 use bevy_ecs::system::Resource;
 use vulkano::{
     command_buffer::{
@@ -14,7 +15,7 @@ use vulkano::{
 };
 use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 
-use crate::core::{app::App, window::raw_handle::DisplayHandleWrapper};
+use crate::core::window::raw_handle::DisplayHandleWrapper;
 
 use super::utils;
 
diff --git a/src/core/vulkan/window_render_context.rs b/src/core/vulkan/window_render_context.rs
index 1c653a7..5cb2618 100644
--- a/src/core/vulkan/window_render_context.rs
+++ b/src/core/vulkan/window_render_context.rs
@@ -1,3 +1,4 @@
+use bevy_app::App;
 use bevy_ecs::system::Resource;
 use std::sync::Arc;
 use vulkano::image::view::ImageView;
@@ -8,7 +9,6 @@ use vulkano::sync::{self, GpuFuture};
 use vulkano::{Validated, VulkanError};
 use winit::window::Window;
 
-use crate::core::app::App;
 use crate::core::window::raw_handle::WindowWrapper;
 
 use super::vulkan_context::VulkanContext;
diff --git a/src/core/window/mod.rs b/src/core/window/mod.rs
index 4368310..d1623b7 100644
--- a/src/core/window/mod.rs
+++ b/src/core/window/mod.rs
@@ -1,10 +1,9 @@
+use bevy_app::{App, AppExit};
 use config::WindowConfig;
 use raw_handle::{DisplayHandleWrapper, EventLoopProxyWrapper};
 use state::WindowState;
 use winit::event_loop::EventLoop;
 
-use super::app::{App, AppExit};
-
 pub mod config;
 pub mod raw_handle;
 pub mod state;
@@ -43,6 +42,9 @@ fn runner(mut app: App, event_loop: EventLoop<()>) -> AppExit {
 
     match event_loop.run_app(&mut window_state) {
         Ok(_) => AppExit::Success,
-        Err(e) => AppExit::Error(Box::new(e)),
+        Err(e) => {
+            log::error!("Error running window state: {e}");
+            AppExit::error()
+        }
     }
 }
diff --git a/src/core/window/state.rs b/src/core/window/state.rs
index 0b0966e..8ffd44b 100644
--- a/src/core/window/state.rs
+++ b/src/core/window/state.rs
@@ -1,13 +1,12 @@
 use std::sync::Arc;
 
+use bevy_app::App;
 use bevy_ecs::world::World;
 use winit::{
     application::ApplicationHandler, event::WindowEvent, event_loop::ActiveEventLoop,
     window::WindowId,
 };
 
-use crate::core::app::App;
-
 use super::{config::WindowConfig, raw_handle::WindowWrapper};
 
 pub struct WindowState {
diff --git a/src/game/mod.rs b/src/game/mod.rs
index 0b7787d..6eb9cab 100644
--- a/src/game/mod.rs
+++ b/src/game/mod.rs
@@ -1,5 +1,6 @@
+use bevy_app::App;
+
 use crate::core::{
-    app::App,
     vulkan::Vulkan,
     window::{Window, config::WindowConfig},
 };
diff --git a/src/main.rs b/src/main.rs
index 8297797..34fad4b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,29 +1,40 @@
-use std::error::Error;
 use winit::event_loop::{ControlFlow, EventLoop};
 
+use bevy_app::{App, AppExit};
+
 pub mod core;
 pub mod game;
 pub mod old_app;
 
-fn main() -> Result<(), impl Error> {
+fn main() {
     env_logger::init();
 
-    run_new_app()
-    // run_old_app()
+    run_new_app();
+    // run_old_app();
 }
 
-fn run_new_app() -> Result<(), impl Error> {
-    let mut app = core::app::App::default();
+fn run_new_app() {
+    let mut app = App::default();
     game::init(&mut app);
-    app.run()
+    match app.run() {
+        AppExit::Success => {}
+        AppExit::Error(e) => {
+            log::error!("Error running new app: {e}");
+        }
+    }
 }
 
-fn run_old_app() -> Result<(), impl Error> {
+fn run_old_app() {
     let event_loop = EventLoop::new().unwrap();
     event_loop.set_control_flow(ControlFlow::Poll);
 
     let vulkan_context = old_app::vulkan_context::VulkanContext::from(&event_loop);
     let mut app = old_app::app::App::from(vulkan_context);
 
-    event_loop.run_app(&mut app)
+    match event_loop.run_app(&mut app) {
+        Ok(_) => {}
+        Err(e) => {
+            log::error!("Error running old app: {e}");
+        }
+    }
 }