improve error handling and add metadata

This commit is contained in:
Chance 2025-04-03 01:00:24 -04:00 committed by BitSyndicate
parent 10932a1a97
commit 710c8e52bd
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
17 changed files with 1162 additions and 787 deletions

509
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"] }

3
engine/build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
built::write_built_file().expect("Failed to write build information");
}

View file

@ -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<u8>;
fn deserialize(data: &[u8; 6]) -> Self;
}
pub trait Entity: Sized {
fn add_component<C: Component>(&mut self, component: C);
fn remove_component<C: Component>(&mut self);
fn get_component<C: Component>(&self) -> Option<&C>;
fn serialize(&self) -> Vec<u8>;
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<HashMap<TypeId, u64>> = Mutex::new(HashMap::new());
static ref NEXT_COMPONENT_BIT: Mutex<u64> = Mutex::new(1);
}
// To allow dynamic dispatch on components (even though Component itself is not objectsafe)
// we wrap them in an objectsafe trait.
pub trait ComponentObject: Any {
fn update_obj(&mut self, delta_time: f32);
fn serialize_obj(&self) -> Vec<u8>;
fn as_any(&self) -> &dyn Any;
}
impl<T: Component + 'static> ComponentObject for T {
fn update_obj(&mut self, delta_time: f32) {
T::update(self, delta_time)
}
fn serialize_obj(&self) -> Vec<u8> {
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<u64, Box<dyn ComponentObject>>,
}
impl EntityImpl {
pub fn new(id: usize) -> Self {
EntityImpl {
id,
bitmask: 0,
components: HashMap::new(),
}
}
}
impl Entity for EntityImpl {
fn add_component<C: Component>(&mut self, component: C) {
let type_id = TypeId::of::<C>();
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<C: Component>(&mut self) {
let type_id = TypeId::of::<C>();
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
self.bitmask &= !bit;
}
}
fn get_component<C: Component>(&self) -> Option<&C> {
let type_id = TypeId::of::<C>();
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
self.components.get(&bit)
.and_then(|boxed| boxed.as_any().downcast_ref::<C>())
} else {
None
}
}
fn serialize(&self) -> Vec<u8> {
// 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<usize, EntityImpl>,
}
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<u8> {
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;
}
}

View file

@ -2,6 +2,5 @@ pub mod ecs;
pub mod panic;
pub mod repl;
pub mod splash;
pub mod workspace;
pub mod render;

View file

@ -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<dyn Error>> {
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)?;
}

View file

