kitgit

tirbofish/dropbear · diff

c18cb84 · tk

converted i into a lib for redback runtime

Unverified

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..0fce33b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "redback-runtime"]
+	path = redback-runtime
+	url = https://github.com/4tkbytes/redback-runtime
diff --git a/Cargo.toml b/Cargo.toml
index 763103b..171f58d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,11 +6,12 @@ package.repository = "https://github.com/4tkbytes/dropbear-engine"
 package.readme = "README.md"
 
 resolver = "3"
-members = ["dropbear-engine", "eucalyptus"]
+members = ["dropbear-engine", "eucalyptus", "redback-runtime"]
 
 [workspace.dependencies]
 anyhow = { version = "1.0", features = ["backtrace"] }
 app_dirs2 = "2.5"
+bincode = {version = "2.0", features = ["serde"] }
 bytemuck = { version = "1.23", features = ["derive"] }
 chrono = "0.4"
 clap = "4.5"
@@ -39,7 +40,7 @@ pollster = "0.4"
 rfd = "0.15.4"
 rhai = "1.22"
 ron = "0.10.1"
-russimp = { version = "3.2" }
+russimp = { version = "3.2", features = ["prebuilt"] }
 serde = { version = "1.0.219", features = ["derive"] }
 spin_sleep = "1.3"
 tokio = { version = "1", features = ["full"] }
@@ -52,8 +53,8 @@ version = "0.25"
 default-features = false
 features = ["png"]
 
-[patch.crates-io]
-russimp-sys = { git = "https://github.com/4tkbytes/russimp-sys" }
+# [patch.crates-io]
+# russimp-sys = { git = "https://github.com/4tkbytes/russimp-sys" }
 
 [profile.dev]
 opt-level = 0
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index c9b9028..1673308 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -26,7 +26,7 @@ serde.workspace = true
 spin_sleep.workspace = true
 wgpu.workspace = true
 winit.workspace = true
- 
+
 [dependencies.image]
 version = "0.25"
 default-features = false
diff --git a/eucalyptus/Cargo.toml b/eucalyptus/Cargo.toml
index 5a44f9d..532e36d 100644
--- a/eucalyptus/Cargo.toml
+++ b/eucalyptus/Cargo.toml
@@ -11,37 +11,92 @@ repository.workspace = true
 readme.workspace = true
 
 [dependencies]
-anyhow.workspace = true
-app_dirs2.workspace = true
-chrono.workspace = true
-dropbear-engine.workspace = true
-egui-toast-fork.workspace = true
-egui.workspace = true
-egui_dnd.workspace = true
-egui_dock-fork.workspace = true
-egui_extras.workspace = true
-gilrs.workspace = true
-git2.workspace = true
-glam.workspace = true
-hecs.workspace = true
-log.workspace = true
-log-once.workspace = true
-model_to_image.workspace = true
-once_cell.workspace = true
-open.workspace = true
-parking_lot.workspace = true
-rfd.workspace = true
-rhai.workspace = true
-ron.workspace = true
-serde.workspace = true
-tokio.workspace = true
-transform-gizmo-egui.workspace = true
-wgpu.workspace = true
-winit.workspace = true
-clap.workspace = true
+anyhow = { workspace = true, optional = true }
+app_dirs2 = { workspace = true, optional = true }
+bincode = { workspace = true, optional = true }
+chrono = { workspace = true, optional = true }
+dropbear-engine = { workspace = true, optional = true }
+egui-toast-fork = { workspace = true, optional = true }
+egui = { workspace = true, optional = true }
+egui_dnd = { workspace = true, optional = true }
+egui_dock-fork = { workspace = true, optional = true }
+egui_extras = { workspace = true, optional = true }
+gilrs = { workspace = true, optional = true }
+git2 = { workspace = true, optional = true }
+glam = { workspace = true, optional = true }
+hecs = { workspace = true, optional = true }
+log = { workspace = true, optional = true }
+log-once = { workspace = true, optional = true }
+model_to_image = { workspace = true, optional = true }
+once_cell = { workspace = true, optional = true }
+open = { workspace = true, optional = true }
+parking_lot = { workspace = true, optional = true }
+rfd = { workspace = true, optional = true }
+rhai = { workspace = true, optional = true }
+ron = { workspace = true, optional = true }
+serde = { workspace = true, optional = true }
+tokio = { workspace = true, optional = true }
+transform-gizmo-egui = { workspace = true, optional = true }
+wgpu = { workspace = true, optional = true }
+winit = { workspace = true, optional = true }
+clap = { workspace = true, optional = true }
+
+[features]
+editor = [
+    "anyhow",
+    "app_dirs2",
+    "bincode",
+    "chrono",
+    "dropbear-engine",
+    "egui-toast-fork",
+    "egui",
+    "egui_dnd",
+    "egui_dock-fork",
+    "egui_extras",
+    "gilrs",
+    "git2",
+    "glam",
+    "hecs",
+    "log",
+    "log-once",
+    "model_to_image",
+    "once_cell",
+    "open",
+    "parking_lot",
+    "rfd",
+    "rhai",
+    "ron",
+    "serde",
+    "tokio",
+    "transform-gizmo-egui",
+    "wgpu",
+    "winit",
+    "clap"
+]
+default = ["editor"]
+
+data-only = [
+    "serde",
+    "bincode",
+    "anyhow",
+    "tokio",
+    "glam",
+    "log",
+    "dropbear-engine",
+    "rhai",
+    "winit",
+    "hecs",
+    "ron",
+    "git2",
+    "log-once",
+    "once_cell",
+    "egui",
+    "chrono",
+    "parking_lot",
+]
 
 [build-dependencies]
 anyhow = "1.0"
 app_dirs2 = "2.5"
 reqwest = { version = "0.12", features = ["blocking"] }
