From b8d5c09d08a65e307b114846aae14e02b18d2de3 Mon Sep 17 00:00:00 2001 From: Caznix Date: Thu, 3 Apr 2025 01:00:24 -0400 Subject: [PATCH] improve error handling and add metadata --- Cargo.lock | 509 ++++++++-------------------- engine/Cargo.toml | 20 +- engine/build.rs | 3 + engine/src/core/ecs/mod.rs | 170 ---------- engine/src/core/mod.rs | 1 - engine/src/core/panic.rs | 7 +- engine/src/core/render/ctx.rs | 191 +++-------- engine/src/core/render/mod.rs | 29 +- engine/src/core/repl/commands.rs | 61 +++- engine/src/core/repl/handler.rs | 29 +- engine/src/core/repl/input.rs | 39 ++- engine/src/core/repl/mod.rs | 3 + engine/src/core/workspace/mod.rs | 17 - engine/src/error.rs | 275 +++++++++++++++ engine/src/main.rs | 36 +- engine/src/metadata.rs | 559 +++++++++++++++++++++++++++++++ main.rs | 0 17 files changed, 1162 insertions(+), 787 deletions(-) create mode 100644 engine/build.rs delete mode 100644 engine/src/core/workspace/mod.rs create mode 100644 engine/src/error.rs create mode 100644 engine/src/metadata.rs create mode 100644 main.rs diff --git a/Cargo.lock b/Cargo.lock index 9f31e91..dd42f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,17 +160,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -237,6 +226,15 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" +dependencies = [ + "chrono", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -397,16 +395,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - [[package]] name = "colored" version = "3.0.0" @@ -475,19 +463,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crashreport" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c31e7862dec41e5354ae345c620741457254bbcaf9636622cf97702732e03e" -dependencies = [ - "colored 2.2.0", - "supports-hyperlinks", - "terminal-link", - "url", - "urlencoding", -] - [[package]] name = "crossbeam-channel" version = "0.5.14" @@ -555,17 +530,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "dlib" version = "0.5.2" @@ -674,15 +638,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures" version = "0.3.31" @@ -897,7 +852,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows", + "windows 0.58.0", ] [[package]] @@ -935,15 +890,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.4.0" @@ -988,145 +934,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "indexmap" version = "2.8.0" @@ -1255,12 +1062,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" -[[package]] -name = "litemap" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" - [[package]] name = "litrs" version = "0.4.1" @@ -1452,6 +1253,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1580,6 +1390,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -1862,7 +1681,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix 0.38.44", "tracing", @@ -1972,6 +1791,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -2206,6 +2034,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2288,12 +2125,6 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -2328,15 +2159,6 @@ dependencies = [ "syn", ] -[[package]] -name = "supports-hyperlinks" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" -dependencies = [ - "atty", -] - [[package]] name = "syn" version = "2.0.100" @@ -2349,14 +2171,16 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.13.1" +name = "sysinfo" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "windows 0.57.0", ] [[package]] @@ -2368,12 +2192,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal-link" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "253bcead4f3aa96243b0f8fa46f9010e87ca23bd5d0c723d474ff1d2417bbdf8" - [[package]] name = "thiserror" version = "1.0.69" @@ -2449,16 +2267,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tobj" version = "4.0.3" @@ -2493,11 +2301,26 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2506,6 +2329,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -2582,12 +2407,6 @@ dependencies = [ "rand", ] -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -2618,35 +2437,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -2994,7 +2784,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows", + "windows 0.58.0", "windows-core 0.58.0", ] @@ -3065,6 +2855,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -3084,19 +2884,42 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -3108,6 +2931,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -3125,6 +2959,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -3140,7 +2983,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] @@ -3410,18 +3253,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "x11-dl" version = "2.21.0" @@ -3491,30 +3322,6 @@ version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zen_core" version = "0.1.0" @@ -3529,27 +3336,26 @@ name = "zenyx" version = "0.1.0" dependencies = [ "ahash", - "anyhow", "backtrace", + "built", "bytemuck", "cgmath", "chrono", - "colored 3.0.0", - "crashreport", - "dirs-next", + "colored", "futures", - "lazy_static", "native-dialog", - "once_cell", "parking_lot", + "raw-cpuid", "regex", "rustyline", + "serde", + "sysinfo", "thiserror 2.0.12", "tobj", "tokio", + "toml", "tracing", "tracing-subscriber", - "typenum", "wgpu", "wgpu_text", "winit", @@ -3594,46 +3400,3 @@ dependencies = [ "quote", "syn", ] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 2e11738..5388c30 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -5,26 +5,14 @@ edition = "2024" repository = "https://github.com/Zenyx-Engine/Zenyx" [dependencies] -# TBR -anyhow = "1.0.94" # TBR (if possible) backtrace = "0.3.74" # TBR (if possible) chrono = "0.4.39" colored = "3.0.0" -# TBR (if possible) -crashreport = "1.0.1" -# TBR -dirs-next = "2.0.0" -# TBR == (To be removed) -# TBR -lazy_static.workspace = true -# TBR -once_cell = "1.21.1" parking_lot.workspace = true # TBR (if possible) regex = "1.11.1" -# TBR (if possible) rustyline = { version = "15.0.0", features = ["derive", "rustyline-derive"] } thiserror = "2.0.11" # Tokio is heavy but so far its the best option, we should make better use of it or switch to another runtime. @@ -38,8 +26,14 @@ cgmath = "0.18.0" tracing = "0.1.41" tracing-subscriber = "0.3.19" # TBR -typenum = { version = "1.18.0", features = ["const-generics"] } tobj = { version = "4.0.3", features = ["tokio"] } ahash = "0.8.11" wgpu_text = "0.9.2" +toml = "0.8.20" +serde = { version = "1.0.219", features = ["derive"] } native-dialog = "0.7.0" +sysinfo = "0.34.2" +raw-cpuid = "11.5.0" + +[build-dependencies] +built = { version = "0.7.7", features = ["chrono"] } diff --git a/engine/build.rs b/engine/build.rs new file mode 100644 index 0000000..a7315d0 --- /dev/null +++ b/engine/build.rs @@ -0,0 +1,3 @@ +fn main() { + built::write_built_file().expect("Failed to write build information"); +} \ No newline at end of file diff --git a/engine/src/core/ecs/mod.rs b/engine/src/core/ecs/mod.rs index ae72ce5..e69de29 100644 --- a/engine/src/core/ecs/mod.rs +++ b/engine/src/core/ecs/mod.rs @@ -1,170 +0,0 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::sync::Mutex; - -pub trait Component: Sized + 'static { - fn update(&mut self, delta_time: f32); - fn serialize(&self) -> Vec; - fn deserialize(data: &[u8; 6]) -> Self; -} - -pub trait Entity: Sized { - fn add_component(&mut self, component: C); - fn remove_component(&mut self); - fn get_component(&self) -> Option<&C>; - fn serialize(&self) -> Vec; - fn deserialize(data: &[u8; 6]) -> Self; -} -lazy_static::lazy_static! { - // Global registry mapping component TypeId to a unique bit flag. - static ref COMPONENT_REGISTRY: Mutex> = Mutex::new(HashMap::new()); - static ref NEXT_COMPONENT_BIT: Mutex = Mutex::new(1); -} - -// To allow dynamic dispatch on components (even though Component itself is not object‐safe) -// we wrap them in an object–safe trait. -pub trait ComponentObject: Any { - fn update_obj(&mut self, delta_time: f32); - fn serialize_obj(&self) -> Vec; - fn as_any(&self) -> &dyn Any; -} -impl ComponentObject for T { - fn update_obj(&mut self, delta_time: f32) { - T::update(self, delta_time) - } - fn serialize_obj(&self) -> Vec { - T::serialize(self) - } - fn as_any(&self) -> &dyn Any { - self - } -} - -pub struct EntityImpl { - id: usize, - bitmask: u64, - // The key is the unique bit flag for the component type. - components: HashMap>, -} - -impl EntityImpl { - pub fn new(id: usize) -> Self { - EntityImpl { - id, - bitmask: 0, - components: HashMap::new(), - } - } -} - -impl Entity for EntityImpl { - fn add_component(&mut self, component: C) { - let type_id = TypeId::of::(); - let mut registry = COMPONENT_REGISTRY.lock().unwrap(); - let bit = registry.entry(type_id).or_insert_with(|| { - let mut next_bit = NEXT_COMPONENT_BIT.lock().unwrap(); - let current = *next_bit; - *next_bit *= 2; - current - }); - self.bitmask |= *bit; - self.components.insert(*bit, Box::new(component)); - } - - fn remove_component(&mut self) { - let type_id = TypeId::of::(); - if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) { - self.bitmask &= !bit; - } - } - - fn get_component(&self) -> Option<&C> { - let type_id = TypeId::of::(); - if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) { - self.components.get(&bit) - .and_then(|boxed| boxed.as_any().downcast_ref::()) - } else { - None - } - } - - fn serialize(&self) -> Vec { - // Serialize the entity's bitmask into 6 bytes (lowest 48 bits). - let mut bytes = self.bitmask.to_le_bytes().to_vec(); - bytes.truncate(6); - bytes - } - - fn deserialize(data: &[u8; 6]) -> Self { - let mut full = [0u8; 8]; - full[..6].copy_from_slice(data); - let bitmask = u64::from_le_bytes(full); - // When deserializing, we recreate an entity with the restored bitmask. - // Note: The individual component data are not restored here. - Self { - id: 0, - bitmask, - components: HashMap::new(), - } - } - -} - -pub struct ECS { - next_entity_id: usize, - pub entities: HashMap, -} - -impl ECS { - pub fn new() -> Self { - ECS { - next_entity_id: 0, - entities: HashMap::new(), - } - } - - pub fn create_entity(&mut self) -> &mut EntityImpl { - let entity = EntityImpl::new(self.next_entity_id); - self.entities.insert(self.next_entity_id, entity); - self.next_entity_id += 1; - self.entities.get_mut(&(self.next_entity_id - 1)).unwrap() - } - - pub fn update(&mut self, delta_time: f32) { - for entity in self.entities.values_mut() { - // Update each component attached to the entity. - for comp in entity.components.values_mut() { - comp.update_obj(delta_time); - } - } - } - - pub fn serialize(&self) -> Vec { - let mut data = Vec::new(); - // For each entity, store its id (8 bytes) and its 6-byte bitmask. - for (id, entity) in &self.entities { - data.extend_from_slice(&id.to_le_bytes()); - data.extend_from_slice(&entity.serialize()); - } - data - } - - pub fn deserialize(&mut self, data: &[u8]) { - self.entities.clear(); - // Each serialized entity uses 8 (id) + 6 (bitmask) = 14 bytes. - let entity_size = 14; - let count = data.len() / entity_size; - for i in 0..count { - let offset = i * entity_size; - let mut id_bytes = [0u8; 8]; - id_bytes.copy_from_slice(&data[offset..offset + 8]); - let id = usize::from_le_bytes(id_bytes); - - let mut mask_bytes = [0u8; 6]; - mask_bytes.copy_from_slice(&data[offset + 8..offset + 14]); - let entity = EntityImpl::deserialize(&mask_bytes); - self.entities.insert(id, entity); - } - self.next_entity_id = count; - } -} \ No newline at end of file diff --git a/engine/src/core/mod.rs b/engine/src/core/mod.rs index 76b95fe..e08a06c 100644 --- a/engine/src/core/mod.rs +++ b/engine/src/core/mod.rs @@ -2,6 +2,5 @@ pub mod ecs; pub mod panic; pub mod repl; pub mod splash; -pub mod workspace; pub mod render; diff --git a/engine/src/core/panic.rs b/engine/src/core/panic.rs index 25dba4d..08e5124 100644 --- a/engine/src/core/panic.rs +++ b/engine/src/core/panic.rs @@ -1,4 +1,5 @@ -use std::error::Error; +use std::str::FromStr; +use std::{error::Error, path::PathBuf}; use std::fmt::Write as FmtWrite; use std::mem; @@ -24,11 +25,11 @@ pub fn set_panic_hook() { } fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box> { - use crate::workspace; + use colored::Colorize; use std::io::Write; - let log_dir = workspace::get_working_dir()?; + let log_dir = PathBuf::from_str("./").expect("wtf, The current directory no longer exists?"); if !log_dir.exists() { std::fs::create_dir_all(&log_dir)?; } diff --git a/engine/src/core/render/ctx.rs b/engine/src/core/render/ctx.rs index 7024e0f..3df1bfc 100644 --- a/engine/src/core/render/ctx.rs +++ b/engine/src/core/render/ctx.rs @@ -5,132 +5,15 @@ use std::time::Instant; use cgmath::{Deg, Matrix4, Point3, Rad, SquareMatrix, Vector3, perspective}; use futures::executor::block_on; -use thiserror::Error; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; use wgpu::TextureUsages; use wgpu::{Backends, InstanceDescriptor, util::DeviceExt}; use wgpu_text::glyph_brush::ab_glyph::FontRef; use wgpu_text::glyph_brush::{HorizontalAlign, Layout, OwnedSection, OwnedText, VerticalAlign}; use wgpu_text::{BrushBuilder, TextBrush}; use winit::window::Window; - -#[derive(Debug, Error)] -#[error(transparent)] -pub enum ContextErrorKind { - #[error("Surface creation failed")] - SurfaceCreation, - #[error("Surface configuration failed")] - SurfaceConfiguration, - #[error("Adapter request failed")] - AdapterRequest, - #[error("Device request failed")] - DeviceRequest, - #[error("Surface texture acquisition failed")] - SurfaceTexture, -} - -#[derive(Debug, Error)] -pub struct RenderContextError { - kind: ContextErrorKind, - label: Option>, - #[source] - source: Option>, -} - -impl RenderContextError { - pub fn new( - kind: ContextErrorKind, - label: impl Into>>, - source: impl Into>>, - ) -> Self { - Self { - kind, - label: label.into(), - source: source.into(), - } - } - - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label = Some(label.into()); - self - } -} - -impl std::fmt::Display for RenderContextError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(label) = &self.label { - writeln!(f, "[{}] {}", label, self.kind)?; - } else { - writeln!(f, "{}", self.kind)?; - } - - if let Some(source) = &self.source { - fn fmt_chain( - err: &(dyn std::error::Error + 'static), - indent: usize, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - let indent_str = " ".repeat(indent); - writeln!(f, "{}{}", indent_str, err)?; - if let Some(next) = err.source() { - writeln!(f, "{}Caused by:", indent_str)?; - fmt_chain(next, indent + 1, f)?; - } - Ok(()) - } - writeln!(f, "Caused by:")?; - fmt_chain(source.as_ref(), 1, f)?; - } - Ok(()) - } -} - -trait IntoRenderContextError { - fn ctx_err( - self, - kind: ContextErrorKind, - label: impl Into>, - ) -> Result; -} - -impl IntoRenderContextError for Result -where - E: std::error::Error + Send + Sync + 'static, -{ - fn ctx_err( - self, - kind: ContextErrorKind, - label: impl Into>, - ) -> Result { - self.map_err(|e| { - RenderContextError::new( - kind, - Some(label.into()), - Some(Box::new(e) as Box), - ) - }) - } -} - -impl From for RenderContextError { - fn from(err: wgpu::CreateSurfaceError) -> Self { - RenderContextError::new( - ContextErrorKind::SurfaceCreation, - Some("Surface creation".into()), - Some(Box::new(err) as Box), - ) - } -} - -impl From for RenderContextError { - fn from(err: wgpu::RequestDeviceError) -> Self { - RenderContextError::new( - ContextErrorKind::DeviceRequest, - Some("Device setup".into()), - Some(Box::new(err) as Box), - ) - } -} +use crate::error::{ZenyxError, ZenyxErrorKind}; +use crate::error::Result; const SHADER_SRC: &str = include_str!("shader.wgsl"); @@ -238,7 +121,7 @@ impl Camera { queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform)); } } - +#[derive(Debug)] struct Model { vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, @@ -246,6 +129,7 @@ struct Model { bind_group: wgpu::BindGroup, index_count: u32, transform: Matrix4, + version: u32, } impl Model { @@ -289,6 +173,7 @@ impl Model { bind_group, index_count: indices.len() as u32, transform: Matrix4::identity(), + version: 1 } } @@ -299,8 +184,12 @@ impl Model { } fn set_transform(&mut self, transform: Matrix4) { - self.transform = transform; + if self.transform != transform { + self.transform = transform; + self.version += 1; + } } + } pub struct Renderer<'window> { @@ -321,6 +210,7 @@ pub struct Renderer<'window> { frame_count: u32, fps: f32, font_state: FontState, + model_versions: Vec, } struct FontState { @@ -331,15 +221,13 @@ struct FontState { } impl<'window> Renderer<'window> { - pub async fn new(window: Arc) -> Result { + pub async fn new(window: Arc) -> Result { let instance = wgpu::Instance::new(&InstanceDescriptor { backends: Backends::from_comma_list("dx12,metal,opengl,webgpu"), ..Default::default() }); - let surface = instance - .create_surface(Arc::clone(&window)) - .ctx_err(ContextErrorKind::SurfaceCreation, "Surface initialization")?; + let surface = instance.create_surface(Arc::clone(&window))?; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -349,17 +237,15 @@ impl<'window> Renderer<'window> { }) .await .ok_or_else(|| { - RenderContextError::new( - ContextErrorKind::AdapterRequest, - Some("Adapter selection".into()), - None, - ) + ZenyxError::builder(ZenyxErrorKind::AdapterRequest) + .with_message("No suitable adapter found") + .build() })?; let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor::default(), None) .await - .ctx_err(ContextErrorKind::DeviceRequest, "Device configuration")?; + .map_err(ZenyxError::from)?; let size = window.inner_size(); let width = size.width.max(1); @@ -473,16 +359,13 @@ impl<'window> Renderer<'window> { &device, surface_config.width, surface_config.height, - // surface_config.format, ); let font_bytes = include_bytes!("DejaVuSans.ttf"); let font = FontRef::try_from_slice(font_bytes).map_err(|e| { - RenderContextError::new( - ContextErrorKind::DeviceRequest, - Some("Font loading".into()), - None, - ) + ZenyxError::builder(ZenyxErrorKind::DeviceRequest) + .with_message("Font loading failed") + .build() })?; let brush = @@ -492,6 +375,8 @@ impl<'window> Renderer<'window> { let scale = base_scale * (surface_config.width as f32 / base_width as f32).clamp(0.5, 2.0); let color = wgpu::Color::WHITE; + + let section = OwnedSection::default() .add_text(OwnedText::new("FPS: 0.00").with_scale(scale).with_color([ color.r as f32, @@ -535,10 +420,11 @@ impl<'window> Renderer<'window> { scale, color, }, + model_versions: vec![] }) } - pub fn new_blocking(window: Arc) -> Result { + pub fn new_blocking(window: Arc) -> Result { block_on(Self::new(window)) } @@ -550,6 +436,7 @@ impl<'window> Renderer<'window> { &self.model_bind_group_layout, ); self.models.push(model); + self.model_versions.push(0); } pub fn resize(&mut self, new_size: (u32, u32)) { @@ -577,18 +464,30 @@ impl<'window> Renderer<'window> { for (i, model) in self.models.iter_mut().enumerate() { let angle = Rad(elapsed * 0.8 + i as f32 * 0.3); + if i % 2 == 0 { model.set_transform(Matrix4::from_angle_y(angle)); - // model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle)); - model.update(&self.queue); + } else { + model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle)); + } + } + for (i, model) in self.models.iter().enumerate() { + if model.version > self.model_versions[i] { + model.update(&self.queue); + #[cfg(debug_assertions)] + trace!("Updating model: {:#?}",model); + self.model_versions[i] = model.version; + } } let surface_texture = self .surface .get_current_texture() - .ctx_err( - ContextErrorKind::SurfaceTexture, - "Surface texture acquisition", - ) + .map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::SurfaceTexture) + .with_message("Failed to acquire surface texture") + .with_source(e) + .build() + }) .unwrap(); let view = surface_texture @@ -679,7 +578,7 @@ impl<'window> Renderer<'window> { self.frame_count += 1; let elapsed_secs = self.last_frame_instant.elapsed().as_secs_f32(); - if elapsed_secs >= 1.0 { + if (elapsed_secs >= 1.0) { let fps = self.frame_count as f32 / elapsed_secs; // trace!("Renderer FPS: {:.2}", fps); self.fps = fps; diff --git a/engine/src/core/render/mod.rs b/engine/src/core/render/mod.rs index b6e54e1..900486c 100644 --- a/engine/src/core/render/mod.rs +++ b/engine/src/core/render/mod.rs @@ -2,6 +2,8 @@ use std::ops::Deref; use std::sync::Arc; use ctx::{Renderer, Vertex}; +use winit::dpi::LogicalSize; +use winit::dpi::Size; use std::env; use std::fs; use std::path::PathBuf; @@ -84,7 +86,7 @@ f 6/11/6 5/10/6 1/1/6 2/13/6 impl App<'_> { fn create_main_window(&mut self, event_loop: &ActiveEventLoop) { - let win_attr = Window::default_attributes().with_title("Zenyx"); + let win_attr = Window::default_attributes().with_title("Zenyx").with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))); match event_loop.create_window(win_attr) { Ok(window) => { let window = Arc::new(window); @@ -119,10 +121,10 @@ impl App<'_> { ); info!("Main window created: {:?}", window_id); } - Err(e) => error!("Failed to create WGPU context: {:?}", e), + Err(e) => error!("Failed to create WGPU context: {:#}", e), } } - Err(e) => error!("Failed to create main window: {:?}", e), + Err(e) => error!("Failed to create main window: {:#}", e), } } @@ -140,9 +142,9 @@ impl App<'_> { window_id: WindowId, key_event: KeyEvent, ) { - if !key_event.state.is_pressed() { + if !key_event.state.is_pressed() || key_event.repeat { return; - } + } match key_event.physical_key { winit::keyboard::PhysicalKey::Code(code) => match code { winit::keyboard::KeyCode::Space => { @@ -186,10 +188,19 @@ impl App<'_> { let title = format!("Zenyx - New Window {}", self.windows.len()); // TODO: Verify that this is safe instead of matching on it let win_attr = unsafe { - let base = Window::default_attributes().with_title(title); + let base = Window::default_attributes().with_title(title).with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))); match main_ctx.window_handle() { - Ok(handle) => base.with_parent_window(Some(handle.as_raw())), - Err(_) => base, + Ok(handle) => { + if !cfg!(target_os = "windows") { + base.with_parent_window(Some(handle.as_raw())) + } else { + base + } + }, + Err(e) => { + error!("{e}"); + base + }, } }; match event_loop.create_window(win_attr) { @@ -350,6 +361,6 @@ pub fn init_renderer(event_loop: EventLoop<()>) { event_loop.set_control_flow(ControlFlow::Wait); let mut app = App::default(); if let Err(e) = event_loop.run_app(&mut app) { - error!("Failed to run application: {:?}", e); + error!("Failed to run application: {}", e); } } diff --git a/engine/src/core/repl/commands.rs b/engine/src/core/repl/commands.rs index c52b117..d34227e 100644 --- a/engine/src/core/repl/commands.rs +++ b/engine/src/core/repl/commands.rs @@ -1,18 +1,17 @@ use std::{fs, path::PathBuf, str::FromStr}; -use anyhow::anyhow; - use parking_lot::RwLock; use regex::Regex; use super::{handler::Command, input::tokenize}; use crate::core::repl::handler::COMMAND_MANAGER; +use crate::error::{ZenyxError,ZenyxErrorKind}; #[derive(Default)] pub struct HelpCommand; impl Command for HelpCommand { - fn execute(&self, _args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, _args: Option>) -> Result<(), ZenyxError> { let manager = COMMAND_MANAGER.read(); println!("Available commands:\n"); @@ -55,11 +54,12 @@ impl Command for HelpCommand { String::from("Help") } } + #[derive(Default)] pub struct ClearCommand; impl Command for ClearCommand { - fn execute(&self, _args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, _args: Option>) -> Result<(), ZenyxError> { println!("Clearing screen..., running command"); let _result = if cfg!(target_os = "windows") { std::process::Command::new("cmd") @@ -96,12 +96,16 @@ impl Command for ClearCommand { pub struct ExitCommand; impl Command for ExitCommand { - fn execute(&self, args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, args: Option>) -> Result<(), ZenyxError> { match args { Some(args) => { - let exit_code = args[0].parse()?; + let exit_code = args[0].parse().map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Failed to parse exit code") + .with_source(e) + .build() + })?; std::process::exit(exit_code); - // Ok(()) } None => { std::process::exit(0); @@ -133,18 +137,31 @@ impl Command for ExitCommand { String::from("None") } } + #[derive(Default)] pub struct ExecFile; impl Command for ExecFile { - fn execute(&self, args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, args: Option>) -> Result<(), ZenyxError> { match args { Some(args) => { - let file_path = PathBuf::from_str(&args[0])?; + let file_path = PathBuf::from_str(&args[0]).map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Invalid file path") + .with_source(e) + .build() + })?; if file_path.extension().is_some() && file_path.extension().unwrap() != "zensh" { - return Err(anyhow!("Selected file was not a zensh file")); + return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Selected file was not a zensh file") + .build()); } else { - let zscript = fs::read_to_string(file_path)?; + let zscript = fs::read_to_string(file_path).map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::Io) + .with_message("Failed to read file") + .with_source(e) + .build() + })?; if let Ok(command) = eval(zscript) { println!("{:#?}", command); for (cmd_name, cmd_args) in command { @@ -163,7 +180,9 @@ impl Command for ExecFile { } Ok(()) } - None => Err(anyhow!("Not enough argumentss")), + None => Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Not enough arguments") + .build()), } } @@ -194,7 +213,7 @@ pub struct CounterCommand { } impl Command for CounterCommand { - fn execute(&self, _args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, _args: Option>) -> Result<(), ZenyxError> { // Increment the counter let mut count = self.counter.write(); *count += 1; @@ -226,10 +245,11 @@ impl Command for CounterCommand { String::from("count") } } + #[derive(Default)] pub struct PanicCommmand; impl Command for PanicCommmand { - fn execute(&self, args: Option>) -> Result<(), anyhow::Error> { + fn execute(&self, args: Option>) -> Result<(), ZenyxError> { if args.is_some() { let panic_msg = &args.unwrap()[0]; panic!("{}", panic_msg) @@ -260,12 +280,19 @@ impl Command for PanicCommmand { } } -fn eval(input: String) -> Result>)>, anyhow::Error> { +fn eval(input: String) -> Result>)>, ZenyxError> { if input.trim().is_empty() { - return Err(anyhow!("Input was empty")); + return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Input was empty") + .build()); } - let pattern = Regex::new(r"[;|\n]").unwrap(); + let pattern = Regex::new(r"[;|\n]").map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Failed to compile regex") + .with_source(e) + .build() + })?; let commands: Vec<&str> = pattern.split(&input).collect(); let mut evaluted = vec![]; diff --git a/engine/src/core/repl/handler.rs b/engine/src/core/repl/handler.rs index 3a02f5c..e9b70b0 100644 --- a/engine/src/core/repl/handler.rs +++ b/engine/src/core/repl/handler.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; use colored::Colorize; -use lazy_static::lazy_static; use parking_lot::RwLock; -lazy_static! { - pub static ref COMMAND_MANAGER: RwLock = RwLock::new(CommandManager::init()); -} +use std::sync::LazyLock; +use crate::error::{ZenyxError, ZenyxErrorKind}; + +pub static COMMAND_MANAGER: LazyLock> = LazyLock::new(|| { RwLock::new(CommandManager::init()) }); + #[macro_export] macro_rules! commands { [$($command:ty),*] => [ @@ -110,24 +111,28 @@ impl CommandManager { &self, command: &str, args: Option>, - ) -> Result<(), anyhow::Error> { + ) -> Result<(), ZenyxError> { if let Some(command) = self.commands.get(command) { command.execute(args)?; Ok(()) } else { let corrected_cmd = check_similarity(command); - if corrected_cmd.is_some() { - println!("Command: {} was not found. Did you mean {}?",command.red().bold(),corrected_cmd - .expect("A command was editied during execution, something has gone seriously wrong").green().bold().italic()); + if let Some(corrected_cmd) = corrected_cmd { + println!( + "Command: {} was not found. Did you mean {}?", + command.red().bold(), + corrected_cmd.green().bold().italic() + ); } - Err(anyhow::anyhow!("Command '{}' not found.", command)) + Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution) + .with_message(format!("Command '{}' not found.", command)) + .build()) } } - pub fn execute(&self, command: &str, args: Option>) -> Result<(), anyhow::Error> { + pub fn execute(&self, command: &str, args: Option>) -> Result<(), ZenyxError> { match self.aliases.get(command) { Some(command) => self.execute(command, args), - // check to see if we are using an alias or the command just doesnt exist None => { self.execute_command(command, args)?; Ok(()) @@ -149,7 +154,7 @@ impl CommandManager { } pub trait Command: Send + Sync { - fn execute(&self, args: Option>) -> Result<(), anyhow::Error>; + fn execute(&self, args: Option>) -> Result<(), ZenyxError>; fn undo(&self); fn redo(&self); fn get_description(&self) -> String; diff --git a/engine/src/core/repl/input.rs b/engine/src/core/repl/input.rs index 784977b..770955d 100644 --- a/engine/src/core/repl/input.rs +++ b/engine/src/core/repl/input.rs @@ -15,6 +15,7 @@ use rustyline::{ use tracing::{debug, error, info, warn}; use super::handler::COMMAND_MANAGER; +use crate::error::{Result, ZenyxError, ZenyxErrorKind}; struct CommandCompleter; impl CommandCompleter { @@ -129,29 +130,38 @@ pub fn tokenize(command: &str) -> Vec { tokens } -pub fn parse_command(input: &str) -> anyhow::Result> { - let pattern = Regex::new(r"[;|\n]").unwrap(); +pub fn parse_command(input: &str) -> Result> { + let pattern = Regex::new(r"[;|\n]").map_err(|_| { + ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Failed to compile regex pattern") + .build() + })?; let commands: Vec = pattern.split(input).map(String::from).collect(); Ok(commands) } -pub fn evaluate_command(input: &str) -> anyhow::Result<()> { + +pub fn evaluate_command(input: &str) -> Result<()> { if input.trim().is_empty() { return Ok(()); } - let pattern = Regex::new(r"[;|\n]").unwrap(); + let pattern = Regex::new(r"[;|\n]").map_err(|_| { + ZenyxError::builder(ZenyxErrorKind::CommandParsing) + .with_message("Failed to compile regex pattern") + .build() + })?; let commands: Vec<&str> = pattern.split(input).collect(); for command in commands { let command = command.trim(); if command.is_empty() { - println!("Empty command, skipping."); + error!("Empty command, skipping."); continue; } let tokens = tokenize(command); if tokens.is_empty() { - println!("Empty command, skipping."); + error!("Empty command, skipping."); continue; } let cmd_name = &tokens[0]; @@ -160,15 +170,20 @@ pub fn evaluate_command(input: &str) -> anyhow::Result<()> { } else { None }; - match COMMAND_MANAGER.read().execute(cmd_name, args) { - Ok(_) => continue, - Err(e) => return Err(e), - } + COMMAND_MANAGER + .read() + .execute(cmd_name, args) + .map_err(|e| { + ZenyxError::builder(ZenyxErrorKind::CommandExecution) + .with_message(format!("Failed to execute command: {cmd_name}")) + .with_context(format!("{e}")) + .build() + })?; } Ok(()) } -pub async fn handle_repl() -> anyhow::Result<()> { +pub async fn handle_repl() -> Result<()> { let mut rl = Editor::::new()?; rl.set_helper(Some(MyHelper { hinter: HistoryHinter::new(), @@ -196,7 +211,7 @@ pub async fn handle_repl() -> anyhow::Result<()> { rl.add_history_entry(line.as_str())?; match evaluate_command(line.as_str()) { Ok(_) => continue, - Err(e) => println!("{e}"), + Err(e) => error!("{e}"), } } Err(ReadlineError::Interrupted) => { diff --git a/engine/src/core/repl/mod.rs b/engine/src/core/repl/mod.rs index c850436..21e1524 100644 --- a/engine/src/core/repl/mod.rs +++ b/engine/src/core/repl/mod.rs @@ -6,6 +6,9 @@ pub mod commands; pub mod handler; pub mod input; + + + pub fn setup() { commands!( HelpCommand, diff --git a/engine/src/core/workspace/mod.rs b/engine/src/core/workspace/mod.rs deleted file mode 100644 index d98bf5a..0000000 --- a/engine/src/core/workspace/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{Context, Result, anyhow}; - -pub fn get_working_dir() -> Result { - let mut dir = dirs_next::data_dir() - .ok_or(anyhow!("Expected working directory, found: None")) - .context("Could not fetch working dir")?; - dir.push("Zenyx"); - Ok(dir) -} - -pub fn get_data_dir() -> Result { - let mut dir = get_working_dir().context("Failed to obtain working dir")?; - dir.push("data"); - Ok(dir) -} diff --git a/engine/src/error.rs b/engine/src/error.rs new file mode 100644 index 0000000..b39bb3e --- /dev/null +++ b/engine/src/error.rs @@ -0,0 +1,275 @@ +use colored::Colorize; +use thiserror::Error; +use std::fmt::Write; + + +#[derive(Debug, Error,PartialEq)] +pub enum ZenyxErrorKind { + #[error("Surface creation failed")] + SurfaceCreation, + #[error("Surface configuration failed")] + SurfaceConfiguration, + #[error("Adapter request failed")] + AdapterRequest, + #[error("Device request failed")] + DeviceRequest, + #[error("Surface texture acquisition failed")] + SurfaceTexture, + #[error("Command parsing failed")] + CommandParsing, + #[error("Command execution failed")] + CommandExecution, + #[error("Font loading failed")] + FontLoading, + #[error("Model loading failed")] + ModelLoading, + #[error("IO operation failed")] + Io, + #[error("REPL operation failed")] + Repl, + #[error("Unknown error occurred")] + Unknown, +} + +#[derive(Debug)] +pub struct ZenyxError { + kind: ZenyxErrorKind, + message: Option, + context: Option, + source: Option>, +} + +impl ZenyxError { + pub fn builder(kind: ZenyxErrorKind) -> Self { + Self { + kind, + message: None, + context: None, + source: None, + } + } + pub fn kind(&self) -> &ZenyxErrorKind { + &self.kind + } + + pub fn with_message(mut self, message: impl Into) -> Self { + self.message = Some(message.into()); + self + } + + pub fn with_context(mut self, context: impl Into) -> Self { + self.context = Some(context.into()); + self + } + + pub fn with_source(mut self, source: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + self.source = Some(Box::new(source)); + self + } + + pub fn build(self) -> Self { + self + } + + pub fn pretty_print(&self) { + let mut output = String::new(); + let padding_spaces = 2; + writeln!( + output, + "{} {}", + "\nERROR:".red().bold(), + format!("{}", self.kind).bright_white().bold() + ) + .unwrap(); + + if let Some(msg) = &self.message { + let line_padding = " ".repeat(padding_spaces); + writeln!( + output, + "{}>> {}\x1b[0m", + line_padding, + msg.bright_white() + ) + .unwrap(); + } + if let Some(ctx) = &self.context { + let line_padding = " ".repeat(padding_spaces); + writeln!(output, "{}│\x1b[0m", line_padding.bright_white().bold()).unwrap(); + writeln!( + output, + "{}╰─ Note: {}\x1b[0m", + line_padding, + ctx + ) + .unwrap(); + } + if let Some(source) = &self.source { + let line_padding = " ".repeat(padding_spaces); + writeln!( + output, + "{}╰─ Caused by: {}\x1b[0m", + line_padding, + source + ) + .unwrap(); + let mut current = source.source(); + let mut depth = 1; + while let Some(err) = current { + let indent = " ".repeat(padding_spaces * depth); + writeln!( + output, + "{}↳ {}\x1b[0m", + indent, + err + ) + .unwrap(); + depth += 1; + current = err.source(); + } + } + print!("{}", output); + } +} + +impl std::fmt::Display for ZenyxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Error: {}{}{}{}", + self.kind, + self.message.as_ref().map_or("".to_string(), |msg| format!(" - {}", msg)), + self.context.as_ref().map_or("".to_string(), |ctx| format!(" [{}]", ctx)), + self.source.as_ref().map_or("".to_string(), |src| format!(" - caused by: {}", src)) + ) + } +} + +impl std::error::Error for ZenyxError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source.as_ref().map(|s| s.as_ref() as &(dyn std::error::Error + 'static)) + } +} + + +impl From for ZenyxError { + fn from(err: std::io::Error) -> Self { + Self::builder(ZenyxErrorKind::Io) + .with_message(err.to_string()) + .with_source(err) + .build() + } +} + +impl From for ZenyxError { + fn from(err: wgpu::CreateSurfaceError) -> Self { + Self::builder(ZenyxErrorKind::SurfaceCreation) + .with_message("Failed to create surface") + .with_source(err) + .build() + } +} + +impl From for ZenyxError { + fn from(err: wgpu::RequestDeviceError) -> Self { + Self::builder(ZenyxErrorKind::DeviceRequest) + .with_message("Failed to request device") + .with_source(err) + .build() + } +} + +impl From for ZenyxError { + fn from(err: rustyline::error::ReadlineError) -> Self { + Self::builder(ZenyxErrorKind::Repl) + .with_message("Readline error occurred") + .with_source(err) + .build() + } +} + +pub type Result = std::result::Result; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zenyx_error_builder() { + let error = ZenyxError::builder(ZenyxErrorKind::Io) + .with_message("An IO error occurred") + .with_context("Reading file") + .build(); + + assert_eq!(error.kind, ZenyxErrorKind::Io); + assert_eq!(error.message.as_deref(), Some("An IO error occurred")); + assert_eq!(error.context.as_deref(), Some("Reading file")); + assert!(error.source.is_none()); + } + + #[test] + fn test_zenyx_error_with_source() { + let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"); + let error = ZenyxError::builder(ZenyxErrorKind::Io) + .with_message("An IO error occurred") + .with_source(io_error) + .build(); + + assert_eq!(error.kind, ZenyxErrorKind::Io); + assert_eq!(error.message.as_deref(), Some("An IO error occurred")); + assert!(error.source.is_some()); + } + + #[test] + fn test_from_io_error() { + let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied"); + let error: ZenyxError = io_error.into(); + + assert_eq!(error.kind, ZenyxErrorKind::Io); + assert_eq!(error.message.as_deref(), Some("Access denied")); + assert!(error.source.is_some()); + } + + + #[test] + fn test_from_rustyline_error() { + let readline_error = rustyline::error::ReadlineError::Interrupted; + let error: ZenyxError = readline_error.into(); + + assert_eq!(error.kind, ZenyxErrorKind::Repl); + assert_eq!(error.message.as_deref(), Some("Readline error occurred")); + assert!(error.source.is_some()); + } + + #[test] + fn test_print() { + let readline_error = rustyline::error::ReadlineError::Interrupted; + let error: ZenyxError = readline_error.into(); + + println!("{error}"); + } + #[test] + fn test_pretty_print() { + let readline_error = rustyline::error::ReadlineError::Interrupted; + let error: ZenyxError = readline_error.into(); + + error.pretty_print(); + } + + + #[test] + fn test_error_source_chain() { + let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"); + let error = ZenyxError::builder(ZenyxErrorKind::Io) + .with_message("An IO error occurred") + .with_source(io_error) + .build(); + + let mut source = std::error::Error::source(&error); + assert!(source.is_some()); + assert_eq!(source.unwrap().to_string(), "File not found"); + + source = source.unwrap().source(); + assert!(source.is_none()); + } +} diff --git a/engine/src/main.rs b/engine/src/main.rs index aaf5154..741b460 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -1,5 +1,5 @@ -use core::{panic::set_panic_hook, repl::setup, splash, workspace}; - +use core::{panic::set_panic_hook, repl::setup, splash}; +use thiserror::Error; use colored::Colorize; use tokio::runtime; #[allow(unused_imports)] @@ -7,6 +7,8 @@ use tracing::{debug, error, info, warn}; use tracing::{level_filters::LevelFilter, subscriber::set_global_default}; use winit::event_loop::EventLoop; pub mod core; +pub mod error; +pub mod metadata; fn init_logger() { let subscriber = tracing_subscriber::fmt() @@ -23,15 +25,26 @@ fn init_logger() { } #[tokio::main(flavor = "current_thread")] -async fn main() -> anyhow::Result<()> { +async fn main() { init_logger(); - if !cfg!(debug_assertions) { - info!("{}", "Debug mode disabled".bright_blue()); - set_panic_hook(); - } + let sysinfo = crate::metadata::SystemMetadata::current(); + + set_panic_hook(); setup(); + + + let event_loop = EventLoop::new().unwrap(); + splash::print_splash(); + if !cfg!(debug_assertions) { + info!("{}", "Debug mode disabled".bright_blue()); + set_panic_hook(); + } else { + println!("{}",sysinfo.verbose_summary()); + } + + info!("Type 'help' for a list of commands."); let repl_thread = std::thread::spawn(|| { let rt = runtime::Builder::new_current_thread() .enable_all() @@ -39,14 +52,9 @@ async fn main() -> anyhow::Result<()> { .unwrap(); rt.block_on(core::repl::input::handle_repl()) }); - - let event_loop = EventLoop::new().unwrap(); - splash::print_splash(); - info!("Type 'help' for a list of commands."); - core::render::init_renderer(event_loop); if let Err(_) = repl_thread.join() { - eprintln!("REPL thread panicked"); + error!("REPL thread panicked"); } - Ok(()) + } diff --git a/engine/src/metadata.rs b/engine/src/metadata.rs new file mode 100644 index 0000000..dadbd01 --- /dev/null +++ b/engine/src/metadata.rs @@ -0,0 +1,559 @@ +use std::fmt; +use std::str::FromStr; +use sysinfo::{CpuRefreshKind, RefreshKind, System}; +use raw_cpuid::CpuId; +use wgpu::DeviceType; +use std::collections::HashSet; + +mod build_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Memory { + bytes: u64, +} + +impl Memory { + pub const fn from_bytes(bytes: u64) -> Self { + Self { bytes } + } + + pub const fn from_kb(kb: u64) -> Self { + Self { bytes: kb * 1024 } + } + + pub const fn from_mb(mb: u64) -> Self { + Self { bytes: mb * 1024 * 1024 } + } + + pub const fn from_gb(gb: u64) -> Self { + Self { bytes: gb * 1024 * 1024 * 1024 } + } + + pub const fn as_bytes(&self) -> u64 { + self.bytes + } + + pub const fn as_kb(&self) -> u64 { + self.bytes / 1024 + } + + pub const fn as_mb(&self) -> u64 { + self.bytes / (1024 * 1024) + } + + pub const fn as_gb(&self) -> u64 { + self.bytes / (1024 * 1024 * 1024) + } + + pub fn format_human(&self) -> String { + const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; + let mut size = self.bytes as f64; + let mut unit_index = 0; + + while size >= 1024.0 && unit_index < UNITS.len() - 1 { + size /= 1024.0; + unit_index += 1; + } + + format!("{:.2} {}", size, UNITS[unit_index]) + } + + pub fn verbose_info(&self) -> String { + format!( + "{} ({} bytes, {} KB, {} MB, {} GB)", + self.format_human(), + self.as_bytes(), + self.as_kb(), + self.as_mb(), + self.as_gb() + ) + } +} + +impl fmt::Display for Memory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.format_human()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CPUBrand { + Intel, + AMD, + SnapDragon, + Apple, + Other(String), +} + +impl From<&str> for CPUBrand { + fn from(s: &str) -> Self { + let sl = s.to_lowercase(); + if sl.contains("intel") { + Self::Intel + } else if sl.contains("amd") { + Self::AMD + } else if sl.contains("snapdragon") { + Self::SnapDragon + } else if sl.contains("apple") { + Self::Apple + } else { + Self::Other(s.to_string()) + } + } +} + +impl fmt::Display for CPUBrand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Intel => write!(f, "Intel"), + Self::AMD => write!(f, "AMD"), + Self::SnapDragon => write!(f, "SnapDragon"), + Self::Apple => write!(f, "Apple"), + Self::Other(s) => write!(f, "{}", s), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CPUArch { + X86, + X86_64, + AArch64, + Other(String), +} + +impl FromStr for CPUArch { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "x86" => Self::X86, + "x86_64" => Self::X86_64, + "arm" => Self::AArch64, + "aarch64" => Self::AArch64, + _ => Self::Other(s.to_string()), + }) + } +} + +impl fmt::Display for CPUArch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::X86 => write!(f, "x86"), + Self::X86_64 => write!(f, "x86_64"), + Self::AArch64 => write!(f, "AArch64"), + Self::Other(s) => write!(f, "{}", s), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ClockSpeed(pub u32); + +impl ClockSpeed { + pub fn as_mhz(&self) -> u32 { + self.0 + } + + pub fn as_ghz(&self) -> f32 { + self.0 as f32 / 1000.0 + } +} + +impl fmt::Display for ClockSpeed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:.2} GHz ({} MHz)", self.as_ghz(), self.as_mhz()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CPU { + pub brand: CPUBrand, + pub arch: CPUArch, + pub name: String, + pub vendor_id: String, + pub physical_cores: Option, + pub logical_cores: Option, + pub max_clock_speed: ClockSpeed, + pub current_clock_speed: ClockSpeed, + pub l1_cache: Option, + pub l2_cache: Option, + pub l3_cache: Option, +} + +impl CPU { + pub fn current() -> Self { + let mut sys = System::new_with_specifics(RefreshKind::default().with_cpu(CpuRefreshKind::everything())); + sys.refresh_cpu_all(); + + let cpu_opt = sys.cpus().first(); + + let brand = cpu_opt.map(|cpu| cpu.brand().into()) + .unwrap_or(CPUBrand::Other("unknown".into())); + let name = cpu_opt.map(|cpu| cpu.name().to_string()) + .unwrap_or_else(|| "unknown".into()); + let vendor_id = cpu_opt.map(|cpu| cpu.vendor_id().to_string()) + .unwrap_or_else(|| "unknown".into()); + let max_clock_speed = cpu_opt.map(|cpu| ClockSpeed(cpu.frequency() as u32)) + .unwrap_or(ClockSpeed(0)); + let current_clock_speed = max_clock_speed; + + let logical_cores = cpu_opt.map(|_| sys.cpus().len() as u8); + let physical_cores = sysinfo::System::physical_core_count().map(|pc| pc as u8); + + let cpuid = CpuId::new(); + let mut l1_cache = None; + let mut l2_cache = None; + let mut l3_cache = None; + if let Some(iter) = cpuid.get_cache_parameters() { + for cache in iter { + match cache.level() { + 1 => { + let size = cache.physical_line_partitions() + * cache.coherency_line_size() + * cache.associativity(); + if size > 0 { l1_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } + }, + 2 => { + let size = cache.physical_line_partitions() + * cache.coherency_line_size() + * cache.associativity(); + if size > 0 { l2_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } + }, + 3 => { + let size = (cache.physical_line_partitions() as u64) + * (cache.coherency_line_size() as u64) + * (cache.associativity() as u64); + if size > 0 { l3_cache = Some(Memory::from_bytes(size)); } + }, + _ => {} + } + } + } + + Self { + brand, + arch: std::env::consts::ARCH.parse().unwrap_or(CPUArch::Other("unknown".into())), + name, + vendor_id, + physical_cores, + logical_cores, + max_clock_speed, + current_clock_speed, + l1_cache, + l2_cache, + l3_cache, + } + } + + pub fn is_intel(&self) -> bool { + matches!(self.brand, CPUBrand::Intel) + } + + pub fn is_amd(&self) -> bool { + matches!(self.brand, CPUBrand::AMD) + } + + pub fn is_arm(&self) -> bool { + matches!(self.brand,CPUBrand::Intel) + } + + pub fn is_high_clock(&self) -> bool { + self.max_clock_speed.0 > 3000 + } + + pub fn verbose_info(&self) -> String { + format!( + "CPU Information:\n\ + - Brand: {}\n\ + - Architecture: {}\n\ + - Name: {}\n\ + - Vendor ID: {}\n\ + - Cores: {} physical, {} logical\n\ + - Clock Speed: {} (max), {} (current)\n\ + - Cache: L1 {}, L2 {}, L3 {}", + self.brand, + self.arch, + self.name, + self.vendor_id, + self.physical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), + self.logical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), + self.max_clock_speed, + self.current_clock_speed, + self.l1_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), + self.l2_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), + self.l3_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum GPUBrand { + Nvidia, + AMD, + Intel, + Other(String), +} + +impl From<&str> for GPUBrand { + fn from(s: &str) -> Self { + let sl = s.to_lowercase(); + if sl.contains("nvidia") || sl.contains("geforce") { + Self::Nvidia + } else if sl.contains("amd") || sl.contains("radeon") { + Self::AMD + } else if sl.contains("intel") { + Self::Intel + } else { + Self::Other(s.to_string()) + } + } +} + +impl fmt::Display for GPUBrand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Nvidia => write!(f, "NVIDIA"), + Self::AMD => write!(f, "AMD"), + Self::Intel => write!(f, "Intel"), + Self::Other(s) => write!(f, "{}", s), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GPU { + pub brand: GPUBrand, + pub name: String, + pub device_type: DeviceType, + pub vram: Memory, + pub driver_version: Option, +} + + +impl GPU { + pub fn current() -> Vec { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }); + + instance + .enumerate_adapters(wgpu::Backends::all()) + .iter() + .map(|adapter| { + let info = adapter.get_info(); + GPU { + brand: info.name.as_str().into(), + name: info.name.to_string(), + device_type: info.device_type, + vram: Memory::from_bytes(0), + driver_version: Some(info.driver.to_string()), + } + }) + .collect() + } + + pub fn is_integrated(&self) -> bool { + matches!(self.brand, GPUBrand::Intel) + } + + pub fn is_dedicated(&self) -> bool { + !self.is_integrated() + } + pub fn is_mobile(&self) -> bool { + let lower_name = self.name.to_lowercase(); + lower_name.contains("adreno") + || lower_name.contains("mali") + || lower_name.contains("apple") + || lower_name.contains("mobile") + || lower_name.contains("snapdragon") + } + + pub fn verbose_info(&self) -> String { + format!( + "GPU Information:\n\ + - Brand: {}\n\ + - Name: {}\n\ + - VRAM: {}\n\ + - Driver: {}", + self.brand, + self.name, + self.vram.format_human(), + self.driver_version.as_deref().unwrap_or("Unknown") + ) + } + + fn unique_id(&self) -> String { + format!("{}-{:?}", self.name, self.device_type) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SystemMemory { + pub total: Memory, + pub used: Memory, + pub free: Memory, + pub available: Memory, + pub swap_total: Memory, + pub swap_used: Memory, + pub swap_free: Memory, +} + +impl SystemMemory { + pub fn current() -> Self { + let mut system = System::new(); + system.refresh_memory(); + + Self { + total: Memory::from_bytes(system.total_memory()), + used: Memory::from_bytes(system.used_memory()), + free: Memory::from_bytes(system.free_memory()), + available: Memory::from_bytes(system.available_memory()), + swap_total: Memory::from_bytes(system.total_swap()), + swap_used: Memory::from_bytes(system.used_swap()), + swap_free: Memory::from_bytes(system.free_swap()), + } + } + + pub fn verbose_info(&self) -> String { + format!( + "Memory Information:\n\ + - RAM: {} total, {} used, {} free, {} available\n\ + - Swap: {} total, {} used, {} free", + self.total.format_human(), + self.used.format_human(), + self.free.format_human(), + self.available.format_human(), + self.swap_total.format_human(), + self.swap_used.format_human(), + self.swap_free.format_human() + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CompileInfo { + pub timestamp: String, + pub pkg_version: String, + pub pkg_name: String, + pub target_arch: String, + pub target_os: String, + pub target_env: String, +} + +impl CompileInfo { + pub fn current() -> Self { + Self { + timestamp: build_info::BUILT_TIME_UTC.to_string(), + pkg_version: build_info::PKG_VERSION.to_string(), + pkg_name: build_info::PKG_NAME.to_string(), + target_arch: build_info::CFG_TARGET_ARCH.to_string(), + target_os: build_info::CFG_OS.to_string(), + target_env: build_info::CFG_ENV.to_string(), + } + } + + pub fn verbose_info(&self) -> String { + format!( + "Build Information:\n\ + - Timestamp: {}\n\ + - Package: {} v{}\n\ + - Target: {} {} {}\n\ + ", + self.timestamp, + self.pkg_name, + self.pkg_version, + self.target_arch, + self.target_os, + self.target_env + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SystemMetadata { + pub cpu: CPU, + pub memory: SystemMemory, + pub gpus: Vec, + pub compile_info: CompileInfo, +} + +impl SystemMetadata { + pub fn current() -> Self { + Self { + cpu: CPU::current(), + memory: SystemMemory::current(), + gpus: GPU::current(), + compile_info: CompileInfo::current(), + } + } + pub fn main_gpu(&self) -> Option<&GPU> { + self.gpus + .iter() + .find(|g| g.is_dedicated()) + .or_else(|| self.gpus.iter().find(|g| g.is_integrated())) + .or_else(|| self.gpus.first()) + } + + + pub fn verbose_summary(&self) -> String { + let main_gpu = self.main_gpu(); + let main_gpu_info = main_gpu + .map(|gpu| format!("Main GPU:\n{}", gpu.verbose_info())) + .unwrap_or_else(|| "No main GPU detected".to_string()); + + let mut seen_gpu_ids = HashSet::new(); + let mut other_gpus_info = Vec::new(); + + if let Some(gpu) = main_gpu { + seen_gpu_ids.insert(gpu.unique_id()); + } + + for gpu in &self.gpus { + let gpu_id = gpu.unique_id(); + if !seen_gpu_ids.contains(&gpu_id) { + other_gpus_info.push(format!("[{:?}] {}", gpu.device_type, gpu.verbose_info())); + seen_gpu_ids.insert(gpu_id); + } + } + + let other_gpu_list = if other_gpus_info.is_empty() { + String::new() + } else { + format!("Other GPUs:\n{}", other_gpus_info.join("\n\n")) + }; + + format!( + "System Metadata:\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}", + self.cpu.verbose_info(), + main_gpu_info, + other_gpu_list, + self.memory.verbose_info(), + self.compile_info.verbose_info() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_conversions() { + let mem = Memory::from_gb(8); + assert_eq!(mem.as_bytes(), 8 * 1024 * 1024 * 1024); + assert_eq!(mem.as_mb(), 8 * 1024); + assert_eq!(mem.as_kb(), 8 * 1024 * 1024); + } + + #[test] + fn test_system_metadata() { + let metadata = SystemMetadata::current(); + assert!(!metadata.cpu.name.is_empty()); + assert!(metadata.memory.total.as_bytes() > 0); + assert!(!metadata.compile_info.pkg_version.is_empty()); + } +} \ No newline at end of file diff --git a/main.rs b/main.rs new file mode 100644 index 0000000..e69de29