@ -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<Cow<'static, str>>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl RenderContextError {
pub fn new(
kind: ContextErrorKind,
label: impl Into<Option<Cow<'static, str>>>,
source: impl Into<Option<Box<dyn std::error::Error + Send + Sync>>>,
) -> Self {
Self {
kind,
label: label.into(),
source: source.into(),
}
}
pub fn with_label(mut self, label: impl Into<Cow<'static, str>>) -> 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<T> {
fn ctx_err(
self,
kind: ContextErrorKind,
label: impl Into<Cow<'static, str>>,
) -> Result<T, RenderContextError>;
}
impl<T, E> IntoRenderContextError<T> for Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn ctx_err(
self,
kind: ContextErrorKind,
label: impl Into<Cow<'static, str>>,
) -> Result<T, RenderContextError> {
self.map_err(|e| {
RenderContextError::new(
kind,
Some(label.into()),
Some(Box::new(e) as Box<dyn std::error::Error + Send + Sync>),
)
})
}
}
impl From<wgpu::CreateSurfaceError> for RenderContextError {
fn from(err: wgpu::CreateSurfaceError) -> Self {
RenderContextError::new(
ContextErrorKind::SurfaceCreation,
Some("Surface creation".into()),
Some(Box::new(err) as Box<dyn std::error::Error + Send + Sync>),
)
}
}
impl From<wgpu::RequestDeviceError> for RenderContextError {
fn from(err: wgpu::RequestDeviceError) -> Self {
RenderContextError::new(
ContextErrorKind::DeviceRequest,
Some("Device setup".into()),
Some(Box::new(err) as Box<dyn std::error::Error + Send + Sync>),
)
}
}
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<f32>,
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<f32>) {
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<u32>,
}
struct FontState {
@ -331,15 +221,13 @@ struct FontState {
}
impl<'window> Renderer<'window> {
pub async fn new(window: Arc<Window>) -> Result<Self, RenderContextError> {
pub async fn new(window: Arc<Window>) -> Result<Self> {
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<Window>) -> Result<Self, RenderContextError> {
pub fn new_blocking(window: Arc<Window>) -> Result<Self> {
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;

View file

@ -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);
}
}

View file

@ -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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> 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<Vec<(String, Option<Vec<String>>)>, anyhow::Error> {
fn eval(input: String) -> Result<Vec<(String, Option<Vec<String>>)>, 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![];

View file

@ -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<CommandManager> = RwLock::new(CommandManager::init());
}
use std::sync::LazyLock;
use crate::error::{ZenyxError, ZenyxErrorKind};
pub static COMMAND_MANAGER: LazyLock<RwLock<CommandManager>> = LazyLock::new(|| { RwLock::new(CommandManager::init()) });
#[macro_export]
macro_rules! commands {
[$($command:ty),*] => [
@ -110,24 +111,28 @@ impl CommandManager {
&self,
command: &str,
args: Option<Vec<String>>,
) -> 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<Vec<String>>) -> Result<(), anyhow::Error> {
pub fn execute(&self, command: &str, args: Option<Vec<String>>) -> 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<Vec<String>>) -> Result<(), anyhow::Error>;
fn execute(&self, args: Option<Vec<String>>) -> Result<(), ZenyxError>;
fn undo(&self);
fn redo(&self);
fn get_description(&self) -> String;

View file

@ -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<String> {
tokens
}
pub fn parse_command(input: &str) -> anyhow::Result<Vec<String>> {
let pattern = Regex::new(r"[;|\n]").unwrap();
pub fn parse_command(input: &str) -> Result<Vec<String>> {
let pattern = Regex::new(r"[;|\n]").map_err(|_| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Failed to compile regex pattern")
.build()
})?;
let commands: Vec<String> = 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::<MyHelper, DefaultHistory>::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) => {

View file

@ -6,6 +6,9 @@ pub mod commands;
pub mod handler;
pub mod input;
pub fn setup() {
commands!(
HelpCommand,

View file

@ -1,17 +0,0 @@
use std::path::PathBuf;
use anyhow::{Context, Result, anyhow};
pub fn get_working_dir() -> Result<PathBuf> {
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<PathBuf> {
let mut dir = get_working_dir().context("Failed to obtain working dir")?;
dir.push("data");
Ok(dir)
}

275
engine/src/error.rs Normal file
View file

@ -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<String>,
context: Option<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
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<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
pub fn with_source<E>(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<std::io::Error> for ZenyxError {
fn from(err: std::io::Error) -> Self {
Self::builder(ZenyxErrorKind::Io)
.with_message(err.to_string())
.with_source(err)
.build()
}
}
impl From<wgpu::CreateSurfaceError> for ZenyxError {
fn from(err: wgpu::CreateSurfaceError) -> Self {
Self::builder(ZenyxErrorKind::SurfaceCreation)
.with_message("Failed to create surface")
.with_source(err)
.build()
}
}
impl From<wgpu::RequestDeviceError> for ZenyxError {
fn from(err: wgpu::RequestDeviceError) -> Self {
Self::builder(ZenyxErrorKind::DeviceRequest)
.with_message("Failed to request device")
.with_source(err)
.build()
}
}
impl From<rustyline::error::ReadlineError> 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<T> = std::result::Result<T, ZenyxError>;
#[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());
}
}

View file

@ -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(())
}

559
engine/src/metadata.rs Normal file
View file

@ -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<Self, Self::Err> {
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<u8>,
pub logical_cores: Option<u8>,
pub max_clock_speed: ClockSpeed,
pub current_clock_speed: ClockSpeed,
pub l1_cache: Option<Memory>,
pub l2_cache: Option<Memory>,
pub l3_cache: Option<Memory>,
}
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<String>,
}
impl GPU {
pub fn current() -> Vec<Self> {
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<GPU>,
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());
}
}

0
main.rs Normal file
View file