diff options
64 files changed, 4196 insertions, 2495 deletions
@@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler" @@ -18,6 +18,24 @@ dependencies = [ ] [[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -68,6 +86,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -83,6 +107,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] name = "clang-sys" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -94,6 +145,31 @@ dependencies = [ ] [[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -109,11 +185,54 @@ dependencies = [ ] [[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] name = "ecs" version = "0.1.0" dependencies = [ + "criterion", "ecs-macros", + "hashbrown 0.15.2", "linkme", + "parking_lot", "paste", "seq-macro", "thiserror", @@ -132,6 +251,12 @@ dependencies = [ ] [[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] name = "engine" version = "0.1.0" dependencies = [ @@ -173,6 +298,12 @@ dependencies = [ ] [[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] name = "game-newest" version = "0.1.0" dependencies = [ @@ -219,12 +350,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] name = "image" version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -246,10 +404,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -310,12 +494,31 @@ dependencies = [ ] [[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -394,12 +597,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -449,6 +681,15 @@ dependencies = [ ] [[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.4.0", +] + +[[package]] name = "regex" version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -456,8 +697,17 @@ checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.3.9", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -468,11 +718,17 @@ checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" @@ -484,6 +740,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] name = "seq-macro" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -510,6 +787,18 @@ dependencies = [ ] [[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] name = "serde_spanned" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -541,9 +830,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" @@ -587,6 +876,16 @@ dependencies = [ ] [[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] name = "toml" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -649,18 +948,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", ] [[package]] @@ -669,12 +956,15 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", - "tracing-log", ] [[package]] @@ -693,10 +983,14 @@ dependencies = [ ] [[package]] -name = "valuable" -version = "0.1.0" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "winapi" @@ -715,12 +1009,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +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_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] name = "winnow" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -8,5 +8,9 @@ members = ["glfw", "engine", "ecs", "ecs-macros", "util-macros"] [dependencies] engine = { path = "./engine" } -tracing = "0.1.39" -tracing-subscriber = "0.3.17" +tracing = { version = "0.1.39", features = ["max_level_debug"] } + +[dependencies.tracing-subscriber] +version = "0.3.17" +default-features = false +features = ["std", "ansi", "fmt", "smallvec", "env-filter"] @@ -1,3 +1,27 @@ +- [ ] Multiple rendering passes + - [ ] Effects using stencil buffer +- [ ] Remove possible edge cases in ECS component storage + - [ ] A Query<()> yields all components. Should this be the behaviour? +- [ ] Improve ECS component storage performance + - [ ] Give archetypes edges for faster component addition & removal + - [ ] Store components of the same kind in the same memory allocation (not boxed) +- [ ] Investigate what happends when a entity has all of it's components removed. +- [ ] Audio +- [ ] Transparent/translucent models +- [ ] Animations +- [ ] Physics +- [ ] Rotation (using quaternions) +- [ ] Add support for entity tags in ECS framework +- [ ] Make OpenGL resource (texture, VBO, VAO) structs not implement Drop. This will + probably make some parts of the code cleaner +- [ ] Add implementation of GJK algorithm for collision detection +- [ ] Add a way to not have to check every collidable object for collision. Something + like binary space partitioning would work +- [ ] Figure out way to handle collision of small fast moving objects +- [ ] Improve vertex code. +- [ ] Add support for optionally rendering objects without a projection +- [ ] Add shadow mapping +- [ ] Use Blinn-phong lighting instead of phong lighting - [x] Support for multiple textures - [x] Non-hardcoded projection settings - [x] Model importing @@ -9,18 +33,11 @@ - [x] Add support for entity relationships in ECS framework - [x] Add component events (removal & addition) in ECS framework - [x] Make renderer not create new buffers each frame -- [ ] Multiple rendering passes - - [ ] Effects using stencil buffer - [x] Remove position field from Camera component -- [ ] Remove possible edge cases in ECS component storage +- [x] Add support for entities with no components - [x] Fix OpenGL warning "Vertex shader in program 3 is being recompiled based on GL state". It started at commit 526edc6f4cb5f29d17e2fe384e316236c033fccd. Fixed by c4686c2992417545e7a05a6a40ee9f1a8bbf3b96 - [x] New texture IDs are created for no reason. Crashes when on texture ID 31 -- [ ] Audio -- [ ] Transparent/translucent models -- [ ] Animations -- [ ] Physics -- [ ] Rotation (using quaternions) -- [ ] Add support for entity tags in ECS framework - +- [x] engine::file_format:wavefront::obj::Obj::to_mesh seems to produce meshes with + pointless vertex indices since it flat maps all face vertices diff --git a/ecs-macros/src/lib.rs b/ecs-macros/src/lib.rs index 8bef0b6..b178022 100644 --- a/ecs-macros/src/lib.rs +++ b/ecs-macros/src/lib.rs @@ -110,6 +110,10 @@ pub fn component_derive(input: TokenStream) -> TokenStream use ::std::collections::HashMap; use #ecs_path::component::Component; + use #ecs_path::event::component::{ + Removed as ComponentRemovedEvent, + Kind as ComponentEventKind, + }; use #ecs_path::system::ComponentRefMut; use #ecs_path::system::ComponentRef; use #ecs_path::uid::{Uid, Kind as UidKind}; @@ -138,6 +142,18 @@ pub fn component_derive(input: TokenStream) -> TokenStream Self::id() } + fn get_event_uid(&self, event_kind: ComponentEventKind) -> Uid + { + match event_kind { + ComponentEventKind::Removed => ComponentRemovedEvent::<Self>::id(), + _ => { + panic!( + "Support for event kind {event_kind:?} not implemented!" + ); + } + } + } + fn as_any_mut(&mut self) -> &mut dyn Any { self diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index 342f087..4945171 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -3,15 +3,22 @@ name = "ecs" version = "0.1.0" edition = "2021" -[features] -debug = ["dep:tracing"] - [dependencies] seq-macro = "0.3.5" paste = "1.0.14" thiserror = "1.0.49" -tracing = { version = "0.1.39", optional = true } +tracing = "0.1.39" linkme = "0.3.29" +hashbrown = "0.15.2" +parking_lot = "0.12.3" ecs-macros = { path = "../ecs-macros" } util-macros = { path = "../util-macros" } +[dev-dependencies.criterion] +version = "0.5.1" +default-features = false +features = ["cargo_bench_support"] + +[[bench]] +name = "query" +harness = false diff --git a/ecs/benches/query.rs b/ecs/benches/query.rs new file mode 100644 index 0000000..f14bb06 --- /dev/null +++ b/ecs/benches/query.rs @@ -0,0 +1,141 @@ +use std::hint::black_box; +use std::path::PathBuf; + +use criterion::{criterion_group, criterion_main, Criterion}; +use ecs::{Component, World}; + +#[derive(Component)] +struct Foo +{ + _text: String, +} + +#[derive(Component)] +struct Bar +{ + _path: PathBuf, + _num: u64, +} + +#[derive(Component)] +struct Position +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosA +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosB +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosC +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct MoreText +{ + _more_text: String, +} + +#[derive(Component)] +struct EvenMoreText +{ + _even_more_text: String, +} + +fn spawn_1000_entities(world: &mut World) +{ + for _ in 0..300 { + world.create_entity(( + Bar { + _path: "/dev/zero".into(), + _num: 65789, + }, + Position { _x: 13.98, _y: 27.0, _z: 0.2 }, + Foo { _text: "Hello there".to_string() }, + PosA { + _x: 1183.98, + _y: 272628.0, + _z: 3306.2, + }, + PosB { + _x: 171183.98, + _y: 28.0, + _z: 336.2901, + }, + PosC { _x: 8273.98, _y: 28.0, _z: 336.2901 }, + MoreText { + _more_text: "Lorem ipsum".to_string(), + }, + EvenMoreText { + _even_more_text: "Wow so much text".to_string(), + }, + )); + } + + for _ in 0..700 { + world.create_entity(( + Bar { + _path: "/dev/null".into(), + _num: 65789, + }, + Position { _x: 88.11, _y: 9.0, _z: 36.11 }, + Foo { _text: "Hey".to_string() }, + PosA { + _x: 118310.98, + _y: 272628.0, + _z: 3306.2, + }, + PosB { _x: 11323.98, _y: 28.0, _z: 336.2901 }, + PosC { + _x: 8273.98, + _y: 21818.0, + _z: 336.2901, + }, + MoreText { + _more_text: "Lorem ipsum".to_string(), + }, + EvenMoreText { + _even_more_text: "Wow much text".to_string(), + }, + )); + } +} + +fn benchbark(criterion: &mut Criterion) +{ + criterion.bench_function("Iterate 1000 entities", |bencher| { + let mut world = World::new(); + + spawn_1000_entities(&mut world); + + let query = world.query::<(&Bar, &Position, &Foo), ()>(); + + bencher.iter(|| { + for comps in query.iter() { + black_box(comps); + } + }) + }); +} + +criterion_group!(benches, benchbark); +criterion_main!(benches); diff --git a/ecs/examples/event_loop.rs b/ecs/examples/event_loop.rs index 8d8d84f..2365eb0 100644 --- a/ecs/examples/event_loop.rs +++ b/ecs/examples/event_loop.rs @@ -1,6 +1,7 @@ use ecs::actions::Actions; -use ecs::event::Event; -use ecs::{Component, Query, World}; +use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; +use ecs::{static_entity, Component, Query, World}; #[derive(Component)] struct Wool @@ -20,7 +21,7 @@ struct Name name: &'static str, } -fn sheer(query: Query<(Wool, Name)>) +fn sheer(query: Query<(&mut Wool, &Name)>) { for (mut wool, name) in &query { if wool.remaining == 0 { @@ -36,7 +37,7 @@ fn sheer(query: Query<(Wool, Name)>) } } -fn feed(query: Query<(Health, Name)>) +fn feed(query: Query<(&mut Health, &Name)>) { for (mut health, name) in &query { health.health += 1; @@ -45,7 +46,7 @@ fn feed(query: Query<(Health, Name)>) } } -fn age(query: Query<(Health, Name)>, mut actions: Actions) +fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions) { for (mut health, name) in &query { if health.health <= 2 { @@ -64,28 +65,28 @@ fn age(query: Query<(Health, Name)>, mut actions: Actions) } } -#[derive(Debug)] -struct EventA; +static_entity!( + SHEER_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE_PHASE)) +); -impl Event for EventA {} +static_entity!( + FEED_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*SHEER_PHASE)) +); -#[derive(Debug)] -struct EventB; - -impl Event for EventB {} - -#[derive(Debug)] -struct EventC; - -impl Event for EventC {} +static_entity!( + AGE_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*FEED_PHASE)) +); fn main() { let mut world = World::new(); - world.register_system(EventA, sheer); - world.register_system(EventB, feed); - world.register_system(EventC, age); + world.register_system(*SHEER_PHASE, sheer); + world.register_system(*FEED_PHASE, feed); + world.register_system(*AGE_PHASE, age); world.create_entity(( Wool { remaining: 30 }, @@ -93,5 +94,5 @@ fn main() Name { name: "Bessy" }, )); - world.event_loop::<(EventA, EventB, EventC)>(); + world.start_loop(); } diff --git a/ecs/examples/extension.rs b/ecs/examples/extension.rs index 2022b05..f6282e1 100644 --- a/ecs/examples/extension.rs +++ b/ecs/examples/extension.rs @@ -1,6 +1,6 @@ use ecs::actions::Actions; -use ecs::event::Event; use ecs::extension::{Collector as ExtensionCollector, Extension}; +use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::{Component, Query, World}; #[derive(Debug, Component)] @@ -19,14 +19,9 @@ enum EvilnessLevel Medium, } -#[derive(Debug)] -struct Update; - -impl Event for Update {} - fn spawn_enemies( - spawner_query: Query<(EnemySpawnSource, Position)>, - enemies_query: Query<(EvilnessLevel,)>, + spawner_query: Query<(&EnemySpawnSource, &Position)>, + enemies_query: Query<(&EvilnessLevel,)>, mut actions: Actions, ) { @@ -57,7 +52,7 @@ impl Extension for EnemySpawningExtension { fn collect(self, mut collector: ExtensionCollector<'_>) { - collector.add_system(Update, spawn_enemies); + collector.add_system(*UPDATE_PHASE, spawn_enemies); collector.add_entity((Position { x: 187, y: 30 }, EnemySpawnSource)); } @@ -70,8 +65,6 @@ fn main() world.add_extension(EnemySpawningExtension); for _ in 0..7 { - world.emit(Update); - - world.perform_queued_actions(); + world.step(); } } diff --git a/ecs/examples/multiple_queries.rs b/ecs/examples/multiple_queries.rs index 2736bce..e0c957f 100644 --- a/ecs/examples/multiple_queries.rs +++ b/ecs/examples/multiple_queries.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use ecs::event::start::Start as StartEvent; +use ecs::phase::START as START_PHASE; use ecs::{Component, Query, World}; #[derive(Component)] @@ -31,8 +31,8 @@ impl Display for EnemyName } fn do_attacks( - attacker_query: Query<(AttackStrength,)>, - enemy_query: Query<(Health, EnemyName)>, + attacker_query: Query<(&AttackStrength,)>, + enemy_query: Query<(&mut Health, &EnemyName)>, ) { for (attack_strength,) in &attacker_query { @@ -61,7 +61,7 @@ fn main() { let mut world = World::new(); - world.register_system(StartEvent, do_attacks); + world.register_system(*START_PHASE, do_attacks); world.create_entity(( Health { health: 100 }, @@ -81,5 +81,5 @@ fn main() world.create_entity((AttackStrength::Strong,)); world.create_entity((AttackStrength::Weak,)); - world.emit(StartEvent); + world.step(); } diff --git a/ecs/examples/optional_component.rs b/ecs/examples/optional_component.rs index e47bf2e..488dad2 100644 --- a/ecs/examples/optional_component.rs +++ b/ecs/examples/optional_component.rs @@ -1,4 +1,4 @@ -use ecs::event::Event; +use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::{Component, Query, World}; #[derive(Debug, Component)] @@ -21,7 +21,7 @@ pub struct CatName name: String, } -fn pet_cats(query: Query<(CatName, PettingCapacity, Option<Aggressivity>)>) +fn pet_cats(query: Query<(&CatName, &mut PettingCapacity, &Option<Aggressivity>)>) { for (cat_name, mut petting_capacity, aggressivity) in &query { let Some(aggressivity) = aggressivity else { @@ -48,16 +48,11 @@ fn pet_cats(query: Query<(CatName, PettingCapacity, Option<Aggressivity>)>) } } -#[derive(Debug)] -struct PettingTime; - -impl Event for PettingTime {} - fn main() { let mut world = World::new(); - world.register_system(PettingTime, pet_cats); + world.register_system(*UPDATE_PHASE, pet_cats); world.create_entity(( CatName { name: "Jasper".to_string() }, @@ -82,5 +77,5 @@ fn main() Aggressivity::Low, )); - world.emit(PettingTime); + world.step(); } diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs index e8fb327..240884a 100644 --- a/ecs/examples/relationship.rs +++ b/ecs/examples/relationship.rs @@ -1,4 +1,4 @@ -use ecs::event::start::Start as StartEvent; +use ecs::phase::START as START_PHASE; use ecs::relationship::Relationship; use ecs::{Component, Query, World}; @@ -19,7 +19,9 @@ struct Health struct Holding; -fn print_player_stats(player_query: Query<(Player, Health, Relationship<Holding, Sword>)>) +fn print_player_stats( + player_query: Query<(&Player, &Health, &Relationship<Holding, Sword>)>, +) { for (_, health, sword_relationship) in &player_query { println!("Player health: {}", health.health); @@ -34,7 +36,7 @@ fn main() { let mut world = World::new(); - world.register_system(StartEvent, print_player_stats); + world.register_system(*START_PHASE, print_player_stats); let sword_uid = world.create_entity((Sword { attack_strength: 17 },)); @@ -44,5 +46,5 @@ fn main() Relationship::<Holding, Sword>::new(sword_uid), )); - world.emit(StartEvent); + world.step(); } diff --git a/ecs/examples/simple.rs b/ecs/examples/simple.rs index 4057c84..0169062 100644 --- a/ecs/examples/simple.rs +++ b/ecs/examples/simple.rs @@ -1,4 +1,4 @@ -use ecs::event::start::Start as StartEvent; +use ecs::phase::START as START_PHASE; use ecs::{Component, Query, World}; #[derive(Component)] @@ -13,7 +13,7 @@ struct Greeting greeting: String, } -fn say_hello(query: Query<(SomeData, Greeting)>) +fn say_hello(query: Query<(&SomeData, &Greeting)>) { for (data, greeting) in &query { println!("{}: {}", greeting.greeting, data.num); @@ -24,7 +24,7 @@ fn main() { let mut world = World::new(); - world.register_system(StartEvent, say_hello); + world.register_system(*START_PHASE, say_hello); world.create_entity(( SomeData { num: 987_654 }, @@ -38,5 +38,5 @@ fn main() Greeting { greeting: "Good evening".to_string() }, )); - world.emit(StartEvent); + world.step(); } diff --git a/ecs/examples/with_local.rs b/ecs/examples/with_local.rs index 5890b90..4658fc0 100644 --- a/ecs/examples/with_local.rs +++ b/ecs/examples/with_local.rs @@ -1,5 +1,5 @@ use ecs::component::local::Local; -use ecs::event::Event; +use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::system::{Into, System}; use ecs::{Component, Query, World}; @@ -21,7 +21,7 @@ struct SayHelloState cnt: usize, } -fn say_hello(query: Query<(SomeData,)>, mut state: Local<SayHelloState>) +fn say_hello(query: Query<(&SomeData,)>, mut state: Local<SayHelloState>) { for (data,) in &query { println!("Hello there. Count {}: {}", state.cnt, data.num); @@ -30,7 +30,7 @@ fn say_hello(query: Query<(SomeData,)>, mut state: Local<SayHelloState>) } } -fn say_whats_up(query: Query<(SomeData, Name)>, mut state: Local<SayHelloState>) +fn say_whats_up(query: Query<(&SomeData, &Name)>, mut state: Local<SayHelloState>) { for (data, name) in &query { println!( @@ -42,24 +42,19 @@ fn say_whats_up(query: Query<(SomeData, Name)>, mut state: Local<SayHelloState>) } } -#[derive(Debug)] -struct Update; - -impl Event for Update {} - fn main() { let mut world = World::new(); world.register_system( - Update, + *UPDATE_PHASE, say_hello .into_system() .initialize((SayHelloState { cnt: 0 },)), ); world.register_system( - Update, + *UPDATE_PHASE, say_whats_up .into_system() .initialize((SayHelloState { cnt: 0 },)), @@ -69,9 +64,6 @@ fn main() world.create_entity((SomeData { num: 345 },)); - world.emit(Update); - - println!("Haha"); - - world.emit(Update); + world.step(); + world.step(); } diff --git a/ecs/examples/with_sole.rs b/ecs/examples/with_sole.rs index a387bea..689e562 100644 --- a/ecs/examples/with_sole.rs +++ b/ecs/examples/with_sole.rs @@ -1,6 +1,7 @@ -use ecs::event::Event; +use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; use ecs::sole::Single; -use ecs::{Component, Query, Sole, World}; +use ecs::{static_entity, Component, Query, Sole, World}; #[derive(Component)] struct Ammo @@ -14,7 +15,7 @@ struct AmmoCounter counter: u32, } -fn count_ammo(query: Query<(Ammo,)>, mut ammo_counter: Single<AmmoCounter>) +fn count_ammo(query: Query<(&Ammo,)>, mut ammo_counter: Single<AmmoCounter>) { for (ammo,) in &query { println!("Found {} ammo", ammo.ammo_left); @@ -30,22 +31,17 @@ fn print_total_ammo_count(ammo_counter: Single<AmmoCounter>) assert_eq!(ammo_counter.counter, 19); } -#[derive(Debug)] -struct EventA; - -impl Event for EventA {} - -#[derive(Debug)] -struct EventB; - -impl Event for EventB {} +static_entity!( + PRINT_AMMO_COUNT_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE_PHASE)) +); fn main() { let mut world = World::new(); - world.register_system(EventA, count_ammo); - world.register_system(EventB, print_total_ammo_count); + world.register_system(*UPDATE_PHASE, count_ammo); + world.register_system(*PRINT_AMMO_COUNT_PHASE, print_total_ammo_count); world.create_entity((Ammo { ammo_left: 4 },)); world.create_entity((Ammo { ammo_left: 7 },)); @@ -53,7 +49,5 @@ fn main() world.add_sole(AmmoCounter::default()).unwrap(); - world.emit(EventA); - - world.emit(EventB); + world.step(); } diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs index f7b00d3..89fd84a 100644 --- a/ecs/src/actions.rs +++ b/ecs/src/actions.rs @@ -6,7 +6,7 @@ use crate::component::{ Metadata as ComponentMetadata, Sequence as ComponentSequence, }; -use crate::system::{NoInitParamFlag, Param as SystemParam, System}; +use crate::system::{Param as SystemParam, System}; use crate::uid::{Kind as UidKind, Uid}; use crate::{ActionQueue, World}; @@ -20,36 +20,61 @@ pub struct Actions<'world> impl<'world> Actions<'world> { - /// Adds a spawning a new entity to the action queue. + /// Queues up a entity to spawn at the end of the current tick. pub fn spawn<Comps: ComponentSequence>(&mut self, components: Comps) { - self.action_queue.push(Action::Spawn(components.into_vec())); + self.action_queue.push(Action::Spawn( + components.into_array().into(), + EventIds { ids: Comps::added_event_ids() }, + )); } - /// Adds component(s) to a entity. + /// Queues up despawning a entity at the end of the current tick. + pub fn despawn(&mut self, entity_uid: Uid) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + self.action_queue.push(Action::Despawn(entity_uid)); + } + + /// Queues up adding component(s) to a entity at the end of the current tick. pub fn add_components<Comps>(&mut self, entity_uid: Uid, components: Comps) where Comps: ComponentSequence, { debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - self.action_queue - .push(Action::AddComponents(entity_uid, components.into_vec())); + if Comps::COUNT == 0 { + return; + } + + self.action_queue.push(Action::AddComponents( + entity_uid, + components.into_array().into(), + EventIds { ids: Comps::added_event_ids() }, + )); } - /// Removes component(s) from a entity. + /// Queues up removing component(s) from a entity at the end of the current tick. pub fn remove_components<Comps>(&mut self, entity_uid: Uid) where Comps: ComponentSequence, { debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - self.action_queue - .push(Action::RemoveComponents(entity_uid, Comps::metadata())); + if Comps::COUNT == 0 { + return; + } + + self.action_queue.push(Action::RemoveComponents( + entity_uid, + Comps::metadata().into_iter().collect(), + EventIds { ids: Comps::removed_event_ids() }, + )); } - /// Adds stopping the loop in [`Engine::event_loop`] at the next opportune time to the - /// action queue. + /// Stops the [`World`]. The world will finish the current tick and that tick will be + /// the last. pub fn stop(&mut self) { self.action_queue.push(Action::Stop); @@ -75,9 +100,8 @@ impl<'world> Actions<'world> } } -unsafe impl<'world> SystemParam<'world> for Actions<'world> +impl<'world> SystemParam<'world> for Actions<'world> { - type Flags = NoInitParamFlag; type Input = (); fn initialize<SystemImpl>( @@ -135,12 +159,19 @@ impl<'weak_ref> Ref<'weak_ref> } } +#[derive(Debug)] +pub(crate) struct EventIds +{ + pub(crate) ids: Vec<Uid>, +} + /// A action for a [`System`] to perform. #[derive(Debug)] pub(crate) enum Action { - Spawn(Vec<Box<dyn Component>>), - AddComponents(Uid, Vec<Box<dyn Component>>), - RemoveComponents(Uid, Vec<ComponentMetadata>), + Spawn(Vec<Box<dyn Component>>, EventIds), + Despawn(Uid), + AddComponents(Uid, Vec<Box<dyn Component>>, EventIds), + RemoveComponents(Uid, Vec<ComponentMetadata>, EventIds), Stop, } diff --git a/ecs/src/archetype.rs b/ecs/src/archetype.rs deleted file mode 100644 index d2ee36a..0000000 --- a/ecs/src/archetype.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - -use crate::component::{ - IsOptional as ComponentIsOptional, - Metadata as ComponentMetadata, -}; -use crate::uid::Uid; - -/// Archetype ID. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Id -{ - hash: u64, -} - -impl Id -{ - pub fn from_components_metadata( - components_metadata: &impl AsRef<[ComponentMetadata]>, - ) -> Self - { - debug_assert!( - components_metadata.as_ref().len() > 0, - "Cannot create a archetype ID from a empty component metadata list" - ); - - debug_assert!( - components_metadata - .as_ref() - .is_sorted_by_key(|comp_metadata| comp_metadata.id), - "Cannot create archetype ID from a unsorted component metadata list" - ); - - Self::new( - components_metadata - .as_ref() - .iter() - .filter_map(|component_metadata| { - if component_metadata.is_optional == ComponentIsOptional::Yes { - return None; - } - - Some(component_metadata.id) - }), - ) - } - - /// Returns the ID of a archetype with the given components. - fn new(component_ids: impl IntoIterator<Item = Uid>) -> Self - { - let mut hasher = DefaultHasher::new(); - - for component_id in component_ids { - component_id.hash(&mut hasher); - } - - let hash = hasher.finish(); - - Self { hash } - } -} diff --git a/ecs/src/component.rs b/ecs/src/component.rs index a9894b7..b2ecf80 100644 --- a/ecs/src/component.rs +++ b/ecs/src/component.rs @@ -3,10 +3,16 @@ use std::fmt::Debug; use seq_macro::seq; -use crate::lock::{ReadGuard, WriteGuard}; -use crate::system::{ComponentRef, ComponentRefMut, Input as SystemInput}; +use crate::event::component::{ + Added as ComponentAddedEvent, + Kind as ComponentEventKind, + Removed as ComponentRemovedEvent, +}; +use crate::lock::{Error as LockError, Lock, ReadGuard, WriteGuard}; +use crate::system::Input as SystemInput; use crate::type_name::TypeName; use crate::uid::Uid; +use crate::util::Array; use crate::{EntityComponent, World}; pub mod local; @@ -20,11 +26,11 @@ pub trait Component: SystemInput + Any + TypeName where Self: Sized; - type RefMut<'component> + type RefMut<'component>: FromOptionalMut<'component> + FromLockedOptional<'component> where Self: Sized; - type Ref<'component> + type Ref<'component>: FromOptional<'component> + FromLockedOptional<'component> where Self: Sized; @@ -36,26 +42,28 @@ pub trait Component: SystemInput + Any + TypeName /// The ID of the component `self`. Returns the same value as [`Component::id`]. fn self_id(&self) -> Uid; + /// Returns the component UID of a component event for this component. + fn get_event_uid(&self, event_kind: ComponentEventKind) -> Uid; + #[doc(hidden)] fn as_any_mut(&mut self) -> &mut dyn Any; #[doc(hidden)] fn as_any(&self) -> &dyn Any; - /// Whether the component `self` is optional. Returns the same value as - /// [`Component::is_optional`]. - fn self_is_optional(&self) -> IsOptional + /// Returns whether the component `self` is optional. + fn self_is_optional(&self) -> bool { - IsOptional::No + false } /// Returns whether this component is optional. #[must_use] - fn is_optional() -> IsOptional + fn is_optional() -> bool where Self: Sized, { - IsOptional::No + false } } @@ -96,10 +104,12 @@ impl TypeName for Box<dyn Component> impl<ComponentT> Component for Option<ComponentT> where ComponentT: Component, + for<'a> Option<ComponentT::Ref<'a>>: FromOptional<'a> + FromLockedOptional<'a>, + for<'a> Option<ComponentT::RefMut<'a>>: FromOptionalMut<'a> + FromLockedOptional<'a>, { type Component = ComponentT; - type Ref<'component> = Option<ComponentRef<'component, ComponentT>>; - type RefMut<'component> = Option<ComponentRefMut<'component, ComponentT>>; + type Ref<'component> = Option<ComponentT::Ref<'component>>; + type RefMut<'component> = Option<ComponentT::RefMut<'component>>; fn id() -> Uid { @@ -111,6 +121,13 @@ where Self::id() } + fn get_event_uid(&self, event_kind: ComponentEventKind) -> Uid + { + match event_kind { + ComponentEventKind::Removed => ComponentRemovedEvent::<Self>::id(), + } + } + fn as_any_mut(&mut self) -> &mut dyn Any { self @@ -121,14 +138,14 @@ where self } - fn self_is_optional(&self) -> IsOptional + fn self_is_optional(&self) -> bool { - Self::is_optional() + true } - fn is_optional() -> IsOptional + fn is_optional() -> bool { - IsOptional::Yes + true } } @@ -147,33 +164,57 @@ impl<ComponentT> SystemInput for Option<ComponentT> where ComponentT: Component /// A sequence of components. pub trait Sequence { - type MutRefs<'component> - where - Self: 'component; + /// The number of components in this component sequence. + const COUNT: usize; - type Refs<'component> - where - Self: 'component; + type Array: Array<Box<dyn Component>>; - fn into_vec(self) -> Vec<Box<dyn Component>>; + fn into_array(self) -> Self::Array; - fn metadata() -> Vec<Metadata>; + fn metadata() -> impl Array<Metadata>; - fn from_components_mut<'component>( - components: impl Iterator<Item = &'component EntityComponent>, - world: &'component World, - lock_component: fn( - entity_component: &EntityComponent, - ) -> WriteGuard<'_, Box<dyn Component>>, - ) -> Self::MutRefs<'component>; + fn added_event_ids() -> Vec<Uid>; + + fn removed_event_ids() -> Vec<Uid>; +} + +/// A sequence of references (immutable or mutable) to components. +pub trait RefSequence +{ + type Handles<'component>; + + type Metadata: Array<Metadata>; + + fn metadata() -> Self::Metadata; fn from_components<'component>( - components: impl Iterator<Item = &'component EntityComponent>, + components: &'component [EntityComponent], + component_index_lookup: impl Fn(Uid) -> Option<usize>, world: &'component World, - lock_component: fn( - entity_component: &EntityComponent, - ) -> ReadGuard<'_, Box<dyn Component>>, - ) -> Self::Refs<'component>; + ) -> Self::Handles<'component>; +} + +/// A mutable or immutable reference to a component. +pub trait Ref +{ + type Component: Component; + type Handle<'component>: FromLockedOptional<'component>; +} + +impl<ComponentT> Ref for &ComponentT +where + ComponentT: Component, +{ + type Component = ComponentT; + type Handle<'component> = ComponentT::Ref<'component>; +} + +impl<ComponentT> Ref for &mut ComponentT +where + ComponentT: Component, +{ + type Component = ComponentT; + type Handle<'component> = ComponentT::RefMut<'component>; } /// [`Component`] metadata. @@ -182,7 +223,7 @@ pub trait Sequence pub struct Metadata { pub id: Uid, - pub is_optional: IsOptional, + pub is_optional: bool, } impl Metadata @@ -204,26 +245,6 @@ impl Metadata } } -/// Whether or not a `Component` is optional. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum IsOptional -{ - Yes, - No, -} - -impl From<bool> for IsOptional -{ - fn from(is_optional: bool) -> Self - { - if is_optional { - return IsOptional::Yes; - } - - IsOptional::No - } -} - pub trait FromOptionalMut<'comp> { fn from_optional_mut_component( @@ -240,6 +261,14 @@ pub trait FromOptional<'comp> ) -> Self; } +pub trait FromLockedOptional<'comp>: Sized +{ + fn from_locked_optional_component( + optional_component: Option<&'comp Lock<Box<dyn Component>>>, + world: &'comp World, + ) -> Result<Self, LockError>; +} + macro_rules! inner { ($c: tt) => { seq!(I in 0..=$c { @@ -248,79 +277,76 @@ macro_rules! inner { #(for<'comp> Comp~I::RefMut<'comp>: FromOptionalMut<'comp>,)* #(for<'comp> Comp~I::Ref<'comp>: FromOptional<'comp>,)* { - type MutRefs<'component> = (#(Comp~I::RefMut<'component>,)*) - where Self: 'component; + const COUNT: usize = $c + 1; - type Refs<'component> = (#(Comp~I::Ref<'component>,)*) - where Self: 'component; + type Array = [Box<dyn Component>; $c + 1]; - fn into_vec(self) -> Vec<Box<dyn Component>> + fn into_array(self) -> Self::Array { - Vec::from_iter([#(Box::new(self.I) as Box<dyn Component>,)*]) + [#(Box::new(self.I) as Box<dyn Component>,)*] } - fn metadata() -> Vec<Metadata> + fn metadata() -> impl Array<Metadata> { - vec![ + [ #( Metadata { id: Comp~I::id(), - is_optional: Comp~I::is_optional() + is_optional: Comp~I::is_optional(), }, )* ] } - fn from_components_mut<'component>( - components: impl Iterator<Item = &'component EntityComponent>, - world: &'component World, - lock_component: fn( - entity_component: &EntityComponent, - ) -> WriteGuard<'_, Box<dyn Component>>, - ) -> Self::MutRefs<'component> + fn added_event_ids() -> Vec<Uid> + { + vec![ + #(ComponentAddedEvent::<Comp~I>::id(),)* + ] + } + + fn removed_event_ids() -> Vec<Uid> { - #( - let mut comp_~I: Option<WriteGuard<Box<dyn Component>>> = None; - )* + vec![ + #(ComponentRemovedEvent::<Comp~I>::id(),)* + ] + } + } - for comp in components { - #( - if comp.id == Comp~I::Component::id() { - comp_~I = Some(lock_component(comp)); - continue; - } - )* - } + impl<#(CompRef~I: Ref,)*> RefSequence for (#(CompRef~I,)*) + { + type Handles<'component> = (#(CompRef~I::Handle<'component>,)*); - (#( - Comp~I::RefMut::from_optional_mut_component(comp_~I, world), - )*) + type Metadata = [Metadata; $c + 1]; + + fn metadata() -> Self::Metadata + { + [#( + Metadata { + id: CompRef~I::Component::id(), + is_optional: CompRef~I::Component::is_optional(), + }, + )*] } fn from_components<'component>( - components: impl Iterator<Item = &'component EntityComponent>, + components: &'component [EntityComponent], + component_index_lookup: impl Fn(Uid) -> Option<usize>, world: &'component World, - lock_component: fn( - entity_component: &EntityComponent, - ) -> ReadGuard<'_, Box<dyn Component>>, - ) -> Self::Refs<'component> + ) -> Self::Handles<'component> { - - #( - let mut comp_~I: Option<ReadGuard<Box<dyn Component>>> = None; - )* - - for comp in components { - #( - if comp.id == Comp~I::Component::id() { - comp_~I = Some(lock_component(comp)); - continue; - } - )* - } - (#( - Comp~I::Ref::from_optional_component(comp_~I, world), + CompRef~I::Handle::from_locked_optional_component( + component_index_lookup(CompRef~I::Component::id()) + .and_then(|component_index| components.get(component_index)) + .map(|component| &component.component), + world, + ).unwrap_or_else(|err| { + panic!( + "Taking component {} lock failed: {err}", + type_name::<CompRef~I::Component>() + ); + }), )*) } } @@ -328,6 +354,53 @@ macro_rules! inner { }; } -seq!(C in 0..=64 { +seq!(C in 0..=16 { inner!(C); }); + +impl Sequence for () +{ + type Array = [Box<dyn Component>; 0]; + + const COUNT: usize = 0; + + fn into_array(self) -> Self::Array + { + [] + } + + fn metadata() -> impl Array<Metadata> + { + [] + } + + fn added_event_ids() -> Vec<Uid> + { + Vec::new() + } + + fn removed_event_ids() -> Vec<Uid> + { + Vec::new() + } +} + +impl RefSequence for () +{ + type Handles<'component> = (); + type Metadata = [Metadata; 0]; + + fn metadata() -> Self::Metadata + { + [] + } + + fn from_components<'component>( + _components: &'component [EntityComponent], + _component_index_lookup: impl Fn(Uid) -> Option<usize>, + _world: &'component World, + ) -> Self::Handles<'component> + { + () + } +} diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs index 20627bf..ad79f6f 100644 --- a/ecs/src/component/local.rs +++ b/ecs/src/component/local.rs @@ -11,11 +11,10 @@ pub struct Local<'world, LocalComponent: Component> local_component: ComponentRefMut<'world, LocalComponent>, } -unsafe impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent> +impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent> where LocalComponent: Component, { - type Flags = (); type Input = LocalComponent; fn initialize<SystemImpl>( diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs index ffd682e..0e1a03d 100644 --- a/ecs/src/component/storage.rs +++ b/ecs/src/component/storage.rs @@ -1,465 +1,312 @@ use std::any::type_name; -use std::borrow::Borrow; -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; -use std::slice::Iter as SliceIter; -use std::vec::IntoIter as OwnedVecIter; - -use crate::archetype::Id as ArchetypeId; -use crate::component::{ - Component, - IsOptional as ComponentIsOptional, - Metadata as ComponentMetadata, +use std::iter::{Chain, Flatten}; + +use hashbrown::{HashMap, HashSet}; + +use crate::component::storage::archetype::{ + Archetype, + ArchetypeEntity, + Id as ArchetypeId, +}; +use crate::component::storage::graph::{ + ArchetypeAddEdgeRecursiveIter, + ArchetypeEdges, + Graph, }; +use crate::component::{Component, Metadata as ComponentMetadata}; use crate::type_name::TypeName; -use crate::uid::Uid; -use crate::util::Sortable; +use crate::uid::{Kind as UidKind, Uid}; use crate::EntityComponent; +pub mod archetype; + +mod graph; + #[derive(Debug, Default)] pub struct Storage { - archetypes: Vec<Archetype>, - archetype_lookup: RefCell<HashMap<ArchetypeId, ArchetypeLookupEntry>>, + graph: Graph, entity_archetype_lookup: HashMap<Uid, ArchetypeId>, } impl Storage { - pub fn find_entities<CompMetadataList>( + pub fn iter_archetypes_with_comps( &self, - mut components_metadata: CompMetadataList, + component_metadata: impl AsRef<[ComponentMetadata]>, ) -> ArchetypeRefIter<'_> - where - CompMetadataList: Sortable<Item = ComponentMetadata>, - CompMetadataList: AsRef<[ComponentMetadata]>, { - components_metadata.sort_by_key_b(|component_metadata| component_metadata.id); - - let archetype_id = ArchetypeId::from_components_metadata(&components_metadata); + let archetype_id = ArchetypeId::from_components_metadata(&component_metadata); - // This looks stupid but the borrow checker complains otherwise - if self.archetype_lookup.borrow().contains_key(&archetype_id) { - return self.iter_archetypes_by_lookup(archetype_id); + ArchetypeRefIter { + storage: self, + iter: [archetype_id].into_iter().chain( + self.graph + .recurse_archetype_add_edges(archetype_id) + .into_iter() + .flatten() + .flatten(), + ), + visited: HashSet::new(), } - - let comp_ids_set = create_non_opt_component_id_set(components_metadata.as_ref()); - - let matching_archetype_indices = self - .archetypes - .iter() - .enumerate() - .filter_map(|(index, archetype)| { - if archetype.component_ids_is_superset(&comp_ids_set) { - return Some(index); - } - - None - }) - .collect(); - - self.archetype_lookup.borrow_mut().insert( - archetype_id, - ArchetypeLookupEntry { - component_ids: comp_ids_set, - archetype_indices: matching_archetype_indices, - }, - ); - - self.iter_archetypes_by_lookup(archetype_id) } - pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> + pub fn get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype> { - let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - - let archetype_index = - self.find_archetype_index_with_entity(*archetype_id, entity_uid)?; - - self.archetypes.get(archetype_index) + Some(self.graph.get_node_by_id(id)?.archetype()) } - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] - pub fn push_entity( - &mut self, - entity_uid: Uid, - mut components: Vec<Box<dyn Component>>, - ) -> Result<(ArchetypeId, Uid), Error> + pub fn create_entity(&mut self, uid: Uid) -> Result<(), Error> { - if self.entity_archetype_lookup.contains_key(&entity_uid) { - return Err(Error::EntityAlreadyExists(entity_uid)); - } - - components.sort_by_key(|component| component.self_id()); - - #[cfg(feature = "debug")] - tracing::debug!( - "Pushing entity with components: ({})", - components - .iter() - .map(|component| component.type_name()) - .collect::<Vec<_>>() - .join(", ") - ); - - let archetype_id = ArchetypeId::from_components_metadata( - &components - .iter() - .map(|component| ComponentMetadata::get(&**component)) - .collect::<Vec<_>>(), - ); - - let comp_ids_set = create_non_opt_component_id_set( - components - .iter() - .map(|component| ComponentMetadata::get(&**component)), - ); - - let archetype_index = - self.get_or_create_archetype(archetype_id, &comp_ids_set, &components); + debug_assert_eq!(uid.kind(), UidKind::Entity); - self.populate_matching_archetype_lookup_entries(&comp_ids_set, archetype_index); + if self.entity_archetype_lookup.contains_key(&uid) { + return Err(Error::EntityAlreadyExists(uid)); + } - let archetype = self - .archetypes - .get_mut(archetype_index) - .expect("Archetype is gone"); + let empty_archetype_id = ArchetypeId::from_components_metadata(&[]); - archetype.push_entity(entity_uid, components); + let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]); - archetype - .entity_lookup - .insert(entity_uid, archetype.entities.len() - 1); + archetype_node + .archetype_mut() + .push_entity(ArchetypeEntity { uid, components: vec![] }); - self.entity_archetype_lookup - .insert(entity_uid, archetype_id); + self.entity_archetype_lookup.insert(uid, empty_archetype_id); - Ok((archetype_id, entity_uid)) + Ok(()) } - pub fn add_components_to_entity( + pub fn remove_entity( &mut self, entity_uid: Uid, - components: Vec<Box<dyn Component>>, - ) -> Option<()> + ) -> Result<Vec<EntityComponent>, Error> { - let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - - let archetype_index = - self.find_archetype_index_with_entity(*archetype_id, entity_uid)?; - - let archetype = self.archetypes.get_mut(archetype_index)?; + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; - let contains_component_already = components - .iter() - .any(|component| archetype.component_ids.contains_key(&component.self_id())); - - if contains_component_already { - let component_cnt = components.len(); - - // TODO: return error - panic!( - "Entity with UID {:?} already contains one or more component(s) ({})", - entity_uid, - components - .iter() - .flat_map(|component| [component.type_name(), ", "]) - .enumerate() - .take_while(|(index, _)| { *index < (component_cnt * 2) - 1 }) - .map(|(_, component)| component) - .collect::<String>() - ); - } + let archetype_node = self + .graph + .get_node_by_id_mut(*archetype_id) + .expect("Archetype should exist"); - let entity = archetype.take_entity(entity_uid)?; + let entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); self.entity_archetype_lookup.remove(&entity_uid); - self.push_entity( - entity_uid, - entity - .components - .into_iter() - .map(|component| component.component.into_inner()) - .chain(components) - .collect(), - ) - .expect("Not supposed to return Err since the entity is removed"); - - Some(()) + Ok(entity.components) } - pub fn remove_components_from_entity( - &mut self, - entity_uid: Uid, - component_ids: impl IntoIterator<Item = Uid>, - ) -> Option<()> + pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> { let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - let archetype_index = - self.find_archetype_index_with_entity(*archetype_id, entity_uid)?; - - let archetype = self.archetypes.get_mut(archetype_index)?; - - let entity = archetype.take_entity(entity_uid)?; - - let component_ids_set = component_ids.into_iter().collect::<HashSet<_>>(); - - self.entity_archetype_lookup.remove(&entity_uid); - - self.push_entity( - entity_uid, - entity - .components - .into_iter() - .map(|component| component.component.into_inner()) - .filter(|component| !component_ids_set.contains(&component.self_id())) - .collect(), - ) - .expect("Not supposed to return Err since the entity is removed"); - - Some(()) + self.get_archetype_by_id(*archetype_id) } - fn populate_matching_archetype_lookup_entries( + pub fn add_entity_component( &mut self, - comp_ids_set: &HashSet<Uid>, - archetype_index: usize, - ) + entity_uid: Uid, + component: Box<dyn Component>, + ) -> Result<(), Error> { - let mut archetype_lookup = self.archetype_lookup.borrow_mut(); - - for (_, lookup_entry) in archetype_lookup.iter_mut() { - if &lookup_entry.component_ids == comp_ids_set { - continue; - } - - // There shouldn't be duplicate archetype indices in the lookup entry - if lookup_entry.archetype_indices.contains(&archetype_index) { - continue; - } + debug_assert!( + !component.self_is_optional(), + "Adding a optional component to a entity is not supported" + ); - if lookup_entry.component_ids.is_subset(comp_ids_set) { - lookup_entry.archetype_indices.push(archetype_index); - } + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; + + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if archetype_node + .archetype() + .has_component_with_id(component.self_id()) + { + return Err(Error::EntityAlreadyHasComponent { + entity: entity_uid, + component: component.self_id(), + }); } - } - fn get_or_create_archetype( - &mut self, - archetype_id: ArchetypeId, - comp_ids_set: &HashSet<Uid>, - components: &[Box<dyn Component>], - ) -> usize - { - let mut archetype_lookup = self.archetype_lookup.borrow_mut(); - - let lookup_entry = archetype_lookup.entry(archetype_id).or_insert_with(|| { - ArchetypeLookupEntry { - component_ids: comp_ids_set.clone(), - archetype_indices: Vec::with_capacity(1), - } - }); - - if lookup_entry.archetype_indices.is_empty() { - self.archetypes.push(Archetype::new( - components.iter().map(|component| component.self_id()), - )); - - lookup_entry - .archetype_indices - .push(self.archetypes.len() - 1); - } + let add_edge_archetype_id = archetype_node + .get_or_insert_edges(component.self_id(), ArchetypeEdges::default) + .add + .unwrap_or_else(|| { + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let (add_edge_id, add_edge_comp_ids) = + archetype_node.make_add_edge(component.self_id()); + + archetype_node + .get_edges_mut(component.self_id()) + .expect("Edges for component in archetype should exist") + .add = Some(add_edge_id); + + if !self.graph.contains_archetype(add_edge_id) { + self.graph.create_node(add_edge_id, &add_edge_comp_ids); + } - // SAFETY: Above, we push a archetype index if archetype_indices is empty so this - // cannot fail - unsafe { *lookup_entry.archetype_indices.first().unwrap_unchecked() } - } + add_edge_id + }); - fn find_archetype_index_with_entity( - &self, - archetype_id: ArchetypeId, - entity_uid: Uid, - ) -> Option<usize> - { - let archetype_lookup = self.archetype_lookup.borrow_mut(); + { + let add_edge_archetype_node = self + .graph + .get_node_by_id_mut(add_edge_archetype_id) + .expect("Add edge archetype should exist"); - let archetype_lookup_entry = archetype_lookup.get(&archetype_id)?; + let add_edge_archetype_edges = add_edge_archetype_node + .get_or_insert_edges(component.self_id(), ArchetypeEdges::default); - // TODO: Also have a hashmap for precise archetype ID -> archetype index lookup. - // This way is slow - archetype_lookup_entry - .archetype_indices - .iter() - .find(|archetype_index| { - let Some(archetype) = self.archetypes.get(**archetype_index) else { - return false; - }; - - archetype.has_entity(entity_uid) - }) - .copied() - } + add_edge_archetype_edges.remove = Some(archetype_id); + } - fn iter_archetypes_by_lookup(&self, archetype_id: ArchetypeId) - -> ArchetypeRefIter<'_> - { - let archetype_lookup = self.archetype_lookup.borrow(); + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); - // The archetype indices have to be cloned to prevent a panic when query - // iterations are nested. The panic happens because the archetype_lookup RefCell - // is borrowed and it tries to mutably borrow it - let archetype_indices = archetype_lookup - .get(&archetype_id) - .unwrap() - .archetype_indices - .clone(); + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); - ArchetypeRefIter { - indices: archetype_indices.into_iter(), - archetypes: &self.archetypes, - } - } -} + let add_edge_archetype = self + .graph + .get_node_by_id_mut(add_edge_archetype_id) + .expect("Add edge archetype should exist") + .archetype_mut(); -/// Component storage error -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error -{ - #[error("Entity already exists")] - EntityAlreadyExists(Uid), -} + let component_index = add_edge_archetype + .get_index_for_component(component.self_id()) + .expect("Archetype should have index for component"); -impl TypeName for Storage -{ - fn type_name(&self) -> &'static str - { - type_name::<Self>() - } -} + entity.components.insert(component_index, component.into()); -#[derive(Debug)] -struct ArchetypeLookupEntry -{ - component_ids: HashSet<Uid>, - archetype_indices: Vec<usize>, -} + add_edge_archetype.push_entity(entity); -#[derive(Debug)] -pub struct Archetype -{ - component_ids: HashMap<Uid, usize>, - entity_lookup: HashMap<Uid, usize>, - entities: Vec<ArchetypeEntity>, -} + self.entity_archetype_lookup + .insert(entity_uid, add_edge_archetype_id); -impl Archetype -{ - fn new(component_ids: impl IntoIterator<Item = Uid>) -> Self - { - Self { - component_ids: component_ids - .into_iter() - .enumerate() - .map(|(index, component_id)| (component_id, index)) - .collect(), - entity_lookup: HashMap::new(), - entities: Vec::new(), - } + Ok(()) } - pub fn component_ids_is_superset(&self, other_component_ids: &HashSet<Uid>) -> bool + pub fn remove_entity_component( + &mut self, + entity_uid: Uid, + component_id: Uid, + ) -> Result<(), Error> { - if other_component_ids.len() <= self.component_ids.len() { - other_component_ids - .iter() - .all(|v| self.component_ids.contains_key(v)) - } else { - false + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; + + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if !archetype_node + .archetype() + .has_component_with_id(component_id) + { + return Err(Error::EntityDoesNotHaveComponent { + entity: entity_uid, + component: component_id, + }); } - } - - pub fn get_entity(&self, entity_uid: Uid) -> Option<&ArchetypeEntity> - { - let entity_index = *self.entity_lookup.get(&entity_uid)?; - self.entities.get(entity_index) - } + let remove_edge_id = archetype_node + .get_or_insert_edges(component_id, ArchetypeEdges::default) + .remove + .unwrap_or_else(|| { + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let (remove_edge_id, remove_edge_comp_ids) = + archetype_node.make_remove_edge(component_id); + + archetype_node + .get_edges_mut(component_id) + .expect("Edges for component in archetype should exist") + .remove = Some(remove_edge_id); + + if !self.graph.contains_archetype(remove_edge_id) { + self.graph + .create_node(remove_edge_id, &remove_edge_comp_ids); + } - pub fn entities(&self) -> EntityIter<'_> - { - EntityIter { iter: self.entities.iter() } - } + remove_edge_id + }); - pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize> - { - self.component_ids.get(&component_id).copied() - } + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); - fn push_entity( - &mut self, - entity_uid: Uid, - components: impl IntoIterator<Item = Box<dyn Component>>, - ) - { - self.entities.push(ArchetypeEntity { - uid: entity_uid, - components: components.into_iter().map(Into::into).collect(), - }); - } + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); - pub fn take_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity> - { - let entity_index = self.entity_lookup.remove(&entity_uid)?; + let (comp_to_remove_index, _) = entity + .components + .iter() + .enumerate() + .find(|(_, comp)| comp.id == component_id) + .expect("Entity should contain component"); - let entity = self.entities.remove(entity_index); + entity.components.remove(comp_to_remove_index); - for index in self.entity_lookup.values_mut() { - if *index > entity_index { - *index -= 1; - } - } + self.graph + .get_node_by_id_mut(remove_edge_id) + .expect("Remove edge archetype should exist") + .archetype_mut() + .push_entity(entity); - Some(entity) - } + self.entity_archetype_lookup + .insert(entity_uid, remove_edge_id); - fn has_entity(&self, entity_uid: Uid) -> bool - { - self.entity_lookup.contains_key(&entity_uid) + Ok(()) } } -#[derive(Debug)] -pub struct ArchetypeEntity -{ - uid: Uid, - components: Vec<EntityComponent>, -} - -impl ArchetypeEntity +impl TypeName for Storage { - pub fn uid(&self) -> Uid - { - self.uid - } - - pub fn components(&self) -> &[EntityComponent] - { - &self.components - } - - pub fn get_component(&self, index: usize) -> Option<&EntityComponent> + fn type_name(&self) -> &'static str { - self.components.get(index) + type_name::<Self>() } } #[derive(Debug)] -pub struct ArchetypeRefIter<'component_storage> +pub struct ArchetypeRefIter<'storage> { - indices: OwnedVecIter<usize>, - archetypes: &'component_storage [Archetype], + storage: &'storage Storage, + iter: Chain< + std::array::IntoIter<ArchetypeId, 1>, + Flatten<Flatten<std::option::IntoIter<ArchetypeAddEdgeRecursiveIter<'storage>>>>, + >, + visited: HashSet<ArchetypeId>, } impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage> @@ -468,154 +315,67 @@ impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage> fn next(&mut self) -> Option<Self::Item> { - let archetype_index = self.indices.next()?; + let archetype_id = self + .iter + .find(|archetype_id| !self.visited.contains(archetype_id))?; + + let archetype = self.storage.get_archetype_by_id(archetype_id)?; + + self.visited.insert(archetype_id); - Some( - self.archetypes - .get(archetype_index) - .expect("Archetype index in archetype lookup entry was not found"), - ) + Some(archetype) } } -#[derive(Debug)] -pub struct EntityIter<'archetype> +#[derive(Debug, thiserror::Error)] +pub enum Error { - iter: SliceIter<'archetype, ArchetypeEntity>, -} + #[error("Entity with ID {0:?} already exists")] + EntityAlreadyExists(Uid), -impl<'archetype> Iterator for EntityIter<'archetype> -{ - type Item = &'archetype ArchetypeEntity; + #[error("Entity with ID {0:?} does not exist")] + EntityDoesNotExist(Uid), - fn next(&mut self) -> Option<Self::Item> + #[error("Entity with ID {entity:?} already has component with ID {component:?}")] + EntityAlreadyHasComponent { - self.iter.next() - } -} + entity: Uid, component: Uid + }, -fn create_non_opt_component_id_set<Item>( - component_metadata_iter: impl IntoIterator<Item = Item>, -) -> HashSet<Uid> -where - Item: Borrow<ComponentMetadata>, -{ - component_metadata_iter - .into_iter() - .filter_map(|item| { - let component_metadata = item.borrow(); - - if component_metadata.is_optional == ComponentIsOptional::Yes { - return None; - } - - Some(component_metadata.id) - }) - .collect::<HashSet<_>>() + #[error("Entity with ID {entity:?} does not have component with ID {component:?}")] + EntityDoesNotHaveComponent + { + entity: Uid, component: Uid + }, } #[cfg(test)] mod tests { - - use ecs_macros::Component; - - use super::Storage; - use crate::archetype::Id as ArchetypeId; - use crate::component::{ - Component, - IsOptional as ComponentIsOptional, - Metadata as ComponentMetadata, - }; + use crate::component::storage::archetype::Id as ArchetypeId; + use crate::component::storage::Storage; use crate::uid::{Kind as UidKind, Uid}; - #[derive(Debug, Component)] - struct HealthPotion - { - _hp_restoration: u32, - } - - #[derive(Debug, Component)] - struct Hookshot - { - _range: u32, - } - - #[derive(Debug, Component)] - struct DekuNut - { - _throwing_damage: u32, - } - - #[derive(Debug, Component)] - struct Bow - { - _damage: u32, - } - - #[derive(Debug, Component)] - struct IronBoots; - #[test] - fn push_entity_works() + fn create_entity_works() { - let mut component_storage = Storage::default(); + let mut new_storage = Storage::default(); - component_storage - .push_entity( - Uid::new_unique(UidKind::Entity), - vec![ - Box::new(HealthPotion { _hp_restoration: 12 }), - Box::new(Hookshot { _range: 50 }), - ], - ) - .expect("Expected Ok"); + let uid = Uid::new_unique(UidKind::Entity); - assert_eq!(component_storage.archetypes.len(), 1); + new_storage.create_entity(uid).expect("Expected Ok"); - let archetype = component_storage - .archetypes - .first() - .expect("Expected a archetype in archetypes Vec"); + let archetype_node = new_storage + .graph + .get_node_by_id(ArchetypeId::from_components_metadata(&[])) + .expect("Archetype for entities with no component doesn't exist"); - assert_eq!(archetype.component_ids.len(), 2); + assert_eq!(archetype_node.archetype().component_cnt(), 0); + assert_eq!(archetype_node.archetype().entity_cnt(), 1); - // One entity - assert_eq!(archetype.entities.len(), 1); - - let entity_components = archetype - .entities - .first() - .expect("Expected a entity in archetype"); - - assert_eq!(entity_components.components.len(), 2); - - assert_eq!(component_storage.archetype_lookup.borrow().len(), 1); - - let mut components_metadata = [ - ComponentMetadata { - id: HealthPotion::id(), - is_optional: ComponentIsOptional::No, - }, - ComponentMetadata { - id: Hookshot::id(), - is_optional: ComponentIsOptional::No, - }, - ]; - - components_metadata.sort_by_key(|comp_metadata| comp_metadata.id); - - let archetype_lookup = component_storage.archetype_lookup.borrow(); - - let lookup_entry = archetype_lookup - .get(&ArchetypeId::from_components_metadata(&components_metadata)) - .expect("Expected entry in archetype lookup map"); - - let first_archetype_index = lookup_entry - .archetype_indices - .first() - .expect("Expected archetype lookup to contain a archetype reference"); - - assert_eq!(*first_archetype_index, 0); + assert_eq!( + new_storage.entity_archetype_lookup.get(&uid).copied(), + Some(ArchetypeId::from_components_metadata(&[])) + ); } } diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs new file mode 100644 index 0000000..ee3d7f8 --- /dev/null +++ b/ecs/src/component/storage/archetype.rs @@ -0,0 +1,216 @@ +use std::borrow::Borrow; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::slice::Iter as SliceIter; + +use hashbrown::HashMap; + +use crate::component::Metadata as ComponentMetadata; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::HashMapExt; +use crate::EntityComponent; + +#[derive(Debug)] +pub struct Archetype +{ + id: Id, + entities: Vec<ArchetypeEntity>, + entity_index_lookup: HashMap<Uid, usize>, + component_index_lookup: HashMap<Uid, usize>, +} + +impl Archetype +{ + pub fn new(id: Id, component_ids: impl IntoIterator<Item: Borrow<Uid>>) -> Self + { + Self { + id, + entities: Vec::new(), + entity_index_lookup: HashMap::new(), + component_index_lookup: component_ids + .into_iter() + .enumerate() + .map(|(index, id)| (*id.borrow(), index)) + .collect(), + } + } + + pub fn id(&self) -> Id + { + self.id + } + + pub fn is_superset(&self, other: &Self) -> bool + { + self.component_index_lookup + .keys_is_superset(&other.component_index_lookup) + } + + pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&ArchetypeEntity> + { + let index = *self.entity_index_lookup.get(&entity_uid)?; + + Some(self.entities.get(index).unwrap_or_else(|| { + panic!( + "In invalid state! Index of entity with ID {entity_uid:?} is out of bounds" + ); + })) + } + + pub fn push_entity(&mut self, entity: ArchetypeEntity) + { + self.entity_index_lookup + .insert(entity.uid, self.entities.len()); + + self.entities.push(entity); + } + + pub fn remove_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity> + { + //debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + let entity_index = self.entity_index_lookup.remove(&entity_uid)?; + + if self.entities.len() == 1 { + return Some(self.entities.remove(entity_index)); + } + + let last_entity_uid = self + .entities + .last() + .expect(concat!( + "Invalid state. No entities in archetype but entry was ", + "removed successfully from entity index lookup" + )) + .uid; + + // By using swap_remove, no memory reallocation occurs and only one index in the + // entity lookup needs to be updated + let removed_entity = self.entities.swap_remove(entity_index); + + self.entity_index_lookup + .insert(last_entity_uid, entity_index); + + Some(removed_entity) + } + + pub fn entities(&self) -> EntityIter<'_> + { + EntityIter { iter: self.entities.iter() } + } + + pub fn entity_cnt(&self) -> usize + { + self.entities.len() + } + + pub fn component_cnt(&self) -> usize + { + self.component_index_lookup.len() + } + + pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize> + { + self.component_index_lookup.get(&component_id).copied() + } + + pub fn component_ids_unsorted(&self) -> impl Iterator<Item = Uid> + '_ + { + self.component_index_lookup.keys().copied() + } + + pub fn has_component_with_id(&self, component_id: Uid) -> bool + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.component_index_lookup.contains_key(&component_id) + } +} + +#[derive(Debug)] +pub struct EntityIter<'archetype> +{ + iter: SliceIter<'archetype, ArchetypeEntity>, +} + +impl<'archetype> Iterator for EntityIter<'archetype> +{ + type Item = &'archetype ArchetypeEntity; + + fn next(&mut self) -> Option<Self::Item> + { + self.iter.next() + } +} + +#[derive(Debug)] +pub struct ArchetypeEntity +{ + pub uid: Uid, + pub components: Vec<EntityComponent>, +} + +/// Archetype ID. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Id +{ + hash: u64, +} + +impl Id +{ + pub fn new(component_ids: &impl AsRef<[Uid]>) -> Self + { + if component_ids.as_ref().len() == 0 { + return Self { hash: 0 }; + } + + debug_assert!( + component_ids.as_ref().is_sorted(), + "Cannot create archetype ID from unsorted component IDs" + ); + + let mut hasher = DefaultHasher::new(); + + for component_id in component_ids.as_ref() { + component_id.hash(&mut hasher); + } + + Self { hash: hasher.finish() } + } + + pub fn from_components_metadata( + components_metadata: &impl AsRef<[ComponentMetadata]>, + ) -> Self + { + if components_metadata.as_ref().len() == 0 { + return Self { hash: 0 }; + } + + debug_assert!( + components_metadata + .as_ref() + .is_sorted_by_key(|comp_metadata| comp_metadata.id), + "Cannot create archetype ID from a unsorted component metadata list" + ); + + let component_ids = + components_metadata + .as_ref() + .iter() + .filter_map(|component_metadata| { + if component_metadata.is_optional { + return None; + } + + Some(component_metadata.id) + }); + + let mut hasher = DefaultHasher::new(); + + for component_id in component_ids { + component_id.hash(&mut hasher); + } + + Self { hash: hasher.finish() } + } +} diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs new file mode 100644 index 0000000..fe97933 --- /dev/null +++ b/ecs/src/component/storage/graph.rs @@ -0,0 +1,269 @@ +use hashbrown::hash_map::Values as HashMapValueIter; +use hashbrown::HashMap; + +use crate::component::storage::archetype::{Archetype, Id as ArchetypeId}; +use crate::uid::{Kind as UidKind, Uid}; + +#[derive(Debug, Default)] +pub struct Graph +{ + nodes: Vec<ArchetypeNode>, + archetype_index_lookup: HashMap<ArchetypeId, usize>, +} + +impl Graph +{ + pub fn create_node(&mut self, id: ArchetypeId, component_ids: &impl AsRef<[Uid]>) + { + debug_assert!(!self.contains_archetype(id)); + + let _ = self.get_or_create_node(id, component_ids); + } + + pub fn get_or_create_node( + &mut self, + id: ArchetypeId, + component_ids: &impl AsRef<[Uid]>, + ) -> &mut ArchetypeNode + { + let index = *self.archetype_index_lookup.entry(id).or_insert_with(|| { + self.nodes.push(ArchetypeNode { + archetype: Archetype::new(id, component_ids.as_ref()), + edges: HashMap::new(), + }); + + self.nodes.len() - 1 + }); + + self.create_missing_edges(id); + + self.nodes + .get_mut(index) + .expect("Archetype index from lookup is out of bounds") + } + + pub fn contains_archetype(&self, id: ArchetypeId) -> bool + { + self.archetype_index_lookup.contains_key(&id) + } + + pub fn get_node_by_id(&self, id: ArchetypeId) -> Option<&ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + pub fn get_node_by_id_mut(&mut self, id: ArchetypeId) -> Option<&mut ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get_mut(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + pub fn recurse_archetype_add_edges( + &self, + archetype_id: ArchetypeId, + ) -> Option<ArchetypeAddEdgeRecursiveIter> + { + Some(ArchetypeAddEdgeRecursiveIter { + graph: self, + stack: vec![self.get_node_by_id(archetype_id)?.edges.values()], + }) + } + + fn create_missing_edges(&mut self, archetype_id: ArchetypeId) + { + let archetype_node = self + .get_node_by_id(archetype_id) + .expect("Archetype should exist"); + + let mut comp_ids = archetype_node + .archetype() + .component_ids_unsorted() + .collect::<Vec<_>>(); + + comp_ids.sort(); + + for component_id_index in 0..archetype_node.archetype().component_cnt() { + let mut comp_ids_without_comp = comp_ids.clone(); + + let removed_comp_id = comp_ids_without_comp.remove(component_id_index); + + let archetype_without_comp_id = ArchetypeId::new(&comp_ids_without_comp); + + let Some(archetype_node_without_comp) = + self.get_node_by_id_mut(archetype_without_comp_id) + else { + continue; + }; + + archetype_node_without_comp + .get_or_insert_edges(removed_comp_id, ArchetypeEdges::default) + .add = Some(archetype_id); + + let archetype_node = self + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + archetype_node + .get_or_insert_edges(removed_comp_id, ArchetypeEdges::default) + .remove = Some(archetype_without_comp_id); + } + + let archetype_node_index = *self + .archetype_index_lookup + .get(&archetype_id) + .expect("Archetype should exist"); + + let (nodes_before, nodes_rest) = self.nodes.split_at_mut(archetype_node_index); + + let ([archetype_node], nodes_after) = nodes_rest.split_at_mut(1) else { + unreachable!(); + }; + + let expected_component_cnt = archetype_node.archetype().component_cnt() + 1; + + for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut()) + { + if other_archetype_node.archetype().component_cnt() != expected_component_cnt + || !other_archetype_node + .archetype() + .is_superset(&archetype_node.archetype()) + { + continue; + } + + let extra_comp_id = other_archetype_node + .archetype() + .component_ids_unsorted() + .find(|comp_id| { + !archetype_node.archetype().has_component_with_id(*comp_id) + }) + .expect("Archetype should contain one extra component ID"); + + other_archetype_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .remove = Some(archetype_id); + + archetype_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .add = Some(other_archetype_node.archetype().id()); + } + } +} + +#[derive(Debug)] +pub struct ArchetypeNode +{ + archetype: Archetype, + edges: HashMap<Uid, ArchetypeEdges>, +} + +impl ArchetypeNode +{ + pub fn archetype(&self) -> &Archetype + { + &self.archetype + } + + pub fn archetype_mut(&mut self) -> &mut Archetype + { + &mut self.archetype + } + + pub fn get_or_insert_edges( + &mut self, + component_id: Uid, + insert_fn: impl FnOnce() -> ArchetypeEdges, + ) -> &mut ArchetypeEdges + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.edges.entry(component_id).or_insert_with(insert_fn) + } + + pub fn get_edges_mut(&mut self, component_id: Uid) -> Option<&mut ArchetypeEdges> + { + debug_assert_eq!(component_id.kind(), UidKind::Component); + + self.edges.get_mut(&component_id) + } + + pub fn make_add_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .chain([component_id]) + .collect::<Vec<_>>(); + + edge_comp_ids.sort(); + + let add_edge_id = ArchetypeId::new(&edge_comp_ids); + + (add_edge_id, edge_comp_ids) + } + + pub fn make_remove_edge(&self, component_id: Uid) -> (ArchetypeId, Vec<Uid>) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .filter(|id| *id != component_id) + .collect::<Vec<_>>(); + + edge_comp_ids.sort(); + + let remove_edge_id = ArchetypeId::new(&edge_comp_ids); + + (remove_edge_id, edge_comp_ids) + } +} + +#[derive(Debug, Default)] +pub struct ArchetypeEdges +{ + pub add: Option<ArchetypeId>, + pub remove: Option<ArchetypeId>, +} + +#[derive(Debug)] +pub struct ArchetypeAddEdgeRecursiveIter<'graph> +{ + graph: &'graph Graph, + stack: Vec<HashMapValueIter<'graph, Uid, ArchetypeEdges>>, +} + +impl<'graph> Iterator for ArchetypeAddEdgeRecursiveIter<'graph> +{ + type Item = Option<ArchetypeId>; + + fn next(&mut self) -> Option<Self::Item> + { + let iter = self.stack.last_mut()?; + + let Some(edges) = iter.next() else { + self.stack.pop(); + + return Some(None); + }; + + let Some(add_edge) = edges.add else { + return Some(None); + }; + + let add_edge_archetype = self + .graph + .get_node_by_id(add_edge) + .expect("Add edge archetype not found"); + + self.stack.push(add_edge_archetype.edges.values()); + + Some(Some(add_edge)) + } +} diff --git a/ecs/src/event.rs b/ecs/src/event.rs index 1a4edcc..9cea807 100644 --- a/ecs/src/event.rs +++ b/ecs/src/event.rs @@ -1,96 +1 @@ -use std::any::TypeId; -use std::fmt::Debug; -use std::hash::{DefaultHasher, Hash, Hasher}; - -use seq_macro::seq; - pub mod component; - -pub trait Event: Debug + 'static -{ - /// Returns the ID of this event. - #[must_use] - fn id() -> Id - where - Self: Sized, - { - Id::new::<Self, ()>(None) - } -} - -pub mod start; - -/// The ID of a [`Event`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Id -{ - inner: TypeId, - extra: Option<u64>, -} - -impl Id -{ - #[must_use] - pub fn new<EventT, Extra>(extra: Option<Extra>) -> Self - where - EventT: Event, - Extra: Hash, - { - Self { - inner: TypeId::of::<EventT>(), - extra: extra.map(|extra| { - let mut hasher = DefaultHasher::new(); - - extra.hash(&mut hasher); - - hasher.finish() - }), - } - } -} - -pub trait Ids -{ - type Iter<'a>: Iterator<Item = &'a Id> - where - Self: 'a; - - fn iter(&self) -> Self::Iter<'_>; -} - -/// A sequence of events. -pub trait Sequence -{ - type Ids: Ids; - - fn ids() -> Self::Ids; -} - -macro_rules! impl_sequence { - ($c: tt) => { - seq!(I in 0..=$c { - impl Ids for [Id; $c + 1] - { - type Iter<'a> = std::slice::Iter<'a, Id>; - - fn iter(&self) -> Self::Iter<'_> { - self.into_iter() - } - } - - impl<#(Event~I: Event,)*> Sequence for (#(Event~I,)*) { - type Ids = [Id; $c + 1]; - - fn ids() -> Self::Ids { - [#( - Event~I::id(), - )*] - } - } - }); - }; -} - -seq!(C in 0..=64 { - impl_sequence!(C); -}); diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs index 8b066a7..b4edffc 100644 --- a/ecs/src/event/component.rs +++ b/ecs/src/event/component.rs @@ -6,13 +6,11 @@ use std::marker::PhantomData; use ecs_macros::Component; use crate::component::Component; -use crate::uid::Uid; -use crate::event::{Event, Id}; -use crate::tuple::{ReduceElement as TupleReduceElement, With as TupleWith}; /// Event emitted when: /// a) A entity with component `ComponentT` is spawned. /// b) A component `ComponentT` is added to a entity. +#[derive(Clone, Component)] pub struct Added<ComponentT> where ComponentT: Component, @@ -43,19 +41,10 @@ where } } -impl<ComponentT> Event for Added<ComponentT> -where - ComponentT: Component, -{ - fn id() -> Id - where - Self: Sized, - { - Id::new::<Added<ComponentForId>, _>(Some(ComponentT::id())) - } -} - -/// Event emitted when a `ComponentT` component is removed from a entity. +/// Event emitted when: +/// a) A `ComponentT` component is removed from a entity. +/// b) A entity with component `ComponentT` is despawned. +#[derive(Clone, Component)] pub struct Removed<ComponentT> where ComponentT: Component, @@ -86,39 +75,10 @@ where } } -impl<ComponentT> Event for Removed<ComponentT> -where - ComponentT: Component, -{ - fn id() -> Id - where - Self: Sized, - { - Id::new::<Removed<ComponentForId>, _>(Some(ComponentT::id())) - } -} - -#[must_use] -pub fn create_added_id(component_id: Uid) -> Id -{ - Id::new::<Added<ComponentForId>, _>(Some(component_id)) -} - -#[must_use] -pub fn create_removed_id(component_id: Uid) -> Id +/// Specifies a kind of component event UID. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Kind { - Id::new::<Removed<ComponentForId>, _>(Some(component_id)) + Removed, } - -pub struct TypeTransformComponentsToAddedEvents; - -impl<ComponentT: Component, Accumulator> - TupleReduceElement<Accumulator, TypeTransformComponentsToAddedEvents> for ComponentT -where - Accumulator: TupleWith<Added<Self>>, -{ - type Return = Accumulator::With; -} - -#[derive(Debug, Component)] -struct ComponentForId; diff --git a/ecs/src/event/start.rs b/ecs/src/event/start.rs deleted file mode 100644 index 248dd50..0000000 --- a/ecs/src/event/start.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::event::Event; - -/// Start event. -#[derive(Debug, Clone, Copy)] -pub struct Start; - -impl Event for Start {} diff --git a/ecs/src/extension.rs b/ecs/src/extension.rs index a945d89..42ebef9 100644 --- a/ecs/src/extension.rs +++ b/ecs/src/extension.rs @@ -1,9 +1,7 @@ use crate::component::Sequence as ComponentSequence; -use crate::event::component::TypeTransformComponentsToAddedEvents; -use crate::event::{Event, Sequence as EventSequence}; use crate::sole::Sole; use crate::system::System; -use crate::tuple::Reduce as TupleReduce; +use crate::uid::Uid; use crate::{SoleAlreadyExistsError, World}; /// A collection of systems, entities & soles that can be added to a [`World`]. @@ -27,21 +25,19 @@ impl<'world> Collector<'world> } /// Adds a system to the [`World`]. - pub fn add_system<'this, EventT, SystemImpl>( + pub fn add_system<'this, SystemImpl>( &'this mut self, - event: EventT, + phase_euid: Uid, system: impl System<'this, SystemImpl>, - ) where - EventT: Event, + ) { - self.world.register_system(event, system); + self.world.register_system(phase_euid, system); } /// Adds a entity to the [`World`]. pub fn add_entity<Comps>(&mut self, components: Comps) where - Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>, - Comps::Out: EventSequence, + Comps: ComponentSequence, { self.world.create_entity(components); } diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 78e526f..dc31daf 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -2,32 +2,35 @@ use std::any::{type_name, TypeId}; use std::cell::RefCell; -use std::collections::HashMap; use std::fmt::Debug; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use hashbrown::HashMap; + use crate::actions::Action; use crate::component::storage::Storage as ComponentStorage; -use crate::component::{Component, Sequence as ComponentSequence}; -use crate::entity::CREATE_STATIC_ENTITIES; -use crate::event::component::{ - create_added_id as create_component_added_event_id, - create_removed_id as create_component_removed_event_id, - TypeTransformComponentsToAddedEvents, +use crate::component::{ + Component, + Metadata as ComponentMetadata, + RefSequence as ComponentRefSequence, + Sequence as ComponentSequence, }; -use crate::event::start::Start as StartEvent; -use crate::event::{Event, Id as EventId, Ids, Sequence as EventSequence}; +use crate::entity::CREATE_STATIC_ENTITIES; +use crate::event::component::Kind as ComponentEventKind; use crate::extension::{Collector as ExtensionCollector, Extension}; use crate::lock::{Lock, WriteGuard}; -use crate::query::options::Options as QueryOptions; +use crate::phase::{Phase, START as START_PHASE}; +use crate::query::flexible::Query as FlexibleQuery; +use crate::query::options::{Not, Options as QueryOptions, With}; +use crate::relationship::{ChildOf, DependsOn, Relationship}; use crate::sole::Sole; use crate::stats::Stats; -use crate::system::{System, TypeErased as TypeErasedSystem}; -use crate::tuple::Reduce as TupleReduce; +use crate::system::{System, SystemComponent}; use crate::type_name::TypeName; use crate::uid::{Kind as UidKind, Uid}; +use crate::util::Sortable; pub mod actions; pub mod component; @@ -35,6 +38,7 @@ pub mod entity; pub mod event; pub mod extension; pub mod lock; +pub mod phase; pub mod query; pub mod relationship; pub mod sole; @@ -43,23 +47,21 @@ pub mod system; pub mod tuple; pub mod type_name; pub mod uid; +pub mod util; #[doc(hidden)] pub mod private; -mod archetype; -mod util; - pub use ecs_macros::{Component, Sole}; pub use crate::query::Query; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct World { - systems: Vec<TypeErasedSystem>, data: WorldData, stop: AtomicBool, + is_first_tick: AtomicBool, } impl World @@ -67,10 +69,18 @@ impl World #[must_use] pub fn new() -> Self { - let mut world = Self::default(); + let mut world = Self { + data: WorldData::default(), + stop: AtomicBool::new(false), + is_first_tick: AtomicBool::new(false), + }; world.add_sole(Stats::default()).ok(); + for create_static_entity in CREATE_STATIC_ENTITIES { + create_static_entity(&world); + } + world } @@ -80,8 +90,7 @@ impl World /// Will panic if mutable internal lock cannot be acquired. pub fn create_entity<Comps>(&mut self, components: Comps) -> Uid where - Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>, - Comps::Out: EventSequence, + Comps: ComponentSequence, { let entity_uid = Uid::new_unique(UidKind::Entity); @@ -90,31 +99,31 @@ impl World entity_uid } - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + #[tracing::instrument(skip_all)] #[doc(hidden)] pub fn create_entity_with_uid<Comps>(&self, components: Comps, entity_uid: Uid) where - Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>, - Comps::Out: EventSequence, + Comps: ComponentSequence, { debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - #[allow(unused_variables)] - if let Err(err) = self - .data - .component_storage - .write_nonblock() - .expect("Failed to acquire read-write component storage lock") - .push_entity(entity_uid, components.into_vec()) { - #[cfg(feature = "debug")] - tracing::error!("Failed to create entity: {err}"); + let mut component_storage_lock = self.lock_component_storage_rw(); - return; - }; + if let Err(err) = component_storage_lock.create_entity(entity_uid) { + tracing::warn!("Failed to create entity: {err}"); + return; + }; - for component_added_event_id in <Comps::Out as EventSequence>::ids().iter() { - self.emit_event_by_id(*component_added_event_id); + Self::add_entity_components( + entity_uid, + components.into_array(), + &mut component_storage_lock, + ); + } + + for added_event_id in Comps::added_event_ids() { + self.emit_event_by_id(added_event_id); } } @@ -129,22 +138,29 @@ impl World self.data.sole_storage.insert(sole) } - pub fn register_system<'this, EventT, SystemImpl>( + pub fn register_system<'this, SystemImpl>( &'this mut self, - event: EventT, + phase_euid: Uid, system: impl System<'this, SystemImpl>, - ) where - EventT: Event, + ) { - self.systems.push(system.into_type_erased()); - - self.data - .events - .entry(EventT::id()) - .or_default() - .push(self.systems.len() - 1); + self.create_entity(( + SystemComponent { system: system.into_type_erased() }, + Relationship::<DependsOn, Phase>::new(phase_euid), + )); + } - drop(event); + pub fn register_observer_system<'this, SystemImpl, Event>( + &'this mut self, + system: impl System<'this, SystemImpl>, + event: Event, + ) where + Event: Component, + { + self.create_entity::<(SystemComponent, Event)>(( + SystemComponent { system: system.into_type_erased() }, + event, + )); } /// Adds a extensions. @@ -158,34 +174,129 @@ impl World extension.collect(extension_collector); } - /// Emits a event, running all systems listening to the event for each compatible - /// entity. - /// - /// # Panics - /// Will panic if a system has dissapeared. - pub fn emit<EventT>(&self, event: EventT) + pub fn query<Comps, OptionsT>(&self) -> Query<Comps, OptionsT> where - EventT: Event, + Comps: ComponentRefSequence, + OptionsT: QueryOptions, { - self.emit_event_by_id(EventT::id()); - - drop(event); + Query::new(self) } - pub fn query<Comps, OptionsT>(&self) -> Query<Comps, OptionsT> + pub fn flexible_query<CompMetadata>( + &self, + comp_metadata: CompMetadata, + ) -> FlexibleQuery<CompMetadata> where - Comps: ComponentSequence, - OptionsT: QueryOptions, + CompMetadata: Sortable<Item = ComponentMetadata> + AsRef<[ComponentMetadata]>, { - Query::new(self) + FlexibleQuery::new(self, comp_metadata) + } + + /// Performs a single tick. + /// + /// # Panics + /// Will panic if a internal lock cannot be acquired. + pub fn step(&self) -> StepResult + { + if self.stop.load(Ordering::Relaxed) { + return StepResult::Stop; + } + + if self + .is_first_tick + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + self.query_and_run_systems(*START_PHASE); + } + + self.perform_phases(); + + self.perform_queued_actions(); + + if self.stop.load(Ordering::Relaxed) { + 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"); + + stats.current_tick += 1; + + StepResult::Continue } - /// Peforms the actions that have been queued up using [`Actions`]. + /// Starts a loop which calls [`Self::step`] until the world is stopped. /// /// # Panics - /// Will panic if a mutable internal lock cannot be acquired. - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] - pub fn perform_queued_actions(&self) + /// Will panic if a internal lock cannot be acquired. + pub fn start_loop(&self) + { + while let StepResult::Continue = self.step() {} + } + + fn query_and_run_systems(&self, phase_euid: Uid) + { + let system_comps_query = + self.query::<(&SystemComponent, &Relationship<DependsOn, Phase>), ()>(); + + let system_iter = system_comps_query.iter().filter(|(_, phase_rel)| { + phase_rel + .target_uids() + .any(|target_uid| target_uid == phase_euid) + }); + + for (system_component, _) in system_iter { + // SAFETY: The world lives long enough + unsafe { + system_component.system.run(self); + } + } + } + + fn perform_child_phases(&self, parent_phase_euid: Uid) + { + let phase_query = self.query::<(&Phase, &Relationship<ChildOf, Phase>), ()>(); + + for (child_phase_euid, (_, phase_rel)) in phase_query.iter_with_euids() { + if !phase_rel + .target_uids() + .any(|phase_euid| phase_euid == parent_phase_euid) + { + continue; + } + + self.query_and_run_systems(child_phase_euid); + self.perform_child_phases(child_phase_euid); + } + } + + fn perform_phases(&self) + { + let phase_query = + self.query::<(&Phase,), Not<With<Relationship<ChildOf, Phase>>>>(); + + for (phase_euid, (_,)) in phase_query.iter_with_euids() { + if phase_euid == *START_PHASE { + continue; + } + + self.query_and_run_systems(phase_euid); + self.perform_child_phases(phase_euid); + } + } + + #[tracing::instrument(skip_all)] + fn perform_queued_actions(&self) { let mut active_action_queue = match *self.data.action_queue.active_queue.borrow() { @@ -204,79 +315,89 @@ impl World for action in active_action_queue.drain(..) { match action { - Action::Spawn(components) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - let component_ids = components - .iter() - .map(|component| component.self_id()) - .collect::<Vec<_>>(); - - #[allow(unused_variables)] - if let Err(err) = component_storage_lock - .push_entity(Uid::new_unique(UidKind::Entity), components) + Action::Spawn(components, component_added_event_ids) => { { - #[cfg(feature = "debug")] - tracing::error!("Failed to create entity: {err}"); - - continue; + let mut component_storage_lock = self.lock_component_storage_rw(); + + let new_entity_uid = Uid::new_unique(UidKind::Entity); + + if let Err(err) = + component_storage_lock.create_entity(new_entity_uid) + { + tracing::warn!("Failed to create entity: {err}"); + continue; + }; + + Self::add_entity_components( + new_entity_uid, + components, + &mut component_storage_lock, + ); } - drop(component_storage_lock); - if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } - for component_id in component_ids { - self.emit_event_by_id(create_component_added_event_id( - component_id, - )); + for comp_added_event_id in component_added_event_ids.ids { + self.emit_event_by_id(comp_added_event_id); } } - Action::AddComponents(entity_uid, components) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - let component_ids = components - .iter() - .map(|component| component.self_id()) - .collect::<Vec<_>>(); - - component_storage_lock - .add_components_to_entity(entity_uid, components); + Action::Despawn(entity_uid) => { + self.despawn_entity(entity_uid, &mut has_swapped_active_queue); + } + Action::AddComponents( + entity_uid, + components, + component_added_event_ids, + ) => { + { + let mut component_storage_lock = self.lock_component_storage_rw(); - drop(component_storage_lock); + Self::add_entity_components( + entity_uid, + components, + &mut component_storage_lock, + ); + } if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } - for component_id in component_ids { - self.emit_event_by_id(create_component_added_event_id( - component_id, - )); + // TODO: Fix that events are emitted for components that haven't been + // added because a error occurred (for example, the entity already has + // the component) + for comp_added_event_id in component_added_event_ids.ids { + self.emit_event_by_id(comp_added_event_id); } } - Action::RemoveComponents(entity_uid, components_metadata) => { - let mut component_storage_lock = self.lock_component_storage_rw(); - - component_storage_lock.remove_components_from_entity( - entity_uid, - components_metadata - .iter() - .map(|component_metadata| component_metadata.id), - ); - - drop(component_storage_lock); + Action::RemoveComponents( + entity_uid, + components_metadata, + component_removed_event_ids, + ) => { + { + let mut component_storage_lock = self.lock_component_storage_rw(); + + Self::remove_entity_components( + entity_uid, + components_metadata + .iter() + .map(|comp_metadata| comp_metadata.id), + &mut component_storage_lock, + ); + } if !has_swapped_active_queue { self.swap_event_queue(&mut has_swapped_active_queue); } - for component_metadata in components_metadata { - self.emit_event_by_id(create_component_removed_event_id( - component_metadata.id, - )); + // TODO: Fix that events are emitted for components that haven't been + // removed because a error occurred (for example, the entity does not + // have the component) + for comp_removed_event_id in component_removed_event_ids.ids { + self.emit_event_by_id(comp_removed_event_id); } } Action::Stop => { @@ -286,60 +407,89 @@ impl World } } - /// A event loop which runs until a stop is issued with [`Flags::stop`]. Before the - /// loop begins, [`StartEvent`] is emitted. - /// - /// # Panics - /// Will panic if a internal lock cannot be acquired. - pub fn event_loop<EventSeq: EventSequence>(&self) + #[tracing::instrument(skip_all)] + fn despawn_entity(&self, entity_uid: Uid, has_swapped_active_queue: &mut bool) { - for create_static_entity in CREATE_STATIC_ENTITIES { - create_static_entity(self); - } - - self.emit(StartEvent); - - let event_seq = EventSeq::ids(); - - loop { - for event_id in event_seq.iter() { - self.emit_event_by_id(*event_id); - } - - self.perform_queued_actions(); + let mut component_storage_lock = self.lock_component_storage_rw(); - if self.stop.load(Ordering::Relaxed) { - break; + let components = match component_storage_lock.remove_entity(entity_uid) { + Ok(components) => components, + Err(err) => { + tracing::error!("Failed to despawn entity: {err}"); + return; } + }; - 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 component_removed_event_uids = components + .iter() + .map(|component| { + component + .component + .read_nonblock() + .unwrap_or_else(|_| { + panic!( + "Failed to acquire read-only {} component lock", + component.name + ) + }) + .get_event_uid(ComponentEventKind::Removed) + }) + .collect::<Vec<_>>(); + + drop(component_storage_lock); + + if !*has_swapped_active_queue { + self.swap_event_queue(has_swapped_active_queue); + } - stats.current_tick += 1; + for comp_removed_event_id in component_removed_event_uids { + self.emit_event_by_id(comp_removed_event_id); } } - fn emit_event_by_id(&self, event_id: EventId) + fn add_entity_components( + entity_uid: Uid, + components: impl IntoIterator<Item = Box<dyn Component>>, + component_storage: &mut ComponentStorage, + ) { - let Some(system_indices) = self.data.events.get(&event_id) else { - return; - }; + for component in components.into_iter() { + if let Err(err) = + component_storage.add_entity_component(entity_uid, component) + { + tracing::error!("Failed to add component to entity: {err}"); + } + } + } - for system_index in system_indices { - let system = self.systems.get(*system_index).unwrap(); + fn remove_entity_components( + entity_uid: Uid, + component_ids: impl IntoIterator<Item = Uid>, + component_storage: &mut ComponentStorage, + ) + { + for component_id in component_ids.into_iter() { + if let Err(err) = + component_storage.remove_entity_component(entity_uid, component_id) + { + tracing::error!("Failed to remove component to entity: {err}"); + } + } + } - // SAFETY: The world lives long enough + fn emit_event_by_id(&self, event_id: Uid) + { + let query = self.flexible_query([ + ComponentMetadata::of::<SystemComponent>(), + ComponentMetadata { id: event_id, is_optional: false }, + ]); + + for (system,) in query + .iter::<()>() + .into_component_iter::<(&SystemComponent,)>(self) + { unsafe { - system.run(self); + system.system.run(self); } } } @@ -365,10 +515,27 @@ impl World } } +impl Default for World +{ + fn default() -> Self + { + Self::new() + } +} + +/// The result of calling [`World::step`]. +pub enum StepResult +{ + /// Another step can be made. + Continue, + + /// The world have been stopped so no step can be made again. + Stop, +} + #[derive(Debug, Default)] pub struct WorldData { - events: HashMap<EventId, Vec<usize>>, component_storage: Arc<Lock<ComponentStorage>>, sole_storage: SoleStorage, action_queue: Arc<ActionQueue>, @@ -496,8 +663,7 @@ impl Drop for SoleStorage for sole in self.storage.values_mut() { if sole.drop_last { - #[cfg(feature = "debug")] - tracing::debug!( + tracing::trace!( "Sole {} pushed to dropping last queue", sole.sole.read_nonblock().unwrap().type_name() ); @@ -505,9 +671,7 @@ impl Drop for SoleStorage soles_to_drop_last.push(sole); continue; } - - #[cfg(feature = "debug")] - tracing::debug!( + tracing::trace!( "Dropping sole {}", sole.sole.read_nonblock().unwrap().type_name() ); @@ -518,8 +682,7 @@ impl Drop for SoleStorage } for sole in &mut soles_to_drop_last { - #[cfg(feature = "debug")] - tracing::debug!( + tracing::trace!( "Dropping sole {} last", sole.sole.read_nonblock().unwrap().type_name() ); diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs index fbc6842..c700098 100644 --- a/ecs/src/lock.rs +++ b/ecs/src/lock.rs @@ -1,6 +1,13 @@ -use std::mem::transmute; +use std::mem::{forget, transmute}; use std::ops::{Deref, DerefMut}; -use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError}; + +use parking_lot::{ + MappedRwLockReadGuard, + MappedRwLockWriteGuard, + RwLock, + RwLockReadGuard, + RwLockWriteGuard, +}; use crate::type_name::TypeName; @@ -27,12 +34,8 @@ where /// Returns `Err` if unavailable (A mutable handle is hold). pub fn read_nonblock(&self) -> Result<ReadGuard<Value>, Error> { - let guard = self.inner.try_read().or_else(|err| match err { - TryLockError::WouldBlock => Err(Error::Unavailable), - TryLockError::Poisoned(poison_err) => Ok(poison_err.into_inner()), - })?; + let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?; - #[cfg(feature = "debug")] tracing::trace!("Acquired lock to value of type {}", guard.type_name()); Ok(ReadGuard { inner: guard }) @@ -44,12 +47,8 @@ where /// Returns `Err` if unavailable (A mutable or immutable handle is hold). pub fn write_nonblock(&self) -> Result<WriteGuard<Value>, Error> { - let guard = self.inner.try_write().or_else(|err| match err { - TryLockError::WouldBlock => Err(Error::Unavailable), - TryLockError::Poisoned(poison_err) => Ok(poison_err.into_inner()), - })?; + let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?; - #[cfg(feature = "debug")] tracing::trace!( "Acquired mutable lock to value of type {}", guard.type_name() @@ -60,17 +59,18 @@ where pub fn into_inner(self) -> Value { - self.inner - .into_inner() - .unwrap_or_else(PoisonError::into_inner) + self.inner.into_inner() } } #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Lock is unavailable")] - Unavailable, + #[error("Lock is unavailable for reading")] + ReadUnavailable, + + #[error("Lock is unavailable for writing")] + WriteUnavailable, } #[derive(Debug)] @@ -85,6 +85,23 @@ impl<'guard, Value> ReadGuard<'guard, Value> where Value: TypeName, { + pub fn map<NewValue>( + self, + func: impl FnOnce(&Value) -> &NewValue, + ) -> MappedReadGuard<'guard, NewValue> + where + NewValue: TypeName, + { + // 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), + } + } + /// Converts the `ReadGuard` to a `ReadGuard` with a possibly longer lifetime. /// /// # Safety @@ -115,12 +132,41 @@ where { fn drop(&mut self) { - #[cfg(feature = "debug")] tracing::trace!("Dropped lock to value of type {}", self.type_name()); } } #[derive(Debug)] +pub struct MappedReadGuard<'guard, Value> +where + Value: TypeName, +{ + inner: MappedRwLockReadGuard<'guard, Value>, +} + +impl<'guard, Value> Deref for MappedReadGuard<'guard, Value> +where + Value: TypeName, +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl<'guard, Value> Drop for MappedReadGuard<'guard, Value> +where + Value: TypeName, +{ + fn drop(&mut self) + { + tracing::trace!("Dropped mapped lock to value of type {}", self.type_name()); + } +} + +#[derive(Debug)] pub struct WriteGuard<'guard, Value> where Value: TypeName, @@ -128,6 +174,28 @@ where inner: RwLockWriteGuard<'guard, Value>, } +impl<'guard, Value> WriteGuard<'guard, Value> +where + Value: TypeName, +{ + pub fn map<NewValue>( + self, + func: impl FnOnce(&mut Value) -> &mut NewValue, + ) -> MappedWriteGuard<'guard, NewValue> + where + NewValue: TypeName, + { + // 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), + } + } +} + impl<'guard, Value> Deref for WriteGuard<'guard, Value> where Value: TypeName, @@ -156,7 +224,49 @@ where { fn drop(&mut self) { - #[cfg(feature = "debug")] tracing::trace!("Dropped mutable lock to value of type {}", self.type_name()); } } + +#[derive(Debug)] +pub struct MappedWriteGuard<'guard, Value> +where + Value: TypeName, +{ + inner: MappedRwLockWriteGuard<'guard, Value>, +} + +impl<'guard, Value> Deref for MappedWriteGuard<'guard, Value> +where + Value: TypeName, +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl<'guard, Value> DerefMut for MappedWriteGuard<'guard, Value> +where + Value: TypeName, +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.inner + } +} + +impl<'guard, Value> Drop for MappedWriteGuard<'guard, Value> +where + Value: TypeName, +{ + fn drop(&mut self) + { + tracing::trace!( + "Dropped mapped mutable lock to value of type {}", + self.type_name() + ); + } +} diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs new file mode 100644 index 0000000..b8660f2 --- /dev/null +++ b/ecs/src/phase.rs @@ -0,0 +1,15 @@ +use ecs_macros::Component; + +use crate::relationship::{ChildOf, Relationship}; +use crate::static_entity; + +#[derive(Debug, Default, Clone, Copy, Component)] +pub struct Phase; + +static_entity!(pub START, (Phase,)); + +static_entity!(pub PRE_UPDATE, (Phase,)); + +static_entity!(pub UPDATE, (Phase, <Relationship<ChildOf, Phase>>::new(*PRE_UPDATE))); + +static_entity!(pub PRESENT, (Phase, <Relationship<ChildOf, Phase>>::new(*UPDATE))); diff --git a/ecs/src/query.rs b/ecs/src/query.rs index f3318bd..ca9345c 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -1,115 +1,84 @@ -use std::iter::{Filter, Flatten, Map}; use std::marker::PhantomData; -use crate::component::storage::{ - Archetype, - ArchetypeEntity, - ArchetypeRefIter, - EntityIter, - Storage as ComponentStorage, +use crate::component::RefSequence as ComponentRefSequence; +use crate::query::flexible::{ + EntityHandle, + Iter as FlexibleQueryIter, + Query as FlexibleQuery, }; -use crate::component::{ - Component, - Metadata as ComponentMetadata, - Sequence as ComponentSequence, -}; -use crate::lock::{ReadGuard, WriteGuard}; use crate::query::options::Options; -use crate::system::{ - NoInitParamFlag as NoInitSystemParamFlag, - Param as SystemParam, - System, -}; +use crate::system::{Param as SystemParam, System}; use crate::uid::Uid; -use crate::{EntityComponent, World}; +use crate::World; +pub mod flexible; pub mod options; #[derive(Debug)] pub struct Query<'world, Comps, OptionsT = ()> where - Comps: ComponentSequence, + Comps: ComponentRefSequence, { world: &'world World, - component_storage: ReadGuard<'world, ComponentStorage>, + inner: FlexibleQuery<'world, Comps::Metadata>, _pd: PhantomData<(Comps, OptionsT)>, } impl<'world, Comps, OptionsT> Query<'world, Comps, OptionsT> where - Comps: ComponentSequence, + Comps: ComponentRefSequence, OptionsT: Options, { - /// Iterates over the entities matching this query. + /// Iterates over the entities matching this query, the iterator item being the entity + /// components. #[must_use] - pub fn iter_mut( - &'world self, - ) -> ComponentIterMut<'world, Comps, QueryEntityIter<'world>> + pub fn iter<'query>( + &'query self, + ) -> ComponentIter<'query, 'world, Comps, FlexibleQueryIter<'query>> { - #[cfg(feature = "debug")] - tracing::debug!("Searching for {}", std::any::type_name::<Comps>()); + tracing::trace!("Searching for {}", std::any::type_name::<Comps>()); - #[allow(clippy::map_flatten)] - ComponentIterMut { + ComponentIter { world: self.world, - entities: self - .component_storage - .find_entities(Comps::metadata()) - .map(Archetype::entities as ComponentIterMapFn) - .flatten() - .filter(|entity| OptionsT::entity_filter(entity.components())), + iter: self.inner.iter::<OptionsT>(), comps_pd: PhantomData, } } - /// Iterates over the entities matching this query. + /// Iterates over the entities matching this query, the iterator item being the entity + /// [`Uid`] and the entity components. #[must_use] - pub fn iter(&'world self) -> ComponentIter<'world, Comps, QueryEntityIter<'world>> + pub fn iter_with_euids<'query>( + &'query self, + ) -> ComponentAndEuidIter<'query, 'world, Comps, FlexibleQueryIter<'query>> { - #[cfg(feature = "debug")] - tracing::debug!("Searching for {}", std::any::type_name::<Comps>()); + tracing::trace!("Searching for {}", std::any::type_name::<Comps>()); - #[allow(clippy::map_flatten)] - ComponentIter { + ComponentAndEuidIter { world: self.world, - entities: self - .component_storage - .find_entities(Comps::metadata()) - .map(Archetype::entities as ComponentIterMapFn) - .flatten() - .filter(|entity| OptionsT::entity_filter(entity.components())), + iter: self.inner.iter::<OptionsT>(), comps_pd: PhantomData, } } - /// Iterates over the entities matching this query and has the provided extra - /// component. + /// Iterates over the entities matching this query using the iterator returned by + /// `func`. + /// + /// This function exists so that a custom [`EntityHandle`] iterator can be given to + /// [`ComponentIter`] without giving the user access to a reference to the [`World`]. #[must_use] - pub fn iter_with_extra_comps( - &'world self, - extra_components: impl IntoIterator<Item = ComponentMetadata>, - ) -> ComponentIter<'world, Comps, QueryEntityIter<'world>> + pub fn iter_with<'query, OutIter>( + &'query self, + func: impl FnOnce(FlexibleQueryIter<'query>) -> OutIter, + ) -> ComponentIter<'query, 'world, Comps, OutIter> + where + OutIter: Iterator<Item = EntityHandle<'query>>, { - #[cfg(feature = "debug")] - tracing::debug!( - "Searching for {} + extra components", - std::any::type_name::<Comps>() - ); + tracing::trace!("Searching for {}", std::any::type_name::<Comps>()); - #[allow(clippy::map_flatten)] ComponentIter { world: self.world, - entities: self - .component_storage - .find_entities( - Comps::metadata() - .into_iter() - .chain(extra_components) - .collect::<Vec<_>>(), - ) - .map(Archetype::entities as ComponentIterMapFn) - .flatten() - .filter(|entity| OptionsT::entity_filter(entity.components())), + iter: func(self.inner.iter::<OptionsT>()), comps_pd: PhantomData, } } @@ -118,51 +87,39 @@ where #[must_use] pub fn get_entity_uid(&self, entity_index: usize) -> Option<Uid> { - Some( - self.component_storage - .find_entities(Comps::metadata()) - .flat_map(|archetype| archetype.entities()) - .filter(|entity| OptionsT::entity_filter(entity.components())) - .nth(entity_index)? - .uid(), - ) + Some(self.inner.iter::<OptionsT>().nth(entity_index)?.uid()) } pub(crate) fn new(world: &'world World) -> Self { Self { world, - component_storage: world - .data - .component_storage - .read_nonblock() - .expect("Failed to acquire read-only component storage lock"), + inner: world.flexible_query(Comps::metadata()), _pd: PhantomData, } } } -impl<'world, Comps, OptionsT> IntoIterator for &'world Query<'world, Comps, OptionsT> +impl<'query, 'world, Comps, OptionsT> IntoIterator + for &'query Query<'world, Comps, OptionsT> where - Comps: ComponentSequence, + Comps: ComponentRefSequence + 'world, OptionsT: Options, { - type IntoIter = ComponentIterMut<'world, Comps, QueryEntityIter<'world>>; - type Item = Comps::MutRefs<'world>; + type IntoIter = ComponentIter<'query, 'world, Comps, FlexibleQueryIter<'query>>; + type Item = Comps::Handles<'query>; fn into_iter(self) -> Self::IntoIter { - self.iter_mut() + self.iter() } } -unsafe impl<'world, Comps, OptionsT> SystemParam<'world> - for Query<'world, Comps, OptionsT> +impl<'world, Comps, OptionsT> SystemParam<'world> for Query<'world, Comps, OptionsT> where - Comps: ComponentSequence, + Comps: ComponentRefSequence, OptionsT: Options, { - type Flags = NoInitSystemParamFlag; type Input = (); fn initialize<SystemImpl>( @@ -181,93 +138,78 @@ where } } -type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> EntityIter<'a>; - -type ComponentIterFilterFn = for<'a, 'b> fn(&'a &'b ArchetypeEntity) -> bool; - -type QueryEntityIter<'world> = Filter< - Flatten<Map<ArchetypeRefIter<'world>, ComponentIterMapFn>>, - ComponentIterFilterFn, ->; - -pub struct ComponentIterMut<'world, Comps, EntityIter> +pub struct ComponentIter<'query, 'world, Comps, EntityHandleIter> where - EntityIter: Iterator<Item = &'world ArchetypeEntity>, + EntityHandleIter: Iterator<Item = EntityHandle<'query>>, { world: &'world World, - entities: EntityIter, + iter: EntityHandleIter, comps_pd: PhantomData<Comps>, } -impl<'world, Comps, EntityIter> Iterator for ComponentIterMut<'world, Comps, EntityIter> +impl<'query, 'world, Comps, EntityHandleIter> + ComponentIter<'query, 'world, Comps, EntityHandleIter> where - Comps: ComponentSequence + 'world, - EntityIter: Iterator<Item = &'world ArchetypeEntity>, + Comps: ComponentRefSequence + 'world, + EntityHandleIter: Iterator<Item = EntityHandle<'query>>, + 'world: 'query, { - type Item = Comps::MutRefs<'world>; + pub(crate) fn new(world: &'world World, iter: EntityHandleIter) -> Self + { + Self { world, iter, comps_pd: PhantomData } + } +} + +impl<'query, 'world, Comps, EntityHandleIter> Iterator + for ComponentIter<'query, 'world, Comps, EntityHandleIter> +where + Comps: ComponentRefSequence + 'world, + EntityHandleIter: Iterator<Item = EntityHandle<'query>>, + 'world: 'query, +{ + type Item = Comps::Handles<'query>; fn next(&mut self) -> Option<Self::Item> { - Some(Comps::from_components_mut( - self.entities.next()?.components().iter(), + let entity_handle = self.iter.next()?; + + Some(Comps::from_components( + entity_handle.components(), + |component_uid| entity_handle.get_component_index(component_uid), self.world, - lock_component_rw, )) } } -fn lock_component_rw( - entity_component: &EntityComponent, -) -> WriteGuard<'_, Box<dyn Component>> -{ - entity_component - .component - .write_nonblock() - .unwrap_or_else(|_| { - panic!( - "Failed to acquire read-write lock to component {}", - entity_component.name - ); - }) -} - -pub struct ComponentIter<'world, Comps, EntityIter> +pub struct ComponentAndEuidIter<'query, 'world, Comps, EntityHandleIter> where - EntityIter: Iterator<Item = &'world ArchetypeEntity>, + EntityHandleIter: Iterator<Item = EntityHandle<'query>>, { world: &'world World, - entities: EntityIter, + iter: EntityHandleIter, comps_pd: PhantomData<Comps>, } -impl<'world, Comps, EntityIter> Iterator for ComponentIter<'world, Comps, EntityIter> +impl<'query, 'world, Comps, EntityHandleIter> Iterator + for ComponentAndEuidIter<'query, 'world, Comps, EntityHandleIter> where - Comps: ComponentSequence + 'world, - EntityIter: Iterator<Item = &'world ArchetypeEntity>, + Comps: ComponentRefSequence + 'world, + EntityHandleIter: Iterator<Item = EntityHandle<'query>>, + 'world: 'query, { - type Item = Comps::Refs<'world>; + type Item = (Uid, Comps::Handles<'query>); fn next(&mut self) -> Option<Self::Item> { - Some(Comps::from_components( - self.entities.next()?.components().iter(), - self.world, - lock_component_ro, + let entity_handle = self.iter.next()?; + + Some(( + entity_handle.uid(), + Comps::from_components( + entity_handle.components(), + |component_uid| entity_handle.get_component_index(component_uid), + self.world, + ), )) } } - -fn lock_component_ro( - entity_component: &EntityComponent, -) -> ReadGuard<'_, Box<dyn Component>> -{ - entity_component - .component - .read_nonblock() - .unwrap_or_else(|_| { - panic!( - "Failed to acquire read-write lock to component {}", - entity_component.name - ); - }) -} diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs new file mode 100644 index 0000000..129ee66 --- /dev/null +++ b/ecs/src/query/flexible.rs @@ -0,0 +1,140 @@ +//! Low-level querying. +use std::iter::{repeat_n, Filter, Flatten, Map, RepeatN, Zip}; + +use crate::component::storage::archetype::{Archetype, ArchetypeEntity, EntityIter}; +use crate::component::storage::{ArchetypeRefIter, Storage as ComponentStorage}; +use crate::component::{ + Metadata as ComponentMetadata, + RefSequence as ComponentRefSequence, +}; +use crate::lock::ReadGuard; +use crate::query::options::Options; +use crate::query::ComponentIter; +use crate::uid::Uid; +use crate::util::Sortable; +use crate::{EntityComponent, World}; + +/// Low-level entity query structure. +#[derive(Debug)] +pub struct Query<'world, CompMetadata> +where + CompMetadata: Sortable<Item = ComponentMetadata> + AsRef<[ComponentMetadata]>, +{ + component_storage: ReadGuard<'world, ComponentStorage>, + comp_metadata: CompMetadata, +} + +impl<'world, CompMetadata> Query<'world, CompMetadata> +where + CompMetadata: Sortable<Item = ComponentMetadata> + AsRef<[ComponentMetadata]>, +{ + /// Iterates over the entities matching this query. + #[must_use] + pub fn iter<'query, OptionsT: Options>(&'query self) -> Iter<'query> + { + Iter { + iter: self + .component_storage + .iter_archetypes_with_comps(&self.comp_metadata) + .map( + (|archetype| { + repeat_n(archetype, archetype.entity_cnt()) + .zip(archetype.entities()) + }) as ComponentIterMapFn, + ) + .flatten() + .filter(|(_, entity)| OptionsT::entity_filter(&entity.components)), + } + } + + pub(crate) fn new(world: &'world World, mut comp_metadata: CompMetadata) -> Self + { + comp_metadata.sort_by_key_b(|metadata| metadata.id); + + Self { + component_storage: world + .data + .component_storage + .read_nonblock() + .expect("Failed to acquire read-only component storage lock"), + comp_metadata, + } + } +} + +pub struct Iter<'query> +{ + iter: QueryEntityIter<'query>, +} + +impl<'query> Iter<'query> +{ + /// Converts this iterator into a [`ComponentIter`]. + /// + /// Note: The matching entities of this iterator should have all of the non-[`Option`] + /// components in `Comps`, otherwise iterating the [`ComponentIter`] will cause a + /// panic. + #[must_use] + #[inline] + pub fn into_component_iter<'world, Comps>( + self, + world: &'world World, + ) -> ComponentIter<'query, 'world, Comps, Self> + where + Comps: ComponentRefSequence + 'world, + 'world: 'query, + { + ComponentIter::new(world, self) + } +} + +impl<'query> Iterator for Iter<'query> +{ + type Item = EntityHandle<'query>; + + fn next(&mut self) -> Option<Self::Item> + { + let (archetype, entity) = self.iter.next()?; + + Some(EntityHandle { archetype, entity }) + } +} + +pub struct EntityHandle<'query> +{ + archetype: &'query Archetype, + entity: &'query ArchetypeEntity, +} + +impl<'query> EntityHandle<'query> +{ + /// Returns the [`Uid`] of this entity. + #[inline] + pub fn uid(&self) -> Uid + { + self.entity.uid + } + + #[inline] + pub fn components(&self) -> &'query [EntityComponent] + { + &self.entity.components + } + + #[inline] + pub fn get_component_index(&self, component_uid: Uid) -> Option<usize> + { + self.archetype.get_index_for_component(component_uid) + } +} + +type ComponentIterMapFn = + for<'a> fn(&'a Archetype) -> Zip<RepeatN<&'a Archetype>, EntityIter<'a>>; + +type ComponentIterFilterFn = + for<'a, 'b> fn(&'a (&'b Archetype, &'b ArchetypeEntity)) -> bool; + +type QueryEntityIter<'query> = Filter< + Flatten<Map<ArchetypeRefIter<'query>, ComponentIterMapFn>>, + ComponentIterFilterFn, +>; diff --git a/ecs/src/query/options.rs b/ecs/src/query/options.rs index bbbe0a8..772d091 100644 --- a/ecs/src/query/options.rs +++ b/ecs/src/query/options.rs @@ -1,22 +1,19 @@ -use std::collections::HashSet; use std::marker::PhantomData; +use hashbrown::HashSet; + use crate::component::Component; use crate::EntityComponent; /// Query options. pub trait Options { - fn entity_filter<'component>( - components: impl IntoIterator<Item = &'component EntityComponent>, - ) -> bool; + fn entity_filter<'component>(components: &'component [EntityComponent]) -> bool; } impl Options for () { - fn entity_filter<'component>( - _: impl IntoIterator<Item = &'component EntityComponent>, - ) -> bool + fn entity_filter<'component>(_components: &'component [EntityComponent]) -> bool { true } @@ -33,9 +30,7 @@ impl<ComponentT> Options for With<ComponentT> where ComponentT: Component, { - fn entity_filter<'component>( - components: impl IntoIterator<Item = &'component EntityComponent>, - ) -> bool + fn entity_filter<'component>(components: &'component [EntityComponent]) -> bool { let ids_set = components .into_iter() @@ -57,9 +52,7 @@ impl<OptionsT> Options for Not<OptionsT> where OptionsT: Options, { - fn entity_filter<'component>( - components: impl IntoIterator<Item = &'component EntityComponent>, - ) -> bool + fn entity_filter<'component>(components: &'component [EntityComponent]) -> bool { !OptionsT::entity_filter(components) } diff --git a/ecs/src/relationship.rs b/ecs/src/relationship.rs index 0ebd9c2..54dc0cd 100644 --- a/ecs/src/relationship.rs +++ b/ecs/src/relationship.rs @@ -6,10 +6,11 @@ use ecs_macros::Component; use crate::component::storage::Storage as ComponentStorage; use crate::component::{ Component, + FromLockedOptional as FromLockedOptionalComponent, FromOptional as FromOptionalComponent, FromOptionalMut as FromOptionalMutComponent, }; -use crate::lock::ReadGuard; +use crate::lock::{Error as LockError, Lock, ReadGuard, WriteGuard}; use crate::system::{ComponentRef, ComponentRefMut}; use crate::uid::{Kind as UidKind, Uid}; use crate::World; @@ -103,6 +104,59 @@ where } } +impl<'rel_comp, Kind, ComponentT> FromOptionalMutComponent<'rel_comp> + for Option<RelationMut<'rel_comp, Kind, ComponentT>> +where + ComponentT: Component, +{ + fn from_optional_mut_component( + optional_component: Option<WriteGuard<'rel_comp, Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Self + { + optional_component.map(|component| { + RelationMut::from_optional_mut_component(Some(component), world) + }) + } +} + +impl<'rel_comp, Kind, ComponentT> FromLockedOptionalComponent<'rel_comp> + for RelationMut<'rel_comp, Kind, ComponentT> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'rel_comp crate::lock::Lock<Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Result<Self, LockError> + { + Ok(Self::from_optional_mut_component( + optional_component + .map(|lock| lock.write_nonblock()) + .transpose()?, + world, + )) + } +} + +impl<'rel_comp, Kind, ComponentT> FromLockedOptionalComponent<'rel_comp> + for Option<RelationMut<'rel_comp, Kind, ComponentT>> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'rel_comp Lock<Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Result<Self, crate::lock::Error> + { + optional_component + .map(|component| { + RelationMut::from_locked_optional_component(Some(component), world) + }) + .transpose() + } +} + impl<'rel_comp, Kind, ComponentT> RelationMut<'rel_comp, Kind, ComponentT> where ComponentT: Component, @@ -120,14 +174,15 @@ where let archetype = self.component_storage_lock.get_entity_archetype(*target)?; let entity = archetype - .get_entity(*target) + .get_entity_by_id(*target) .expect("Target entity is gone from archetype"); let component_index = archetype.get_index_for_component(ComponentT::id())?; let component = ComponentRefMut::new( entity - .get_component(component_index)? + .components + .get(component_index)? .component .write_nonblock() .unwrap_or_else(|_| { @@ -307,6 +362,58 @@ where } } +impl<'rel_comp, Kind, ComponentT> FromOptionalComponent<'rel_comp> + for Option<Relation<'rel_comp, Kind, ComponentT>> +where + ComponentT: Component, +{ + fn from_optional_component( + optional_component: Option<ReadGuard<'rel_comp, Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Self + { + optional_component + .map(|component| Relation::from_optional_component(Some(component), world)) + } +} + +impl<'rel_comp, Kind, ComponentT> FromLockedOptionalComponent<'rel_comp> + for Relation<'rel_comp, Kind, ComponentT> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'rel_comp Lock<Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Result<Self, LockError> + { + Ok(Self::from_optional_component( + optional_component + .map(|lock| lock.read_nonblock()) + .transpose()?, + world, + )) + } +} + +impl<'rel_comp, Kind, ComponentT> FromLockedOptionalComponent<'rel_comp> + for Option<Relation<'rel_comp, Kind, ComponentT>> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'rel_comp Lock<Box<dyn Component>>>, + world: &'rel_comp World, + ) -> Result<Self, crate::lock::Error> + { + optional_component + .map(|component| { + Relation::from_locked_optional_component(Some(component), world) + }) + .transpose() + } +} + impl<'rel_comp, Kind, ComponentT> Relation<'rel_comp, Kind, ComponentT> where ComponentT: Component, @@ -324,14 +431,15 @@ where let archetype = self.component_storage_lock.get_entity_archetype(*target)?; let entity = archetype - .get_entity(*target) + .get_entity_by_id(*target) .expect("Target entity is gone from archetype"); let component_index = archetype.get_index_for_component(ComponentT::id())?; let component = ComponentRef::new( entity - .get_component(component_index)? + .components + .get(component_index)? .component .read_nonblock() .unwrap_or_else(|_| { @@ -365,6 +473,12 @@ where } } + pub fn target_uids(&self) -> impl Iterator<Item = Uid> + '_ + { + (0..self.target_count()) + .map_while(|target_index| self.get_target(target_index).copied()) + } + /// Returns a iterator of the components of the targets of this relationship. #[must_use] pub fn iter(&self) -> TargetComponentIter<'_, 'rel_comp, Kind, ComponentT> @@ -416,3 +530,11 @@ where self.relation.get(index) } } + +/// Relationship kind denoting a dependency to another entity +#[derive(Debug, Default, Clone, Copy)] +pub struct DependsOn; + +/// Relationship kind denoting being the child of another entity. +#[derive(Debug, Default, Clone, Copy)] +pub struct ChildOf; diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs index 084a06b..a35b520 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::{NoInitParamFlag, Param as SystemParam, System}; +use crate::system::{Param as SystemParam, System}; use crate::type_name::TypeName; use crate::World; @@ -88,11 +88,10 @@ where } } -unsafe impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT> +impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT> where SoleT: Sole, { - type Flags = NoInitParamFlag; type Input = (); fn initialize<SystemImpl>( diff --git a/ecs/src/system.rs b/ecs/src/system.rs index 046d25b..b410d8f 100644 --- a/ecs/src/system.rs +++ b/ecs/src/system.rs @@ -3,17 +3,25 @@ use std::convert::Infallible; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; -use std::panic::{RefUnwindSafe, UnwindSafe}; +use ecs_macros::Component; use seq_macro::seq; use crate::component::{ Component, + FromLockedOptional as FromLockedOptionalComponent, FromOptional as FromOptionalComponent, FromOptionalMut as FromOptionalMutComponent, }; -use crate::lock::{ReadGuard, WriteGuard}; -use crate::tuple::{ReduceElement as TupleReduceElement, With as TupleWith}; +use crate::lock::{ + Error as LockError, + Lock, + MappedReadGuard, + MappedWriteGuard, + ReadGuard, + WriteGuard, +}; +use crate::tuple::{ReduceElement as TupleReduceElement, Tuple}; use crate::World; pub mod stateful; @@ -47,8 +55,8 @@ macro_rules! impl_system { impl<'world, Func, #(TParam~I,)*> System<'world, fn(#(TParam~I,)*)> for Func where - Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, - #(TParam~I: Param<'world, Flags = NoInitParamFlag>,)* + Func: Fn(#(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Input = ()>,)* { type Input = Infallible; @@ -121,7 +129,7 @@ pub trait Into<Impl> pub struct TypeErased { - data: Box<dyn Any + RefUnwindSafe + UnwindSafe>, + data: Box<dyn Any>, run: Box<TypeErasedRunFn>, } @@ -149,13 +157,12 @@ impl Debug for TypeErased } /// Function in [`TypeErased`] used to run the system. -type TypeErasedRunFn = dyn Fn(&dyn Any, &World) + RefUnwindSafe + UnwindSafe; +type TypeErasedRunFn = dyn Fn(&dyn Any, &World); /// A parameter to a [`System`]. -pub unsafe trait Param<'world> +pub trait Param<'world> { type Input; - type Flags; fn initialize<SystemImpl>( system: &mut impl System<'world, SystemImpl>, @@ -168,8 +175,6 @@ pub unsafe trait Param<'world> ) -> Self; } -pub struct NoInitParamFlag {} - /// A type which can be used as input to a [`System`]. pub trait Input: 'static {} @@ -179,9 +184,9 @@ pub struct ParamWithInputFilter; impl<InputT: Input, Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for InputT where - Accumulator: TupleWith<Self>, + Accumulator: Tuple, { - type Return = Accumulator::With; + type Return = Accumulator::WithElementAtEnd<Self>; } impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for () @@ -192,7 +197,7 @@ impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for () #[derive(Debug)] pub struct ComponentRefMut<'a, ComponentT: Component> { - inner: WriteGuard<'a, Box<dyn Component>>, + inner: MappedWriteGuard<'a, ComponentT>, _ph: PhantomData<ComponentT>, } @@ -200,7 +205,19 @@ impl<'a, ComponentT: Component> ComponentRefMut<'a, ComponentT> { pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Component>>) -> Self { - Self { inner, _ph: PhantomData } + Self { + inner: inner.map(|component| { + let component_type_name = component.type_name(); + + component.downcast_mut::<ComponentT>().unwrap_or_else(|| { + panic!( + "Cannot downcast component {component_type_name} to type {}", + type_name::<ComponentT>() + ); + }) + }), + _ph: PhantomData, + } } } @@ -212,15 +229,29 @@ impl<'component, ComponentT: Component> FromOptionalMutComponent<'component> _world: &'component World, ) -> Self { - Self { - inner: inner.unwrap_or_else(|| { - panic!( - "Component {} was not found in entity", - type_name::<ComponentT>() - ); - }), - _ph: PhantomData, - } + Self::new(inner.unwrap_or_else(|| { + panic!( + "Component {} was not found in entity", + type_name::<ComponentT>() + ); + })) + } +} + +impl<'component, ComponentT: Component> FromLockedOptionalComponent<'component> + for ComponentRefMut<'component, ComponentT> +{ + fn from_locked_optional_component( + optional_component: Option<&'component crate::lock::Lock<Box<dyn Component>>>, + world: &'component World, + ) -> Result<Self, LockError> + { + Ok(Self::from_optional_mut_component( + optional_component + .map(|lock| lock.write_nonblock()) + .transpose()?, + world, + )) } } @@ -238,13 +269,29 @@ where } } +impl<'comp, ComponentT> FromLockedOptionalComponent<'comp> + for Option<ComponentRefMut<'comp, ComponentT>> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'comp Lock<Box<dyn Component>>>, + _world: &'comp World, + ) -> Result<Self, LockError> + { + optional_component + .map(|lock| Ok(ComponentRefMut::new(lock.write_nonblock()?))) + .transpose() + } +} + impl<'a, ComponentT: Component> Deref for ComponentRefMut<'a, ComponentT> { type Target = ComponentT; fn deref(&self) -> &Self::Target { - self.inner.downcast_ref().unwrap() + &self.inner } } @@ -252,14 +299,14 @@ impl<'a, ComponentT: Component> DerefMut for ComponentRefMut<'a, ComponentT> { fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.downcast_mut().unwrap() + &mut self.inner } } #[derive(Debug)] pub struct ComponentRef<'a, ComponentT: Component> { - inner: ReadGuard<'a, Box<dyn Component>>, + inner: MappedReadGuard<'a, ComponentT>, _ph: PhantomData<ComponentT>, } @@ -267,7 +314,18 @@ impl<'a, ComponentT: Component> ComponentRef<'a, ComponentT> { pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Component>>) -> Self { - Self { inner, _ph: PhantomData } + Self { + inner: inner.map(|component| { + component.downcast_ref::<ComponentT>().unwrap_or_else(|| { + panic!( + "Cannot downcast component {} to type {}", + component.type_name(), + type_name::<ComponentT>() + ); + }) + }), + _ph: PhantomData, + } } } @@ -279,15 +337,29 @@ impl<'component, ComponentT: Component> FromOptionalComponent<'component> _world: &'component World, ) -> Self { - Self { - inner: inner.unwrap_or_else(|| { - panic!( - "Component {} was not found in entity", - type_name::<ComponentT>() - ); - }), - _ph: PhantomData, - } + Self::new(inner.unwrap_or_else(|| { + panic!( + "Component {} was not found in entity", + type_name::<ComponentT>() + ); + })) + } +} + +impl<'component, ComponentT: Component> FromLockedOptionalComponent<'component> + for ComponentRef<'component, ComponentT> +{ + fn from_locked_optional_component( + optional_component: Option<&'component crate::lock::Lock<Box<dyn Component>>>, + world: &'component World, + ) -> Result<Self, LockError> + { + Ok(Self::from_optional_component( + optional_component + .map(|lock| lock.read_nonblock()) + .transpose()?, + world, + )) } } @@ -305,12 +377,34 @@ where } } +impl<'comp, ComponentT> FromLockedOptionalComponent<'comp> + for Option<ComponentRef<'comp, ComponentT>> +where + ComponentT: Component, +{ + fn from_locked_optional_component( + optional_component: Option<&'comp Lock<Box<dyn Component>>>, + _world: &'comp World, + ) -> Result<Self, LockError> + { + optional_component + .map(|lock| Ok(ComponentRef::new(lock.read_nonblock()?))) + .transpose() + } +} + impl<'a, ComponentT: Component> Deref for ComponentRef<'a, ComponentT> { type Target = ComponentT; fn deref(&self) -> &Self::Target { - self.inner.downcast_ref().unwrap() + &self.inner } } + +#[derive(Debug, Component)] +pub(crate) struct SystemComponent +{ + pub(crate) system: TypeErased, +} diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs index 536e6ed..80ac346 100644 --- a/ecs/src/system/stateful.rs +++ b/ecs/src/system/stateful.rs @@ -1,7 +1,7 @@ -use std::any::{type_name, Any, TypeId}; -use std::collections::HashMap; +use std::any::{Any, TypeId}; use std::panic::{RefUnwindSafe, UnwindSafe}; +use hashbrown::HashMap; use seq_macro::seq; use crate::component::Component; @@ -15,10 +15,9 @@ use crate::system::{ TypeErased, }; use crate::tuple::{ - IntoInOptions as TupleIntoInOptions, Reduce as TupleReduce, - TakeOptionElementResult as TupleTakeOptionElementResult, - WithOptionElements as TupleWithOptionElements, + Tuple, + WithAllElemLtStatic as TupleWithAllElemLtStatic, }; use crate::uid::Uid; use crate::World; @@ -39,9 +38,10 @@ macro_rules! impl_system { Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, #(TParam~I: Param<'world>,)* #(TParam~I::Input: 'static,)* - (#(TParam~I::Input,)*): TupleReduce<ParamWithInputFilter>, - <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out: - TupleIntoInOptions + (#(TParam~I::Input,)*): TupleReduce< + ParamWithInputFilter, + Out: Tuple<InOptions: TupleWithAllElemLtStatic> + >, { type Input = <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out; @@ -50,35 +50,27 @@ macro_rules! impl_system { { let mut option_input = input.into_in_options(); + let mut index = 0; + #( if TypeId::of::<TParam~I::Input>() != TypeId::of::<()>() { - let input = match option_input.take::<TParam~I::Input>() { - TupleTakeOptionElementResult::Found(input) => input, - TupleTakeOptionElementResult::NotFound => { - panic!( - "Parameter input {} not found", - type_name::<TParam~I::Input>() - ); - } - TupleTakeOptionElementResult::AlreadyTaken => { - panic!( - concat!( - "Parameter {} is already initialized. ", - "System cannot contain multiple inputs with ", - "the same type", - ), - type_name::<TParam~I>() - ); - - } - }; + 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; + } } )* diff --git a/ecs/src/tuple.rs b/ecs/src/tuple.rs index 1434592..def25a0 100644 --- a/ecs/src/tuple.rs +++ b/ecs/src/tuple.rs @@ -1,36 +1,44 @@ use std::any::TypeId; -use std::mem::{transmute_copy, ManuallyDrop}; use paste::paste; use seq_macro::seq; use util_macros::sub; -/// Used to append a element to a tuple type. -pub trait With<OtherElem> +pub trait Tuple: sealed::Sealed { - type With; -} - -/// Used to remove the last element of a tuple type. -/// -/// For example: `(u32, String, PathBuf)` becomes `(u32, String)`. -pub trait WithoutLast -{ - type Return; -} - -/// Used to make all elements of a tuple type wrapped in [`Option`]. -pub trait IntoInOptions -{ - type InOptions: WithOptionElements; - + /// `Self` with the given type added as the last element. + /// + /// `(String, i32, u8)::WithElementAtEnd<Path> = (String, i32, u8, Path)` + /// + /// # Important note + /// If `Self` has 16 elements, this will be `()`. The reason for this is that the + /// `Tuple` trait is only implemented for tuples with up to and including 16 elements. + type WithElementAtEnd<NewElem>: Tuple; + + /// `Self` without the last element. + /// + /// `(u16, AtomicU8)::WithoutLastElement = (u16,)` + type WithoutLastElement: Tuple; + + /// The last element of `Self`. + type LastElement; + + /// Self with all elements wrapped in [`Option`]. + type InOptions: Tuple; + + /// Pops the last element from this tuple, returning the new tuple and the popped + /// element. + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement); + + /// Converts this tuple so that all elements are wrapped in [`Option`]. fn into_in_options(self) -> Self::InOptions; } -/// A tuple with all elements wrapped in [`Option`]. -pub trait WithOptionElements +/// A tuple with element types that all have the lifetime `'static`. +pub trait WithAllElemLtStatic: Tuple + sealed::Sealed { - fn take<Element: 'static>(&mut self) -> TakeOptionElementResult<Element>; + /// Returns the element at the given index. + fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element>; } /// Using the type system, reduces the elements of a tuple to a single one. Each element @@ -45,22 +53,6 @@ pub trait ReduceElement<Accumulator, Operation> type Return; } -/// The result of trying to [`take`] a element from a implementation of -/// [`WithOptionElements`]. -/// -/// [`take`]: WithOptionElements::take -pub enum TakeOptionElementResult<Element> -{ - /// The element was succesfully taken. - Found(Element), - - /// The element is not a element of the tuple. - NotFound, - - /// The element has already been taken - AlreadyTaken, -} - macro_rules! tuple_reduce_elem_tuple { (overflow) => { () @@ -95,13 +87,6 @@ macro_rules! elem_by_index { }; } -pub trait Pop: WithoutLast -{ - type LastElem; - - fn pop(self) -> (<Self as WithoutLast>::Return, Self::LastElem); -} - macro_rules! all_except_last { (start $($rest: tt)*) => { all_except_last!(@[] $($rest)*) @@ -131,19 +116,23 @@ macro_rules! all_except_last { macro_rules! impl_tuple_traits { ($cnt: tt) => { seq!(I in 0..$cnt { - impl<OtherElem, #(Elem~I,)*> With<OtherElem> for (#(Elem~I,)*) + impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) { - type With = (#(Elem~I,)* OtherElem,); - } + type WithElementAtEnd<NewElem> = (#(Elem~I,)* NewElem,); - impl<#(Elem~I,)*> WithoutLast for (#(Elem~I,)*) - { - type Return = all_except_last!(start #(Elem~I)*); - } + type WithoutLastElement = all_except_last!(start #(Elem~I)*); - impl<#(Element~I: 'static,)*> IntoInOptions for (#(Element~I,)*) - { - type InOptions = (#(Option<Element~I>,)*); + type LastElement = sub!($cnt - 1, elem_type_by_index); + + type InOptions = (#(Option<Elem~I>,)*); + + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) + { + ( + all_except_last!(start #((self.I))*), + sub!($cnt - 1, elem_by_index, (self)) + ) + } fn into_in_options(self) -> Self::InOptions { @@ -152,32 +141,28 @@ macro_rules! impl_tuple_traits { } } - impl<#(Element~I: 'static,)*> WithOptionElements for (#(Option<Element~I>,)*) + impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) { - fn take<Element: 'static>(&mut self) - -> TakeOptionElementResult<Element> + fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element> { - #( - if TypeId::of::<Element~I>() == TypeId::of::<Element>() { - let input = match self.I.take() { - Some(input) => ManuallyDrop::new(input), - None => { - return TakeOptionElementResult::AlreadyTaken; - } - }; - - return TakeOptionElementResult::Found( - // SAFETY: It can be transmuted safely since it is the - // same type and the type is 'static - unsafe { transmute_copy(&input) } - ); - } - )* - - TakeOptionElementResult::NotFound + match index { + #( + I => { + assert!(TypeId::of::<Element>() == TypeId::of::<Elem~I>()); + + // SAFETY: It is checked above that the type is correct + Some(unsafe { &mut *(&raw mut self.I).cast::<Element>() }) + } + )* + _ => None + } } } + impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) + { + } + paste! { impl<Operation, #(Elem~I,)*> Reduce<Operation> for (#(Elem~I,)*) where @@ -190,19 +175,6 @@ macro_rules! impl_tuple_traits { type Out = sub!($cnt - 1, tuple_reduce_elem_tuple); } } - - impl<#(Elem~I,)*> Pop for (#(Elem~I,)*) - { - type LastElem = sub!($cnt - 1, elem_type_by_index); - - fn pop(self) -> (<Self as WithoutLast>::Return, Self::LastElem) - { - ( - all_except_last!(start #((self.I))*), - sub!($cnt - 1, elem_by_index, (self)) - ) - } - } }); }; } @@ -210,3 +182,57 @@ macro_rules! impl_tuple_traits { seq!(N in 0..16 { impl_tuple_traits!(N); }); + +seq!(I in 0..16 { + impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) + { + type WithElementAtEnd<NewElem> = (); + + type WithoutLastElement = all_except_last!(start #(Elem~I)*); + + type LastElement = Elem15; + + type InOptions = (#(Option<Elem~I>,)*); + + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) + { + ( + all_except_last!(start #((self.I))*), + self.15 + ) + } + + fn into_in_options(self) -> Self::InOptions + { + #![allow(clippy::unused_unit)] + (#(Some(self.I),)*) + } + } + + impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) + { + fn get_mut<Element: 'static>(&mut self, index: usize) -> Option<&mut Element> + { + match index { + #( + I => { + assert!(TypeId::of::<Element>() == TypeId::of::<Elem~I>()); + + // SAFETY: It is checked above that the type is correct + Some(unsafe { &mut *(&raw mut self.I).cast::<Element>() }) + } + )* + _ => None + } + } + } + + impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) + { + } +}); + +mod sealed +{ + pub trait Sealed {} +} diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs index 0e5d88a..bcef73e 100644 --- a/ecs/src/uid.rs +++ b/ecs/src/uid.rs @@ -1,10 +1,13 @@ +use std::fmt::{Debug, Formatter}; use std::mem::transmute; use std::sync::atomic::{AtomicU32, Ordering}; +use crate::util::{gen_mask_64, BitMask, NumberExt}; + static NEXT: AtomicU32 = AtomicU32::new(1); -// Bit 0 and 1 for the kind -const KIND_BITS: u64 = 0x03; +const ID_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(32..=63)); +const KIND_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(0..=1)); #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] @@ -15,7 +18,7 @@ pub enum Kind } /// Unique entity/component ID. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Uid { inner: u64, @@ -26,18 +29,36 @@ impl Uid /// Returns a new unique entity/component ID. pub fn new_unique(kind: Kind) -> Self { - let id_part = NEXT.fetch_add(1, Ordering::Relaxed); + let id = NEXT.fetch_add(1, Ordering::Relaxed); Self { - inner: (u64::from(id_part) << 32) | kind as u64, + inner: ID_BITS.field_prep(id as u64) | KIND_BITS.field_prep(kind as u64), } } #[must_use] + pub fn id(&self) -> u32 + { + self.inner.field_get(ID_BITS) as u32 + } + + #[must_use] pub fn kind(&self) -> Kind { // SAFETY: The kind bits cannot be invalid since they are set using the Kind enum // in the new_unique function - unsafe { transmute((self.inner & KIND_BITS) as u8) } + unsafe { transmute(self.inner.field_get(KIND_BITS) as u8) } + } +} + +impl Debug for Uid +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + formatter + .debug_struct("Uid") + .field("id", &self.id()) + .field("kind", &self.kind()) + .finish_non_exhaustive() } } diff --git a/ecs/src/util.rs b/ecs/src/util.rs index 4480fc8..71021cd 100644 --- a/ecs/src/util.rs +++ b/ecs/src/util.rs @@ -1,3 +1,52 @@ +use std::hash::Hash; +use std::ops::BitAnd; + +use hashbrown::HashMap; + +pub trait HashMapExt<Key, Value> +{ + /// Returns true if the keys are a subset of another [`HashMap`]'s keys, i.e., `other` + /// contains at least all the keys in `self`. + fn keys_is_subset(&self, other: &Self) -> bool; + + /// Returns true if the keys are a superset of another [`HashMap`]'s keys, i.e., + /// `self` contains at least all the keys in `other`. + fn keys_is_superset(&self, other: &Self) -> bool; +} + +impl<Key, Value> HashMapExt<Key, Value> for HashMap<Key, Value> +where + Key: Eq + Hash, +{ + fn keys_is_subset(&self, other: &Self) -> bool + { + if self.len() <= other.len() { + self.keys().all(|key| other.contains_key(key)) + } else { + false + } + } + + fn keys_is_superset(&self, other: &Self) -> bool + { + other.keys_is_subset(self) + } +} + +pub trait Array<Item>: + AsRef<[Item]> + + AsMut<[Item]> + + IntoIterator<Item = Item> + + Into<Vec<Item>> + + Sortable<Item = Item> + + sealed::Sealed +{ +} + +impl<Item, const CNT: usize> Array<Item> for [Item; CNT] {} + +impl<Item, const CNT: usize> sealed::Sealed for [Item; CNT] {} + pub trait Sortable { type Item; @@ -46,3 +95,94 @@ impl<Item> Sortable for Vec<Item> self.sort_by_key(func); } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct BitMask<Value> +{ + mask: Value, +} + +impl BitMask<u64> +{ + #[must_use] + pub const fn new(mask: u64) -> Self + { + Self { mask } + } + + pub const fn value(self) -> u64 + { + self.mask + } + + /// Prepares a bitfield value in the range of bits specified by this `BitMask`. + #[must_use] + pub const fn field_prep(self, field_value: u64) -> u64 + { + ((field_value) << self.mask.trailing_zeros()) & (self.mask) + } +} + +impl BitAnd<u64> for BitMask<u64> +{ + type Output = u64; + + fn bitand(self, rhs: u64) -> Self::Output + { + self.mask & rhs + } +} + +pub trait NumberExt: Sized +{ + /// Returns a range of bits (field) specified by the provided [`BitMask`]. + fn field_get(self, field_mask: BitMask<Self>) -> Self; +} + +impl NumberExt for u64 +{ + fn field_get(self, field_mask: BitMask<Self>) -> Self + { + (field_mask & self) >> field_mask.value().trailing_zeros() + } +} + +macro_rules! gen_mask_64 { + ($low: literal..=$high: literal) => { + const { + if $high <= $low { + panic!("High bit index cannot be less than or equal to low bit index"); + } + + (((!0u64) - (1u64 << ($low)) + 1) + & (!0u64 >> (u64::BITS as u64 - 1 - ($high)))) + } + }; +} + +pub(crate) use gen_mask_64; + +mod sealed +{ + pub trait Sealed {} +} + +#[cfg(test)] +mod tests +{ + + use super::BitMask; + use crate::util::NumberExt; + + #[test] + fn field_get_works() + { + assert_eq!(0b11011u64.field_get(BitMask::new(0b11100)), 0b00110); + } + + #[test] + fn bitmask_field_prep_works() + { + assert_eq!(BitMask::new(0b11000).field_prep(3), 0b11000); + } +} diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 1333360..087f727 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,11 +1,11 @@ use ecs::component::local::Local; +use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::sole::Single; use ecs::system::{Into, System}; use ecs::{Component, Query}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; -use crate::event::Update as UpdateEvent; use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::Position; use crate::util::builder; @@ -60,7 +60,7 @@ impl ecs::extension::Extension for Extension fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_system( - UpdateEvent, + *UPDATE_PHASE, update .into_system() .initialize((CursorState::default(), self.0)), @@ -75,7 +75,7 @@ pub struct Options } fn update( - camera_query: Query<(Camera, Position, Fly, ActiveCamera)>, + camera_query: Query<(&mut Camera, &mut Position, &mut Fly, &ActiveCamera)>, keys: Single<Keys>, cursor: Single<Cursor>, cursor_flags: Single<CursorFlags>, @@ -121,23 +121,23 @@ fn update( camera.global_up = cam_right.cross(&direction).normalize(); - if matches!(keys.get_key_state(Key::W), KeyState::Pressed) { + if keys.get_key_state(Key::W) == KeyState::Pressed { camera_pos.position += direction * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::S), KeyState::Pressed) { + if keys.get_key_state(Key::S) == KeyState::Pressed { camera_pos.position -= direction * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::A), KeyState::Pressed) { + if keys.get_key_state(Key::A) == KeyState::Pressed { let cam_left = -direction.cross(&Vec3::UP).normalize(); camera_pos.position += cam_left * fly_camera.speed * delta_time.as_secs_f32(); } - if matches!(keys.get_key_state(Key::D), KeyState::Pressed) { + if keys.get_key_state(Key::D) == KeyState::Pressed { let cam_right = direction.cross(&Vec3::UP).normalize(); camera_pos.position += diff --git a/engine/src/collision.rs b/engine/src/collision.rs new file mode 100644 index 0000000..aefd9b6 --- /dev/null +++ b/engine/src/collision.rs @@ -0,0 +1,142 @@ +use ecs::Component; + +use crate::mesh::Mesh; +use crate::vector::Vec3; + +pub trait Collider<Other> +{ + fn intersects(&self, other: &Other) -> bool; +} + +#[derive(Debug, Default, Clone, Component)] +#[non_exhaustive] +pub struct BoxCollider +{ + pub min: Vec3<f32>, + pub max: Vec3<f32>, +} + +impl BoxCollider +{ + pub fn for_mesh(mesh: &Mesh) -> Self + { + let furthest_dir_points = mesh.find_furthest_vertex_positions(); + + Self { + min: Vec3 { + x: furthest_dir_points.left.x, + y: furthest_dir_points.down.y, + z: furthest_dir_points.back.z, + }, + max: Vec3 { + x: furthest_dir_points.right.x, + y: furthest_dir_points.up.y, + z: furthest_dir_points.front.z, + }, + } + } + + pub fn offset(self, offset: Vec3<f32>) -> Self + { + Self { + min: self.min + offset, + max: self.max + offset, + } + } +} + +impl Collider<BoxCollider> for BoxCollider +{ + fn intersects(&self, other: &BoxCollider) -> bool + { + self.min.x <= other.max.x + && self.max.x >= other.min.x + && self.min.y <= other.max.y + && self.max.y >= other.min.y + && self.min.z <= other.max.z + && self.max.z >= other.min.z + } +} + +impl Collider<SphereCollider> for BoxCollider +{ + fn intersects(&self, other: &SphereCollider) -> bool + { + other.intersects(self) + } +} + +impl Collider<Vec3<f32>> for BoxCollider +{ + fn intersects(&self, other: &Vec3<f32>) -> bool + { + other.x >= self.min.x + && other.y >= self.min.y + && other.z >= self.min.z + && other.x <= self.max.x + && other.y <= self.max.y + && other.z <= self.max.z + } +} + +#[derive(Debug, Default, Clone, Component)] +pub struct SphereCollider +{ + pub center: Vec3<f32>, + pub radius: f32, +} + +impl SphereCollider +{ + pub fn offset(self, offset: Vec3<f32>) -> Self + { + Self { + center: self.center + offset, + radius: self.radius, + } + } +} + +impl Collider<SphereCollider> for SphereCollider +{ + fn intersects(&self, other: &SphereCollider) -> bool + { + (&self.center - &other.center).length() <= self.radius + other.radius + } +} + +impl Collider<BoxCollider> for SphereCollider +{ + fn intersects(&self, other: &BoxCollider) -> bool + { + let mut min_distance = 0.0; + + if self.center.x < other.min.x { + min_distance += (self.center.x - other.min.x).powf(2.0); + } else if self.center.x > other.max.x { + min_distance += (self.center.x - other.max.x).powf(2.0); + } + + if self.center.y < other.min.y { + min_distance += (self.center.y - other.min.y).powf(2.0); + } else if self.center.y > other.max.y { + min_distance += (self.center.y - other.max.y).powf(2.0); + } + + if self.center.z < other.min.z { + min_distance += (self.center.z - other.min.z).powf(2.0); + } else if self.center.z > other.max.z { + min_distance += (self.center.z - other.max.z).powf(2.0); + } + + min_distance <= self.radius.powf(2.0) + } +} + +impl Collider<Vec3<f32>> for SphereCollider +{ + fn intersects(&self, other: &Vec3<f32>) -> bool + { + (&self.center - other).length() <= self.radius + } +} diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs index b395627..5002436 100644 --- a/engine/src/data_types/dimens.rs +++ b/engine/src/data_types/dimens.rs @@ -1,7 +1,16 @@ -/// Dimensions. +/// 2D dimensions. #[derive(Debug, Clone, Copy)] pub struct Dimens<Value> { pub width: Value, pub height: Value, } + +/// 3D dimensions. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct Dimens3<Value> +{ + pub width: Value, + pub height: Value, + pub depth: Value, +} diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs index 17953f4..802a4a7 100644 --- a/engine/src/data_types/vector.rs +++ b/engine/src/data_types/vector.rs @@ -2,7 +2,7 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use crate::color::Color; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct Vec2<Value> { pub x: Value, @@ -74,7 +74,7 @@ where } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] #[repr(C)] pub struct Vec3<Value> { @@ -85,6 +85,11 @@ pub struct Vec3<Value> impl Vec3<f32> { + pub const BACK: Self = Self { x: 0.0, y: 0.0, z: -1.0 }; + pub const DOWN: Self = Self { x: 0.0, y: -1.0, z: 0.0 }; + pub const FRONT: Self = Self { x: 0.0, y: 0.0, z: 1.0 }; + pub const LEFT: Self = Self { x: -1.0, y: 0.0, z: 0.0 }; + pub const RIGHT: Self = Self { x: 1.0, y: 0.0, z: 0.0 }; pub const UP: Self = Self { x: 0.0, y: 1.0, z: 0.0 }; /// Returns the length of the vector. @@ -209,6 +214,22 @@ where } } +impl<Value> Mul for Vec3<Value> +where + Value: Mul<Value, Output = Value>, +{ + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output + { + Self::Output { + x: self.x * rhs.x, + y: self.y * rhs.y, + z: self.z * rhs.z, + } + } +} + impl<Value> Neg for Vec3<Value> where Value: Neg<Output = Value>, diff --git a/engine/src/event.rs b/engine/src/event.rs deleted file mode 100644 index e5ae486..0000000 --- a/engine/src/event.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub use ecs::event::start::Start; -use ecs::event::Event; - -#[derive(Debug)] -pub struct Update; - -impl Event for Update {} - -#[derive(Debug)] -pub struct PreUpdate; - -impl Event for PreUpdate {} - -#[derive(Debug)] -pub struct Present; - -impl Event for Present {} - -#[derive(Debug)] -pub struct PostPresent; - -impl Event for PostPresent {} - -#[derive(Debug)] -pub struct Conclude; - -impl Event for Conclude {} diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs index 88e6580..6ca11c2 100644 --- a/engine/src/file_format/wavefront/obj.rs +++ b/engine/src/file_format/wavefront/obj.rs @@ -2,6 +2,7 @@ //! //! File format documentation: <https://paulbourke.net/dataformats/obj> +use std::collections::HashMap; use std::fs::read_to_string; use std::path::PathBuf; @@ -82,26 +83,32 @@ impl Obj /// - A face index does not fit in a [`u32`] pub fn to_mesh(&self) -> Result<Mesh, Error> { - let vertices = self - .faces - .iter() - .flat_map(|face| face.vertices.clone()) - .map(|face_vertex| face_vertex.to_vertex(self)) - .collect::<Result<Vec<_>, Error>>()?; - - Ok(Mesh::new( - vertices, - Some( - self.faces - .iter() - .flat_map(|face| face.vertices.clone()) - .enumerate() - .map(|(index, _)| { - u32::try_from(index).map_err(|_| Error::FaceIndexTooBig(index)) - }) - .collect::<Result<Vec<_>, _>>()?, - ), - )) + let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3); + let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3); + + let mut added_face_vertices = + HashMap::<FaceVertex, u32>::with_capacity(self.faces.len() * 3); + + for face in &self.faces { + for face_vertex in &face.vertices { + if let Some(index) = added_face_vertices.get(&face_vertex) { + indices.push(*index); + + continue; + } + + vertices.push(face_vertex.to_vertex(self)?); + + let vertex_index = u32::try_from(vertices.len() - 1) + .map_err(|_| Error::FaceIndexTooBig(vertices.len() - 1))?; + + indices.push(vertex_index); + + added_face_vertices.insert(face_vertex.clone(), vertex_index); + } + } + + Ok(Mesh::new(vertices, Some(indices))) } /// Reads and parses the material libraries of this `Obj`. @@ -142,7 +149,7 @@ pub struct Face pub material_name: Option<String>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FaceVertex { pub position: u32, diff --git a/engine/src/input.rs b/engine/src/input.rs index e847702..95de048 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -1,16 +1,13 @@ use std::collections::HashMap; use ecs::extension::Collector as ExtensionCollector; +use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; use ecs::sole::Single; -use ecs::Sole; +use ecs::{static_entity, Sole}; -use crate::event::{ - PostPresent as PostPresentEvent, - PreUpdate as PreUpdateEvent, - Start as StartEvent, -}; use crate::vector::Vec2; -use crate::window::Window; +use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE}; mod reexports { @@ -19,10 +16,19 @@ mod reexports pub use reexports::*; +static_entity!( + SET_PREV_KEY_STATE_PHASE, + ( + Phase, + <Relationship<ChildOf, Phase>>::new(*WINDOW_UPDATE_PHASE) + ) +); + #[derive(Debug, Sole)] pub struct Keys { map: HashMap<Key, KeyData>, + pending: Vec<(Key, KeyState)>, } impl Keys @@ -43,6 +49,7 @@ impl Keys ) }) .collect(), + pending: Vec::with_capacity(Key::KEYS.len()), } } @@ -145,9 +152,9 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { - collector.add_system(StartEvent, initialize); - collector.add_system(PreUpdateEvent, maybe_clear_cursor_is_first_move); - collector.add_system(PostPresentEvent, set_keys_prev_tick_state); + 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(); @@ -169,7 +176,7 @@ fn initialize( let mut keys = keys_ref.to_single(); - keys.set_key_state(key, key_state); + keys.pending.push((key, key_state)); }); let cursor_weak_ref = cursor.to_weak_ref(); @@ -220,11 +227,21 @@ fn maybe_clear_cursor_is_first_move( } } -fn set_keys_prev_tick_state(mut keys: Single<Keys>) +fn set_pending_key_states(mut keys: Single<Keys>) { - for key_data in keys.map.values_mut() { + 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)] diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 07b2f8d..a9a5a97 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,40 +1,30 @@ #![deny(clippy::all, clippy::pedantic)] #![allow(clippy::needless_pass_by_value)] -use ecs::component::Sequence as ComponentSequence; -use ecs::event::component::TypeTransformComponentsToAddedEvents; -use ecs::event::{Event, Sequence as EventSequence}; +use ecs::component::{Component, Sequence as ComponentSequence}; use ecs::extension::Extension; +use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; use ecs::sole::Sole; use ecs::system::{Into, System}; -use ecs::tuple::Reduce as TupleReduce; use ecs::uid::Uid; use ecs::{SoleAlreadyExistsError, World}; use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; -use crate::event::{ - Conclude as ConcludeEvent, - PostPresent as PostPresentEvent, - PreUpdate as PreUpdateEvent, - Present as PresentEvent, - Update as UpdateEvent, -}; mod opengl; mod util; pub mod camera; +pub mod collision; pub mod data_types; pub mod delta_time; pub mod draw_flags; -pub mod event; pub mod file_format; pub mod input; pub mod lighting; pub mod material; pub mod math; pub mod mesh; -pub mod performance; pub mod projection; pub mod renderer; pub mod texture; @@ -47,14 +37,6 @@ pub extern crate ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; -type EventOrder = ( - PreUpdateEvent, - UpdateEvent, - PresentEvent, - PostPresentEvent, - ConcludeEvent, -); - #[derive(Debug)] pub struct Engine { @@ -72,7 +54,7 @@ impl Engine world.add_sole(DeltaTime::default()).ok(); world.register_system( - PreUpdateEvent, + *PRE_UPDATE_PHASE, update_delta_time .into_system() .initialize((LastUpdate::default(),)), @@ -83,19 +65,28 @@ impl Engine pub fn spawn<Comps>(&mut self, components: Comps) -> Uid where - Comps: ComponentSequence + TupleReduce<TypeTransformComponentsToAddedEvents>, - Comps::Out: EventSequence, + Comps: ComponentSequence, { self.world.create_entity(components) } pub fn register_system<'this, SystemImpl>( &'this mut self, - event: impl Event, + phase_euid: Uid, system: impl System<'this, SystemImpl>, ) { - self.world.register_system(event, system); + self.world.register_system(phase_euid, system); + } + + pub fn register_observer_system<'this, SystemImpl, Event>( + &'this mut self, + system: impl System<'this, SystemImpl>, + event: Event, + ) where + Event: Component, + { + self.world.register_observer_system(system, event); } /// Adds a globally shared singleton value. @@ -115,7 +106,7 @@ impl Engine /// Runs the event loop. pub fn start(&self) { - self.world.event_loop::<EventOrder>(); + self.world.start_loop(); } } diff --git a/engine/src/material.rs b/engine/src/material.rs index aae6003..e368519 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -19,6 +19,14 @@ pub struct Material pub shininess: f32, } +impl Material +{ + pub fn builder() -> Builder + { + Builder::default() + } +} + /// [`Material`] builder. #[derive(Debug, Clone)] pub struct Builder diff --git a/engine/src/math.rs b/engine/src/math.rs index b86e760..0340de8 100644 --- a/engine/src/math.rs +++ b/engine/src/math.rs @@ -13,5 +13,5 @@ pub fn calc_triangle_surface_normal( let v1 = edge_b - egde_a; let v2 = edge_c - egde_a; - v1.cross(&v2) + v1.cross(&v2).normalize() } diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index de0af70..917e7f7 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,5 +1,6 @@ use ecs::Component; +use crate::vector::Vec3; use crate::vertex::Vertex; pub mod cube; @@ -26,8 +27,119 @@ impl Mesh } #[must_use] + pub fn vertices_mut(&mut self) -> &mut [Vertex] + { + &mut self.vertices + } + + #[must_use] pub fn indices(&self) -> Option<&[u32]> { self.indices.as_deref() } + + #[must_use] + pub fn indices_mut(&mut self) -> Option<&mut [u32]> + { + self.indices.as_deref_mut() + } + + /// Finds the vertex positions that are furthest in every 3D direction. Keep in mind + /// that this can be quite time-expensive if the mesh has many vertices. + pub fn find_furthest_vertex_positions(&self) -> DirectionPositions<'_> + { + let mut point_iter = self.vertices().iter().map(|vertex| &vertex.pos).into_iter(); + + let first_point = point_iter.next().unwrap(); + + point_iter + .fold( + FurthestPosAcc { + up: FurthestPos::new(&first_point, &Vec3::UP), + down: FurthestPos::new(&first_point, &Vec3::DOWN), + left: FurthestPos::new(&first_point, &Vec3::LEFT), + right: FurthestPos::new(&first_point, &Vec3::RIGHT), + back: FurthestPos::new(&first_point, &Vec3::BACK), + front: FurthestPos::new(&first_point, &Vec3::FRONT), + }, + |mut furthest_pos_acc, pos| { + furthest_pos_acc.up.update_if_further(pos); + furthest_pos_acc.down.update_if_further(pos); + furthest_pos_acc.left.update_if_further(pos); + furthest_pos_acc.right.update_if_further(pos); + furthest_pos_acc.back.update_if_further(pos); + furthest_pos_acc.front.update_if_further(pos); + + furthest_pos_acc + }, + ) + .into() + } +} + +#[derive(Debug, Clone)] +pub struct DirectionPositions<'mesh> +{ + pub up: &'mesh Vec3<f32>, + pub down: &'mesh Vec3<f32>, + pub left: &'mesh Vec3<f32>, + pub right: &'mesh Vec3<f32>, + pub back: &'mesh Vec3<f32>, + pub front: &'mesh Vec3<f32>, +} + +impl<'mesh> From<FurthestPosAcc<'mesh>> for DirectionPositions<'mesh> +{ + fn from(acc: FurthestPosAcc<'mesh>) -> Self + { + Self { + up: acc.up.pos, + down: acc.down.pos, + left: acc.left.pos, + right: acc.right.pos, + back: acc.back.pos, + front: acc.front.pos, + } + } +} + +#[derive(Debug)] +struct FurthestPosAcc<'mesh> +{ + up: FurthestPos<'mesh>, + down: FurthestPos<'mesh>, + left: FurthestPos<'mesh>, + right: FurthestPos<'mesh>, + back: FurthestPos<'mesh>, + front: FurthestPos<'mesh>, +} + +#[derive(Debug)] +struct FurthestPos<'mesh> +{ + pos: &'mesh Vec3<f32>, + dot_prod: f32, + direction: &'mesh Vec3<f32>, +} + +impl<'mesh> FurthestPos<'mesh> +{ + fn new(pos: &'mesh Vec3<f32>, direction: &'mesh Vec3<f32>) -> Self + { + Self { + pos, + dot_prod: direction.dot(&pos), + direction, + } + } + + fn update_if_further(&mut self, point: &'mesh Vec3<f32>) + { + let point_dot_prod = self.direction.dot(point); + + if point_dot_prod > self.dot_prod { + self.pos = point; + self.dot_prod = point_dot_prod; + } + } } diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs index 7cdf885..c29ce0b 100644 --- a/engine/src/mesh/cube.rs +++ b/engine/src/mesh/cube.rs @@ -1,7 +1,7 @@ use crate::math::calc_triangle_surface_normal; use crate::mesh::Mesh; use crate::util::builder; -use crate::vector::Vec3; +use crate::vector::{Vec2, Vec3}; use crate::vertex::{Builder as VertexBuilder, Vertex}; builder! { @@ -27,676 +27,455 @@ impl CreationSpec } } -#[derive(Debug)] +/// Describes a single side of a cube (obviously). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Side { + /// +Z Front, + + /// -Z Back, + + /// -X Left, + + /// +X Right, + + /// +Y Top, + + /// -Y Bottom, } -#[derive(Debug)] -pub enum Corner +/// Describes what location on a side of a cube a face is. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum FaceLocation { - TopRight, - TopLeft, - BottomRight, - BottomLeft, + /// 🮝 + RightUp, + + /// 🮟 + LeftDown, } /// Creates a cube mesh. +/// +/// By default, the texture coordinates are arranged so that the full texture is visible +/// on every side. This can be changed inside of the `face_cb` function. pub fn create( creation_spec: CreationSpec, - vertex_builder_cb: impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, + face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, ) -> Mesh { - let mut vertices = [const { None }; VertexIndex::VARIANT_CNT]; - - create_front(&creation_spec, &mut vertices, &vertex_builder_cb); - create_back(&creation_spec, &mut vertices, &vertex_builder_cb); - create_right(&creation_spec, &mut vertices, &vertex_builder_cb); - create_left(&creation_spec, &mut vertices, &vertex_builder_cb); - create_top(&creation_spec, &mut vertices, &vertex_builder_cb); - create_bottom(&creation_spec, &mut vertices, &vertex_builder_cb); - - Mesh::new( - vertices.map(Option::unwrap).to_vec(), - Some( - VERTEX_INDICES - .into_iter() - .flatten() - .map(|index| index as u32) - .collect(), - ), - ) -} + let mut data = Data::default(); -macro_rules! one { - ($tt: tt) => { - 1 - }; -} + create_side(&SidePositions::new_top(&creation_spec), &mut data); + create_side(&SidePositions::new_bottom(&creation_spec), &mut data); + create_side(&SidePositions::new_left(&creation_spec), &mut data); + create_side(&SidePositions::new_right(&creation_spec), &mut data); + create_side(&SidePositions::new_back(&creation_spec), &mut data); + create_side(&SidePositions::new_front(&creation_spec), &mut data); -macro_rules! enum_with_variant_cnt { - ( - $(#[$attr: meta])* - enum $name: ident { - $($variant: ident,)* - } - ) => { - $(#[$attr])* - enum $name { - $($variant,)* - } - - impl $name { - const VARIANT_CNT: usize = 0 $(+ one!($variant))*; - } - }; + data.into_mesh(face_cb) } -enum_with_variant_cnt! { -#[repr(u32)] -enum VertexIndex +#[derive(Debug, Default)] +struct Data { - FrontTopRight, - FrontBottomRight, - FrontBottomLeft, - FrontTopLeft, - - BackTopRight, - BackBottomRight, - BackBottomLeft, - BackTopLeft, - - RightBackTop, - RightBackBottom, - RightFrontTop, - RightFrontBottom, - - LeftBackTop, - LeftBackBottom, - LeftFrontTop, - LeftFrontBottom, - - TopBackRight, - TopBackLeft, - TopFrontRight, - TopFrontLeft, - - BottomBackRight, - BottomBackLeft, - BottomFrontRight, - BottomFrontLeft, -} + faces: Vec<Face>, + vertex_data: VertexData, } -fn create_front( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +#[derive(Debug, Default)] +struct VertexData { - let front_top_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; - - let front_bottom_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + vertex_positions: Vec<Vec3<f32>>, + vertex_normals: Vec<Vec3<f32>>, +} - let front_bottom_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; +impl Data +{ + fn into_mesh( + self, + mut face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, + ) -> Mesh + { + let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3); + let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3); - let front_top_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + let mut face_location = FaceLocation::RightUp; - let front_normal = calc_triangle_surface_normal( - &front_top_right_pos, - &front_bottom_right_pos, - &front_top_left_pos, - ); - - vertices[VertexIndex::FrontTopRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_top_right_pos) - .normal(front_normal), - Side::Front, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::FrontBottomRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_bottom_right_pos) - .normal(front_normal), - Side::Front, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::FrontBottomLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_bottom_left_pos) - .normal(front_normal), - Side::Front, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::FrontTopLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(front_top_left_pos) - .normal(front_normal), - Side::Front, - Corner::TopLeft, - ) - .build(), - ); -} + let Self { faces, vertex_data } = self; -fn create_back( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) -{ - let back_top_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + for face in faces { + let side = face.side; - let back_bottom_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + let face_vertices = face_cb( + FaceVertices::new(face, &vertex_data) + .with_full_per_side_tex_coords(face_location), + side, + face_location, + ); - let back_bottom_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + for vertex in face_vertices.vertices { + if let Some((prev_vertex_index, _)) = vertices + .iter() + .enumerate() + .find(|(_, prev_vertex)| *prev_vertex == &vertex) + { + indices + .push(u32::try_from(prev_vertex_index).expect( + "Vertex index does not fit into 32-bit unsigned int", + )); - let back_top_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + continue; + } - let back_normal = -calc_triangle_surface_normal( - &back_top_right_pos, - &back_bottom_right_pos, - &back_top_left_pos, - ); - - vertices[VertexIndex::BackTopRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_top_right_pos) - .normal(back_normal), - Side::Back, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::BackBottomRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_bottom_right_pos) - .normal(back_normal), - Side::Back, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::BackBottomLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_bottom_left_pos) - .normal(back_normal), - Side::Back, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::BackTopLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(back_top_left_pos) - .normal(back_normal), - Side::Back, - Corner::TopLeft, - ) - .build(), - ); -} + vertices.push(vertex); -fn create_right( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) -{ - let right_back_top_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + let vertex_index = u32::try_from(vertices.len() - 1) + .expect("Vertex index does not fit into 32-bit unsigned int"); - let right_back_bottom_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + indices.push(vertex_index); + } - let right_front_top_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + match face_location { + FaceLocation::RightUp => face_location = FaceLocation::LeftDown, + FaceLocation::LeftDown => face_location = FaceLocation::RightUp, + } + } - let right_front_bottom_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + Mesh::new(vertices, Some(indices)) + } +} - let right_normal = calc_triangle_surface_normal( - &right_back_top_pos, - &right_back_bottom_pos, - &right_front_top_pos, - ); - - vertices[VertexIndex::RightBackTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_back_top_pos) - .normal(right_normal), - Side::Right, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::RightBackBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_back_bottom_pos) - .normal(right_normal), - Side::Right, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::RightFrontTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_front_top_pos) - .normal(right_normal), - Side::Right, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::RightFrontBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(right_front_bottom_pos) - .normal(right_normal), - Side::Right, - Corner::BottomRight, - ) - .build(), - ); +/// The vertices of a single face of a cube. +#[derive(Debug, Default, Clone)] +pub struct FaceVertices +{ + /// The three vertices of a face in counter-clockwise order. + /// + /// Order when [`FaceLocation::RightUp`]: + /// ```text + /// ₂ ₁ + /// 🮝 + /// ³ + /// ``` + /// + /// Order when [`FaceLocation::LeftDown`]: + /// ```text + /// ₁ + /// 🮟 + /// ² ³ + /// ``` + pub vertices: [Vertex; 3], } -fn create_left( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +impl FaceVertices { - let left_back_top_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new(face: Face, vertex_data: &VertexData) -> Self + { + Self { + vertices: face.vertices.map(|face_vertex| { + let vertex_pos = vertex_data + .vertex_positions + .get(face_vertex.pos_index as usize) + .expect("Vertex position index is out of bounds") + .clone(); + + let vertex_normal = vertex_data + .vertex_normals + .get(face_vertex.normal_index as usize) + .expect("Vertex normal index is out of bounds") + .clone(); + + VertexBuilder::default() + .pos(vertex_pos) + .normal(vertex_normal) + .build() + }), + } + } - let left_back_bottom_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, - }; + fn with_full_per_side_tex_coords(mut self, face_location: FaceLocation) -> Self + { + match face_location { + FaceLocation::RightUp => { + self.vertices[0].texture_coords = Vec2 { x: 1.0, y: 1.0 }; + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 1.0 }; + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + } + FaceLocation::LeftDown => { + self.vertices[0].texture_coords = Vec2 { x: 0.0, y: 1.0 }; + self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 0.0 }; + self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; + } + }; + + self + } +} - let left_front_top_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; +#[derive(Debug)] +struct Face +{ + vertices: [FaceVertex; 3], + side: Side, +} - let left_front_bottom_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct FaceVertex +{ + pos_index: u32, + normal_index: u32, +} - let left_normal = -calc_triangle_surface_normal( - &left_back_top_pos, - &left_back_bottom_pos, - &left_front_top_pos, - ); - - vertices[VertexIndex::LeftBackTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_back_top_pos) - .normal(left_normal), - Side::Left, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::LeftBackBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_back_bottom_pos) - .normal(left_normal), - Side::Left, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::LeftFrontTop as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_front_top_pos) - .normal(left_normal), - Side::Left, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::LeftFrontBottom as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(left_front_bottom_pos) - .normal(left_normal), - Side::Left, - Corner::BottomLeft, - ) - .build(), - ); +#[derive(Debug)] +struct SidePositions +{ + up_left: Vec3<f32>, + up_right: Vec3<f32>, + down_left: Vec3<f32>, + down_right: Vec3<f32>, + normal_calc_order: NormalCalcOrder, + side: Side, } -fn create_top( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +impl SidePositions { - let top_back_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new_top(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Top, + } + } - let top_back_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: creation_spec.depth / 2.0, - }; + fn new_bottom(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: -creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Bottom, + } + } - let top_front_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + fn new_left(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + let down_right = Vec3 { + x: -(creation_spec.width / 2.0), + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { z: down_right.z, ..up_left.clone() }, + down_left: Vec3 { z: up_left.z, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Left, + } + } - let top_front_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: creation_spec.height / 2.0, - z: -(creation_spec.depth / 2.0), - }; + fn new_right(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: (creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -(creation_spec.depth / 2.0), + }; + + let down_right = Vec3 { + x: (creation_spec.width / 2.0), + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { z: down_right.z, ..up_left.clone() }, + down_left: Vec3 { z: up_left.z, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Right, + } + } + + fn new_back(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: -creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -(creation_spec.height / 2.0), + z: -creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::Clockwise, + side: Side::Back, + } + } - let top_normal = -calc_triangle_surface_normal( - &top_back_right_pos, - &top_back_left_pos, - &top_front_right_pos, - ); - - vertices[VertexIndex::TopBackRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_back_right_pos) - .normal(top_normal), - Side::Top, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::TopBackLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_back_left_pos) - .normal(top_normal), - Side::Top, - Corner::TopLeft, - ) - .build(), - ); - - vertices[VertexIndex::TopFrontLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_front_left_pos) - .normal(top_normal), - Side::Top, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::TopFrontRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(top_front_right_pos) - .normal(top_normal), - Side::Top, - Corner::BottomRight, - ) - .build(), - ); + fn new_front(creation_spec: &CreationSpec) -> Self + { + let up_left = Vec3 { + x: -(creation_spec.width / 2.0), + y: creation_spec.height / 2.0, + z: creation_spec.depth / 2.0, + }; + + let down_right = Vec3 { + x: creation_spec.width / 2.0, + y: -(creation_spec.height / 2.0), + z: creation_spec.depth / 2.0, + }; + + Self { + up_left, + up_right: Vec3 { x: down_right.x, ..up_left.clone() }, + down_left: Vec3 { x: up_left.x, ..down_right.clone() }, + down_right, + normal_calc_order: NormalCalcOrder::CounterClockwise, + side: Side::Front, + } + } } -fn create_bottom( - creation_spec: &CreationSpec, - vertices: &mut [Option<Vertex>], - vertex_builder_cb: &impl Fn(VertexBuilder, Side, Corner) -> VertexBuilder, -) +#[derive(Debug)] +enum NormalCalcOrder { - let bottom_back_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: (creation_spec.depth / 2.0), - }; + Clockwise, + CounterClockwise, +} - let bottom_back_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: creation_spec.depth / 2.0, +fn create_side(side_positions: &SidePositions, data: &mut Data) +{ + let normal = match side_positions.normal_calc_order { + NormalCalcOrder::Clockwise => calc_triangle_surface_normal( + &side_positions.up_left, + &side_positions.up_right, + &side_positions.down_left, + ), + NormalCalcOrder::CounterClockwise => calc_triangle_surface_normal( + &side_positions.up_left, + &side_positions.down_left, + &side_positions.up_right, + ), }; - let bottom_front_right_pos = Vec3 { - x: creation_spec.width / 2.0, - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + data.vertex_data.vertex_normals.push(normal); - let bottom_front_left_pos = Vec3 { - x: -(creation_spec.width / 2.0), - y: -(creation_spec.height / 2.0), - z: -(creation_spec.depth / 2.0), - }; + let top_normal_index = data.vertex_data.vertex_normals.len() - 1; - let bottom_normal = calc_triangle_surface_normal( - &bottom_back_right_pos, - &bottom_back_left_pos, - &bottom_front_right_pos, - ); - - vertices[VertexIndex::BottomBackRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_back_right_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::BottomRight, - ) - .build(), - ); - - vertices[VertexIndex::BottomBackLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_back_left_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::BottomLeft, - ) - .build(), - ); - - vertices[VertexIndex::BottomFrontRight as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_front_right_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::TopRight, - ) - .build(), - ); - - vertices[VertexIndex::BottomFrontLeft as usize] = Some( - vertex_builder_cb( - VertexBuilder::default() - .pos(bottom_front_left_pos) - .normal(bottom_normal), - Side::Bottom, - Corner::TopLeft, - ) - .build(), - ); -} + data.vertex_data + .vertex_positions + .push(side_positions.up_right); -const VERTEX_INDICES_FRONT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::FrontTopRight, - VertexIndex::FrontBottomRight, - VertexIndex::FrontTopLeft, - // - // 🮟 - VertexIndex::FrontBottomRight, - VertexIndex::FrontBottomLeft, - VertexIndex::FrontTopLeft, -]; + let up_right_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_BACK: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::BackTopRight, - VertexIndex::BackBottomRight, - VertexIndex::BackTopLeft, - // - // 🮟 - VertexIndex::BackBottomRight, - VertexIndex::BackBottomLeft, - VertexIndex::BackTopLeft, -]; + data.vertex_data + .vertex_positions + .push(side_positions.up_left); -const VERTEX_INDICES_RIGHT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::RightBackTop, - VertexIndex::RightBackBottom, - VertexIndex::RightFrontTop, - // - // 🮟 - VertexIndex::RightBackBottom, - VertexIndex::RightFrontBottom, - VertexIndex::RightFrontTop, -]; + let up_left_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_LEFT: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::LeftBackTop, - VertexIndex::LeftBackBottom, - VertexIndex::LeftFrontTop, - // - // 🮟 - VertexIndex::LeftBackBottom, - VertexIndex::LeftFrontBottom, - VertexIndex::LeftFrontTop, -]; + data.vertex_data + .vertex_positions + .push(side_positions.down_left); -const VERTEX_INDICES_TOP: [VertexIndex; 6] = [ - // 🮝 - VertexIndex::TopBackRight, - VertexIndex::TopBackLeft, - VertexIndex::TopFrontRight, - // - // 🮟 - VertexIndex::TopBackLeft, - VertexIndex::TopFrontLeft, - VertexIndex::TopFrontRight, -]; + let down_left_pos_index = data.vertex_data.vertex_positions.len() - 1; + + data.vertex_data + .vertex_positions + .push(side_positions.down_right); + + let down_right_pos_index = data.vertex_data.vertex_positions.len() - 1; -const VERTEX_INDICES_BOTTOM: [VertexIndex; 6] = [ // 🮝 - VertexIndex::BottomBackRight, - VertexIndex::BottomBackLeft, - VertexIndex::BottomFrontRight, - // + data.faces.push(Face { + vertices: [ + FaceVertex { + pos_index: up_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: up_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + ], + side: side_positions.side, + }); + // 🮟 - VertexIndex::BottomBackLeft, - VertexIndex::BottomFrontLeft, - VertexIndex::BottomFrontRight, -]; - -const VERTEX_INDICES: [[VertexIndex; 6]; 6] = [ - VERTEX_INDICES_FRONT, - VERTEX_INDICES_BACK, - VERTEX_INDICES_RIGHT, - VERTEX_INDICES_LEFT, - VERTEX_INDICES_TOP, - VERTEX_INDICES_BOTTOM, -]; + data.faces.push(Face { + vertices: [ + FaceVertex { + pos_index: up_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_left_pos_index as u32, + normal_index: top_normal_index as u32, + }, + FaceVertex { + pos_index: down_right_pos_index as u32, + normal_index: top_normal_index as u32, + }, + ], + side: side_positions.side, + }); +} diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs index 2be7f12..68a75fb 100644 --- a/engine/src/opengl/buffer.rs +++ b/engine/src/opengl/buffer.rs @@ -39,19 +39,6 @@ impl<Item> Buffer<Item> { self.buf } - - /// Does a weak clone of this buffer. The buffer itself is NOT copied in any way this - /// function only copies the internal buffer ID. - /// - /// # Safety - /// The returned `Buffer` must not be dropped if another `Buffer` referencing the - /// same buffer ID is used later or if a [`VertexArray`] is used later. - /// - /// [`VertexArray`]: crate::opengl::vertex_array::VertexArray - pub unsafe fn clone_weak(&self) -> Self - { - Self { buf: self.buf, _pd: PhantomData } - } } impl<Item> Drop for Buffer<Item> diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs index da5d91e..e1e1a15 100644 --- a/engine/src/opengl/vertex_array.rs +++ b/engine/src/opengl/vertex_array.rs @@ -125,17 +125,6 @@ impl VertexArray { unsafe { gl::BindVertexArray(self.array) } } - - /// Does a weak clone of this vertex array. The vertex array itself is NOT copied in - /// any way this function only copies the internal vertex array ID. - /// - /// # Safety - /// The returned `VertexArray` must not be dropped if another `VertexArray` - /// referencing the same vertex array ID is used later. - pub unsafe fn clone_unsafe(&self) -> Self - { - Self { array: self.array } - } } impl Drop for VertexArray diff --git a/engine/src/performance.rs b/engine/src/performance.rs deleted file mode 100644 index 3ec8994..0000000 --- a/engine/src/performance.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::time::Instant; - -use ecs::component::local::Local; -use ecs::system::{Into, System}; -use ecs::Component; - -use crate::event::PostPresent as PostPresentEvent; - -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct Extension {} - -impl ecs::extension::Extension for Extension -{ - fn collect(self, mut collector: ecs::extension::Collector<'_>) - { - collector.add_system( - PostPresentEvent, - log_perf.into_system().initialize((State::default(),)), - ); - } -} - -fn log_perf(mut state: Local<State>) -{ - let Some(last_time) = state.last_time else { - state.last_time = Some(Instant::now()); - return; - }; - - let time_now = Instant::now(); - - state.last_time = Some(time_now); - - tracing::info!( - "Frame time: {}us", - time_now.duration_since(last_time).as_micros() - ); -} - -#[derive(Debug, Default, Component)] -struct State -{ - last_time: Option<Instant>, -} diff --git a/engine/src/projection.rs b/engine/src/projection.rs index aa84a9f..faa741f 100644 --- a/engine/src/projection.rs +++ b/engine/src/projection.rs @@ -1,10 +1,14 @@ +use crate::data_types::dimens::Dimens3; use crate::matrix::Matrix; +use crate::util::builder; +use crate::vector::Vec3; #[derive(Debug)] #[non_exhaustive] pub enum Projection { Perspective(Perspective), + Orthographic(Orthographic), } /// Perspective projection parameters. @@ -16,6 +20,29 @@ pub struct Perspective pub near: f32, } +impl Perspective +{ + /// Creates a perspective projection matrix using right-handed coordinates. + #[inline] + pub fn to_matrix_rh(&self, aspect: f32, clip_volume: ClipVolume) + -> Matrix<f32, 4, 4> + { + let mut out = Matrix::new(); + + match clip_volume { + ClipVolume::NegOneToOne => { + out.set_cell(0, 0, (1.0 / (self.fov_radians / 2.0).tan()) / aspect); + out.set_cell(1, 1, 1.0 / (self.fov_radians / 2.0).tan()); + out.set_cell(2, 2, (self.near + self.far) / (self.near - self.far)); + out.set_cell(2, 3, (2.0 * self.near * self.far) / (self.near - self.far)); + out.set_cell(3, 2, -1.0); + } + } + + out + } +} + impl Default for Perspective { fn default() -> Self @@ -28,30 +55,83 @@ impl Default for Perspective } } -pub(crate) fn new_perspective_matrix( - perspective: &Perspective, - aspect: f32, -) -> Matrix<f32, 4, 4> +builder! { +#[builder(name = OrthographicBuilder, derives=(Debug, Clone))] +#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[non_exhaustive] +pub struct Orthographic { - let mut out = Matrix::new(); + pub size: Dimens3<f32>, +} +} - out.set_cell(0, 0, (1.0 / (perspective.fov_radians / 2.0).tan()) / aspect); +impl Orthographic +{ + pub fn builder() -> OrthographicBuilder + { + OrthographicBuilder::default() + } - out.set_cell(1, 1, 1.0 / (perspective.fov_radians / 2.0).tan()); + /// Creates a orthographic projection matrix using right-handed coordinates. + pub fn to_matrix_rh( + &self, + center_pos: &Vec3<f32>, + clip_volume: ClipVolume, + ) -> Matrix<f32, 4, 4> + { + let mut result = Matrix::<f32, 4, 4>::new(); - out.set_cell( - 2, - 2, - (perspective.near + perspective.far) / (perspective.near - perspective.far), - ); + let left = center_pos.x - (self.size.width / 2.0); + let right = center_pos.x + (self.size.width / 2.0); + let bottom = center_pos.y - (self.size.height / 2.0); + let top = center_pos.y + (self.size.height / 2.0); + let near = center_pos.z - (self.size.depth / 2.0); + let far = center_pos.z + (self.size.depth / 2.0); - out.set_cell( - 2, - 3, - (2.0 * perspective.near * perspective.far) / (perspective.near - perspective.far), - ); + match clip_volume { + ClipVolume::NegOneToOne => { + result.set_cell(0, 0, 2.0 / (right - left)); + result.set_cell(1, 1, 2.0 / (top - bottom)); + result.set_cell(2, 2, -2.0 / (far - near)); + result.set_cell(0, 3, -(right + left) / (right - left)); + result.set_cell(1, 3, -(top + bottom) / (top - bottom)); + result.set_cell(2, 3, -(far + near) / (far - near)); + result.set_cell(3, 3, 1.0); + } + } - out.set_cell(3, 2, -1.0); + result + } +} - out +impl Default for Orthographic +{ + fn default() -> Self + { + Self { + size: Dimens3 { + width: 10.0, + height: 7.0, + depth: 10.0, + }, + } + } +} + +impl Default for OrthographicBuilder +{ + fn default() -> Self + { + let orthographic = Orthographic::default(); + + OrthographicBuilder { size: orthographic.size } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum ClipVolume +{ + /// -1 to +1. This is the OpenGL clip volume definition. + NegOneToOne, } diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index c036cc0..c44a479 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -9,6 +9,7 @@ use std::process::abort; use ecs::actions::Actions; use ecs::component::local::Local; +use ecs::phase::{PRESENT as PRESENT_PHASE, START as START_PHASE}; use ecs::query::options::{Not, With}; use ecs::sole::Single; use ecs::system::{Into as _, System}; @@ -18,7 +19,6 @@ 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::event::{Present as PresentEvent, Start as StartEvent}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; @@ -61,22 +61,22 @@ use crate::opengl::{ Capability, ContextFlags, }; -use crate::projection::{new_perspective_matrix, Projection}; +use crate::projection::{ClipVolume, Projection}; use crate::texture::{Id as TextureId, Texture}; use crate::transform::{Position, Scale}; -use crate::util::NeverDrop; +use crate::util::{defer, Defer, RefOrValue}; use crate::vector::{Vec2, Vec3}; use crate::vertex::{AttributeComponentType, Vertex}; use crate::window::Window; -type RenderableEntity = ( - Mesh, - Material, - Option<MaterialFlags>, - Option<Position>, - Option<Scale>, - Option<DrawFlags>, - Option<GlObjects>, +type RenderableEntity<'a> = ( + &'a Mesh, + &'a Material, + &'a Option<MaterialFlags>, + &'a Option<Position>, + &'a Option<Scale>, + &'a Option<DrawFlags>, + &'a Option<GlObjects>, ); #[derive(Debug, Default)] @@ -87,10 +87,10 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system(StartEvent, initialize); + collector.add_system(*START_PHASE, initialize); collector.add_system( - PresentEvent, + *PRESENT_PHASE, render .into_system() .initialize((GlobalGlObjects::default(),)), @@ -133,10 +133,10 @@ fn initialize(window: Single<Window>) #[allow(clippy::too_many_arguments)] fn render( - query: Query<RenderableEntity, Not<With<NoDraw>>>, - point_light_query: Query<(PointLight,)>, - directional_lights: Query<(DirectionalLight,)>, - camera_query: Query<(Camera, Position, ActiveCamera)>, + query: Query<RenderableEntity<'_>, Not<With<NoDraw>>>, + point_light_query: Query<(&PointLight,)>, + directional_lights: Query<(&DirectionalLight,)>, + camera_query: Query<(&Camera, &Position, &ActiveCamera)>, window: Single<Window>, global_light: Single<GlobalLight>, mut gl_objects: Local<GlobalGlObjects>, @@ -166,32 +166,25 @@ fn render( clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for ( - entity_index, + euid, (mesh, material, material_flags, position, scale, draw_flags, gl_objects), - ) in query.iter().enumerate() + ) in query.iter_with_euids() { let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); - let new_gl_objects; - - let gl_objects = if let Some(gl_objects) = gl_objects.as_deref() { - gl_objects - } else { - // TODO: Account for when meshes are changed - let gl_objects = GlObjects::new(&mesh); - - new_gl_objects = Some(gl_objects.clone()); - - actions.add_components( - query.get_entity_uid(entity_index).unwrap(), - (gl_objects,), - ); - - &*new_gl_objects.unwrap() + let gl_objs = match gl_objects.as_deref() { + Some(gl_objs) => RefOrValue::Ref(gl_objs), + None => RefOrValue::Value(Some(GlObjects::new(&mesh))), }; + defer!(|gl_objs| { + if let RefOrValue::Value(opt_gl_objs) = gl_objs { + actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); + }; + }); + apply_transformation_matrices( Transformation { position: position.map(|pos| *pos).unwrap_or_default().position, @@ -238,7 +231,7 @@ fn render( ); } - draw_mesh(gl_objects); + draw_mesh(gl_objs.get().unwrap()); if draw_flags.is_some() { let default_polygon_mode_config = PolygonModeConfig::default(); @@ -276,9 +269,9 @@ fn draw_mesh(gl_objects: &GlObjects) gl_objects.vertex_arr.bind(); if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.index_cnt); + VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, 3); + VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); } } @@ -363,9 +356,9 @@ fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> struct GlObjects { /// Vertex and index buffer has to live as long as the vertex array - vertex_buffer: Buffer<Vertex>, + _vertex_buffer: Buffer<Vertex>, index_buffer: Option<Buffer<u32>>, - index_cnt: u32, + element_cnt: u32, vertex_arr: VertexArray, } @@ -418,37 +411,27 @@ impl GlObjects vertex_arr.bind_element_buffer(&index_buffer); return Self { - vertex_buffer, + _vertex_buffer: vertex_buffer, index_buffer: Some(index_buffer), - index_cnt: indices.len().try_into().unwrap(), + 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: vertex_buffer, index_buffer: None, - index_cnt: 0, + element_cnt: mesh + .vertices() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), vertex_arr, } } - - pub fn clone(&self) -> NeverDrop<Self> - { - NeverDrop::new(Self { - // SAFETY: The vertex buffer will never become dropped (NeverDrop ensures it) - vertex_buffer: unsafe { self.vertex_buffer.clone_weak() }, - index_buffer: self - .index_buffer - .as_ref() - // SAFETY: The index buffer will never become dropped (NeverDrop ensures - // it) - .map(|index_buffer| unsafe { index_buffer.clone_weak() }), - index_cnt: self.index_cnt, - // SAFETY: The vertex array will never become dropped (NeverDrop ensures it) - vertex_arr: unsafe { self.vertex_arr.clone_unsafe() }, - }) - } } fn apply_transformation_matrices( @@ -462,19 +445,22 @@ fn apply_transformation_matrices( gl_shader_program .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation)); - let view = create_view(camera, camera_pos); + let view_matrix = create_view_matrix(camera, &camera_pos.position); - gl_shader_program.set_uniform_matrix_4fv(c"view", &view); + gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix); #[allow(clippy::cast_precision_loss)] - let projection = match &camera.projection { - Projection::Perspective(perspective) => new_perspective_matrix( - perspective, + let proj_matrix = match &camera.projection { + Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( window_size.width as f32 / window_size.height as f32, + ClipVolume::NegOneToOne, ), + Projection::Orthographic(orthographic_proj) => { + orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne) + } }; - gl_shader_program.set_uniform_matrix_4fv(c"projection", &projection); + gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix); } fn apply_light<PointLightHolder>( @@ -687,11 +673,11 @@ fn create_light_uniform_name( } } -fn create_view(camera: &Camera, camera_pos: &Position) -> Matrix<f32, 4, 4> +fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4> { let mut view = Matrix::new(); - view.look_at(&camera_pos.position, &camera.target, &camera.global_up); + view.look_at(&camera_pos, &camera.target, &camera.global_up); view } diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl index 1bc23a4..f12b5fe 100644 --- a/engine/src/renderer/opengl/glsl/light.glsl +++ b/engine/src/renderer/opengl/glsl/light.glsl @@ -80,10 +80,10 @@ vec3 calc_specular_light( { vec3 view_direction = normalize(view_pos - frag_pos); - vec3 reflect_direction = reflect(-light_dir, norm); + vec3 halfway_direction = normalize(light_dir + view_direction); float spec = - pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess); + pow(max(dot(norm, halfway_direction), 0.0), material.shininess); return light_phong.specular * ( spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 16c1941..4a4fe86 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -8,6 +8,7 @@ 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); @@ -30,62 +31,26 @@ pub struct Texture 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 - #[allow(clippy::new_without_default)] pub fn open(path: &Path) -> Result<Self, 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::UnsupportedImageDataKind); - } - }; - - let dimensions = Dimens { - width: image.width(), - height: image.height(), - }; - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Ok(Self { - id: Id::new(id), - image, - pixel_data_format, - dimensions, - properties: Properties::default(), - }) + Self::builder().open(path) } #[must_use] pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> 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); - - Self { - id: Id::new(id), - image: image.into(), - pixel_data_format: PixelDataFormat::Rgb8, - dimensions: *dimensions, - properties: Properties::default(), - } + Self::builder().build_with_single_color(dimensions, color) } #[must_use] @@ -132,6 +97,84 @@ impl Drop for Texture } } +/// Texture builder. +#[derive(Debug, Default, Clone)] +pub struct Builder +{ + properties: Properties, +} + +impl Builder +{ + pub fn properties(mut self, properties: Properties) -> 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(), + }) + } + + #[must_use] + pub fn build_with_single_color( + &self, + dimensions: &Dimens<u32>, + color: &Color<u8>, + ) -> Texture + { + 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(), + } + } +} + /// Texture error. #[derive(Debug, thiserror::Error)] pub enum Error @@ -142,11 +185,13 @@ pub enum Error #[error("Failed to decode texture image")] DecodeImageFailed(#[source] ImageError), - #[error("Unsupported image data kind")] - UnsupportedImageDataKind, + #[error("Unsupported image data format")] + UnsupportedImageDataFormat, } +builder! { /// Texture properties +#[builder(name = PropertiesBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone)] #[non_exhaustive] pub struct Properties @@ -155,6 +200,15 @@ pub struct Properties pub magnifying_filter: Filtering, pub minifying_filter: Filtering, } +} + +impl Properties +{ + pub fn builder() -> PropertiesBuilder + { + PropertiesBuilder::default() + } +} impl Default for Properties { @@ -168,6 +222,14 @@ impl Default for Properties } } +impl Default for PropertiesBuilder +{ + fn default() -> Self + { + Properties::default().into() + } +} + /// Texture ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id diff --git a/engine/src/util.rs b/engine/src/util.rs index a505a38..0f6c78c 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + macro_rules! try_option { ($expr: expr) => { match $expr { @@ -9,9 +11,6 @@ macro_rules! try_option { }; } -use std::mem::ManuallyDrop; -use std::ops::{Deref, DerefMut}; - pub(crate) use try_option; macro_rules! or { @@ -97,36 +96,74 @@ macro_rules! builder { pub(crate) use builder; -/// Wrapper that ensures the contained value will never be dropped. -#[derive(Debug)] -pub struct NeverDrop<Value> +pub enum RefOrValue<'a, T> { - value: ManuallyDrop<Value>, + Ref(&'a T), + Value(Option<T>), } -impl<Value> NeverDrop<Value> +impl<'a, T> RefOrValue<'a, T> { - #[must_use] - pub fn new(value: Value) -> Self + pub fn get(&self) -> Option<&T> { - Self { value: ManuallyDrop::new(value) } + match self { + Self::Ref(val_ref) => Some(val_ref), + Self::Value(val_cell) => val_cell.as_ref(), + } } } -impl<Value> Deref for NeverDrop<Value> +#[derive(Debug)] +pub struct Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, { - type Target = Value; + func: Func, + pub data: Data, + _pd: PhantomData<&'func ()>, +} - fn deref(&self) -> &Self::Target +impl<'func, Func, Data> Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, +{ + pub fn new(data: Data, func: Func) -> Self { - &self.value + Self { func, data, _pd: PhantomData } } } -impl<Value> DerefMut for NeverDrop<Value> +impl<'func, Func, Data> Drop for Defer<'func, Func, Data> +where + Func: FnMut(&mut Data) + 'func, { - fn deref_mut(&mut self) -> &mut Self::Target + fn drop(&mut self) { - &mut self.value + (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/vertex.rs b/engine/src/vertex.rs index 897ee97..30640c4 100644 --- a/engine/src/vertex.rs +++ b/engine/src/vertex.rs @@ -5,13 +5,14 @@ use crate::vector::{Vec2, Vec3}; builder! { #[builder(name = Builder, derives = (Debug, Default))] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] #[repr(C)] +#[non_exhaustive] pub struct Vertex { - pos: Vec3<f32>, - texture_coords: Vec2<f32>, - normal: Vec3<f32>, + pub pos: Vec3<f32>, + pub texture_coords: Vec2<f32>, + pub normal: Vec3<f32>, } } diff --git a/engine/src/window.rs b/engine/src/window.rs index ad239a1..00c360e 100644 --- a/engine/src/window.rs +++ b/engine/src/window.rs @@ -4,16 +4,22 @@ use std::ffi::{CStr, CString}; use bitflags::bitflags; use ecs::actions::Actions; use ecs::extension::Collector as ExtensionCollector; +use ecs::phase::{Phase, PRESENT as PRESENT_PHASE, START as START_PHASE}; +use ecs::relationship::{ChildOf, Relationship}; use ecs::sole::Single; -use ecs::Sole; +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::event::{Conclude as ConcludeEvent, Start as StartEvent}; use crate::vector::Vec2; +static_entity!( + pub UPDATE_PHASE, + (Phase, <Relationship<ChildOf, Phase>>::new(*PRESENT_PHASE)) +); + #[derive(Debug, Sole)] /// Has to be dropped last since it holds the OpenGL context. #[sole(drop_last)] @@ -169,6 +175,24 @@ impl Window .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) { @@ -514,6 +538,60 @@ impl KeyState } } +#[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 { @@ -613,8 +691,8 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { - collector.add_system(StartEvent, initialize); - collector.add_system(ConcludeEvent, update); + collector.add_system(*START_PHASE, initialize); + collector.add_system(*UPDATE_PHASE, update); let window = self .window_builder diff --git a/glfw/src/window.rs b/glfw/src/window.rs index 5c30c16..1e7e777 100644 --- a/glfw/src/window.rs +++ b/glfw/src/window.rs @@ -180,8 +180,6 @@ impl Window } /// Sets the mouse button callback. - /// - /// The callback is called when a mouse button is pressed. pub fn set_mouse_button_callback( &self, callback: impl Fn(MouseButton, MouseButtonState, KeyModifiers) + 'static, diff --git a/organize_todo.py b/organize_todo.py new file mode 100644 index 0000000..95460ea --- /dev/null +++ b/organize_todo.py @@ -0,0 +1,57 @@ +from enum import Enum +from typing import List + + +TODO_FILE_NAME = "TODO.md" + +TODO_LINE_START_COMPLETED = "- [x] " +TODO_LINE_START_UNCOMPLETED = "- [ ] " + + +class PrevItemState(Enum): + COMPLETED = 0 + UNCOMPLETED = 1 + UNDETERMINED = 2 + + +def main(): + with open(TODO_FILE_NAME) as todo_file: + todo_lines = todo_file.readlines() + + completed: List[str] = [] + uncompleted: List[str] = [] + + prev_item_state = PrevItemState.UNDETERMINED + + for [index, line] in enumerate(todo_lines): + if len(line) == 0: + continue + + if line.startswith(TODO_LINE_START_COMPLETED): + completed.append(line) + prev_item_state = PrevItemState.COMPLETED + elif line.startswith(TODO_LINE_START_UNCOMPLETED): + uncompleted.append(line) + prev_item_state = PrevItemState.UNCOMPLETED + elif line[0].isspace(): + if prev_item_state == PrevItemState.COMPLETED: + completed.append(line) + elif prev_item_state == PrevItemState.UNCOMPLETED: + uncompleted.append(line) + elif prev_item_state == PrevItemState.UNDETERMINED: + print( + f"Error: todo line {index + 1} starts with whitespace but there is " + "no previous item" + ) + exit(1) + else: + print(f"Error: todo line {index + 1} does not have correct syntax") + exit(1) + + with open(TODO_FILE_NAME, "w") as todo_file: + todo_file.writelines(uncompleted) + todo_file.writelines(completed) + + +if __name__ == "__main__": + main() diff --git a/res/cube.obj b/res/cube.obj deleted file mode 100644 index 59c3fd8..0000000 --- a/res/cube.obj +++ /dev/null @@ -1,43 +0,0 @@ -o Cube -v 1.000000 1.000000 -1.000000 -v 1.000000 -1.000000 -1.000000 -v 1.000000 1.000000 1.000000 -v 1.000000 -1.000000 1.000000 -v -1.000000 1.000000 -1.000000 -v -1.000000 -1.000000 -1.000000 -v -1.000000 1.000000 1.000000 -v -1.000000 -1.000000 1.000000 -vn -0.0000 1.0000 -0.0000 -vn -0.0000 -0.0000 1.0000 -vn -1.0000 -0.0000 -0.0000 -vn -0.0000 -1.0000 -0.0000 -vn 1.0000 -0.0000 -0.0000 -vn -0.0000 -0.0000 -1.0000 -vt 0.875000 0.500000 -vt 0.625000 0.750000 -vt 0.625000 0.500000 -vt 0.375000 1.000000 -vt 0.375000 0.750000 -vt 0.625000 0.000000 -vt 0.375000 0.250000 -vt 0.375000 0.000000 -vt 0.375000 0.500000 -vt 0.125000 0.750000 -vt 0.125000 0.500000 -vt 0.625000 0.250000 -vt 0.875000 0.750000 -vt 0.625000 1.000000 -s 0 -f 5//1 3//1 1//1 -f 5/1/1 3/2/1 1/3/1 -f 3/2/2 8/4/2 4/5/2 -f 7/6/3 6/7/3 8/8/3 -f 2/9/4 8/10/4 6/11/4 -f 1/3/5 4/5/5 2/9/5 -f 5/12/6 2/9/6 6/7/6 -f 5/1/1 7/13/1 3/2/1 -f 3/2/2 7/14/2 8/4/2 -f 7/6/3 5/12/3 6/7/3 -f 2/9/4 4/5/4 8/10/4 -f 1/3/5 3/2/5 4/5/5 -f 5/12/6 1/3/6 2/9/6 diff --git a/src/main.rs b/src/main.rs index 9405abe..1d0d33a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,27 +10,31 @@ use engine::camera::fly::{ use engine::camera::{Active as ActiveCamera, Camera}; use engine::color::Color; use engine::data_types::dimens::Dimens; +use engine::ecs::phase::START as START_PHASE; use engine::ecs::sole::Single; -use engine::event::Start as StartEvent; use engine::file_format::wavefront::mtl::parse as parse_mtl; use engine::file_format::wavefront::obj::parse as parse_obj; use engine::input::Extension as InputExtension; use engine::lighting::{AttenuationParams, GlobalLight, PointLight}; use engine::material::{Builder as MaterialBuilder, Flags as MaterialFlags}; +use engine::mesh::cube::{ + create as cube_mesh_create, + CreationSpec as CubeMeshCreationSpec, +}; use engine::renderer::opengl::Extension as OpenglRendererExtension; use engine::transform::Position; use engine::vector::Vec3; use engine::window::{ Builder as WindowBuilder, - CreationHint as WindowCreationHint, - CreationHintValue as WindowCreationHintValue, CursorMode, Extension as WindowExtension, Window, }; use engine::Engine; -use tracing::Level; -use tracing_subscriber::FmtSubscriber; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; const WINDOW_SIZE: Dimens<u32> = Dimens { width: 1920, height: 1080 }; @@ -44,11 +48,14 @@ const RESOURCE_DIR: &str = "res"; fn main() -> Result<(), Box<dyn Error>> { - let subscriber = FmtSubscriber::builder() - .with_max_level(Level::TRACE) - .finish(); - - tracing::subscriber::set_global_default(subscriber)?; + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env()?, + ) + .init(); let mut engine = Engine::new(); @@ -84,8 +91,14 @@ fn main() -> Result<(), Box<dyn Error>> }) .build(), Position::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }), - parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("cube.obj"))?)? - .to_mesh()?, + 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(), )); @@ -101,17 +114,14 @@ fn main() -> Result<(), Box<dyn Error>> engine.add_sole(GlobalLight::default())?; - engine.register_system(StartEvent, prepare_window); + engine.register_system(*START_PHASE, prepare_window); engine.add_extension(OpenglRendererExtension::default()); engine.add_extension( - WindowExtension::new(WindowBuilder::default().creation_hint( - WindowCreationHint::Samples, - WindowCreationHintValue::Number(8), - )) - .window_title("Game") - .window_size(WINDOW_SIZE), + WindowExtension::new(WindowBuilder::default().multisampling_sample_count(8)) + .window_title("Game") + .window_size(WINDOW_SIZE), ); engine.add_extension(FlyCameraExtension(FlyCameraOptions { |