improve error handling and add metadata
This commit is contained in:
parent
87cef23366
commit
b8d5c09d08
17 changed files with 1162 additions and 787 deletions
509
Cargo.lock
generated
509
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
3
engine/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
built::write_built_file().expect("Failed to write build information");
|
||||
}
|
|
@ -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 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<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;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,5 @@ pub mod ecs;
|
|||
pub mod panic;
|
||||
pub mod repl;
|
||||
pub mod splash;
|
||||
pub mod workspace;
|
||||
|
||||
pub mod render;
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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![];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -6,6 +6,9 @@ pub mod commands;
|
|||
pub mod handler;
|
||||
pub mod input;
|
||||
|
||||
|
||||
|
||||
|
||||
pub fn setup() {
|
||||
commands!(
|
||||
HelpCommand,
|
||||
|
|
|
@ -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
275
engine/src/error.rs
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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
559
engine/src/metadata.rs
Normal 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
0
main.rs
Normal file
Loading…
Add table
Add a link
Reference in a new issue