kitgit

tirbofish/dropbear · diff

0e8ba86 · tk

about to merge to work on it

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 171f58d..13f2a5d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ members = ["dropbear-engine", "eucalyptus", "redback-runtime"]
 [workspace.dependencies]
 anyhow = { version = "1.0", features = ["backtrace"] }
 app_dirs2 = "2.5"
-bincode = {version = "2.0", features = ["serde"] }
+bincode = { version = "2.0", features = ["serde"] }
 bytemuck = { version = "1.23", features = ["derive"] }
 chrono = "0.4"
 clap = "4.5"
@@ -47,6 +47,8 @@ tokio = { version = "1", features = ["full"] }
 transform-gizmo-egui = { git = "https://github.com/4tkbytes/transform-gizmo"}
 wgpu = "25"
 winit = { version = "0.30", features = [] }
+zip = "0.6"
+walkdir = "2.3"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/README.md b/README.md
index bf5232c..0ab1a29 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ If you might have not realised, all the crates/projects names are after Australi
 ## Related projects
 
 - [eucalyptus](https://github.com/4tkbytes/dropbear/tree/main/eucalyptus) is the visual editor used to create games visually, taking inspiration from Unity and other engines.
-- [redback](https://github.com/4tkbytes/dropbear/tree/main/redback) is the build system used by [eucalyptus](https://github.com/4tkbytes/dropbear/tree/main/eucalyptus) to bind, build and ship games made with the engine.
+- [redback](https://github.com/4tkbytes/dropbear/tree/main/redback) is the runtime used to load .eupak files and run the games loaded on them.
 
 ## Build
 
diff --git a/eucalyptus/Cargo.toml b/eucalyptus/Cargo.toml
index 532e36d..486a081 100644
--- a/eucalyptus/Cargo.toml
+++ b/eucalyptus/Cargo.toml
@@ -40,6 +40,8 @@ transform-gizmo-egui = { workspace = true, optional = true }
 wgpu = { workspace = true, optional = true }
 winit = { workspace = true, optional = true }
 clap = { workspace = true, optional = true }
+walkdir = { workspace = true, optional = true }
+zip = { workspace = true, optional = true }
 
 [features]
 editor = [
@@ -71,7 +73,9 @@ editor = [
     "transform-gizmo-egui",
     "wgpu",
     "winit",
-    "clap"
+    "clap",
+    "walkdir",
+    "zip"
 ]
 default = ["editor"]
 
@@ -93,6 +97,8 @@ data-only = [
     "egui",
     "chrono",
     "parking_lot",
+    "walkdir",
+    "zip"
 ]
 
 [build-dependencies]
diff --git a/eucalyptus/README.md b/eucalyptus/README.md
index 84b4855..5fe767a 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) runtime to run and load your game in an easy way.
diff --git a/eucalyptus/src/build/mod.rs b/eucalyptus/src/build/mod.rs
index bff2276..ffb67cc 100644
--- a/eucalyptus/src/build/mod.rs
+++ b/eucalyptus/src/build/mod.rs
@@ -1,11 +1,274 @@
-use std::{collections::HashMap, fs, path::PathBuf};
+use std::{collections::HashMap, fs, path::PathBuf, process::Command};
 
 use clap::ArgMatches;
 
 use crate::states::{ProjectConfig, RuntimeData, SCENES, SOURCE};
 
-pub fn package(_project_path: PathBuf, _sub_matches: &ArgMatches) {
-    todo!()
+pub fn package(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result<()> {
+    if !project_path.exists() {
+        return Err(anyhow::anyhow!("Unable to locate project config file"));
+    }
+
+    let build_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build");
+
+    // check health
+    println!("Checking health (checking if commands exist)");
+    health()?;
+    println!("Health check completed!");
+
+    let clone_dir = build_dir.join("redback-runtime");
+    
+    if clone_dir.exists() {
+        println!("Repository directory exists, checking for updates...");
+        if should_update_repository(&clone_dir)? {
+            println!("Repository has changes or is outdated, removing and re-cloning...");
+            std::fs::remove_dir_all(&clone_dir)?;
+            clone_repository(&build_dir)?;
+        } else {
+            println!("Repository is up to date, skipping clone");
+        }
+    } else {
+        println!("Cloning repository");
+        clone_repository(&build_dir)?;
+    }
+
+    let project_config = ProjectConfig::read_from(&project_path)?;
+    let project_name = project_config.project_name.clone();
+
+    // cd into redback-runtime folder and compile redback-runtime using cargo
+    let runtime_dir = build_dir.join("redback-runtime");
+    if !runtime_dir.exists() {
+        return Err(anyhow::anyhow!("redback-runtime directory not found after cloning"));
+    }
+
+    let cargo_toml_path = runtime_dir.join("Cargo.toml");
+    if cargo_toml_path.exists() {
+        let cargo_toml_content = std::fs::read_to_string(&cargo_toml_path)?;
+        let modified_content = cargo_toml_content.replace(
+            r#"name = "redback-runtime""#, 
+            &format!(r#"name = "{}""#, project_name)
+        );
+        std::fs::write(&cargo_toml_path, modified_content)?;
+        println!("Updated Cargo.toml with project name: {}", project_name);
+    }
+
+    println!("Building {} for release", project_name);
+    let cargo_build = Command::new("cargo")
+        .args(&["build", "--release"])
+        .current_dir(&runtime_dir)
+        .output()?;
+
+    if !cargo_build.status.success() {
+        let stderr = String::from_utf8_lossy(&cargo_build.stderr);
+        return Err(anyhow::anyhow!("Failed to build {}:\n{}", project_name, stderr));
+    }
+    println!("{} built successfully!", project_name);
+
+    let target_dir = runtime_dir.join("target").join("release");
+    let exe_name = if cfg!(target_os = "windows") {
+        format!("{}.exe", project_name)
+    } else {
+        project_name.clone()
+    };
+    
+    let built_exe = target_dir.join(&exe_name);
+    if !built_exe.exists() {
+        return Err(anyhow::anyhow!("Built executable not found at: {}", built_exe.display()));
+    }
+
+    println!("Building project data (.eupak file)");
+    build(project_path.clone(), _sub_matches)?;
+
+    let output_dir = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("package");
+    std::fs::create_dir_all(&output_dir)?;
+
+    let output_exe = output_dir.join(&exe_name);
+    
+    println!("Copying executable to: {}", output_exe.display());
+    std::fs::copy(&built_exe, &output_exe)?;
+
+    let eupak_source = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join("output").join(format!("{}.eupak", project_name));
+    let eupak_dest = output_dir.join(format!("{}.eupak", project_name));
+    
+    if !eupak_source.exists() {
+        return Err(anyhow::anyhow!("Expected .eupak file not found at: {}", eupak_source.display()));
+    }
+    
+    println!("Copying .eupak file to: {}", eupak_dest.display());
+    std::fs::copy(&eupak_source, &eupak_dest)?;
+
+    let project_resources = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("resources");
+    if project_resources.exists() {
+        println!("Copying resources folder...");
+        let output_resources = output_dir.join("resources");
+        copy_resources_folder(&project_resources, &output_resources)?;
+    }
+
+    #[cfg(unix)]
+    {
+        use std::os::unix::fs::PermissionsExt;
+        let mut perms = std::fs::metadata(&output_exe)?.permissions();
+        perms.set_mode(0o755); // rwxr-xr-x
+        std::fs::set_permissions(&output_exe, perms)?;
+    }
+
+    copy_system_libraries(&output_dir)?;
+
+    println!("Creating zip package...");
+    let zip_path = project_path.parent().ok_or(anyhow::anyhow!("Unable to get parent"))?.join("build").join(format!("{}.zip", project_name));
+    create_zip_package(&output_dir, &zip_path)?;
+
+    println!("Cleaning up temporary files");
+    if build_dir.join("redback-runtime").exists() {
+        std::fs::remove_dir_all(build_dir.join("redback-runtime"))?;
+    }
+    if build_dir.join("output").exists() {
+        std::fs::remove_dir_all(build_dir.join("output"))?;
+    }
+
+    println!("\n✓ Package completed successfully!");
+    println!("Output directory: {}", output_dir.display());
+    println!("Zip package: {}", zip_path.display());
+    println!("Executable: {}", exe_name);
+    println!("Data file: {}.eupak", project_name);
+
+    Ok(())
+}
+
+fn clone_repository(build_dir: &PathBuf) -> anyhow::Result<()> {
+    git2::build::RepoBuilder::new()
+        .clone("https://github.com/4tkbytes/redback-runtime", &build_dir.join("redback-runtime"))?;
+    println!("Repository cloned successfully!");
+    Ok(())
+}
+
+fn should_update_repository(repo_dir: &PathBuf) -> anyhow::Result<bool> {
+    let repo = match git2::Repository::open(repo_dir) {
+        Ok(repo) => repo,
+        Err(_) => {
+            return Ok(true);
+        }
+    };
+
+    let statuses = repo.statuses(None)?;
+    if !statuses.is_empty() {
+        println!("Found local changes in repository");
+        return Ok(true);
+    }
+
+    let mut remote = repo.find_remote("origin")?;
+    remote.fetch(&["refs/heads/*:refs/remotes/origin/*"], None, None)?;
+
+    let head = repo.head()?.target().ok_or(anyhow::anyhow!("No HEAD commit"))?;
+    
+    let remote_ref = if let Ok(remote_main) = repo.find_reference("refs/remotes/origin/main") {
+        remote_main
+    } else if let Ok(remote_master) = repo.find_reference("refs/remotes/origin/master") {
+        remote_master
+    } else {
+        return Ok(true);
+    };
+
+    let remote_commit = remote_ref.target().ok_or(anyhow::anyhow!("No remote commit"))?;
+
+    Ok(head != remote_commit)
+}
+
+fn copy_resources_folder(src: &PathBuf, dest: &PathBuf) -> anyhow::Result<()> {
+    std::fs::create_dir_all(dest)?;
+
+    for entry in fs::read_dir(src)? {
+        let entry = entry?;
+        let src_path = entry.path();
+        let file_name = entry.file_name();
+
+        if file_name == "resources.eucc" {
+            continue;
+        }
+
+        let dest_path = dest.join(&file_name);
+
+        if src_path.is_dir() {
+            copy_resources_folder(&src_path, &dest_path)?;
+        } else {
+            std::fs::copy(&src_path, &dest_path)?;
+        }
+    }
+
+    Ok(())
+}
+
+fn copy_system_libraries(output_dir: &PathBuf) -> anyhow::Result<()> {
+    #[cfg(target_os = "windows")]
+    {
+        let dll_paths = vec![
+            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp-vc143-mt.dll",
+            "C:\\vcpkg\\installed\\x64-windows\\bin\\assimp.dll",
+            "C:\\Program Files\\Assimp\\bin\\assimp.dll",
+            "C:\\Program Files (x86)\\Assimp\\bin\\assimp.dll",
+        ];
+
+        for dll_path in dll_paths {
+            if std::path::Path::new(dll_path).exists() {
+                let dll_name = std::path::Path::new(dll_path).file_name().unwrap();
+                let dest = output_dir.join(dll_name);
+                std::fs::copy(dll_path, dest)?;
+                println!("Copied system library: {}", dll_name.to_string_lossy());
+                break;
+            }
+        }
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    {
+        let lib_dir = output_dir.join("lib");
+        std::fs::create_dir_all(&lib_dir)?;
+
+        let lib_paths = vec![
+            "/usr/lib/libassimp.so",
+            "/usr/lib/x86_64-linux-gnu/libassimp.so",
+            "/usr/lib64/libassimp.so",
+            "/usr/local/lib/libassimp.so",
+            "/opt/homebrew/lib/libassimp.dylib",
+            "/usr/local/lib/libassimp.dylib",
+        ];
+
+        for lib_path in lib_paths {
+            if std::path::Path::new(lib_path).exists() {
+                let lib_name = std::path::Path::new(lib_path).file_name().unwrap();
+                let dest = lib_dir.join(lib_name);
+                std::fs::copy(lib_path, dest)?;
+                println!("Copied system library: {}", lib_name.to_string_lossy());
+                break;
+            }
+        }
+    }
+
+    Ok(())
+}
+
+fn create_zip_package(source_dir: &PathBuf, zip_path: &PathBuf) -> anyhow::Result<()> {
+    let file = std::fs::File::create(zip_path)?;
+    let mut zip = zip::ZipWriter::new(file);
+
+    let walkdir = walkdir::WalkDir::new(source_dir);
+    for entry in walkdir {
+        let entry = entry?;
+        let path = entry.path();
+        
+        if path.is_file() {
+            let relative_path = path.strip_prefix(source_dir)?;
+            let name = relative_path.to_string_lossy();
+            
+            zip.start_file(name, zip::write::FileOptions::default())?;
+            let mut file = std::fs::File::open(path)?;
+            std::io::copy(&mut file, &mut zip)?;
+        }
+    }
+
+    zip.finish()?;
+    println!("Created zip package: {}", zip_path.display());
+    Ok(())
 }
 
 pub fn read_from_eupak(eupak_path: PathBuf) -> anyhow::Result<()> {
@@ -70,6 +333,120 @@ pub fn build(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result
     Ok(())
 }
 
-pub fn health() {
-    todo!()
+pub fn health() -> anyhow::Result<()> {
+    let mut all_healthy = true;
+    
+    match Command::new("cargo").arg("--version").output() {
+        Ok(output) => {
+            if output.status.success() {
+                let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
+                println!("Does cargo exist? ✓ YES - {}", version);
+                
+                match Command::new("rustc").arg("--version").output() {
+                    Ok(rustc_output) => {
+                        if rustc_output.status.success() {
+                            let rustc_version = String::from_utf8_lossy(&rustc_output.stdout).trim().to_string();
+                            println!("Does rustc compiler exist? ✓ YES - {}", rustc_version);
+                        } else {
+                            println!("Does rustc compiler exist? ✗ NO - rustc command failed");
+                            all_healthy = false;
+                        }
+                    }
+                    Err(_) => {
+                        println!("Does rustc compiler exist? ✗ NO - rustc not found in PATH");
+                        all_healthy = false;
+                    }
+                }
+            } else {
+                println!("Does cargo exist? ✗ NO - cargo command failed");
+                println!("Does rustc compiler exist? ✗ NO - cargo failed, cannot check rustc");
+                all_healthy = false;
+            }
+        }
+        Err(_) => {
+            println!("Does cargo exist? ✗ NO - cargo not found in PATH");
+            println!("Does rustc compiler exist? ✗ NO - cargo not found, cannot check rustc");
+            all_healthy = false;
+        }
+    }
+    
+    let assimp_found = check_assimp_availability();
+    if assimp_found {
+        println!("Does assimp lib exist? ✓ YES - Found assimp library");
+    } else {
+        println!("Does assimp lib exist? ⚠ MAYBE - Could not definitively locate assimp, but it may be available through system package manager or vcpkg");
+    }
+    
+    if all_healthy {
+        println!("\n✓ All core tools are available!");
+    } else {
+        println!("\n✗ Some required tools are missing. Please install Rust and Cargo.");
+        return Err(anyhow::anyhow!("Health check failed - missing required tools"));
+    }
+    
+    Ok(())
+}
+
+fn check_assimp_availability() -> bool {
+    #[cfg(target_os = "windows")]
+    {
+        let common_paths = vec![
+            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp-vc143-mt.lib",
+            "C:\\vcpkg\\installed\\x64-windows\\lib\\assimp.lib",
+            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp-vc143-mt.lib",
+            "C:\\vcpkg\\installed\\x86-windows\\lib\\assimp.lib",
+            "C:\\Program Files\\Assimp\\lib\\assimp.lib",
+            "C:\\Program Files (x86)\\Assimp\\lib\\assimp.lib",
+        ];
+        
+        for path in common_paths {
+            if std::path::Path::new(path).exists() {
+                return true;
+            }
+        }
+        
+        if let Ok(output) = Command::new("where").arg("assimp.dll").output() {
+            if output.status.success() {
+                return true;
+            }
+        }
+    }
+    
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    {
+        let common_paths = vec![
+            "/usr/lib/libassimp.so",
+            "/usr/lib/x86_64-linux-gnu/libassimp.so",
+            "/usr/lib64/libassimp.so",
+            "/usr/local/lib/libassimp.so",
+            "/opt/homebrew/lib/libassimp.dylib",
+            "/usr/local/lib/libassimp.dylib",
+        ];
+        
+        for path in common_paths {
+            if std::path::Path::new(path).exists() {
+                return true;
+            }
+        }
+        
+        if let Ok(output) = Command::new("pkg-config").args(&["--exists", "assimp"]).output() {
+            if output.status.success() {
+                return true;
+            }
+        }
+        
+        #[cfg(target_os = "linux")]
+        {
+            if let Ok(output) = Command::new("ldconfig").args(&["-p"]).output() {
+                if output.status.success() {
+                    let output_str = String::from_utf8_lossy(&output.stdout);
+                    if output_str.contains("libassimp") {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    
+    false
 }
diff --git a/eucalyptus/src/main.rs b/eucalyptus/src/main.rs
index 07e8f64..9dbf4fa 100644
--- a/eucalyptus/src/main.rs
+++ b/eucalyptus/src/main.rs
@@ -16,7 +16,7 @@ async fn main() -> anyhow::Result<()> {
         .arg_required_else_help(false)
         .subcommand(
             Command::new("build")
-                .about("Build a eucalyptus project")
+                .about("Build a eucalyptus project, but only the .eupak file and its resources")
                 .arg(
                     Arg::new("project")
                         .help("Path to the .eucp project file")
@@ -26,7 +26,7 @@ async fn main() -> anyhow::Result<()> {
         )
         .subcommand(
             Command::new("package")
-                .about("Package a eucalyptus project")
+                .about("Package a eucalyptus project, which compiles the runtime and the resource .eupak file")
                 .arg(
                     Arg::new("project")
                         .help("Path to the .eucp project file")
@@ -73,10 +73,10 @@ async fn main() -> anyhow::Result<()> {
                 },
             };
 
-            eucalyptus::build::package(project_path, sub_matches);
+            eucalyptus::build::package(project_path, sub_matches)?;
         }
         Some(("health", _)) => {
-            eucalyptus::build::health();
+            eucalyptus::build::health()?;
         }
         Some(("read", sub_matches)) => {
             let project_path = match sub_matches.get_one::<String>("eupak_file") {
diff --git a/eucalyptus/src/scene1.rs b/eucalyptus/src/scene1.rs
index 07949c4..e5efb8d 100644
--- a/eucalyptus/src/scene1.rs
+++ b/eucalyptus/src/scene1.rs
@@ -48,7 +48,6 @@ impl TestingScene1 {
     }
 }
 
-#[async_trait]
 impl Scene for TestingScene1 {
     fn load(&mut self, graphics: &mut Graphics) {
         let shader = Shader::new(
@@ -121,6 +120,22 @@ impl Scene for TestingScene1 {
             a: 1.0,
         };
 
+        // Note: new versions use egui rendering for viewport instead
+        // of using the full window
+        let texture_id = graphics.state.texture_id.clone();
+        let (display_width, display_height) = graphics.screen_size;
+        egui::CentralPanel::default()
+            .frame(egui::Frame::new())
+            .show(graphics.get_egui_context(), |ui| {
+                let rect = ui.max_rect();
+                ui.put(
+                    rect,
+                    egui::Image::new((texture_id, [display_width, display_height].into()))
+                        .fit_to_exact_size([display_width, display_height].into()),
+                );
+            });
+
+        // render everything here
         if let Some(pipeline) = &self.render_pipeline {
             {
                 let mut query = self.world.query::<(&AdoptedEntity, &Transform)>();
diff --git a/redback-runtime b/redback-runtime
index fd266ad..81b4ef5 160000
--- a/redback-runtime
+++ b/redback-runtime
@@ -1 +1 @@
-Subproject commit fd266ad9009590769b06981d6e4d358baf17d0b3
+Subproject commit 81b4ef5bd0c8b98a7983ef0668ea913658285a39