-zip = "4.3"
+zip = "4.3"
\ No newline at end of file
diff --git a/eucalyptus/README.md b/eucalyptus/README.md
index 351a158..84b4855 100644
--- a/eucalyptus/README.md
+++ b/eucalyptus/README.md
@@ -1,3 +1,3 @@
 # eucalyptus
 
-The game editor for the dropbear game engine. The game engine is powered by [dropbear](https://github.com/4tkbytes/dropbear/tree/main/dropbear-engine) and uses the [redback](https://github.com/4tkbytes/dropbear/tree/main/redback) to bind, build and ship your game. 
\ No newline at end of file
+The game editor for the dropbear game engine. The game engine is powered by [dropbear](https://github.com/4tkbytes/dropbear/tree/main/dropbear-engine) and uses the [redback](https://github.com/4tkbytes/dropbear/tree/main/redback) to bind, build and ship your game.
\ No newline at end of file
diff --git a/eucalyptus/src/build/mod.rs b/eucalyptus/src/build/mod.rs
index b20e171..8ca7a07 100644
--- a/eucalyptus/src/build/mod.rs
+++ b/eucalyptus/src/build/mod.rs
@@ -1,18 +1,88 @@
-use std::path::PathBuf;
+use std::{collections::HashMap, fs, path::PathBuf};
 
+use bincode::{Decode, Encode};
 use clap::ArgMatches;
 
-pub(crate) fn package(_project_path: PathBuf, _sub_matches: &ArgMatches) {
+use crate::states::{ProjectConfig, SceneConfig, SourceConfig, SCENES, SOURCE};
+
+pub fn package(_project_path: PathBuf, _sub_matches: &ArgMatches) {
     todo!()
 }
 
-pub(crate) fn build(_project_path: PathBuf, _sub_matches: &ArgMatches) {
-    todo!()
+pub fn read_from_eupak(eupak_path: PathBuf) -> anyhow::Result<()> {
+    let bytes = std::fs::read(&eupak_path)?;
+    let (content, _): (RuntimeData, usize) = bincode::decode_from_slice(&bytes, bincode::config::standard())?;
+    println!("{} contents: {:#?}", eupak_path.display(), content);
+    Ok(())
 }
 
-pub(crate) fn health() {
-    todo!()
+pub fn build(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result<()> {
+    if !project_path.exists() {
+        return Err(anyhow::anyhow!("Unable to locate project config file"));
+    }
+    ProjectConfig::read_from(&project_path)?.load_config_to_memory()?;
+    
+    let mut project_config = ProjectConfig::read_from(&project_path)?;
+    project_config.load_config_to_memory()?;
+
+    let source_config = {
+        let source_guard = SOURCE.read().map_err(|_| anyhow::anyhow!("Unable to lock SOURCE"))?;
+        source_guard.clone()
+    };
+
+    let scene_data = {
+        let scenes_guard = SCENES.read().map_err(|_| anyhow::anyhow!("Unable to lock SCENES"))?;
+        scenes_guard.clone()
+    };
+
+    let build_dir = project_path.parent().unwrap().join("build").join("output");
+    std::fs::create_dir_all(&build_dir)?;
+
+    let project_name = project_config.project_name.clone();
+
+    let mut scripts = HashMap::new();
+    let script_dir = project_path.parent().unwrap().join("src");
+    if script_dir.exists() {
+        for entry in fs::read_dir(&script_dir)? {
+            let entry = entry?;
+            let path = entry.path();
+            if let Some(ext) = path.extension() {
+                if ext == "rhai" {
+                    let name = path.file_name().unwrap().to_string_lossy().to_string();
+                    let contents = fs::read_to_string(&path)?;
+                    scripts.insert(name, contents);
+                }
+            }
+        }
+    }
+
+    let runtime_data = RuntimeData {
+        project_config,
+        source_config,
+        scene_data,
+        scripts
+    };
+
+    let runtime_file = build_dir.join(format!("{}.eupak", project_name));
+    let serialized = bincode::serde::encode_to_vec(runtime_data, bincode::config::standard())?;
+    std::fs::write(&runtime_file, serialized)?;
+
+    println!("Build completed successfully. Output at {:?}", runtime_file.display());
+    Ok(())
 }
 
-#[allow(dead_code)]
-pub(crate) fn play(_project_path: &PathBuf) {}
+#[derive(Decode, Encode, serde::Serialize, serde::Deserialize, Debug)]
+pub struct RuntimeData {
+    #[bincode(with_serde)]
+    project_config: ProjectConfig,
+    #[bincode(with_serde)]
+    source_config: SourceConfig,
+    #[bincode(with_serde)]
+    scene_data: Vec<SceneConfig>,
+    #[bincode(with_serde)]
+    scripts: HashMap<String, String>,
+}
+
+pub fn health() {
+    todo!()
+}
diff --git a/eucalyptus/src/editor/dock.rs b/eucalyptus/src/editor/dock.rs
index 8c0b013..cce1b45 100644
--- a/eucalyptus/src/editor/dock.rs
+++ b/eucalyptus/src/editor/dock.rs
@@ -100,7 +100,7 @@ pub static TABS_GLOBAL: LazyLock<Mutex<INeedABetterNameForThisStruct>> =
     LazyLock::new(|| Mutex::new(INeedABetterNameForThisStruct::default()));
 
 #[derive(Default)]
-pub(crate) struct INeedABetterNameForThisStruct {
+pub struct INeedABetterNameForThisStruct {
     show_context_menu: bool,
     context_menu_pos: egui::Pos2,
     context_menu_tab: Option<EditorTab>,
diff --git a/eucalyptus/src/lib.rs b/eucalyptus/src/lib.rs
new file mode 100644
index 0000000..bccce4b
--- /dev/null
+++ b/eucalyptus/src/lib.rs
@@ -0,0 +1,19 @@
+#[cfg(feature = "editor")]
+pub mod editor;
+#[cfg(feature = "editor")]
+pub mod menu;
+
+#[cfg(feature = "editor")]
+pub mod build;
+
+pub mod camera;
+pub mod logging;
+pub mod scripting;
+pub mod states;
+pub mod utils;
+
+#[cfg(feature = "editor")]
+pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
+    name: "Eucalyptus",
+    author: "4tkbytes",
+};
\ No newline at end of file
diff --git a/eucalyptus/src/logging.rs b/eucalyptus/src/logging.rs
index dc0f3d4..ebed13e 100644
--- a/eucalyptus/src/logging.rs
+++ b/eucalyptus/src/logging.rs
@@ -5,11 +5,18 @@
 //! - Console
 //! - File (to be implemented)
 
+#[cfg(feature = "editor")]
 use egui::Context;
+
+#[cfg(feature = "editor")]
 use egui_toast_fork::Toasts;
+
+#[cfg(feature = "editor")]
 use once_cell::sync::Lazy;
+#[cfg(feature = "editor")]
 use parking_lot::Mutex;
 
+#[cfg(feature = "editor")]
 pub static GLOBAL_TOASTS: Lazy<Mutex<Toasts>> = Lazy::new(|| {
     Mutex::new(
         Toasts::new()
@@ -21,6 +28,7 @@ pub static GLOBAL_TOASTS: Lazy<Mutex<Toasts>> = Lazy::new(|| {
 /// Renders the toasts. Requires an egui context.
 ///
 /// Useful when paired with a function that contains [`crate`]
+#[cfg(feature = "editor")]
 pub(crate) fn render(context: &Context) {
     let mut toasts = GLOBAL_TOASTS.lock();
     toasts.show(context);
@@ -38,6 +46,7 @@ macro_rules! fatal {
         let _msg = format!($($arg)*);
         log::error!("{}", _msg);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -66,6 +75,7 @@ macro_rules! success {
         let _msg = format!($($arg)*);
         log::debug!("{}", _msg);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -95,6 +105,7 @@ macro_rules! warn {
         let _msg = format!($($arg)*);
         log::warn!("{}", _msg);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -123,6 +134,7 @@ macro_rules! info {
         let _msg = format!($($arg)*);
         log::debug!("{}", _msg);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -152,6 +164,7 @@ macro_rules! info_without_console {
     ($($arg:tt)*) => {
         let _msg = format!($($arg)*);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -181,6 +194,7 @@ macro_rules! success_without_console {
     ($($arg:tt)*) => {
         let _msg = format!($($arg)*);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
@@ -210,6 +224,7 @@ macro_rules! warn_without_console {
     ($($arg:tt)*) => {
         let _msg = format!($($arg)*);
 
+        #[cfg(feature = "editor")]
         {
             use egui_toast_fork::{Toast, ToastKind};
             use crate::logging::GLOBAL_TOASTS;
diff --git a/eucalyptus/src/main.rs b/eucalyptus/src/main.rs
index f8f1ce3..25b86c7 100644
--- a/eucalyptus/src/main.rs
+++ b/eucalyptus/src/main.rs
@@ -1,25 +1,14 @@
-mod editor;
-mod menu;
-
-pub(crate) mod build;
-pub(crate) mod camera;
-pub(crate) mod logging;
-pub(crate) mod scripting;
-pub(crate) mod states;
-pub(crate) mod utils;
-
-use std::{cell::RefCell, fs, path::PathBuf, rc::Rc};
-
+#[cfg(feature = "editor")]
+use std::{cell::RefCell, rc::Rc, fs, path::PathBuf};
+#[cfg(feature = "editor")]
 use clap::{Arg, Command};
-use dropbear_engine::{WindowConfiguration, scene};
 
-pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
-    name: "Eucalyptus",
-    author: "4tkbytes",
-};
+#[cfg(feature = "editor")]
+use dropbear_engine::{WindowConfiguration, scene};
 
 #[tokio::main]
-async fn main() {
+#[cfg(feature = "editor")]
+async fn main() -> anyhow::Result<()> {
     let matches = Command::new("eucalyptus")
         .about("A visual game editor")
         .version("1.0.0")
@@ -45,6 +34,15 @@ async fn main() {
                         .required(false),
                 ),
         )
+        .subcommand(Command::new("read")
+            .about("Reads and displays the contents of a .eupak file for debugging")
+            .arg(
+                    Arg::new("eupak_file")
+                        .help("Path to the .eupak file")
+                        .value_name("RESOURCE_FILE")
+                        .required(false),
+                ),
+        )
         .subcommand(Command::new("health").about("Check the health of the eucalyptus installation"))
         .get_matches();
 
@@ -61,7 +59,7 @@ async fn main() {
                 },
             };
 
-            crate::build::build(project_path, sub_matches);
+            eucalyptus::build::build(project_path, sub_matches)?;
         }
         Some(("package", sub_matches)) => {
             let project_path = match sub_matches.get_one::<String>("project") {
@@ -75,10 +73,24 @@ async fn main() {
                 },
             };
 
-            crate::build::package(project_path, sub_matches);
+            eucalyptus::build::package(project_path, sub_matches);
         }
         Some(("health", _)) => {
-            crate::build::health();
+            eucalyptus::build::health();
+        }
+        Some(("read", sub_matches)) => {
+            let project_path = match sub_matches.get_one::<String>("eupak_file") {
+                Some(path) => PathBuf::from(path),
+                None => match find_eucp_file() {
+                    Ok(path) => path,
+                    Err(e) => {
+                        eprintln!("Error: {}", e);
+                        std::process::exit(1);
+                    }
+                },
+            };
+
+            eucalyptus::build::read_from_eupak(project_path)?;
         }
         None => {
             let config = WindowConfiguration {
@@ -88,8 +100,8 @@ async fn main() {
             };
 
             let _app = dropbear_engine::run_app!(config, |mut scene_manager, mut input_manager| {
-                let main_menu = Rc::new(RefCell::new(menu::MainMenu::new()));
-                let editor = Rc::new(RefCell::new(editor::Editor::new()));
+                let main_menu = Rc::new(RefCell::new(eucalyptus::menu::MainMenu::new()));
+                let editor = Rc::new(RefCell::new(eucalyptus::editor::Editor::new()));
 
                 scene::add_scene_with_input(
                     &mut scene_manager,
@@ -112,8 +124,16 @@ async fn main() {
         }
         _ => unreachable!(),
     }
+    Ok(())
+}
+
+#[cfg(not(feature = "editor"))]
+fn main() {
+    panic!("You have not enabled the \"editor\" feature, therefore cannot use the eucalyptus editor. 
+Ether import as a lib to use its structs and enums or enable the editor feature");
 }
 
+#[cfg(feature = "editor")]
 fn find_eucp_file() -> Result<PathBuf, String> {
     let current_dir = std::env::current_dir().map_err(|_| "Failed to get current directory")?;
 
diff --git a/eucalyptus/src/scripting/input.rs b/eucalyptus/src/scripting/input.rs
index 3d81e05..2427634 100644
--- a/eucalyptus/src/scripting/input.rs
+++ b/eucalyptus/src/scripting/input.rs
@@ -30,7 +30,6 @@ impl InputState {
             mouse_delta: None,
         }
     }
-
     pub fn register_input_modules(engine: &mut Engine) {
         engine.register_type_with_name::<InputState>("InputState");
         engine.register_type_with_name::<Key>("Key");
diff --git a/eucalyptus/src/states.rs b/eucalyptus/src/states.rs
index c2adc71..2649ebc 100644
--- a/eucalyptus/src/states.rs
+++ b/eucalyptus/src/states.rs
@@ -18,14 +18,19 @@ use dropbear_engine::{
     entity::{AdoptedEntity, Transform},
     graphics::Graphics,
 };
+
+#[cfg(feature = "editor")]
 use egui_dock_fork::DockState;
+
 use hecs;
 use log;
 use once_cell::sync::Lazy;
 use ron::ser::PrettyConfig;
 use serde::{Deserialize, Serialize};
 
-use crate::{camera::CameraType, editor::EditorTab};
+use crate::camera::CameraType;
+#[cfg(feature = "editor")]
+use crate::editor::EditorTab;
 
 pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
     Lazy::new(|| RwLock::new(ProjectConfig::default()));
@@ -42,6 +47,7 @@ pub static SCENES: Lazy<RwLock<Vec<SceneConfig>>> = Lazy::new(|| RwLock::new(Vec
 /// # Location
 /// This file is {project_name}.eucp and is located at {project_dir}/
 #[derive(Debug, Deserialize, Serialize, Default)]
+#[cfg(feature = "editor")]
 pub struct ProjectConfig {
     pub project_name: String,
     pub project_path: PathBuf,
@@ -51,21 +57,47 @@ pub struct ProjectConfig {
     pub dock_layout: Option<DockState<EditorTab>>,
 }
 
+#[derive(Debug, Deserialize, Serialize, Default)]
+#[cfg(not(feature = "editor"))]
+pub struct ProjectConfig {
+    pub project_name: String,
+    pub project_path: PathBuf,
+    pub date_created: String,
+    pub date_last_accessed: String,
+    // #[serde(default)]
+    // pub dock_layout: Option<DockState<EditorTab>>,
+}
+
 impl ProjectConfig {
     /// Creates a new instance of the ProjectConfig. This function is typically used when creating
     /// a new project, with it creating new defaults for everything.
     pub fn new(project_name: String, project_path: &PathBuf) -> Self {
         let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
         let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S"));
-        let mut result = Self {
-            project_name,
-            project_path: project_path.to_path_buf(),
-            date_created,
-            date_last_accessed,
-            dock_layout: None,
-        };
-        let _ = result.load_config_to_memory(); // TODO: Deal with later...
-        result
+        #[cfg(not(feature = "editor"))]
+        {
+            let mut result = Self {
+                project_name,
+                project_path: project_path.to_path_buf(),
+                date_created,
+                date_last_accessed,
+            };
+            let _ = result.load_config_to_memory(); // TODO: Deal with later...
+            result
+        }
+
+        #[cfg(feature = "editor")]
+        {
+            let mut result = Self {
+                project_name,
+                project_path: project_path.to_path_buf(),
+                date_created,
+                date_last_accessed,
+                dock_layout: None,
+            };
+            let _ = result.load_config_to_memory(); // TODO: Deal with later...
+            result
+        }
     }
 
     /// This function writes the [`ProjectConfig`] struct (and other PathBufs) to a file of the choice
@@ -240,20 +272,20 @@ pub(crate) fn path_contains_folder(path: &PathBuf, folder: &str) -> bool {
     path.components().any(|comp| comp.as_os_str() == folder)
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub enum Node {
     File(File),
     Folder(Folder),
 }
 
-#[derive(Default, Debug, Serialize, Deserialize)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct File {
     pub name: String,
     pub path: PathBuf,
     pub resource_type: Option<ResourceType>,
 }
 
-#[derive(Default, Debug, Serialize, Deserialize)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct Folder {
     pub name: String,
     pub path: PathBuf,
@@ -337,7 +369,7 @@ impl ResourceConfig {
     }
 }
 
-#[derive(Default, Debug, Serialize, Deserialize)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct SourceConfig {
     /// The path to the resource folder.
     pub path: PathBuf,
diff --git a/eucalyptus/src/utils.rs b/eucalyptus/src/utils.rs
index 4b01906..ea8c4c0 100644
--- a/eucalyptus/src/utils.rs
+++ b/eucalyptus/src/utils.rs
@@ -1,16 +1,26 @@
+#[cfg(feature = "editor")]
 use std::{
-    fs,
-    path::PathBuf,
-    sync::mpsc::{self, Receiver},
+    fs, sync::mpsc::{self, Receiver},
 };
 
+use std::path::PathBuf;
+
+#[cfg(feature = "editor")]
 use anyhow::anyhow;
-use dropbear_engine::{camera::Camera, entity::Transform, scene::SceneCommand};
+use dropbear_engine::{camera::Camera, entity::Transform};
+#[cfg(feature = "editor")]
+use dropbear_engine::scene::SceneCommand;
+#[cfg(feature = "editor")]
 use egui::Context;
+
+#[cfg(feature = "editor")]
 use egui_toast_fork::{Toast, ToastOptions, Toasts};
+#[cfg(feature = "editor")]
 use git2::Repository;
 
-use crate::states::{ModelProperties, Node, PROJECT, ProjectConfig};
+use crate::states::{ModelProperties, Node};
+#[cfg(feature = "editor")]
+use crate::states::{PROJECT, ProjectConfig};
 
 pub fn search_nodes_recursively<'a, F>(nodes: &'a [Node], matcher: &F, results: &mut Vec<&'a Node>)
 where
@@ -43,6 +53,7 @@ pub enum ProjectProgress {
 /// Open a project file and update the global state.
 /// Returns Ok(Some(SceneCommand::SwitchScene)) on success, or an error string on failure.
 #[allow(dead_code)]
+#[cfg(feature = "editor")]
 pub fn open_project(
     scene_command: &mut SceneCommand,
     toast: &mut Toasts,
@@ -79,6 +90,8 @@ pub fn open_project(
 
 /// Start creating a new project in a background thread.
 /// Returns a Receiver for progress updates.
+/// 
+#[cfg(feature = "editor")]
 pub fn start_project_creation(
     project_name: String,
     project_path: Option<PathBuf>,
@@ -159,6 +172,7 @@ pub fn start_project_creation(
     Some(rx)
 }
 
+#[cfg(feature = "editor")]
 pub fn show_new_project_window<F>(
     ctx: &Context,
     show_new_project: &mut bool,
diff --git a/redback-runtime b/redback-runtime
new file mode 160000
index 0000000..cbe1cad
--- /dev/null
+++ b/redback-runtime
@@ -0,0 +1 @@
+Subproject commit cbe1cadb0509c7b52efa962295f354749b1beaf4