tirbofish/dropbear · commit
c18cb840623c3f214f00b6128e474229f3c50d70
converted i into a lib for redback runtime
Signature present but could not be verified.
Unverified
@@ -0,0 +1,3 @@ +[submodule "redback-runtime"] + path = redback-runtime + url = https://github.com/4tkbytes/redback-runtime @@ -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 @@ -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 @@ -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" @@ -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. +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. @@ -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!() +} @@ -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>, @@ -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", +}; @@ -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; @@ -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")?; @@ -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"); @@ -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, @@ -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, @@ -0,0 +1 @@ +Subproject commit cbe1cadb0509c7b52efa962295f354749b1beaf4