diff options
101 files changed, 11129 insertions, 4964 deletions
@@ -9,6 +9,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -24,6 +37,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -36,6 +76,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -49,26 +107,6 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" -version = "0.68.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" -dependencies = [ - "bitflags 2.9.0", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn", -] - -[[package]] -name = "bindgen" version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" @@ -78,13 +116,13 @@ dependencies = [ "clang-sys", "itertools", "log", - "prettyplease", + "prettyplease 0.2.31", "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash", "shlex", - "syn", + "syn 2.0.100", ] [[package]] @@ -100,6 +138,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -112,12 +165,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -133,6 +235,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -202,6 +319,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -245,19 +421,76 @@ dependencies = [ ] [[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] name = "ecs" version = "0.1.0" dependencies = [ "criterion", "ecs-macros", "hashbrown", - "linkme", "parking_lot", "paste", "seq-macro", @@ -273,7 +506,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "toml", ] @@ -288,15 +521,22 @@ name = "engine" version = "0.1.0" dependencies = [ "bitflags 2.9.0", + "cfg_aliases", + "crossbeam-channel", "ecs", - "gl", - "glfw", + "glutin", "image", + "nu-ansi-term", + "opengl-bindings", + "parking_lot", "paste", + "raw-window-handle", + "safer-ffi", "seq-macro", "thiserror", "tracing", "util-macros", + "winit", ] [[package]] @@ -306,6 +546,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "extension-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +dependencies = [ + "ext-trait", +] + +[[package]] +name = "extern-c" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" + +[[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -331,6 +616,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] name = "game-newest" version = "0.1.0" dependencies = [ @@ -340,12 +652,36 @@ dependencies = [ ] [[package]] -name = "gl" -version = "0.14.0" +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "gl_generator", + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -360,21 +696,64 @@ dependencies = [ ] [[package]] -name = "glfw" -version = "0.1.0" +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" dependencies = [ - "bindgen 0.68.1", "bitflags 2.9.0", - "libc", - "thiserror", - "util-macros", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", ] [[package]] -name = "glob" -version = "0.3.2" +name = "glutin_egl_sys" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] [[package]] name = "graphviz-sys" @@ -382,7 +761,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97dbac5b269c7160b78812a9873f548668aa34b62f28f7a64328a6cd94feb47d" dependencies = [ - "bindgen 0.71.1", + "bindgen", ] [[package]] @@ -444,7 +823,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -463,12 +842,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] name = "khronos_api" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -481,12 +902,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -499,28 +914,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] -name = "linkme" -version = "0.3.32" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "linkme-impl", + "bitflags 2.9.0", + "libc", + "redox_syscall 0.5.10", ] [[package]] -name = "linkme-impl" -version = "0.3.32" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" @@ -539,6 +957,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] +name = "macro_rules_attribute" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" + +[[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -554,6 +988,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -570,6 +1013,36 @@ dependencies = [ ] [[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -599,6 +1072,273 @@ dependencies = [ ] [[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.0", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.0", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] name = "once_cell" version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -611,6 +1351,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] +name = "opengl-bindings" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags 2.9.0", + "gl_generator", + "glutin", + "safer-ffi", + "thiserror", + "toml", + "util-macros", +] + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -634,9 +1397,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.10", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -646,10 +1409,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] [[package]] name = "pin-project-lite" @@ -658,6 +1441,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -671,13 +1460,56 @@ dependencies = [ ] [[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.7", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] name = "prettyplease" version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.100", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", ] [[package]] @@ -690,6 +1522,15 @@ dependencies = [ ] [[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -699,6 +1540,57 @@ dependencies = [ ] [[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] name = "redox_syscall" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -753,15 +1645,50 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustc-hash" -version = "2.1.1" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -770,6 +1697,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] +name = "safer-ffi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" +dependencies = [ + "extern-c", + "libc", + "macro_rules_attribute", + "paste", + "safer_ffi-proc_macros", + "scopeguard", + "stabby", + "uninit", + "unwind_safe", + "with_builtin_macros", +] + +[[package]] +name = "safer_ffi-proc_macros" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" +dependencies = [ + "macro_rules_attribute", + "prettyplease 0.1.25", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -779,12 +1737,24 @@ dependencies = [ ] [[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] name = "seq-macro" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -807,7 +1777,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -832,6 +1802,12 @@ dependencies = [ ] [[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -853,12 +1829,101 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.9.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "stabby" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8" +dependencies = [ + "rustversion", + "stabby-abi", +] + +[[package]] +name = "stabby-abi" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a" +dependencies = [ + "rustc_version", + "rustversion", + "sha2-const-stable", + "stabby-macros", +] + +[[package]] +name = "stabby-macros" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "rand", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -886,7 +1951,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -962,7 +2027,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -998,15 +2063,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "uninit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" +dependencies = [ + "extension-traits", +] + +[[package]] +name = "unwind_safe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" + +[[package]] name = "util-macros" version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] name = "vizoxide" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1027,6 +2119,221 @@ dependencies = [ ] [[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags 2.9.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +dependencies = [ + "rustix 0.38.44", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1048,7 +2355,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1059,11 +2366,59 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1072,30 +2427,66 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" @@ -1108,29 +2499,128 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] +name = "winit" +version = "0.30.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] name = "winnow" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1140,7 +2630,113 @@ dependencies = [ ] [[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "with_builtin_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] name = "xml-rs" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["glfw", "engine", "ecs", "ecs-macros", "util-macros"] +members = ["engine", "ecs", "ecs-macros", "util-macros", "opengl-bindings"] [dependencies] engine = { path = "./engine" } diff --git a/ecs-macros/src/lib.rs b/ecs-macros/src/lib.rs index aab8dd2..7d00736 100644 --- a/ecs-macros/src/lib.rs +++ b/ecs-macros/src/lib.rs @@ -14,7 +14,6 @@ use syn::{ ItemStruct, ItemUnion, Path, - Type, }; use toml::value::{Table as TomlTable, Value as TomlValue}; @@ -49,15 +48,11 @@ macro_rules! syn_path_segment { /// - Not attributed to a type item /// - The attributed-to type item is generic /// - If parsing the user crate's `Cargo.toml` file fails. -#[proc_macro_derive(Component, attributes(component))] +#[proc_macro_derive(Component)] pub fn component_derive(input: TokenStream) -> TokenStream { let item: TypeItem = parse::<Item>(input).unwrap().try_into().unwrap(); - let ComponentAttribute { handle_type, handle_mut_type } = item - .attribute::<ComponentAttribute>("component") - .unwrap_or_default(); - let item_ident = item.ident(); let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); @@ -88,10 +83,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream use ::std::sync::{LazyLock, Mutex}; use #ecs_path::component::Component; - use #ecs_path::component::{ - Handle as ComponentHandle, - HandleMut as ComponentHandleMut - }; use #ecs_path::uid::{Uid, Kind as UidKind}; use #ecs_path::system::Input as SystemInput; @@ -102,9 +93,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream impl #impl_generics Component for #item_ident #type_generics #where_clause { - type HandleMut<'component> = #handle_mut_type; - type Handle<'component> = #handle_type; - fn id() -> Uid { *#id_var_ident @@ -326,61 +314,3 @@ fn find_engine_ecs_crate_path() -> Option<Path> None }) } - -#[derive(Debug)] -struct ComponentAttribute -{ - handle_type: proc_macro2::TokenStream, - handle_mut_type: proc_macro2::TokenStream, -} - -impl FromAttribute for ComponentAttribute -{ - fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error> - { - let mut handle_type: Option<Type> = None; - let mut handle_mut_type: Option<Type> = None; - - attribute.parse_nested_meta(|meta| { - let Some(flag) = meta.path.get_ident() else { - return Err(meta.error("Not a single identifier")); - }; - - if flag == "handle_type" { - let value = meta.value()?; - - handle_type = Some(value.parse::<Type>()?); - - return Ok(()); - } else if flag == "handle_mut_type" { - let value = meta.value()?; - - handle_mut_type = Some(value.parse::<Type>()?); - - return Ok(()); - } - - Err(meta.error("Unrecognized token")) - })?; - - Ok(Self { - handle_type: handle_type - .map_or_else(|| Self::default().handle_type, ToTokens::into_token_stream), - handle_mut_type: handle_mut_type.map_or_else( - || Self::default().handle_mut_type, - ToTokens::into_token_stream, - ), - }) - } -} - -impl Default for ComponentAttribute -{ - fn default() -> Self - { - Self { - handle_type: quote! { ComponentHandle<'component, Self> }, - handle_mut_type: quote! { ComponentHandleMut<'component, Self> }, - } - } -} diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index cf35a74..5ea9fc7 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -11,7 +11,6 @@ seq-macro = "0.3.5" paste = "1.0.14" thiserror = "1.0.49" tracing = "0.1.39" -linkme = "0.3.29" hashbrown = "0.15.2" parking_lot = "0.12.3" ecs-macros = { path = "../ecs-macros" } diff --git a/ecs/examples/component_changed_event.rs b/ecs/examples/component_changed_event.rs new file mode 100644 index 0000000..1a53a88 --- /dev/null +++ b/ecs/examples/component_changed_event.rs @@ -0,0 +1,78 @@ +use ecs::event::component::Changed; +use ecs::pair::Pair; +use ecs::phase::UPDATE as UPDATE_PHASE; +use ecs::system::observer::Observe; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct SomeData +{ + num: u64, +} + +#[derive(Component)] +struct Greeting +{ + greeting: String, +} + +fn say_hello(query: Query<(&SomeData, &mut Greeting)>) +{ + for (data, mut greeting) in &query { + println!("{}: {}", greeting.greeting, data.num); + + if greeting.greeting == "Good evening" { + greeting.greeting = "Good morning".to_string(); + greeting.set_changed(); + } + } +} + +fn print_changed_greetings(observe: Observe<'_, Pair<Changed, Greeting>>) +{ + println!("\nChanged greetings:"); + + for evt_match in &observe { + let greeting = evt_match.get_changed_comp(); + + println!("A greeting changed to {}", greeting.greeting); + } + + println!(""); +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE_PHASE, say_hello); + + world.register_observer(print_changed_greetings); + + world.create_entity(( + SomeData { num: 987_654 }, + Greeting { + greeting: "Good afternoon".to_string(), + }, + )); + + world.create_entity(( + SomeData { num: 345 }, + Greeting { greeting: "Good evening".to_string() }, + )); + + world.step(); + + world.step(); + + for (mut greeting,) in &world.query::<(&mut Greeting,), ()>() { + if greeting.greeting == "Good afternoon" { + greeting.greeting = "Yo yo".to_string(); + greeting.set_changed(); + } + } + + world.step(); + + world.step(); +} diff --git a/ecs/examples/component_events.rs b/ecs/examples/component_events.rs new file mode 100644 index 0000000..af09ff9 --- /dev/null +++ b/ecs/examples/component_events.rs @@ -0,0 +1,64 @@ +use ecs::actions::Actions; +use ecs::component::Component; +use ecs::event::component::{Changed, Removed}; +use ecs::pair::Pair; +use ecs::phase::UPDATE; +use ecs::system::observer::Observe; +use ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct CheeseCrumbs +{ + cnt: usize, +} + +#[derive(Debug, Component)] +struct Cheese +{ + name: &'static str, +} + +fn eat_cheese(query: Query<(&Cheese, &mut CheeseCrumbs)>, mut actions: Actions) +{ + for (cheese_ent_id, (_, mut cheese_crumbs)) in query.iter_with_euids() { + println!("Eating cheese!"); + + cheese_crumbs.cnt += 40; + cheese_crumbs.set_changed(); + + actions.remove_components(cheese_ent_id, [Cheese::id()]); + } +} + +fn on_cheese_removed(observe: Observe<Pair<Removed, Cheese>>) +{ + for evt_match in &observe { + let cheese = evt_match.get_removed_comp(); + + println!("{} cheese was eaten", cheese.name); + } +} + +fn on_cheese_crumbs_changed(observe: Observe<Pair<Changed, CheeseCrumbs>>) +{ + for evt_match in &observe { + let cheese_crumbs = evt_match.get_changed_comp(); + + println!("Cheese crumbs count changed to {}", cheese_crumbs.cnt); + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE, eat_cheese); + world.register_observer(on_cheese_removed); + world.register_observer(on_cheese_crumbs_changed); + + world.create_entity((Cheese { name: "Brie" }, CheeseCrumbs { cnt: 0 })); + world.create_entity((Cheese { name: "Parmesan" }, CheeseCrumbs { cnt: 0 })); + world.create_entity((Cheese { name: "Gouda" }, CheeseCrumbs { cnt: 0 })); + + world.step(); +} diff --git a/ecs/examples/component_relationship.rs b/ecs/examples/component_relationship.rs new file mode 100644 index 0000000..e07b214 --- /dev/null +++ b/ecs/examples/component_relationship.rs @@ -0,0 +1,65 @@ +use ecs::pair::Pair; +use ecs::phase::START as START_PHASE; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct Person +{ + name: String, +} + +fn print_dog_likers(query: Query<(&Person, Pair<Likes, &Dogs>)>) +{ + for (person, liked_dogs) in &query { + println!( + "{} likes {} dogs!", + person.name, + if liked_dogs.large { "large" } else { "small" }, + ); + } +} + +#[derive(Component)] +struct Likes; + +#[derive(Component)] +struct Cats; + +#[derive(Component)] +struct Dogs +{ + large: bool, +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, print_dog_likers); + + world.create_entity(( + Person { name: "Irving".to_string() }, + Pair::builder() + .relation::<Likes>() + .target_as_data(Dogs { large: true }) + .build(), + )); + + world.create_entity(( + Person { name: "Mark".to_string() }, + Pair::builder() + .relation::<Likes>() + .target_as_data(Cats) + .build(), + )); + + world.create_entity(( + Person { name: "Helena".to_string() }, + Pair::builder() + .relation::<Likes>() + .target_as_data(Dogs { large: false }) + .build(), + )); + + world.step(); +} diff --git a/ecs/examples/component_removed_event.rs b/ecs/examples/component_removed_event.rs new file mode 100644 index 0000000..776aa48 --- /dev/null +++ b/ecs/examples/component_removed_event.rs @@ -0,0 +1,46 @@ +use ecs::actions::Actions; +use ecs::component::Component; +use ecs::event::component::Removed; +use ecs::pair::Pair; +use ecs::phase::UPDATE; +use ecs::system::observer::Observe; +use ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct Cheese +{ + name: &'static str, +} + +fn eat_cheese(query: Query<(&Cheese,)>, mut actions: Actions) +{ + for (cheese_ent_id, (_,)) in query.iter_with_euids() { + println!("Eating cheese!"); + + actions.remove_components(cheese_ent_id, [Cheese::id()]); + } +} + +fn on_cheese_removed(observe: Observe<Pair<Removed, Cheese>>) +{ + for evt_match in &observe { + let cheese = evt_match.get_removed_comp(); + + println!("{} cheese was eaten", cheese.name); + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE, eat_cheese); + world.register_observer(on_cheese_removed); + + world.create_entity((Cheese { name: "Brie" },)); + world.create_entity((Cheese { name: "Parmesan" },)); + world.create_entity((Cheese { name: "Gouda" },)); + + world.step(); + world.step(); +} diff --git a/ecs/examples/event_loop.rs b/ecs/examples/event_loop.rs index 61d7ba4..bec2c00 100644 --- a/ecs/examples/event_loop.rs +++ b/ecs/examples/event_loop.rs @@ -1,7 +1,7 @@ use ecs::actions::Actions; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::{static_entity, Component, Query, World}; +use ecs::{declare_entity, Component, Query, World}; #[derive(Component)] struct Wool @@ -65,16 +65,47 @@ fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions) } } -static_entity!(SHEER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); - -static_entity!(FEED_PHASE, (Phase, Pair::new::<ChildOf>(*SHEER_PHASE))); - -static_entity!(AGE_PHASE, (Phase, Pair::new::<ChildOf>(*FEED_PHASE))); +declare_entity!( + SHEER_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*UPDATE_PHASE) + .build() + ) +); + +declare_entity!( + FEED_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*SHEER_PHASE) + .build() + ) +); + +declare_entity!( + AGE_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*FEED_PHASE) + .build() + ) +); fn main() { let mut world = World::new(); + world.create_declared_entity(&SHEER_PHASE); + world.create_declared_entity(&FEED_PHASE); + world.create_declared_entity(&AGE_PHASE); + world.register_system(*SHEER_PHASE, sheer); world.register_system(*FEED_PHASE, feed); world.register_system(*AGE_PHASE, age); diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs index b607398..4e94151 100644 --- a/ecs/examples/relationship.rs +++ b/ecs/examples/relationship.rs @@ -1,6 +1,5 @@ -use ecs::pair::Pair; +use ecs::pair::{Pair, Wildcard}; use ecs::phase::START as START_PHASE; -use ecs::uid::Wildcard; use ecs::{Component, Query, World}; #[derive(Component)] @@ -23,10 +22,10 @@ struct Holding; fn print_player_stats(player_query: Query<(&Player, &Health, Pair<Holding, Wildcard>)>) { - for (_, health, sword_relationship) in &player_query { + for (_, health, target_sword) in &player_query { println!("Player health: {}", health.health); - if let Some(sword_ent) = sword_relationship.get_target_entity() { + if let Some(sword_ent) = target_sword.get_target_ent() { let sword = sword_ent .get::<Sword>() .expect("Sword entity is missing sword component"); @@ -47,7 +46,10 @@ fn main() world.create_entity(( Player, Health { health: 180 }, - Pair::new::<Holding>(sword_uid), + Pair::builder() + .relation::<Holding>() + .target_id(sword_uid) + .build(), )); world.step(); diff --git a/ecs/examples/with_local.rs b/ecs/examples/with_local.rs index 4658fc0..7a36d0e 100644 --- a/ecs/examples/with_local.rs +++ b/ecs/examples/with_local.rs @@ -1,6 +1,7 @@ use ecs::component::local::Local; use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::system::{Into, System}; +use ecs::system::initializable::Initializable; +use ecs::system::Into; use ecs::{Component, Query, World}; #[derive(Component)] diff --git a/ecs/examples/with_sole.rs b/ecs/examples/with_sole.rs index c3feaab..7e89b0a 100644 --- a/ecs/examples/with_sole.rs +++ b/ecs/examples/with_sole.rs @@ -1,7 +1,7 @@ use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; use ecs::sole::Single; -use ecs::{static_entity, Component, Query, Sole, World}; +use ecs::{declare_entity, Component, Query, Sole, World}; #[derive(Component)] struct Ammo @@ -31,15 +31,23 @@ fn print_total_ammo_count(ammo_counter: Single<AmmoCounter>) assert_eq!(ammo_counter.counter, 19); } -static_entity!( +declare_entity!( PRINT_AMMO_COUNT_PHASE, - (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE)) + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*UPDATE_PHASE) + .build() + ) ); fn main() { let mut world = World::new(); + world.create_declared_entity(&PRINT_AMMO_COUNT_PHASE); + world.register_system(*UPDATE_PHASE, count_ammo); world.register_system(*PRINT_AMMO_COUNT_PHASE, print_total_ammo_count); diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs index e0efeda..549e341 100644 --- a/ecs/src/actions.rs +++ b/ecs/src/actions.rs @@ -1,9 +1,12 @@ +use std::any::type_name; use std::marker::PhantomData; use std::rc::{Rc, Weak}; use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence}; -use crate::system::{Param as SystemParam, System}; -use crate::uid::{Kind as UidKind, Uid}; +use crate::event::component::Removed; +use crate::pair::Pair; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; +use crate::uid::{Kind as UidKind, Uid, WithUidTuple}; use crate::{ActionQueue, World}; /// Used to to queue up actions for a [`World`] to perform. @@ -11,16 +14,23 @@ use crate::{ActionQueue, World}; pub struct Actions<'world> { action_queue: &'world ActionQueue, - action_queue_weak: Weak<ActionQueue>, + world: Option<&'world World>, } -impl<'world> Actions<'world> +impl Actions<'_> { - /// Queues up a entity to spawn at the end of the current tick. - pub fn spawn<Comps: ComponentSequence>(&mut self, components: Comps) + /// Queues up a entity to spawn at the end of the current tick, returning the [`Uid`] + /// that the entity will have. + pub fn spawn<Comps: ComponentSequence>(&mut self, components: Comps) -> Uid { - self.action_queue - .push(Action::Spawn(components.into_parts_array().into())); + let new_entity_uid = Uid::new_unique(UidKind::Entity); + + self.action_queue.push(Action::Spawn( + new_entity_uid, + components.into_parts_array().into(), + )); + + new_entity_uid } /// Queues up despawning a entity at the end of the current tick. @@ -28,6 +38,31 @@ impl<'world> Actions<'world> { debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + let Some(world) = self.world else { + self.action_queue.push(Action::Despawn(entity_uid)); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot entity that doesn't exist"); + return; + }; + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in ent.component_ids() { + if comp_id.kind() == UidKind::Pair { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::<Removed>() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + self.action_queue.push(Action::Despawn(entity_uid)); } @@ -49,6 +84,7 @@ impl<'world> Actions<'world> } /// Queues up removing component(s) from a entity at the end of the current tick. + #[tracing::instrument(skip(self, component_ids))] pub fn remove_components( &mut self, entity_uid: Uid, @@ -63,10 +99,50 @@ impl<'world> Actions<'world> return; } - self.action_queue.push(Action::RemoveComponents( - entity_uid, - component_ids.collect(), - )); + let Some(world) = self.world else { + self.action_queue.push(Action::RemoveComponents( + entity_uid, + component_ids.collect(), + )); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot remove components from entity that doesn't exist"); + return; + }; + + let component_ids = component_ids + .filter(|comp_id| ent.has_component(*comp_id)) + .collect::<Vec<_>>(); + + if component_ids.is_empty() { + return; + } + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in &component_ids { + if comp_id.kind() == UidKind::Pair { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::<Removed>() + .target_id(*comp_id) + .build(), + entity_uid, + ); + } + + self.action_queue + .push(Action::RemoveComponents(entity_uid, component_ids)); + } + + /// Queues up removing component(s) from a entity at the end of the current tick. + pub fn remove_comps<Ids: WithUidTuple>(&mut self, entity_uid: Uid) + { + self.remove_components(entity_uid, Ids::uids()); } /// Stops the [`World`]. The world will finish the current tick and that tick will be @@ -79,19 +155,22 @@ impl<'world> Actions<'world> /// Returns a struct which holds a weak reference to the [`World`] that `Actions` /// references and that can be used to aquire a new `Actions` instance if the /// referenced [`World`] is still alive. + /// + /// # Panics + /// This function will panic if `self` was retrieved from a [`WeakRef`]. #[must_use] pub fn to_weak_ref(&self) -> WeakRef { - WeakRef { - action_queue: self.action_queue_weak.clone(), - } - } + let world = self.world.unwrap_or_else(|| { + panic!( + "This function cannot be called if the {} was retrieved from a {}", + type_name::<Self>(), + type_name::<WeakRef>() + ) + }); - fn new(action_queue: &'world Rc<ActionQueue>) -> Self - { - Self { - action_queue, - action_queue_weak: Rc::downgrade(action_queue), + WeakRef { + action_queue: Rc::downgrade(&world.data.action_queue), } } } @@ -100,19 +179,12 @@ impl<'world> SystemParam<'world> for Actions<'world> { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { - Self::new(&world.data.action_queue) + Self { + action_queue: &world.data.action_queue, + world: Some(world), + } } } @@ -151,7 +223,10 @@ impl Ref<'_> #[must_use] pub fn to_actions(&self) -> Actions<'_> { - Actions::new(&self.action_queue) + Actions { + action_queue: &self.action_queue, + world: None, + } } } @@ -159,7 +234,7 @@ impl Ref<'_> #[derive(Debug)] pub(crate) enum Action { - Spawn(Vec<ComponentParts>), + Spawn(Uid, Vec<ComponentParts>), Despawn(Uid), AddComponents(Uid, Vec<ComponentParts>), RemoveComponents(Uid, Vec<Uid>), diff --git a/ecs/src/component.rs b/ecs/src/component.rs index a0ed752..17b279b 100644 --- a/ecs/src/component.rs +++ b/ecs/src/component.rs @@ -1,10 +1,11 @@ use std::any::{type_name, Any}; -use std::error::Error; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; use seq_macro::seq; +use crate::event::component::Changed; +use crate::event::Submitter as EventSubmitter; use crate::lock::{ Error as LockError, MappedReadGuard, @@ -12,6 +13,7 @@ use crate::lock::{ ReadGuard, WriteGuard, }; +use crate::pair::Pair; use crate::system::Input as SystemInput; use crate::uid::Uid; use crate::util::Array; @@ -23,14 +25,6 @@ pub(crate) mod storage; pub trait Component: SystemInput + Any { - type HandleMut<'component>: HandleFromEntityComponentRef<'component> - where - Self: Sized; - - type Handle<'component>: HandleFromEntityComponentRef<'component> - where - Self: Sized; - /// Returns the ID of this component. fn id() -> Uid where @@ -77,87 +71,44 @@ pub trait Sequence fn into_parts_array(self) -> Self::PartsArray; } -/// [`Component`] metadata. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Metadata +#[derive(Debug)] +pub struct Handle<'a, DataT: 'static> { - pub id: Uid, + inner: MappedReadGuard<'a, DataT>, } -impl Metadata +impl<'comp, DataT: 'static> Handle<'comp, DataT> { - #[must_use] - pub fn of<ComponentT: Component>() -> Self - { - Self { id: ComponentT::id() } - } -} - -pub trait HandleFromEntityComponentRef<'comp>: Sized -{ - type Error: Error; - /// Creates a new handle instance from a [`EntityComponentRef`]. /// /// # Errors - /// See the implementation's [`Self::Error`] type. - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - world: &'comp World, - ) -> Result<Self, Self::Error>; -} - -#[derive(Debug)] -pub struct Handle<'a, ComponentData: 'static> -{ - inner: MappedReadGuard<'a, ComponentData>, -} - -impl<'a, ComponentData: 'static> Handle<'a, ComponentData> -{ - pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Any>>) -> Self + /// Will return `Err` if acquiring the component's lock fails. + pub fn from_entity_component_ref( + entity_component_ref: &EntityComponentRef<'comp>, + ) -> Result<Self, HandleError> { - Self { - inner: inner.map(|component| { - component - .downcast_ref::<ComponentData>() - .unwrap_or_else(|| { - panic!( - "Failed to downcast component to type {}", - type_name::<ComponentData>() - ); - }) - }), - } + Self::new( + entity_component_ref + .component() + .read_nonblock() + .map_err(AcquireLockError)?, + ) } -} - -impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp> - for Handle<'comp, ComponentData> -{ - type Error = HandleError; - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - _world: &'comp World, - ) -> Result<Self, Self::Error> + fn new(inner: ReadGuard<'comp, Box<dyn Any>>) -> Result<Self, HandleError> { - let entity_comp = - entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?; - - Ok(Self::new( - entity_comp - .component() - .read_nonblock() - .map_err(AcquireComponentLockFailed)?, - )) + Ok(Self { + inner: ReadGuard::try_map(inner, |component| { + component.downcast_ref::<DataT>() + }) + .map_err(|_| HandleError::IncorrectType)?, + }) } } -impl<ComponentData: 'static> Deref for Handle<'_, ComponentData> +impl<DataT: 'static> Deref for Handle<'_, DataT> { - type Target = ComponentData; + type Target = DataT; fn deref(&self) -> &Self::Target { @@ -166,55 +117,54 @@ impl<ComponentData: 'static> Deref for Handle<'_, ComponentData> } #[derive(Debug)] -pub struct HandleMut<'a, ComponentData: 'static> +pub struct HandleMut<'a, DataT: 'static> { - inner: MappedWriteGuard<'a, ComponentData>, + entity_component_ref: EntityComponentRef<'a>, + inner: MappedWriteGuard<'a, DataT>, + event_submitter: EventSubmitter<'a>, } -impl<'a, ComponentData: 'static> HandleMut<'a, ComponentData> +impl<'comp, DataT: 'static> HandleMut<'comp, DataT> { - pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Any>>) -> Self + /// Creates a new handle instance from a [`EntityComponentRef`]. + /// + /// # Errors + /// Will return `Err` if acquiring the component's lock fails. + pub fn from_entity_component_ref( + entity_component_ref: &EntityComponentRef<'comp>, + world: &'comp World, + ) -> Result<Self, HandleError> { - Self { - inner: inner.map(|component| { - component - .downcast_mut::<ComponentData>() - .unwrap_or_else(|| { - panic!( - "Failed to downcast component to type {}", - type_name::<ComponentData>() - ); - }) - }), - } + let inner = entity_component_ref + .component() + .write_nonblock() + .map_err(AcquireLockError)?; + + Ok(Self { + entity_component_ref: entity_component_ref.clone(), + inner: WriteGuard::try_map(inner, |component| { + component.downcast_mut::<DataT>() + }) + .map_err(|_| HandleError::IncorrectType)?, + event_submitter: world.event_submitter(), + }) } -} -impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp> - for HandleMut<'comp, ComponentData> -{ - type Error = HandleError; - - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - _world: &'comp World, - ) -> Result<Self, Self::Error> + pub fn set_changed(&self) { - let entity_comp = - entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?; - - Ok(Self::new( - entity_comp - .component() - .write_nonblock() - .map_err(AcquireComponentLockFailed)?, - )) + self.event_submitter.submit_event( + &Pair::builder() + .relation::<Changed>() + .target_id(self.entity_component_ref.id()) + .build(), + self.entity_component_ref.entity_id(), + ); } } -impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData> +impl<DataT: 'static> Deref for HandleMut<'_, DataT> { - type Target = ComponentData; + type Target = DataT; fn deref(&self) -> &Self::Target { @@ -222,7 +172,7 @@ impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData> } } -impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData> +impl<DataT: 'static> DerefMut for HandleMut<'_, DataT> { fn deref_mut(&mut self) -> &mut Self::Target { @@ -234,15 +184,15 @@ impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData> pub enum HandleError { #[error(transparent)] - AcquireComponentLockFailed(#[from] AcquireComponentLockFailed), + AcquireLockFailed(#[from] AcquireLockError), - #[error("Component does not exist")] - ComponentDoesNotExist, + #[error("Incorrect component type")] + IncorrectType, } #[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct AcquireComponentLockFailed(LockError); +#[error("Failed to acquire component lock")] +pub struct AcquireLockError(#[source] LockError); macro_rules! inner { ($c: tt) => { @@ -357,6 +307,12 @@ impl PartsBuilder data: Box::new(data), } } + + #[must_use] + pub fn build_with_any_data(self, id: Uid, data: Box<dyn Any>) -> Parts + { + Parts { id, name: self.name, data } + } } impl Default for PartsBuilder diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs index 0f6f641..b19a30b 100644 --- a/ecs/src/component/local.rs +++ b/ecs/src/component/local.rs @@ -1,7 +1,17 @@ +use std::any::type_name; use std::ops::{Deref, DerefMut}; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::system::{Param as SystemParam, System}; +use ecs_macros::Component; + +use crate::component::{ + Component, + HandleMut as ComponentHandleMut, + IntoParts as _, + Parts as ComponentParts, +}; +use crate::pair::Pair; +use crate::system::initializable::Param as InitializableParam; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::World; /// Holds a component which is local to a single system. @@ -17,27 +27,52 @@ where { type Input = LocalComponent; - fn initialize<SystemImpl>( - system: &mut impl System<'world, SystemImpl>, - input: Self::Input, - ) + fn new(world: &'world World, system_metadata: &SystemMetadata) -> Self { - system.set_local_component(input); - } + let Some(system_ent) = world.get_entity(system_metadata.ent_id) else { + panic!( + "System entity with ID {} does not exist", + system_metadata.ent_id + ); + }; - fn new<SystemImpl>( - system: &'world impl System<'world, SystemImpl>, - _world: &'world World, - ) -> Self - { - let local_component = system - .get_local_component_mut::<LocalComponent>() - .expect("Local component is uninitialized"); + let Some(local_component) = system_ent.get_with_id_mut::<LocalComponent>( + Pair::builder() + .relation::<IsLocalComponent>() + .target::<LocalComponent>() + .build() + .id(), + ) else { + panic!( + "Local component {} of system with ID {} is uninitialized", + type_name::<LocalComponent>(), + system_metadata.ent_id + ); + }; Self { local_component } } } +impl<'world, LocalComponent, SystemT> InitializableParam<'world, SystemT> + for Local<'world, LocalComponent> +where + LocalComponent: Component, + SystemT: SystemWithLocalComponents, + Self: SystemParam<'world, Input = LocalComponent>, +{ + fn initialize(system: &mut SystemT, input: Self::Input) + { + system.add_local_component( + Pair::builder() + .relation::<IsLocalComponent>() + .target_as_data(input) + .build() + .into_parts(), + ); + } +} + impl<LocalComponent> Deref for Local<'_, LocalComponent> where LocalComponent: Component, @@ -59,3 +94,11 @@ where &mut self.local_component } } + +pub trait SystemWithLocalComponents +{ + fn add_local_component(&mut self, component_parts: ComponentParts); +} + +#[derive(Component)] +struct IsLocalComponent; diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs index b27b552..a8711c5 100644 --- a/ecs/src/component/storage.rs +++ b/ecs/src/component/storage.rs @@ -270,7 +270,7 @@ impl Storage entity.insert_component( component_id, - ArchetypeEntityComponent::new(component, component_id, component_name), + ArchetypeEntityComponent::new(component, component_name), add_edge_archetype, ); @@ -642,10 +642,10 @@ impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage, '_> let mut add_edge_archetype_comps = archetype.component_ids_sorted().collect::<Vec<_>>(); - add_edge_archetype_comps.insert_at_partition_point_by_key( - add_edge_component_id, - |comp_id| *comp_id, - ); + add_edge_archetype_comps + .insert_at_part_pt_by_key(add_edge_component_id, |comp_id| { + comp_id + }); self.storage.imaginary_archetypes.borrow_mut().push( ImaginaryArchetype { @@ -712,8 +712,7 @@ impl ArchetypeRefIter<'_, '_> let mut add_edge_comp_ids = imaginary_archetype_comps.to_vec(); - add_edge_comp_ids - .insert_at_partition_point_by_key(unique_comp_id, |id| *id); + add_edge_comp_ids.insert_at_part_pt_by_key(unique_comp_id, |id| id); let add_edge = ArchetypeId::new(&add_edge_comp_ids); diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs index a88e0e8..d96632e 100644 --- a/ecs/src/component/storage/archetype.rs +++ b/ecs/src/component/storage/archetype.rs @@ -122,7 +122,7 @@ impl Archetype pub fn get_matching_component_indices( &self, component_id: Uid, - ) -> MatchingComponentIter + ) -> MatchingComponentIter<'_> { assert!( component_id.kind() == UidKind::Component @@ -314,29 +314,18 @@ impl Entity #[derive(Debug)] pub struct EntityComponent { - id: Uid, component: Lock<Box<dyn Any>>, } impl EntityComponent { - pub fn new( - component: Box<dyn Any>, - component_id: Uid, - component_name: &'static str, - ) -> Self + pub fn new(component: Box<dyn Any>, component_name: &'static str) -> Self { Self { - id: component_id, component: Lock::new(component, component_name), } } - pub fn id(&self) -> Uid - { - self.id - } - pub fn component(&self) -> &Lock<Box<dyn Any>> { &self.component @@ -370,11 +359,10 @@ impl Id } for comp_id in component_id_iter { - if prev_component_id.is_some_and(|prev_comp_id| *comp_id < prev_comp_id) { - panic!( - "Cannot create archetype ID from a unsorted component metadata list" - ); - } + assert!( + prev_component_id.is_none_or(|prev_comp_id| *comp_id >= prev_comp_id), + "Cannot create archetype ID from a unsorted component metadata list" + ); prev_component_id = Some(*comp_id); diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs index 29fa937..76200f9 100644 --- a/ecs/src/component/storage/graph.rs +++ b/ecs/src/component/storage/graph.rs @@ -80,7 +80,7 @@ impl Graph pub fn dfs_archetype_add_edges( &self, archetype_id: ArchetypeId, - ) -> Option<ArchetypeAddEdgeDfsIter> + ) -> Option<ArchetypeAddEdgeDfsIter<'_>> { let node = self.get_node_by_id(archetype_id)?; diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs index 562f7ea..ad9f179 100644 --- a/ecs/src/entity.rs +++ b/ecs/src/entity.rs @@ -1,6 +1,6 @@ use std::any::type_name; - -use linkme::distributed_slice; +use std::ops::Deref; +use std::sync::LazyLock; use crate::component::storage::archetype::{ Archetype, @@ -10,19 +10,26 @@ use crate::component::storage::archetype::{ use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; +use crate::pair::{ + ComponentOrWildcard, + MultipleWithWildcard as PairMultipleWithWildcard, + Pair, + WithWildcard as PairWithWildcard, +}; use crate::uid::{Kind as UidKind, Uid}; use crate::{EntityComponentRef, World}; +pub mod obtainer; + /// A handle to a entity. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Handle<'a> { - world: &'a World, archetype: &'a Archetype, entity: &'a ArchetypeEntity, + world: &'a World, } impl<'a> Handle<'a> @@ -43,20 +50,21 @@ impl<'a> Handle<'a> /// - The component's ID is not a component ID /// - The component is mutably borrowed elsewhere #[must_use] - pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'_, ComponentT>> + pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'a, ComponentT>> { assert_eq!(ComponentT::id().kind(), UidKind::Component); let component = self.get_matching_components(ComponentT::id()).next()?; Some( - ComponentHandle::from_entity_component_ref(Some(component), self.world) - .unwrap_or_else(|err| { + ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( + |err| { panic!( - "Taking component {} lock failed: {err}", + "Creating handle to component {} failed: {err}", type_name::<ComponentT>() ); - }), + }, + ), ) } @@ -70,23 +78,118 @@ impl<'a> Handle<'a> #[must_use] pub fn get_mut<ComponentT: Component>( &self, - ) -> Option<ComponentHandleMut<'_, ComponentT>> + ) -> Option<ComponentHandleMut<'a, ComponentT>> { assert_eq!(ComponentT::id().kind(), UidKind::Component); let component = self.get_matching_components(ComponentT::id()).next()?; Some( - ComponentHandleMut::from_entity_component_ref(Some(component), self.world) + ComponentHandleMut::from_entity_component_ref(&component, self.world) .unwrap_or_else(|err| { panic!( - "Taking component {} lock failed: {err}", + "Creating handle to component {} failed: {err}", type_name::<ComponentT>() ); }), ) } + /// Returns a reference to the component with the ID `id` in this entity. + /// `None` is returned if the component isn't found. + /// + /// # Panics + /// Will panic if: + /// - The ID is not a component/pair ID + /// - The component is borrowed mutably elsewhere + /// - The component type is incorrect + #[must_use] + pub fn get_with_id<ComponentDataT: 'static>( + &self, + id: Uid, + ) -> Option<ComponentHandle<'a, ComponentDataT>> + { + assert!( + matches!(id.kind(), UidKind::Component | UidKind::Pair), + "ID {id:?} is not a component/pair ID" + ); + + let component = self.get_matching_components(id).next()?; + + Some( + ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( + |err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentDataT>() + ); + }, + ), + ) + } + + /// Returns a mutable reference to the component with the ID `id` in this entity. + /// `None` is returned if the component isn't found. + /// + /// # Panics + /// Will panic if: + /// - The ID is not a component/pair ID + /// - The component is borrowed elsewhere + /// - The component type is incorrect + #[must_use] + pub fn get_with_id_mut<ComponentDataT: 'static>( + &self, + id: Uid, + ) -> Option<ComponentHandleMut<'a, ComponentDataT>> + { + assert!( + matches!(id.kind(), UidKind::Component | UidKind::Pair), + "ID {id:?} is not a component/pair ID" + ); + + let component = self.get_matching_components(id).next()?; + + Some( + ComponentHandleMut::from_entity_component_ref(&component, self.world) + .unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentDataT>() + ); + }), + ) + } + + #[must_use] + pub fn get_first_wildcard_pair_match<Relation, Target>( + &self, + ) -> Option<PairWithWildcard<'a, Relation, Target>> + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + let mut matching_comps = self.get_matching_components( + Pair::builder() + .relation_id(Relation::uid()) + .target_id(Target::uid()) + .build() + .id(), + ); + + Some(PairWithWildcard::new(self.world, matching_comps.next()?)) + } + + #[must_use] + pub fn get_wildcard_pair_matches<Relation, Target>( + &self, + ) -> PairMultipleWithWildcard<'a, Relation, Target> + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + PairMultipleWithWildcard::new(self.world, self.clone()) + } + #[inline] #[must_use] pub fn get_matching_components(&self, component_uid: Uid) @@ -98,13 +201,27 @@ impl<'a> Handle<'a> } } + /// Returns whether or not this entity contains a component with the specified `Uid`. + #[must_use] + pub fn has_component(&self, component_uid: Uid) -> bool + { + self.archetype + .contains_component_with_exact_id(component_uid) + } + + /// Returns the `Uids`s of the components this entity has. + pub fn component_ids(&self) -> impl Iterator<Item = Uid> + '_ + { + self.archetype.component_ids_sorted() + } + pub(crate) fn new( - world: &'a World, archetype: &'a Archetype, entity: &'a ArchetypeEntity, + world: &'a World, ) -> Self { - Self { world, archetype, entity } + Self { archetype, entity, world } } } @@ -126,35 +243,53 @@ impl<'a> Iterator for MatchingComponentIter<'a> Some(EntityComponentRef::new( matching_component_id, self.entity.components().get(index).unwrap(), + self.entity.uid(), )) } } +/// The data type of a declaration of a entity. +#[derive(Debug)] +pub struct Declaration +{ + uid: LazyLock<Uid>, + create_func: fn(&mut World), +} + +impl Declaration +{ + pub(crate) fn create(&self, world: &mut World) + { + (self.create_func)(world); + } + + #[doc(hidden)] + pub const fn new(create_func: fn(&mut World)) -> Self + { + Self { + uid: LazyLock::new(|| Uid::new_unique(UidKind::Entity)), + create_func, + } + } +} + +impl Deref for Declaration +{ + type Target = Uid; + + fn deref(&self) -> &Self::Target + { + &self.uid + } +} + #[allow(clippy::module_name_repetitions)] #[macro_export] -macro_rules! static_entity { +macro_rules! declare_entity { ($visibility: vis $ident: ident, $components: expr) => { - $visibility static $ident: ::std::sync::LazyLock<$crate::uid::Uid> = - ::std::sync::LazyLock::new(|| { - $crate::uid::Uid::new_unique($crate::uid::Kind::Entity) + $visibility static $ident: $crate::entity::Declaration = + $crate::entity::Declaration::new(|world| { + world.create_entity_with_uid(*$ident, $components); }); - - $crate::private::paste::paste! { - mod [<__ecs_ $ident:lower _static_entity_priv>] { - use super::*; - - #[$crate::private::linkme::distributed_slice( - $crate::entity::CREATE_STATIC_ENTITIES - )] - #[linkme(crate=$crate::private::linkme)] - static CREATE_STATIC_ENTITY: fn(&mut $crate::World) = |world| { - world.create_entity_with_uid($components, *$ident); - }; - } - } } } - -#[distributed_slice] -#[doc(hidden)] -pub static CREATE_STATIC_ENTITIES: [fn(&mut World)]; diff --git a/ecs/src/entity/obtainer.rs b/ecs/src/entity/obtainer.rs new file mode 100644 index 0000000..6c2ea96 --- /dev/null +++ b/ecs/src/entity/obtainer.rs @@ -0,0 +1,29 @@ +use crate::entity::Handle as EntityHandle; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; +use crate::uid::Uid; +use crate::World; + +#[derive(Debug)] +pub struct Obtainer<'world> +{ + world: &'world World, +} + +impl<'world> SystemParam<'world> for Obtainer<'world> +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + Self { world } + } +} + +impl Obtainer<'_> +{ + #[must_use] + pub fn get_entity(&self, entity_id: Uid) -> Option<EntityHandle<'_>> + { + self.world.get_entity(entity_id) + } +} diff --git a/ecs/src/event.rs b/ecs/src/event.rs index 9cea807..15455b6 100644 --- a/ecs/src/event.rs +++ b/ecs/src/event.rs @@ -1 +1,105 @@ +use crate::lock::Lock; +use crate::pair::Pair; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::VecExt; + pub mod component; + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Emitted<'a> +{ + pub event: Uid, + pub match_ids: &'a [Uid], +} + +#[derive(Debug)] +pub struct Submitter<'world> +{ + new_events: &'world Lock<NewEvents>, +} + +impl<'world> Submitter<'world> +{ + /// Submits a event to be handled later. + /// + /// # Panics + /// Will panic if unable to acquire a read-write lock to the event store. + pub fn submit_event(&self, event: &Pair<Uid, Uid>, match_id: Uid) + { + let mut new_events_lock = self + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events"); + + new_events_lock.push_event_match(event, match_id); + } + + pub(crate) fn new(new_events: &'world Lock<NewEvents>) -> Self + { + Self { new_events } + } +} + +#[derive(Debug, Default)] +pub(crate) struct NewEvents +{ + events: Vec<(Uid, Matches)>, +} + +impl NewEvents +{ + pub fn push_event_match(&mut self, event: &Pair<Uid, Uid>, match_id: Uid) + { + let event_id = event.id(); + + assert_eq!(event_id.kind(), UidKind::Pair); + + if let Ok(event_index) = self + .events + .binary_search_by_key(&event_id, |(other_event_id, _)| *other_event_id) + { + let Some((_, matches)) = self.events.get_mut(event_index) else { + unreachable!(); + }; + + matches.sorted_push(match_id); + + return; + } + + self.events.insert_at_part_pt_by_key( + (event_id, Matches { match_ids: Vec::from([match_id]) }), + |(other_event_id, _)| other_event_id, + ); + } + + pub fn take(&mut self) -> Vec<(Uid, Matches)> + { + std::mem::take(&mut self.events) + } + + pub fn is_empty(&self) -> bool + { + self.events.is_empty() + } +} + +#[derive(Debug)] +pub(crate) struct Matches +{ + pub match_ids: Vec<Uid>, +} + +impl Matches +{ + fn sorted_push(&mut self, match_id: Uid) + { + if self.match_ids.binary_search(&match_id).is_ok() { + return; + } + + self.match_ids + .insert_at_part_pt_by_key(match_id, |other_match_id| other_match_id); + } +} diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs index ef09480..ed6b7cf 100644 --- a/ecs/src/event/component.rs +++ b/ecs/src/event/component.rs @@ -1,8 +1,12 @@ //! Component events. use std::convert::Infallible; -use std::fmt::Debug; +use crate::component::{Handle as ComponentHandle, HandleMut as ComponentHandleMut}; +use crate::entity::Handle as EntityHandle; +use crate::pair::Pair; +use crate::system::observer::EventMatch; +use crate::util::impl_multiple; use crate::Component; /// Pair relation for events emitted when: @@ -11,8 +15,57 @@ use crate::Component; #[derive(Debug, Component)] pub struct Added(Infallible); -/// Pair relation for events emitted when: +/// Pair relation for events emitted **before**: /// a) The target component is removed from a entity. /// b) A entity with the target component is despawned. #[derive(Debug, Component)] pub struct Removed(Infallible); + +#[derive(Debug, Component)] +pub struct Changed(Infallible); + +impl_multiple!( + EventMatch, + ( + impl<Target: Component> _<'_><Pair<Removed, Target>> (removed), + impl<Target: Component> _<'_><Pair<Added, Target>> (added), + impl<Target: Component> _<'_><Pair<Changed, Target>> (changed) + ) + cb=(type_params=(observable_type), event_name) => { + paste::paste! { + #[must_use] + pub fn [<get_ $event_name _comp>](&self) -> ComponentHandle<'_, Target> + { + let ent = self.get_ent_infallible(); + + let Some(comp) = ent.get::<Target>() else { + unreachable!(); + }; + + comp + } + + #[must_use] + pub fn [<get_ $event_name _comp_mut>](&self) -> ComponentHandleMut<'_, Target> + { + let ent = self.get_ent_infallible(); + + let Some(comp) = ent.get_mut::<Target>() else { + unreachable!(); + }; + + comp + } + } + + #[must_use] + pub fn get_ent_infallible(&self) -> EntityHandle<'_> + { + let Some(ent) = self.get_entity() else { + unreachable!(); + }; + + ent + } + } +); diff --git a/ecs/src/extension.rs b/ecs/src/extension.rs index 42ebef9..9c6614b 100644 --- a/ecs/src/extension.rs +++ b/ecs/src/extension.rs @@ -1,5 +1,7 @@ use crate::component::Sequence as ComponentSequence; +use crate::entity::Declaration as EntityDeclaration; use crate::sole::Sole; +use crate::system::observer::Observer; use crate::system::System; use crate::uid::Uid; use crate::{SoleAlreadyExistsError, World}; @@ -34,6 +36,15 @@ impl<'world> Collector<'world> self.world.register_system(phase_euid, system); } + /// Adds a observer system to the [`World`]. + pub fn add_observer<'this, SystemImpl>( + &'this mut self, + observer: impl Observer<'this, SystemImpl>, + ) + { + self.world.register_observer(observer); + } + /// Adds a entity to the [`World`]. pub fn add_entity<Comps>(&mut self, components: Comps) where @@ -42,6 +53,12 @@ impl<'world> Collector<'world> self.world.create_entity(components); } + /// Adds a declared entity to the [`World`]. + pub fn add_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + self.world.create_declared_entity(entity_decl); + } + /// Adds a globally shared singleton value to the [`World`]. /// /// # Errors diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 53abc6b..f6fba64 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -1,7 +1,6 @@ #![deny(clippy::all, clippy::pedantic)] use std::any::{type_name, Any, TypeId}; -use std::cell::RefCell; use std::fmt::Debug; use std::mem::ManuallyDrop; use std::rc::Rc; @@ -15,31 +14,36 @@ use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComp use crate::component::storage::Storage as ComponentStorage; use crate::component::{ Component, + IntoParts as IntoComponentParts, Parts as ComponentParts, Sequence as ComponentSequence, }; -use crate::entity::CREATE_STATIC_ENTITIES; -use crate::event::component::{ - Added as ComponentAddedEvent, - Removed as ComponentRemovedEvent, -}; +use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; +use crate::event::component::Added; +use crate::event::{Emitted as EmittedEvent, NewEvents, Submitter as EventSubmitter}; use crate::extension::{Collector as ExtensionCollector, Extension}; use crate::lock::Lock; use crate::pair::{ChildOf, DependsOn, Pair}; -use crate::phase::{Phase, START as START_PHASE}; +use crate::phase::{ + Phase, + POST_UPDATE as POST_UPDATE_PHASE, + PRE_UPDATE as PRE_UPDATE_PHASE, + START as START_PHASE, + UPDATE as UPDATE_PHASE, +}; use crate::query::flexible::Query as FlexibleQuery; -use crate::query::term::Without; use crate::query::{ - Iter as QueryIter, TermWithFieldTuple as QueryTermWithFieldTuple, TermWithoutFieldTuple as QueryTermWithoutFieldTuple, Terms as QueryTerms, TermsBuilderInterface, + MAX_TERM_CNT as QUERY_MAX_TERM_CNT, }; -use crate::sole::Sole; +use crate::sole::{Single, Sole}; use crate::stats::Stats; -use crate::system::{System, SystemComponent}; -use crate::uid::{Kind as UidKind, Uid, Wildcard}; +use crate::system::observer::{Observer, WrapperComponent as ObserverWrapperComponent}; +use crate::system::{Callbacks, Metadata as SystemMetadata, System, SystemComponent}; +use crate::uid::{Kind as UidKind, Uid}; pub mod actions; pub mod component; @@ -56,9 +60,6 @@ pub mod tuple; pub mod uid; pub mod util; -#[doc(hidden)] -pub mod private; - mod lock; pub use ecs_macros::{Component, Sole}; @@ -84,49 +85,49 @@ impl World is_first_tick: AtomicBool::new(false), }; - world.add_sole(Stats::default()).ok(); + crate::phase::spawn_entities(&mut world); - for create_static_entity in CREATE_STATIC_ENTITIES { - create_static_entity(&mut world); - } + world.add_sole(Stats::default()).ok(); world } - /// Creates a new entity with the given components. + /// Creates a entity with the given components. A new unique [`Uid`] will be generated + /// for this entity. pub fn create_entity<Comps>(&mut self, components: Comps) -> Uid where Comps: ComponentSequence, { let entity_uid = Uid::new_unique(UidKind::Entity); - self.create_entity_with_uid(components, entity_uid); + self.create_entity_with_uid(entity_uid, components); entity_uid } + /// Creates a entity with the given components. The entity will have the specified + /// [`Uid`]. #[tracing::instrument(skip_all)] - #[doc(hidden)] - pub fn create_entity_with_uid<Comps>(&mut self, components: Comps, entity_uid: Uid) + pub fn create_entity_with_uid<Comps>(&mut self, entity_uid: Uid, components: Comps) where Comps: ComponentSequence, { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - if let Err(err) = self.data.component_storage.create_entity(entity_uid) { - tracing::warn!("Failed to create entity: {err}"); - return; - } + self.create_ent(entity_uid, components.into_parts_array()); + } - let added_component_ids = Self::add_entity_components( - entity_uid, - components.into_parts_array(), + pub fn add_component(&mut self, entity_id: Uid, component_parts: ComponentParts) + { + Self::add_entity_components( + entity_id, + [component_parts], &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), ); + } - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } + pub fn create_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + entity_decl.create(self); } /// Adds a globally shared singleton value. @@ -140,28 +141,45 @@ impl World self.data.sole_storage.insert(sole) } - pub fn register_system<'this, SystemImpl>( + pub fn register_observer<'this, SystemImpl, ObserverT>( &'this mut self, - phase_euid: Uid, - system: impl System<'this, SystemImpl>, - ) + observer: ObserverT, + ) where + ObserverT: Observer<'this, SystemImpl>, { - self.create_entity(( - SystemComponent { system: system.into_type_erased() }, - Pair::new::<DependsOn>(phase_euid), - )); + let (wrapper_comp, mut system_callbacks) = observer.finish_observer(); + + let ent_id = Uid::new_unique(UidKind::Entity); + + self.create_ent( + ent_id, + [wrapper_comp.into_parts()].into_iter().chain( + ObserverT::observed_events() + .into_iter() + .map(IntoComponentParts::into_parts), + ), + ); + + system_callbacks.on_created(self, SystemMetadata { ent_id }); } - pub fn register_observer_system<'this, SystemImpl>( + pub fn register_system<'this, SystemImpl>( &'this mut self, + phase_euid: Uid, system: impl System<'this, SystemImpl>, - event: Pair<Uid, Uid>, ) { - self.create_entity(( - SystemComponent { system: system.into_type_erased() }, - event, + let (type_erased_system, mut system_callbacks) = system.finish(); + + let system_ent_id = self.create_entity(( + SystemComponent { system: type_erased_system }, + Pair::builder() + .relation::<DependsOn>() + .target_id(phase_euid) + .build(), )); + + system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id }); } /// Adds a extensions. @@ -172,7 +190,9 @@ impl World extension.collect(extension_collector); } - pub fn query<FieldTerms, FieldlessTerms>(&self) -> Query<FieldTerms, FieldlessTerms> + pub fn query<FieldTerms, FieldlessTerms>( + &self, + ) -> Query<'_, FieldTerms, FieldlessTerms> where FieldTerms: QueryTermWithFieldTuple, FieldlessTerms: QueryTermWithoutFieldTuple, @@ -188,6 +208,30 @@ impl World FlexibleQuery::new(self, terms) } + pub fn get_entity(&self, entity_id: Uid) -> Option<EntityHandle<'_>> + { + let archetype = self + .data + .component_storage + .get_entity_archetype(entity_id)?; + + let Some(entity) = archetype.get_entity_by_id(entity_id) else { + unreachable!("Should exist since archetype was found by entity id"); + }; + + Some(EntityHandle::new(archetype, entity, self)) + } + + pub fn get_sole<SoleT: Sole>(&self) -> Option<Single<'_, SoleT>> + { + Some(Single::new(self.data.sole_storage.get::<SoleT>()?)) + } + + pub fn event_submitter(&self) -> EventSubmitter<'_> + { + EventSubmitter::new(&self.data.new_events) + } + /// Performs a single tick. /// # Panics /// Will panic if mutable internal lock cannot be acquired. @@ -207,6 +251,8 @@ impl World self.perform_phases(); + self.emit_new_events(); + self.data.component_storage.create_imaginary_archetypes(); self.perform_queued_actions(); @@ -215,17 +261,9 @@ impl World return StepResult::Stop; } - let mut stats_lock = self - .data - .sole_storage - .get::<Stats>() - .expect("No stats sole found") - .write_nonblock() - .expect("Failed to aquire read-write stats sole lock"); - - let stats = stats_lock - .downcast_mut::<Stats>() - .expect("Casting stats sole to Stats type failed"); + let Some(mut stats) = self.get_sole::<Stats>() else { + unreachable!(); // Reason: is added in World::new + }; stats.current_tick += 1; @@ -285,23 +323,51 @@ impl World ) } + #[tracing::instrument(skip_all)] + fn create_ent( + &mut self, + entity_uid: Uid, + components: impl IntoIterator<Item = ComponentParts>, + ) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + if let Err(err) = self.data.component_storage.create_entity(entity_uid) { + tracing::warn!("Failed to create entity: {err}"); + return; + } + + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + fn query_and_run_systems(&self, phase_euid: Uid) { - let system_query = self.flexible_query( - QueryTerms::<2>::builder() - .with_required([ - SystemComponent::id(), - Pair::new::<DependsOn>(phase_euid).id(), - ]) - .build(), + let system_query = Query::<(&SystemComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::<QUERY_MAX_TERM_CNT>::builder() + .with_required([ + SystemComponent::id(), + Pair::builder() + .relation::<DependsOn>() + .target_id(phase_euid) + .build() + .id(), + ]) + .build(), + ), ); - for (system_component,) in - QueryIter::<(&SystemComponent,), _>::new(self, system_query.iter()) - { + for (system_ent_id, (system_component,)) in system_query.iter_with_euids() { // SAFETY: The world lives long enough unsafe { - system_component.system.run(self); + system_component + .system + .run(self, SystemMetadata { ent_id: system_ent_id }); } } } @@ -312,7 +378,11 @@ impl World QueryTerms::<2>::builder() .with_required([ Phase::id(), - Pair::new::<ChildOf>(parent_phase_euid).id(), + Pair::builder() + .relation::<ChildOf>() + .target_id(parent_phase_euid) + .build() + .id(), ]) .build(), ); @@ -323,43 +393,63 @@ impl World } } + fn perform_single_phase(&self, phase_entity_id: Uid) + { + self.query_and_run_systems(phase_entity_id); + self.perform_child_phases(phase_entity_id); + } + fn perform_phases(&self) { - let phase_query = self.query::<(&Phase,), (Without<Pair<ChildOf, Wildcard>>,)>(); + self.perform_single_phase(*PRE_UPDATE_PHASE); + self.perform_single_phase(*UPDATE_PHASE); + self.perform_single_phase(*POST_UPDATE_PHASE); + } - for (phase_entity_id, _) in phase_query.iter_with_euids() { - if phase_entity_id == *START_PHASE { - continue; - } + fn emit_new_events(&self) + { + loop { + let new_events = { + let mut new_events_lock = self + .data + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events"); + + if new_events_lock.is_empty() { + break; + } - self.query_and_run_systems(phase_entity_id); - self.perform_child_phases(phase_entity_id); + new_events_lock.take() + }; + + for (event_id, event_matches) in new_events { + self.emit_event_observers( + event_id, + &EmittedEvent { + event: event_id, + match_ids: &event_matches.match_ids, + }, + ); + } } } #[tracing::instrument(skip_all)] fn perform_queued_actions(&mut self) { - let mut active_action_queue = match *self.data.action_queue.active_queue.borrow() - { - ActiveActionQueue::A => &self.data.action_queue.queue_a, - ActiveActionQueue::B => &self.data.action_queue.queue_b, - } - .write_nonblock() - .unwrap_or_else(|err| { - panic!( - "Failed to take read-write action queue lock {:?}: {err}", - self.data.action_queue.active_queue - ); - }); - - let mut has_swapped_active_queue = false; + let mut action_queue_lock = self + .data + .action_queue + .queue + .write_nonblock() + .unwrap_or_else(|err| { + panic!("Failed to take read-write action queue lock: {err}",); + }); - for action in active_action_queue.drain(..) { + for action in action_queue_lock.drain(..) { match action { - Action::Spawn(components) => { - let new_entity_uid = Uid::new_unique(UidKind::Entity); - + Action::Spawn(new_entity_uid, components) => { if let Err(err) = self.data.component_storage.create_entity(new_entity_uid) { @@ -367,69 +457,34 @@ impl World continue; } - let added_component_ids = Self::add_entity_components( + Self::add_entity_components( new_entity_uid, components, &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } } Action::Despawn(entity_uid) => { - let removed_entity = - match self.data.component_storage.remove_entity(entity_uid) { - Ok(components) => components, - Err(err) => { - tracing::error!("Failed to despawn entity: {err}"); - return; - } - }; - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for removed_ent_comp in removed_entity.components() { - self.emit_event_by_id::<ComponentRemovedEvent>( - removed_ent_comp.id(), - ); + if let Err(err) = + self.data.component_storage.remove_entity(entity_uid) + { + tracing::error!("Failed to despawn entity: {err}"); } } Action::AddComponents(entity_uid, components) => { - let added_component_ids = Self::add_entity_components( + Self::add_entity_components( entity_uid, components, &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } } Action::RemoveComponents(entity_uid, component_ids) => { - let removed_component_ids = Self::remove_entity_components( + Self::remove_entity_components( entity_uid, component_ids, &mut self.data.component_storage, ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in removed_component_ids { - self.emit_event_by_id::<ComponentRemovedEvent>(comp_id); - } } Action::Stop => { self.stop.store(true, Ordering::Relaxed); @@ -442,85 +497,77 @@ impl World entity_uid: Uid, components: impl IntoIterator<Item = ComponentParts>, component_storage: &mut ComponentStorage, - ) -> Vec<Uid> + event_submitter: &EventSubmitter<'_>, + ) { let component_iter = components.into_iter(); - let mut added_component_ids = - Vec::<Uid>::with_capacity(component_iter.size_hint().0); - for component_parts in component_iter { let comp_id = component_parts.id(); + let comp_name = component_parts.name(); + if let Err(err) = component_storage.add_entity_component( entity_uid, - (comp_id, component_parts.name(), component_parts.into_data()), + (comp_id, comp_name, component_parts.into_data()), ) { - tracing::error!("Failed to add component to entity: {err}"); + tracing::error!("Failed to add component {comp_name} to entity: {err}"); continue; } - added_component_ids.push(comp_id); - } + if comp_id.kind() == UidKind::Pair { + continue; + } - added_component_ids + event_submitter.submit_event( + &Pair::builder() + .relation::<Added>() + .target_id(comp_id) + .build(), + entity_uid, + ); + } } fn remove_entity_components( entity_uid: Uid, component_ids: impl IntoIterator<Item = Uid>, component_storage: &mut ComponentStorage, - ) -> Vec<Uid> + ) { let component_id_iter = component_ids.into_iter(); - let mut removed_component_ids = - Vec::<Uid>::with_capacity(component_id_iter.size_hint().0); - for component_id in component_id_iter { if let Err(err) = component_storage.remove_entity_component(entity_uid, component_id) { tracing::error!("Failed to remove component to entity: {err}"); - continue; } - - removed_component_ids.push(component_id); } - - removed_component_ids } - fn emit_event_by_id<Event: Component>(&self, target: Uid) + fn emit_event_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>) { - if target.kind() == UidKind::Pair { - return; - } - - let query = self.flexible_query( - QueryTerms::<2>::builder() - .with_required([SystemComponent::id(), Pair::new::<Event>(target).id()]) - .build(), + assert_eq!(event_id.kind(), UidKind::Pair); + + let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::<QUERY_MAX_TERM_CNT>::builder() + .with_required([ObserverWrapperComponent::id(), event_id]) + .build(), + ), ); - for (system,) in QueryIter::<(&SystemComponent,), _>::new(self, query.iter()) { + for (observer_ent_id, (observer,)) in query.iter_with_euids() { unsafe { - system.system.run(self); + observer.run( + self, + SystemMetadata { ent_id: observer_ent_id }, + emitted_event.clone(), + ); } } } - - fn swap_event_queue(&self, has_swapped_active_queue: &mut bool) - { - let mut active_queue = self.data.action_queue.active_queue.borrow_mut(); - - *active_queue = match *active_queue { - ActiveActionQueue::A => ActiveActionQueue::B, - ActiveActionQueue::B => ActiveActionQueue::A, - }; - - *has_swapped_active_queue = true; - } } impl Default for World @@ -541,31 +588,21 @@ pub enum StepResult Stop, } -#[derive(Debug)] -pub struct WorldData +#[derive(Debug, Default)] +struct WorldData { component_storage: ComponentStorage, sole_storage: SoleStorage, action_queue: Rc<ActionQueue>, + new_events: Lock<NewEvents>, } -impl Default for WorldData -{ - fn default() -> Self - { - Self { - component_storage: ComponentStorage::default(), - sole_storage: SoleStorage::default(), - action_queue: Rc::new(ActionQueue::default()), - } - } -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EntityComponentRef<'a> { component_id: Uid, component: &'a ArchetypeEntityComponent, + entity_id: Uid, } impl<'a> EntityComponentRef<'a> @@ -581,56 +618,37 @@ impl<'a> EntityComponentRef<'a> self.component_id } - fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self + #[must_use] + pub fn entity_id(&self) -> Uid { - Self { component_id, component: comp } + self.entity_id } -} -#[derive(Debug, Default, Clone, Copy)] -enum ActiveActionQueue -{ - #[default] - A, - B, + fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid) + -> Self + { + Self { + component_id, + component: comp, + entity_id, + } + } } -#[derive(Debug)] +#[derive(Debug, Default)] struct ActionQueue { - queue_a: Lock<Vec<Action>>, - queue_b: Lock<Vec<Action>>, - active_queue: RefCell<ActiveActionQueue>, + queue: Lock<Vec<Action>>, } impl ActionQueue { fn push(&self, action: Action) { - match *self.active_queue.borrow() { - ActiveActionQueue::A => self - .queue_a - .write_nonblock() - .expect("Failed to aquire read-write action queue A lock") - .push(action), - ActiveActionQueue::B => self - .queue_b - .write_nonblock() - .expect("Failed to aquire read-write action queue A lock") - .push(action), - } - } -} - -impl Default for ActionQueue -{ - fn default() -> Self - { - Self { - queue_a: Lock::new(Vec::new(), type_name::<Vec<Action>>()), - queue_b: Lock::new(Vec::new(), type_name::<Vec<Action>>()), - active_queue: RefCell::new(ActiveActionQueue::default()), - } + self.queue + .write_nonblock() + .expect("Failed to aquire read-write lock to action queue") + .push(action); } } diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs index 0b36922..fe4e08b 100644 --- a/ecs/src/lock.rs +++ b/ecs/src/lock.rs @@ -1,3 +1,4 @@ +use std::any::type_name; use std::mem::forget; use std::ops::{Deref, DerefMut}; @@ -30,7 +31,7 @@ impl<Value> Lock<Value> /// /// # Errors /// Returns `Err` if unavailable (A mutable handle is hold). - pub fn read_nonblock(&self) -> Result<ReadGuard<Value>, Error> + pub fn read_nonblock(&self) -> Result<ReadGuard<'_, Value>, Error> { let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?; @@ -46,7 +47,7 @@ impl<Value> Lock<Value> /// /// # Errors /// Returns `Err` if unavailable (A mutable or immutable handle is hold). - pub fn write_nonblock(&self) -> Result<WriteGuard<Value>, Error> + pub fn write_nonblock(&self) -> Result<WriteGuard<'_, Value>, Error> { let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?; @@ -62,6 +63,14 @@ impl<Value> Lock<Value> } } +impl<Value: Default + 'static> Default for Lock<Value> +{ + fn default() -> Self + { + Self::new(Value::default(), type_name::<Value>()) + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { @@ -81,21 +90,26 @@ pub struct ReadGuard<'guard, Value> impl<'guard, Value> ReadGuard<'guard, Value> { - pub fn map<NewValue>( - self, - func: impl FnOnce(&Value) -> &NewValue, - ) -> MappedReadGuard<'guard, NewValue> + pub fn try_map<NewValue>( + this: Self, + func: impl FnOnce(&Value) -> Option<&NewValue>, + ) -> Result<MappedReadGuard<'guard, NewValue>, Self> { - let value_type_name = self.value_type_name; + let value_type_name = this.value_type_name; // The 'inner' field cannot be moved out of ReadGuard in a normal way since // ReadGuard implements Drop - let inner = unsafe { std::ptr::read(&self.inner) }; - forget(self); - - MappedReadGuard { - inner: RwLockReadGuard::map(inner, func), - value_type_name, + let inner = unsafe { std::ptr::read(&raw const this.inner) }; + forget(this); + + match RwLockReadGuard::try_map(inner, func) { + Ok(mapped_guard) => { + Ok(MappedReadGuard { inner: mapped_guard, value_type_name }) + } + Err(unmapped_guard) => Err(Self { + inner: unmapped_guard, + value_type_name, + }), } } } @@ -155,21 +169,26 @@ pub struct WriteGuard<'guard, Value> impl<'guard, Value> WriteGuard<'guard, Value> { - pub fn map<NewValue>( - self, - func: impl FnOnce(&mut Value) -> &mut NewValue, - ) -> MappedWriteGuard<'guard, NewValue> + pub fn try_map<NewValue>( + this: Self, + func: impl FnOnce(&mut Value) -> Option<&mut NewValue>, + ) -> Result<MappedWriteGuard<'guard, NewValue>, Self> { - let value_type_name = self.value_type_name; + let value_type_name = this.value_type_name; // The 'inner' field cannot be moved out of ReadGuard in a normal way since // ReadGuard implements Drop - let inner = unsafe { std::ptr::read(&self.inner) }; - forget(self); - - MappedWriteGuard { - inner: RwLockWriteGuard::map(inner, func), - value_type_name, + let inner = unsafe { std::ptr::read(&raw const this.inner) }; + forget(this); + + match RwLockWriteGuard::try_map(inner, func) { + Ok(mapped_guard) => { + Ok(MappedWriteGuard { inner: mapped_guard, value_type_name }) + } + Err(unmapped_guard) => Err(Self { + inner: unmapped_guard, + value_type_name, + }), } } } diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs index 2055d5e..b4bfa57 100644 --- a/ecs/src/pair.rs +++ b/ecs/src/pair.rs @@ -1,6 +1,14 @@ +use std::any::type_name; use std::convert::Infallible; +use std::marker::PhantomData; -use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts}; +use crate::component::{ + Handle as ComponentHandle, + HandleError as ComponentHandleError, + HandleMut as ComponentHandleMut, + IntoParts as IntoComponentParts, + Parts as ComponentParts, +}; use crate::entity::{ Handle as EntityHandle, MatchingComponentIter as EntityMatchingComponentIter, @@ -10,24 +18,127 @@ use crate::query::{ TermsBuilder as QueryTermsBuilder, TermsBuilderInterface, }; -use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid}; -use crate::{Component, World}; +use crate::uid::{Kind as UidKind, PairParams as UidPairParams, Uid, With as WithUid}; +use crate::util::impl_multiple; +use crate::{Component, EntityComponentRef, World}; +/// Pair builder. #[derive(Debug)] -pub struct Pair<RelationElem: Element, TargetElem: Element> +pub struct Builder<Relation, Target> { - relation: RelationElem, - target: TargetElem, + relation: Relation, + target: Target, } -impl Pair<Uid, Uid> +impl<Relation, Target> Builder<Relation, Target> +{ + pub fn relation<NewRelation: Component>(self) -> Builder<Uid, Target> + { + Builder { + relation: NewRelation::id(), + target: self.target, + } + } + + pub fn relation_id(self, id: Uid) -> Builder<Uid, Target> + { + Builder { relation: id, target: self.target } + } + + pub fn target<NewTarget: Component>(self) -> Builder<Relation, Uid> + { + Builder { + relation: self.relation, + target: NewTarget::id(), + } + } + + pub fn target_id(self, id: Uid) -> Builder<Relation, Uid> + { + Builder { relation: self.relation, target: id } + } +} + +impl_multiple!( + Builder, + (impl<Target> _<><Uid, Target>, impl<Target> _<><(), Target>) + cb=(type_params=(ty_param_1, ty_param_2)) => { + pub fn target_as_data<NewTarget: Component>( + self, + data: NewTarget, + ) -> Builder<$ty_param_1, NewTarget> + { + Builder { + relation: self.relation, + target: data, + } + } + } +); + +impl_multiple!( + Builder, + (impl<Relation> _<><Relation, Uid>, impl<Relation> _<><Relation, ()>) + cb=(type_params=(ty_param_1, ty_param_2)) => { + pub fn relation_as_data<NewRelation: Component>( + self, + data: NewRelation, + ) -> Builder<NewRelation, $ty_param_2> + { + Builder { + relation: data, + target: self.target, + } + } + } +); + +impl_multiple!( + Builder, + ( + impl _<><Uid, Uid>, + impl<Relation: Component> _<><Relation, Uid>, + impl<Target: Component> _<><Uid, Target>, + impl<Relation: Component, Target: Component> _<><Relation, Target> + ) + cb=(type_params=(ty_param_1, ty_param_2)) => { + #[must_use] + pub fn build(self) -> Pair<$ty_param_1, $ty_param_2> + { + Pair { + relation: self.relation, + target: self.target + } + } + } +); + +impl Default for Builder<(), ()> +{ + fn default() -> Self + { + Self { relation: (), target: () } + } +} + +#[derive(Debug)] +pub struct Pair<Relation, Target> +{ + relation: Relation, + target: Target, +} + +impl Pair<(), ()> { #[must_use] - pub fn new<Relation: WithUid>(target: Uid) -> Self + pub fn builder() -> Builder<(), ()> { - Self { relation: Relation::uid(), target } + Builder { relation: (), target: () } } +} +impl Pair<Uid, Uid> +{ #[must_use] pub fn id(&self) -> Uid { @@ -46,12 +157,116 @@ impl IntoComponentParts for Pair<Uid, Uid> } } -impl<Relation, Target> QueryTermWithField for Pair<Relation, Target> +impl<Target> IntoComponentParts for Pair<Uid, Target> where - Relation: WithUid, - Target: WithUid, + Target: Component, { - type Field<'a> = Handle<'a>; + fn into_parts(self) -> ComponentParts + { + let id = Uid::new_pair(&UidPairParams { + relation: self.relation, + target: Target::id(), + }); + + ComponentParts::builder() + .name("Pair") + .build(id, self.target) + } +} + +impl<Relation> IntoComponentParts for Pair<Relation, Uid> +where + Relation: Component, +{ + fn into_parts(self) -> ComponentParts + { + let id = Uid::new_pair(&UidPairParams { + relation: Relation::id(), + target: self.target, + }); + + ComponentParts::builder() + .name("Pair") + .build(id, self.relation) + } +} + +impl<Relation, Target> QueryTermWithField for Pair<Relation, &Target> +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandle<'a, Target>; + + fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( + terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, + ) + { + terms_builder.with_required([Pair::<Relation, Target>::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Pair::<Relation, Target>::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(&target_component).unwrap_or_else(|err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::<Target>() + ); + }) + } +} + +impl<Relation, Target> QueryTermWithField for Pair<Relation, &mut Target> +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandleMut<'a, Target>; + + fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( + terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, + ) + { + terms_builder.with_required([Pair::<Relation, Target>::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Pair::<Relation, Target>::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(&target_component, world).unwrap_or_else( + |err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::<Target>() + ); + }, + ) + } +} + +// TODO: implement QueryTermWithField for Pair<&Relation, Target> (or equivalent) +// TODO: implement QueryTermWithField for Pair<&mut Relation, Target> (or equivalent) + +impl<Relation> QueryTermWithField for Pair<Relation, Wildcard> +where + Relation: Component, +{ + type Field<'a> = WithWildcard<'a, Relation, Wildcard>; fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, @@ -70,38 +285,51 @@ where .next() .expect("Not possible"); - Handle { + WithWildcard { world, - pair_uid: first_matching_comp.id(), + component_ref: first_matching_comp, + _pd: PhantomData, } } } impl<Relation, Target> WithUid for Pair<Relation, Target> where - Relation: WithUid, - Target: WithUid, + Relation: Component, + Target: Component, { fn uid() -> Uid { Uid::new_pair(&UidPairParams { - relation: Relation::uid(), - target: Target::uid(), + relation: Relation::id(), + target: Target::id(), }) } } -impl<Relation> QueryTermWithField for &'static [Pair<Relation, Wildcard>] +impl<Relation> WithUid for Pair<Relation, Wildcard> where - Relation: WithUid, + Relation: Component, { - type Field<'a> = HandleIter<'a>; + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::id(), + target: Wildcard::uid(), + }) + } +} + +impl<Relation> QueryTermWithField for &'_ [Pair<Relation, Wildcard>] +where + Relation: Component, +{ + type Field<'a> = MultipleWithWildcard<'a, Relation, Wildcard>; fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( - terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, + _terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, ) { - terms_builder.with_required([Pair::<Relation, Wildcard>::uid()]); } fn get_field<'world>( @@ -109,80 +337,304 @@ where world: &'world World, ) -> Self::Field<'world> { - HandleIter { - inner: entity_handle - .get_matching_components(Pair::<Relation, Wildcard>::uid()), + MultipleWithWildcard { + entity_handle: entity_handle.clone(), world, + _pd: PhantomData, } } } -pub struct Handle<'world> +/// Reference to a pair with a wildcard relation/target. +#[derive(Debug)] +pub struct WithWildcard<'world, Relation, Target> { world: &'world World, - pair_uid: Uid, + component_ref: EntityComponentRef<'world>, + _pd: PhantomData<(Relation, Target)>, } -impl Handle<'_> +impl<'world, Relation, Target> WithWildcard<'world, Relation, Target> { + /// Returns a new `WithWildcard`. + /// + /// # Panics + /// This function will panic if: + /// - The given component's ID is not a pair ID. + /// - `Relation::uid()` is not wildcard and does not equal to the relation of the + /// given component's ID + /// - `Target::uid()` is not wildcard and does not equal to the target of the given + /// component's ID + /// - Both `Relation::uid()` and `Target::uid()` are wildcards + /// - Neither `Relation::uid()` or `Target::uid()` are wildcards + pub fn new(world: &'world World, component_ref: EntityComponentRef<'world>) -> Self + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + let component_id = component_ref.id(); + + assert!(component_id.kind() == UidKind::Pair); + + assert!( + Relation::uid() == Wildcard::uid() + || component_id.relation_component() == Relation::uid() + ); + + assert!( + Target::uid() == Wildcard::uid() + || component_id.target_component() == Target::uid() + ); + + assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); + + assert!( + !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) + ); + + WithWildcard { + world, + component_ref, + _pd: PhantomData, + } + } + + /// Returns the [`Uid`] of the pair. + #[must_use] + pub fn id(&self) -> Uid + { + self.component_ref.id() + } + + /// Attempts to get the component data of this pair, returning `None` if the `Data` + /// type is incorrect. + /// + /// # Panics + /// Will panic if the component data is mutably borrowed elsewhere. #[must_use] - pub fn get_target_entity(&self) -> Option<EntityHandle<'_>> + pub fn get_data<Data>(&self) -> Option<ComponentHandle<'_, Data>> + where + Data: 'static, + { + ComponentHandle::<Data>::from_entity_component_ref(&self.component_ref) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::<Data>() + ); + } + }, + Some, + ) + } + + /// Attempts to get the component data of this pair, returning `None` if the `Data` + /// type is incorrect. + /// + /// # Panics + /// Will panic if the component data is borrowed elsewhere. + #[must_use] + pub fn get_data_mut<Data>(&self) -> Option<ComponentHandleMut<'_, Data>> + where + Data: 'static, + { + ComponentHandleMut::<Data>::from_entity_component_ref( + &self.component_ref, + self.world, + ) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::<Data>() + ); + } + }, + Some, + ) + } +} + +impl<Relation> WithWildcard<'_, Relation, Wildcard> +where + Relation: Component, +{ + /// Attempts to retrieve the target as a entity, returning `None` if not found. + #[must_use] + pub fn get_target_ent(&self) -> Option<EntityHandle<'_>> { let archetype = self .world .data .component_storage - .get_entity_archetype(self.pair_uid.target_entity())?; + .get_entity_archetype(self.component_ref.id().target_entity())?; let Some(archetype_entity) = - archetype.get_entity_by_id(self.pair_uid.target_entity()) + archetype.get_entity_by_id(self.component_ref.id().target_entity()) else { unreachable!(); }; - Some(EntityHandle::new(self.world, archetype, archetype_entity)) + Some(EntityHandle::new(archetype, archetype_entity, self.world)) + } + + /// Attempts to get the component data of this pair, returning `None` if the + /// `Relation` type is incorrect. + /// + /// # Panics + /// Will panic if the component data is mutably borrowed elsewhere. + #[must_use] + pub fn get_data_as_relation(&self) -> Option<ComponentHandle<'_, Relation>> + { + ComponentHandle::<Relation>::from_entity_component_ref(&self.component_ref) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::<Relation>() + ); + } + }, + Some, + ) + } + + /// Attempts to get the component data of this pair, returning `None` if the + /// `Relation` type is incorrect. + /// + /// # Panics + /// Will panic if the component data is borrowed elsewhere. + #[must_use] + pub fn get_data_as_relation_mut(&self) -> Option<ComponentHandleMut<'_, Relation>> + { + ComponentHandleMut::<Relation>::from_entity_component_ref( + &self.component_ref, + self.world, + ) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::<Relation>() + ); + } + }, + Some, + ) } } -pub struct HandleIter<'a> +/// Used to access matching pairs in a entity containing zero or more matching pairs. +#[derive(Debug)] +pub struct MultipleWithWildcard<'a, Relation, Target> { - inner: EntityMatchingComponentIter<'a>, + entity_handle: EntityHandle<'a>, world: &'a World, + _pd: PhantomData<(Relation, Target)>, } -impl<'a> Iterator for HandleIter<'a> +impl<'world, Relation, Target> MultipleWithWildcard<'world, Relation, Target> { - type Item = Handle<'a>; - - fn next(&mut self) -> Option<Self::Item> + /// Returns a new `MultipleWithWildcard`. + /// + /// # Panics + /// This function will panic if: + /// - Both `Relation::uid()` and `Target::uid()` are wildcards + /// - Neither `Relation::uid()` or `Target::uid()` are wildcards + pub fn new(world: &'world World, entity_handle: EntityHandle<'world>) -> Self + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, { - let matching_comp = self.inner.next()?; + assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); + + assert!( + !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) + ); - Some(Handle { + MultipleWithWildcard { + entity_handle, + world, + _pd: PhantomData, + } + } +} + +impl<'a, Relation: Component> MultipleWithWildcard<'a, Relation, Wildcard> +{ + #[must_use] + pub fn get_with_target_id( + &self, + target_id: Uid, + ) -> Option<WithWildcard<'a, Relation, Wildcard>> + { + Some(WithWildcard { world: self.world, - pair_uid: matching_comp.id(), + component_ref: self + .entity_handle + .get_matching_components( + Pair::builder() + .relation::<Relation>() + .target_id(target_id) + .build() + .id(), + ) + .next()?, + _pd: PhantomData, }) } } -pub trait Element: sealed::Sealed +impl<'a, Relation: Component> IntoIterator + for MultipleWithWildcard<'a, Relation, Wildcard> { - type Value; + type IntoIter = WithWildcardIter<'a, Relation, Wildcard>; + type Item = <Self::IntoIter as Iterator>::Item; + + fn into_iter(self) -> Self::IntoIter + { + WithWildcardIter { + inner: self + .entity_handle + .get_matching_components(Pair::<Relation, Wildcard>::uid()), + world: self.world, + _pd: PhantomData, + } + } } -impl Element for Uid +/// Iterator of matching pairs in a entity. +pub struct WithWildcardIter<'a, Relation, Target> { - type Value = Uid; + inner: EntityMatchingComponentIter<'a>, + world: &'a World, + _pd: PhantomData<(Relation, Target)>, } -impl sealed::Sealed for Uid {} - -impl<WithUidT: WithUid> Element for WithUidT +impl<'a, Relation, Target> Iterator for WithWildcardIter<'a, Relation, Target> { - type Value = Infallible; -} + type Item = WithWildcard<'a, Relation, Target>; -impl<WithUidT: WithUid> sealed::Sealed for WithUidT {} + fn next(&mut self) -> Option<Self::Item> + { + let matching_comp = self.inner.next()?; + + Some(WithWildcard { + world: self.world, + component_ref: matching_comp, + _pd: PhantomData, + }) + } +} /// Relation denoting a dependency to another entity #[derive(Debug, Default, Clone, Copy, Component)] @@ -192,6 +644,43 @@ pub struct DependsOn; #[derive(Debug, Default, Clone, Copy, Component)] pub struct ChildOf; +#[derive(Debug)] +pub struct Wildcard(Infallible); + +impl Wildcard +{ + #[must_use] + pub fn uid() -> Uid + { + Uid::wildcard() + } +} + +pub trait ComponentOrWildcard: sealed::Sealed +{ + fn uid() -> Uid; +} + +impl<ComponentT: Component> ComponentOrWildcard for ComponentT +{ + fn uid() -> Uid + { + ComponentT::id() + } +} + +impl<ComponentT: Component> sealed::Sealed for ComponentT {} + +impl ComponentOrWildcard for Wildcard +{ + fn uid() -> Uid + { + Wildcard::uid() + } +} + +impl sealed::Sealed for Wildcard {} + mod sealed { pub trait Sealed {} diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs index 9f47fb8..39f2a08 100644 --- a/ecs/src/phase.rs +++ b/ecs/src/phase.rs @@ -1,13 +1,19 @@ use ecs_macros::Component; -use crate::pair::{ChildOf, Pair}; -use crate::static_entity; +use crate::{declare_entity, World}; #[derive(Debug, Default, Clone, Copy, Component)] pub struct Phase; -static_entity!(pub START, (Phase,)); +declare_entity!(pub START, (Phase,)); +declare_entity!(pub PRE_UPDATE, (Phase,)); +declare_entity!(pub UPDATE, (Phase,)); +declare_entity!(pub POST_UPDATE, (Phase,)); -static_entity!(pub PRE_UPDATE, (Phase,)); - -static_entity!(pub UPDATE, (Phase, Pair::new::<ChildOf>(*PRE_UPDATE))); +pub(crate) fn spawn_entities(world: &mut World) +{ + world.create_declared_entity(&START); + world.create_declared_entity(&PRE_UPDATE); + world.create_declared_entity(&UPDATE); + world.create_declared_entity(&POST_UPDATE); +} diff --git a/ecs/src/private.rs b/ecs/src/private.rs deleted file mode 100644 index 56a6552..0000000 --- a/ecs/src/private.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[doc(hidden)] -pub use {linkme, paste}; diff --git a/ecs/src/query.rs b/ecs/src/query.rs index 7e10c5b..5f13579 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -6,12 +6,11 @@ use seq_macro::seq; use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; use crate::entity::Handle as EntityHandle; use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery}; -use crate::system::{Param as SystemParam, System}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::uid::{Kind as UidKind, Uid, With as WithUid}; use crate::util::array_vec::ArrayVec; use crate::util::Array; @@ -20,15 +19,16 @@ use crate::World; pub mod flexible; pub mod term; +// A term tuple type can have a maximum of 17 elements +pub const MAX_TERM_CNT: usize = 17; + #[derive(Debug)] pub struct Query<'world, FieldTerms, FieldlessTerms = ()> where FieldTerms: TermWithFieldTuple, FieldlessTerms: TermWithoutFieldTuple, { - world: &'world World, - // A term tuple type can have a maximum of 17 elements - inner: FlexibleQuery<'world, 17>, + inner: FlexibleQuery<'world, MAX_TERM_CNT>, _pd: PhantomData<(FieldTerms, FieldlessTerms)>, } @@ -47,8 +47,8 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); Iter { - world: self.world, - iter: self.inner.iter(), + world: self.inner.world(), + inner: self.inner.iter(), comps_pd: PhantomData, } } @@ -63,7 +63,7 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); ComponentAndEuidIter { - world: self.world, + world: self.inner.world(), iter: self.inner.iter(), comps_pd: PhantomData, } @@ -85,8 +85,8 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); Iter { - world: self.world, - iter: func(self.inner.iter()), + world: self.inner.world(), + inner: func(self.inner.iter()), comps_pd: PhantomData, } } @@ -98,6 +98,25 @@ where Some(self.inner.iter().nth(entity_index)?.uid()) } + /// Returns a new `Query` created from a [`FlexibleQuery`]. + /// + /// # Important notes + /// The terms in `FieldTerms` and `FieldlessTerms` must be compatible with the terms + /// in the given [`FlexibleQuery`], otherwise any method call or iterating might + /// panic. + #[must_use] + pub fn from_flexible_query( + flexible_query: FlexibleQuery<'world, MAX_TERM_CNT>, + ) -> Self + { + // TODO: Check compatability of terms + + Self { + inner: flexible_query, + _pd: PhantomData, + } + } + pub(crate) fn new(world: &'world World) -> Self { let mut terms_builder = Terms::builder(); @@ -106,7 +125,6 @@ where FieldlessTerms::apply_terms_to_builder(&mut terms_builder); Self { - world, inner: world.flexible_query(terms_builder.build()), _pd: PhantomData, } @@ -136,17 +154,7 @@ where { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { Self::new(world) } @@ -342,18 +350,26 @@ impl<ComponentT: Component> TermWithField for &ComponentT fn get_field<'world>( entity_handle: &EntityHandle<'world>, - world: &'world World, + _world: &'world World, ) -> Self::Field<'world> { assert_eq!(ComponentT::id().kind(), UidKind::Component); - Self::Field::from_entity_component_ref( - entity_handle - .get_matching_components(ComponentT::id()) - .next(), - world, - ) - .unwrap_or_else(|err| { + let Some(component) = entity_handle + .get_matching_components(ComponentT::id()) + .next() + else { + panic!( + concat!( + "Component {} was not found in entity {}. There ", + "is most likely a bug in the entity querying" + ), + type_name::<ComponentT>(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component).unwrap_or_else(|err| { panic!( "Creating handle to component {} failed: {err}", type_name::<ComponentT>() @@ -380,13 +396,21 @@ impl<ComponentT: Component> TermWithField for &mut ComponentT { assert_eq!(ComponentT::id().kind(), UidKind::Component); - Self::Field::from_entity_component_ref( - entity_handle - .get_matching_components(ComponentT::id()) - .next(), - world, - ) - .unwrap_or_else(|err| { + let Some(component) = entity_handle + .get_matching_components(ComponentT::id()) + .next() + else { + panic!( + concat!( + "Component {} was not found in entity {}. There ", + "is most likely a bug in the entity querying" + ), + type_name::<ComponentT>(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component, world).unwrap_or_else(|err| { panic!( "Creating handle to component {} failed: {err}", type_name::<ComponentT>() @@ -422,29 +446,10 @@ where EntityHandleIter: Iterator<Item = EntityHandle<'query>>, { world: &'world World, - iter: EntityHandleIter, + inner: EntityHandleIter, comps_pd: PhantomData<FieldTerms>, } -impl<'query, 'world, FieldTerms, EntityHandleIter> - Iter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator<Item = EntityHandle<'query>>, - 'world: 'query, -{ - /// Creates a new iterator from the given entity handle iterator. - /// - /// # Important - /// All of the yielded entities of the entity handle iterator should match the - /// terms `Terms`. The [`Self::next`] function will panic if it encounters a - /// entity that does not match the terms `Terms`. - pub fn new(world: &'world World, iter: EntityHandleIter) -> Self - { - Self { world, iter, comps_pd: PhantomData } - } -} - impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator for Iter<'query, 'world, FieldTerms, EntityHandleIter> where @@ -456,7 +461,7 @@ where fn next(&mut self) -> Option<Self::Item> { - let entity_handle = self.iter.next()?; + let entity_handle = self.inner.next()?; Some(FieldTerms::get_fields(&entity_handle, self.world)) } diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs index 6d65ee0..936ab82 100644 --- a/ecs/src/query/flexible.rs +++ b/ecs/src/query/flexible.rs @@ -22,7 +22,6 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> pub fn iter(&self) -> Iter<'_> { Iter { - world: self.world, iter: self .world .data @@ -37,9 +36,16 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> .zip(archetype.entities()) }) as ComponentIterMapFn, ), + world: self.world, } } + #[must_use] + pub fn world(&self) -> &'world World + { + self.world + } + pub(crate) fn new(world: &'world World, terms: Terms<MAX_TERM_CNT>) -> Self { Self { world, terms } @@ -59,8 +65,8 @@ impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_T pub struct Iter<'query> { - world: &'query World, iter: QueryEntityIter<'query>, + world: &'query World, } impl<'query> Iterator for Iter<'query> @@ -71,7 +77,7 @@ impl<'query> Iterator for Iter<'query> { let (archetype, entity) = self.iter.next()?; - Some(EntityHandle::new(self.world, archetype, entity)) + Some(EntityHandle::new(archetype, entity, self.world)) } } diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs index 2e1ecca..0683918 100644 --- a/ecs/src/query/term.rs +++ b/ecs/src/query/term.rs @@ -4,7 +4,6 @@ use std::marker::PhantomData; use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; use crate::query::{ @@ -65,17 +64,14 @@ impl<ComponentT: Component> TermWithField for Option<&ComponentT> fn get_field<'world>( entity_handle: &crate::entity::Handle<'world>, - world: &'world crate::World, + _world: &'world crate::World, ) -> Self::Field<'world> { Some( ComponentHandle::<'world, ComponentT>::from_entity_component_ref( - Some( - entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - ), - world, + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, ) .unwrap_or_else(|err| { panic!( @@ -104,11 +100,9 @@ impl<ComponentT: Component> TermWithField for Option<&mut ComponentT> { Some( ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref( - Some( - entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - ), + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, world, ) .unwrap_or_else(|err| { diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs index 1cce419..7cfcc24 100644 --- a/ecs/src/sole.rs +++ b/ecs/src/sole.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Weak}; use crate::lock::{Lock, WriteGuard}; -use crate::system::{Param as SystemParam, System}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::World; /// A type which has a single instance and is shared globally. @@ -64,7 +64,7 @@ where } } - fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self + pub(crate) fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self { Self { sole: sole.write_nonblock().unwrap_or_else(|_| { @@ -85,17 +85,7 @@ where { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { let sole = world.data.sole_storage.get::<SoleT>().unwrap_or_else(|| { panic!("Sole {} was not found in world", type_name::<SoleT>()) diff --git a/ecs/src/system.rs b/ecs/src/system.rs index 603c015..95ab7a8 100644 --- a/ecs/src/system.rs +++ b/ecs/src/system.rs @@ -1,37 +1,28 @@ -use std::any::Any; -use std::convert::Infallible; use std::fmt::Debug; use ecs_macros::Component; use seq_macro::seq; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::tuple::{ReduceElement as TupleReduceElement, Tuple}; +use crate::uid::Uid; use crate::World; +pub mod initializable; +pub mod observer; pub mod stateful; -pub trait System<'world, Impl>: 'static +/// Metadata of a system. +#[derive(Debug)] +#[non_exhaustive] +pub struct Metadata { - type Input; - - #[must_use] - fn initialize(self, input: Self::Input) -> Self; - - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world; - - fn into_type_erased(self) -> TypeErased; + pub ent_id: Uid, +} - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>>; +pub trait System<'world, Impl>: 'static +{ + type Callbacks: Callbacks; - fn set_local_component<LocalComponent: Component>( - &mut self, - local_component: LocalComponent, - ); + fn finish(self) -> (TypeErased, Self::Callbacks); } macro_rules! impl_system { @@ -43,58 +34,23 @@ macro_rules! impl_system { Func: Fn(#(TParam~I,)*) + Copy + 'static, #(TParam~I: Param<'world, Input = ()>,)* { - type Input = Infallible; - - fn initialize(self, _input: Self::Input) -> Self - { - self - } + type Callbacks = NoCallbacks; - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world + fn finish(self) -> (TypeErased, Self::Callbacks) { - let func = *self; - - func(#({ - TParam~I::new(self, world) - },)*); - } - - fn into_type_erased(self) -> TypeErased - { - TypeErased { - data: Box::new(self), - run: Box::new(|data, world| { - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let data = unsafe { &*std::ptr::from_ref(data) }; - - let me = data - .downcast_ref::<Func>() - .expect("Function downcast failed"); - + let type_erased = TypeErased { + run: Box::new(move |world, metadata| { // SAFETY: The caller of TypeErased::run ensures the lifetime // is correct let world = unsafe { &*std::ptr::from_ref(world) }; - me.run(world); + self(#({ + TParam~I::new(world, &metadata) + },)*); }), - } - } + }; - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>> - { - panic!("System does not have any local components"); - } - - fn set_local_component<LocalComponent: Component>( - &mut self, - _local_component: LocalComponent, - ) { - panic!("System does not have any local components"); + (type_erased, NoCallbacks) } } }); @@ -105,7 +61,7 @@ seq!(C in 1..16 { impl_system!(C); }); -pub trait Into<Impl> +pub trait Into<'world, Impl> { type System; @@ -114,7 +70,6 @@ pub trait Into<Impl> pub struct TypeErased { - data: Box<dyn Any>, run: Box<TypeErasedRunFn>, } @@ -124,12 +79,9 @@ impl TypeErased /// /// # Safety /// `world_data` must live at least as long as the [`World`] the system belongs to. - pub unsafe fn run(&self, world: &World) + pub unsafe fn run(&self, world: &World, metadata: Metadata) { - // You have to dereference for downcasting to work for some reason - let data = &*self.data; - - (self.run)(data, world); + (self.run)(world, metadata); } } @@ -142,41 +94,29 @@ impl Debug for TypeErased } /// Function in [`TypeErased`] used to run the system. -type TypeErasedRunFn = dyn Fn(&dyn Any, &World); +type TypeErasedRunFn = dyn Fn(&World, Metadata); /// A parameter to a [`System`]. pub trait Param<'world> { type Input; - fn initialize<SystemImpl>( - system: &mut impl System<'world, SystemImpl>, - input: Self::Input, - ); - - fn new<SystemImpl>( - system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self; + fn new(world: &'world World, system_metadata: &Metadata) -> Self; } /// A type which can be used as input to a [`System`]. pub trait Input: 'static {} -/// Component tuple reducing operation to get the parameters that takes input. -pub struct ParamWithInputFilter; - -impl<InputT: Input, Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> - for InputT -where - Accumulator: Tuple, +pub trait Callbacks { - type Return = Accumulator::WithElementAtEnd<Self>; + fn on_created(&mut self, world: &mut World, metadata: Metadata); } -impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for () +pub struct NoCallbacks; + +impl Callbacks for NoCallbacks { - type Return = Accumulator; + fn on_created(&mut self, _world: &mut World, _metadata: Metadata) {} } #[derive(Debug, Component)] diff --git a/ecs/src/system/initializable.rs b/ecs/src/system/initializable.rs new file mode 100644 index 0000000..b6ec8e8 --- /dev/null +++ b/ecs/src/system/initializable.rs @@ -0,0 +1,131 @@ +use std::marker::PhantomData; + +use seq_macro::seq; + +use crate::system::{Input, Param as SystemParam, System}; +use crate::tuple::{Reduce as TupleReduce, ReduceElement as TupleReduceElement, Tuple}; + +/// A initializable system. +pub trait Initializable<'world, Impl>: System<'world, Impl> +{ + type Inputs; + + #[must_use] + fn initialize(self, inputs: Self::Inputs) -> Self; +} + +pub trait Param<'world, SystemT>: SystemParam<'world> +{ + fn initialize(system: &mut SystemT, input: Self::Input); +} + +pub struct ParamTupleFilter<'world, SystemT> +{ + _pd: PhantomData<(&'world (), SystemT)>, +} + +impl<'world, SystemT, ParamT, Accumulator> + TupleReduceElement<Accumulator, ParamTupleFilter<'world, SystemT>> for ParamT +where + ParamT: SystemParam< + 'world, + Input: AppendInitializableParam<'world, Accumulator, ParamT, SystemT>, + >, + Accumulator: Tuple, +{ + type Return = <ParamT::Input as AppendInitializableParam< + 'world, + Accumulator, + ParamT, + SystemT, + >>::Return; +} + +pub trait AppendInitializableParam<'world, Accumulator, ParamT, SystemT> +{ + type Return; +} + +impl<'world, InputT, ParamT, Accumulator, SystemT> + AppendInitializableParam<'world, Accumulator, ParamT, SystemT> for InputT +where + InputT: Input, + Accumulator: Tuple, + ParamT: Param<'world, SystemT>, +{ + type Return = Accumulator::WithElementAtEnd<ParamT>; +} + +impl<ParamT, Accumulator, SystemT> + AppendInitializableParam<'_, Accumulator, ParamT, SystemT> for () +where + Accumulator: Tuple, +{ + type Return = Accumulator; +} + +pub trait ParamTuple<'world, SystemT> +{ + type Inputs; + + fn initialize_all(system: &mut SystemT, inputs: Self::Inputs); +} + +macro_rules! impl_initializable_param_tuple { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, SystemT, #(Param~I,)*> ParamTuple<'world, SystemT> + for (#(Param~I,)*) + where + #(Param~I: Param<'world, SystemT>,)* + { + type Inputs = (#(Param~I::Input,)*); + + fn initialize_all( + system: &mut SystemT, + inputs: Self::Inputs, + ) { + #( + <Param~I as Param<'world, SystemT>>::initialize( + system, + inputs.I + ); + )* + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_initializable_param_tuple!(C); +}); + +impl<SystemT> ParamTuple<'_, SystemT> for () +{ + type Inputs = (); + + fn initialize_all(_system: &mut SystemT, _inputs: Self::Inputs) {} +} + +/// A tuple of system parameters that may or may not be initializable. +pub trait MaybeInitializableParamTuple<'world, SystemT> +{ + /// A tuple of the inputs of the initializable system parameters in this tuple. + type Inputs; + + fn init_initializable(system: &mut SystemT, inputs: Self::Inputs); +} + +impl<'world, SystemT, Params> MaybeInitializableParamTuple<'world, SystemT> for Params +where + Params: + TupleReduce<ParamTupleFilter<'world, SystemT>, Out: ParamTuple<'world, SystemT>>, +{ + type Inputs = <Params::Out as ParamTuple<'world, SystemT>>::Inputs; + + fn init_initializable(system: &mut SystemT, inputs: Self::Inputs) + { + Params::Out::initialize_all(system, inputs); + } +} diff --git a/ecs/src/system/observer.rs b/ecs/src/system/observer.rs new file mode 100644 index 0000000..236420c --- /dev/null +++ b/ecs/src/system/observer.rs @@ -0,0 +1,310 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::transmute; +use std::slice::Iter as SliceIter; + +use ecs_macros::Component; +use seq_macro::seq; + +use crate::component::Component; +use crate::entity::Handle as EntityHandle; +use crate::event::Emitted as EmittedEvent; +use crate::pair::Pair; +use crate::system::{ + Metadata, + NoCallbacks, + Param, + System, + TypeErased as TypeErasedSystem, +}; +use crate::uid::Uid; +use crate::util::Array; +use crate::World; + +pub trait Observed +{ + type Events: Array<Pair<Uid, Uid>>; + + fn events() -> Self::Events; +} + +impl<Relation, Target> Observed for Pair<Relation, Target> +where + Relation: Component, + Target: Component, +{ + type Events = [Pair<Uid, Uid>; 1]; + + fn events() -> Self::Events + { + [Pair::builder() + .relation::<Relation>() + .target::<Target>() + .build()] + } +} + +/// Observer system. +pub trait Observer<'world, Impl>: System<'world, Impl> +{ + type ObservedEvents: Array<Pair<Uid, Uid>>; + + fn observed_events() -> Self::ObservedEvents; + + fn finish_observer(self) -> (WrapperComponent, Self::Callbacks); +} + +pub struct Observe<'world, ObservedT: Observed> +{ + _pd: PhantomData<ObservedT>, + world: &'world World, + emitted_event: EmittedEvent<'world>, +} + +impl<'world, ObservedT: Observed> Observe<'world, ObservedT> +{ + pub fn new(world: &'world World, emitted_event: EmittedEvent<'world>) -> Self + { + Self { + _pd: PhantomData, + world, + emitted_event, + } + } + + #[must_use] + pub fn event(&self) -> Uid + { + self.emitted_event.event + } +} + +impl<ObservedT: Observed> Observe<'_, ObservedT> +{ + #[must_use] + pub fn iter(&self) -> ObserveIter<'_, ObservedT> + { + ObserveIter { + world: self.world, + inner: self.emitted_event.match_ids.iter(), + _pd: PhantomData, + } + } +} + +impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT> +{ + type IntoIter = ObserveIter<'a, ObservedT>; + type Item = <Self::IntoIter as Iterator>::Item; + + fn into_iter(self) -> Self::IntoIter + { + self.iter() + } +} + +pub struct ObserveIter<'observe, ObservedT: Observed> +{ + world: &'observe World, + inner: SliceIter<'observe, Uid>, + _pd: PhantomData<ObservedT>, +} + +impl<'observe, ObservedT: Observed> Iterator for ObserveIter<'observe, ObservedT> +{ + type Item = EventMatch<'observe, ObservedT>; + + fn next(&mut self) -> Option<Self::Item> + { + let match_id = *self.inner.next()?; + + Some(EventMatch { + world: self.world, + id: match_id, + _pd: PhantomData, + }) + } +} + +/// A event match. +#[derive(Debug)] +pub struct EventMatch<'world, ObservedT: Observed> +{ + world: &'world World, + id: Uid, + _pd: PhantomData<ObservedT>, +} + +impl<'world, ObservedT: Observed> EventMatch<'world, ObservedT> +{ + #[must_use] + pub fn id(&self) -> Uid + { + self.id + } + + /// Attempts to get the entity with the id of this match. + #[must_use] + pub fn get_entity(&self) -> Option<EntityHandle<'world>> + { + self.world.get_entity(self.id) + } +} + +macro_rules! impl_observer { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, ObservedT, Func, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Input = ()>,)* + { + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErasedSystem, NoCallbacks) + { + unimplemented!(); + } + } + + impl<'world, ObservedT, Func, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Input = ()>,)* + { + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (WrapperComponent, NoCallbacks) + { + let wrapper_comp = WrapperComponent { + run: Box::new(move |world, metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>( + emitted_event + ) + }; + + self(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*); + }), + }; + + (wrapper_comp, NoCallbacks) + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_observer!(C); +}); + +impl<'world, ObservedT, Func> System<'world, fn(Observe<'world, ObservedT>)> for Func +where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>) + Copy + 'static, +{ + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErasedSystem, NoCallbacks) + { + const { + panic!("Observers cannot be used as regular systems"); + } + } +} + +impl<'world, ObservedT, Func> Observer<'world, fn(Observe<'world, ObservedT>)> for Func +where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>) + Copy + 'static, +{ + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (WrapperComponent, NoCallbacks) + { + let wrapper_comp = WrapperComponent { + run: Box::new(move |world, _metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(emitted_event) + }; + + self(Observe::new(world, emitted_event)); + }), + }; + + (wrapper_comp, NoCallbacks) + } +} + +#[derive(Component)] +pub struct WrapperComponent +{ + run: Box<RunFn>, +} + +impl WrapperComponent +{ + pub fn new(run: impl Fn(&World, Metadata, EmittedEvent<'_>) + 'static) -> Self + { + Self { run: Box::new(run) } + } + + /// Runs the observer system. + /// + /// # Safety + /// `world` must live at least as long as the [`World`] the system belongs to. + pub unsafe fn run( + &self, + world: &World, + metadata: Metadata, + emitted_event: EmittedEvent<'_>, + ) + { + (self.run)(world, metadata, emitted_event); + } +} + +impl Debug for WrapperComponent +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter + .debug_struct("WrapperComponent") + .finish_non_exhaustive() + } +} + +type RunFn = dyn Fn(&World, Metadata, EmittedEvent<'_>); diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs index 54f6979..e74ef31 100644 --- a/ecs/src/system/stateful.rs +++ b/ecs/src/system/stateful.rs @@ -1,152 +1,243 @@ -use std::any::{type_name, Any, TypeId}; +use std::mem::transmute; use std::panic::{RefUnwindSafe, UnwindSafe}; -use hashbrown::HashMap; use seq_macro::seq; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::lock::Lock; -use crate::system::{ - Into as IntoSystem, - Param, - ParamWithInputFilter, - System, - TypeErased, +use crate::component::local::SystemWithLocalComponents; +use crate::component::Parts as ComponentParts; +use crate::event::Emitted as EmittedEvent; +use crate::system::initializable::{Initializable, MaybeInitializableParamTuple}; +use crate::system::observer::{ + Observe, + Observed, + Observer, + WrapperComponent as ObserverWrapperComponent, }; -use crate::tuple::{ - Reduce as TupleReduce, - Tuple, - WithAllElemLtStatic as TupleWithAllElemLtStatic, -}; -use crate::uid::Uid; +use crate::system::{Into as IntoSystem, Metadata, Param, System, TypeErased}; use crate::World; /// A stateful system. pub struct Stateful<Func> { func: Func, - local_components: HashMap<Uid, Lock<Box<dyn Any>>>, + local_components: Vec<ComponentParts>, } macro_rules! impl_system { ($c: tt) => { seq!(I in 0..$c { - impl<'world, Func, #(TParam~I,)*> System<'world, fn(&'world (), #(TParam~I,)*)> - for Stateful<Func> + impl<'world, Func, #(TParam~I,)*> + System<'world, fn(&'world (), #(TParam~I,)*)> for Stateful<Func> where Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, - #(TParam~I: Param<'world>,)* - #(TParam~I::Input: 'static,)* - (#(TParam~I::Input,)*): TupleReduce< - ParamWithInputFilter, - Out: Tuple<InOptions: TupleWithAllElemLtStatic> - >, + #(TParam~I: Param<'world, Input: 'static>,)* { - type Input = - <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out; + type Callbacks = Callbacks; - fn initialize(mut self, input: Self::Input) -> Self + fn finish(self) -> (TypeErased, Self::Callbacks) { - let mut option_input = input.into_in_options(); - - let mut index = 0; - - #( - if TypeId::of::<TParam~I::Input>() != - TypeId::of::<()>() - { - let input = option_input - .get_mut::<Option<TParam~I::Input>>(index) - .expect("Input element index out of range") - .take() - .expect("Input element is already taken"); - - TParam~I::initialize( - &mut self, - input - ); - - #[allow(unused_assignments)] - { - index += 1; - } - } - )* + let Self { func, local_components } = self; - self + let callbacks = Callbacks { local_components }; + + let type_erased = TypeErased { + run: Box::new(move |world, metadata| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + func(#({ + TParam~I::new(&world, &metadata) + },)*); + }), + }; + + + (type_erased, callbacks) } + } - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world + impl<'world, Func, #(TParam~I,)*> + Initializable<'world, fn(&'world (), #(TParam~I,)*)> for Stateful<Func> + where + Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, + #(TParam~I: Param<'world, Input: 'static>,)* + (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self> + { + type Inputs = < + (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self> + >::Inputs; + + fn initialize(mut self, inputs: Self::Inputs) -> Self { - let func = self.func; + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - func(#({ - TParam~I::new(self, &world) - },)*); + self } + } - fn into_type_erased(self) -> TypeErased + impl<'world, Func, #(TParam~I,)*> IntoSystem<'world, fn(#(TParam~I,)*)> + for Func + where + #(TParam~I: Param<'world>,)* + Func: Fn(#(TParam~I,)*) + Copy + 'static, + { + type System = Stateful<Func>; + + fn into_system(self) -> Self::System { - TypeErased { - data: Box::new(self), - run: Box::new(|data, world| { - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let data = unsafe { &*std::ptr::from_ref::<dyn Any>(data) }; + Self::System { + func: self, + local_components: Vec::new(), // TODO: Use Vec::with_capacity + } + } + } + }); + }; +} - let me = data.downcast_ref::<Self>().unwrap(); +seq!(C in 1..16 { + impl_system!(C); +}); - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let world = unsafe { &*std::ptr::from_ref(world) }; +impl<Func> SystemWithLocalComponents for Stateful<Func> +{ + fn add_local_component(&mut self, component_parts: ComponentParts) + { + self.local_components.push(component_parts); + } +} - me.run(world); - }), - } +#[derive(Debug)] +pub struct Callbacks +{ + local_components: Vec<ComponentParts>, +} + +impl crate::system::Callbacks for Callbacks +{ + fn on_created(&mut self, world: &mut World, metadata: Metadata) + { + for local_comp_parts in self.local_components.drain(..) { + world.add_component(metadata.ent_id, local_comp_parts); + } + } +} + +fn init_initializable_params<'world, SystemT, Params>( + system: &mut SystemT, + inputs: Params::Inputs, +) where + Params: MaybeInitializableParamTuple<'world, SystemT>, +{ + Params::init_initializable(system, inputs); +} + +macro_rules! impl_observer { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, ObservedT, Func, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + { + type Callbacks = Callbacks; + + fn finish(self) -> (TypeErased, Callbacks) + { + unimplemented!(); } + } - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>> + impl<'world, ObservedT, Func, #(TParam~I,)*> Initializable< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self> + { + type Inputs = < + (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self> + >::Inputs; + + fn initialize(mut self, inputs: Self::Inputs) -> Self { - let local_component = self.local_components - .get(&LocalComponent::id())? - .write_nonblock() - .expect("Failed to aquire read-write local component lock"); + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - Some(ComponentHandleMut::new(local_component)) + self } + } + + impl<'world, ObservedT, Func, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + { + type ObservedEvents = ObservedT::Events; - fn set_local_component<LocalComponent: Component>( - &mut self, - local_component: LocalComponent, - ) + fn observed_events() -> Self::ObservedEvents { - self.local_components - .insert( - LocalComponent::id(), - Lock::new( - Box::new(local_component), - type_name::<LocalComponent>() - ) - ); + ObservedT::events() + } + + fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks) + { + let Self { func, local_components } = self; + + let callbacks = Callbacks { local_components }; + + let wrapper_comp = ObserverWrapperComponent::new( + move |world, metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>( + emitted_event + ) + }; + + func(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*); + }, + ); + + (wrapper_comp, callbacks) } } - impl<Func, #(TParam~I,)*> IntoSystem<fn(#(TParam~I,)*)> - for Func + impl<'world, Func, ObservedT, #(TParam~I,)*> IntoSystem< + 'world, + fn(Observe<'world, ObservedT>, + #(TParam~I,)*) + > for Func where - Func: Fn(#(TParam~I,)*) + Copy + 'static, + ObservedT: Observed, + #(TParam~I: Param<'world>,)* + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, { type System = Stateful<Func>; - fn into_system(self) -> Self::System + fn into_system(self) -> Stateful<Func> { - Self::System { + Stateful { func: self, - local_components: HashMap::new(), + local_components: Vec::new(), // TODO: Use Vec::with_capacity } } } @@ -155,5 +246,5 @@ macro_rules! impl_system { } seq!(C in 1..16 { - impl_system!(C); + impl_observer!(C); }); diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs index feed62c..bb393a1 100644 --- a/ecs/src/uid.rs +++ b/ecs/src/uid.rs @@ -2,8 +2,10 @@ use std::fmt::{Debug, Display, Formatter}; use std::mem::transmute; use std::sync::atomic::{AtomicU32, Ordering}; +use seq_macro::seq; + use crate::component::Component; -use crate::util::{gen_mask_64, BitMask, NumberExt}; +use crate::util::{gen_mask_64, Array, BitMask, NumberExt}; static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID); @@ -218,7 +220,7 @@ pub struct PairParams pub target: Uid, } -pub trait With: 'static +pub trait With { fn uid() -> Uid; } @@ -231,13 +233,29 @@ impl<ComponentT: Component> With for ComponentT } } -#[derive(Debug)] -pub enum Wildcard {} - -impl With for Wildcard +pub trait WithUidTuple { - fn uid() -> Uid - { - Uid::wildcard() - } + type UidsArray: Array<Uid>; + + fn uids() -> Self::UidsArray; +} + +macro_rules! impl_with_uid_tuple { + ($c: tt) => { + seq!(I in 0..=$c { + impl<#(WithUid~I: With,)*> WithUidTuple for (#(WithUid~I,)*) + { + type UidsArray = [Uid; $c + 1]; + + fn uids() -> Self::UidsArray + { + [#(WithUid~I::uid(),)*] + } + } + }); + }; } + +seq!(C in 0..=16 { + impl_with_uid_tuple!(C); +}); diff --git a/ecs/src/util.rs b/ecs/src/util.rs index 9ab4dc6..27e9748 100644 --- a/ecs/src/util.rs +++ b/ecs/src/util.rs @@ -8,20 +8,20 @@ pub(crate) mod array_vec; pub trait VecExt<Item> { - fn insert_at_partition_point_by_key<Key>( + fn insert_at_part_pt_by_key<Key>( &mut self, item: Item, - func: impl FnMut(&Item) -> Key, + func: impl FnMut(&Item) -> &Key, ) where Key: Ord; } impl<Item> VecExt<Item> for Vec<Item> { - fn insert_at_partition_point_by_key<Key>( + fn insert_at_part_pt_by_key<Key>( &mut self, item: Item, - mut func: impl FnMut(&Item) -> Key, + mut func: impl FnMut(&Item) -> &Key, ) where Key: Ord, { @@ -300,6 +300,88 @@ macro_rules! gen_mask_64 { pub(crate) use gen_mask_64; +macro_rules! impl_multiple { + ( + $type: ident, + ($( + impl$(<$($generic: tt$(: $bound: ident)?),*>)? + _<$($lt_param: lifetime),*><$($type_param: ty),*> + $(($($extra_cb_arg: expr),*))? + ),*) + cb=( + type_params=($($ty_param_matcher: ident),*) + $(, $($extra_matcher: ident),+)? + ) => { + $($item_tt: tt)* + } + ) => { + const _: () = { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + _gen_multiple_impl_item, + ($($ty_param_matcher),*), + ($($($extra_matcher),+)?), + ($($item_tt)*) + ); + + $( + impl $(<$($generic$(: $bound)?,)*>)? $type<$($lt_param,)* $($type_param),*> + { + _gen_multiple_impl_item!( + type_params=($($type_param),*), + $($($extra_cb_arg),*)? + ); + } + )* + }; + }; + + ( + @(make_gen_item_macro) + $name: ident, + ($($ty_param_matcher: ident),*), + ($($extra_matcher: ident),*), + ($($transcriber: tt)*) + ) => { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + ($), + $name, + ($($ty_param_matcher),*), + ($($extra_matcher),*), + ($($transcriber)*) + ); + }; + + ( + @(make_gen_item_macro) + ($dollar: tt), + $name: ident, + ($($ty_param_matcher: ident),*), + ($($extra_matcher: ident),*), + ($($transcriber: tt)*) + ) => { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + $name, + ( + type_params=($($dollar$ty_param_matcher: ty),*), + $($dollar$extra_matcher: expr),* + ) => { + $($transcriber)* + } + ); + }; + + (@(make_gen_item_macro) $name: ident, $($rule: tt)*) => { + macro_rules! $name { + $($rule)* + } + }; +} + +pub(crate) use impl_multiple; + mod sealed { pub trait Sealed {} diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs index a37b1f9..5d0aac9 100644 --- a/ecs/src/util/array_vec.rs +++ b/ecs/src/util/array_vec.rs @@ -45,8 +45,8 @@ impl<Item, const CAPACITY: usize> ArrayVec<Item, CAPACITY> unsafe { std::ptr::copy( - &self.items[index], - &mut self.items[index + 1], + &raw const self.items[index], + &raw mut self.items[index + 1], self.len - index, ); } diff --git a/ecs/tests/query.rs b/ecs/tests/query.rs index 062fd5a..7b218e3 100644 --- a/ecs/tests/query.rs +++ b/ecs/tests/query.rs @@ -1,4 +1,5 @@ use ecs::component::Component; +use ecs::pair::{Pair, Wildcard}; use ecs::query::term::Without; use ecs::query::{ TermWithFieldTuple as QueryTermWithFieldTuple, @@ -320,3 +321,93 @@ fn query_without_comp_and_archetype_nonexistant() vec![ent_1_id, ent_2_id], ); } + +#[test] +fn query_with_wildcard_target_pair() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, C)); + + world.create_entity((B,)); + + let ent_2_id = world.create_entity(( + B, + Pair::builder().relation::<G>().target_id(ent_1_id).build(), + )); + + world.create_entity(( + B, + Pair::builder().relation::<F>().target_id(ent_1_id).build(), + )); + world.create_entity(( + B, + A, + C, + Pair::builder().relation::<F>().target_id(ent_1_id).build(), + )); + + let ent_3_id = world.create_entity(( + B, + Pair::builder().relation::<G>().target_id(ent_2_id).build(), + )); + + let ent_4_id = world.create_entity(( + B, + E, + Pair::builder().relation::<G>().target_as_data(D).build(), + )); + + assert_query_finds_ents( + world.query::<(&B, Pair<G, Wildcard>), ()>(), + vec![ent_2_id, ent_3_id, ent_4_id], + ); +} + +#[test] +fn query_with_component_target_pair() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, C)); + + world.create_entity((B,)); + + world.create_entity(( + B, + Pair::builder().relation::<G>().target_id(ent_1_id).build(), + )); + + world.create_entity(( + B, + Pair::builder().relation::<F>().target_id(ent_1_id).build(), + )); + world.create_entity(( + B, + A, + C, + Pair::builder().relation::<F>().target_id(ent_1_id).build(), + )); + + let ent_2_id = world + .create_entity((B, Pair::builder().relation::<G>().target_as_data(F).build())); + + let ent_3_id = world.create_entity(( + B, + E, + Pair::builder().relation::<G>().target_as_data(F).build(), + )); + + assert_query_finds_ents( + world.query::<(&B, Pair<G, &F>), ()>(), + vec![ent_2_id, ent_3_id], + ); +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index f6cd5cf..6ddcf12 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -4,17 +4,31 @@ version = "0.1.0" edition = "2021" [dependencies] -glfw = { path = "../glfw", features = ["opengl"] } +glutin = "0.32.3" +raw-window-handle = "0.6.2" thiserror = "1.0.49" -gl = "0.14.0" bitflags = "2.4.0" tracing = "0.1.39" seq-macro = "0.3.5" paste = "1.0.14" +parking_lot = "0.12.3" +crossbeam-channel = "0.5.15" +safer-ffi = "0.1.13" +nu-ansi-term = "0.46.0" ecs = { path = "../ecs" } util-macros = { path = "../util-macros" } +opengl-bindings = { path = "../opengl-bindings" } -[dependencies.image] +[dependencies.winit] +version = "0.30.11" +default-features = false +features = ["rwh_06", "wayland", "wayland-dlopen", "x11"] + +[dependencies.image_rs] version = "0.24.7" default-features = false features = ["png", "jpeg"] +package = "image" + +[build-dependencies] +cfg_aliases = "0.2.1" diff --git a/engine/build.rs b/engine/build.rs new file mode 100644 index 0000000..58029fc --- /dev/null +++ b/engine/build.rs @@ -0,0 +1,63 @@ +// Original file: +// https://github.com/rust-windowing/glutin/blob/ +// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/build.rs +// +// Copyright © 2022 Kirill Chibisov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Keep this in sync with glutin's build.rs. + +use cfg_aliases::cfg_aliases; + +fn main() +{ + // Setup alias to reduce `cfg` boilerplate. + cfg_aliases! { + // Systems. + android_platform: { target_os = "android" }, + wasm_platform: { target_family = "wasm" }, + macos_platform: { target_os = "macos" }, + ios_platform: { target_os = "ios" }, + apple: { any(ios_platform, macos_platform) }, + free_unix: { all(unix, not(apple), not(android_platform)) }, + + // Native displays. + x11_platform: { all(free_unix, not(wasm_platform)) }, + wayland_platform: { all(free_unix, not(wasm_platform)) }, + // x11_platform: { all(feature = "x11", free_unix, not(wasm_platform)) }, + // wayland_platform: { all(feature = "wayland", free_unix, not(wasm_platform)) }, + + // Backends. + egl_backend: { + all(any(windows, unix), not(apple), not(wasm_platform)) + }, + glx_backend: { all(x11_platform, not(wasm_platform)) }, + wgl_backend: { all(windows, not(wasm_platform)) }, + cgl_backend: { all(macos_platform, not(wasm_platform)) }, + + // Backends. + // egl_backend: { + // all(feature = "egl", any(windows, unix), not(apple), not(wasm_platform)) + // }, + // glx_backend: { all(feature = "glx", x11_platform, not(wasm_platform)) }, + // wgl_backend: { all(feature = "wgl", windows, not(wasm_platform)) }, + // cgl_backend: { all(macos_platform, not(wasm_platform)) }, + } +} diff --git a/engine/src/asset.rs b/engine/src/asset.rs new file mode 100644 index 0000000..db4d23c --- /dev/null +++ b/engine/src/asset.rs @@ -0,0 +1,777 @@ +use std::any::{type_name, Any}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; +use std::convert::Infallible; +use std::ffi::{OsStr, OsString}; +use std::fmt::{Debug, Display}; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::{ + channel as mpsc_channel, + Receiver as MpscReceiver, + Sender as MpscSender, +}; +use std::sync::Arc; + +use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; +use ecs::sole::Single; +use ecs::Sole; + +use crate::work_queue::{Work, WorkQueue}; + +/// Asset label. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Label<'a> +{ + pub path: Cow<'a, Path>, + pub name: Option<Cow<'a, str>>, +} + +impl Label<'_> +{ + pub fn to_owned(&self) -> LabelOwned + { + LabelOwned { + path: self.path.to_path_buf(), + name: self.name.as_ref().map(|name| name.to_string()), + } + } +} + +impl<'a> From<&'a Path> for Label<'a> +{ + fn from(path: &'a Path) -> Self + { + Self { path: path.into(), name: None } + } +} + +impl From<PathBuf> for Label<'_> +{ + fn from(path: PathBuf) -> Self + { + Self { path: path.into(), name: None } + } +} + +impl<'a> From<&'a LabelOwned> for Label<'a> +{ + fn from(label: &'a LabelOwned) -> Self + { + Self { + path: (&label.path).into(), + name: label.name.as_ref().map(|name| Cow::Borrowed(name.as_str())), + } + } +} + +impl Display for Label<'_> +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + write!(formatter, "{}", self.path.display())?; + + if let Some(name) = &self.name { + formatter.write_str("::")?; + formatter.write_str(&name)?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LabelOwned +{ + pub path: PathBuf, + pub name: Option<String>, +} + +impl LabelOwned +{ + pub fn to_label(&self) -> Label<'_> + { + Label { + path: (&self.path).into(), + name: self.name.as_ref().map(|name| Cow::Borrowed(name.as_str())), + } + } +} + +impl Display for LabelOwned +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + write!(formatter, "{}", self.path.display())?; + + if let Some(name) = &self.name { + formatter.write_str("::")?; + formatter.write_str(&name)?; + } + + Ok(()) + } +} + +#[derive(Debug, Sole)] +pub struct Assets +{ + assets: Vec<StoredAsset>, + asset_lookup: RefCell<HashMap<LabelHash, LookupEntry>>, + importers: Vec<WrappedImporterFn>, + importer_lookup: HashMap<OsString, usize>, + import_work_queue: WorkQueue<ImportWorkUserData>, + import_work_msg_receiver: MpscReceiver<ImportWorkMessage>, + import_work_msg_sender: MpscSender<ImportWorkMessage>, +} + +impl Assets +{ + pub fn with_capacity(capacity: usize) -> Self + { + let (import_work_msg_sender, import_work_msg_receiver) = + mpsc_channel::<ImportWorkMessage>(); + + Self { + assets: Vec::with_capacity(capacity), + asset_lookup: RefCell::new(HashMap::with_capacity(capacity)), + importers: Vec::new(), + importer_lookup: HashMap::new(), + import_work_queue: WorkQueue::new(), + import_work_msg_receiver, + import_work_msg_sender, + } + } + + pub fn set_importer<'file_ext, AssetSettings, Err>( + &mut self, + file_extensions: impl IntoIterator<Item: Into<Cow<'file_ext, str>>>, + func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + ) where + AssetSettings: 'static, + Err: std::error::Error + 'static, + { + self.importers.push(WrappedImporterFn::new(func)); + + let importer_index = self.importers.len() - 1; + + self.importer_lookup + .extend(file_extensions.into_iter().map(|file_ext| { + let file_ext: Cow<str> = file_ext.into(); + + (file_ext.into_owned().into(), importer_index) + })); + } + + #[tracing::instrument(skip_all, fields(asset_type=type_name::<Asset>()))] + pub fn get<'this, 'handle, Asset: 'static + Send + Sync>( + &'this self, + handle: &'handle Handle<Asset>, + ) -> Option<&'handle Asset> + where + 'this: 'handle, + { + let LookupEntry::Occupied(asset_index) = + *self.asset_lookup.borrow().get(&handle.id.label_hash)? + else { + return None; + }; + + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + let Some(asset) = stored_asset.strong.downcast_ref::<Asset>() else { + tracing::error!("Wrong asset type"); + return None; + }; + + Some(asset) + } + + #[tracing::instrument(skip(self))] + pub fn load<'i, Asset: 'static + Send + Sync>( + &self, + label: impl Into<Label<'i>> + Debug, + ) -> Handle<Asset> + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + let mut asset_lookup = self.asset_lookup.borrow_mut(); + + if Self::is_pending(&asset_lookup, &label) { + return Handle::new(label_hash); + } + + let Some(lookup_entry) = asset_lookup.get(&label_hash) else { + self.add_import_work::<Infallible>( + &label, + label_hash, + None, + &mut asset_lookup, + ); + + return Handle::new(label_hash); + }; + + match *lookup_entry { + LookupEntry::Occupied(asset_index) => { + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + if stored_asset.strong.downcast_ref::<Asset>().is_none() { + tracing::error!("Wrong asset type {}", type_name::<Asset>()); + } + } + LookupEntry::Pending => {} + } + + Handle::new(label_hash) + } + + #[tracing::instrument(skip(self))] + pub fn load_with_settings<'i, Asset, AssetSettings>( + &self, + label: impl Into<Label<'i>> + Debug, + asset_settings: AssetSettings, + ) -> Handle<Asset> + where + Asset: Send + Sync + 'static, + AssetSettings: Send + Sync + Debug + 'static, + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + let mut asset_lookup = self.asset_lookup.borrow_mut(); + + if Self::is_pending(&asset_lookup, &label) { + return Handle::new(label_hash); + } + + let Some(lookup_entry) = asset_lookup.get(&label_hash) else { + self.add_import_work::<AssetSettings>( + &label, + label_hash, + Some(asset_settings), + &mut asset_lookup, + ); + + return Handle::new(label_hash); + }; + + match *lookup_entry { + LookupEntry::Occupied(asset_index) => { + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + if stored_asset.strong.downcast_ref::<Asset>().is_none() { + tracing::error!( + "Wrong asset type {} for asset", + type_name::<Asset>() + ); + } + } + LookupEntry::Pending => {} + } + + Handle::new(label_hash) + } + + pub fn store_with_name<'name, Asset: 'static + Send + Sync>( + &mut self, + name: impl Into<Cow<'name, str>>, + asset: Asset, + ) -> Handle<Asset> + { + self.store_with_label( + Label { + path: Path::new("").into(), + name: Some(name.into()), + }, + asset, + ) + } + + #[tracing::instrument(skip(self, asset), fields(asset_type=type_name::<Asset>()))] + pub fn store_with_label<'i, Asset: 'static + Send + Sync>( + &mut self, + label: impl Into<Label<'i>> + Debug, + asset: Asset, + ) -> Handle<Asset> + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + if matches!( + self.asset_lookup.get_mut().get(&label_hash), + Some(LookupEntry::Occupied(_)) + ) { + tracing::error!("Asset already exists"); + + return Handle::new(label_hash); + } + + tracing::debug!("Storing asset"); + + self.assets.push(StoredAsset::new(asset)); + + let index = self.assets.len() - 1; + + self.asset_lookup + .get_mut() + .insert(label_hash, LookupEntry::Occupied(index)); + + if label.name.is_some() { + let parent_asset_label_hash = + LabelHash::new(&Label { path: label.path, name: None }); + + if matches!( + self.asset_lookup.get_mut().get(&parent_asset_label_hash), + Some(LookupEntry::Pending) + ) { + self.asset_lookup.get_mut().remove(&parent_asset_label_hash); + } else if self + .asset_lookup + .get_mut() + .get(&parent_asset_label_hash) + .is_none() + { + self.assets + .push(StoredAsset::new::<Option<Infallible>>(None)); + + self.asset_lookup.get_mut().insert( + parent_asset_label_hash, + LookupEntry::Occupied(self.assets.len() - 1), + ); + } + } + + Handle::new(label_hash) + } + + fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool + { + if label.name.is_some() { + if let Some(LookupEntry::Pending) = + asset_lookup.get(&LabelHash::new(&Label { + path: label.path.as_ref().into(), + name: None, + })) + { + return true; + } + } + + if let Some(LookupEntry::Pending) = asset_lookup.get(&LabelHash::new(label)) { + return true; + }; + + false + } + + fn add_import_work<AssetSettings>( + &self, + label: &Label<'_>, + label_hash: LabelHash, + asset_settings: Option<AssetSettings>, + asset_lookup: &mut HashMap<LabelHash, LookupEntry>, + ) where + AssetSettings: Any + Send + Sync, + { + let Some(file_ext) = label.path.extension() else { + tracing::error!("Asset file is missing a file extension"); + return; + }; + + let Some(importer) = self.get_importer(file_ext) else { + tracing::error!( + "No importer exists for asset file extension {}", + file_ext.to_string_lossy() + ); + return; + }; + + self.import_work_queue.add_work(Work { + func: |ImportWorkUserData { + import_work_msg_sender, + asset_path, + asset_settings, + importer, + }| { + if let Err(err) = importer.call( + import_work_msg_sender, + asset_path.as_path(), + asset_settings.as_deref(), + ) { + tracing::error!( + "Failed to load asset {}: {err}", + asset_path.display() + ); + } + }, + user_data: ImportWorkUserData { + import_work_msg_sender: self.import_work_msg_sender.clone(), + asset_path: label.path.to_path_buf(), + asset_settings: asset_settings.map(|asset_settings| { + Box::new(asset_settings) as Box<dyn Any + Send + Sync> + }), + importer: importer.clone(), + }, + }); + + asset_lookup.insert(label_hash, LookupEntry::Pending); + + if label.name.is_some() { + asset_lookup.insert( + LabelHash::new(&Label { + path: label.path.as_ref().into(), + name: None, + }), + LookupEntry::Pending, + ); + } + } + + fn get_importer(&self, file_ext: &OsStr) -> Option<&WrappedImporterFn> + { + let index = *self.importer_lookup.get(file_ext)?; + + Some(self.importers.get(index).expect("Not possible")) + } +} + +impl Default for Assets +{ + fn default() -> Self + { + Self::with_capacity(0) + } +} + +pub struct Submitter<'path> +{ + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &'path Path, +} + +impl Submitter<'_> +{ + pub fn submit_load_other<'label, Asset: Send + Sync + 'static>( + &self, + label: impl Into<Label<'label>>, + ) -> Handle<Asset> + { + let label = label.into(); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { + do_load: |assets, label, _asset_settings| { + let _ = assets.load::<Asset>(label); + }, + label: label.to_owned(), + asset_settings: None, + }); + + Handle::new(LabelHash::new(&label)) + } + + pub fn submit_load_other_with_settings<'label, Asset, AssetSettings>( + &self, + label: impl Into<Label<'label>>, + asset_settings: AssetSettings, + ) -> Handle<Asset> + where + Asset: Send + Sync + 'static, + AssetSettings: Send + Sync + Debug + 'static, + { + let label = label.into(); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { + do_load: |assets, label, asset_settings| { + let asset_settings = *asset_settings + .expect("Not possible") + .downcast::<AssetSettings>() + .expect("Not possible"); + + let _ = assets + .load_with_settings::<Asset, AssetSettings>(label, asset_settings); + }, + label: label.to_owned(), + asset_settings: Some(Box::new(asset_settings)), + }); + + Handle::new(LabelHash::new(&label)) + } + + pub fn submit_store<Asset: Send + Sync + 'static>( + &self, + asset: Asset, + ) -> Handle<Asset> + { + let label = LabelOwned { + path: self.asset_path.into(), + name: None, + }; + + let label_hash = LabelHash::new(&label.to_label()); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { + do_store: |assets, label, boxed_asset| { + let Ok(asset) = boxed_asset.downcast::<Asset>() else { + unreachable!(); + }; + + assets.store_with_label::<Asset>(&label, *asset); + }, + label, + asset: Box::new(asset), + }); + + Handle::new(label_hash) + } + + pub fn submit_store_named<Asset: Send + Sync + 'static>( + &self, + name: impl AsRef<str>, + asset: Asset, + ) -> Handle<Asset> + { + let label = LabelOwned { + path: self.asset_path.into(), + name: Some(name.as_ref().into()), + }; + + let label_hash = LabelHash::new(&label.to_label()); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { + do_store: |assets, label, boxed_asset| { + let Ok(asset) = boxed_asset.downcast::<Asset>() else { + unreachable!(); + }; + + assets.store_with_label::<Asset>(&label, *asset); + }, + label, + asset: Box::new(asset), + }); + + Handle::new(label_hash) + } +} + +/// Asset handle. +#[derive(Debug)] +pub struct Handle<Asset: 'static> +{ + id: Id, + _pd: PhantomData<Asset>, +} + +impl<Asset: 'static> Handle<Asset> +{ + pub fn id(&self) -> Id + { + self.id + } + + fn new(label_hash: LabelHash) -> Self + { + Self { + id: Id { label_hash }, + _pd: PhantomData, + } + } +} + +impl<Asset: 'static> Clone for Handle<Asset> +{ + fn clone(&self) -> Self + { + Self { id: self.id, _pd: PhantomData } + } +} + +/// Asset ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id +{ + label_hash: LabelHash, +} + +#[derive(Debug, thiserror::Error)] +enum ImporterError +{ + #[error("Settings has a incorrect type")] + IncorrectAssetSettingsType(PathBuf), + + #[error(transparent)] + Other(Box<dyn std::error::Error>), +} + +#[derive(Debug, Clone)] +struct WrappedImporterFn +{ + wrapper_func: fn( + MpscSender<ImportWorkMessage>, + &Path, + Option<&(dyn Any + Send + Sync)>, + ) -> Result<(), ImporterError>, +} + +impl WrappedImporterFn +{ + fn new<InnerFunc, AssetSettings, Err>(inner_func_param: InnerFunc) -> Self + where + InnerFunc: + Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + AssetSettings: 'static, + Err: std::error::Error + 'static, + { + assert_eq!(size_of::<InnerFunc>(), 0); + + let wrapper_func = + |import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &Path, + asset_settings: Option<&(dyn Any + Send + Sync)>| { + let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() }; + + let asset_settings = asset_settings + .map(|asset_settings| { + asset_settings + .downcast_ref::<AssetSettings>() + .ok_or_else(|| { + ImporterError::IncorrectAssetSettingsType( + asset_path.to_path_buf(), + ) + }) + }) + .transpose()?; + + inner_func( + &mut Submitter { import_work_msg_sender, asset_path }, + asset_path, + asset_settings, + ) + .map_err(|err| ImporterError::Other(Box::new(err)))?; + + Ok(()) + }; + + std::mem::forget(inner_func_param); + + Self { wrapper_func } + } + + fn call( + &self, + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &Path, + asset_settings: Option<&(dyn Any + Send + Sync)>, + ) -> Result<(), ImporterError> + { + (self.wrapper_func)(import_work_msg_sender, asset_path, asset_settings) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct LabelHash(u64); + +impl LabelHash +{ + fn new(label: &Label<'_>) -> Self + { + let mut hasher = DefaultHasher::new(); + + label.hash(&mut hasher); + + Self(hasher.finish()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct Extension +{ + pub assets: Assets, +} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + let _ = collector.add_sole(self.assets); + + collector.add_system(*PRE_UPDATE_PHASE, add_received_assets); + } +} + +fn add_received_assets(mut assets: Single<Assets>) +{ + while let Some(import_work_msg) = assets.import_work_msg_receiver.try_recv().ok() { + match import_work_msg { + ImportWorkMessage::Store { do_store, label, asset } => { + do_store(&mut assets, label, asset); + } + ImportWorkMessage::Load { do_load, label, asset_settings } => { + do_load( + &assets, + Label { + path: label.path.as_path().into(), + name: label.name.as_deref().map(|name| name.into()), + }, + asset_settings, + ); + } + } + } +} + +#[derive(Debug)] +struct ImportWorkUserData +{ + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: PathBuf, + asset_settings: Option<Box<dyn Any + Send + Sync>>, + importer: WrappedImporterFn, +} + +#[derive(Debug)] +enum ImportWorkMessage +{ + Store + { + do_store: fn(&mut Assets, LabelOwned, Box<dyn Any + Send + Sync>), + label: LabelOwned, + asset: Box<dyn Any + Send + Sync>, + }, + + Load + { + do_load: fn(&Assets, Label<'_>, Option<Box<dyn Any + Send + Sync>>), + label: LabelOwned, + asset_settings: Option<Box<dyn Any + Send + Sync>>, + }, +} + +#[derive(Debug, Clone, Copy)] +enum LookupEntry +{ + Occupied(usize), + Pending, +} + +#[derive(Debug)] +struct StoredAsset +{ + strong: Arc<dyn Any + Send + Sync>, +} + +impl StoredAsset +{ + fn new<Asset: Any + Send + Sync>(asset: Asset) -> Self + { + let strong = Arc::new(asset); + + Self { strong } + } +} diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 254d020..a034851 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,14 +1,16 @@ use ecs::component::local::Local; use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::sole::Single; -use ecs::system::{Into, System}; +use ecs::system::initializable::Initializable; +use ecs::system::Into; use ecs::{Component, Query}; +use crate::builder; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; -use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; +use crate::input::keyboard::{Key, KeyState, Keyboard}; +use crate::input::mouse::Motion as MouseMotion; use crate::transform::WorldPosition; -use crate::util::builder; use crate::vector::{Vec2, Vec3}; builder! { @@ -59,12 +61,7 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system( - *UPDATE_PHASE, - update - .into_system() - .initialize((CursorState::default(), self.0)), - ); + collector.add_system(*UPDATE_PHASE, update.into_system().initialize((self.0,))); } } @@ -76,35 +73,29 @@ pub struct Options fn update( camera_query: Query<(&mut Camera, &mut WorldPosition, &mut Fly, &ActiveCamera)>, - keys: Single<Keys>, - cursor: Single<Cursor>, - cursor_flags: Single<CursorFlags>, + keyboard: Single<Keyboard>, + mouse_motion: Single<MouseMotion>, delta_time: Single<DeltaTime>, - mut cursor_state: Local<CursorState>, options: Local<Options>, ) { for (mut camera, mut camera_world_pos, mut fly_camera, _) in &camera_query { - if cursor.has_moved && cursor_flags.is_first_move.flag { - tracing::debug!("First cursor move"); - - cursor_state.last_pos = cursor.position; - } - let delta_time = delta_time.duration; - let mut x_offset = cursor.position.x - cursor_state.last_pos.x; - let mut y_offset = cursor_state.last_pos.y - cursor.position.y; + // tracing::info!("Mouse motion: {:?}", mouse_motion.position_delta); - cursor_state.last_pos = cursor.position; + if mouse_motion.position_delta != (Vec2 { x: 0.0, y: 0.0 }) { + let x_offset = + mouse_motion.position_delta.x * f64::from(options.mouse_sensitivity); - x_offset *= f64::from(options.mouse_sensitivity); - y_offset *= f64::from(options.mouse_sensitivity); + let y_offset = + (-mouse_motion.position_delta.y) * f64::from(options.mouse_sensitivity); - fly_camera.current_yaw += x_offset; - fly_camera.current_pitch += y_offset; + fly_camera.current_yaw += x_offset; + fly_camera.current_pitch += y_offset; - fly_camera.current_pitch = fly_camera.current_pitch.clamp(-89.0, 89.0); + fly_camera.current_pitch = fly_camera.current_pitch.clamp(-89.0, 89.0); + } // TODO: This casting to a f32 from a f64 is horrible. fix it #[allow(clippy::cast_possible_truncation)] @@ -121,24 +112,24 @@ fn update( camera.global_up = cam_right.cross(&direction).normalize(); - if keys.get_key_state(Key::W) == KeyState::Pressed { + if keyboard.get_key_state(Key::W) == KeyState::Pressed { camera_world_pos.position += direction * fly_camera.speed * delta_time.as_secs_f32(); } - if keys.get_key_state(Key::S) == KeyState::Pressed { + if keyboard.get_key_state(Key::S) == KeyState::Pressed { camera_world_pos.position -= direction * fly_camera.speed * delta_time.as_secs_f32(); } - if keys.get_key_state(Key::A) == KeyState::Pressed { + if keyboard.get_key_state(Key::A) == KeyState::Pressed { let cam_left = -direction.cross(&Vec3::UP).normalize(); camera_world_pos.position += cam_left * fly_camera.speed * delta_time.as_secs_f32(); } - if keys.get_key_state(Key::D) == KeyState::Pressed { + if keyboard.get_key_state(Key::D) == KeyState::Pressed { let cam_right = direction.cross(&Vec3::UP).normalize(); camera_world_pos.position += @@ -148,9 +139,3 @@ fn update( camera.target = camera_world_pos.position + direction; } } - -#[derive(Debug, Default, Component)] -struct CursorState -{ - last_pos: Vec2<f64>, -} diff --git a/engine/src/data_types/color.rs b/engine/src/data_types/color.rs index cef3b92..c5316e6 100644 --- a/engine/src/data_types/color.rs +++ b/engine/src/data_types/color.rs @@ -1,7 +1,6 @@ use std::ops::{Add, Div, Mul, Neg, Sub}; #[derive(Debug, Clone, Default)] -#[repr(C)] pub struct Color<Value> { pub red: Value, diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs index d8d0247..8bf239f 100644 --- a/engine/src/data_types/dimens.rs +++ b/engine/src/data_types/dimens.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroU32; + /// 2D dimensions. #[derive(Debug, Clone, Copy)] pub struct Dimens<Value> @@ -22,6 +24,18 @@ impl<Value> From<(Value, Value)> for Dimens<Value> } } +impl Dimens<u32> +{ + #[must_use] + pub fn try_into_nonzero(self) -> Option<Dimens<NonZeroU32>> + { + Some(Dimens { + width: NonZeroU32::new(self.width)?, + height: NonZeroU32::new(self.height)?, + }) + } +} + /// 3D dimensions. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct Dimens3<Value> diff --git a/engine/src/data_types/matrix.rs b/engine/src/data_types/matrix.rs index 3a29ae2..b754b62 100644 --- a/engine/src/data_types/matrix.rs +++ b/engine/src/data_types/matrix.rs @@ -4,7 +4,7 @@ use crate::vector::Vec3; pub struct Matrix<Value, const ROWS: usize, const COLUMNS: usize> { /// Items must be layed out this way for it to work with OpenGL shaders. - items: [[Value; ROWS]; COLUMNS], + pub items: [[Value; ROWS]; COLUMNS], } impl<Value, const ROWS: usize, const COLUMNS: usize> Matrix<Value, ROWS, COLUMNS> diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs index 100c709..dc6df30 100644 --- a/engine/src/data_types/vector.rs +++ b/engine/src/data_types/vector.rs @@ -3,7 +3,6 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use crate::color::Color; #[derive(Debug, Default, Clone, Copy, PartialEq)] -#[repr(C)] pub struct Vec2<Value> { pub x: Value, @@ -15,6 +14,29 @@ impl Vec2<u32> pub const ZERO: Self = Self { x: 0, y: 0 }; } +impl<Value> Add for Vec2<Value> +where + Value: Add<Value, Output = Value>, +{ + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output + { + Self::Output { x: self.x + rhs.x, y: self.y + rhs.y } + } +} + +impl<Value> AddAssign for Vec2<Value> +where + Value: Add<Value, Output = Value> + Clone, +{ + fn add_assign(&mut self, rhs: Self) + { + self.x = self.x.clone() + rhs.x; + self.y = self.y.clone() + rhs.y; + } +} + impl<Value> Add<Value> for Vec2<Value> where Value: Add<Output = Value> + Clone, @@ -76,7 +98,6 @@ where } #[derive(Debug, Default, Clone, Copy, PartialEq)] -#[repr(C)] pub struct Vec3<Value> { pub x: Value, diff --git a/engine/src/draw_flags.rs b/engine/src/draw_flags.rs index df5eed1..426f865 100644 --- a/engine/src/draw_flags.rs +++ b/engine/src/draw_flags.rs @@ -1,6 +1,6 @@ use ecs::Component; -use crate::util::builder; +use crate::builder; builder! { /// Flags for how a object should be drawn. diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index 7d1c570..f3c7a64 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -234,6 +234,24 @@ fn statements_to_materials( path: Path::new(texture_file_path).to_path_buf(), }); } + Keyword::Ns => { + if statement.arguments.len() != 1 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let shininess = statement.get_float_arg(0, line_no)?; + + tracing::debug!( + "Adding shininess {shininess} to material {}", + curr_material.name + ); + + curr_material.shininess = shininess; + } Keyword::Newmtl => {} } } @@ -279,5 +297,7 @@ keyword! { #[keyword(rename = "map_Ks")] MapKs, + + Ns, } } diff --git a/engine/src/image.rs b/engine/src/image.rs new file mode 100644 index 0000000..0e04412 --- /dev/null +++ b/engine/src/image.rs @@ -0,0 +1,184 @@ +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +use image_rs::GenericImageView as _; + +use crate::asset::{Assets, Submitter as AssetSubmitter}; +use crate::color::Color; +use crate::data_types::dimens::Dimens; +use crate::builder; + +#[derive(Debug)] +pub struct Image +{ + inner: image_rs::DynamicImage, +} + +impl Image +{ + pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> + { + let buffered_reader = + BufReader::new(File::open(&path).map_err(Error::ReadFailed)?); + + let image_reader = image_rs::io::Reader::with_format( + buffered_reader, + image_rs::ImageFormat::from_path(path) + .map_err(|_| Error::UnsupportedFormat)?, + ); + + Ok(Self { + inner: image_reader + .decode() + .map_err(|err| Error::DecodeFailed(DecodeError(err)))?, + }) + } + + pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>) + -> Self + { + let dimens: Dimens<u32> = dimens.into(); + + let color: Color<u8> = color.into(); + + Self { + inner: image_rs::RgbImage::from_pixel( + dimens.width, + dimens.height, + image_rs::Rgb([color.red, color.green, color.blue]), + ) + .into(), + } + } + + pub fn dimensions(&self) -> Dimens<u32> + { + self.inner.dimensions().into() + } + + pub fn color_type(&self) -> ColorType + { + self.inner.color().into() + } + + pub fn as_bytes(&self) -> &[u8] + { + self.inner.as_bytes() + } +} + +builder! { +#[builder(name = SettingsBuilder, derives=(Debug, Clone))] +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct Settings { +} +} + +impl Settings +{ + pub fn builder() -> SettingsBuilder + { + SettingsBuilder::default() + } +} + +impl Default for SettingsBuilder +{ + fn default() -> Self + { + Settings::default().into() + } +} + +/// An enumeration over supported color types and bit depths +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +#[non_exhaustive] +pub enum ColorType +{ + /// Pixel is 8-bit luminance + L8, + + /// Pixel is 8-bit luminance with an alpha channel + La8, + + /// Pixel contains 8-bit R, G and B channels + Rgb8, + + /// Pixel is 8-bit RGB with an alpha channel + Rgba8, + + /// Pixel is 16-bit luminance + L16, + + /// Pixel is 16-bit luminance with an alpha channel + La16, + + /// Pixel is 16-bit RGB + Rgb16, + + /// Pixel is 16-bit RGBA + Rgba16, + + /// Pixel is 32-bit float RGB + Rgb32F, + + /// Pixel is 32-bit float RGBA + Rgba32F, +} + +impl From<image_rs::ColorType> for ColorType +{ + fn from(color_type: image_rs::ColorType) -> Self + { + match color_type { + image_rs::ColorType::L8 => Self::L8, + image_rs::ColorType::La8 => Self::La8, + image_rs::ColorType::Rgb8 => Self::Rgb8, + image_rs::ColorType::Rgba8 => Self::Rgba8, + image_rs::ColorType::L16 => Self::L16, + image_rs::ColorType::La16 => Self::La16, + image_rs::ColorType::Rgb16 => Self::Rgb16, + image_rs::ColorType::Rgba16 => Self::Rgba16, + image_rs::ColorType::Rgb32F => Self::Rgb32F, + image_rs::ColorType::Rgba32F => Self::Rgba32F, + _ => { + panic!("Unrecognized image_rs::ColorType variant"); + } + } + } +} + +pub fn set_asset_importers(assets: &mut Assets) +{ + assets.set_importer::<_, _>(["png", "jpg"], import_asset); +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to read image file")] + ReadFailed(#[source] std::io::Error), + + #[error("Failed to decode image")] + DecodeFailed(DecodeError), + + #[error("Unsupported image format")] + UnsupportedFormat, +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct DecodeError(image_rs::ImageError); + +fn import_asset( + asset_submitter: &mut AssetSubmitter<'_>, + path: &Path, + _settings: Option<&'_ Settings>, +) -> Result<(), Error> +{ + asset_submitter.submit_store::<Image>(Image::open(path)?); + + Ok(()) +} diff --git a/engine/src/input.rs b/engine/src/input.rs index d6a82f6..f8c9dfd 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -1,249 +1,32 @@ -use std::collections::HashMap; - +use ecs::declare_entity; use ecs::extension::Collector as ExtensionCollector; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE}; -use ecs::sole::Single; -use ecs::{static_entity, Sole}; - -use crate::vector::Vec2; -use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE}; - -mod reexports -{ - pub use crate::window::{Key, KeyState}; -} - -pub use reexports::*; - -static_entity!( - SET_PREV_KEY_STATE_PHASE, - (Phase, Pair::new::<ChildOf>(*WINDOW_UPDATE_PHASE)) +use ecs::pair::{DependsOn, Pair}; +use ecs::phase::Phase; + +use crate::windowing::PHASE as WINDOWING_PHASE; + +pub mod keyboard; +pub mod mouse; + +declare_entity!( + pub PHASE, + ( + Phase, + Pair::builder() + .relation::<DependsOn>() + .target_id(*WINDOWING_PHASE) + .build() + ) ); -#[derive(Debug, Sole)] -pub struct Keys -{ - map: HashMap<Key, KeyData>, - pending: Vec<(Key, KeyState)>, -} - -impl Keys -{ - #[must_use] - pub fn new() -> Self - { - Self { - map: Key::KEYS - .iter() - .map(|key| { - ( - *key, - KeyData { - state: KeyState::Released, - prev_tick_state: KeyState::Released, - }, - ) - }) - .collect(), - pending: Vec::with_capacity(Key::KEYS.len()), - } - } - - #[must_use] - pub fn get_key_state(&self, key: Key) -> KeyState - { - let Some(key_data) = self.map.get(&key) else { - unreachable!(); - }; - - key_data.state - } - - #[must_use] - pub fn get_prev_key_state(&self, key: Key) -> KeyState - { - let Some(key_data) = self.map.get(&key) else { - unreachable!(); - }; - - key_data.prev_tick_state - } - - pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState) - { - let Some(key_data) = self.map.get_mut(&key) else { - unreachable!(); - }; - - key_data.state = new_key_state; - } - - #[must_use] - pub fn is_anything_pressed(&self) -> bool - { - self.map - .values() - .any(|key_data| matches!(key_data.state, KeyState::Pressed)) - } -} - -impl Default for Keys -{ - fn default() -> Self - { - Self::new() - } -} - -#[derive(Debug, Default, Clone, Sole)] -pub struct Cursor -{ - pub position: Vec2<f64>, - pub has_moved: bool, -} - -#[derive(Debug, Clone, Sole)] -pub struct CursorFlags -{ - /// This flag is set in two situations: - /// A: The window has just started - /// B: The window has gained focus again after losing focus. - /// - /// This flag only lasts a single tick then it is cleared (at the beginning of the - /// next tick). - pub is_first_move: CursorFlag, -} - -impl Default for CursorFlags -{ - fn default() -> Self - { - Self { - is_first_move: CursorFlag { flag: true, ..Default::default() }, - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct CursorFlag -{ - pub flag: bool, - pub pending_clear: bool, -} - -impl CursorFlag -{ - pub fn clear(&mut self) - { - self.flag = false; - self.pending_clear = false; - } -} - /// Input extension. #[derive(Debug, Default)] pub struct Extension {} impl ecs::extension::Extension for Extension { - fn collect(self, mut collector: ExtensionCollector<'_>) + fn collect(self, _collector: ExtensionCollector<'_>) { - collector.add_system(*START_PHASE, initialize); - collector.add_system(*PRE_UPDATE_PHASE, maybe_clear_cursor_is_first_move); - collector.add_system(*SET_PREV_KEY_STATE_PHASE, set_pending_key_states); - - collector.add_sole(Keys::default()).ok(); - collector.add_sole(Cursor::default()).ok(); - collector.add_sole(CursorFlags::default()).ok(); + // TODO: Add input mapping } } - -fn initialize( - keys: Single<Keys>, - cursor: Single<Cursor>, - cursor_flags: Single<CursorFlags>, - window: Single<Window>, -) -{ - let keys_weak_ref = keys.to_weak_ref(); - - window.set_key_callback(move |key, _scancode, key_state, _modifiers| { - let keys_ref = keys_weak_ref.access().expect("No world"); - - let mut keys = keys_ref.to_single(); - - keys.pending.push((key, key_state)); - }); - - let cursor_weak_ref = cursor.to_weak_ref(); - - window.set_cursor_pos_callback(move |cursor_position| { - let cursor_ref = cursor_weak_ref.access().expect("No world"); - - let mut cursor = cursor_ref.to_single(); - - cursor.position = Vec2 { - x: cursor_position.x, - y: cursor_position.y, - }; - - cursor.has_moved = true; - }); - - let cursor_flags_weak_ref = cursor_flags.to_weak_ref(); - - window.set_focus_callback(move |is_focused| { - tracing::trace!("Window is focused: {is_focused}"); - - let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world"); - - cursor_flags_ref.to_single().is_first_move.flag = is_focused; - }); -} - -fn maybe_clear_cursor_is_first_move( - cursor: Single<Cursor>, - mut cursor_flags: Single<CursorFlags>, -) -{ - if cursor_flags.is_first_move.pending_clear { - tracing::trace!("Clearing is_first_move"); - - // This flag was set for the whole previous tick so it can be cleared now - cursor_flags.is_first_move.clear(); - - return; - } - - if cursor.has_moved && cursor_flags.is_first_move.flag { - tracing::trace!("Setting flag to clear is_first_move next tick"); - - // Make this system clear is_first_move the next time it runs - cursor_flags.is_first_move.pending_clear = true; - } -} - -fn set_pending_key_states(mut keys: Single<Keys>) -{ - let Keys { map, pending } = &mut *keys; - - for key_data in map.values_mut() { - key_data.prev_tick_state = key_data.state; - } - - for (key, key_state) in pending { - let Some(key_data) = map.get_mut(key) else { - unreachable!(); - }; - - key_data.state = *key_state; - } -} - -#[derive(Debug)] -struct KeyData -{ - state: KeyState, - prev_tick_state: KeyState, -} diff --git a/engine/src/input/keyboard.rs b/engine/src/input/keyboard.rs new file mode 100644 index 0000000..d226df0 --- /dev/null +++ b/engine/src/input/keyboard.rs @@ -0,0 +1,6 @@ +mod reexports +{ + pub use crate::windowing::keyboard::{Key, KeyState, Keyboard}; +} + +pub use reexports::*; diff --git a/engine/src/input/mouse.rs b/engine/src/input/mouse.rs new file mode 100644 index 0000000..90091f3 --- /dev/null +++ b/engine/src/input/mouse.rs @@ -0,0 +1,6 @@ +mod reexports +{ + pub use crate::windowing::mouse::{Button, ButtonState, Buttons, Motion}; +} + +pub use reexports::*; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index c537e06..d5531c1 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -3,40 +3,48 @@ use ecs::component::Sequence as ComponentSequence; use ecs::extension::Extension; -use ecs::pair::Pair; use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; use ecs::sole::Sole; +use ecs::system::initializable::Initializable; +use ecs::system::observer::Observer; use ecs::system::{Into, System}; use ecs::uid::Uid; use ecs::{SoleAlreadyExistsError, World}; +use crate::asset::{Assets, Extension as AssetExtension}; use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; mod opengl; mod util; +mod work_queue; +pub mod asset; pub mod camera; pub mod collision; pub mod data_types; pub mod delta_time; pub mod draw_flags; pub mod file_format; +pub mod image; pub mod input; pub mod lighting; pub mod material; pub mod math; pub mod mesh; +pub mod model; pub mod projection; pub mod renderer; pub mod texture; pub mod transform; -pub mod window; +pub mod windowing; pub extern crate ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; +const INITIAL_ASSET_CAPACITY: usize = 128; + #[derive(Debug)] pub struct Engine { @@ -49,6 +57,9 @@ impl Engine #[must_use] pub fn new() -> Self { + #[cfg(windows)] + nu_ansi_term::enable_ansi_support().unwrap(); + let mut world = World::new(); world.add_sole(DeltaTime::default()).ok(); @@ -60,6 +71,13 @@ impl Engine .initialize((LastUpdate::default(),)), ); + let mut assets = Assets::with_capacity(INITIAL_ASSET_CAPACITY); + + crate::model::set_asset_importers(&mut assets); + crate::image::set_asset_importers(&mut assets); + + world.add_extension(AssetExtension { assets }); + Self { world } } @@ -79,13 +97,12 @@ impl Engine self.world.register_system(phase_euid, system); } - pub fn register_observer_system<'this, SystemImpl>( + pub fn register_observer<'this, SystemImpl>( &'this mut self, - system: impl System<'this, SystemImpl>, - event: Pair<Uid, Uid>, + observer: impl Observer<'this, SystemImpl>, ) { - self.world.register_observer_system(system, event); + self.world.register_observer(observer); } /// Adds a globally shared singleton value. diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs index 09dd980..9ab2ca8 100644 --- a/engine/src/lighting.rs +++ b/engine/src/lighting.rs @@ -1,8 +1,8 @@ use ecs::{Component, Sole}; +use crate::builder; use crate::color::Color; use crate::data_types::vector::Vec3; -use crate::util::builder; builder! { #[builder(name = PointLightBuilder, derives = (Debug, Clone))] @@ -59,7 +59,6 @@ pub struct AttenuationParams impl Default for AttenuationParams { - #[must_use] fn default() -> Self { Self { diff --git a/engine/src/material.rs b/engine/src/material.rs index e368519..56ff15f 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,21 +1,19 @@ use ecs::Component; use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::texture::{Id as TextureId, Texture}; -use crate::util::builder; +use crate::texture::Texture; +use crate::builder; -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone)] #[non_exhaustive] pub struct Material { pub ambient: Color<f32>, pub diffuse: Color<f32>, pub specular: Color<f32>, - pub ambient_map: TextureId, - pub diffuse_map: TextureId, - pub specular_map: TextureId, - pub textures: Vec<Texture>, + pub ambient_map: Option<Texture>, + pub diffuse_map: Option<Texture>, + pub specular_map: Option<Texture>, pub shininess: f32, } @@ -27,17 +25,24 @@ impl Material } } +impl Default for Material +{ + fn default() -> Self + { + Self::builder().build() + } +} + /// [`Material`] builder. #[derive(Debug, Clone)] pub struct Builder { - ambient: Option<Color<f32>>, - diffuse: Option<Color<f32>>, - specular: Option<Color<f32>>, - ambient_map: Option<TextureId>, - diffuse_map: Option<TextureId>, - specular_map: Option<TextureId>, - textures: Vec<Texture>, + ambient: Color<f32>, + diffuse: Color<f32>, + specular: Color<f32>, + ambient_map: Option<Texture>, + diffuse_map: Option<Texture>, + specular_map: Option<Texture>, shininess: f32, } @@ -47,13 +52,12 @@ impl Builder pub fn new() -> Self { Self { - ambient: None, - diffuse: None, - specular: None, + ambient: Color::WHITE_F32, + diffuse: Color::WHITE_F32, + specular: Color::WHITE_F32, ambient_map: None, diffuse_map: None, specular_map: None, - textures: Vec::new(), shininess: 32.0, } } @@ -61,7 +65,7 @@ impl Builder #[must_use] pub fn ambient(mut self, ambient: Color<f32>) -> Self { - self.ambient = Some(ambient); + self.ambient = ambient; self } @@ -69,7 +73,7 @@ impl Builder #[must_use] pub fn diffuse(mut self, diffuse: Color<f32>) -> Self { - self.diffuse = Some(diffuse); + self.diffuse = diffuse; self } @@ -77,13 +81,13 @@ impl Builder #[must_use] pub fn specular(mut self, specular: Color<f32>) -> Self { - self.specular = Some(specular); + self.specular = specular; self } #[must_use] - pub fn ambient_map(mut self, ambient_map: TextureId) -> Self + pub fn ambient_map(mut self, ambient_map: Texture) -> Self { self.ambient_map = Some(ambient_map); @@ -91,7 +95,7 @@ impl Builder } #[must_use] - pub fn diffuse_map(mut self, diffuse_map: TextureId) -> Self + pub fn diffuse_map(mut self, diffuse_map: Texture) -> Self { self.diffuse_map = Some(diffuse_map); @@ -99,7 +103,7 @@ impl Builder } #[must_use] - pub fn specular_map(mut self, specular_map: TextureId) -> Self + pub fn specular_map(mut self, specular_map: Texture) -> Self { self.specular_map = Some(specular_map); @@ -107,22 +111,6 @@ impl Builder } #[must_use] - pub fn textures(mut self, textures: impl IntoIterator<Item = Texture>) -> Self - { - self.textures = textures.into_iter().collect(); - - self - } - - #[must_use] - pub fn texture(mut self, texture: Texture) -> Self - { - self.textures.push(texture); - - self - } - - #[must_use] pub fn shininess(mut self, shininess: f32) -> Self { self.shininess = shininess; @@ -135,43 +123,15 @@ impl Builder /// # Panics /// Will panic if no ambient map, diffuse map or specular map is set. #[must_use] - pub fn build(mut self) -> Material + pub fn build(self) -> Material { - let ambient_map = self.ambient_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - - let diffuse_map = self.diffuse_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - - let specular_map = self.specular_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - Material { - ambient: self.ambient.unwrap_or(Color::WHITE_F32), - diffuse: self.diffuse.unwrap_or(Color::WHITE_F32), - specular: self.specular.unwrap_or(Color::WHITE_F32), - ambient_map, - diffuse_map, - specular_map, - textures: self.textures, + ambient: self.ambient, + diffuse: self.diffuse, + specular: self.specular, + ambient_map: self.ambient_map, + diffuse_map: self.diffuse_map, + specular_map: self.specular_map, shininess: self.shininess, } } @@ -206,8 +166,3 @@ impl Flags FlagsBuilder::default() } } - -fn create_1x1_white_texture() -> Texture -{ - Texture::new_from_color(&Dimens { width: 1, height: 1 }, &Color::WHITE_U8) -} diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index 91d199e..fb977af 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,11 +1,9 @@ -use ecs::Component; - -use crate::util::builder; +use crate::builder; use crate::vector::{Vec2, Vec3}; pub mod cube; -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Default)] pub struct Mesh { vertices: Vec<Vertex>, diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs index 4d2c470..e91cf0e 100644 --- a/engine/src/mesh/cube.rs +++ b/engine/src/mesh/cube.rs @@ -1,7 +1,7 @@ use crate::data_types::dimens::Dimens3; use crate::math::calc_triangle_surface_normal; use crate::mesh::{Mesh, Vertex}; -use crate::util::builder; +use crate::builder; use crate::vector::{Vec2, Vec3}; builder! { diff --git a/engine/src/model.rs b/engine/src/model.rs new file mode 100644 index 0000000..9f5840c --- /dev/null +++ b/engine/src/model.rs @@ -0,0 +1,176 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::fs::read_to_string; +use std::path::Path; + +use ecs::Component; + +use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter}; +use crate::material::Material; +use crate::mesh::Mesh; +use crate::texture::Texture; + +#[derive(Debug, Clone, Component)] +#[non_exhaustive] +pub struct Model +{ + pub asset_handle: AssetHandle<Data>, +} + +impl Model +{ + pub fn new(asset_handle: AssetHandle<Data>) -> Self + { + Self { asset_handle } + } +} + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct Data +{ + pub mesh: Mesh, + pub materials: HashMap<String, Material>, +} + +impl Data +{ + pub fn builder() -> DataBuilder + { + DataBuilder::default() + } +} + +#[derive(Debug, Default, Clone)] +pub struct DataBuilder +{ + mesh: Mesh, + materials: HashMap<String, Material>, +} + +impl DataBuilder +{ + pub fn mesh(mut self, mesh: Mesh) -> Self + { + self.mesh = mesh; + + self + } + + pub fn material<'name>( + mut self, + name: impl Into<Cow<'name, str>>, + material: Material, + ) -> Self + { + self.materials.insert(name.into().into_owned(), material); + + self + } + + pub fn build(self) -> Data + { + Data { + mesh: self.mesh, + materials: self.materials, + } + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Settings {} + +#[derive(Debug, thiserror::Error)] +enum Error +{ + #[error("Failed to read model file")] + ReadModelFileFailed(#[source] std::io::Error), + + #[error("Failed to read material file")] + ReadMaterialFileFailed(#[source] std::io::Error), + + #[error("Failed to parse model file")] + ParsingFailed(#[from] ParsingError), +} + +pub fn set_asset_importers(assets: &mut Assets) +{ + assets.set_importer(["obj"], import_wavefront_obj_asset); +} + +#[derive(Debug, thiserror::Error)] +enum ParsingError +{ + #[error(transparent)] + Obj(#[from] crate::file_format::wavefront::obj::Error), + + #[error(transparent)] + Mtl(#[from] crate::file_format::wavefront::mtl::Error), +} + +fn import_wavefront_obj_asset( + asset_submitter: &mut AssetSubmitter<'_>, + path: &Path, + _settings: Option<&'_ Settings>, +) -> Result<(), Error> +{ + let obj = crate::file_format::wavefront::obj::parse( + &read_to_string(path).map_err(Error::ReadModelFileFailed)?, + ) + .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; + + let mesh = obj + .to_mesh() + .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; + + let mut materials = + HashMap::<String, Material>::with_capacity(obj.mtl_libs.iter().flatten().count()); + + for mtl_lib_path in obj.mtl_libs.iter().flatten() { + materials.extend(import_mtl(asset_submitter, &mtl_lib_path)?); + } + + asset_submitter.submit_store(Data { mesh, materials }); + + Ok(()) +} + +fn import_mtl<'a>( + asset_submitter: &'a AssetSubmitter<'_>, + path: &Path, +) -> Result<impl Iterator<Item = (String, Material)> + 'a, Error> +{ + let named_materials = crate::file_format::wavefront::mtl::parse( + &read_to_string(path).map_err(Error::ReadMaterialFileFailed)?, + ) + .map_err(|err| Error::ParsingFailed(ParsingError::Mtl(err)))?; + + Ok(named_materials.into_iter().map(|named_material| { + let mut material_builder = Material::builder() + .ambient(named_material.ambient) + .diffuse(named_material.diffuse) + .specular(named_material.specular) + .shininess(named_material.shininess); + + if let Some(ambient_map) = named_material.ambient_map { + material_builder = material_builder.ambient_map(Texture::new( + asset_submitter.submit_load_other(ambient_map.path.as_path()), + )); + } + + if let Some(diffuse_map) = named_material.diffuse_map { + material_builder = material_builder.diffuse_map(Texture::new( + asset_submitter.submit_load_other(diffuse_map.path.as_path()), + )); + } + + if let Some(specular_map) = named_material.specular_map { + material_builder = material_builder.specular_map(Texture::new( + asset_submitter.submit_load_other(specular_map.path.as_path()), + )); + } + + (named_material.name, material_builder.build()) + })) +} diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs deleted file mode 100644 index eded553..0000000 --- a/engine/src/opengl/buffer.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::marker::PhantomData; -use std::mem::size_of_val; -use std::ptr::null; - -#[derive(Debug)] -pub struct Buffer<Item> -{ - buf: gl::types::GLuint, - _pd: PhantomData<Item>, -} - -impl<Item> Buffer<Item> -{ - pub fn new() -> Self - { - let mut buffer = gl::types::GLuint::default(); - - unsafe { - gl::CreateBuffers(1, &mut buffer); - }; - - Self { buf: buffer, _pd: PhantomData } - } - - /// Stores items in the currently bound buffer. - pub fn store(&mut self, items: &[Item], usage: Usage) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::NamedBufferData( - self.buf, - size_of_val(items) as gl::types::GLsizeiptr, - items.as_ptr().cast(), - usage.into_gl(), - ); - } - } - - pub fn store_mapped<Value>( - &mut self, - values: &[Value], - usage: Usage, - mut map_func: impl FnMut(&Value) -> Item, - ) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::NamedBufferData( - self.buf, - (size_of::<Item>() * values.len()) as gl::types::GLsizeiptr, - null(), - usage.into_gl(), - ); - } - - for (index, value) in values.iter().enumerate() { - let item = map_func(value); - - unsafe { - gl::NamedBufferSubData( - self.buf, - (index * size_of::<Item>()) as gl::types::GLintptr, - size_of::<Item>() as gl::types::GLsizeiptr, - (&raw const item).cast(), - ); - } - } - } - - pub fn object(&self) -> gl::types::GLuint - { - self.buf - } -} - -/// Buffer usage. -#[derive(Debug)] -#[allow(dead_code)] -pub enum Usage -{ - /// The buffer data is set only once and used by the GPU at most a few times. - Stream, - - /// The buffer data is set only once and used many times. - Static, - - /// The buffer data is changed a lot and used many times. - Dynamic, -} - -impl Usage -{ - fn into_gl(self) -> gl::types::GLenum - { - match self { - Self::Stream => gl::STREAM_DRAW, - Self::Static => gl::STATIC_DRAW, - Self::Dynamic => gl::DYNAMIC_DRAW, - } - } -} diff --git a/engine/src/opengl/debug.rs b/engine/src/opengl/debug.rs deleted file mode 100644 index 203590a..0000000 --- a/engine/src/opengl/debug.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::ffi::c_void; -use std::io::{stderr, Write}; -use std::panic::catch_unwind; -use std::ptr::null_mut; -use std::sync::Mutex; - -use crate::opengl::util::gl_enum; - -pub type MessageCallback = fn( - source: MessageSource, - ty: MessageType, - id: u32, - severity: MessageSeverity, - message: &str, -); - -pub fn enable_debug_output() -{ - unsafe { - gl::Enable(gl::DEBUG_OUTPUT); - gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS); - } -} - -pub fn set_debug_message_callback(cb: MessageCallback) -{ - *DEBUG_MESSAGE_CB.lock().unwrap() = Some(cb); - - unsafe { - gl::DebugMessageCallback(Some(debug_message_cb), null_mut()); - } -} - -pub fn set_debug_message_control( - source: Option<MessageSource>, - ty: Option<MessageType>, - severity: Option<MessageSeverity>, - ids: &[u32], - ids_action: MessageIdsAction, -) -{ - // Ids shouldn't realistically be large enough to cause a panic here - let ids_len: i32 = ids.len().try_into().unwrap(); - - unsafe { - gl::DebugMessageControl( - source.map_or(gl::DONT_CARE, |source| source as u32), - ty.map_or(gl::DONT_CARE, |ty| ty as u32), - severity.map_or(gl::DONT_CARE, |severity| severity as u32), - ids_len, - ids.as_ptr(), - ids_action as u8, - ); - } -} - -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub enum MessageIdsAction -{ - Enable = 1, - Disable = 0, -} - -gl_enum! { -pub enum MessageSource -{ - Api = gl::DEBUG_SOURCE_API, - WindowSystem = gl::DEBUG_SOURCE_WINDOW_SYSTEM, - ShaderCompiler = gl::DEBUG_SOURCE_SHADER_COMPILER, - ThirdParty = gl::DEBUG_SOURCE_THIRD_PARTY, - Application = gl::DEBUG_SOURCE_APPLICATION, - Other = gl::DEBUG_SOURCE_OTHER, -} -} - -gl_enum! { -pub enum MessageType -{ - DeprecatedBehavior = gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR, - Error = gl::DEBUG_TYPE_ERROR, - Marker = gl::DEBUG_TYPE_MARKER, - Other = gl::DEBUG_TYPE_OTHER, - Performance = gl::DEBUG_TYPE_PERFORMANCE, - PopGroup = gl::DEBUG_TYPE_POP_GROUP, - PushGroup = gl::DEBUG_TYPE_PUSH_GROUP, - Portability = gl::DEBUG_TYPE_PORTABILITY, - UndefinedBehavior = gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR, -} -} - -gl_enum! { -pub enum MessageSeverity -{ - High = gl::DEBUG_SEVERITY_HIGH, - Medium = gl::DEBUG_SEVERITY_MEDIUM, - Low = gl::DEBUG_SEVERITY_LOW, - Notification = gl::DEBUG_SEVERITY_NOTIFICATION, -} -} - -static DEBUG_MESSAGE_CB: Mutex<Option<MessageCallback>> = Mutex::new(None); - -extern "system" fn debug_message_cb( - source: gl::types::GLenum, - ty: gl::types::GLenum, - id: gl::types::GLuint, - severity: gl::types::GLenum, - message_length: gl::types::GLsizei, - message: *const gl::types::GLchar, - _user_param: *mut c_void, -) -{ - // Unwinds are catched because unwinding from Rust code into foreign code is UB. - let res = catch_unwind(|| { - let cb_lock = DEBUG_MESSAGE_CB.lock().unwrap(); - - if let Some(cb) = *cb_lock { - let msg_source = MessageSource::from_gl(source).unwrap(); - let msg_type = MessageType::from_gl(ty).unwrap(); - let msg_severity = MessageSeverity::from_gl(severity).unwrap(); - - let msg_length = usize::try_from(message_length).unwrap(); - - // SAFETY: The received message should be a valid ASCII string - let message = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - message.cast(), - msg_length, - )) - }; - - cb(msg_source, msg_type, id, msg_severity, message); - } - }); - - if res.is_err() { - // eprintln is not used since it can panic and unwinds are unwanted because - // unwinding from Rust code into foreign code is UB. - stderr() - .write_all(b"ERROR: Panic in debug message callback") - .ok(); - println!(); - } -} diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index 53e0120..2208ac6 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -1,128 +1 @@ -use bitflags::bitflags; -use gl::types::GLint; - -use crate::data_types::dimens::Dimens; -use crate::vector::Vec2; - -pub mod buffer; pub mod glsl; -pub mod shader; -pub mod texture; -pub mod vertex_array; - -mod util; - -pub mod debug; - -pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) -{ - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::Viewport( - position.x as i32, - position.y as i32, - size.width as i32, - size.height as i32, - ); - } -} - -pub fn clear_buffers(mask: BufferClearMask) -{ - unsafe { - gl::Clear(mask.bits()); - } -} - -pub fn set_polygon_mode(face: impl Into<PolygonModeFace>, mode: impl Into<PolygonMode>) -{ - unsafe { - gl::PolygonMode(face.into() as u32, mode.into() as u32); - } -} - -pub fn enable(capacity: Capability) -{ - unsafe { - gl::Enable(capacity as u32); - } -} - -pub fn get_context_flags() -> ContextFlags -{ - let mut context_flags: GLint = 0; - - unsafe { - gl::GetIntegerv(gl::CONTEXT_FLAGS as u32, &mut context_flags); - } - - ContextFlags::from_bits_truncate(context_flags as u32) -} - -bitflags! { - #[derive(Debug, Clone, Copy)] - pub struct BufferClearMask: u32 { - const COLOR = gl::COLOR_BUFFER_BIT; - const DEPTH = gl::DEPTH_BUFFER_BIT; - const STENCIL = gl::STENCIL_BUFFER_BIT; - } -} - -#[derive(Debug)] -#[repr(u32)] -pub enum Capability -{ - DepthTest = gl::DEPTH_TEST, - MultiSample = gl::MULTISAMPLE, -} - -#[derive(Debug)] -#[repr(u32)] -pub enum PolygonMode -{ - Point = gl::POINT, - Line = gl::LINE, - Fill = gl::FILL, -} - -impl From<crate::draw_flags::PolygonMode> for PolygonMode -{ - fn from(mode: crate::draw_flags::PolygonMode) -> Self - { - match mode { - crate::draw_flags::PolygonMode::Point => Self::Point, - crate::draw_flags::PolygonMode::Fill => Self::Fill, - crate::draw_flags::PolygonMode::Line => Self::Line, - } - } -} - -#[derive(Debug)] -#[repr(u32)] -pub enum PolygonModeFace -{ - Front = gl::FRONT, - Back = gl::BACK, - FrontAndBack = gl::FRONT_AND_BACK, -} - -impl From<crate::draw_flags::PolygonModeFace> for PolygonModeFace -{ - fn from(face: crate::draw_flags::PolygonModeFace) -> Self - { - match face { - crate::draw_flags::PolygonModeFace::Front => Self::Front, - crate::draw_flags::PolygonModeFace::Back => Self::Back, - crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, - } - } -} - -bitflags! { -#[derive(Debug, Clone, Copy)] -pub struct ContextFlags: u32 { - const FORWARD_COMPATIBLE = gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; - const DEBUG = gl::CONTEXT_FLAG_DEBUG_BIT; - const ROBUST_ACCESS = gl::CONTEXT_FLAG_ROBUST_ACCESS_BIT; -} -} diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs deleted file mode 100644 index a626fc7..0000000 --- a/engine/src/opengl/shader.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::ffi::CStr; -use std::ptr::null_mut; - -use crate::matrix::Matrix; -use crate::vector::Vec3; - -#[derive(Debug)] -pub struct Shader -{ - shader: gl::types::GLuint, -} - -impl Shader -{ - pub fn new(kind: Kind) -> Self - { - let shader = unsafe { gl::CreateShader(kind.into_gl()) }; - - Self { shader } - } - - pub fn set_source(&mut self, source: &str) -> Result<(), Error> - { - if !source.is_ascii() { - return Err(Error::SourceNotAscii); - } - - unsafe { - #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl::ShaderSource( - self.shader, - 1, - &source.as_ptr().cast(), - &(source.len() as gl::types::GLint), - ); - } - - Ok(()) - } - - pub fn compile(&mut self) -> Result<(), Error> - { - unsafe { - gl::CompileShader(self.shader); - } - - let mut compile_success = gl::types::GLint::default(); - - unsafe { - gl::GetShaderiv(self.shader, gl::COMPILE_STATUS, &mut compile_success); - } - - if compile_success == 0 { - let info_log = self.get_info_log(); - - return Err(Error::CompileFailed(info_log)); - } - - Ok(()) - } - - fn get_info_log(&self) -> String - { - let mut buf = vec![gl::types::GLchar::default(); 512]; - - unsafe { - #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl::GetShaderInfoLog( - self.shader, - buf.len() as gl::types::GLsizei, - null_mut(), - buf.as_mut_ptr(), - ); - } - - let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; - - unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } - } -} - -impl Drop for Shader -{ - fn drop(&mut self) - { - unsafe { - gl::DeleteShader(self.shader); - } - } -} - -/// Shader kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Kind -{ - Vertex, - Fragment, -} - -impl Kind -{ - fn into_gl(self) -> gl::types::GLenum - { - match self { - Self::Vertex => gl::VERTEX_SHADER, - Self::Fragment => gl::FRAGMENT_SHADER, - } - } -} - -/// Shader program -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Program -{ - program: gl::types::GLuint, -} - -impl Program -{ - pub fn new() -> Self - { - let program = unsafe { gl::CreateProgram() }; - - Self { program } - } - - pub fn attach(&mut self, shader: &Shader) - { - unsafe { - gl::AttachShader(self.program, shader.shader); - } - } - - pub fn link(&mut self) -> Result<(), Error> - { - unsafe { - gl::LinkProgram(self.program); - } - - let mut link_success = gl::types::GLint::default(); - - unsafe { - gl::GetProgramiv(self.program, gl::LINK_STATUS, &mut link_success); - } - - if link_success == 0 { - let info_log = self.get_info_log(); - - return Err(Error::CompileFailed(info_log)); - } - - Ok(()) - } - - pub fn activate(&self) - { - unsafe { - gl::UseProgram(self.program); - } - } - - pub fn set_uniform(&mut self, name: &CStr, var: &impl UniformVariable) - { - let location = UniformLocation(unsafe { - gl::GetUniformLocation(self.program, name.as_ptr().cast()) - }); - - var.set(self, location); - } - - fn get_info_log(&self) -> String - { - let mut buf = vec![gl::types::GLchar::default(); 512]; - - unsafe { - #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl::GetProgramInfoLog( - self.program, - buf.len() as gl::types::GLsizei, - null_mut(), - buf.as_mut_ptr(), - ); - } - - let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; - - unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } - } -} - -impl Drop for Program -{ - fn drop(&mut self) - { - unsafe { - gl::DeleteProgram(self.program); - } - } -} - -pub trait UniformVariable -{ - fn set(&self, program: &mut Program, uniform_location: UniformLocation); -} - -impl UniformVariable for f32 -{ - fn set(&self, program: &mut Program, uniform_location: UniformLocation) - { - unsafe { - gl::ProgramUniform1f(program.program, uniform_location.0, *self); - } - } -} - -impl UniformVariable for i32 -{ - fn set(&self, program: &mut Program, uniform_location: UniformLocation) - { - unsafe { - gl::ProgramUniform1i(program.program, uniform_location.0, *self); - } - } -} - -impl UniformVariable for Vec3<f32> -{ - fn set(&self, program: &mut Program, uniform_location: UniformLocation) - { - unsafe { - gl::ProgramUniform3f( - program.program, - uniform_location.0, - self.x, - self.y, - self.z, - ); - } - } -} - -impl UniformVariable for Matrix<f32, 4, 4> -{ - fn set(&self, program: &mut Program, uniform_location: UniformLocation) - { - unsafe { - gl::ProgramUniformMatrix4fv( - program.program, - uniform_location.0, - 1, - gl::FALSE, - self.as_ptr(), - ); - } - } -} - -#[derive(Debug)] -pub struct UniformLocation(gl::types::GLint); - -/// Shader error. -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("All characters in source are not within the ASCII range")] - SourceNotAscii, - - #[error("Failed to compile: {0}")] - CompileFailed(String), -} diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs deleted file mode 100644 index 52c8554..0000000 --- a/engine/src/opengl/texture.rs +++ /dev/null @@ -1,240 +0,0 @@ -use crate::data_types::dimens::Dimens; -use crate::texture::Properties; - -#[derive(Debug)] -pub struct Texture -{ - texture: gl::types::GLuint, -} - -impl Texture -{ - pub fn new() -> Self - { - let mut texture = gl::types::GLuint::default(); - - unsafe { - gl::CreateTextures(gl::TEXTURE_2D, 1, &mut texture); - }; - - Self { texture } - } - - pub fn bind(&self) - { - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.texture); - } - } - - pub fn generate( - &mut self, - dimens: Dimens<u32>, - data: &[u8], - pixel_data_format: PixelDataFormat, - ) - { - self.alloc_image(pixel_data_format, dimens, data); - - unsafe { - gl::GenerateTextureMipmap(self.texture); - } - } - - pub fn apply_properties(&mut self, properties: &Properties) - { - self.set_wrap(properties.wrap); - self.set_magnifying_filter(properties.magnifying_filter); - self.set_minifying_filter(properties.minifying_filter); - } - - pub fn set_wrap(&mut self, wrapping: Wrapping) - { - let wrapping_gl = wrapping.to_gl(); - - #[allow(clippy::cast_possible_wrap)] - unsafe { - gl::TextureParameteri(self.texture, gl::TEXTURE_WRAP_S, wrapping_gl as i32); - gl::TextureParameteri(self.texture, gl::TEXTURE_WRAP_T, wrapping_gl as i32); - } - } - - pub fn set_magnifying_filter(&mut self, filtering: Filtering) - { - let filtering_gl = filtering.to_gl(); - - #[allow(clippy::cast_possible_wrap)] - unsafe { - gl::TextureParameteri( - self.texture, - gl::TEXTURE_MAG_FILTER, - filtering_gl as i32, - ); - } - } - - pub fn set_minifying_filter(&mut self, filtering: Filtering) - { - let filtering_gl = filtering.to_gl(); - - #[allow(clippy::cast_possible_wrap)] - unsafe { - gl::TextureParameteri( - self.texture, - gl::TEXTURE_MIN_FILTER, - filtering_gl as i32, - ); - } - } - - fn alloc_image( - &mut self, - pixel_data_format: PixelDataFormat, - dimens: Dimens<u32>, - data: &[u8], - ) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::TextureStorage2D( - self.texture, - 1, - pixel_data_format.to_sized_internal_format(), - dimens.width as i32, - dimens.height as i32, - ); - - #[allow(clippy::cast_possible_wrap)] - gl::TextureSubImage2D( - self.texture, - 0, - 0, - 0, - dimens.width as i32, - dimens.height as i32, - pixel_data_format.to_format(), - gl::UNSIGNED_BYTE, - data.as_ptr().cast(), - ); - } - } -} - -impl Drop for Texture -{ - fn drop(&mut self) - { - unsafe { - gl::DeleteTextures(1, &self.texture); - } - } -} - -/// Texture wrapping. -#[derive(Debug, Clone, Copy)] -pub enum Wrapping -{ - Repeat, - MirroredRepeat, - ClampToEdge, - ClampToBorder, -} - -impl Wrapping -{ - fn to_gl(self) -> gl::types::GLenum - { - match self { - Self::Repeat => gl::REPEAT, - Self::MirroredRepeat => gl::MIRRORED_REPEAT, - Self::ClampToEdge => gl::CLAMP_TO_EDGE, - Self::ClampToBorder => gl::CLAMP_TO_BORDER, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Filtering -{ - Nearest, - Linear, -} - -impl Filtering -{ - fn to_gl(self) -> gl::types::GLenum - { - match self { - Self::Linear => gl::LINEAR, - Self::Nearest => gl::NEAREST, - } - } -} - -/// Texture pixel data format. -#[derive(Debug, Clone, Copy)] -pub enum PixelDataFormat -{ - Rgb8, - Rgba8, -} - -impl PixelDataFormat -{ - fn to_sized_internal_format(self) -> gl::types::GLenum - { - match self { - Self::Rgb8 => gl::RGB8, - Self::Rgba8 => gl::RGBA8, - } - } - - fn to_format(self) -> gl::types::GLenum - { - match self { - Self::Rgb8 => gl::RGB, - Self::Rgba8 => gl::RGBA, - } - } -} - -pub fn set_active_texture_unit(texture_unit: TextureUnit) -{ - unsafe { - gl::ActiveTexture(texture_unit.into_gl()); - } -} - -macro_rules! texture_unit_enum { - (cnt=$cnt: literal) => { - seq_macro::seq!(N in 0..$cnt { - #[derive(Debug, Clone, Copy)] - pub enum TextureUnit { - #( - No~N, - )* - } - - impl TextureUnit { - fn into_gl(self) -> gl::types::GLenum { - match self { - #( - Self::No~N => gl::TEXTURE~N, - )* - } - } - - pub fn from_num(num: usize) -> Option<Self> { - match num { - #( - N => Some(Self::No~N), - )* - _ => None - } - } - } - }); - }; -} - -texture_unit_enum!(cnt = 31); diff --git a/engine/src/opengl/util.rs b/engine/src/opengl/util.rs deleted file mode 100644 index e60778f..0000000 --- a/engine/src/opengl/util.rs +++ /dev/null @@ -1,30 +0,0 @@ -// May only be used when certain crate features are enabled -#![allow(unused_macros, unused_imports)] - -macro_rules! gl_enum { - ( - $visibility: vis enum $name: ident - {$( - $variant: ident = gl::$gl_enum: ident, - )+} - ) => { - #[derive(Debug, Clone, Copy)] - #[repr(u32)] - $visibility enum $name - {$( - $variant = gl::$gl_enum, - )+} - - impl $name { - fn from_gl(num: gl::types::GLenum) -> Option<Self> - { - match num { - $(gl::$gl_enum => Some(Self::$variant),)+ - _ => None - } - } - } - }; -} - -pub(crate) use gl_enum; diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs deleted file mode 100644 index 1f8a870..0000000 --- a/engine/src/opengl/vertex_array.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::mem::size_of; - -use crate::opengl::buffer::Buffer; - -#[derive(Debug)] -pub struct VertexArray -{ - array: gl::types::GLuint, -} - -impl VertexArray -{ - pub fn new() -> Self - { - let mut array = 0; - - unsafe { - gl::CreateVertexArrays(1, &mut array); - } - - Self { array } - } - - /// Draws the currently bound vertex array. - pub fn draw_arrays(primitive_kind: PrimitiveKind, start_index: u32, cnt: u32) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::DrawArrays( - primitive_kind.into_gl(), - start_index as gl::types::GLint, - cnt as gl::types::GLsizei, - ); - } - } - - /// Draws the currently bound vertex array. - pub fn draw_elements(primitive_kind: PrimitiveKind, offset: u32, cnt: u32) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::DrawElements( - primitive_kind.into_gl(), - cnt as gl::types::GLsizei, - gl::UNSIGNED_INT, - (offset as gl::types::GLint) as *const _, - ); - } - } - - pub fn bind_element_buffer(&mut self, element_buffer: &Buffer<u32>) - { - unsafe { - gl::VertexArrayElementBuffer(self.array, element_buffer.object()); - } - } - - pub fn bind_vertex_buffer<VertexT>( - &mut self, - binding_index: u32, - vertex_buffer: &Buffer<VertexT>, - offset: isize, - ) - { - unsafe { - gl::VertexArrayVertexBuffer( - self.array, - binding_index, - vertex_buffer.object(), - offset, - size_of::<VertexT>() as i32, - ); - } - } - - pub fn enable_attrib(&mut self, attrib_index: u32) - { - unsafe { - gl::EnableVertexArrayAttrib(self.array, attrib_index as gl::types::GLuint); - } - } - - pub fn set_attrib_format( - &mut self, - attrib_index: u32, - data_type: DataType, - normalized: bool, - offset: u32, - ) - { - unsafe { - #[allow(clippy::cast_possible_wrap)] - gl::VertexArrayAttribFormat( - self.array, - attrib_index, - data_type.size() as gl::types::GLint, - data_type as u32, - if normalized { gl::TRUE } else { gl::FALSE }, - offset, - ); - } - } - - /// Associate a vertex attribute and a vertex buffer binding. - pub fn set_attrib_vertex_buf_binding( - &mut self, - attrib_index: u32, - vertex_buf_binding_index: u32, - ) - { - unsafe { - gl::VertexArrayAttribBinding( - self.array, - attrib_index, - vertex_buf_binding_index, - ); - } - } - - pub fn bind(&self) - { - unsafe { gl::BindVertexArray(self.array) } - } -} - -#[derive(Debug)] -pub enum PrimitiveKind -{ - Triangles, -} - -impl PrimitiveKind -{ - fn into_gl(self) -> gl::types::GLenum - { - match self { - Self::Triangles => gl::TRIANGLES, - } - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(u32)] -pub enum DataType -{ - Float = gl::FLOAT, -} - -impl DataType -{ - pub fn size(self) -> u32 - { - #[allow(clippy::cast_possible_truncation)] - match self { - Self::Float => size_of::<gl::types::GLfloat>() as u32, - } - } -} diff --git a/engine/src/projection.rs b/engine/src/projection.rs index faa741f..115ca39 100644 --- a/engine/src/projection.rs +++ b/engine/src/projection.rs @@ -1,6 +1,6 @@ use crate::data_types::dimens::Dimens3; use crate::matrix::Matrix; -use crate::util::builder; +use crate::builder; use crate::vector::Vec3; #[derive(Debug)] diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs index 17bc925..6d25f6b 100644 --- a/engine/src/renderer.rs +++ b/engine/src/renderer.rs @@ -1,7 +1,80 @@ use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::static_entity; +use ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE}; +use ecs::{declare_entity, Component}; + +use crate::builder; pub mod opengl; -static_entity!(pub RENDER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); +declare_entity!( + pub RENDER_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*POST_UPDATE_PHASE) + .build() + ) +); + +builder! { +/// Window graphics properties. +#[builder(name=GraphicsPropertiesBuilder, derives=(Debug, Clone))] +#[derive(Debug, Clone, Component)] +#[non_exhaustive] +pub struct GraphicsProperties +{ + /// Number of samples for multisampling. `None` means no multisampling. + #[builder(skip_generate_fn)] + pub multisampling_sample_cnt: Option<u8>, + + /// Whether graphics API debugging is enabled. + pub debug: bool, + + /// Whether depth testing is enabled + pub depth_test: bool, +} +} + +impl GraphicsProperties +{ + pub fn builder() -> GraphicsPropertiesBuilder + { + GraphicsPropertiesBuilder::default() + } +} + +impl Default for GraphicsProperties +{ + fn default() -> Self + { + Self::builder().build() + } +} + +impl GraphicsPropertiesBuilder +{ + pub fn multisampling_sample_cnt(mut self, multisampling_sample_cnt: u8) -> Self + { + self.multisampling_sample_cnt = Some(multisampling_sample_cnt); + self + } + + pub fn no_multisampling(mut self) -> Self + { + self.multisampling_sample_cnt = None; + self + } +} + +impl Default for GraphicsPropertiesBuilder +{ + fn default() -> Self + { + Self { + multisampling_sample_cnt: Some(8), + debug: false, + depth_test: true, + } + } +} diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 858a899..fb7dfbe 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -1,87 +1,173 @@ //! OpenGL renderer. +use std::any::type_name; use std::collections::HashMap; -use std::ffi::{c_void, CString}; +use std::ffi::CString; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::path::Path; -use std::process::abort; use ecs::actions::Actions; -use ecs::component::local::Local; use ecs::component::Handle as ComponentHandle; -use ecs::phase::START as START_PHASE; +use ecs::entity::obtainer::Obtainer as EntityObtainer; +use ecs::event::component::{Changed, Removed}; +use ecs::pair::{ChildOf, Pair, Wildcard}; +use ecs::phase::Phase; use ecs::query::term::Without; use ecs::sole::Single; -use ecs::system::{Into as _, System}; -use ecs::{Component, Query}; - -use crate::camera::{Active as ActiveCamera, Camera}; -use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; -use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; -use crate::material::{Flags as MaterialFlags, Material}; -use crate::matrix::Matrix; -use crate::mesh::Mesh; -use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; -use crate::opengl::debug::{ - enable_debug_output, +use ecs::system::observer::Observe; +use ecs::{declare_entity, Component, Query}; +use glutin::display::GetGlDisplay; +use glutin::prelude::GlDisplay; +use glutin::surface::GlSurface; +use opengl_bindings::debug::{ set_debug_message_callback, set_debug_message_control, MessageIdsAction, MessageSeverity, MessageSource, MessageType, + SetDebugMessageControlError as GlSetDebugMessageControlError, }; -use crate::opengl::glsl::{ - preprocess as glsl_preprocess, - PreprocessingError as GlslPreprocessingError, +use opengl_bindings::misc::{ + clear_buffers, + enable, + set_enabled, + BufferClearMask, + Capability, + SetViewportError as GlSetViewportError, }; -use crate::opengl::shader::{ +use opengl_bindings::shader::{ Error as GlShaderError, Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; -use crate::opengl::texture::{ - set_active_texture_unit, +use opengl_bindings::texture::{ + Filtering as GlTextureFiltering, + GenerateError as GlTextureGenerateError, + PixelDataFormat as GlTexturePixelDataFormat, Texture as GlTexture, - TextureUnit, + Wrapping as GlTextureWrapping, }; -use crate::opengl::vertex_array::{ - DataType as VertexArrayDataType, +use opengl_bindings::vertex_array::{ + DrawError as GlDrawError, PrimitiveKind, VertexArray, }; -use crate::opengl::{ - clear_buffers, - enable, - get_context_flags as get_opengl_context_flags, - BufferClearMask, - Capability, - ContextFlags, +use opengl_bindings::{ContextWithFns, CurrentContextWithFns}; +use safer_ffi::layout::ReprC; + +use crate::asset::{Assets, Id as AssetId}; +use crate::camera::{Active as ActiveCamera, Camera}; +use crate::color::Color; +use crate::data_types::dimens::Dimens; +use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; +use crate::image::{ColorType as ImageColorType, Image}; +use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; +use crate::material::{Flags as MaterialFlags, Material}; +use crate::matrix::Matrix; +use crate::model::Model; +use crate::opengl::glsl::{ + preprocess as glsl_preprocess, + PreprocessingError as GlslPreprocessingError, }; use crate::projection::{ClipVolume, Projection}; -use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex}; -use crate::renderer::RENDER_PHASE; -use crate::texture::{Id as TextureId, Texture}; +use crate::renderer::opengl::glutin_compat::{ + DisplayBuilder, + Error as GlutinCompatError, +}; +use crate::renderer::opengl::graphics_mesh::GraphicsMesh; +use crate::renderer::{GraphicsProperties, RENDER_PHASE}; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Wrapping as TextureWrapping, +}; use crate::transform::{Scale, WorldPosition}; -use crate::util::{defer, Defer, RefOrValue}; +use crate::util::MapVec; use crate::vector::{Vec2, Vec3}; -use crate::window::Window; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady, + Window, +}; +use crate::windowing::Context as WindowingContext; +mod glutin_compat; +mod graphics_mesh; mod vertex; +const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0; +const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1; +const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2; + type RenderableEntity<'a> = ( - &'a Mesh, - &'a Material, + &'a Model, Option<&'a MaterialFlags>, Option<&'a WorldPosition>, Option<&'a Scale>, Option<&'a DrawFlags>, - Option<&'a GlObjects>, + &'a [Pair<DataInGraphicsContext, Wildcard>], ); +declare_entity!( + pub POST_RENDER_PHASE, + (Phase, Pair::builder().relation::<ChildOf>().target_id(*RENDER_PHASE).build()) +); + +#[derive(Debug, Component)] +struct WithGraphicsContext; + +#[derive(Debug, Component)] +struct WindowGlConfig +{ + gl_config: glutin::config::Config, +} + +#[derive(Debug, Component)] +struct WindowGraphicsSurface +{ + surface: glutin::surface::Surface<glutin::surface::WindowSurface>, +} + +#[derive(Component)] +struct GraphicsContext +{ + context: ContextWithFns, + shader_program: Option<GlShaderProgram>, + textures_objs: HashMap<AssetId, GlTexture>, + default_1x1_texture_obj: Option<GlTexture>, + graphics_mesh_store: GraphicsMeshStore, +} + +#[derive(Debug, Default)] +struct GraphicsMeshStore +{ + graphics_meshes: MapVec<GraphicsMeshId, GraphicsMesh>, + next_id: GraphicsMeshId, +} + +impl GraphicsMeshStore +{ + fn insert(&mut self, graphics_mesh: GraphicsMesh) -> GraphicsMeshId + { + let id = self.next_id; + + self.graphics_meshes.insert(id, graphics_mesh); + + self.next_id.inner += 1; + + id + } +} + +#[derive(Debug, Component)] +struct DataInGraphicsContext +{ + graphics_mesh_id: GraphicsMeshId, +} + #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} @@ -90,202 +176,904 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system(*START_PHASE, initialize); + collector.add_declared_entity(&RENDER_PHASE); + collector.add_declared_entity(&POST_RENDER_PHASE); - collector.add_system( - *RENDER_PHASE, - render - .into_system() - .initialize((GlobalGlObjects::default(),)), - ); + collector.add_system(*RENDER_PHASE, render); + + collector.add_system(*POST_RENDER_PHASE, prepare_windows); + collector.add_system(*POST_RENDER_PHASE, init_window_graphics); + + collector.add_observer(handle_model_removed); + + collector.add_observer(handle_window_changed); + collector.add_observer(handle_window_removed); + } +} + +#[tracing::instrument(skip_all)] +fn handle_model_removed(observe: Observe<Pair<Removed, Model>>, mut actions: Actions) +{ + for evt_match in &observe { + let ent_id = evt_match.id(); + + tracing::debug!(entity_id=%ent_id, "Cleaning up after model"); + + let ent = evt_match.get_ent_infallible(); + + for data_in_graphics_ctx_pair in + ent.get_wildcard_pair_matches::<DataInGraphicsContext, Wildcard>() + { + actions.remove_components(ent_id, [data_in_graphics_ctx_pair.id()]); + + let Some(graphics_context_ent) = data_in_graphics_ctx_pair.get_target_ent() + else { + tracing::trace!( + concat!( + "Graphics context referenced by pair ({}, {}) does not exist. ", + "Skipping cleanup of this model" + ), + type_name::<DataInGraphicsContext>(), + data_in_graphics_ctx_pair.id().target_entity() + ); + + continue; + }; + + let Some(data_in_graphics_ctx) = + data_in_graphics_ctx_pair.get_data_as_relation() + else { + unreachable!(); + }; + + let Some(mut graphics_context) = + graphics_context_ent.get_mut::<GraphicsContext>() + else { + tracing::trace!( + "Graphics context entity {} does not have a {} component", + graphics_context_ent.uid(), + type_name::<GraphicsContext>() + ); + continue; + }; + + graphics_context + .graphics_mesh_store + .graphics_meshes + .remove(data_in_graphics_ctx.graphics_mesh_id); + } } } -fn initialize(window: Single<Window>) +#[tracing::instrument(skip_all)] +fn handle_window_changed( + observe: Observe<Pair<Changed, Window>>, + entity_obtainer: EntityObtainer, +) { - window - .make_context_current() - .expect("Failed to make window context current"); + for evt_match in &observe { + let window_ent = evt_match.get_ent_infallible(); + + tracing::trace!( + new_state = ?evt_match.get_changed_comp(), + "Handling window change" + ); + + let Some(window_graphics_surface) = window_ent.get::<WindowGraphicsSurface>() + else { + continue; + }; - gl::load_with(|symbol| match window.get_proc_address(symbol) { - Ok(addr) => addr as *const c_void, - Err(err) => { - println!( - "FATAL ERROR: Failed to get adress of OpenGL function {symbol}: {err}", + let Some(graphics_context_ent_id) = window_ent + .get_matching_components( + Pair::builder() + .relation::<WithGraphicsContext>() + .target_id(Wildcard::uid()) + .build() + .id(), + ) + .next() + .map(|comp_ref| comp_ref.id().target_entity()) + else { + continue; + }; + + let Some(graphics_context_ent) = + entity_obtainer.get_entity(graphics_context_ent_id) + else { + tracing::error!("Graphics context entity does not exist"); + continue; + }; + + let Some(graphics_context) = graphics_context_ent.get::<GraphicsContext>() else { + tracing::error!( + "Graphics context entity does not have a GraphicsContext component" ); + continue; + }; - abort(); + let Ok(current_graphics_context) = graphics_context + .context + .make_current(&window_graphics_surface.surface) + else { + tracing::error!("Failed to make graphics context current"); + continue; + }; + + if let Err(err) = set_viewport( + ¤t_graphics_context, + Vec2::default(), + evt_match.get_changed_comp().inner_size(), + ) { + tracing::error!("Failed to set viewport: {err}"); } - }); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_removed(observe: Observe<Pair<Removed, Window>>, mut actions: Actions) +{ + for evt_match in &observe { + let window_ent_id = evt_match.id(); + + let window_ent = evt_match.get_ent_infallible(); + + tracing::debug!( + entity_id = %window_ent_id, + title = %evt_match.get_removed_comp().title, + "Handling removal of window" + ); - if get_opengl_context_flags().contains(ContextFlags::DEBUG) { - initialize_debug(); + actions.remove_comps::<(WindowGraphicsSurface, WindowGlConfig)>(window_ent_id); + + let Some(with_graphics_ctx_pair_handle) = + window_ent.get_first_wildcard_pair_match::<WithGraphicsContext, Wildcard>() + else { + tracing::warn!("Window entity is missing a (WithGraphicsContext, *) pair"); + continue; + }; + + let graphics_context_ent_id = with_graphics_ctx_pair_handle.id().target_entity(); + + actions.remove_comps::<(GraphicsContext,)>(graphics_context_ent_id); + + actions.remove_components(window_ent_id, [with_graphics_ctx_pair_handle.id()]); } +} - let window_size = window.size().expect("Failed to get window size"); +#[derive(Debug, Component)] +struct SetupFailed; + +// fn on_window_creation_attrs_added( +// observe: Observe<Pair<Added, WindowCreationAttributes>>, +// windowing: Single<Windowing>, +// window_store: Single<WindowStore>, +// mut actions: Actions, +// ) +// { +// for evt_match in &observe { +// let Some(ent) = evt_match.get_entity() else { +// unreachable!(); +// }; +// +// if ent.has_component(WindowGlConfig::id()) || +// ent.has_component(WindowClosed::id()) || ent.has_component() {} } +// } + +fn prepare_windows( + window_query: Query< + ( + Option<&Window>, + &mut WindowCreationAttributes, + Option<&GraphicsProperties>, + ), + ( + Without<CreationReady>, + Without<WindowGlConfig>, + Without<WindowClosed>, + Without<SetupFailed>, + ), + >, + windowing_context: Single<WindowingContext>, + mut actions: Actions, +) +{ + let Some(display_handle) = windowing_context.display_handle() else { + return; + }; + + for (window_ent_id, (window, mut window_creation_attrs, graphics_props)) in + window_query.iter_with_euids() + { + tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); - set_viewport(Vec2 { x: 0, y: 0 }, window_size); + let mut glutin_config_template_builder = + glutin::config::ConfigTemplateBuilder::new(); - window.set_framebuffer_size_callback(|new_window_size| { - set_viewport(Vec2::ZERO, new_window_size); - }); + let graphics_props = match graphics_props.as_ref() { + Some(graphics_props) => &*graphics_props, + None => { + actions.add_components(window_ent_id, (GraphicsProperties::default(),)); + + &GraphicsProperties::default() + } + }; + + if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { + glutin_config_template_builder = glutin_config_template_builder + .with_multisampling(multisampling_sample_cnt); + } - enable(Capability::DepthTest); - enable(Capability::MultiSample); + let window_handle = match window + .as_ref() + .map(|window| unsafe { + windowing_context.get_window_as_handle(&window.wid()) + }) + .flatten() + .transpose() + { + Ok(window_handle) => window_handle, + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + }; + + let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new() + .with_window_attributes(window_creation_attrs.clone()) + .build( + window_handle, + &display_handle, + glutin_config_template_builder, + |mut cfgs| cfgs.next(), + ) { + Ok((new_window_creation_attrs, gl_config)) => { + (new_window_creation_attrs, gl_config) + } + Err(GlutinCompatError::WindowRequired) => { + actions.add_components(window_ent_id, (CreationReady,)); + continue; + } + Err(err) => { + tracing::error!("Failed to create platform graphics display: {err}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + }; + + *window_creation_attrs = new_window_creation_attrs; + + // let gl_config_template = glutin_config_template_builder.build(); + // + // let display = match glutin_winit_compat::create_display( + // unsafe { engine_display.as_display_handle() }, + // glutin_winit_compat::ApiPreference::default(), + // None, + // ) { + // Ok(gl_display) => gl_display, + // Err(err) => { + // tracing::error!("Failed to create graphics platform display: {err}"); + // continue; + // } + // }; + // + // let mut gl_configs = match unsafe { display.find_configs(gl_config_template) } + // { Ok(gl_configs) => gl_configs, + // Err(err) => { + // tracing::error!("Failed to find GL configs: {err:?}"); + // continue; + // } + // }; + // + // let Some(first_gl_config) = gl_configs.next() else { + // tracing::error!("No matching GL configuration exists"); + // continue; + // }; + // + // *window_creation_attrs = finalize_window_creation_attrs( + // window_creation_attrs.clone(), + // &first_gl_config, + // ); + + actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); + + if window.is_none() { + actions.add_components(window_ent_id, (CreationReady,)); + } + } } +#[tracing::instrument(skip_all)] +fn init_window_graphics( + window_query: Query< + (&Window, &WindowGlConfig, &GraphicsProperties), + (Without<WindowGraphicsSurface>, Without<SetupFailed>), + >, + mut actions: Actions, + windowing_context: Single<WindowingContext>, +) +{ + for (window_ent_id, (window, window_gl_config, graphics_props)) in + window_query.iter_with_euids() + { + tracing::info!("Initializing graphics for window {window_ent_id}"); + + let display = window_gl_config.gl_config.display(); + + let window_handle = + match unsafe { windowing_context.get_window_as_handle(&window.wid()) } + .transpose() + { + Ok(Some(window_handle)) => window_handle, + Ok(None) => { + tracing::error!( + wid = ?window.wid(), + entity_id = %window_ent_id, + "Windowing context does not contain window" + ); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + }; + + let Some(window_inner_size) = window.inner_size().try_into_nonzero() else { + tracing::error!( + "Cannot create a surface for a window with a width/height of 0", + ); + continue; + }; + + let surface = match unsafe { + display.create_window_surface( + &window_gl_config.gl_config, + &glutin::surface::SurfaceAttributesBuilder::< + glutin::surface::WindowSurface, + >::new() + .build( + window_handle.as_raw(), + window_inner_size.width, + window_inner_size.height, + ), + ) + } { + Ok(surface) => surface, + Err(err) => { + tracing::error!("Failed to create window surface: {err}"); + continue; + } + }; + + let context = match unsafe { + display.create_context( + &window_gl_config.gl_config, + &glutin::context::ContextAttributesBuilder::new() + .with_debug(graphics_props.debug) + .build(Some(window_handle.as_raw())), + ) + } { + Ok(context) => context, + Err(err) => { + tracing::error!("Failed to create graphics context: {err}"); + continue; + } + }; + + let context = match ContextWithFns::new(context, &surface) { + Ok(context) => context, + Err(err) => { + tracing::error!("Failed to create graphics context: {err}"); + continue; + } + }; + + let Ok(current_graphics_context) = context.make_current(&surface) else { + tracing::error!("Failed to make graphics context current"); + continue; + }; + + if let Err(err) = set_viewport( + ¤t_graphics_context, + Vec2 { x: 0, y: 0 }, + window.inner_size(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + + set_enabled( + ¤t_graphics_context, + Capability::DepthTest, + graphics_props.depth_test, + ); + + set_enabled( + ¤t_graphics_context, + Capability::MultiSample, + graphics_props.multisampling_sample_cnt.is_some(), + ); + + if graphics_props.debug { + enable(¤t_graphics_context, Capability::DebugOutput); + enable( + ¤t_graphics_context, + Capability::DebugOutputSynchronous, + ); + + set_debug_message_callback( + ¤t_graphics_context, + opengl_debug_message_cb, + ); + + match set_debug_message_control( + ¤t_graphics_context, + None, + None, + None, + &[], + MessageIdsAction::Disable, + ) { + Ok(()) => {} + Err(GlSetDebugMessageControlError::TooManyIds { + id_cnt: _, + max_id_cnt: _, + }) => { + unreachable!() // No ids are given + } + } + } + + let graphics_context_ent_id = actions.spawn((GraphicsContext { + context, + shader_program: None, + textures_objs: HashMap::new(), + default_1x1_texture_obj: None, + graphics_mesh_store: GraphicsMeshStore::default(), + },)); + + actions.add_components( + window_ent_id, + ( + WindowGraphicsSurface { surface }, + Pair::builder() + .relation::<WithGraphicsContext>() + .target_id(graphics_context_ent_id) + .build(), + ), + ); + } +} + +#[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] fn render( query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, point_light_query: Query<(&PointLight, &WorldPosition)>, directional_lights: Query<(&DirectionalLight,)>, camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, - window: Single<Window>, + window_query: Query<( + &Window, + &WindowGraphicsSurface, + &GraphicsProperties, + Pair<WithGraphicsContext, Wildcard>, + )>, global_light: Single<GlobalLight>, - mut gl_objects: Local<GlobalGlObjects>, + assets: Single<Assets>, mut actions: Actions, ) { - let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { - tracing::warn!("No current camera. Nothing will be rendered"); - return; - }; + for ( + window_ent_id, + (window, window_graphics_surface, window_graphics_props, graphics_context_pair), + ) in window_query.iter_with_euids() + { + let Some(graphics_context_ent) = graphics_context_pair.get_target_ent() else { + tracing::error!("Window's associated graphics context entity does not exist"); + actions.remove_components(window_ent_id, [graphics_context_pair.id()]); + continue; + }; - let directional_lights = directional_lights.iter().collect::<Vec<_>>(); + let Some(mut graphics_context) = + graphics_context_ent.get_mut::<GraphicsContext>() + else { + tracing::error!( + "Graphics context entity does not have a GraphicsContext component" + ); + return; + }; - let GlobalGlObjects { - shader_program, - textures: gl_textures, - } = &mut *gl_objects; + let GraphicsContext { + ref context, + ref mut shader_program, + ref mut textures_objs, + ref mut default_1x1_texture_obj, + ref mut graphics_mesh_store, + } = *graphics_context; + + let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { + tracing::warn!("No current camera. Nothing will be rendered"); + return; + }; - let shader_program = - shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); + let Ok(current_graphics_context) = + context.make_current(&window_graphics_surface.surface) + else { + tracing::error!("Failed to make graphics context current"); + continue; + }; - clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); + let directional_lights = directional_lights.iter().collect::<Vec<_>>(); - for ( - euid, - (mesh, material, material_flags, position, scale, draw_flags, gl_objects), - ) in query.iter_with_euids() - { - let material_flags = material_flags - .map(|material_flags| material_flags.clone()) - .unwrap_or_default(); + let shader_program = shader_program.get_or_insert_with(|| { + create_default_shader_program(¤t_graphics_context).unwrap() + }); - let gl_objs = match gl_objects.as_deref() { - Some(gl_objs) => RefOrValue::Ref(gl_objs), - None => RefOrValue::Value(Some(GlObjects::new(&mesh))), - }; + let mut clear_mask = BufferClearMask::COLOR; - defer!(|gl_objs| { - if let RefOrValue::Value(opt_gl_objs) = gl_objs { - actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); - }; - }); + clear_mask.set(BufferClearMask::DEPTH, window_graphics_props.depth_test); - apply_transformation_matrices( - Transformation { - position: position.map(|pos| *pos).unwrap_or_default().position, - scale: scale.map(|scale| *scale).unwrap_or_default().scale, - }, - shader_program, - &camera, - &camera_world_pos, - window.size().expect("Failed to get window size"), - ); + clear_buffers(¤t_graphics_context, clear_mask); - apply_light( - &material, - &material_flags, - &global_light, - shader_program, - (point_light_query.iter(), point_light_query.iter().count()), - directional_lights - .iter() - .map(|(dir_light,)| &**dir_light) - .collect::<Vec<_>>() - .as_slice(), - &camera_world_pos, - ); + for ( + euid, + ( + model, + material_flags, + position, + scale, + draw_flags, + data_in_graphics_ctx_pairs, + ), + ) in query.iter_with_euids() + { + let Some(model_data) = assets.get(&model.asset_handle) else { + tracing::trace!("Missing model asset"); + continue; + }; - for (index, texture) in material.textures.iter().enumerate() { - let gl_texture = gl_textures - .entry(texture.id()) - .or_insert_with(|| create_gl_texture(texture)); + let material_flags = material_flags + .map(|material_flags| material_flags.clone()) + .unwrap_or_default(); + + let graphics_mesh_id = match data_in_graphics_ctx_pairs + .get_with_target_id(graphics_context_ent.uid()) + { + Some(data_in_graphics_ctx_pair) => { + let Some(data_in_graphics_ctx) = + data_in_graphics_ctx_pair.get_data::<DataInGraphicsContext>() + else { + tracing::warn!( + concat!( + "Pair with relation {} ({}) has no data or data with a ", + "wrong type. This pair will be removed" + ), + type_name::<DataInGraphicsContext>(), + data_in_graphics_ctx_pair.id() + ); + + actions.remove_components(euid, [data_in_graphics_ctx_pair.id()]); + continue; + }; + + data_in_graphics_ctx.graphics_mesh_id + } + None => { + let graphics_mesh = match GraphicsMesh::new( + ¤t_graphics_context, + &model_data.mesh, + ) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!( + "Failed to create {}: {err}", + type_name::<GraphicsMesh>() + ); + + // This system should not try again + actions.add_components(euid, (NoDraw,)); + + continue; + } + }; + + let graphics_mesh_id = graphics_mesh_store.insert(graphics_mesh); + + actions.add_components( + euid, + (Pair::builder() + .relation_as_data(DataInGraphicsContext { graphics_mesh_id }) + .target_id(graphics_context_ent.uid()) + .build(),), + ); + + graphics_mesh_id + } + }; - let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); + let Some(graphics_mesh) = + graphics_mesh_store.graphics_meshes.get(&graphics_mesh_id) + else { + tracing::error!("Graphics mesh with ID: {graphics_mesh_id:?} not found"); + continue; + }; - set_active_texture_unit(texture_unit); + apply_transformation_matrices( + ¤t_graphics_context, + Transformation { + position: position.map(|pos| *pos).unwrap_or_default().position, + scale: scale.map(|scale| *scale).unwrap_or_default().scale, + }, + shader_program, + &camera, + &camera_world_pos, + window.inner_size(), + ); - gl_texture.bind(); - } + if model_data.materials.len() > 1 { + tracing::warn!(concat!( + "Multiple model materials are not supported ", + "so only the first material will be used" + )); + } - shader_program.activate(); + let material = match model_data.materials.values().next() { + Some(material) => material, + None => { + tracing::warn!("Model has no materials. Using default material"); - if let Some(draw_flags) = &draw_flags { - crate::opengl::set_polygon_mode( - draw_flags.polygon_mode_config.face, - draw_flags.polygon_mode_config.mode, + &Material::default() + } + }; + + apply_light( + ¤t_graphics_context, + &material, + &material_flags, + &global_light, + shader_program, + (point_light_query.iter(), point_light_query.iter().count()), + directional_lights + .iter() + .map(|(dir_light,)| &**dir_light) + .collect::<Vec<_>>() + .as_slice(), + &camera_world_pos, ); - } - draw_mesh(gl_objs.get().unwrap()); + match create_bind_material_textures( + ¤t_graphics_context, + &material, + &assets, + textures_objs, + default_1x1_texture_obj, + ) { + Ok(()) => {} + Err(CreateBindMaterialTexturesError::MissingTextureAsset) => { + continue; + } + Err( + err @ CreateBindMaterialTexturesError::CreateTextureFailed { .. }, + ) => { + tracing::error!( + "Creating &/ binding material textures failed: {err}" + ); + + // This system should not try again + actions.add_components(euid, (NoDraw,)); + + continue; + } + } - if draw_flags.is_some() { - let default_polygon_mode_config = PolygonModeConfig::default(); + shader_program.activate(¤t_graphics_context); - crate::opengl::set_polygon_mode( - default_polygon_mode_config.face, - default_polygon_mode_config.mode, - ); + if let Some(draw_flags) = &draw_flags { + opengl_bindings::misc::set_polygon_mode( + ¤t_graphics_context, + draw_flags.polygon_mode_config.face, + draw_flags.polygon_mode_config.mode, + ); + } + + if let Err(err) = draw_mesh(¤t_graphics_context, &graphics_mesh) { + tracing::error!( + entity_id = %euid, + graphics_context_entity_id = %graphics_context_ent.uid(), + "Failed to draw mesh: {err}", + ); + + // This system should not try again + actions.add_components(euid, (NoDraw,)); + + continue; + }; + + if draw_flags.is_some() { + let default_polygon_mode_config = PolygonModeConfig::default(); + + opengl_bindings::misc::set_polygon_mode( + ¤t_graphics_context, + default_polygon_mode_config.face, + default_polygon_mode_config.mode, + ); + } + } + + if let Err(err) = window_graphics_surface + .surface + .swap_buffers(context.context()) + { + tracing::error!("Failed to swap buffers: {err}"); } } } -#[derive(Debug, Default, Component)] -struct GlobalGlObjects +fn create_default_texture(current_context: &CurrentContextWithFns<'_>) -> GlTexture { - shader_program: Option<GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, + match create_gl_texture( + current_context, + &Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8), + &TextureProperties::default(), + ) { + Ok(gl_texture) => gl_texture, + Err( + GlTextureGenerateError::SizeWidthValueTooLarge { value: _, max_value: _ } + | GlTextureGenerateError::SizeHeightValueTooLarge { value: _, max_value: _ }, + ) => unreachable!(), + } } -fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) +fn create_bind_material_textures( + current_context: &CurrentContextWithFns<'_>, + material: &Material, + assets: &Assets, + texture_objs: &mut HashMap<AssetId, GlTexture>, + default_1x1_texture_obj: &mut Option<GlTexture>, +) -> Result<(), CreateBindMaterialTexturesError> { - crate::opengl::set_viewport(position, size); + let material_texture_maps = [ + (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT), + (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT), + (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT), + ]; + + for (texture, texture_unit) in material_texture_maps { + let Some(texture) = texture else { + let gl_texture = default_1x1_texture_obj + .get_or_insert_with(|| create_default_texture(current_context)); + + gl_texture.bind_to_texture_unit(current_context, texture_unit); + + continue; + }; + + let texture_image_asset_id = texture.asset_handle.id(); + + let gl_texture = match texture_objs.get(&texture_image_asset_id) { + Some(gl_texture) => gl_texture, + None => { + let Some(image) = assets.get::<Image>(&texture.asset_handle) else { + tracing::trace!(handle=?texture.asset_handle, "Missing texture asset"); + return Err(CreateBindMaterialTexturesError::MissingTextureAsset); + }; + + texture_objs.entry(texture_image_asset_id).or_insert( + create_gl_texture(current_context, image, &texture.properties) + .map_err(|err| { + CreateBindMaterialTexturesError::CreateTextureFailed { + err, + image_asset_id: texture_image_asset_id, + } + })?, + ) + } + }; + + gl_texture.bind_to_texture_unit(current_context, texture_unit); + } + + Ok(()) } -fn initialize_debug() +#[derive(Debug, thiserror::Error)] +enum CreateBindMaterialTexturesError { - enable_debug_output(); + #[error("Missing texture asset")] + MissingTextureAsset, - set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); + #[error("Failed to create texture from image asset with ID {image_asset_id:?}")] + CreateTextureFailed + { + #[source] + err: GlTextureGenerateError, + image_asset_id: AssetId, + }, } -fn draw_mesh(gl_objects: &GlObjects) +fn set_viewport( + current_context: &CurrentContextWithFns<'_>, + position: Vec2<u32>, + size: &Dimens<u32>, +) -> Result<(), GlSetViewportError> { - gl_objects.vertex_arr.bind(); + let position = + opengl_bindings::data_types::Vec2::<u32> { x: position.x, y: position.y }; + + let size = opengl_bindings::data_types::Dimens::<u32> { + width: size.width, + height: size.height, + }; + + opengl_bindings::misc::set_viewport(current_context, &position, &size) +} - if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); +fn draw_mesh( + current_context: &CurrentContextWithFns<'_>, + graphics_mesh: &GraphicsMesh, +) -> Result<(), GlDrawError> +{ + graphics_mesh.vertex_arr.bind(current_context); + + if graphics_mesh.index_buffer.is_some() { + VertexArray::draw_elements( + current_context, + PrimitiveKind::Triangles, + 0, + graphics_mesh.element_cnt, + )?; } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); + VertexArray::draw_arrays( + current_context, + PrimitiveKind::Triangles, + 0, + graphics_mesh.element_cnt, + )?; } + + Ok(()) } -fn create_gl_texture(texture: &Texture) -> GlTexture +fn create_gl_texture( + current_context: &CurrentContextWithFns<'_>, + image: &Image, + texture_properties: &TextureProperties, +) -> Result<GlTexture, GlTextureGenerateError> { - let mut gl_texture = GlTexture::new(); + let gl_texture = GlTexture::new(current_context); gl_texture.generate( - *texture.dimensions(), - texture.image().as_bytes(), - texture.pixel_data_format(), + current_context, + &image.dimensions().into(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, + )?; + + gl_texture.set_wrap( + current_context, + texture_wrapping_to_gl(texture_properties.wrap), + ); + + gl_texture.set_magnifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.magnifying_filter), ); - gl_texture.apply_properties(texture.properties()); + gl_texture.set_minifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.minifying_filter), + ); - gl_texture + Ok(gl_texture) } const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); @@ -294,32 +1082,34 @@ const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl") const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl"); const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl"); -fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError> +fn create_default_shader_program( + current_context: &CurrentContextWithFns<'_>, +) -> Result<GlShaderProgram, CreateShaderError> { - let mut vertex_shader = GlShader::new(ShaderKind::Vertex); + let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); - vertex_shader.set_source(&*glsl_preprocess( - VERTEX_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; + vertex_shader.set_source( + current_context, + &*glsl_preprocess(VERTEX_GLSL_SHADER_SRC, &get_glsl_shader_content)?, + )?; - vertex_shader.compile()?; + vertex_shader.compile(current_context)?; - let mut fragment_shader = GlShader::new(ShaderKind::Fragment); + let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); - fragment_shader.set_source(&*glsl_preprocess( - FRAGMENT_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; + fragment_shader.set_source( + current_context, + &*glsl_preprocess(FRAGMENT_GLSL_SHADER_SRC, &get_glsl_shader_content)?, + )?; - fragment_shader.compile()?; + fragment_shader.compile(current_context)?; - let mut gl_shader_program = GlShaderProgram::new(); + let gl_shader_program = GlShaderProgram::new(current_context); - gl_shader_program.attach(&vertex_shader); - gl_shader_program.attach(&fragment_shader); + gl_shader_program.attach(current_context, &vertex_shader); + gl_shader_program.attach(current_context, &fragment_shader); - gl_shader_program.link()?; + gl_shader_program.link(current_context)?; Ok(gl_shader_program) } @@ -350,108 +1140,36 @@ fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> )) } -#[derive(Debug, Component)] -struct GlObjects +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct GraphicsMeshId { - /// Vertex and index buffer has to live as long as the vertex array - _vertex_buffer: Buffer<Vertex>, - index_buffer: Option<Buffer<u32>>, - element_cnt: u32, - - vertex_arr: VertexArray, -} - -impl GlObjects -{ - #[tracing::instrument(skip_all)] - fn new(mesh: &Mesh) -> Self - { - tracing::trace!( - "Creating vertex array, vertex buffer{}", - if mesh.indices().is_some() { - " and index buffer" - } else { - "" - } - ); - - let mut vertex_arr = VertexArray::new(); - let mut vertex_buffer = Buffer::new(); - - vertex_buffer.store_mapped(mesh.vertices(), BufferUsage::Static, |vertex| { - Vertex { - pos: vertex.pos, - texture_coords: vertex.texture_coords, - normal: vertex.normal, - } - }); - - vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0); - - let mut offset = 0u32; - - for attrib in Vertex::attrs() { - vertex_arr.enable_attrib(attrib.index); - - vertex_arr.set_attrib_format( - attrib.index, - match attrib.component_type { - AttributeComponentType::Float => VertexArrayDataType::Float, - }, - false, - offset, - ); - - vertex_arr.set_attrib_vertex_buf_binding(attrib.index, 0); - - offset += attrib.component_size * attrib.component_cnt as u32; - } - - if let Some(indices) = mesh.indices() { - let mut index_buffer = Buffer::new(); - - index_buffer.store(indices, BufferUsage::Static); - - vertex_arr.bind_element_buffer(&index_buffer); - - return Self { - _vertex_buffer: vertex_buffer, - index_buffer: Some(index_buffer), - element_cnt: indices - .len() - .try_into() - .expect("Mesh index count does not fit into a 32-bit unsigned int"), - vertex_arr, - }; - } - - Self { - _vertex_buffer: vertex_buffer, - index_buffer: None, - element_cnt: mesh - .vertices() - .len() - .try_into() - .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), - vertex_arr, - } - } + inner: usize, } fn apply_transformation_matrices( + current_context: &CurrentContextWithFns<'_>, transformation: Transformation, gl_shader_program: &mut GlShaderProgram, camera: &Camera, camera_world_pos: &WorldPosition, - window_size: Dimens<u32>, + window_size: &Dimens<u32>, ) { - gl_shader_program - .set_uniform(c"model", &create_transformation_matrix(transformation)); + gl_shader_program.set_uniform( + current_context, + c"model", + &opengl_bindings::data_types::Matrix { + items: create_transformation_matrix(transformation).items, + }, + ); let view_matrix = create_view_matrix(camera, &camera_world_pos.position); - gl_shader_program.set_uniform(c"view", &view_matrix); + gl_shader_program.set_uniform( + current_context, + c"view", + &opengl_bindings::data_types::Matrix { items: view_matrix.items }, + ); #[allow(clippy::cast_precision_loss)] let proj_matrix = match &camera.projection { @@ -463,10 +1181,15 @@ fn apply_transformation_matrices( .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne), }; - gl_shader_program.set_uniform(c"projection", &proj_matrix); + gl_shader_program.set_uniform( + current_context, + c"projection", + &opengl_bindings::data_types::Matrix { items: proj_matrix.items }, + ); } fn apply_light<'point_light>( + current_context: &CurrentContextWithFns<'_>, material: &Material, material_flags: &MaterialFlags, global_light: &GlobalLight, @@ -495,16 +1218,20 @@ fn apply_light<'point_light>( ); for (dir_light_index, dir_light) in directional_lights.iter().enumerate() { + let direction: opengl_bindings::data_types::Vec3<_> = dir_light.direction.into(); + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name( "directional_lights", dir_light_index, "direction", ), - &dir_light.direction, + &direction, ); set_light_phong_uniforms( + current_context, gl_shader_program, "directional_lights", dir_light_index, @@ -514,18 +1241,26 @@ fn apply_light<'point_light>( // There probably won't be more than 2147483648 directional lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl_shader_program - .set_uniform(c"directional_light_cnt", &(directional_lights.len() as i32)); + gl_shader_program.set_uniform( + current_context, + c"directional_light_cnt", + &(directional_lights.len() as i32), + ); for (point_light_index, (point_light, point_light_world_pos)) in point_light_iter.enumerate() { + let pos: opengl_bindings::data_types::Vec3<_> = + (point_light_world_pos.position + point_light.local_position).into(); + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name("point_lights", point_light_index, "position"), - &(point_light_world_pos.position + point_light.local_position), + &pos, ); set_light_phong_uniforms( + current_context, gl_shader_program, "point_lights", point_light_index, @@ -533,6 +1268,7 @@ fn apply_light<'point_light>( ); set_light_attenuation_uniforms( + current_context, gl_shader_program, "point_lights", point_light_index, @@ -542,55 +1278,67 @@ fn apply_light<'point_light>( // There probably won't be more than 2147483648 point lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl_shader_program.set_uniform(c"point_light_cnt", &(point_light_cnt as i32)); - gl_shader_program.set_uniform( - c"material.ambient", - &Vec3::from(if material_flags.use_ambient_color { + current_context, + c"point_light_cnt", + &(point_light_cnt as i32), + ); + + let ambient: opengl_bindings::data_types::Vec3<_> = + Vec3::from(if material_flags.use_ambient_color { material.ambient.clone() } else { global_light.ambient.clone() - }), - ); + }) + .into(); - gl_shader_program - .set_uniform(c"material.diffuse", &Vec3::from(material.diffuse.clone())); + gl_shader_program.set_uniform(current_context, c"material.ambient", &ambient); - #[allow(clippy::cast_possible_wrap)] - gl_shader_program - .set_uniform(c"material.specular", &Vec3::from(material.specular.clone())); + let diffuse: opengl_bindings::data_types::Vec3<_> = + Vec3::from(material.diffuse.clone()).into(); + + gl_shader_program.set_uniform(current_context, c"material.diffuse", &diffuse); + + let specular: opengl_bindings::data_types::Vec3<_> = + Vec3::from(material.specular.clone()).into(); - let texture_map = material - .textures - .iter() - .enumerate() - .map(|(index, texture)| (texture.id(), index)) - .collect::<HashMap<_, _>>(); + #[allow(clippy::cast_possible_wrap)] + gl_shader_program.set_uniform(current_context, c"material.specular", &specular); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( + current_context, c"material.ambient_map", - &(*texture_map.get(&material.ambient_map).unwrap() as i32), + &(AMBIENT_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( + current_context, c"material.diffuse_map", - &(*texture_map.get(&material.diffuse_map).unwrap() as i32), + &(DIFFUSE_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( + current_context, c"material.specular_map", - &(*texture_map.get(&material.specular_map).unwrap() as i32), + &(SPECULAR_MAP_TEXTURE_UNIT as i32), + ); + + gl_shader_program.set_uniform( + current_context, + c"material.shininess", + &material.shininess, ); - gl_shader_program.set_uniform(c"material.shininess", &material.shininess); + let view_pos: opengl_bindings::data_types::Vec3<_> = camera_world_pos.position.into(); - gl_shader_program.set_uniform(c"view_pos", &camera_world_pos.position); + gl_shader_program.set_uniform(current_context, c"view_pos", &view_pos); } fn set_light_attenuation_uniforms( + current_context: &CurrentContextWithFns<'_>, gl_shader_program: &mut GlShaderProgram, light_array: &str, light_index: usize, @@ -598,6 +1346,7 @@ fn set_light_attenuation_uniforms( ) { gl_shader_program.set_uniform( + current_context, &create_light_uniform_name( light_array, light_index, @@ -607,11 +1356,13 @@ fn set_light_attenuation_uniforms( ); gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"), &light.attenuation_params.linear, ); gl_shader_program.set_uniform( + current_context, &create_light_uniform_name( light_array, light_index, @@ -622,6 +1373,7 @@ fn set_light_attenuation_uniforms( } fn set_light_phong_uniforms( + current_context: &CurrentContextWithFns<'_>, gl_shader_program: &mut GlShaderProgram, light_array: &str, light_index: usize, @@ -629,13 +1381,23 @@ fn set_light_phong_uniforms( ) { gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "phong.diffuse"), - &Vec3::from(light.diffuse().clone()), + &opengl_bindings::data_types::Vec3 { + x: light.diffuse().red, + y: light.diffuse().green, + z: light.diffuse().blue, + }, ); gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "phong.specular"), - &Vec3::from(light.specular().clone()), + &opengl_bindings::data_types::Vec3 { + x: light.specular().red, + y: light.specular().green, + z: light.specular().blue, + }, ); } @@ -723,7 +1485,8 @@ fn opengl_debug_message_cb( let backtrace = Backtrace::capture(); if matches!(backtrace.status(), BacktraceStatus::Captured) { - event!(Level::TRACE, "{backtrace}"); + tracing::error!("{backtrace}"); + // event!(Level::TRACE, "{backtrace}"); } } MessageType::Other => { @@ -751,3 +1514,74 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4 matrix } + +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping +{ + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } +} + +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering +{ + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} + +impl<Value: ReprC + Copy> From<Vec2<Value>> for opengl_bindings::data_types::Vec2<Value> +{ + fn from(vec2: Vec2<Value>) -> Self + { + Self { x: vec2.x, y: vec2.y } + } +} + +impl<Value: ReprC + Copy> From<Vec3<Value>> for opengl_bindings::data_types::Vec3<Value> +{ + fn from(vec3: Vec3<Value>) -> Self + { + Self { x: vec3.x, y: vec3.y, z: vec3.z } + } +} + +impl<Value: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value> +{ + fn from(dimens: Dimens<Value>) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} + +impl From<crate::draw_flags::PolygonMode> for opengl_bindings::misc::PolygonMode +{ + fn from(mode: crate::draw_flags::PolygonMode) -> Self + { + match mode { + crate::draw_flags::PolygonMode::Point => Self::Point, + crate::draw_flags::PolygonMode::Fill => Self::Fill, + crate::draw_flags::PolygonMode::Line => Self::Line, + } + } +} + +impl From<crate::draw_flags::PolygonModeFace> for opengl_bindings::misc::PolygonModeFace +{ + fn from(face: crate::draw_flags::PolygonModeFace) -> Self + { + match face { + crate::draw_flags::PolygonModeFace::Front => Self::Front, + crate::draw_flags::PolygonModeFace::Back => Self::Back, + crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, + } + } +} diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs new file mode 100644 index 0000000..cfd6ea7 --- /dev/null +++ b/engine/src/renderer/opengl/glutin_compat.rs @@ -0,0 +1,268 @@ +// Original file: +// https://github.com/rust-windowing/glutin/blob/ +// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs +// +// Copyright © 2022 Kirill Chibisov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! This library provides helpers for cross-platform [`glutin`] bootstrapping +//! with [`winit`]. + +#![deny(rust_2018_idioms)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::all)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![cfg_attr(clippy, deny(warnings))] + +use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::display::{Display, DisplayApiPreference}; +use glutin::error::Error as GlutinError; +#[cfg(x11_platform)] +use glutin::platform::x11::X11GlConfigExt; +use glutin::prelude::*; +use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; + +use crate::windowing::window::CreationAttributes as WindowCreationAttributes; + +#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] +compile_error!("Please select at least one api backend"); + +/// The helper to perform [`Display`] creation and OpenGL platform +/// bootstrapping with the help of [`winit`] with little to no platform specific +/// code. +/// +/// This is only required for the initial setup. If you want to create +/// additional windows just use the [`finalize_window`] function and the +/// configuration you've used either for the original window or picked with the +/// existing [`Display`]. +/// +/// [`winit`]: winit +/// [`Display`]: glutin::display::Display +#[derive(Default, Debug, Clone)] +pub struct DisplayBuilder +{ + preference: ApiPreference, + window_attributes: WindowCreationAttributes, +} + +impl DisplayBuilder +{ + /// Create new display builder. + pub fn new() -> Self + { + Default::default() + } + + /// The preference in picking the configuration. + #[allow(dead_code)] + pub fn with_preference(mut self, preference: ApiPreference) -> Self + { + self.preference = preference; + self + } + + /// The window attributes to use when building a window. + /// + /// By default no window is created. + pub fn with_window_attributes( + mut self, + window_creation_attrs: WindowCreationAttributes, + ) -> Self + { + self.window_attributes = window_creation_attrs; + self + } + + /// Initialize the OpenGL platform and create a compatible window to use + /// with it when the [`WindowAttributes`] was passed with + /// [`Self::with_window_attributes()`]. It's optional, since on some + /// platforms like `Android` it is not available early on, so you want to + /// find configuration and later use it with the [`finalize_window`]. + /// But if you don't care about such platform you can always pass + /// [`WindowAttributes`]. + /// + /// # Api-specific + /// + /// **WGL:** - [`WindowAttributes`] **must** be passed in + /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, + /// otherwise only builtin functions like `glClear` will be available. + pub fn build<ConfigPickerFn>( + self, + window_handle: Option<WindowHandle<'_>>, + display_handle: &DisplayHandle<'_>, + template_builder: ConfigTemplateBuilder, + config_picker_fn: ConfigPickerFn, + ) -> Result<(WindowCreationAttributes, Config), Error> + where + ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>, + { + // XXX with WGL backend window should be created first. + let raw_window_handle = if cfg!(wgl_backend) { + let Some(window_handle) = window_handle else { + return Err(Error::WindowRequired); + }; + + Some(window_handle.as_raw()) + } else { + None + }; + + let gl_display = + create_display(display_handle, self.preference, raw_window_handle) + .map_err(Error::CreateDisplayFailed)?; + + // XXX the native window must be passed to config picker when WGL is used + // otherwise very limited OpenGL features will be supported. + #[cfg(wgl_backend)] + let template_builder = if let Some(raw_window_handle) = raw_window_handle { + template_builder.compatible_with_native_window(raw_window_handle) + } else { + template_builder + }; + + let template = template_builder.build(); + + // SAFETY: The RawWindowHandle passed on the config template + // (when cfg(wgl_backend)) will always point to a valid object since it is + // derived from the window_handle argument which when Some is a WindowHandle and + // WindowHandles always point to a valid object + let gl_configs = unsafe { gl_display.find_configs(template) } + .map_err(Error::FindConfigsFailed)?; + + let picked_gl_config = + config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; + + #[cfg(not(wgl_backend))] + let window_attrs = + { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) }; + + #[cfg(wgl_backend)] + let window_attrs = self.window_attributes; + + Ok((window_attrs, picked_gl_config)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to create display")] + CreateDisplayFailed(#[source] GlutinError), + + #[error("Failed to find configs")] + FindConfigsFailed(#[source] GlutinError), + + #[error("No config was picked by config picker function")] + NoConfigPicked, + + #[error("Window required for building display on current platform")] + WindowRequired, +} + +fn create_display( + display_handle: &DisplayHandle<'_>, + _api_preference: ApiPreference, + _raw_window_handle: Option<RawWindowHandle>, +) -> Result<Display, GlutinError> +{ + #[cfg(egl_backend)] + let _preference = DisplayApiPreference::Egl; + + #[cfg(glx_backend)] + let _preference = DisplayApiPreference::Glx(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )); + + #[cfg(cgl_backend)] + let _preference = DisplayApiPreference::Cgl; + + #[cfg(wgl_backend)] + let _preference = DisplayApiPreference::Wgl(_raw_window_handle); + + #[cfg(all(egl_backend, glx_backend))] + let _preference = match _api_preference { + ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + }; + + #[cfg(all(wgl_backend, egl_backend))] + let _preference = match _api_preference { + ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), + ApiPreference::FallbackEgl => { + DisplayApiPreference::WglThenEgl(_raw_window_handle) + } + }; + + let handle = display_handle.as_raw(); + unsafe { Ok(Display::new(handle, _preference)?) } +} + +/// Finalize [`Window`] creation by applying the options from the [`Config`], be +/// aware that it could remove incompatible options from the window builder like +/// `transparency`, when the provided config doesn't support it. +/// +/// [`Window`]: winit::window::Window +/// [`Config`]: glutin::config::Config +#[cfg(not(wgl_backend))] +fn finalize_window_creation_attrs( + mut attributes: WindowCreationAttributes, + gl_config: &Config, +) -> WindowCreationAttributes +{ + // Disable transparency if the end config doesn't support it. + if gl_config.supports_transparency() == Some(false) { + attributes = attributes.with_transparent(false); + } + + #[cfg(x11_platform)] + let attributes = if let Some(x11_visual) = gl_config.x11_visual() { + attributes.with_x11_visual(x11_visual.visual_id() as _) + } else { + attributes + }; + + attributes +} + +/// Simplified version of the [`DisplayApiPreference`] which is used to simplify +/// cross platform window creation. +/// +/// To learn about platform differences the [`DisplayApiPreference`] variants. +/// +/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ApiPreference +{ + /// Prefer `EGL` over system provider like `GLX` and `WGL`. + PreferEgl, + + /// Fallback to `EGL` when failed to create the system profile. + /// + /// This behavior is used by default. However consider using + /// [`Self::PreferEgl`] if you don't care about missing EGL features. + #[default] + FallbackEgl, +} diff --git a/engine/src/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs new file mode 100644 index 0000000..9b929c0 --- /dev/null +++ b/engine/src/renderer/opengl/graphics_mesh.rs @@ -0,0 +1,120 @@ +use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; +use opengl_bindings::vertex_array::{ + DataType as GlVertexArrayDataType, + VertexArray as GlVertexArray, +}; +use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns; + +use crate::mesh::Mesh; +use crate::renderer::opengl::vertex::{ + AttributeComponentType as VertexAttributeComponentType, + Vertex as RendererVertex, +}; + +#[derive(Debug)] +pub struct GraphicsMesh +{ + /// Vertex and index buffer has to live as long as the vertex array + _vertex_buffer: GlBuffer<RendererVertex>, + pub index_buffer: Option<GlBuffer<u32>>, + pub element_cnt: u32, + pub vertex_arr: GlVertexArray, +} + +impl GraphicsMesh +{ + #[tracing::instrument(skip_all)] + pub fn new( + current_context: &GlCurrentContextWithFns<'_>, + mesh: &Mesh, + ) -> Result<Self, Error> + { + tracing::trace!( + "Creating vertex array, vertex buffer{}", + if mesh.indices().is_some() { + " and index buffer" + } else { + "" + } + ); + + let vertex_arr = GlVertexArray::new(current_context); + let vertex_buffer = GlBuffer::new(current_context); + + vertex_buffer + .store_mapped( + current_context, + mesh.vertices(), + GlBufferUsage::Static, + |vertex| RendererVertex { + pos: vertex.pos.into(), + texture_coords: vertex.texture_coords.into(), + normal: vertex.normal.into(), + }, + ) + .map_err(Error::StoreVerticesFailed)?; + + vertex_arr.bind_vertex_buffer(current_context, 0, &vertex_buffer, 0); + + let mut offset = 0u32; + + for attrib in RendererVertex::attrs() { + vertex_arr.enable_attrib(current_context, attrib.index); + + vertex_arr.set_attrib_format( + current_context, + attrib.index, + match attrib.component_type { + VertexAttributeComponentType::Float => GlVertexArrayDataType::Float, + }, + false, + offset, + ); + + vertex_arr.set_attrib_vertex_buf_binding(current_context, attrib.index, 0); + + offset += attrib.component_size * attrib.component_cnt as u32; + } + + if let Some(indices) = mesh.indices() { + let index_buffer = GlBuffer::new(current_context); + + index_buffer + .store(current_context, indices, GlBufferUsage::Static) + .map_err(Error::StoreIndicesFailed)?; + + vertex_arr.bind_element_buffer(current_context, &index_buffer); + + return Ok(Self { + _vertex_buffer: vertex_buffer, + index_buffer: Some(index_buffer), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), + vertex_arr, + }); + } + + Ok(Self { + _vertex_buffer: vertex_buffer, + index_buffer: None, + element_cnt: mesh + .vertices() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), + vertex_arr, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to store vertices in vertex buffer")] + StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), + + #[error("Failed to store indices in index buffer")] + StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), +} diff --git a/engine/src/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs index 499b94b..5a1593e 100644 --- a/engine/src/renderer/opengl/vertex.rs +++ b/engine/src/renderer/opengl/vertex.rs @@ -1,12 +1,13 @@ -use crate::vector::{Vec2, Vec3}; +use safer_ffi::derive_ReprC; #[derive(Debug, Clone)] +#[derive_ReprC] #[repr(C)] pub struct Vertex { - pub pos: Vec3<f32>, - pub texture_coords: Vec2<f32>, - pub normal: Vec3<f32>, + pub pos: opengl_bindings::data_types::Vec3<f32>, + pub texture_coords: opengl_bindings::data_types::Vec2<f32>, + pub normal: opengl_bindings::data_types::Vec3<f32>, } impl Vertex diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 4a4fe86..d02b9ff 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -1,194 +1,34 @@ -use std::fmt::Display; -use std::path::Path; -use std::sync::atomic::{AtomicU32, Ordering}; - -use image::io::Reader as ImageReader; -use image::{DynamicImage, ImageError, Rgb, RgbImage}; - -use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::opengl::texture::PixelDataFormat; -use crate::util::builder; - -static NEXT_ID: AtomicU32 = AtomicU32::new(0); - -mod reexports -{ - pub use crate::opengl::texture::{Filtering, Wrapping}; -} - -pub use reexports::*; +use crate::asset::Handle as AssetHandle; +use crate::image::Image; +use crate::builder; #[derive(Debug, Clone)] +#[non_exhaustive] pub struct Texture { - id: Id, - image: DynamicImage, - pixel_data_format: PixelDataFormat, - dimensions: Dimens<u32>, - properties: Properties, + pub asset_handle: AssetHandle<Image>, + pub properties: Properties, } impl Texture { - pub fn builder() -> Builder - { - Builder::default() - } - - /// Opens a texture image. - /// - /// # Errors - /// Will return `Err` if: - /// - Opening the image fails - /// - The image data is not 8-bit/color RGB - pub fn open(path: &Path) -> Result<Self, Error> - { - Self::builder().open(path) - } - - #[must_use] - pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self - { - Self::builder().build_with_single_color(dimensions, color) - } - - #[must_use] - pub fn id(&self) -> Id - { - self.id - } - - #[must_use] - pub fn properties(&self) -> &Properties - { - &self.properties - } - - pub fn properties_mut(&mut self) -> &mut Properties - { - &mut self.properties - } - - #[must_use] - pub fn dimensions(&self) -> &Dimens<u32> - { - &self.dimensions - } - - #[must_use] - pub fn pixel_data_format(&self) -> PixelDataFormat - { - self.pixel_data_format - } - - #[must_use] - pub fn image(&self) -> &DynamicImage - { - &self.image - } -} - -impl Drop for Texture -{ - fn drop(&mut self) - { - NEXT_ID.fetch_sub(1, Ordering::Relaxed); - } -} - -/// Texture builder. -#[derive(Debug, Default, Clone)] -pub struct Builder -{ - properties: Properties, -} - -impl Builder -{ - pub fn properties(mut self, properties: Properties) -> Self + pub fn new(asset_handle: AssetHandle<Image>) -> Self { - self.properties = properties; - self - } - - /// Opens a image as a texture. - /// - /// # Errors - /// Will return `Err` if: - /// - Opening the image fails - /// - Decoding the image fails - /// - The image data is in a unsupported format - pub fn open(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<Texture, Error> - { - let image = ImageReader::open(path) - .map_err(Error::OpenImageFailed)? - .decode() - .map_err(Error::DecodeImageFailed)?; - - let pixel_data_format = match &image { - DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8, - DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8, - _ => { - return Err(Error::UnsupportedImageDataFormat); - } - }; - - let dimensions = Dimens { - width: image.width(), - height: image.height(), - }; - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Ok(Texture { - id: Id::new(id), - image, - pixel_data_format, - dimensions, - properties: self.properties.clone(), - }) + Self { + asset_handle, + properties: Properties::default(), + } } - #[must_use] - pub fn build_with_single_color( - &self, - dimensions: &Dimens<u32>, - color: &Color<u8>, - ) -> Texture + pub fn with_properties( + asset_handle: AssetHandle<Image>, + properties: Properties, + ) -> Self { - let image = RgbImage::from_pixel( - dimensions.width, - dimensions.height, - Rgb([color.red, color.green, color.blue]), - ); - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Texture { - id: Id::new(id), - image: image.into(), - pixel_data_format: PixelDataFormat::Rgb8, - dimensions: *dimensions, - properties: self.properties.clone(), - } + Self { asset_handle, properties } } } -/// Texture error. -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to open texture image")] - OpenImageFailed(#[source] std::io::Error), - - #[error("Failed to decode texture image")] - DecodeImageFailed(#[source] ImageError), - - #[error("Unsupported image data format")] - UnsupportedImageDataFormat, -} - builder! { /// Texture properties #[builder(name = PropertiesBuilder, derives=(Debug, Clone))] @@ -230,25 +70,21 @@ impl Default for PropertiesBuilder } } -/// Texture ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id -{ - id: u32, -} - -impl Id +#[non_exhaustive] +pub enum Filtering { - fn new(id: u32) -> Self - { - Self { id } - } + Nearest, + Linear, } -impl Display for Id +/// Texture wrapping. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum Wrapping { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - self.id.fmt(formatter) - } + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToBorder, } diff --git a/engine/src/util.rs b/engine/src/util.rs index 0f6c78c..9174734 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -1,4 +1,72 @@ -use std::marker::PhantomData; +use ecs::util::VecExt; + +#[derive(Debug)] +pub struct MapVec<Key: Ord, Value> +{ + inner: Vec<(Key, Value)>, +} + +impl<Key: Ord, Value> MapVec<Key, Value> +{ + pub fn insert(&mut self, key: Key, value: Value) + { + self.inner + .insert_at_part_pt_by_key((key, value), |(a_key, _)| a_key); + } + + pub fn remove(&mut self, key: Key) -> Option<Value> + { + let index = self + .inner + .binary_search_by_key(&&key, |(a_key, _)| a_key) + .ok()?; + + let (_, value) = self.inner.remove(index); + + Some(value) + } + + pub fn get(&self, key: &Key) -> Option<&Value> + { + let index = self + .inner + .binary_search_by_key(&key, |(a_key, _)| a_key) + .ok()?; + + let Some((_, value)) = self.inner.get(index) else { + unreachable!(); // Reason: Index from binary search cannot be OOB + }; + + Some(value) + } + + pub fn get_mut(&mut self, key: &Key) -> Option<&mut Value> + { + let index = self + .inner + .binary_search_by_key(&key, |(a_key, _)| a_key) + .ok()?; + + let Some((_, value)) = self.inner.get_mut(index) else { + unreachable!(); // Reason: Index from binary search cannot be OOB + }; + + Some(value) + } + + pub fn values(&self) -> impl Iterator<Item = &Value> + { + self.inner.iter().map(|(_, value)| value) + } +} + +impl<Key: Ord, Value> Default for MapVec<Key, Value> +{ + fn default() -> Self + { + Self { inner: Vec::new() } + } +} macro_rules! try_option { ($expr: expr) => { @@ -25,6 +93,18 @@ macro_rules! or { pub(crate) use or; +#[macro_export] +macro_rules! expand_map_opt { + ($in: tt, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => { + $($occurance)* + }; + + (, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => { + $($no_occurance)* + }; +} + +#[macro_export] macro_rules! builder { ( $(#[doc = $doc: literal])* @@ -36,7 +116,8 @@ macro_rules! builder { $visibility: vis struct $name: ident { $( - $(#[$field_attr: meta])* + $(#[doc = $field_doc: literal])* + $(#[builder(skip_generate_fn$($field_skip_generate_fn: tt)?)])? $field_visibility: vis $field: ident: $field_type: ty, )* } @@ -46,7 +127,7 @@ macro_rules! builder { $visibility struct $name { $( - $(#[$field_attr])* + $(#[doc = $field_doc])* $field_visibility $field: $field_type, )* } @@ -62,12 +143,18 @@ macro_rules! builder { impl $builder_name { $( - #[must_use] - $visibility fn $field(mut self, $field: $field_type) -> Self - { - self.$field = $field; - self - } + $crate::expand_map_opt!( + $(true $($field_skip_generate_fn)?)?, + no_occurance=( + #[must_use] + $visibility fn $field(mut self, $field: $field_type) -> Self + { + self.$field = $field; + self + } + ), + occurance=() + ); )* #[must_use] @@ -82,6 +169,7 @@ macro_rules! builder { impl From<$name> for $builder_name { + #[allow(unused_variables)] fn from(built: $name) -> Self { Self { @@ -93,77 +181,3 @@ macro_rules! builder { } }; } - -pub(crate) use builder; - -pub enum RefOrValue<'a, T> -{ - Ref(&'a T), - Value(Option<T>), -} - -impl<'a, T> RefOrValue<'a, T> -{ - pub fn get(&self) -> Option<&T> - { - match self { - Self::Ref(val_ref) => Some(val_ref), - Self::Value(val_cell) => val_cell.as_ref(), - } - } -} - -#[derive(Debug)] -pub struct Defer<'func, Func, Data> -where - Func: FnMut(&mut Data) + 'func, -{ - func: Func, - pub data: Data, - _pd: PhantomData<&'func ()>, -} - -impl<'func, Func, Data> Defer<'func, Func, Data> -where - Func: FnMut(&mut Data) + 'func, -{ - pub fn new(data: Data, func: Func) -> Self - { - Self { func, data, _pd: PhantomData } - } -} - -impl<'func, Func, Data> Drop for Defer<'func, Func, Data> -where - Func: FnMut(&mut Data) + 'func, -{ - fn drop(&mut self) - { - (self.func)(&mut self.data) - } -} - -/// Defines a function that will be called at the end of the current scope. -/// -/// Only captured variables that are later mutably borrowed needs to specified as -/// captures. -macro_rules! defer { - (|$capture: ident| {$($tt: tt)*}) => { - // This uses the automatic temporary lifetime extension behaviour introduced - // in Rust 1.79.0 (https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html) to - // create a unnamable variable for the Defer struct. The variable should be - // unnamable so that it cannot be missused and so that this macro can be used - // multiple times without having to give it a identifier for the Defer struct - // variable - let Defer { data: $capture, .. } = if true { - &Defer::new($capture, |$capture| { - $($tt)* - }) - } - else { - unreachable!(); - }; - }; -} - -pub(crate) use defer; diff --git a/engine/src/window.rs b/engine/src/window.rs deleted file mode 100644 index d342341..0000000 --- a/engine/src/window.rs +++ /dev/null @@ -1,753 +0,0 @@ -use std::borrow::Cow; -use std::ffi::{CStr, CString}; - -use bitflags::bitflags; -use ecs::actions::Actions; -use ecs::extension::Collector as ExtensionCollector; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, START as START_PHASE}; -use ecs::sole::Single; -use ecs::{static_entity, Sole}; -use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue}; -use glfw::WindowSize; -use util_macros::VariantArr; - -use crate::data_types::dimens::Dimens; -use crate::renderer::RENDER_PHASE; -use crate::vector::Vec2; - -static_entity!( - pub UPDATE_PHASE, - (Phase, Pair::new::<ChildOf>(*RENDER_PHASE)) -); - -#[derive(Debug, Sole)] -/// Has to be dropped last since it holds the OpenGL context. -#[sole(drop_last)] -pub struct Window -{ - inner: glfw::Window, -} - -impl Window -{ - /// Returns a new Window builder. - #[must_use] - pub fn builder() -> Builder - { - Builder::default() - } - - /// Sets the value of a input mode. - /// - /// # Errors - /// Returns `Err` if the input mode is unsupported on the current system. - pub fn set_input_mode( - &self, - input_mode: InputMode, - enabled: bool, - ) -> Result<(), Error> - { - Ok(self - .inner - .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?) - } - - /// Sets the cursor mode. - /// - /// # Errors - /// If a platform error occurs. - pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> - { - Ok(self - .inner - .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?) - } - - /// Returns whether or not the window should close. Will return true when the user has - /// attempted to close the window. - #[must_use] - pub fn should_close(&self) -> bool - { - self.inner.should_close() - } - - /// Processes all pending events. - /// - /// # Errors - /// If a platform error occurs. - pub fn poll_events(&self) -> Result<(), Error> - { - Ok(self.inner.poll_events()?) - } - - /// Swaps the front and back buffers of the window. - /// - /// # Errors - /// Will return `Err` if a platform error occurs or if no OpenGL window context - /// is present. - pub fn swap_buffers(&self) -> Result<(), Error> - { - Ok(self.inner.swap_buffers()?) - } - - /// Returns the size of the window. - /// - /// # Errors - /// Will return `Err` if a platform error occurs. - pub fn size(&self) -> Result<Dimens<u32>, Error> - { - let size = self.inner.size()?; - - Ok(Dimens { - width: size.width, - height: size.height, - }) - } - - /// Returns the address of the specified OpenGL function, if it is supported by the - /// current OpenGL context. - /// - /// # Errors - /// Will return `Err` if a platform error occurs or if no current context has - /// been set. - /// - /// # Panics - /// Will panic if the `proc_name` argument contains a nul byte. - pub fn get_proc_address( - &self, - proc_name: &str, - ) -> Result<unsafe extern "C" fn(), Error> - { - let proc_name_c: Cow<CStr> = CStr::from_bytes_with_nul(proc_name.as_bytes()) - .map(Cow::Borrowed) - .or_else(|_| CString::new(proc_name).map(Cow::Owned)) - .expect("OpenGL function name contains a nul byte"); - - Ok(self.inner.get_proc_address(&proc_name_c)?) - } - - /// Makes the OpenGL context of the window current for the calling thread. - /// - /// # Errors - /// Will return `Err` if a platform error occurs or if no OpenGL context is - /// present. - pub fn make_context_current(&self) -> Result<(), Error> - { - Ok(self.inner.make_context_current()?) - } - - /// Sets the window's framebuffer size callback. - pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Dimens<u32>) + 'static) - { - self.inner.set_framebuffer_size_callback(move |size| { - callback(Dimens { - width: size.width, - height: size.height, - }); - }); - } - - /// Sets the window's key size callback. - pub fn set_key_callback( - &self, - callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, - ) - { - self.inner - .set_key_callback(move |key, scancode, key_state, key_modifiers| { - let Some(key_state) = KeyState::from_glfw_key_state(key_state) else { - return; - }; - - callback( - Key::from_glfw_key(key), - scancode, - key_state, - KeyModifiers::from_bits_truncate(key_modifiers.bits()), - ) - }); - } - - /// Sets the window's cursor position callback. - pub fn set_cursor_pos_callback(&self, callback: impl Fn(Vec2<f64>) + 'static) - { - self.inner - .set_cursor_pos_callback(move |pos| callback(Vec2 { x: pos.x, y: pos.y })); - } - - /// Sets the window's mouse button callback. The given function is called when a mouse - /// button enters a new state. - pub fn set_mouse_button_callback( - &self, - callback: impl Fn(MouseButton, MouseButtonState, KeyModifiers) + 'static, - ) - { - self.inner.set_mouse_button_callback( - move |mouse_button, mouse_button_state, key_modifiers| { - callback( - MouseButton::from_glfw_mouse_button(mouse_button), - MouseButtonState::from_glfw_mouse_button_state(mouse_button_state), - KeyModifiers::from_bits_truncate(key_modifiers.bits()), - ) - }, - ); - } - - /// Sets the window's close callback. - pub fn set_close_callback(&self, callback: impl Fn() + 'static) - { - self.inner.set_close_callback(callback); - } - - /// Sets the window's focus callback. The callback is called when the window loses or - /// gains input focus. - pub fn set_focus_callback(&self, callback: impl Fn(bool) + 'static) - { - self.inner.set_focus_callback(callback); - } -} - -/// [`Window`] builder. -#[derive(Debug, Clone, Default)] -pub struct Builder -{ - inner: glfw::WindowBuilder, -} - -impl Builder -{ - /// Sets whether the OpenGL context should be created in debug mode, which may - /// provide additional error and diagnostic reporting functionality. - pub fn opengl_debug_context(mut self, enabled: bool) -> Self - { - self.inner = self.inner.hint( - WindowCreationHint::OpenGLDebugContext, - WindowCreationHintValue::Bool(enabled), - ); - - self - } - - /// Set the desired number of samples to use for multisampling. Zero disables - /// multisampling. - pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self - { - self.inner = self.inner.hint( - WindowCreationHint::Samples, - WindowCreationHintValue::Number(sample_count as i32), - ); - - self - } - - /// Creates a new window. - /// - /// # Errors - /// Will return `Err` if the title contains a internal nul byte or if a platform error - /// occurs. - pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error> - { - let builder = self.inner.clone().hint( - WindowCreationHint::OpenGLDebugContext, - WindowCreationHintValue::Bool(true), - ); - - let window = builder.create( - &WindowSize { - width: size.width, - height: size.height, - }, - title, - )?; - - Ok(Window { inner: window }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)] -#[variant_arr(name = KEYS)] -pub enum Key -{ - Space, - Apostrophe, - Comma, - Minus, - Period, - Slash, - Digit0, - Digit1, - Digit2, - Digit3, - Digit4, - Digit5, - Digit6, - Digit7, - Digit8, - Digit9, - Semicolon, - Equal, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - LeftBracket, - Backslash, - RightBracket, - GraveAccent, - World1, - World2, - Escape, - Enter, - Tab, - Backspace, - Insert, - Delete, - Right, - Left, - Down, - Up, - PageUp, - PageDown, - Home, - End, - CapsLock, - ScrollLock, - NumLock, - PrintScreen, - Pause, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - F25, - Kp0, - Kp1, - Kp2, - Kp3, - Kp4, - Kp5, - Kp6, - Kp7, - Kp8, - Kp9, - KpDecimal, - KpDivide, - KpMultiply, - KpSubtract, - KpAdd, - KpEnter, - KpEqual, - LeftShift, - LeftControl, - LeftAlt, - LeftSuper, - RightShift, - RightControl, - RightAlt, - RightSuper, - Menu, -} - -impl Key -{ - fn from_glfw_key(glfw_key: glfw::window::Key) -> Self - { - match glfw_key { - glfw::window::Key::Space => Self::Space, - glfw::window::Key::Apostrophe => Self::Apostrophe, - glfw::window::Key::Comma => Self::Comma, - glfw::window::Key::Minus => Self::Minus, - glfw::window::Key::Period => Self::Period, - glfw::window::Key::Slash => Self::Slash, - glfw::window::Key::Digit0 => Self::Digit0, - glfw::window::Key::Digit1 => Self::Digit1, - glfw::window::Key::Digit2 => Self::Digit2, - glfw::window::Key::Digit3 => Self::Digit3, - glfw::window::Key::Digit4 => Self::Digit4, - glfw::window::Key::Digit5 => Self::Digit5, - glfw::window::Key::Digit6 => Self::Digit6, - glfw::window::Key::Digit7 => Self::Digit7, - glfw::window::Key::Digit8 => Self::Digit8, - glfw::window::Key::Digit9 => Self::Digit9, - glfw::window::Key::Semicolon => Self::Semicolon, - glfw::window::Key::Equal => Self::Equal, - glfw::window::Key::A => Self::A, - glfw::window::Key::B => Self::B, - glfw::window::Key::C => Self::C, - glfw::window::Key::D => Self::D, - glfw::window::Key::E => Self::E, - glfw::window::Key::F => Self::F, - glfw::window::Key::G => Self::G, - glfw::window::Key::H => Self::H, - glfw::window::Key::I => Self::I, - glfw::window::Key::J => Self::J, - glfw::window::Key::K => Self::K, - glfw::window::Key::L => Self::L, - glfw::window::Key::M => Self::M, - glfw::window::Key::N => Self::N, - glfw::window::Key::O => Self::O, - glfw::window::Key::P => Self::P, - glfw::window::Key::Q => Self::Q, - glfw::window::Key::R => Self::R, - glfw::window::Key::S => Self::S, - glfw::window::Key::T => Self::T, - glfw::window::Key::U => Self::U, - glfw::window::Key::V => Self::V, - glfw::window::Key::W => Self::W, - glfw::window::Key::X => Self::X, - glfw::window::Key::Y => Self::Y, - glfw::window::Key::Z => Self::Z, - glfw::window::Key::LeftBracket => Self::LeftBracket, - glfw::window::Key::Backslash => Self::Backslash, - glfw::window::Key::RightBracket => Self::RightBracket, - glfw::window::Key::GraveAccent => Self::GraveAccent, - glfw::window::Key::World1 => Self::World1, - glfw::window::Key::World2 => Self::World2, - glfw::window::Key::Escape => Self::Escape, - glfw::window::Key::Enter => Self::Enter, - glfw::window::Key::Tab => Self::Tab, - glfw::window::Key::Backspace => Self::Backspace, - glfw::window::Key::Insert => Self::Insert, - glfw::window::Key::Delete => Self::Delete, - glfw::window::Key::Right => Self::Right, - glfw::window::Key::Left => Self::Left, - glfw::window::Key::Down => Self::Down, - glfw::window::Key::Up => Self::Up, - glfw::window::Key::PageUp => Self::PageUp, - glfw::window::Key::PageDown => Self::PageDown, - glfw::window::Key::Home => Self::Home, - glfw::window::Key::End => Self::End, - glfw::window::Key::CapsLock => Self::CapsLock, - glfw::window::Key::ScrollLock => Self::ScrollLock, - glfw::window::Key::NumLock => Self::NumLock, - glfw::window::Key::PrintScreen => Self::PrintScreen, - glfw::window::Key::Pause => Self::Pause, - glfw::window::Key::F1 => Self::F1, - glfw::window::Key::F2 => Self::F2, - glfw::window::Key::F3 => Self::F3, - glfw::window::Key::F4 => Self::F4, - glfw::window::Key::F5 => Self::F5, - glfw::window::Key::F6 => Self::F6, - glfw::window::Key::F7 => Self::F7, - glfw::window::Key::F8 => Self::F8, - glfw::window::Key::F9 => Self::F9, - glfw::window::Key::F10 => Self::F10, - glfw::window::Key::F11 => Self::F11, - glfw::window::Key::F12 => Self::F12, - glfw::window::Key::F13 => Self::F13, - glfw::window::Key::F14 => Self::F14, - glfw::window::Key::F15 => Self::F15, - glfw::window::Key::F16 => Self::F16, - glfw::window::Key::F17 => Self::F17, - glfw::window::Key::F18 => Self::F18, - glfw::window::Key::F19 => Self::F19, - glfw::window::Key::F20 => Self::F20, - glfw::window::Key::F21 => Self::F21, - glfw::window::Key::F22 => Self::F22, - glfw::window::Key::F23 => Self::F23, - glfw::window::Key::F24 => Self::F24, - glfw::window::Key::F25 => Self::F25, - glfw::window::Key::Kp0 => Self::Kp0, - glfw::window::Key::Kp1 => Self::Kp1, - glfw::window::Key::Kp2 => Self::Kp2, - glfw::window::Key::Kp3 => Self::Kp3, - glfw::window::Key::Kp4 => Self::Kp4, - glfw::window::Key::Kp5 => Self::Kp5, - glfw::window::Key::Kp6 => Self::Kp6, - glfw::window::Key::Kp7 => Self::Kp7, - glfw::window::Key::Kp8 => Self::Kp8, - glfw::window::Key::Kp9 => Self::Kp9, - glfw::window::Key::KpDecimal => Self::KpDecimal, - glfw::window::Key::KpDivide => Self::KpDivide, - glfw::window::Key::KpMultiply => Self::KpMultiply, - glfw::window::Key::KpSubtract => Self::KpSubtract, - glfw::window::Key::KpAdd => Self::KpAdd, - glfw::window::Key::KpEnter => Self::KpEnter, - glfw::window::Key::KpEqual => Self::KpEqual, - glfw::window::Key::LeftShift => Self::LeftShift, - glfw::window::Key::LeftControl => Self::LeftControl, - glfw::window::Key::LeftAlt => Self::LeftAlt, - glfw::window::Key::LeftSuper => Self::LeftSuper, - glfw::window::Key::RightShift => Self::RightShift, - glfw::window::Key::RightControl => Self::RightControl, - glfw::window::Key::RightAlt => Self::RightAlt, - glfw::window::Key::RightSuper => Self::RightSuper, - glfw::window::Key::Menu => Self::Menu, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum KeyState -{ - Pressed, - Released, -} - -impl KeyState -{ - fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option<Self> - { - match glfw_key_state { - glfw::window::KeyState::Pressed => Some(Self::Pressed), - glfw::window::KeyState::Released => Some(Self::Released), - glfw::window::KeyState::Repeat => None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MouseButton -{ - One, - Two, - Three, - Four, - Five, - Six, - Seven, - Eight, -} - -impl MouseButton -{ - pub const LEFT: Self = Self::One; - pub const MIDDLE: Self = Self::Three; - pub const RIGHT: Self = Self::Two; - - fn from_glfw_mouse_button(mouse_button: glfw::window::MouseButton) -> Self - { - match mouse_button { - glfw::window::MouseButton::One => Self::One, - glfw::window::MouseButton::Two => Self::Two, - glfw::window::MouseButton::Three => Self::Three, - glfw::window::MouseButton::Four => Self::Four, - glfw::window::MouseButton::Five => Self::Five, - glfw::window::MouseButton::Six => Self::Six, - glfw::window::MouseButton::Seven => Self::Seven, - glfw::window::MouseButton::Eight => Self::Eight, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MouseButtonState -{ - Pressed, - Released, -} - -impl MouseButtonState -{ - fn from_glfw_mouse_button_state( - mouse_button_state: glfw::window::MouseButtonState, - ) -> Self - { - match mouse_button_state { - glfw::window::MouseButtonState::Pressed => Self::Pressed, - glfw::window::MouseButtonState::Released => Self::Released, - } - } -} - -bitflags! { - #[derive(Debug, Clone, Copy)] - pub struct KeyModifiers: i32 { - const SHIFT = glfw::window::KeyModifiers::SHIFT.bits(); - const CONTROL = glfw::window::KeyModifiers::CONTROL.bits(); - const ALT = glfw::window::KeyModifiers::ALT.bits(); - const SUPER = glfw::window::KeyModifiers::SUPER.bits(); - const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits(); - const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits(); - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum CursorMode -{ - /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. - Disabled, - - /// Makes the cursor invisible when it is over the content area of the window but - /// does not restrict the cursor from leaving. - Hidden, - - /// Makes the cursor visible and behaving normally. - Normal, -} - -impl CursorMode -{ - fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode - { - match self { - Self::Disabled => glfw::window::CursorMode::Disabled, - Self::Hidden => glfw::window::CursorMode::Hidden, - Self::Normal => glfw::window::CursorMode::Normal, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum InputMode -{ - /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be - /// enabled if available. - /// - /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It - /// is not affected by the scaling and acceleration applied to the motion of the - /// desktop cursor. That processing is suitable for a cursor while raw motion is - /// better for controlling for example a 3D camera. Because of this, raw mouse motion - /// is only provided when the cursor is disabled. - RawMouseMotion, -} - -impl InputMode -{ - fn to_glfw_input_mode(self) -> glfw::window::InputMode - { - match self { - Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion, - } - } -} - -#[derive(Debug)] -pub struct Extension -{ - window_builder: Builder, - window_size: Dimens<u32>, - window_title: String, -} - -impl Extension -{ - #[must_use] - pub fn new(window_builder: Builder) -> Self - { - Self { window_builder, ..Default::default() } - } - - #[must_use] - pub fn window_size(mut self, window_size: Dimens<u32>) -> Self - { - self.window_size = window_size; - - self - } - - #[must_use] - pub fn window_title(mut self, window_title: impl Into<String>) -> Self - { - self.window_title = window_title.into(); - - self - } -} - -impl ecs::extension::Extension for Extension -{ - fn collect(self, mut collector: ExtensionCollector<'_>) - { - collector.add_system(*START_PHASE, initialize); - collector.add_system(*UPDATE_PHASE, update); - - let window = self - .window_builder - .create(self.window_size, &self.window_title) - .unwrap(); - - window.set_cursor_mode(CursorMode::Normal).unwrap(); - - collector.add_sole(window).ok(); - } -} - -impl Default for Extension -{ - fn default() -> Self - { - Self { - window_builder: Builder::default(), - window_size: Dimens { width: 1920, height: 1080 }, - window_title: String::new(), - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(glfw::Error); - -impl From<glfw::Error> for Error -{ - fn from(err: glfw::Error) -> Self - { - Self(err) - } -} - -fn initialize(window: Single<Window>, actions: Actions) -{ - let actions_weak_ref = actions.to_weak_ref(); - - window.set_close_callback(move || { - let actions_weak_ref = actions_weak_ref.clone(); - - let actions_ref = actions_weak_ref.access().expect("No world"); - - actions_ref.to_actions().stop(); - }); -} - -fn update(window: Single<Window>) -{ - window - .swap_buffers() - .expect("Failed to swap window buffers"); - - window.poll_events().expect("Failed to poll window events"); -} diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs new file mode 100644 index 0000000..69adae9 --- /dev/null +++ b/engine/src/windowing.rs @@ -0,0 +1,669 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Weak}; +use std::thread::{spawn, JoinHandle as ThreadJoinHandle}; + +use crossbeam_channel::{ + bounded as bounded_channel, + Receiver as ChannelReceiver, + Sender as ChannelSender, + TrySendError, +}; +use ecs::actions::Actions; +use ecs::component::Component; +use ecs::entity::obtainer::Obtainer as EntityObtainer; +use ecs::event::component::{Added, Changed, Removed}; +use ecs::pair::{ChildOf, Pair}; +use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use ecs::sole::Single; +use ecs::system::observer::Observe; +use ecs::uid::Uid; +use ecs::{declare_entity, Query, Sole}; +use raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle, WindowHandle}; +use winit::application::ApplicationHandler; +use winit::dpi::PhysicalPosition; +use winit::error::EventLoopError; +use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; +use winit::event_loop::{ + ActiveEventLoop, + ControlFlow as EventLoopControlFlow, + EventLoop, + OwnedDisplayHandle, +}; +use winit::keyboard::PhysicalKey; +use winit::window::{Window as WinitWindow, WindowId as WinitWindowId}; + +use crate::data_types::dimens::Dimens; +use crate::util::MapVec; +use crate::vector::Vec2; +use crate::windowing::keyboard::{Key, KeyState, Keyboard, UnknownKeyCodeError}; +use crate::windowing::mouse::{ + Button as MouseButton, + ButtonState as MouseButtonState, + Buttons as MouseButtons, + Motion as MouseMotion, +}; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady as WindowCreationReady, + CursorGrabMode, + Id as WindowId, + Window, +}; + +pub mod keyboard; +pub mod mouse; +pub mod window; + +const MESSAGE_FROM_APP_CHANNEL_CAP: usize = 128; + +const MESSAGE_TO_APP_CHANNEL_CAP: usize = 16; // Increase if more messages are added + +declare_entity!( + pub PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*UPDATE_PHASE) + .build() + ) +); + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Extension {} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + collector.add_sole(Context::default()).ok(); + collector.add_sole(Keyboard::default()).ok(); + collector.add_sole(MouseMotion::default()).ok(); + collector.add_sole(MouseButtons::default()).ok(); + + collector.add_declared_entity(&PHASE); + + collector.add_system(*PHASE, update_stuff); + + collector.add_observer(handle_window_changed); + collector.add_observer(handle_window_removed); + collector.add_observer(handle_window_creation_ready); + } +} + +fn handle_window_creation_ready( + observe: Observe<Pair<Added, WindowCreationReady>>, + context: Single<Context>, +) +{ + for evt_match in &observe { + let Some(ent) = evt_match.get_entity() else { + unreachable!(); + }; + + if ent.has_component(Window::id()) || ent.has_component(WindowClosed::id()) { + continue; + } + + let Some(window_creation_attrs) = ent.get::<WindowCreationAttributes>() else { + unreachable!(); + }; + + context.try_send_message_to_app(MessageToApp::CreateWindow( + ent.uid(), + window_creation_attrs.clone(), + )); + } +} + +#[tracing::instrument(skip_all)] +fn update_stuff( + mut context: Single<Context>, + mut keyboard: Single<Keyboard>, + mut mouse_motion: Single<MouseMotion>, + mut mouse_buttons: Single<MouseButtons>, + mut actions: Actions, + entity_obtainer: EntityObtainer, +) +{ + keyboard.make_key_states_previous(); + mouse_buttons.make_states_previous(); + mouse_motion.position_delta = Vec2::default(); + + let Context { + ref message_from_app_receiver, + ref mut display_handle, + ref mut windows, + .. + } = *context; + + for message in message_from_app_receiver.try_iter() { + match message { + MessageFromApp::Init(new_display_handle) => { + *display_handle = Some(new_display_handle); + } + MessageFromApp::WindowCreated( + window_ent_id, + winit_window, + window_creation_attrs, + ) => { + actions.add_components( + window_ent_id, + (Window::new(&winit_window, &window_creation_attrs),), + ); + + actions.remove_comps::<(WindowCreationReady,)>(window_ent_id); + + tracing::debug!("Added window component to window entity"); + + windows.insert( + WindowId::from_inner(winit_window.id()), + (winit_window, window_ent_id), + ); + } + MessageFromApp::WindowResized(window_id, new_window_size) => { + let Some(window_ent_id) = + windows.get(&window_id).map(|(_, ent_id)| ent_id) + else { + continue; + }; + + let Some(window_ent) = entity_obtainer.get_entity(*window_ent_id) else { + continue; + }; + + let Some(mut window) = window_ent.get_mut::<Window>() else { + continue; + }; + + window.set_inner_size(new_window_size); + + window.set_changed(); + } + MessageFromApp::WindowCloseRequested(window_id) => { + let Some(window_ent_id) = + windows.get(&window_id).map(|(_, ent_id)| ent_id) + else { + tracing::error!( + wid = ?window_id, + "Window does not exist in windowing context" + ); + continue; + }; + + actions.remove_comps::<(Window,)>(*window_ent_id); + } + MessageFromApp::KeyboardKeyStateChanged(key, key_state) => { + keyboard.set_key_state(key, key_state); + } + MessageFromApp::MouseMoved { position_delta } => { + mouse_motion.position_delta += position_delta; + } + MessageFromApp::MouseButtonStateChanged(mouse_button, mouse_button_state) => { + mouse_buttons.set(mouse_button, mouse_button_state); + } + } + } +} + +fn handle_window_changed( + observe: Observe<'_, Pair<Changed, Window>>, + context: Single<Context>, +) +{ + for evt_match in &observe { + let window_ent_id = evt_match.id(); + + let window = evt_match.get_changed_comp(); + + let Some((winit_window, _)) = context.windows.get(&window.wid()) else { + tracing::error!( + wid = ?window.wid(), + entity_id = %window_ent_id, + "Window does not exist in windowing context", + ); + continue; + }; + + window.apply(winit_window); + + context.try_send_message_to_app(MessageToApp::SetWindowCursorGrabMode( + window.wid(), + window.cursor_grab_mode, + )); + } +} + +fn handle_window_removed( + observe: Observe<Pair<Removed, Window>>, + window_query: Query<(&Window,)>, + mut context: Single<Context>, + mut actions: Actions, +) +{ + for evt_match in &observe { + let window = evt_match.get_removed_comp(); + + context.windows.remove(window.wid()); + + actions.add_components(evt_match.id(), (WindowClosed,)); + } + + if window_query.iter().count() == 1 { + actions.stop(); + } +} + +#[derive(Debug, Sole)] +pub struct Context +{ + _thread: ThreadJoinHandle<()>, + is_dropped: Arc<AtomicBool>, + message_from_app_receiver: ChannelReceiver<MessageFromApp>, + message_to_app_sender: ChannelSender<MessageToApp>, + display_handle: Option<OwnedDisplayHandle>, + windows: MapVec<WindowId, (Arc<winit::window::Window>, Uid)>, +} + +impl Context +{ + pub fn display_handle(&self) -> Option<DisplayHandle<'_>> + { + let display_handle = self.display_handle.as_ref()?; + + display_handle.display_handle().ok() + } + + /// Returns the specified window as a window handle, if it exists. + /// + /// # Safety + /// The Window handle must only be used with thread safe APIs. + pub unsafe fn get_window_as_handle( + &self, + window_id: &WindowId, + ) -> Option<Result<WindowHandle<'_>, HandleError>> + { + self.windows.get(window_id).map(|(winit_window, _)| { + #[cfg(windows)] + { + use winit::platform::windows::WindowExtWindows; + + // SAFETY: I don't care + unsafe { winit_window.window_handle_any_thread() } + } + + #[cfg(not(windows))] + { + use raw_window_handle::HasWindowHandle; + + winit_window.window_handle() + } + }) + } + + fn try_send_message_to_app(&self, message: MessageToApp) + { + if let Err(err) = self.message_to_app_sender.try_send(message) { + let error = match &err { + TrySendError::Full(_) => TrySendError::Full(()), + TrySendError::Disconnected(_) => TrySendError::Disconnected(()), + }; + + let message = err.into_inner(); + + tracing::error!("Failed to send message {error}: {message:?}"); + } + } +} + +impl Default for Context +{ + fn default() -> Self + { + let is_dropped = Arc::new(AtomicBool::new(false)); + + let is_dropped_b = is_dropped.clone(); + + let (message_from_app_sender, message_from_app_receiver) = + bounded_channel::<MessageFromApp>(MESSAGE_FROM_APP_CHANNEL_CAP); + + let message_from_app_receiver_b = message_from_app_receiver.clone(); + + let (message_to_app_sender, message_to_app_receiver) = + bounded_channel::<MessageToApp>(MESSAGE_TO_APP_CHANNEL_CAP); + + Self { + _thread: spawn(move || { + let mut app = App { + message_from_app_sender, + message_from_app_receiver: message_from_app_receiver_b, + message_to_app_receiver, + is_dropped: is_dropped_b, + windows: MapVec::default(), + focused_window_id: None, + }; + + let event_loop = match create_event_loop() { + Ok(event_loop) => event_loop, + Err(err) => { + tracing::error!("Failed to create event loop: {err}"); + return; + } + }; + + event_loop.set_control_flow(EventLoopControlFlow::Poll); + + if let Err(err) = event_loop.run_app(&mut app) { + tracing::error!("Event loop error occurred: {err}"); + } + }), + is_dropped, + message_from_app_receiver, + message_to_app_sender, + display_handle: None, + windows: MapVec::default(), + } + } +} + +impl Drop for Context +{ + fn drop(&mut self) + { + self.is_dropped.store(true, Ordering::Relaxed); + } +} + +fn create_event_loop() -> Result<EventLoop<()>, EventLoopError> +{ + let mut event_loop_builder = EventLoop::builder(); + + #[cfg(any(x11_platform, wayland_platform))] + winit::platform::x11::EventLoopBuilderExtX11::with_any_thread( + &mut event_loop_builder, + true, + ); + + #[cfg(windows)] + winit::platform::windows::EventLoopBuilderExtWindows::with_any_thread( + &mut event_loop_builder, + true, + ); + + #[cfg(not(any(x11_platform, wayland_platform, windows)))] + compile_error!("Unsupported platform"); + + event_loop_builder.build() +} + +#[derive(Debug)] +enum MessageFromApp +{ + Init(OwnedDisplayHandle), + WindowCreated(Uid, Arc<WinitWindow>, WindowCreationAttributes), + WindowResized(WindowId, Dimens<u32>), + WindowCloseRequested(WindowId), + KeyboardKeyStateChanged(Key, KeyState), + MouseMoved + { + position_delta: Vec2<f64>, + }, + MouseButtonStateChanged(MouseButton, MouseButtonState), +} + +#[derive(Debug)] +enum MessageToApp +{ + CreateWindow(Uid, WindowCreationAttributes), + SetWindowCursorGrabMode(WindowId, CursorGrabMode), +} + +#[derive(Debug)] +struct App +{ + message_from_app_sender: ChannelSender<MessageFromApp>, + message_from_app_receiver: ChannelReceiver<MessageFromApp>, + message_to_app_receiver: ChannelReceiver<MessageToApp>, + is_dropped: Arc<AtomicBool>, + windows: MapVec<WindowId, (Weak<WinitWindow>, WindowSettings)>, + focused_window_id: Option<WindowId>, +} + +impl App +{ + fn handle_received_messages(&mut self, event_loop: &ActiveEventLoop) + { + for message in self.message_to_app_receiver.try_iter() { + match message { + MessageToApp::CreateWindow(window_ent_id, window_creation_attrs) => { + tracing::info!( + "Creating window with title {}", + window_creation_attrs.title() + ); + + let winit_window = Arc::new( + match event_loop + .create_window(window_creation_attrs.clone().into_inner()) + { + Ok(window) => window, + Err(err) => { + tracing::error!("Failed to create window: {err}"); + continue; + } + }, + ); + + tracing::info!("Created window has title {}", winit_window.title()); + + self.windows.insert( + WindowId::from_inner(winit_window.id()), + (Arc::downgrade(&winit_window), WindowSettings::default()), + ); + + self.send_message(MessageFromApp::WindowCreated( + window_ent_id, + winit_window, + window_creation_attrs, + )); + } + MessageToApp::SetWindowCursorGrabMode(window_id, cursor_grab_mode) => { + let Some((_, window_settings)) = self.windows.get_mut(&window_id) + else { + tracing::warn!( + window_id=?window_id, + "Cannot set window cursor grab mode. Window not found" + ); + + continue; + }; + + window_settings.cursor_grab_mode = cursor_grab_mode; + } + } + } + } + + fn send_message(&self, message: MessageFromApp) + { + if self.message_from_app_sender.is_full() { + tracing::warn!( + "Message channel is full! Dropping oldest message from channel" + ); + + self.message_from_app_receiver.try_recv().ok(); + } + + if let Err(err) = self.message_from_app_sender.try_send(message) { + let error = match &err { + TrySendError::Full(_) => TrySendError::Full(()), + TrySendError::Disconnected(_) => TrySendError::Disconnected(()), + }; + + let message = err.into_inner(); + + tracing::error!("Failed to send message {error}: {message:?}"); + } + } +} + +impl ApplicationHandler for App +{ + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) + { + match cause { + StartCause::Init => { + self.send_message(MessageFromApp::Init( + event_loop.owned_display_handle(), + )); + } + StartCause::Poll => { + if self.is_dropped.load(Ordering::Relaxed) { + event_loop.exit(); + return; + } + + self.handle_received_messages(event_loop); + } + _ => {} + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) + { + for (window, _) in self.windows.values() { + let Some(window) = window.upgrade() else { + continue; + }; + + window.request_redraw(); + } + } + + fn resumed(&mut self, _event_loop: &ActiveEventLoop) {} + + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + window_id: WinitWindowId, + event: WindowEvent, + ) + { + match event { + WindowEvent::Resized(new_window_size) => { + self.send_message(MessageFromApp::WindowResized( + WindowId::from_inner(window_id), + new_window_size.into(), + )); + } + WindowEvent::CloseRequested => { + self.send_message(MessageFromApp::WindowCloseRequested( + WindowId::from_inner(window_id), + )); + } + WindowEvent::KeyboardInput { + device_id: _, + event: keyboard_event, + is_synthetic: _, + } => { + if keyboard_event.repeat { + return; + } + + let key_code = match keyboard_event.physical_key { + PhysicalKey::Code(key_code) => key_code, + PhysicalKey::Unidentified(native_key) => { + tracing::warn!("Ignoring unidentified key: {native_key:?}"); + return; + } + }; + + let key: Key = match key_code.try_into() { + Ok(key) => key, + Err(UnknownKeyCodeError) => { + tracing::warn!("Ignoring key with unknown key code {key_code:?}"); + return; + } + }; + + self.send_message(MessageFromApp::KeyboardKeyStateChanged( + key, + keyboard_event.state.into(), + )); + } + WindowEvent::MouseInput { device_id: _, state, button } => { + self.send_message(MessageFromApp::MouseButtonStateChanged( + button.into(), + state.into(), + )); + } + WindowEvent::Focused(is_focused) => { + if is_focused { + self.focused_window_id = Some(WindowId::from_inner(window_id)); + } else { + self.focused_window_id = None; + } + } + _ => {} + } + } + + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + _device_id: DeviceId, + device_event: DeviceEvent, + ) + { + match device_event { + DeviceEvent::MouseMotion { delta } => { + self.send_message(MessageFromApp::MouseMoved { + position_delta: Vec2 { x: delta.0, y: delta.1 }, + }); + + let Some(focused_window_id) = self.focused_window_id else { + return; + }; + + let Some((focused_window, focused_window_settings)) = + self.windows.get(&focused_window_id) + else { + tracing::error!( + window_id=?focused_window_id, + "Focused window not found" + ); + return; + }; + + if focused_window_settings.cursor_grab_mode != CursorGrabMode::Locked { + return; + } + + // TODO: This might need to be optimized + let Some(focused_window) = focused_window.upgrade() else { + return; + }; + + let focused_window_size = focused_window.inner_size(); + + if let Err(err) = focused_window.set_cursor_position(PhysicalPosition { + x: focused_window_size.width / 2, + y: focused_window_size.height / 2, + }) { + tracing::error!( + window_id=?focused_window_id, + "Failed to set cursor position in focused window: {err}" + ); + }; + } + _ => {} + } + } +} + +#[derive(Debug, Default)] +struct WindowSettings +{ + cursor_grab_mode: CursorGrabMode, +} diff --git a/engine/src/windowing/keyboard.rs b/engine/src/windowing/keyboard.rs new file mode 100644 index 0000000..e4fffe5 --- /dev/null +++ b/engine/src/windowing/keyboard.rs @@ -0,0 +1,763 @@ +use std::collections::HashMap; + +use ecs::Sole; + +#[derive(Debug, Default, Sole)] +pub struct Keyboard +{ + map: HashMap<Key, KeyData>, +} + +impl Keyboard +{ + #[must_use] + pub fn get_key_state(&self, key: Key) -> KeyState + { + let Some(key_data) = self.map.get(&key) else { + return KeyState::Released; + }; + + key_data.curr_state + } + + #[must_use] + pub fn get_prev_key_state(&self, key: Key) -> KeyState + { + let Some(key_data) = self.map.get(&key) else { + return KeyState::Released; + }; + + key_data.previous_state + } + + pub fn set_key_state(&mut self, key: Key, key_state: KeyState) + { + let key_data = self.map.entry(key).or_default(); + + key_data.curr_state = key_state; + } + + pub fn make_key_states_previous(&mut self) + { + for key_data in self.map.values_mut() { + key_data.previous_state = key_data.curr_state; + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum Key +{ + /// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave. + /// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd> + /// (hankaku/zenkaku/kanji) key on Japanese keyboards + Backquote, + /// Used for both the US <kbd>\\</kbd> (on the 101-key layout) and also for the key + /// located between the <kbd>"</kbd> and <kbd>Enter</kbd> keys on row C of the 102-, + /// 104- and 106-key layouts. + /// Labeled <kbd>#</kbd> on a UK (102) keyboard. + Backslash, + /// <kbd>[</kbd> on a US keyboard. + BracketLeft, + /// <kbd>]</kbd> on a US keyboard. + BracketRight, + /// <kbd>,</kbd> on a US keyboard. + Comma, + /// <kbd>0</kbd> on a US keyboard. + Digit0, + /// <kbd>1</kbd> on a US keyboard. + Digit1, + /// <kbd>2</kbd> on a US keyboard. + Digit2, + /// <kbd>3</kbd> on a US keyboard. + Digit3, + /// <kbd>4</kbd> on a US keyboard. + Digit4, + /// <kbd>5</kbd> on a US keyboard. + Digit5, + /// <kbd>6</kbd> on a US keyboard. + Digit6, + /// <kbd>7</kbd> on a US keyboard. + Digit7, + /// <kbd>8</kbd> on a US keyboard. + Digit8, + /// <kbd>9</kbd> on a US keyboard. + Digit9, + /// <kbd>=</kbd> on a US keyboard. + Equal, + /// Located between the left <kbd>Shift</kbd> and <kbd>Z</kbd> keys. + /// Labeled <kbd>\\</kbd> on a UK keyboard. + IntlBackslash, + /// Located between the <kbd>/</kbd> and right <kbd>Shift</kbd> keys. + /// Labeled <kbd>\\</kbd> (ro) on a Japanese keyboard. + IntlRo, + /// Located between the <kbd>=</kbd> and <kbd>Backspace</kbd> keys. + /// Labeled <kbd>¥</kbd> (yen) on a Japanese keyboard. <kbd>\\</kbd> on a + /// Russian keyboard. + IntlYen, + /// <kbd>a</kbd> on a US keyboard. + /// Labeled <kbd>q</kbd> on an AZERTY (e.g., French) keyboard. + A, + /// <kbd>b</kbd> on a US keyboard. + B, + /// <kbd>c</kbd> on a US keyboard. + C, + /// <kbd>d</kbd> on a US keyboard. + D, + /// <kbd>e</kbd> on a US keyboard. + E, + /// <kbd>f</kbd> on a US keyboard. + F, + /// <kbd>g</kbd> on a US keyboard. + G, + /// <kbd>h</kbd> on a US keyboard. + H, + /// <kbd>i</kbd> on a US keyboard. + I, + /// <kbd>j</kbd> on a US keyboard. + J, + /// <kbd>k</kbd> on a US keyboard. + K, + /// <kbd>l</kbd> on a US keyboard. + L, + /// <kbd>m</kbd> on a US keyboard. + M, + /// <kbd>n</kbd> on a US keyboard. + N, + /// <kbd>o</kbd> on a US keyboard. + O, + /// <kbd>p</kbd> on a US keyboard. + P, + /// <kbd>q</kbd> on a US keyboard. + /// Labeled <kbd>a</kbd> on an AZERTY (e.g., French) keyboard. + Q, + /// <kbd>r</kbd> on a US keyboard. + R, + /// <kbd>s</kbd> on a US keyboard. + S, + /// <kbd>t</kbd> on a US keyboard. + T, + /// <kbd>u</kbd> on a US keyboard. + U, + /// <kbd>v</kbd> on a US keyboard. + V, + /// <kbd>w</kbd> on a US keyboard. + /// Labeled <kbd>z</kbd> on an AZERTY (e.g., French) keyboard. + W, + /// <kbd>x</kbd> on a US keyboard. + X, + /// <kbd>y</kbd> on a US keyboard. + /// Labeled <kbd>z</kbd> on a QWERTZ (e.g., German) keyboard. + Y, + /// <kbd>z</kbd> on a US keyboard. + /// Labeled <kbd>w</kbd> on an AZERTY (e.g., French) keyboard, and <kbd>y</kbd> on a + /// QWERTZ (e.g., German) keyboard. + Z, + /// <kbd>-</kbd> on a US keyboard. + Minus, + /// <kbd>.</kbd> on a US keyboard. + Period, + /// <kbd>'</kbd> on a US keyboard. + Quote, + /// <kbd>;</kbd> on a US keyboard. + Semicolon, + /// <kbd>/</kbd> on a US keyboard. + Slash, + /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>. + AltLeft, + /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>. + /// This is labeled <kbd>AltGr</kbd> on many keyboard layouts. + AltRight, + /// <kbd>Backspace</kbd> or <kbd>⌫</kbd>. + /// Labeled <kbd>Delete</kbd> on Apple keyboards. + Backspace, + /// <kbd>CapsLock</kbd> or <kbd>⇪</kbd> + CapsLock, + /// The application context menu key, which is typically found between the right + /// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key. + ContextMenu, + /// <kbd>Control</kbd> or <kbd>⌃</kbd> + ControlLeft, + /// <kbd>Control</kbd> or <kbd>⌃</kbd> + ControlRight, + /// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards. + Enter, + /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. + SuperLeft, + /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. + SuperRight, + /// <kbd>Shift</kbd> or <kbd>⇧</kbd> + ShiftLeft, + /// <kbd>Shift</kbd> or <kbd>⇧</kbd> + ShiftRight, + /// <kbd> </kbd> (space) + Space, + /// <kbd>Tab</kbd> or <kbd>⇥</kbd> + Tab, + /// Japanese: <kbd>変</kbd> (henkan) + Convert, + /// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd> + /// (katakana/hiragana/romaji) + KanaMode, + /// Korean: HangulMode <kbd>한/영</kbd> (han/yeong) + /// + /// Japanese (Mac keyboard): <kbd>か</kbd> (kana) + Lang1, + /// Korean: Hanja <kbd>한</kbd> (hanja) + /// + /// Japanese (Mac keyboard): <kbd>英</kbd> (eisu) + Lang2, + /// Japanese (word-processing keyboard): Katakana + Lang3, + /// Japanese (word-processing keyboard): Hiragana + Lang4, + /// Japanese (word-processing keyboard): Zenkaku/Hankaku + Lang5, + /// Japanese: <kbd>無変換</kbd> (muhenkan) + NonConvert, + /// <kbd>⌦</kbd>. The forward delete key. + /// Note that on Apple keyboards, the key labelled <kbd>Delete</kbd> on the main part + /// of the keyboard is encoded as [`Backspace`]. + /// + /// [`Backspace`]: Self::Backspace + Delete, + /// <kbd>Page Down</kbd>, <kbd>End</kbd>, or <kbd>↘</kbd> + End, + /// <kbd>Help</kbd>. Not present on standard PC keyboards. + Help, + /// <kbd>Home</kbd> or <kbd>↖</kbd> + Home, + /// <kbd>Insert</kbd> or <kbd>Ins</kbd>. Not present on Apple keyboards. + Insert, + /// <kbd>Page Down</kbd>, <kbd>PgDn</kbd>, or <kbd>⇟</kbd> + PageDown, + /// <kbd>Page Up</kbd>, <kbd>PgUp</kbd>, or <kbd>⇞</kbd> + PageUp, + /// <kbd>↓</kbd> + ArrowDown, + /// <kbd>←</kbd> + ArrowLeft, + /// <kbd>→</kbd> + ArrowRight, + /// <kbd>↑</kbd> + ArrowUp, + /// On the Mac, this is used for the numpad <kbd>Clear</kbd> key. + NumLock, + /// <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control + Numpad0, + /// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or + /// remote control + Numpad1, + /// <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control + Numpad2, + /// <kbd>3 PgDn</kbd> on a keyboard. <kbd>3 DEF</kbd> on a phone or remote control + Numpad3, + /// <kbd>4 ←</kbd> on a keyboard. <kbd>4 GHI</kbd> on a phone or remote control + Numpad4, + /// <kbd>5</kbd> on a keyboard. <kbd>5 JKL</kbd> on a phone or remote control + Numpad5, + /// <kbd>6 →</kbd> on a keyboard. <kbd>6 MNO</kbd> on a phone or remote control + Numpad6, + /// <kbd>7 Home</kbd> on a keyboard. <kbd>7 PQRS</kbd> or <kbd>7 PRS</kbd> on a phone + /// or remote control + Numpad7, + /// <kbd>8 ↑</kbd> on a keyboard. <kbd>8 TUV</kbd> on a phone or remote control + Numpad8, + /// <kbd>9 PgUp</kbd> on a keyboard. <kbd>9 WXYZ</kbd> or <kbd>9 WXY</kbd> on a phone + /// or remote control + Numpad9, + /// <kbd>+</kbd> + NumpadAdd, + /// Found on the Microsoft Natural Keyboard. + NumpadBackspace, + /// <kbd>C</kbd> or <kbd>A</kbd> (All Clear). Also for use with numpads that have a + /// <kbd>Clear</kbd> key that is separate from the <kbd>NumLock</kbd> key. On the + /// Mac, the numpad <kbd>Clear</kbd> key is encoded as [`NumLock`]. + /// + /// [`NumLock`]: Self::NumLock + NumpadClear, + /// <kbd>C</kbd> (Clear Entry) + NumpadClearEntry, + /// <kbd>,</kbd> (thousands separator). For locales where the thousands separator + /// is a "." (e.g., Brazil), this key may generate a <kbd>.</kbd>. + NumpadComma, + /// <kbd>. Del</kbd>. For locales where the decimal separator is "," (e.g., + /// Brazil), this key may generate a <kbd>,</kbd>. + NumpadDecimal, + /// <kbd>/</kbd> + NumpadDivide, + NumpadEnter, + /// <kbd>=</kbd> + NumpadEqual, + /// <kbd>#</kbd> on a phone or remote control device. This key is typically found + /// below the <kbd>9</kbd> key and to the right of the <kbd>0</kbd> key. + NumpadHash, + /// <kbd>M</kbd> Add current entry to the value stored in memory. + NumpadMemoryAdd, + /// <kbd>M</kbd> Clear the value stored in memory. + NumpadMemoryClear, + /// <kbd>M</kbd> Replace the current entry with the value stored in memory. + NumpadMemoryRecall, + /// <kbd>M</kbd> Replace the value stored in memory with the current entry. + NumpadMemoryStore, + /// <kbd>M</kbd> Subtract current entry from the value stored in memory. + NumpadMemorySubtract, + /// <kbd>*</kbd> on a keyboard. For use with numpads that provide mathematical + /// operations (<kbd>+</kbd>, <kbd>-</kbd> <kbd>*</kbd> and <kbd>/</kbd>). + /// + /// Use `NumpadStar` for the <kbd>*</kbd> key on phones and remote controls. + NumpadMultiply, + /// <kbd>(</kbd> Found on the Microsoft Natural Keyboard. + NumpadParenLeft, + /// <kbd>)</kbd> Found on the Microsoft Natural Keyboard. + NumpadParenRight, + /// <kbd>*</kbd> on a phone or remote control device. + /// + /// This key is typically found below the <kbd>7</kbd> key and to the left of + /// the <kbd>0</kbd> key. + /// + /// Use <kbd>"NumpadMultiply"</kbd> for the <kbd>*</kbd> key on + /// numeric keypads. + NumpadStar, + /// <kbd>-</kbd> + NumpadSubtract, + /// <kbd>Esc</kbd> or <kbd>⎋</kbd> + Escape, + /// <kbd>Fn</kbd> This is typically a hardware key that does not generate a separate + /// code. + Fn, + /// <kbd>FLock</kbd> or <kbd>FnLock</kbd>. Function Lock key. Found on the Microsoft + /// Natural Keyboard. + FnLock, + /// <kbd>PrtScr SysRq</kbd> or <kbd>Print Screen</kbd> + PrintScreen, + /// <kbd>Scroll Lock</kbd> + ScrollLock, + /// <kbd>Pause Break</kbd> + Pause, + /// Some laptops place this key to the left of the <kbd>↑</kbd> key. + /// + /// This also the "back" button (triangle) on Android. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the <kbd>↑</kbd> key. + BrowserForward, + /// The "home" button on Android. + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + /// <kbd>Eject</kbd> or <kbd>⏏</kbd>. This key is placed in the function section on + /// some Apple keyboards. + Eject, + /// Sometimes labelled <kbd>My Computer</kbd> on the keyboard + LaunchApp1, + /// Sometimes labelled <kbd>Calculator</kbd> on the keyboard + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + /// This key is placed in the function section on some Apple keyboards, replacing the + /// <kbd>Eject</kbd> key. + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + // Legacy modifier key. Also called "Super" in certain places. + Meta, + // Legacy modifier key. + Hyper, + Turbo, + Abort, + Resume, + Suspend, + /// Found on Sun’s USB keyboard. + Again, + /// Found on Sun’s USB keyboard. + Copy, + /// Found on Sun’s USB keyboard. + Cut, + /// Found on Sun’s USB keyboard. + Find, + /// Found on Sun’s USB keyboard. + Open, + /// Found on Sun’s USB keyboard. + Paste, + /// Found on Sun’s USB keyboard. + Props, + /// Found on Sun’s USB keyboard. + Select, + /// Found on Sun’s USB keyboard. + Undo, + /// Use for dedicated <kbd>ひらがな</kbd> key found on some Japanese word processing + /// keyboards. + Hiragana, + /// Use for dedicated <kbd>カタカナ</kbd> key found on some Japanese word processing + /// keyboards. + Katakana, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl TryFrom<winit::keyboard::KeyCode> for Key +{ + type Error = UnknownKeyCodeError; + + fn try_from(key_code: winit::keyboard::KeyCode) -> Result<Self, Self::Error> + { + match key_code { + winit::keyboard::KeyCode::Backquote => Ok(Self::Backquote), + winit::keyboard::KeyCode::Backslash => Ok(Self::Backslash), + winit::keyboard::KeyCode::BracketLeft => Ok(Self::BracketLeft), + winit::keyboard::KeyCode::BracketRight => Ok(Self::BracketRight), + winit::keyboard::KeyCode::Comma => Ok(Self::Comma), + winit::keyboard::KeyCode::Digit0 => Ok(Self::Digit0), + winit::keyboard::KeyCode::Digit1 => Ok(Self::Digit1), + winit::keyboard::KeyCode::Digit2 => Ok(Self::Digit2), + winit::keyboard::KeyCode::Digit3 => Ok(Self::Digit3), + winit::keyboard::KeyCode::Digit4 => Ok(Self::Digit4), + winit::keyboard::KeyCode::Digit5 => Ok(Self::Digit5), + winit::keyboard::KeyCode::Digit6 => Ok(Self::Digit6), + winit::keyboard::KeyCode::Digit7 => Ok(Self::Digit7), + winit::keyboard::KeyCode::Digit8 => Ok(Self::Digit8), + winit::keyboard::KeyCode::Digit9 => Ok(Self::Digit9), + winit::keyboard::KeyCode::Equal => Ok(Self::Equal), + winit::keyboard::KeyCode::IntlBackslash => Ok(Self::IntlBackslash), + winit::keyboard::KeyCode::IntlRo => Ok(Self::IntlRo), + winit::keyboard::KeyCode::IntlYen => Ok(Self::IntlYen), + winit::keyboard::KeyCode::KeyA => Ok(Self::A), + winit::keyboard::KeyCode::KeyB => Ok(Self::B), + winit::keyboard::KeyCode::KeyC => Ok(Self::C), + winit::keyboard::KeyCode::KeyD => Ok(Self::D), + winit::keyboard::KeyCode::KeyE => Ok(Self::E), + winit::keyboard::KeyCode::KeyF => Ok(Self::F), + winit::keyboard::KeyCode::KeyG => Ok(Self::G), + winit::keyboard::KeyCode::KeyH => Ok(Self::H), + winit::keyboard::KeyCode::KeyI => Ok(Self::I), + winit::keyboard::KeyCode::KeyJ => Ok(Self::J), + winit::keyboard::KeyCode::KeyK => Ok(Self::K), + winit::keyboard::KeyCode::KeyL => Ok(Self::L), + winit::keyboard::KeyCode::KeyM => Ok(Self::M), + winit::keyboard::KeyCode::KeyN => Ok(Self::N), + winit::keyboard::KeyCode::KeyO => Ok(Self::O), + winit::keyboard::KeyCode::KeyP => Ok(Self::P), + winit::keyboard::KeyCode::KeyQ => Ok(Self::Q), + winit::keyboard::KeyCode::KeyR => Ok(Self::R), + winit::keyboard::KeyCode::KeyS => Ok(Self::S), + winit::keyboard::KeyCode::KeyT => Ok(Self::T), + winit::keyboard::KeyCode::KeyU => Ok(Self::U), + winit::keyboard::KeyCode::KeyV => Ok(Self::V), + winit::keyboard::KeyCode::KeyW => Ok(Self::W), + winit::keyboard::KeyCode::KeyX => Ok(Self::X), + winit::keyboard::KeyCode::KeyY => Ok(Self::Y), + winit::keyboard::KeyCode::KeyZ => Ok(Self::Z), + winit::keyboard::KeyCode::Minus => Ok(Self::Minus), + winit::keyboard::KeyCode::Period => Ok(Self::Period), + winit::keyboard::KeyCode::Quote => Ok(Self::Quote), + winit::keyboard::KeyCode::Semicolon => Ok(Self::Semicolon), + winit::keyboard::KeyCode::Slash => Ok(Self::Slash), + winit::keyboard::KeyCode::AltLeft => Ok(Self::AltLeft), + winit::keyboard::KeyCode::AltRight => Ok(Self::AltRight), + winit::keyboard::KeyCode::Backspace => Ok(Self::Backspace), + winit::keyboard::KeyCode::CapsLock => Ok(Self::CapsLock), + winit::keyboard::KeyCode::ContextMenu => Ok(Self::ContextMenu), + winit::keyboard::KeyCode::ControlLeft => Ok(Self::ControlLeft), + winit::keyboard::KeyCode::ControlRight => Ok(Self::ControlRight), + winit::keyboard::KeyCode::Enter => Ok(Self::Enter), + winit::keyboard::KeyCode::SuperLeft => Ok(Self::SuperLeft), + winit::keyboard::KeyCode::SuperRight => Ok(Self::SuperRight), + winit::keyboard::KeyCode::ShiftLeft => Ok(Self::ShiftLeft), + winit::keyboard::KeyCode::ShiftRight => Ok(Self::ShiftRight), + winit::keyboard::KeyCode::Space => Ok(Self::Space), + winit::keyboard::KeyCode::Tab => Ok(Self::Tab), + winit::keyboard::KeyCode::Convert => Ok(Self::Convert), + winit::keyboard::KeyCode::KanaMode => Ok(Self::KanaMode), + winit::keyboard::KeyCode::Lang1 => Ok(Self::Lang1), + winit::keyboard::KeyCode::Lang2 => Ok(Self::Lang2), + winit::keyboard::KeyCode::Lang3 => Ok(Self::Lang3), + winit::keyboard::KeyCode::Lang4 => Ok(Self::Lang4), + winit::keyboard::KeyCode::Lang5 => Ok(Self::Lang5), + winit::keyboard::KeyCode::NonConvert => Ok(Self::NonConvert), + winit::keyboard::KeyCode::Delete => Ok(Self::Delete), + winit::keyboard::KeyCode::End => Ok(Self::End), + winit::keyboard::KeyCode::Help => Ok(Self::Help), + winit::keyboard::KeyCode::Home => Ok(Self::Home), + winit::keyboard::KeyCode::Insert => Ok(Self::Insert), + winit::keyboard::KeyCode::PageDown => Ok(Self::PageDown), + winit::keyboard::KeyCode::PageUp => Ok(Self::PageUp), + winit::keyboard::KeyCode::ArrowDown => Ok(Self::ArrowDown), + winit::keyboard::KeyCode::ArrowLeft => Ok(Self::ArrowLeft), + winit::keyboard::KeyCode::ArrowRight => Ok(Self::ArrowRight), + winit::keyboard::KeyCode::ArrowUp => Ok(Self::ArrowUp), + winit::keyboard::KeyCode::NumLock => Ok(Self::NumLock), + winit::keyboard::KeyCode::Numpad0 => Ok(Self::Numpad0), + winit::keyboard::KeyCode::Numpad1 => Ok(Self::Numpad1), + winit::keyboard::KeyCode::Numpad2 => Ok(Self::Numpad2), + winit::keyboard::KeyCode::Numpad3 => Ok(Self::Numpad3), + winit::keyboard::KeyCode::Numpad4 => Ok(Self::Numpad4), + winit::keyboard::KeyCode::Numpad5 => Ok(Self::Numpad5), + winit::keyboard::KeyCode::Numpad6 => Ok(Self::Numpad6), + winit::keyboard::KeyCode::Numpad7 => Ok(Self::Numpad7), + winit::keyboard::KeyCode::Numpad8 => Ok(Self::Numpad8), + winit::keyboard::KeyCode::Numpad9 => Ok(Self::Numpad9), + winit::keyboard::KeyCode::NumpadAdd => Ok(Self::NumpadAdd), + winit::keyboard::KeyCode::NumpadBackspace => Ok(Self::NumpadBackspace), + winit::keyboard::KeyCode::NumpadClear => Ok(Self::NumpadClear), + winit::keyboard::KeyCode::NumpadClearEntry => Ok(Self::NumpadClearEntry), + winit::keyboard::KeyCode::NumpadComma => Ok(Self::NumpadComma), + winit::keyboard::KeyCode::NumpadDecimal => Ok(Self::NumpadDecimal), + winit::keyboard::KeyCode::NumpadDivide => Ok(Self::NumpadDivide), + winit::keyboard::KeyCode::NumpadEnter => Ok(Self::NumpadEnter), + winit::keyboard::KeyCode::NumpadEqual => Ok(Self::NumpadEqual), + winit::keyboard::KeyCode::NumpadHash => Ok(Self::NumpadHash), + winit::keyboard::KeyCode::NumpadMemoryAdd => Ok(Self::NumpadMemoryAdd), + winit::keyboard::KeyCode::NumpadMemoryClear => Ok(Self::NumpadMemoryClear), + winit::keyboard::KeyCode::NumpadMemoryRecall => Ok(Self::NumpadMemoryRecall), + winit::keyboard::KeyCode::NumpadMemoryStore => Ok(Self::NumpadMemoryStore), + winit::keyboard::KeyCode::NumpadMemorySubtract => { + Ok(Self::NumpadMemorySubtract) + } + winit::keyboard::KeyCode::NumpadMultiply => Ok(Self::NumpadMultiply), + winit::keyboard::KeyCode::NumpadParenLeft => Ok(Self::NumpadParenLeft), + winit::keyboard::KeyCode::NumpadParenRight => Ok(Self::NumpadParenRight), + winit::keyboard::KeyCode::NumpadStar => Ok(Self::NumpadStar), + winit::keyboard::KeyCode::NumpadSubtract => Ok(Self::NumpadSubtract), + winit::keyboard::KeyCode::Escape => Ok(Self::Escape), + winit::keyboard::KeyCode::Fn => Ok(Self::Fn), + winit::keyboard::KeyCode::FnLock => Ok(Self::FnLock), + winit::keyboard::KeyCode::PrintScreen => Ok(Self::PrintScreen), + winit::keyboard::KeyCode::ScrollLock => Ok(Self::ScrollLock), + winit::keyboard::KeyCode::Pause => Ok(Self::Pause), + winit::keyboard::KeyCode::BrowserBack => Ok(Self::BrowserBack), + winit::keyboard::KeyCode::BrowserFavorites => Ok(Self::BrowserFavorites), + winit::keyboard::KeyCode::BrowserForward => Ok(Self::BrowserForward), + winit::keyboard::KeyCode::BrowserHome => Ok(Self::BrowserHome), + winit::keyboard::KeyCode::BrowserRefresh => Ok(Self::BrowserRefresh), + winit::keyboard::KeyCode::BrowserSearch => Ok(Self::BrowserSearch), + winit::keyboard::KeyCode::BrowserStop => Ok(Self::BrowserStop), + winit::keyboard::KeyCode::Eject => Ok(Self::Eject), + winit::keyboard::KeyCode::LaunchApp1 => Ok(Self::LaunchApp1), + winit::keyboard::KeyCode::LaunchApp2 => Ok(Self::LaunchApp2), + winit::keyboard::KeyCode::LaunchMail => Ok(Self::LaunchMail), + winit::keyboard::KeyCode::MediaPlayPause => Ok(Self::MediaPlayPause), + winit::keyboard::KeyCode::MediaSelect => Ok(Self::MediaSelect), + winit::keyboard::KeyCode::MediaStop => Ok(Self::MediaStop), + winit::keyboard::KeyCode::MediaTrackNext => Ok(Self::MediaTrackNext), + winit::keyboard::KeyCode::MediaTrackPrevious => Ok(Self::MediaTrackPrevious), + winit::keyboard::KeyCode::Power => Ok(Self::Power), + winit::keyboard::KeyCode::Sleep => Ok(Self::Sleep), + winit::keyboard::KeyCode::AudioVolumeDown => Ok(Self::AudioVolumeDown), + winit::keyboard::KeyCode::AudioVolumeMute => Ok(Self::AudioVolumeMute), + winit::keyboard::KeyCode::AudioVolumeUp => Ok(Self::AudioVolumeUp), + winit::keyboard::KeyCode::WakeUp => Ok(Self::WakeUp), + winit::keyboard::KeyCode::Meta => Ok(Self::Meta), + winit::keyboard::KeyCode::Hyper => Ok(Self::Hyper), + winit::keyboard::KeyCode::Turbo => Ok(Self::Turbo), + winit::keyboard::KeyCode::Abort => Ok(Self::Abort), + winit::keyboard::KeyCode::Resume => Ok(Self::Resume), + winit::keyboard::KeyCode::Suspend => Ok(Self::Suspend), + winit::keyboard::KeyCode::Again => Ok(Self::Again), + winit::keyboard::KeyCode::Copy => Ok(Self::Copy), + winit::keyboard::KeyCode::Cut => Ok(Self::Cut), + winit::keyboard::KeyCode::Find => Ok(Self::Find), + winit::keyboard::KeyCode::Open => Ok(Self::Open), + winit::keyboard::KeyCode::Paste => Ok(Self::Paste), + winit::keyboard::KeyCode::Props => Ok(Self::Props), + winit::keyboard::KeyCode::Select => Ok(Self::Select), + winit::keyboard::KeyCode::Undo => Ok(Self::Undo), + winit::keyboard::KeyCode::Hiragana => Ok(Self::Hiragana), + winit::keyboard::KeyCode::Katakana => Ok(Self::Katakana), + winit::keyboard::KeyCode::F1 => Ok(Self::F1), + winit::keyboard::KeyCode::F2 => Ok(Self::F2), + winit::keyboard::KeyCode::F3 => Ok(Self::F3), + winit::keyboard::KeyCode::F4 => Ok(Self::F4), + winit::keyboard::KeyCode::F5 => Ok(Self::F5), + winit::keyboard::KeyCode::F6 => Ok(Self::F6), + winit::keyboard::KeyCode::F7 => Ok(Self::F7), + winit::keyboard::KeyCode::F8 => Ok(Self::F8), + winit::keyboard::KeyCode::F9 => Ok(Self::F9), + winit::keyboard::KeyCode::F10 => Ok(Self::F10), + winit::keyboard::KeyCode::F11 => Ok(Self::F11), + winit::keyboard::KeyCode::F12 => Ok(Self::F12), + winit::keyboard::KeyCode::F13 => Ok(Self::F13), + winit::keyboard::KeyCode::F14 => Ok(Self::F14), + winit::keyboard::KeyCode::F15 => Ok(Self::F15), + winit::keyboard::KeyCode::F16 => Ok(Self::F16), + winit::keyboard::KeyCode::F17 => Ok(Self::F17), + winit::keyboard::KeyCode::F18 => Ok(Self::F18), + winit::keyboard::KeyCode::F19 => Ok(Self::F19), + winit::keyboard::KeyCode::F20 => Ok(Self::F20), + winit::keyboard::KeyCode::F21 => Ok(Self::F21), + winit::keyboard::KeyCode::F22 => Ok(Self::F22), + winit::keyboard::KeyCode::F23 => Ok(Self::F23), + winit::keyboard::KeyCode::F24 => Ok(Self::F24), + winit::keyboard::KeyCode::F25 => Ok(Self::F25), + winit::keyboard::KeyCode::F26 => Ok(Self::F26), + winit::keyboard::KeyCode::F27 => Ok(Self::F27), + winit::keyboard::KeyCode::F28 => Ok(Self::F28), + winit::keyboard::KeyCode::F29 => Ok(Self::F29), + winit::keyboard::KeyCode::F30 => Ok(Self::F30), + winit::keyboard::KeyCode::F31 => Ok(Self::F31), + winit::keyboard::KeyCode::F32 => Ok(Self::F32), + winit::keyboard::KeyCode::F33 => Ok(Self::F33), + winit::keyboard::KeyCode::F34 => Ok(Self::F34), + winit::keyboard::KeyCode::F35 => Ok(Self::F35), + _ => Err(UnknownKeyCodeError), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Unknown key code")] +pub struct UnknownKeyCodeError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum KeyState +{ + Pressed, + Released, +} + +impl KeyState +{ + #[must_use] + #[inline] + pub fn is_pressed(&self) -> bool + { + matches!(self, Self::Pressed) + } + + #[must_use] + #[inline] + pub fn is_released(&self) -> bool + { + matches!(self, Self::Released) + } +} + +impl From<winit::event::ElementState> for KeyState +{ + fn from(element_state: winit::event::ElementState) -> Self + { + match element_state { + winit::event::ElementState::Pressed => Self::Pressed, + winit::event::ElementState::Released => Self::Released, + } + } +} + +#[derive(Debug)] +struct KeyData +{ + curr_state: KeyState, + previous_state: KeyState, +} + +impl Default for KeyData +{ + fn default() -> Self + { + KeyData { + curr_state: KeyState::Released, + previous_state: KeyState::Released, + } + } +} diff --git a/engine/src/windowing/mouse.rs b/engine/src/windowing/mouse.rs new file mode 100644 index 0000000..1afe594 --- /dev/null +++ b/engine/src/windowing/mouse.rs @@ -0,0 +1,136 @@ +use std::collections::HashMap; + +use ecs::Sole; + +use crate::vector::Vec2; + +#[derive(Debug, Default, Clone, Sole)] +#[non_exhaustive] +pub struct Motion +{ + pub position_delta: Vec2<f64>, +} + +/// Mouse buttons. +#[derive(Debug, Default, Sole)] +pub struct Buttons +{ + map: HashMap<Button, ButtonData>, +} + +impl Buttons +{ + pub fn get(&self, button: Button) -> ButtonState + { + let Some(button_data) = self.map.get(&button) else { + return ButtonState::Released; + }; + + button_data.current_state + } + + pub fn get_previous(&self, button: Button) -> ButtonState + { + let Some(button_data) = self.map.get(&button) else { + return ButtonState::Released; + }; + + button_data.previous_state + } + + pub fn set(&mut self, button: Button, button_state: ButtonState) + { + let button_data = self.map.entry(button).or_default(); + + button_data.current_state = button_state; + } + + pub(crate) fn make_states_previous(&mut self) + { + for button_data in self.map.values_mut() { + button_data.previous_state = button_data.current_state; + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Button +{ + Left, + Right, + Middle, + Back, + Forward, + Other(u16), +} + +impl From<winit::event::MouseButton> for Button +{ + fn from(mouse_button: winit::event::MouseButton) -> Self + { + match mouse_button { + winit::event::MouseButton::Left => Self::Left, + winit::event::MouseButton::Right => Self::Right, + winit::event::MouseButton::Middle => Self::Middle, + winit::event::MouseButton::Back => Self::Back, + winit::event::MouseButton::Forward => Self::Forward, + winit::event::MouseButton::Other(other_mouse_button) => { + Self::Other(other_mouse_button) + } + } + } +} + +/// Mouse button state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ButtonState +{ + Pressed, + Released, +} + +impl ButtonState +{ + #[must_use] + #[inline] + pub fn is_pressed(&self) -> bool + { + matches!(self, Self::Pressed) + } + + #[must_use] + #[inline] + pub fn is_released(&self) -> bool + { + matches!(self, Self::Released) + } +} + +impl From<winit::event::ElementState> for ButtonState +{ + fn from(element_state: winit::event::ElementState) -> Self + { + match element_state { + winit::event::ElementState::Pressed => Self::Pressed, + winit::event::ElementState::Released => Self::Released, + } + } +} + +#[derive(Debug)] +struct ButtonData +{ + current_state: ButtonState, + previous_state: ButtonState, +} + +impl Default for ButtonData +{ + fn default() -> Self + { + Self { + current_state: ButtonState::Released, + previous_state: ButtonState::Released, + } + } +} diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs new file mode 100644 index 0000000..79b2102 --- /dev/null +++ b/engine/src/windowing/window.rs @@ -0,0 +1,171 @@ +use std::borrow::Cow; + +use ecs::Component; + +use crate::data_types::dimens::Dimens; + +pub mod platform; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id +{ + inner: winit::window::WindowId, +} + +impl Id +{ + pub(crate) fn from_inner(inner: winit::window::WindowId) -> Self + { + Self { inner } + } +} + +macro_rules! impl_creation_attributes_field_fns { + ($field: ident, ($($getter_ret_pre: tt)?), $getter_ret_type: ty, $field_type: ty) => { + impl CreationAttributes + { + pub fn $field(&self) -> $getter_ret_type + { + $($getter_ret_pre)? self.attrs.$field + } + + paste::paste! { + pub fn [<with_ $field>](mut self, $field: impl Into<$field_type>) -> Self { + self.attrs.$field = $field.into(); + self + } + } + } + }; +} + +#[derive(Debug, Component, Clone)] +#[non_exhaustive] +pub struct CreationAttributes +{ + attrs: winit::window::WindowAttributes, +} + +impl_creation_attributes_field_fns!(title, (&), &str, String); +impl_creation_attributes_field_fns!(transparent, (), bool, bool); + +impl CreationAttributes +{ + #[cfg(target_os = "linux")] + pub fn with_x11_visual(mut self, visual_id: XVisualID) -> Self + { + use winit::platform::x11::WindowAttributesExtX11; + + self.attrs = self.attrs.with_x11_visual(visual_id); + + self + } +} + +impl CreationAttributes +{ + pub(crate) fn into_inner(self) -> winit::window::WindowAttributes + { + self.attrs + } +} + +impl Default for CreationAttributes +{ + fn default() -> Self + { + CreationAttributes { + attrs: winit::window::WindowAttributes::default().with_title("Application"), + } + } +} + +#[derive(Debug, Component, Clone, Copy)] +pub struct CreationReady; + +#[derive(Debug, Component)] +#[non_exhaustive] +pub struct Window +{ + wid: Id, + pub title: Cow<'static, str>, + pub cursor_visible: bool, + pub cursor_grab_mode: CursorGrabMode, + inner_size: Dimens<u32>, +} + +impl Window +{ + pub fn wid(&self) -> Id + { + self.wid + } + + pub fn inner_size(&self) -> &Dimens<u32> + { + &self.inner_size + } + + pub(crate) fn new( + winit_window: &winit::window::Window, + creation_attrs: &CreationAttributes, + ) -> Self + { + Self { + wid: Id::from_inner(winit_window.id()), + title: creation_attrs.title().to_string().into(), + cursor_visible: true, + cursor_grab_mode: CursorGrabMode::None, + inner_size: winit_window.inner_size().into(), + } + } + + pub(crate) fn apply(&self, winit_window: &winit::window::Window) + { + winit_window.set_title(&self.title); + winit_window.set_cursor_visible(self.cursor_visible); + } + + pub(crate) fn set_inner_size(&mut self, inner_size: Dimens<u32>) + { + self.inner_size = inner_size; + } +} + +#[derive(Debug, Component)] +pub struct Closed; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CursorGrabMode +{ + #[default] + None, + + /// The cursor is locked to a specific position in the window. + Locked, +} + +/// A unique identifier for an X11 visual. +pub type XVisualID = u32; + +impl<P> From<winit::dpi::PhysicalSize<P>> for Dimens<P> +{ + fn from(size: winit::dpi::PhysicalSize<P>) -> Self + { + Self { + width: size.width, + height: size.height, + } + } +} + +impl<P> From<Dimens<P>> for winit::dpi::PhysicalSize<P> +{ + fn from(dimens: Dimens<P>) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} diff --git a/engine/src/windowing/window/platform.rs b/engine/src/windowing/window/platform.rs new file mode 100644 index 0000000..f3908a2 --- /dev/null +++ b/engine/src/windowing/window/platform.rs @@ -0,0 +1,12 @@ +#[cfg(x11_platform)] +pub mod x11 +{ + use std::ffi::c_void; + + pub type XlibErrorHook = Box<dyn Fn(*mut c_void, *mut c_void) -> bool + Send + Sync>; + + pub fn register_xlib_error_hook(hook: XlibErrorHook) + { + winit::platform::x11::register_xlib_error_hook(hook); + } +} diff --git a/engine/src/work_queue.rs b/engine/src/work_queue.rs new file mode 100644 index 0000000..7226c7d --- /dev/null +++ b/engine/src/work_queue.rs @@ -0,0 +1,44 @@ +use std::marker::PhantomData; +use std::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender}; +use std::thread::JoinHandle as ThreadHandle; + +pub struct Work<UserData: Send + Sync + 'static> +{ + pub func: fn(UserData), + pub user_data: UserData, +} + +#[derive(Debug)] +pub struct WorkQueue<UserData: Send + Sync + 'static> +{ + work_sender: MpscSender<Work<UserData>>, + _thread: ThreadHandle<()>, + _pd: PhantomData<UserData>, +} + +impl<UserData: Send + Sync + 'static> WorkQueue<UserData> +{ + pub fn new() -> Self + { + let (work_sender, work_receiver) = mpsc_channel::<Work<UserData>>(); + + Self { + work_sender, + _thread: std::thread::spawn(move || { + let work_receiver = work_receiver; + + while let Ok(work) = work_receiver.recv() { + (work.func)(work.user_data); + } + }), + _pd: PhantomData, + } + } + + pub fn add_work(&self, work: Work<UserData>) + { + if self.work_sender.send(work).is_err() { + tracing::error!("Cannot add work to work queue. Work queue thread is dead"); + } + } +} diff --git a/glfw/Cargo.toml b/glfw/Cargo.toml deleted file mode 100644 index 0a47e73..0000000 --- a/glfw/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "glfw" -version = "0.1.0" -edition = "2021" - -[features] -opengl = [] - -[dependencies] -libc = "0.2.148" -thiserror = "1.0.49" -bitflags = "2.4.0" -util-macros = { path = "../util-macros" } - -[build-dependencies.bindgen] -version = "0.68.1" -default-features = false -features = ["runtime"] diff --git a/glfw/build.rs b/glfw/build.rs deleted file mode 100644 index 18ac677..0000000 --- a/glfw/build.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::env; -use std::error::Error; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::PathBuf; - -use bindgen::{Abi, MacroTypeVariation}; - -fn main() -> Result<(), Box<dyn Error>> -{ - println!("cargo:rustc-link-lib=glfw"); - - println!("cargo:rerun-if-changed=glfw.h"); - - let bindings = bindgen::Builder::default() - .header("glfw.h") - .clang_arg("-fretain-comments-from-system-headers") - .generate_comments(true) - .allowlist_function("glfw.*") - .allowlist_type("GLFW.*") - .allowlist_var("GLFW.*") - .blocklist_type("GLFWglproc") - .default_macro_constant_type(MacroTypeVariation::Signed) - .override_abi(Abi::CUnwind, ".*") - .generate()?; - - let out_path = PathBuf::from(env::var("OUT_DIR")?); - - let bindings_file_path = out_path.join("bindings.rs"); - - bindings.write_to_file(&bindings_file_path)?; - - let mut bindings_file = OpenOptions::new().append(true).open(bindings_file_path)?; - - // Cannot be C-unwind :( - writeln!( - bindings_file, - "pub type GLFWglproc = ::std::option::Option<unsafe extern \"C\" fn()>;" - )?; - - Ok(()) -} diff --git a/glfw/glfw.h b/glfw/glfw.h deleted file mode 100644 index 7b435c0..0000000 --- a/glfw/glfw.h +++ /dev/null @@ -1,6 +0,0 @@ -#include <GLFW/glfw3.h> - -#if GLFW_VERSION_MAJOR != 3 -#error "Unsupported major version of GLFW. Must be 3" -#endif - diff --git a/glfw/src/ffi.rs b/glfw/src/ffi.rs deleted file mode 100644 index d0affd0..0000000 --- a/glfw/src/ffi.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![allow( - non_snake_case, - non_camel_case_types, - non_upper_case_globals, - unused, - clippy::unreadable_literal -)] - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/glfw/src/init.rs b/glfw/src/init.rs deleted file mode 100644 index df5a989..0000000 --- a/glfw/src/init.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::ffi::c_int; -use std::ptr::null_mut; - -use crate::util::is_main_thread; -use crate::{get_glfw_error, Error}; - -/// GLFW 3.x.x is supported -static SUPPORTED_GLFW_VERSION_MAJOR: c_int = 3; - -/// Initializes GLFW and returns a initialization token. -/// -/// # Errors -/// Will return `Err` if -/// - The current thread is not the main thread -/// - A GLFW error occurs -pub fn initialize() -> Result<Glfw, Error> -{ - if !is_main_thread() { - return Err(Error::NotInMainThread); - } - - let mut major: c_int = 0; - - unsafe { crate::ffi::glfwGetVersion(&mut major, null_mut(), null_mut()) }; - - if major != SUPPORTED_GLFW_VERSION_MAJOR { - return Err(Error::UnsupportedVersion); - } - - // SAFETY: The current thread is the main thread - let success = unsafe { crate::ffi::glfwInit() }; - - if success == crate::ffi::GLFW_FALSE { - get_glfw_error()?; - } - - Ok(Glfw { _priv: &() }) -} - -/// GLFW initialization token. -#[derive(Debug)] -pub struct Glfw -{ - /// This field has two purposes - /// - To make the struct not constructable without calling [`initialize`]. - /// - To make the struct `!Send` and `!Sync`. - _priv: *const (), -} - -impl Drop for Glfw -{ - fn drop(&mut self) - { - // SAFETY: The current thread cannot be any other thread than the main thread - // since the initialize function checks it and the GLFW initialization token is - // neither Send or Sync - unsafe { crate::ffi::glfwTerminate() }; - } -} diff --git a/glfw/src/lib.rs b/glfw/src/lib.rs deleted file mode 100644 index f858766..0000000 --- a/glfw/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![deny(clippy::all, clippy::pedantic)] - -use std::ffi::{c_char, CStr}; -use std::ptr::null; - -mod ffi; -mod init; -mod util; - -pub mod window; - -pub use window::{Builder as WindowBuilder, Size as WindowSize, Window}; - -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - /// The current thread is not the main thread. - #[error("The current thread is not the main thread")] - NotInMainThread, - - /// An internal nul byte was found in a window title. - #[error("An internal nul byte was found in the window title")] - InternalNulByteInWindowTitle, - - #[error("This version of GLFW is unsupported")] - UnsupportedVersion, - - /// GLFW error. - #[error("GLFW error {0} occured. {1}")] - GlfwError(i32, String), -} - -fn get_glfw_error() -> Result<(), Error> -{ - let mut description_ptr: *const c_char = null(); - - let err = unsafe { crate::ffi::glfwGetError(&mut description_ptr) }; - - if err == crate::ffi::GLFW_NO_ERROR { - return Ok(()); - } - - // SAFETY: The description is guaranteed by GLFW to be valid UTF-8 - let desc_str = unsafe { - std::str::from_utf8_unchecked(CStr::from_ptr(description_ptr).to_bytes()) - }; - - // The description has to be copied because it is guaranteed to be valid only - // until the next GLFW error occurs or the GLFW library is terminated. - let description = desc_str.to_string(); - - Err(Error::GlfwError(err, description)) -} diff --git a/glfw/src/util.rs b/glfw/src/util.rs deleted file mode 100644 index f77aaf8..0000000 --- a/glfw/src/util.rs +++ /dev/null @@ -1,10 +0,0 @@ -use libc::{c_long, getpid, syscall, SYS_gettid}; - -pub fn is_main_thread() -> bool -{ - let ttid = unsafe { syscall(SYS_gettid) }; - - let pid = c_long::from(unsafe { getpid() }); - - ttid == pid -} diff --git a/glfw/src/window.rs b/glfw/src/window.rs deleted file mode 100644 index 1e7e777..0000000 --- a/glfw/src/window.rs +++ /dev/null @@ -1,863 +0,0 @@ -use std::cell::RefCell; -use std::ffi::{c_double, c_int, CString}; -use std::hint::unreachable_unchecked; -use std::io::{stdout, Write}; -use std::ptr::null_mut; - -use bitflags::bitflags; -use util_macros::{FromRepr, VariantArr}; - -use crate::init::{initialize, Glfw}; -use crate::{get_glfw_error, Error}; - -#[derive(Debug)] -pub struct Window -{ - _init: Glfw, - handle: *mut crate::ffi::GLFWwindow, -} - -impl Window -{ - /// Makes the context of the window current for the calling thread. - /// - /// # Errors - /// Will return `Err` if a GLFW platform error occurs or if no OpenGL context is - /// present. - #[cfg(feature = "opengl")] - pub fn make_context_current(&self) -> Result<(), Error> - { - unsafe { crate::ffi::glfwMakeContextCurrent(self.handle) }; - - get_glfw_error()?; - - Ok(()) - } - - /// Returns the address of the specified OpenGL function, if it is supported by the - /// current context. - /// - /// # Errors - /// Will return `Err` if a GLFW platform error occurs or if no current context has - /// been set. - #[cfg(feature = "opengl")] - pub fn get_proc_address( - &self, - proc_name: &std::ffi::CStr, - ) -> Result<unsafe extern "C" fn(), Error> - { - let proc_addr = unsafe { crate::ffi::glfwGetProcAddress(proc_name.as_ptr()) }; - - get_glfw_error()?; - - // SAFETY: Is only None when a error has occured and that case is handled above - Ok(unsafe { proc_addr.unwrap_unchecked() }) - } - - /// Processes all pending events. - /// - /// # Errors - /// Will return `Err` if a GLFW platform error occurs. - pub fn poll_events(&self) -> Result<(), Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { crate::ffi::glfwPollEvents() }; - - get_glfw_error()?; - - Ok(()) - } - - /// Swaps the front and back buffers of the window. - /// - /// # Errors - /// Will return `Err` if a GLFW platform error occurs or if no OpenGL window context - /// is present. - pub fn swap_buffers(&self) -> Result<(), Error> - { - unsafe { - crate::ffi::glfwSwapBuffers(self.handle); - }; - - get_glfw_error()?; - - Ok(()) - } - - /// Returns whether or not the window should close. - #[must_use] - pub fn should_close(&self) -> bool - { - let should_close = unsafe { crate::ffi::glfwWindowShouldClose(self.handle) }; - - should_close == crate::ffi::GLFW_TRUE - } - - /// Retrieves the size of the window. - /// - /// # Errors - /// Will return `Err` if a GLFW platform error occurs. - pub fn size(&self) -> Result<Size, Error> - { - let mut width = 0; - let mut height = 0; - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { crate::ffi::glfwGetWindowSize(self.handle, &mut width, &mut height) }; - - get_glfw_error()?; - - #[allow(clippy::cast_sign_loss)] - Ok(Size { - width: width as u32, - height: height as u32, - }) - } - - /// Sets the callback which is called when the user attempts to close the window. - pub fn set_close_callback(&self, callback: impl Fn() + 'static) - { - CLOSE_CALLBACK.with_borrow_mut(|cb| { - *cb = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetWindowCloseCallback(self.handle, Some(close_callback)); - } - } - - pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Size) + 'static) - { - FRAMEBUFFER_SIZE_CB.with_borrow_mut(|framebuffer_size_cb| { - *framebuffer_size_cb = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetFramebufferSizeCallback( - self.handle, - Some(framebuffer_size_callback), - ); - } - } - - pub fn set_key_callback( - &self, - callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, - ) - { - KEY_CALLBACK.with_borrow_mut(|key_callback| { - *key_callback = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetKeyCallback(self.handle, Some(key_callback)); - } - } - - /// Sets the cursor position callback. - /// - /// The callback is provided with the position, in screen coordinates, relative to the - /// upper-left corner of the content area of the window. - pub fn set_cursor_pos_callback(&self, callback: impl Fn(CursorPosition) + 'static) - { - CURSOR_POS_CALLBACK.with_borrow_mut(|cursor_pos_callback| { - *cursor_pos_callback = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetCursorPosCallback(self.handle, Some(cursor_pos_callback)); - } - } - - /// Sets the mouse button callback. - pub fn set_mouse_button_callback( - &self, - callback: impl Fn(MouseButton, MouseButtonState, KeyModifiers) + 'static, - ) - { - MOUSE_BUTTON_CALLBACK.with_borrow_mut(|mouse_button_callback| { - *mouse_button_callback = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetMouseButtonCallback( - self.handle, - Some(mouse_button_callback), - ); - } - } - - /// Sets the callback called when the window gains or loses input focus. - pub fn set_focus_callback(&self, callback: impl Fn(bool) + 'static) - { - FOCUS_CALLBACK.with_borrow_mut(|focus_cb| { - *focus_cb = Some(Box::new(callback)); - }); - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetWindowFocusCallback(self.handle, Some(focus_callback)); - } - } - - /// Returns the last reported state of a keyboard key. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn get_key(&self, key: Key) -> Result<KeyState, Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - let state = unsafe { crate::ffi::glfwGetKey(self.handle, key as i32) }; - - get_glfw_error()?; - - Ok(match state { - crate::ffi::GLFW_PRESS => KeyState::Pressed, - crate::ffi::GLFW_RELEASE => KeyState::Released, - _ => { - // SAFETY: glfwGetKey can only return GLFW_PRESS or GLFW_RELEASE - unsafe { - unreachable_unchecked(); - } - } - }) - } - - /// Returns the last reported state of a mouse button. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn get_mouse_button( - &self, - mouse_button: MouseButton, - ) -> Result<MouseButtonState, Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - let state = - unsafe { crate::ffi::glfwGetMouseButton(self.handle, mouse_button as i32) }; - - get_glfw_error()?; - - Ok(match state { - crate::ffi::GLFW_PRESS => MouseButtonState::Pressed, - crate::ffi::GLFW_RELEASE => MouseButtonState::Released, - _ => { - // SAFETY: glfwGetMouseButton can only return GLFW_PRESS or GLFW_RELEASE - unsafe { - unreachable_unchecked(); - } - } - }) - } - - /// Returns the position of the cursor, in screen coordinates, relative to the - /// upper-left corner of the content area of the window. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn get_cursor_position(&self) -> Result<CursorPosition, Error> - { - let mut x_pos = 0.0; - let mut y_pos = 0.0; - - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwGetCursorPos(self.handle, &mut x_pos, &mut y_pos); - } - - get_glfw_error()?; - - Ok(CursorPosition { x: x_pos, y: y_pos }) - } - - /// Sets a input mode option. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn set_input_mode( - &self, - input_mode: InputMode, - enabled: bool, - ) -> Result<(), Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetInputMode( - self.handle, - input_mode as i32, - i32::from(enabled), - ); - } - - get_glfw_error()?; - - Ok(()) - } - - /// Sets the cursor mode. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - unsafe { - crate::ffi::glfwSetInputMode( - self.handle, - crate::ffi::GLFW_CURSOR, - cursor_mode as i32, - ); - } - - get_glfw_error()?; - - Ok(()) - } - - /// Returns whether or not raw mouse motion is supported. - /// - /// # Errors - /// Will return `Err` if a GLFW error occurs. - pub fn is_raw_mouse_motion_supported(&self) -> Result<bool, Error> - { - // SAFETY: The initialize function (called when the window is created) makes sure - // the current thread is the main thread - let supported = unsafe { crate::ffi::glfwRawMouseMotionSupported() }; - - get_glfw_error()?; - - Ok(supported == crate::ffi::GLFW_TRUE) - } -} - -/// [`Window`] builder. -#[derive(Debug, Clone, Default)] -pub struct Builder -{ - hints: Vec<(Hint, HintValue)>, -} - -impl Builder -{ - #[must_use] - pub fn new() -> Self - { - Self { hints: Vec::new() } - } - - /// Adds a window creation hint to set. - #[must_use] - pub fn hint(mut self, hint: Hint, value: HintValue) -> Self - { - self.hints.push((hint, value)); - - self - } - - /// Sets the window hints to set. - pub fn hints(mut self, hints: impl IntoIterator<Item = (Hint, HintValue)>) - { - self.hints = hints.into_iter().collect(); - } - - /// Creates a new window. - /// - /// # Errors - /// Will return `Err` if - /// - The title contains an internal nul byte - /// - A GLFW error occurs - pub fn create(&self, size: &Size, title: &str) -> Result<Window, Error> - { - let c_title = - CString::new(title).map_err(|_| Error::InternalNulByteInWindowTitle)?; - - let init = initialize()?; - - for (hint, value) in &self.hints { - // SAFETY: The initialize function makes sure the current thread is the main - // thread - // - // Error is not checked for after since the two possible errors - // (GLFW_NOT_INITIALIZED and GLFW_INVALID_ENUM) cannot occur. - unsafe { - crate::ffi::glfwWindowHint( - *hint as i32, - match value { - HintValue::Number(num) => *num, - HintValue::Bool(boolean) => { - if *boolean { - crate::ffi::GLFW_TRUE - } else { - crate::ffi::GLFW_FALSE - } - } - }, - ); - } - } - - // SAFETY: The initialize function makes sure the current thread is the main - // thread - let handle = unsafe { - #[allow(clippy::cast_possible_wrap)] - crate::ffi::glfwCreateWindow( - size.width as i32, - size.height as i32, - c_title.as_ptr(), - null_mut(), - null_mut(), - ) - }; - - get_glfw_error()?; - - Ok(Window { _init: init, handle }) - } -} - -/// Window creation hint -#[derive(Debug, Clone, Copy)] -#[repr(i32)] -#[non_exhaustive] -pub enum Hint -{ - /// Specifies whether the OpenGL context should be created in debug mode, which may - /// provide additional error and diagnostic reporting functionality. - /// - /// Valid values are [`HintValue::Bool`]. - OpenGLDebugContext = crate::ffi::GLFW_OPENGL_DEBUG_CONTEXT, - - /// Specifies the desired number of samples to use for multisampling. Zero disables - /// multisampling. - /// - /// Valid values are: [`HintValue::Number`]. - Samples = crate::ffi::GLFW_SAMPLES, - - /// Specifies whether the framebuffer should be double buffered. You nearly always - /// want to use double buffering. This is a hard constraint. - /// - /// Valid values are [`HintValue::Bool`]. - DoubleBuffer = crate::ffi::GLFW_DOUBLEBUFFER, -} - -/// Window creation hint value. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum HintValue -{ - Number(i32), - Bool(bool), -} - -/// Window size. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Size -{ - pub width: u32, - pub height: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(i32)] -pub enum InputMode -{ - /// When sticky keys mode is enabled, the pollable state of a key will remain - /// [`KeyState::Pressed`] until the state of that key is polled with - /// [`Window::get_key`]. Once it has been polled, if a key release event had been - /// processed in the meantime, the state will reset to [`KeyState::Released`], - /// otherwise it will remain [`KeyState::Pressed`]. - StickyKeys = crate::ffi::GLFW_STICKY_KEYS, - - /// When sticky mouse buttons mode is enabled, the pollable state of a mouse button - /// will remain [`MouseButtonState::Pressed`] until the state of that button is - /// polled with [`Window::get_mouse_button`]. Once it has been polled, if a mouse - /// button release event had been processed in the meantime, the state will reset - /// to [`MouseButtonState::Released`], otherwise it will remain - /// [`MouseButton::Pressed`]. - StickyMouseButtons = crate::ffi::GLFW_STICKY_MOUSE_BUTTONS, - - LockKeyMods = crate::ffi::GLFW_LOCK_KEY_MODS, - - /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be - /// enabled if available. - /// - /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It - /// is not affected by the scaling and acceleration applied to the motion of the - /// desktop cursor. That processing is suitable for a cursor while raw motion is - /// better for controlling for example a 3D camera. Because of this, raw mouse motion - /// is only provided when the cursor is disabled. - RawMouseMotion = crate::ffi::GLFW_RAW_MOUSE_MOTION, -} - -#[derive(Debug, Clone, Copy)] -#[repr(i32)] -pub enum CursorMode -{ - /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This - /// is useful for implementing for example 3D camera controls. - Disabled = crate::ffi::GLFW_CURSOR_DISABLED, - - /// Makes the cursor invisible when it is over the content area of the window but - /// does not restrict the cursor from leaving. - Hidden = crate::ffi::GLFW_CURSOR_HIDDEN, - - /// Makes the cursor visible and behaving normally. - Normal = crate::ffi::GLFW_CURSOR_NORMAL, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromRepr, VariantArr)] -#[variant_arr(name = KEYS)] -#[repr(i32)] -pub enum Key -{ - // Unknown = crate::ffi::GLFW_KEY_UNKNOWN, - Space = crate::ffi::GLFW_KEY_SPACE, - Apostrophe = crate::ffi::GLFW_KEY_APOSTROPHE, - Comma = crate::ffi::GLFW_KEY_COMMA, - Minus = crate::ffi::GLFW_KEY_MINUS, - Period = crate::ffi::GLFW_KEY_PERIOD, - Slash = crate::ffi::GLFW_KEY_SLASH, - Digit0 = crate::ffi::GLFW_KEY_0, - Digit1 = crate::ffi::GLFW_KEY_1, - Digit2 = crate::ffi::GLFW_KEY_2, - Digit3 = crate::ffi::GLFW_KEY_3, - Digit4 = crate::ffi::GLFW_KEY_4, - Digit5 = crate::ffi::GLFW_KEY_5, - Digit6 = crate::ffi::GLFW_KEY_6, - Digit7 = crate::ffi::GLFW_KEY_7, - Digit8 = crate::ffi::GLFW_KEY_8, - Digit9 = crate::ffi::GLFW_KEY_9, - Semicolon = crate::ffi::GLFW_KEY_SEMICOLON, - Equal = crate::ffi::GLFW_KEY_EQUAL, - A = crate::ffi::GLFW_KEY_A, - B = crate::ffi::GLFW_KEY_B, - C = crate::ffi::GLFW_KEY_C, - D = crate::ffi::GLFW_KEY_D, - E = crate::ffi::GLFW_KEY_E, - F = crate::ffi::GLFW_KEY_F, - G = crate::ffi::GLFW_KEY_G, - H = crate::ffi::GLFW_KEY_H, - I = crate::ffi::GLFW_KEY_I, - J = crate::ffi::GLFW_KEY_J, - K = crate::ffi::GLFW_KEY_K, - L = crate::ffi::GLFW_KEY_L, - M = crate::ffi::GLFW_KEY_M, - N = crate::ffi::GLFW_KEY_N, - O = crate::ffi::GLFW_KEY_O, - P = crate::ffi::GLFW_KEY_P, - Q = crate::ffi::GLFW_KEY_Q, - R = crate::ffi::GLFW_KEY_R, - S = crate::ffi::GLFW_KEY_S, - T = crate::ffi::GLFW_KEY_T, - U = crate::ffi::GLFW_KEY_U, - V = crate::ffi::GLFW_KEY_V, - W = crate::ffi::GLFW_KEY_W, - X = crate::ffi::GLFW_KEY_X, - Y = crate::ffi::GLFW_KEY_Y, - Z = crate::ffi::GLFW_KEY_Z, - LeftBracket = crate::ffi::GLFW_KEY_LEFT_BRACKET, - Backslash = crate::ffi::GLFW_KEY_BACKSLASH, - RightBracket = crate::ffi::GLFW_KEY_RIGHT_BRACKET, - GraveAccent = crate::ffi::GLFW_KEY_GRAVE_ACCENT, - World1 = crate::ffi::GLFW_KEY_WORLD_1, - World2 = crate::ffi::GLFW_KEY_WORLD_2, - Escape = crate::ffi::GLFW_KEY_ESCAPE, - Enter = crate::ffi::GLFW_KEY_ENTER, - Tab = crate::ffi::GLFW_KEY_TAB, - Backspace = crate::ffi::GLFW_KEY_BACKSPACE, - Insert = crate::ffi::GLFW_KEY_INSERT, - Delete = crate::ffi::GLFW_KEY_DELETE, - Right = crate::ffi::GLFW_KEY_RIGHT, - Left = crate::ffi::GLFW_KEY_LEFT, - Down = crate::ffi::GLFW_KEY_DOWN, - Up = crate::ffi::GLFW_KEY_UP, - PageUp = crate::ffi::GLFW_KEY_PAGE_UP, - PageDown = crate::ffi::GLFW_KEY_PAGE_DOWN, - Home = crate::ffi::GLFW_KEY_HOME, - End = crate::ffi::GLFW_KEY_END, - CapsLock = crate::ffi::GLFW_KEY_CAPS_LOCK, - ScrollLock = crate::ffi::GLFW_KEY_SCROLL_LOCK, - NumLock = crate::ffi::GLFW_KEY_NUM_LOCK, - PrintScreen = crate::ffi::GLFW_KEY_PRINT_SCREEN, - Pause = crate::ffi::GLFW_KEY_PAUSE, - F1 = crate::ffi::GLFW_KEY_F1, - F2 = crate::ffi::GLFW_KEY_F2, - F3 = crate::ffi::GLFW_KEY_F3, - F4 = crate::ffi::GLFW_KEY_F4, - F5 = crate::ffi::GLFW_KEY_F5, - F6 = crate::ffi::GLFW_KEY_F6, - F7 = crate::ffi::GLFW_KEY_F7, - F8 = crate::ffi::GLFW_KEY_F8, - F9 = crate::ffi::GLFW_KEY_F9, - F10 = crate::ffi::GLFW_KEY_F10, - F11 = crate::ffi::GLFW_KEY_F11, - F12 = crate::ffi::GLFW_KEY_F12, - F13 = crate::ffi::GLFW_KEY_F13, - F14 = crate::ffi::GLFW_KEY_F14, - F15 = crate::ffi::GLFW_KEY_F15, - F16 = crate::ffi::GLFW_KEY_F16, - F17 = crate::ffi::GLFW_KEY_F17, - F18 = crate::ffi::GLFW_KEY_F18, - F19 = crate::ffi::GLFW_KEY_F19, - F20 = crate::ffi::GLFW_KEY_F20, - F21 = crate::ffi::GLFW_KEY_F21, - F22 = crate::ffi::GLFW_KEY_F22, - F23 = crate::ffi::GLFW_KEY_F23, - F24 = crate::ffi::GLFW_KEY_F24, - F25 = crate::ffi::GLFW_KEY_F25, - Kp0 = crate::ffi::GLFW_KEY_KP_0, - Kp1 = crate::ffi::GLFW_KEY_KP_1, - Kp2 = crate::ffi::GLFW_KEY_KP_2, - Kp3 = crate::ffi::GLFW_KEY_KP_3, - Kp4 = crate::ffi::GLFW_KEY_KP_4, - Kp5 = crate::ffi::GLFW_KEY_KP_5, - Kp6 = crate::ffi::GLFW_KEY_KP_6, - Kp7 = crate::ffi::GLFW_KEY_KP_7, - Kp8 = crate::ffi::GLFW_KEY_KP_8, - Kp9 = crate::ffi::GLFW_KEY_KP_9, - KpDecimal = crate::ffi::GLFW_KEY_KP_DECIMAL, - KpDivide = crate::ffi::GLFW_KEY_KP_DIVIDE, - KpMultiply = crate::ffi::GLFW_KEY_KP_MULTIPLY, - KpSubtract = crate::ffi::GLFW_KEY_KP_SUBTRACT, - KpAdd = crate::ffi::GLFW_KEY_KP_ADD, - KpEnter = crate::ffi::GLFW_KEY_KP_ENTER, - KpEqual = crate::ffi::GLFW_KEY_KP_EQUAL, - LeftShift = crate::ffi::GLFW_KEY_LEFT_SHIFT, - LeftControl = crate::ffi::GLFW_KEY_LEFT_CONTROL, - LeftAlt = crate::ffi::GLFW_KEY_LEFT_ALT, - LeftSuper = crate::ffi::GLFW_KEY_LEFT_SUPER, - RightShift = crate::ffi::GLFW_KEY_RIGHT_SHIFT, - RightControl = crate::ffi::GLFW_KEY_RIGHT_CONTROL, - RightAlt = crate::ffi::GLFW_KEY_RIGHT_ALT, - RightSuper = crate::ffi::GLFW_KEY_RIGHT_SUPER, - Menu = crate::ffi::GLFW_KEY_MENU, -} - -#[derive(Debug, Clone, Copy, FromRepr)] -#[repr(i32)] -pub enum KeyState -{ - Pressed = crate::ffi::GLFW_PRESS, - Released = crate::ffi::GLFW_RELEASE, - Repeat = crate::ffi::GLFW_REPEAT, -} - -bitflags! { - #[derive(Debug, Clone, Copy)] - pub struct KeyModifiers: i32 { - const SHIFT = crate::ffi::GLFW_MOD_SHIFT; - const CONTROL = crate::ffi::GLFW_MOD_CONTROL; - const ALT = crate::ffi::GLFW_MOD_ALT; - const SUPER = crate::ffi::GLFW_MOD_SUPER; - const CAPS_LOCK = crate::ffi::GLFW_MOD_CAPS_LOCK; - const NUM_LOCK = crate::ffi::GLFW_MOD_NUM_LOCK; - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromRepr)] -#[repr(i32)] -pub enum MouseButton -{ - One = crate::ffi::GLFW_MOUSE_BUTTON_1, - Two = crate::ffi::GLFW_MOUSE_BUTTON_2, - Three = crate::ffi::GLFW_MOUSE_BUTTON_3, - Four = crate::ffi::GLFW_MOUSE_BUTTON_4, - Five = crate::ffi::GLFW_MOUSE_BUTTON_5, - Six = crate::ffi::GLFW_MOUSE_BUTTON_6, - Seven = crate::ffi::GLFW_MOUSE_BUTTON_7, - Eight = crate::ffi::GLFW_MOUSE_BUTTON_8, -} - -impl MouseButton -{ - pub const LEFT: Self = Self::One; - pub const MIDDLE: Self = Self::Three; - pub const RIGHT: Self = Self::Two; -} - -#[derive(Debug, Clone, Copy, FromRepr)] -#[repr(i32)] -pub enum MouseButtonState -{ - Pressed = crate::ffi::GLFW_PRESS, - Released = crate::ffi::GLFW_RELEASE, -} - -#[derive(Debug, Clone)] -pub struct CursorPosition -{ - pub x: f64, - pub y: f64, -} - -type CloseCallback = Box<dyn Fn()>; -type FramebufferSizeCb = Box<dyn Fn(Size)>; -type KeyCallback = Box<dyn Fn(Key, i32, KeyState, KeyModifiers)>; -type CursorPositionCallback = Box<dyn Fn(CursorPosition)>; -type MouseButtonCallback = Box<dyn Fn(MouseButton, MouseButtonState, KeyModifiers)>; -type FocusCallback = Box<dyn Fn(bool)>; - -thread_local! { -static CLOSE_CALLBACK: RefCell<Option<CloseCallback>> = RefCell::new(None); -static FRAMEBUFFER_SIZE_CB: RefCell<Option<FramebufferSizeCb>> = RefCell::new(None); -static KEY_CALLBACK: RefCell<Option<KeyCallback>> = RefCell::new(None); -static CURSOR_POS_CALLBACK: RefCell<Option<CursorPositionCallback>> = RefCell::new(None); -static MOUSE_BUTTON_CALLBACK: RefCell<Option<MouseButtonCallback>> = RefCell::new(None); -static FOCUS_CALLBACK: RefCell<Option<FocusCallback>> = RefCell::new(None); -} - -extern "C-unwind" fn close_callback(_window: *mut crate::ffi::GLFWwindow) -{ - CLOSE_CALLBACK - .try_with(|close_cb| { - if let Some(cb) = close_cb.borrow().as_deref() { - cb(); - } - }) - .ok(); -} - -extern "C-unwind" fn framebuffer_size_callback( - _window: *mut crate::ffi::GLFWwindow, - c_width: c_int, - c_height: c_int, -) -{ - // Width and height can't possibly have their sign bit set - let width = u32::from_le_bytes(c_width.to_le_bytes()); - let height = u32::from_le_bytes(c_height.to_le_bytes()); - - FRAMEBUFFER_SIZE_CB - .try_with(|framebuffer_size_cb| { - if let Some(cb) = framebuffer_size_cb.borrow().as_deref() { - cb(Size { width, height }); - } - }) - .ok(); -} - -extern "C-unwind" fn key_callback( - _window: *mut crate::ffi::GLFWwindow, - key_raw: c_int, - scancode: c_int, - action_raw: c_int, - mods: c_int, -) -{ - let Some(key) = Key::from_repr(key_raw) else { - write!(stdout(), "Unknown key {key_raw}").ok(); - return; - }; - - let Some(key_state) = KeyState::from_repr(action_raw) else { - write!(stdout(), "Unknown key state {action_raw}").ok(); - return; - }; - - let Some(key_modifiers) = KeyModifiers::from_bits(mods) else { - write!( - stdout(), - "Key modifiers {action_raw} contain one or more unknown bit(s)" - ) - .ok(); - - return; - }; - - KEY_CALLBACK - .try_with(|key_callback| { - if let Some(cb) = key_callback.borrow().as_deref() { - cb(key, scancode, key_state, key_modifiers); - } - }) - .ok(); -} - -extern "C-unwind" fn cursor_pos_callback( - _window: *mut crate::ffi::GLFWwindow, - x_pos: c_double, - y_pos: c_double, -) -{ - CURSOR_POS_CALLBACK - .try_with(|cursor_pos_callback| { - if let Some(cb) = cursor_pos_callback.borrow().as_deref() { - cb(CursorPosition { x: x_pos, y: y_pos }); - } - }) - .ok(); -} - -extern "C-unwind" fn mouse_button_callback( - _window: *mut crate::ffi::GLFWwindow, - mouse_button_raw: c_int, - mouse_action: c_int, - modifier_keys: c_int, -) -{ - let Some(mouse_button) = MouseButton::from_repr(mouse_button_raw) else { - write!(stdout(), "Unknown mouse button {mouse_button_raw}").ok(); - return; - }; - - let Some(mouse_button_state) = MouseButtonState::from_repr(mouse_action) else { - write!(stdout(), "Unknown mouse action {mouse_action}").ok(); - return; - }; - - let Some(key_modifiers) = KeyModifiers::from_bits(modifier_keys) else { - write!( - stdout(), - "Key modifiers {modifier_keys:#b} contain one or more unknown bit(s)" - ) - .ok(); - - return; - }; - - MOUSE_BUTTON_CALLBACK - .try_with(|mouse_button_callback| { - if let Some(cb) = mouse_button_callback.borrow().as_deref() { - cb(mouse_button, mouse_button_state, key_modifiers); - } - }) - .ok(); -} - -extern "C-unwind" fn focus_callback(_window: *mut crate::ffi::GLFWwindow, focused: c_int) -{ - let is_focused = if focused == crate::ffi::GLFW_TRUE { - true - } else if focused == crate::ffi::GLFW_FALSE { - false - } else { - write!(stdout(), "Invalid focus state {focused}").ok(); - return; - }; - - FOCUS_CALLBACK - .try_with(|focus_callback| { - if let Some(cb) = focus_callback.borrow().as_deref() { - cb(is_focused); - } - }) - .ok(); -} diff --git a/opengl-bindings/Cargo.toml b/opengl-bindings/Cargo.toml new file mode 100644 index 0000000..8251642 --- /dev/null +++ b/opengl-bindings/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "opengl-bindings" +version = "0.1.0" +edition = "2021" + +[dependencies] +glutin = "0.32.3" +thiserror = "1.0.49" +safer-ffi = "0.1.13" +bitflags = "2.4.0" +util-macros = { path = "../util-macros" } + +[build-dependencies] +gl_generator = "=0.14.0" +toml = "0.8.12" +anyhow = "1.0.100" + +[package.metadata.build] +gl_commands = [ + "CreateBuffers", + "NamedBufferData", + "NamedBufferSubData", + "CreateVertexArrays", + "DrawArrays", + "DrawElements", + "VertexArrayElementBuffer", + "VertexArrayVertexBuffer", + "EnableVertexArrayAttrib", + "VertexArrayAttribFormat", + "VertexArrayAttribBinding", + "BindVertexArray", + "TextureStorage2D", + "TextureSubImage2D", + "DeleteTextures", + "GenerateTextureMipmap", + "TextureParameteri", + "CreateTextures", + "BindTextureUnit", + "DeleteShader", + "CreateShader", + "ShaderSource", + "CompileShader", + "GetShaderiv", + "GetShaderInfoLog", + "LinkProgram", + "GetProgramiv", + "CreateProgram", + "AttachShader", + "UseProgram", + "GetUniformLocation", + "ProgramUniform1f", + "ProgramUniform1i", + "ProgramUniform3f", + "ProgramUniformMatrix4fv", + "GetProgramInfoLog", + "DeleteProgram", + "Viewport", + "Clear", + "PolygonMode", + "Enable", + "Disable", + "GetIntegerv", + "DebugMessageCallback", + "DebugMessageControl" +] diff --git a/opengl-bindings/build.rs b/opengl-bindings/build.rs new file mode 100644 index 0000000..060472c --- /dev/null +++ b/opengl-bindings/build.rs @@ -0,0 +1,107 @@ +use std::collections::HashSet; +use std::env; +use std::fs::File; +use std::path::{Path, PathBuf}; + +use anyhow::anyhow; +use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator}; + +fn main() -> Result<(), anyhow::Error> +{ + println!("cargo::rerun-if-changed=build.rs"); + println!("cargo::rerun-if-changed=Cargo.toml"); + + let dest = env::var("OUT_DIR")?; + + let mut file = File::create(Path::new(&dest).join("bindings.rs"))?; + + let mut registry = Registry::new(Api::Gl, (4, 6), Profile::Core, Fallbacks::All, []); + + let mut build_metadata = get_build_metadata()?; + + filter_gl_commands(&mut registry, &mut build_metadata)?; + + registry.write_bindings(StructGenerator, &mut file)?; + + Ok(()) +} + +fn filter_gl_commands( + registry: &mut Registry, + build_metadata: &mut BuildMetadata, +) -> Result<(), anyhow::Error> +{ + registry + .cmds + .retain(|command| build_metadata.gl_commands.remove(&command.proto.ident)); + + if !build_metadata.gl_commands.is_empty() { + return Err(anyhow!( + "Invalid GL commands: [{}]", + build_metadata + .gl_commands + .iter() + .cloned() + .collect::<Vec<_>>() + .join(", ") + )); + } + + Ok(()) +} + +fn get_build_metadata() -> Result<BuildMetadata, anyhow::Error> +{ + let manifest_path = PathBuf::from(std::env::var("CARGO_MANIFEST_PATH")?); + + let manifest = std::fs::read_to_string(manifest_path)?.parse::<toml::Table>()?; + + let package = match manifest + .get("package") + .ok_or_else(|| anyhow!("Manifest does not have a package table"))? + { + toml::Value::Table(package) => Ok(package), + _ => Err(anyhow!("Manifest package must be a table")), + }?; + + let metadata = match package + .get("metadata") + .ok_or_else(|| anyhow!("Manifest does not have a package.metadata table"))? + { + toml::Value::Table(metadata) => Ok(metadata), + _ => Err(anyhow!("Manifest package.metadata must be a table")), + }?; + + let build_metadata = match metadata + .get("build") + .ok_or_else(|| anyhow!("Manifest does not have a package.metadata.build table"))? + { + toml::Value::Table(build_metadata) => Ok(build_metadata), + _ => Err(anyhow!("Manifest package.metadata.build must be a table")), + }?; + + let gl_command_values = match build_metadata.get("gl_commands").ok_or_else(|| { + anyhow!("Manifest does not have a package.metadata.build.gl_commands array") + })? { + toml::Value::Array(gl_commands) => Ok(gl_commands), + _ => Err(anyhow!( + "Manifest package.metadata.build.gl_commands must be a array" + )), + }?; + + let gl_commands = gl_command_values + .iter() + .map(|gl_command_val| match gl_command_val { + toml::Value::String(gl_command) => Ok(gl_command.clone()), + _ => Err(anyhow!("GL command must be a string")), + }) + .collect::<Result<HashSet<_>, _>>()?; + + Ok(BuildMetadata { gl_commands }) +} + +#[derive(Debug)] +struct BuildMetadata +{ + gl_commands: HashSet<String>, +} diff --git a/opengl-bindings/src/buffer.rs b/opengl-bindings/src/buffer.rs new file mode 100644 index 0000000..c64ec8d --- /dev/null +++ b/opengl-bindings/src/buffer.rs @@ -0,0 +1,167 @@ +use std::marker::PhantomData; +use std::mem::size_of_val; +use std::ptr::null; + +use safer_ffi::layout::ReprC; + +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Buffer<Item: ReprC> +{ + buf: crate::sys::types::GLuint, + _pd: PhantomData<Item>, +} + +impl<Item: ReprC> Buffer<Item> +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut buffer = crate::sys::types::GLuint::default(); + + unsafe { + current_context.fns().CreateBuffers(1, &raw mut buffer); + }; + + Self { buf: buffer, _pd: PhantomData } + } + + /// Stores items in this buffer. + /// + /// # Errors + /// Returns `Err` if the total size (in bytes) is too large. + pub fn store( + &self, + current_context: &CurrentContextWithFns<'_>, + items: &[Item], + usage: Usage, + ) -> Result<(), Error> + { + let total_size = size_of_val(items); + + let total_size: crate::sys::types::GLsizeiptr = + total_size + .try_into() + .map_err(|_| Error::TotalItemsSizeTooLarge { + total_size, + max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, + })?; + + unsafe { + current_context.fns().NamedBufferData( + self.buf, + total_size, + items.as_ptr().cast(), + usage.into_gl(), + ); + } + + Ok(()) + } + + /// Maps the values in the `values` slice into `Item`s which is stored into this + /// buffer. + /// + /// # Errors + /// Returns `Err` if the total size (in bytes) is too large. + pub fn store_mapped<Value>( + &self, + current_context: &CurrentContextWithFns<'_>, + values: &[Value], + usage: Usage, + mut map_func: impl FnMut(&Value) -> Item, + ) -> Result<(), Error> + { + let item_size: crate::sys::types::GLsizeiptr = const { + assert!(size_of::<Item>() <= crate::sys::types::GLsizeiptr::MAX as usize); + + size_of::<Item>().cast_signed() + }; + + let total_size = size_of::<Item>() * values.len(); + + let total_size: crate::sys::types::GLsizeiptr = + total_size + .try_into() + .map_err(|_| Error::TotalItemsSizeTooLarge { + total_size, + max_total_size: crate::sys::types::GLsizeiptr::MAX as usize, + })?; + + unsafe { + current_context.fns().NamedBufferData( + self.buf, + total_size, + null(), + usage.into_gl(), + ); + } + + for (index, value) in values.iter().enumerate() { + let item = map_func(value); + + let offset = index * size_of::<Item>(); + + let Ok(offset_casted) = crate::sys::types::GLintptr::try_from(offset) else { + unreachable!(); // Reason: The total size can be casted to a GLintptr + // (done above) so offsets should be castable as well + }; + + unsafe { + current_context.fns().NamedBufferSubData( + self.buf, + offset_casted, + item_size, + (&raw const item).cast(), + ); + } + } + + Ok(()) + } + + pub(crate) fn object(&self) -> crate::sys::types::GLuint + { + self.buf + } +} + +/// Buffer usage. +#[derive(Debug)] +pub enum Usage +{ + /// The buffer data is set only once and used by the GPU at most a few times. + Stream, + + /// The buffer data is set only once and used many times. + Static, + + /// The buffer data is changed a lot and used many times. + Dynamic, +} + +impl Usage +{ + fn into_gl(self) -> crate::sys::types::GLenum + { + match self { + Self::Stream => crate::sys::STREAM_DRAW, + Self::Static => crate::sys::STATIC_DRAW, + Self::Dynamic => crate::sys::DYNAMIC_DRAW, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error( + "Total size of items ({total_size}) is too large. Must be < {max_total_size}" + )] + TotalItemsSizeTooLarge + { + total_size: usize, + max_total_size: usize, + }, +} diff --git a/opengl-bindings/src/data_types.rs b/opengl-bindings/src/data_types.rs new file mode 100644 index 0000000..7ead0ab --- /dev/null +++ b/opengl-bindings/src/data_types.rs @@ -0,0 +1,37 @@ +use safer_ffi::derive_ReprC; +use safer_ffi::layout::ReprC; + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Matrix<Value: ReprC, const ROWS: usize, const COLUMNS: usize> +{ + /// Items must be layed out this way for it to work with OpenGL shaders. + pub items: [[Value; ROWS]; COLUMNS], +} + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Vec3<Value: ReprC> +{ + pub x: Value, + pub y: Value, + pub z: Value, +} + +#[derive(Debug, Clone)] +#[derive_ReprC] +#[repr(C)] +pub struct Vec2<Value> +{ + pub x: Value, + pub y: Value, +} + +#[derive(Debug, Clone)] +pub struct Dimens<Value> +{ + pub width: Value, + pub height: Value, +} diff --git a/opengl-bindings/src/debug.rs b/opengl-bindings/src/debug.rs new file mode 100644 index 0000000..a9369a4 --- /dev/null +++ b/opengl-bindings/src/debug.rs @@ -0,0 +1,161 @@ +use std::ffi::c_void; +use std::io::{stderr, Write}; +use std::mem::transmute; +use std::panic::catch_unwind; + +use util_macros::FromRepr; + +use crate::CurrentContextWithFns; + +pub fn set_debug_message_callback( + current_context: &CurrentContextWithFns<'_>, + cb: MessageCallback, +) +{ + unsafe { + current_context + .fns() + .DebugMessageCallback(Some(debug_message_cb), cb as *mut c_void); + } +} + +/// Sets debug message parameters. +/// +/// # Errors +/// Returns `Err` if `ids` contains too many ids. +pub fn set_debug_message_control( + current_context: &CurrentContextWithFns<'_>, + source: Option<MessageSource>, + ty: Option<MessageType>, + severity: Option<MessageSeverity>, + ids: &[u32], + ids_action: MessageIdsAction, +) -> Result<(), SetDebugMessageControlError> +{ + let ids_len: crate::sys::types::GLsizei = + ids.len() + .try_into() + .map_err(|_| SetDebugMessageControlError::TooManyIds { + id_cnt: ids.len(), + max_id_cnt: crate::sys::types::GLsizei::MAX as usize, + })?; + + unsafe { + current_context.fns().DebugMessageControl( + source.map_or(crate::sys::DONT_CARE, |source| source as u32), + ty.map_or(crate::sys::DONT_CARE, |ty| ty as u32), + severity.map_or(crate::sys::DONT_CARE, |severity| severity as u32), + ids_len, + ids.as_ptr(), + ids_action as u8, + ); + } + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum SetDebugMessageControlError +{ + #[error("Too many ids provided ({id_cnt}). Must be < {max_id_cnt}")] + TooManyIds + { + id_cnt: usize, max_id_cnt: usize + }, +} + +pub type MessageCallback = fn( + source: MessageSource, + ty: MessageType, + id: u32, + severity: MessageSeverity, + message: &str, +); + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] // GLboolean = u8 +pub enum MessageIdsAction +{ + Enable = crate::sys::TRUE, + Disable = crate::sys::FALSE, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageSource +{ + Api = crate::sys::DEBUG_SOURCE_API, + WindowSystem = crate::sys::DEBUG_SOURCE_WINDOW_SYSTEM, + ShaderCompiler = crate::sys::DEBUG_SOURCE_SHADER_COMPILER, + ThirdParty = crate::sys::DEBUG_SOURCE_THIRD_PARTY, + Application = crate::sys::DEBUG_SOURCE_APPLICATION, + Other = crate::sys::DEBUG_SOURCE_OTHER, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageType +{ + DeprecatedBehavior = crate::sys::DEBUG_TYPE_DEPRECATED_BEHAVIOR, + Error = crate::sys::DEBUG_TYPE_ERROR, + Marker = crate::sys::DEBUG_TYPE_MARKER, + Other = crate::sys::DEBUG_TYPE_OTHER, + Performance = crate::sys::DEBUG_TYPE_PERFORMANCE, + PopGroup = crate::sys::DEBUG_TYPE_POP_GROUP, + PushGroup = crate::sys::DEBUG_TYPE_PUSH_GROUP, + Portability = crate::sys::DEBUG_TYPE_PORTABILITY, + UndefinedBehavior = crate::sys::DEBUG_TYPE_UNDEFINED_BEHAVIOR, +} + +#[derive(Debug, Clone, Copy, FromRepr)] +#[repr(u32)] // GLenum = u32 +pub enum MessageSeverity +{ + High = crate::sys::DEBUG_SEVERITY_HIGH, + Medium = crate::sys::DEBUG_SEVERITY_MEDIUM, + Low = crate::sys::DEBUG_SEVERITY_LOW, + Notification = crate::sys::DEBUG_SEVERITY_NOTIFICATION, +} + +extern "system" fn debug_message_cb( + source: crate::sys::types::GLenum, + ty: crate::sys::types::GLenum, + id: crate::sys::types::GLuint, + severity: crate::sys::types::GLenum, + message_length: crate::sys::types::GLsizei, + message: *const crate::sys::types::GLchar, + user_cb: *mut c_void, +) +{ + let user_cb = unsafe { transmute::<*mut c_void, MessageCallback>(user_cb) }; + + let Ok(msg_length) = usize::try_from(message_length) else { + return; + }; + + // Unwinds are catched because unwinding from Rust code into foreign code is UB. + let res = catch_unwind(|| { + let msg_source = MessageSource::from_repr(source).unwrap(); + let msg_type = MessageType::from_repr(ty).unwrap(); + let msg_severity = MessageSeverity::from_repr(severity).unwrap(); + + // SAFETY: The received message should be a valid ASCII string + let message = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + message.cast(), + msg_length, + )) + }; + + user_cb(msg_source, msg_type, id, msg_severity, message); + }); + + if res.is_err() { + // eprintln is not used since it can panic and unwinds are unwanted because + // unwinding from Rust code into foreign code is UB. + stderr() + .write_all(b"ERROR: Panic in debug message callback") + .ok(); + println!(); + } +} diff --git a/opengl-bindings/src/lib.rs b/opengl-bindings/src/lib.rs new file mode 100644 index 0000000..7fd9933 --- /dev/null +++ b/opengl-bindings/src/lib.rs @@ -0,0 +1,119 @@ +#![deny(clippy::all, clippy::pedantic)] +use std::ffi::CString; +use std::process::abort; + +use glutin::context::{NotCurrentContext, PossiblyCurrentContext}; +use glutin::display::GetGlDisplay; +use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext}; +use glutin::surface::{Surface, SurfaceTypeTrait}; + +pub mod buffer; +pub mod data_types; +pub mod debug; +pub mod misc; +pub mod shader; +pub mod texture; +pub mod vertex_array; + +pub struct ContextWithFns +{ + context: PossiblyCurrentContext, + fns: Box<sys::Gl>, +} + +impl ContextWithFns +{ + /// Returns a new `ContextWithFns`. + /// + /// # Errors + /// Returns `Err` if making this context current fails. + pub fn new<SurfaceType: SurfaceTypeTrait>( + context: NotCurrentContext, + surface: &Surface<SurfaceType>, + ) -> Result<Self, Error> + { + let context = context + .make_current(surface) + .map_err(Error::MakeContextCurrentFailed)?; + + let display = context.display(); + + let gl = sys::Gl::load_with(|symbol| { + let Ok(symbol) = CString::new(symbol) else { + eprintln!("GL symbol contains nul byte"); + abort(); + }; + + display.get_proc_address(&symbol) + }); + + Ok(Self { context, fns: Box::new(gl) }) + } + + /// Attempts to make this context current. + /// + /// # Errors + /// Returns `Err` if making this context current fails. + pub fn make_current<SurfaceType: SurfaceTypeTrait>( + &self, + surface: &Surface<SurfaceType>, + ) -> Result<CurrentContextWithFns<'_>, Error> + { + if !self.context.is_current() { + self.context + .make_current(surface) + .map_err(Error::MakeContextCurrentFailed)?; + } + + Ok(CurrentContextWithFns { ctx: self }) + } + + #[must_use] + pub fn context(&self) -> &PossiblyCurrentContext + { + &self.context + } + + #[must_use] + pub fn context_mut(&mut self) -> &mut PossiblyCurrentContext + { + &mut self.context + } +} + +pub struct CurrentContextWithFns<'ctx> +{ + ctx: &'ctx ContextWithFns, +} + +impl CurrentContextWithFns<'_> +{ + #[inline] + pub(crate) fn fns(&self) -> &sys::Gl + { + &self.ctx.fns + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to make context current")] + MakeContextCurrentFailed(#[source] glutin::error::Error), +} + +mod sys +{ + #![allow( + clippy::missing_safety_doc, + clippy::missing_transmute_annotations, + clippy::too_many_arguments, + clippy::unused_unit, + clippy::upper_case_acronyms, + clippy::doc_markdown, + clippy::unreadable_literal, + unsafe_op_in_unsafe_fn + )] + + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} diff --git a/opengl-bindings/src/misc.rs b/opengl-bindings/src/misc.rs new file mode 100644 index 0000000..bb54c1a --- /dev/null +++ b/opengl-bindings/src/misc.rs @@ -0,0 +1,190 @@ +use bitflags::bitflags; + +use crate::data_types::{Dimens, Vec2}; +use crate::CurrentContextWithFns; + +/// Sets the viewport. +/// +/// The `u32` values in `position` and `size` must fit in `i32`s. +/// +/// # Errors +/// Returns `Err` if any value in `position` or `size` does not fit into a `i32`. +pub fn set_viewport( + current_context: &CurrentContextWithFns<'_>, + position: &Vec2<u32>, + size: &Dimens<u32>, +) -> Result<(), SetViewportError> +{ + let position = Vec2::<crate::sys::types::GLint> { + x: position.x.try_into().map_err(|_| { + SetViewportError::PositionXValueTooLarge { + value: position.x, + max_value: crate::sys::types::GLint::MAX as u32, + } + })?, + y: position.y.try_into().map_err(|_| { + SetViewportError::PositionYValueTooLarge { + value: position.y, + max_value: crate::sys::types::GLint::MAX as u32, + } + })?, + }; + + let size = Dimens::<crate::sys::types::GLsizei> { + width: size.width.try_into().map_err(|_| { + SetViewportError::SizeWidthValueTooLarge { + value: size.width, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + height: size.height.try_into().map_err(|_| { + SetViewportError::SizeHeightValueTooLarge { + value: size.height, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + }; + + unsafe { + current_context + .fns() + .Viewport(position.x, position.y, size.width, size.height); + } + + Ok(()) +} + +pub fn clear_buffers(current_context: &CurrentContextWithFns<'_>, mask: BufferClearMask) +{ + unsafe { + current_context.fns().Clear(mask.bits()); + } +} + +pub fn set_polygon_mode( + current_context: &CurrentContextWithFns<'_>, + face: impl Into<PolygonModeFace>, + mode: impl Into<PolygonMode>, +) +{ + unsafe { + current_context + .fns() + .PolygonMode(face.into() as u32, mode.into() as u32); + } +} + +pub fn enable(current_context: &CurrentContextWithFns<'_>, capacity: Capability) +{ + unsafe { + current_context.fns().Enable(capacity as u32); + } +} + +pub fn disable(current_context: &CurrentContextWithFns<'_>, capability: Capability) +{ + unsafe { + current_context.fns().Disable(capability as u32); + } +} + +pub fn set_enabled( + current_context: &CurrentContextWithFns<'_>, + capability: Capability, + enabled: bool, +) +{ + if enabled { + enable(current_context, capability); + } else { + disable(current_context, capability); + } +} + +#[must_use] +pub fn get_context_flags(current_context: &CurrentContextWithFns<'_>) -> ContextFlags +{ + let mut context_flags = crate::sys::types::GLint::default(); + + unsafe { + current_context + .fns() + .GetIntegerv(crate::sys::CONTEXT_FLAGS, &raw mut context_flags); + } + + ContextFlags::from_bits_truncate(context_flags.cast_unsigned()) +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct BufferClearMask: u32 { + const COLOR = crate::sys::COLOR_BUFFER_BIT; + const DEPTH = crate::sys::DEPTH_BUFFER_BIT; + const STENCIL = crate::sys::STENCIL_BUFFER_BIT; + } +} + +#[derive(Debug)] +#[repr(u32)] +pub enum Capability +{ + DepthTest = crate::sys::DEPTH_TEST, + MultiSample = crate::sys::MULTISAMPLE, + DebugOutput = crate::sys::DEBUG_OUTPUT, + DebugOutputSynchronous = crate::sys::DEBUG_OUTPUT_SYNCHRONOUS, +} + +#[derive(Debug)] +#[repr(u32)] +pub enum PolygonMode +{ + Point = crate::sys::POINT, + Line = crate::sys::LINE, + Fill = crate::sys::FILL, +} + +#[derive(Debug)] +#[repr(u32)] +pub enum PolygonModeFace +{ + Front = crate::sys::FRONT, + Back = crate::sys::BACK, + FrontAndBack = crate::sys::FRONT_AND_BACK, +} + +bitflags! { +#[derive(Debug, Clone, Copy)] +pub struct ContextFlags: u32 { + const FORWARD_COMPATIBLE = crate::sys::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; + const DEBUG = crate::sys::CONTEXT_FLAG_DEBUG_BIT; + const ROBUST_ACCESS = crate::sys::CONTEXT_FLAG_ROBUST_ACCESS_BIT; +} +} + +#[derive(Debug, thiserror::Error)] +pub enum SetViewportError +{ + #[error("Position X value ({value}) is too large. Must be < {max_value}")] + PositionXValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Position Y value ({value}) is too large. Must be < {max_value}")] + PositionYValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Size width value ({value}) is too large. Must be < {max_value}")] + SizeWidthValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Size height value ({value}) is too large. Must be < {max_value}")] + SizeHeightValueTooLarge + { + value: u32, max_value: u32 + }, +} diff --git a/opengl-bindings/src/shader.rs b/opengl-bindings/src/shader.rs new file mode 100644 index 0000000..5ed66a2 --- /dev/null +++ b/opengl-bindings/src/shader.rs @@ -0,0 +1,366 @@ +use std::ffi::CStr; +use std::ptr::null_mut; + +use safer_ffi::layout::ReprC; + +use crate::data_types::{Matrix, Vec3}; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Shader +{ + shader: crate::sys::types::GLuint, +} + +impl Shader +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>, kind: Kind) -> Self + { + let shader = unsafe { + current_context + .fns() + .CreateShader(kind as crate::sys::types::GLenum) + }; + + Self { shader } + } + + /// Sets the source code of this shader. + /// + /// # Errors + /// Returns `Err` if `source` is not ASCII. + pub fn set_source( + &self, + current_context: &CurrentContextWithFns<'_>, + source: &str, + ) -> Result<(), Error> + { + if !source.is_ascii() { + return Err(Error::SourceNotAscii); + } + + let length: crate::sys::types::GLint = + source.len().try_into().map_err(|_| Error::SourceTooLarge { + length: source.len(), + max_length: crate::sys::types::GLint::MAX as usize, + })?; + + unsafe { + current_context.fns().ShaderSource( + self.shader, + 1, + &source.as_ptr().cast(), + &raw const length, + ); + } + + Ok(()) + } + + /// Compiles this shader. + /// + /// # Errors + /// Returns `Err` if compiling fails. + pub fn compile( + &self, + current_context: &CurrentContextWithFns<'_>, + ) -> Result<(), Error> + { + unsafe { + current_context.fns().CompileShader(self.shader); + } + + let mut compile_success = crate::sys::types::GLint::default(); + + unsafe { + current_context.fns().GetShaderiv( + self.shader, + crate::sys::COMPILE_STATUS, + &raw mut compile_success, + ); + } + + if compile_success == 0 { + let info_log = self.get_info_log(current_context); + + return Err(Error::CompileFailed { log: info_log }); + } + + Ok(()) + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().DeleteShader(self.shader); + } + } + + fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String + { + const BUF_SIZE: crate::sys::types::GLsizei = 512; + + let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; + + unsafe { + current_context.fns().GetShaderInfoLog( + self.shader, + BUF_SIZE, + null_mut(), + buf.as_mut_ptr(), + ); + } + + let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; + + unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } + } +} + +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum Kind +{ + Vertex = crate::sys::VERTEX_SHADER, + Fragment = crate::sys::FRAGMENT_SHADER, +} + +/// Shader program +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Program +{ + program: crate::sys::types::GLuint, +} + +impl Program +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let program = unsafe { current_context.fns().CreateProgram() }; + + Self { program } + } + + pub fn attach(&self, current_context: &CurrentContextWithFns<'_>, shader: &Shader) + { + unsafe { + current_context + .fns() + .AttachShader(self.program, shader.shader); + } + } + + /// Links this program. + /// + /// # Errors + /// Returns `Err` if linking fails. + pub fn link(&self, current_context: &CurrentContextWithFns<'_>) -> Result<(), Error> + { + unsafe { + current_context.fns().LinkProgram(self.program); + } + + let mut link_success = crate::sys::types::GLint::default(); + + unsafe { + current_context.fns().GetProgramiv( + self.program, + crate::sys::LINK_STATUS, + &raw mut link_success, + ); + } + + if link_success == 0 { + let info_log = self.get_info_log(current_context); + + return Err(Error::LinkFailed { log: info_log }); + } + + Ok(()) + } + + pub fn activate(&self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().UseProgram(self.program); + } + } + + pub fn set_uniform( + &self, + current_context: &CurrentContextWithFns<'_>, + name: &CStr, + var: &impl UniformVariable, + ) + { + let location = UniformLocation(unsafe { + current_context + .fns() + .GetUniformLocation(self.program, name.as_ptr().cast()) + }); + + var.set(current_context, self, location); + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context.fns().DeleteProgram(self.program); + } + } + + fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String + { + const BUF_SIZE: crate::sys::types::GLsizei = 512; + + let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; + + unsafe { + current_context.fns().GetProgramInfoLog( + self.program, + BUF_SIZE, + null_mut(), + buf.as_mut_ptr(), + ); + } + + let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; + + unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } + } +} + +pub trait UniformVariable: ReprC + sealed::Sealed +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ); +} + +impl UniformVariable for f32 +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform1f( + program.program, + uniform_location.0, + *self, + ); + } + } +} + +impl sealed::Sealed for f32 {} + +impl UniformVariable for i32 +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform1i( + program.program, + uniform_location.0, + *self, + ); + } + } +} + +impl sealed::Sealed for i32 {} + +impl UniformVariable for Vec3<f32> +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniform3f( + program.program, + uniform_location.0, + self.x, + self.y, + self.z, + ); + } + } +} + +impl sealed::Sealed for Vec3<f32> {} + +impl UniformVariable for Matrix<f32, 4, 4> +{ + fn set( + &self, + current_context: &CurrentContextWithFns<'_>, + program: &Program, + uniform_location: UniformLocation, + ) + { + unsafe { + current_context.fns().ProgramUniformMatrix4fv( + program.program, + uniform_location.0, + 1, + crate::sys::FALSE, + self.items.as_ptr().cast::<f32>(), + ); + } + } +} + +impl sealed::Sealed for Matrix<f32, 4, 4> {} + +#[derive(Debug)] +pub struct UniformLocation(crate::sys::types::GLint); + +/// Shader error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("All characters in source are not within the ASCII range")] + SourceNotAscii, + + #[error("Source is too large. Length ({length}) must be < {max_length}")] + SourceTooLarge + { + length: usize, max_length: usize + }, + + #[error("Failed to compile shader")] + CompileFailed + { + log: String + }, + + #[error("Failed to link shader program")] + LinkFailed + { + log: String + }, +} + +mod sealed +{ + pub trait Sealed {} +} diff --git a/opengl-bindings/src/texture.rs b/opengl-bindings/src/texture.rs new file mode 100644 index 0000000..1859beb --- /dev/null +++ b/opengl-bindings/src/texture.rs @@ -0,0 +1,236 @@ +use crate::data_types::Dimens; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct Texture +{ + texture: crate::sys::types::GLuint, +} + +impl Texture +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut texture = crate::sys::types::GLuint::default(); + + unsafe { + current_context.fns().CreateTextures( + crate::sys::TEXTURE_2D, + 1, + &raw mut texture, + ); + }; + + Self { texture } + } + + pub fn bind_to_texture_unit( + &self, + current_context: &CurrentContextWithFns<'_>, + texture_unit: u32, + ) + { + unsafe { + current_context + .fns() + .BindTextureUnit(texture_unit, self.texture); + } + } + + /// Allocates the texture storage, stores pixel data & generates a mipmap. + /// + /// # Errors + /// Returns `Err` if any value in `size` does not fit into a `i32`. + pub fn generate( + &self, + current_context: &CurrentContextWithFns<'_>, + size: &Dimens<u32>, + data: &[u8], + pixel_data_format: PixelDataFormat, + ) -> Result<(), GenerateError> + { + let size = Dimens::<crate::sys::types::GLsizei> { + width: size.width.try_into().map_err(|_| { + GenerateError::SizeWidthValueTooLarge { + value: size.width, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + height: size.height.try_into().map_err(|_| { + GenerateError::SizeHeightValueTooLarge { + value: size.height, + max_value: crate::sys::types::GLsizei::MAX as u32, + } + })?, + }; + + self.alloc_image(current_context, pixel_data_format, &size, data); + + unsafe { + current_context.fns().GenerateTextureMipmap(self.texture); + } + + Ok(()) + } + + pub fn set_wrap( + &self, + current_context: &CurrentContextWithFns<'_>, + wrapping: Wrapping, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_WRAP_S, + wrapping as i32, + ); + + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_WRAP_T, + wrapping as i32, + ); + } + } + + pub fn set_magnifying_filter( + &self, + current_context: &CurrentContextWithFns<'_>, + filtering: Filtering, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_MAG_FILTER, + filtering as i32, + ); + } + } + + pub fn set_minifying_filter( + &self, + current_context: &CurrentContextWithFns<'_>, + filtering: Filtering, + ) + { + unsafe { + current_context.fns().TextureParameteri( + self.texture, + crate::sys::TEXTURE_MIN_FILTER, + filtering as i32, + ); + } + } + + pub fn delete(self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { + current_context + .fns() + .DeleteTextures(1, &raw const self.texture); + } + } + + fn alloc_image( + &self, + current_context: &CurrentContextWithFns<'_>, + pixel_data_format: PixelDataFormat, + size: &Dimens<crate::sys::types::GLsizei>, + data: &[u8], + ) + { + unsafe { + current_context.fns().TextureStorage2D( + self.texture, + 1, + pixel_data_format.to_sized_internal_format(), + size.width, + size.height, + ); + + current_context.fns().TextureSubImage2D( + self.texture, + 0, + 0, + 0, + size.width, + size.height, + pixel_data_format.to_format(), + crate::sys::UNSIGNED_BYTE, + data.as_ptr().cast(), + ); + } + } +} + +const fn try_cast_u32_to_i32(val: u32) -> i32 +{ + assert!(val <= i32::MAX as u32); + + val.cast_signed() +} + +/// Texture wrapping. +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +pub enum Wrapping +{ + Repeat = const { try_cast_u32_to_i32(crate::sys::REPEAT) }, + MirroredRepeat = const { try_cast_u32_to_i32(crate::sys::MIRRORED_REPEAT) }, + ClampToEdge = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_EDGE) }, + ClampToBorder = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_BORDER) }, +} + +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +pub enum Filtering +{ + Nearest = const { try_cast_u32_to_i32(crate::sys::NEAREST) }, + Linear = const { try_cast_u32_to_i32(crate::sys::LINEAR) }, +} + +/// Texture pixel data format. +#[derive(Debug, Clone, Copy)] +pub enum PixelDataFormat +{ + Rgb8, + Rgba8, +} + +impl PixelDataFormat +{ + fn to_sized_internal_format(self) -> crate::sys::types::GLenum + { + match self { + Self::Rgb8 => crate::sys::RGB8, + Self::Rgba8 => crate::sys::RGBA8, + } + } + + fn to_format(self) -> crate::sys::types::GLenum + { + match self { + Self::Rgb8 => crate::sys::RGB, + Self::Rgba8 => crate::sys::RGBA, + } + } +} + +/// Error generating texture. +#[derive(Debug, thiserror::Error)] +pub enum GenerateError +{ + #[error("Size width value ({value}) is too large. Must be < {max_value}")] + SizeWidthValueTooLarge + { + value: u32, max_value: u32 + }, + #[error("Size height value ({value}) is too large. Must be < {max_value}")] + SizeHeightValueTooLarge + { + value: u32, max_value: u32 + }, +} diff --git a/opengl-bindings/src/vertex_array.rs b/opengl-bindings/src/vertex_array.rs new file mode 100644 index 0000000..9942fe7 --- /dev/null +++ b/opengl-bindings/src/vertex_array.rs @@ -0,0 +1,260 @@ +use std::ffi::{c_int, c_void}; +use std::mem::size_of; + +use safer_ffi::layout::ReprC; + +use crate::buffer::Buffer; +use crate::CurrentContextWithFns; + +#[derive(Debug)] +pub struct VertexArray +{ + array: crate::sys::types::GLuint, +} + +impl VertexArray +{ + #[must_use] + pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self + { + let mut array = 0; + + unsafe { + current_context.fns().CreateVertexArrays(1, &raw mut array); + } + + Self { array } + } + + /// Draws the currently bound vertex array. + /// + /// # Errors + /// Returns `Err` if: + /// - `start_index` is too large + /// - `cnt` is too large + pub fn draw_arrays( + current_context: &CurrentContextWithFns<'_>, + primitive_kind: PrimitiveKind, + start_index: u32, + cnt: u32, + ) -> Result<(), DrawError> + { + let start_index: crate::sys::types::GLint = + start_index + .try_into() + .map_err(|_| DrawError::StartIndexValueTooLarge { + value: start_index, + max_value: crate::sys::types::GLint::MAX as u32, + })?; + + let cnt: crate::sys::types::GLsizei = + cnt.try_into().map_err(|_| DrawError::CountValueTooLarge { + value: cnt, + max_value: crate::sys::types::GLsizei::MAX as u32, + })?; + + unsafe { + current_context + .fns() + .DrawArrays(primitive_kind.into_gl(), start_index, cnt); + } + + Ok(()) + } + + /// Draws the currently bound vertex array. + /// + /// # Errors + /// Returns `Err` if `cnt` is too large. + pub fn draw_elements( + current_context: &CurrentContextWithFns<'_>, + primitive_kind: PrimitiveKind, + start_index: u32, + cnt: u32, + ) -> Result<(), DrawError> + { + let cnt: crate::sys::types::GLsizei = + cnt.try_into().map_err(|_| DrawError::CountValueTooLarge { + value: cnt, + max_value: crate::sys::types::GLsizei::MAX as u32, + })?; + + unsafe { + current_context.fns().DrawElements( + primitive_kind.into_gl(), + cnt, + crate::sys::UNSIGNED_INT, + // TODO: Make this not sometimes UB. DrawElements expects a actual + // pointer to a memory location when no VBO is bound. + // See: https://stackoverflow.com/q/21706113 + std::ptr::without_provenance::<c_void>(start_index as usize), + ); + } + + Ok(()) + } + + pub fn bind_element_buffer( + &self, + current_context: &CurrentContextWithFns<'_>, + element_buffer: &Buffer<u32>, + ) + { + unsafe { + current_context + .fns() + .VertexArrayElementBuffer(self.array, element_buffer.object()); + } + } + + pub fn bind_vertex_buffer<VertexT: ReprC>( + &self, + current_context: &CurrentContextWithFns<'_>, + binding_index: u32, + vertex_buffer: &Buffer<VertexT>, + offset: isize, + ) + { + let vertex_size = const { cast_usize_to_c_int(size_of::<VertexT>()) }; + + unsafe { + current_context.fns().VertexArrayVertexBuffer( + self.array, + binding_index, + vertex_buffer.object(), + offset, + vertex_size, + ); + } + } + + pub fn enable_attrib( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + ) + { + unsafe { + current_context.fns().EnableVertexArrayAttrib( + self.array, + attrib_index as crate::sys::types::GLuint, + ); + } + } + + pub fn set_attrib_format( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + data_type: DataType, + normalized: bool, + offset: u32, + ) + { + unsafe { + current_context.fns().VertexArrayAttribFormat( + self.array, + attrib_index, + data_type.size(), + data_type as u32, + if normalized { + crate::sys::TRUE + } else { + crate::sys::FALSE + }, + offset, + ); + } + } + + /// Associate a vertex attribute and a vertex buffer binding. + pub fn set_attrib_vertex_buf_binding( + &self, + current_context: &CurrentContextWithFns<'_>, + attrib_index: u32, + vertex_buf_binding_index: u32, + ) + { + unsafe { + current_context.fns().VertexArrayAttribBinding( + self.array, + attrib_index, + vertex_buf_binding_index, + ); + } + } + + pub fn bind(&self, current_context: &CurrentContextWithFns<'_>) + { + unsafe { current_context.fns().BindVertexArray(self.array) } + } +} + +#[derive(Debug)] +pub enum PrimitiveKind +{ + Triangles, +} + +impl PrimitiveKind +{ + fn into_gl(self) -> crate::sys::types::GLenum + { + match self { + Self::Triangles => crate::sys::TRIANGLES, + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u32)] +pub enum DataType +{ + Float = crate::sys::FLOAT, +} + +impl DataType +{ + fn size(self) -> crate::sys::types::GLint + { + match self { + Self::Float => const { cast_usize_to_c_int(size_of::<f32>()) }, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum DrawError +{ + #[error("Start index value {value} is too large. Must be < {max_value}")] + StartIndexValueTooLarge + { + value: u32, max_value: u32 + }, + + #[error("Count value {value} is too large. Must be < {max_value}")] + CountValueTooLarge + { + value: u32, max_value: u32 + }, +} + +const fn cast_usize_to_c_int(num: usize) -> c_int +{ + assert!(num <= c_int::MAX.cast_unsigned() as usize); + + c_int::from_ne_bytes(shorten_byte_array(num.to_ne_bytes())) +} + +const fn shorten_byte_array<const SRC_LEN: usize, const DST_LEN: usize>( + src: [u8; SRC_LEN], +) -> [u8; DST_LEN] +{ + assert!(DST_LEN < SRC_LEN); + + let mut ret = [0; DST_LEN]; + + ret.copy_from_slice(src.split_at(DST_LEN).0); + + ret +} diff --git a/src/main.rs b/src/main.rs index 438b2bf..4ede773 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::error::Error; -use std::fs::read_to_string; use std::path::Path; +use engine::asset::Assets; use engine::camera::fly::{ Extension as FlyCameraExtension, Fly as FlyCamera, @@ -9,26 +9,28 @@ use engine::camera::fly::{ }; use engine::camera::{Active as ActiveCamera, Camera}; use engine::color::Color; -use engine::data_types::dimens::Dimens; +use engine::data_types::dimens::Dimens3; +use engine::ecs::actions::Actions; +use engine::ecs::event::component::Added; +use engine::ecs::pair::Pair; use engine::ecs::phase::START as START_PHASE; use engine::ecs::sole::Single; -use engine::file_format::wavefront::mtl::parse as parse_mtl; -use engine::file_format::wavefront::obj::parse as parse_obj; +use engine::ecs::system::observer::Observe; use engine::input::Extension as InputExtension; use engine::lighting::{AttenuationParams, GlobalLight, PointLight}; -use engine::material::{Builder as MaterialBuilder, Flags as MaterialFlags, Material}; +use engine::material::{Flags as MaterialFlags, Material}; use engine::mesh::cube::{ create as cube_mesh_create, CreationSpec as CubeMeshCreationSpec, }; +use engine::model::{Data as ModelData, Model}; use engine::renderer::opengl::Extension as OpenglRendererExtension; -use engine::texture::Texture; -use engine::transform::Position; +use engine::renderer::GraphicsProperties; +use engine::transform::WorldPosition; use engine::vector::Vec3; -use engine::window::{ - Builder as WindowBuilder, - CursorMode, - Extension as WindowExtension, +use engine::windowing::window::{ + CreationAttributes as WindowCreationAttributes, + CursorGrabMode as WindowCursorGrabMode, Window, }; use engine::Engine; @@ -37,8 +39,6 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; -const WINDOW_SIZE: Dimens<u32> = Dimens { width: 1920, height: 1080 }; - const YELLOW: Color<f32> = Color { red: 0.988235294118, green: 0.941176470588, @@ -60,81 +60,9 @@ fn main() -> Result<(), Box<dyn Error>> let mut engine = Engine::new(); - let teapot_obj = - parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("teapot.obj"))?)?; - - let teapot_mat_name = teapot_obj - .faces - .first() - .and_then(|face| face.material_name.as_ref()); - - let teapot_mats = teapot_obj.read_and_parse_material_libs(parse_mtl)?; - - let teapot_named_mat = teapot_mats - .into_iter() - .find(|mat| Some(&mat.name) == teapot_mat_name) - .ok_or("Teapot material was not found")?; - - let mut teapot_mat_builder = Material::builder() - .ambient(teapot_named_mat.ambient) - .diffuse(teapot_named_mat.diffuse) - .specular(teapot_named_mat.specular); - - if let Some(ambient_map) = teapot_named_mat.ambient_map { - let texture = Texture::open(&ambient_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .ambient_map(texture.id()) - .texture(texture); - } - - if let Some(diffuse_map) = teapot_named_mat.diffuse_map { - let texture = Texture::open(&diffuse_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .diffuse_map(texture.id()) - .texture(texture); - } - - if let Some(specular_map) = teapot_named_mat.specular_map { - let texture = Texture::open(&specular_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .specular_map(texture.id()) - .texture(texture); - } - - engine.spawn(( - teapot_obj.to_mesh()?, - teapot_mat_builder.build(), - Position::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }), - )); - - engine.spawn(( - PointLight::builder() - .diffuse(YELLOW) - .attenuation_params(AttenuationParams { - linear: 0.045, - quadratic: 0.0075, - ..Default::default() - }) - .build(), - Position::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }), - cube_mesh_create( - CubeMeshCreationSpec::builder() - .width(2.0) - .height(2.0) - .depth(2.0) - .build(), - |face_verts, _, _| face_verts, - ), - MaterialBuilder::new().ambient(YELLOW * 5.0).build(), - MaterialFlags::builder().use_ambient_color(true).build(), - )); - engine.spawn(( Camera::default(), - Position { + WorldPosition { position: Vec3 { x: 0.0, y: 0.0, z: 3.0 }, }, ActiveCamera, @@ -143,15 +71,13 @@ fn main() -> Result<(), Box<dyn Error>> engine.add_sole(GlobalLight::default())?; - engine.register_system(*START_PHASE, prepare_window); + engine.register_system(*START_PHASE, init); - engine.add_extension(OpenglRendererExtension::default()); + engine.register_observer(configure_window_on_added); - engine.add_extension( - WindowExtension::new(WindowBuilder::default().multisampling_sample_count(8)) - .window_title("Game") - .window_size(WINDOW_SIZE), - ); + engine.add_extension(engine::windowing::Extension::default()); + + engine.add_extension(OpenglRendererExtension::default()); engine.add_extension(FlyCameraExtension(FlyCameraOptions { mouse_sensitivity: 0.2, @@ -159,12 +85,62 @@ fn main() -> Result<(), Box<dyn Error>> engine.add_extension(InputExtension::default()); + engine.spawn(( + WindowCreationAttributes::default().with_title("Game"), + GraphicsProperties::builder().debug(true).build(), + )); + engine.start(); Ok(()) } -fn prepare_window(window: Single<Window>) +fn configure_window_on_added(observe: Observe<Pair<Added, Window>>) +{ + for evt_match in &observe { + let mut window = evt_match.get_added_comp_mut(); + + window.cursor_visible = false; + window.cursor_grab_mode = WindowCursorGrabMode::Locked; + + window.set_changed(); + } +} + +fn init(mut assets: Single<Assets>, mut actions: Actions) { - window.set_cursor_mode(CursorMode::Disabled).unwrap(); + actions.spawn(( + PointLight::builder() + .diffuse(YELLOW) + .attenuation_params(AttenuationParams { + linear: 0.045, + quadratic: 0.0075, + ..Default::default() + }) + .build(), + WorldPosition::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }), + Model::new( + assets.store_with_name( + "light_cube", + ModelData::builder() + .mesh(cube_mesh_create( + CubeMeshCreationSpec::builder() + .dimens(Dimens3::from(2.0)) + .build(), + |face_verts, _, _| face_verts, + )) + .material( + "surface", + Material::builder().ambient(YELLOW * 5.0).build(), + ) + .build(), + ), + ), + MaterialFlags::builder().use_ambient_color(true).build(), + )); + + actions.spawn(( + Model::new(assets.load::<ModelData>(Path::new(RESOURCE_DIR).join("teapot.obj"))), + WorldPosition::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }), + )); } |
