summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock721
-rw-r--r--Cargo.toml10
-rw-r--r--TODO.md38
-rw-r--r--ecs-macros/src/lib.rs172
-rw-r--r--ecs/Cargo.toml15
-rw-r--r--ecs/benches/query.rs141
-rw-r--r--ecs/examples/event_loop.rs34
-rw-r--r--ecs/examples/extension.rs17
-rw-r--r--ecs/examples/multiple_queries.rs10
-rw-r--r--ecs/examples/optional_component.rs13
-rw-r--r--ecs/examples/relationship.rs20
-rw-r--r--ecs/examples/simple.rs8
-rw-r--r--ecs/examples/with_local.rs22
-rw-r--r--ecs/examples/with_sole.rs28
-rw-r--r--ecs/src/actions.rs81
-rw-r--r--ecs/src/archetype.rs61
-rw-r--r--ecs/src/component.rs469
-rw-r--r--ecs/src/component/local.rs13
-rw-r--r--ecs/src/component/storage.rs1118
-rw-r--r--ecs/src/component/storage/archetype.rs387
-rw-r--r--ecs/src/component/storage/graph.rs432
-rw-r--r--ecs/src/entity.rs133
-rw-r--r--ecs/src/event.rs95
-rw-r--r--ecs/src/event/component.rs126
-rw-r--r--ecs/src/event/start.rs7
-rw-r--r--ecs/src/extension.rs16
-rw-r--r--ecs/src/lib.rs672
-rw-r--r--ecs/src/lock.rs210
-rw-r--r--ecs/src/pair.rs198
-rw-r--r--ecs/src/phase.rs13
-rw-r--r--ecs/src/query.rs622
-rw-r--r--ecs/src/query/flexible.rs84
-rw-r--r--ecs/src/query/options.rs66
-rw-r--r--ecs/src/query/term.rs115
-rw-r--r--ecs/src/relationship.rs418
-rw-r--r--ecs/src/sole.rs22
-rw-r--r--ecs/src/system.rs162
-rw-r--r--ecs/src/system/stateful.rs60
-rw-r--r--ecs/src/tuple.rs204
-rw-r--r--ecs/src/type_name.rs15
-rw-r--r--ecs/src/uid.rs216
-rw-r--r--ecs/src/util.rs285
-rw-r--r--ecs/src/util/array_vec.rs131
-rw-r--r--ecs/tests/query.rs322
-rw-r--r--engine/Cargo.toml9
-rw-r--r--engine/src/asset.rs777
-rw-r--r--engine/src/camera/fly.rs35
-rw-r--r--engine/src/collision.rs142
-rw-r--r--engine/src/data_types/dimens.rs51
-rw-r--r--engine/src/data_types/vector.rs26
-rw-r--r--engine/src/draw_flags.rs2
-rw-r--r--engine/src/event.rs27
-rw-r--r--engine/src/file_format/wavefront/mtl.rs211
-rw-r--r--engine/src/file_format/wavefront/obj.rs54
-rw-r--r--engine/src/image.rs184
-rw-r--r--engine/src/input.rs47
-rw-r--r--engine/src/lib.rs60
-rw-r--r--engine/src/lighting.rs7
-rw-r--r--engine/src/material.rs125
-rw-r--r--engine/src/math.rs2
-rw-r--r--engine/src/mesh.rs139
-rw-r--r--engine/src/mesh/cube.rs1007
-rw-r--r--engine/src/model.rs176
-rw-r--r--engine/src/opengl/buffer.rs51
-rw-r--r--engine/src/opengl/glsl.rs (renamed from engine/src/shader_preprocessor.rs)535
-rw-r--r--engine/src/opengl/mod.rs23
-rw-r--r--engine/src/opengl/shader.rs118
-rw-r--r--engine/src/opengl/texture.rs97
-rw-r--r--engine/src/opengl/vertex_array.rs31
-rw-r--r--engine/src/performance.rs59
-rw-r--r--engine/src/projection.rs118
-rw-r--r--engine/src/renderer.rs6
-rw-r--r--engine/src/renderer/opengl.rs525
-rw-r--r--engine/src/renderer/opengl/glsl/fragment.glsl (renamed from engine/fragment.glsl)0
-rw-r--r--engine/src/renderer/opengl/glsl/light.glsl (renamed from engine/light.glsl)4
-rw-r--r--engine/src/renderer/opengl/glsl/vertex.glsl (renamed from engine/vertex.glsl)0
-rw-r--r--engine/src/renderer/opengl/glsl/vertex_data.glsl (renamed from engine/vertex_data.glsl)0
-rw-r--r--engine/src/renderer/opengl/vertex.rs (renamed from engine/src/vertex.rs)32
-rw-r--r--engine/src/shader.rs186
-rw-r--r--engine/src/texture.rs191
-rw-r--r--engine/src/transform.rs6
-rw-r--r--engine/src/util.rs111
-rw-r--r--engine/src/window.rs487
-rw-r--r--engine/src/work_queue.rs44
-rw-r--r--glfw/src/window.rs2
-rw-r--r--organize_todo.py57
-rw-r--r--res/cube.obj43
-rw-r--r--src/main.rs130
88 files changed, 9111 insertions, 4828 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e7faae2..625d625 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,27 +1,51 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
-name = "adler"
-version = "1.0.2"
+name = "adler2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
-version = "1.1.1"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[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"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bindgen"
@@ -29,7 +53,7 @@ version = "0.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.9.0",
"cexpr",
"clang-sys",
"lazy_static",
@@ -38,7 +62,27 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "rustc-hash",
+ "rustc-hash 1.1.0",
+ "shlex",
+ "syn",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.71.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
+dependencies = [
+ "bitflags 2.9.0",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash 2.1.1",
"shlex",
"syn",
]
@@ -51,15 +95,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.4.0"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bytemuck"
-version = "1.14.0"
+version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
[[package]]
name = "byteorder"
@@ -68,6 +112,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,10 +133,37 @@ 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"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
@@ -94,6 +171,31 @@ dependencies = [
]
[[package]]
+name = "clap"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
+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"
@@ -101,24 +203,68 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
-version = "1.3.2"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
+[[package]]
name = "ecs"
version = "0.1.0"
dependencies = [
+ "criterion",
"ecs-macros",
+ "hashbrown",
"linkme",
+ "parking_lot",
"paste",
"seq-macro",
"thiserror",
"tracing",
"util-macros",
+ "vizoxide",
]
[[package]]
@@ -132,10 +278,16 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "engine"
version = "0.1.0"
dependencies = [
- "bitflags 2.4.0",
+ "bitflags 2.9.0",
"ecs",
"gl",
"glfw",
@@ -144,34 +296,41 @@ dependencies = [
"seq-macro",
"thiserror",
"tracing",
+ "util-macros",
]
[[package]]
name = "equivalent"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fdeflate"
-version = "0.3.1"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
-version = "1.0.28"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
name = "game-newest"
version = "0.1.0"
dependencies = [
@@ -204,8 +363,8 @@ dependencies = [
name = "glfw"
version = "0.1.0"
dependencies = [
- "bindgen",
- "bitflags 2.4.0",
+ "bindgen 0.68.1",
+ "bitflags 2.9.0",
"libc",
"thiserror",
"util-macros",
@@ -213,46 +372,101 @@ dependencies = [
[[package]]
name = "glob"
-version = "0.3.1"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+
+[[package]]
+name = "graphviz-sys"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97dbac5b269c7160b78812a9873f548668aa34b62f28f7a64328a6cd94feb47d"
+dependencies = [
+ "bindgen 0.71.1",
+]
+
+[[package]]
+name = "half"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
[[package]]
name = "hashbrown"
-version = "0.14.3"
+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.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
[[package]]
name = "image"
-version = "0.24.7"
+version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
+checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"jpeg-decoder",
- "num-rational",
"num-traits",
"png",
]
[[package]]
name = "indexmap"
-version = "2.2.6"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[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.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
name = "jpeg-decoder"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
+checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
[[package]]
name = "khronos_api"
@@ -262,9 +476,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
@@ -274,34 +488,34 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.148"
+version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libloading"
-version = "0.7.4"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
- "winapi",
+ "windows-targets",
]
[[package]]
name = "linkme"
-version = "0.3.29"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70fe496a7af8c406f877635cbf3cd6a9fac9d6f443f58691cd8afe6ce0971af4"
+checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe"
dependencies = [
"linkme-impl",
]
[[package]]
name = "linkme-impl"
-version = "0.3.29"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b01f197a15988fb5b2ec0a5a9800c97e70771499c456ad757d63b3c5e9b96e75"
+checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be"
dependencies = [
"proc-macro2",
"quote",
@@ -309,16 +523,35 @@ 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"
+version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+
+[[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"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
@@ -328,11 +561,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.7.1"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
- "adler",
+ "adler2",
"simd-adler32",
]
@@ -357,40 +590,25 @@ dependencies = [
]
[[package]]
-name = "num-integer"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-rational"
-version = "0.4.1"
+name = "num-traits"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
- "num-integer",
- "num-traits",
]
[[package]]
-name = "num-traits"
-version = "0.2.17"
+name = "once_cell"
+version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
-dependencies = [
- "autocfg",
-]
+checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
-name = "once_cell"
-version = "1.18.0"
+name = "oorandom"
+version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "overload"
@@ -399,10 +617,33 @@ 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"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "peeking_take_while"
@@ -412,15 +653,15 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pin-project-lite"
-version = "0.2.13"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "png"
-version = "0.17.10"
+version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
@@ -430,51 +671,85 @@ dependencies = [
]
[[package]]
+name = "prettyplease"
+version = "0.2.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.35"
+version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
+name = "redox_syscall"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
+dependencies = [
+ "bitflags 2.9.0",
+]
+
+[[package]]
name = "regex"
-version = "1.9.6"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
- "regex-automata",
- "regex-syntax",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
-version = "0.3.9"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax",
+ "regex-syntax 0.8.5",
]
[[package]]
name = "regex-syntax"
-version = "0.7.5"
+version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
@@ -483,25 +758,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[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"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
+checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
[[package]]
name = "serde"
-version = "1.0.197"
+version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.197"
+version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@@ -509,10 +811,22 @@ dependencies = [
]
[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
name = "serde_spanned"
-version = "0.6.5"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
@@ -528,9 +842,9 @@ dependencies = [
[[package]]
name = "shlex"
-version = "1.2.0"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
@@ -540,15 +854,15 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "smallvec"
-version = "1.11.1"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "syn"
-version = "2.0.51"
+version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -557,18 +871,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.49"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
@@ -577,19 +891,29 @@ dependencies = [
[[package]]
name = "thread_local"
-version = "1.1.7"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[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"
+version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
+checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
@@ -599,18 +923,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.6.5"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
-version = "0.22.9"
+version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
+checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
@@ -621,9 +945,9 @@ dependencies = [
[[package]]
name = "tracing"
-version = "0.1.39"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@@ -632,9 +956,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
@@ -643,44 +967,35 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
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]]
name = "tracing-subscriber"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
+ "matchers",
"nu-ansi-term",
+ "once_cell",
+ "regex",
"sharded-slab",
"smallvec",
"thread_local",
+ "tracing",
"tracing-core",
- "tracing-log",
]
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "util-macros"
@@ -692,10 +1007,24 @@ dependencies = [
]
[[package]]
-name = "valuable"
-version = "0.1.0"
+name = "vizoxide"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec190047a5e02a6423f147cf40890be1d37bbc68b9eba441689acbcd255f942"
+dependencies = [
+ "base64",
+ "graphviz-sys",
+]
+
+[[package]]
+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"
@@ -714,22 +1043,104 @@ 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",
+]
+
+[[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.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"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
+checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
dependencies = [
"memchr",
]
[[package]]
name = "xml-rs"
-version = "0.8.19"
+version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
diff --git a/Cargo.toml b/Cargo.toml
index 5eacd0c..e14b33c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,10 @@ edition = "2021"
members = ["glfw", "engine", "ecs", "ecs-macros", "util-macros"]
[dependencies]
-engine = { path = "./engine", features = ["debug"] }
-tracing = "0.1.39"
-tracing-subscriber = "0.3.17"
+engine = { path = "./engine" }
+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"]
diff --git a/TODO.md b/TODO.md
index 18c1952..ecbb8e7 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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
+ - [x] 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
+- [x] Use Blinn-phong lighting instead of phong lighting
- [x] Support for multiple textures
- [x] Non-hardcoded projection settings
- [x] Model importing
@@ -9,17 +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
-- [ ] Audio
-- [ ] Transparent/translucent models
-- [ ] Animations
-- [ ] Physics
-- [ ] Rotation (using quaternions)
-- [ ] Add support for entity tags in ECS framework
-
+- [x] New texture IDs are created for no reason. Crashes when on texture ID 31
+- [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..7d00736 100644
--- a/ecs-macros/src/lib.rs
+++ b/ecs-macros/src/lib.rs
@@ -1,3 +1,4 @@
+#![deny(clippy::all, clippy::pedantic)]
use std::path::PathBuf as FsPathBuf;
use proc_macro::TokenStream;
@@ -6,7 +7,6 @@ use syn::spanned::Spanned;
use syn::{
parse,
Attribute,
- GenericParam,
Generics,
Ident,
Item,
@@ -14,7 +14,6 @@ use syn::{
ItemStruct,
ItemUnion,
Path,
- Type,
};
use toml::value::{Table as TomlTable, Value as TomlValue};
@@ -42,60 +41,35 @@ macro_rules! syn_path_segment {
};
}
-#[proc_macro_derive(Component, attributes(component))]
+/// Generates a `Component` implementation.
+///
+/// # Panics
+/// Will panic if:
+/// - Not attributed to a type item
+/// - The attributed-to type item is generic
+/// - If parsing the user crate's `Cargo.toml` file fails.
+#[proc_macro_derive(Component)]
pub fn component_derive(input: TokenStream) -> TokenStream
{
let item: TypeItem = parse::<Item>(input).unwrap().try_into().unwrap();
- let ComponentAttribute { ref_type, ref_mut_type } = item
- .attribute::<ComponentAttribute>("component")
- .unwrap_or_default();
-
let item_ident = item.ident();
let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl();
let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs));
- let (id_or_ids, get_id) = if !item.generics().params.is_empty() {
- let id_lut_ident =
- format_ident!("{}_ID_LUT", item_ident.to_string().to_uppercase());
+ assert!(
+ item.generics().params.is_empty(),
+ "Generic types are not supported as components"
+ );
- let id_lut = quote! {
- static #id_lut_ident: LazyLock<Mutex<HashMap<TypeId, Uid>>> =
- LazyLock::new(|| Mutex::new(HashMap::new()));
- };
+ let id_var_ident = format_ident!("{}_ID", item_ident.to_string().to_uppercase());
- let generics = item.generics().params.iter().map(|param| match param {
- GenericParam::Type(type_param) => type_param.ident.clone(),
- GenericParam::Lifetime(_) => panic!("Lifetime generics are not supported"),
- GenericParam::Const(_) => panic!("Const generics are not supported"),
+ let id_var = quote! {
+ static #id_var_ident: LazyLock<Uid> = LazyLock::new(|| {
+ Uid::new_unique(UidKind::Component)
});
-
- let get_id = quote! {
- *#id_lut_ident
- .try_lock()
- .unwrap()
- .entry(TypeId::of::<(#(#generics,)*)>())
- .or_insert_with(|| Uid::new_unique(UidKind::Component))
- };
-
- (id_lut, get_id)
- } else {
- let id_lazylock_ident =
- format_ident!("{}_ID", item_ident.to_string().to_uppercase());
-
- let id_lazylock = quote! {
- static #id_lazylock_ident: LazyLock<Uid> = LazyLock::new(|| {
- Uid::new_unique(UidKind::Component)
- });
- };
-
- let get_id = quote! {
- *#id_lazylock_ident
- };
-
- (id_lazylock, get_id)
};
let mod_ident = format_ident!(
@@ -107,45 +81,26 @@ pub fn component_derive(input: TokenStream) -> TokenStream
mod #mod_ident {
use ::std::any::{Any, TypeId};
use ::std::sync::{LazyLock, Mutex};
- use ::std::collections::HashMap;
use #ecs_path::component::Component;
- use #ecs_path::system::ComponentRefMut;
- use #ecs_path::system::ComponentRef;
use #ecs_path::uid::{Uid, Kind as UidKind};
use #ecs_path::system::Input as SystemInput;
- use #ecs_path::type_name::TypeName;
use super::*;
- #id_or_ids
+ #id_var
impl #impl_generics Component for #item_ident #type_generics
#where_clause
{
- type Component = Self;
-
- type RefMut<'component> = #ref_mut_type;
- type Ref<'component> = #ref_type;
-
fn id() -> Uid
{
- #get_id
- }
-
- fn self_id(&self) -> Uid
- {
- Self::id()
+ *#id_var_ident
}
- fn as_any_mut(&mut self) -> &mut dyn Any
+ fn name(&self) -> &'static str
{
- self
- }
-
- fn as_any(&self) -> &dyn Any
- {
- self
+ std::any::type_name::<Self>()
}
}
@@ -153,20 +108,16 @@ pub fn component_derive(input: TokenStream) -> TokenStream
#where_clause
{
}
-
- impl #impl_generics TypeName for #item_ident #type_generics
- #where_clause
- {
- fn type_name(&self) -> &'static str
- {
- std::any::type_name::<Self>()
- }
- }
}
}
.into()
}
+/// Generates a `Sole` implementation.
+///
+/// # Panics
+/// Will panic if not attributed to a type item or if parsing the user crate's
+/// `Cargo.toml` file fails.
#[proc_macro_derive(Sole, attributes(sole))]
pub fn sole_derive(input: TokenStream) -> TokenStream
{
@@ -201,15 +152,6 @@ pub fn sole_derive(input: TokenStream) -> TokenStream
self
}
}
-
- impl #impl_generics #ecs_path::type_name::TypeName for #item_ident #type_generics
- #where_clause
- {
- fn type_name(&self) -> &'static str
- {
- std::any::type_name::<Self>()
- }
- }
}
.into()
}
@@ -248,9 +190,7 @@ impl TypeItem
let mut attr: Option<&Attribute> = None;
for item_attr in item_attrs {
- if attr.is_some() {
- panic!("Expected only one {} attribute", attr_ident);
- }
+ assert!(attr.is_none(), "Expected only one {attr_ident} attribute");
if item_attr.path().get_ident()? == attr_ident {
attr = Some(item_attr);
@@ -374,61 +314,3 @@ fn find_engine_ecs_crate_path() -> Option<Path>
None
})
}
-
-#[derive(Debug)]
-struct ComponentAttribute
-{
- ref_type: proc_macro2::TokenStream,
- ref_mut_type: proc_macro2::TokenStream,
-}
-
-impl FromAttribute for ComponentAttribute
-{
- fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error>
- {
- let mut ref_type: Option<Type> = None;
- let mut ref_mut_type: Option<Type> = None;
-
- attribute.parse_nested_meta(|meta| {
- let Some(flag) = meta.path.get_ident() else {
- return Err(meta.error("Not a single identifier"));
- };
-
- if flag == "ref_type" {
- let value = meta.value()?;
-
- ref_type = Some(value.parse::<Type>()?);
-
- return Ok(());
- } else if flag == "ref_mut_type" {
- let value = meta.value()?;
-
- ref_mut_type = Some(value.parse::<Type>()?);
-
- return Ok(());
- }
-
- Err(meta.error("Unrecognized token"))
- })?;
-
- Ok(Self {
- ref_type: ref_type
- .map(|ref_type| ref_type.into_token_stream())
- .unwrap_or_else(|| Self::default().ref_type),
- ref_mut_type: ref_mut_type
- .map(|ref_mut_type| ref_mut_type.into_token_stream())
- .unwrap_or_else(|| Self::default().ref_mut_type),
- })
- }
-}
-
-impl Default for ComponentAttribute
-{
- fn default() -> Self
- {
- Self {
- ref_type: quote! { ComponentRef<'component, Self> },
- ref_mut_type: quote! { ComponentRefMut<'component, Self> },
- }
- }
-}
diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml
index 342f087..cf35a74 100644
--- a/ecs/Cargo.toml
+++ b/ecs/Cargo.toml
@@ -4,14 +4,25 @@ version = "0.1.0"
edition = "2021"
[features]
-debug = ["dep:tracing"]
+vizoxide = ["dep:vizoxide"]
[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" }
+vizoxide = { version = "1.0.5", optional = true }
+[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..61d7ba4 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::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
+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,19 @@ fn age(query: Query<(Health, Name)>, mut actions: Actions)
}
}
-#[derive(Debug)]
-struct EventA;
+static_entity!(SHEER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE)));
-impl Event for EventA {}
+static_entity!(FEED_PHASE, (Phase, Pair::new::<ChildOf>(*SHEER_PHASE)));
-#[derive(Debug)]
-struct EventB;
-
-impl Event for EventB {}
-
-#[derive(Debug)]
-struct EventC;
-
-impl Event for EventC {}
+static_entity!(AGE_PHASE, (Phase, Pair::new::<ChildOf>(*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 +85,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..ebc9115 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..b607398 100644
--- a/ecs/examples/relationship.rs
+++ b/ecs/examples/relationship.rs
@@ -1,5 +1,6 @@
-use ecs::event::start::Start as StartEvent;
-use ecs::relationship::Relationship;
+use ecs::pair::Pair;
+use ecs::phase::START as START_PHASE;
+use ecs::uid::Wildcard;
use ecs::{Component, Query, World};
#[derive(Component)]
@@ -17,14 +18,19 @@ struct Health
health: u32,
}
+#[derive(Component)]
struct Holding;
-fn print_player_stats(player_query: Query<(Player, Health, Relationship<Holding, Sword>)>)
+fn print_player_stats(player_query: Query<(&Player, &Health, Pair<Holding, Wildcard>)>)
{
for (_, health, sword_relationship) in &player_query {
println!("Player health: {}", health.health);
- if let Some(sword) = sword_relationship.get(0) {
+ if let Some(sword_ent) = sword_relationship.get_target_entity() {
+ let sword = sword_ent
+ .get::<Sword>()
+ .expect("Sword entity is missing sword component");
+
println!("Player sword attack strength: {}", sword.attack_strength);
}
}
@@ -34,15 +40,15 @@ 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 },));
world.create_entity((
Player,
Health { health: 180 },
- Relationship::<Holding, Sword>::new(sword_uid),
+ Pair::new::<Holding>(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..c3feaab 100644
--- a/ecs/examples/with_sole.rs
+++ b/ecs/examples/with_sole.rs
@@ -1,6 +1,7 @@
-use ecs::event::Event;
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
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, Pair::new::<ChildOf>(*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..3dd8755 100644
--- a/ecs/src/actions.rs
+++ b/ecs/src/actions.rs
@@ -1,12 +1,8 @@
use std::marker::PhantomData;
-use std::sync::{Arc, Weak};
-
-use crate::component::{
- Component,
- Metadata as ComponentMetadata,
- Sequence as ComponentSequence,
-};
-use crate::system::{NoInitParamFlag, Param as SystemParam, System};
+use std::rc::{Rc, Weak};
+
+use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence};
+use crate::system::{Param as SystemParam, System};
use crate::uid::{Kind as UidKind, Uid};
use crate::{ActionQueue, World};
@@ -20,36 +16,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_parts_array().into()));
}
- /// Adds component(s) to a entity.
+ /// Queues up despawning a entity at the end of the **next** 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_parts_array().into(),
+ ));
}
- /// Removes component(s) from a entity.
- pub fn remove_components<Comps>(&mut self, entity_uid: Uid)
- where
- Comps: ComponentSequence,
+ /// Queues up removing component(s) from a entity at the end of the **next** tick.
+ pub fn remove_components(
+ &mut self,
+ entity_uid: Uid,
+ component_ids: impl IntoIterator<Item = Uid>,
+ )
{
debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
- self.action_queue
- .push(Action::RemoveComponents(entity_uid, Comps::metadata()));
+ let mut component_ids = component_ids.into_iter().peekable();
+
+ if component_ids.peek().is_none() {
+ return;
+ }
+
+ self.action_queue.push(Action::RemoveComponents(
+ entity_uid,
+ component_ids.collect(),
+ ));
}
- /// 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);
@@ -66,18 +87,17 @@ impl<'world> Actions<'world>
}
}
- fn new(action_queue: &'world Arc<ActionQueue>) -> Self
+ fn new(action_queue: &'world Rc<ActionQueue>) -> Self
{
Self {
action_queue,
- action_queue_weak: Arc::downgrade(action_queue),
+ action_queue_weak: Rc::downgrade(action_queue),
}
}
}
-unsafe impl<'world> SystemParam<'world> for Actions<'world>
+impl<'world> SystemParam<'world> for Actions<'world>
{
- type Flags = NoInitParamFlag;
type Input = ();
fn initialize<SystemImpl>(
@@ -122,11 +142,11 @@ impl WeakRef
#[derive(Debug, Clone)]
pub struct Ref<'weak_ref>
{
- action_queue: Arc<ActionQueue>,
+ action_queue: Rc<ActionQueue>,
_pd: PhantomData<&'weak_ref ()>,
}
-impl<'weak_ref> Ref<'weak_ref>
+impl Ref<'_>
{
#[must_use]
pub fn to_actions(&self) -> Actions<'_>
@@ -139,8 +159,9 @@ impl<'weak_ref> Ref<'weak_ref>
#[derive(Debug)]
pub(crate) enum Action
{
- Spawn(Vec<Box<dyn Component>>),
- AddComponents(Uid, Vec<Box<dyn Component>>),
- RemoveComponents(Uid, Vec<ComponentMetadata>),
+ Spawn(Vec<ComponentParts>),
+ Despawn(Uid),
+ AddComponents(Uid, Vec<ComponentParts>),
+ RemoveComponents(Uid, Vec<Uid>),
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..5a8cd0b 100644
--- a/ecs/src/component.rs
+++ b/ecs/src/component.rs
@@ -1,79 +1,53 @@
use std::any::{type_name, Any};
use std::fmt::Debug;
+use std::ops::{Deref, DerefMut};
+use ecs_macros::Component;
+use hashbrown::HashSet;
use seq_macro::seq;
-use crate::lock::{ReadGuard, WriteGuard};
-use crate::system::{ComponentRef, ComponentRefMut, Input as SystemInput};
-use crate::type_name::TypeName;
+use crate::lock::{
+ Error as LockError,
+ MappedReadGuard,
+ MappedWriteGuard,
+ ReadGuard,
+ WriteGuard,
+};
+use crate::system::Input as SystemInput;
use crate::uid::Uid;
-use crate::{EntityComponent, World};
+use crate::util::Array;
+use crate::EntityComponentRef;
pub mod local;
pub(crate) mod storage;
-pub trait Component: SystemInput + Any + TypeName
+pub trait Component: SystemInput + Any
{
- /// The component type in question. Will usually be `Self`
- type Component: Component
- where
- Self: Sized;
-
- type RefMut<'component>
- where
- Self: Sized;
-
- type Ref<'component>
- where
- Self: Sized;
-
/// Returns the ID of this component.
fn id() -> Uid
where
Self: Sized;
- /// The ID of the component `self`. Returns the same value as [`Component::id`].
- fn self_id(&self) -> 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
- {
- IsOptional::No
- }
-
- /// Returns whether this component is optional.
- #[must_use]
- fn is_optional() -> IsOptional
- where
- Self: Sized,
- {
- IsOptional::No
- }
+ /// Returns the name of this component.
+ fn name(&self) -> &'static str;
}
impl dyn Component
{
pub fn downcast_mut<Real: 'static>(&mut self) -> Option<&mut Real>
{
- self.as_any_mut().downcast_mut()
+ (self as &mut dyn Any).downcast_mut()
}
pub fn downcast_ref<Real: 'static>(&self) -> Option<&Real>
{
- self.as_any().downcast_ref()
+ (self as &dyn Any).downcast_ref()
}
pub fn is<Other: 'static>(&self) -> bool
{
- self.as_any().is::<Other>()
+ (self as &dyn Any).is::<Other>()
}
}
@@ -85,249 +59,292 @@ impl Debug for dyn Component
}
}
-impl TypeName for Box<dyn Component>
+/// A sequence of components.
+pub trait Sequence
{
- fn type_name(&self) -> &'static str
- {
- self.as_ref().type_name()
- }
+ /// The number of components in this component sequence.
+ const COUNT: usize;
+
+ type PartsArray: Array<Parts>;
+
+ fn into_parts_array(self) -> Self::PartsArray;
}
-impl<ComponentT> Component for Option<ComponentT>
-where
- ComponentT: Component,
+#[derive(Debug)]
+pub struct Handle<'a, ComponentData: 'static>
{
- type Component = ComponentT;
- type Ref<'component> = Option<ComponentRef<'component, ComponentT>>;
- type RefMut<'component> = Option<ComponentRefMut<'component, ComponentT>>;
+ inner: MappedReadGuard<'a, ComponentData>,
+}
- fn id() -> Uid
+impl<'comp, ComponentData: 'static> Handle<'comp, ComponentData>
+{
+ /// Creates a new handle instance from a [`EntityComponentRef`].
+ ///
+ /// # Errors
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
{
- ComponentT::id()
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .read_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
}
- fn self_id(&self) -> Uid
+ pub(crate) fn new(inner: ReadGuard<'comp, Box<dyn Any>>) -> Self
{
- Self::id()
+ Self {
+ inner: inner.map(|component| {
+ component
+ .downcast_ref::<ComponentData>()
+ .unwrap_or_else(|| {
+ panic!(
+ "Failed to downcast component to type {}",
+ type_name::<ComponentData>()
+ );
+ })
+ }),
+ }
}
+}
+
+impl<ComponentData: 'static> Deref for Handle<'_, ComponentData>
+{
+ type Target = ComponentData;
- fn as_any_mut(&mut self) -> &mut dyn Any
+ fn deref(&self) -> &Self::Target
{
- self
+ &self.inner
}
+}
- fn as_any(&self) -> &dyn Any
+#[derive(Debug)]
+pub struct HandleMut<'a, ComponentData: 'static>
+{
+ inner: MappedWriteGuard<'a, ComponentData>,
+}
+
+impl<'comp, ComponentData: 'static> HandleMut<'comp, ComponentData>
+{
+ /// Creates a new handle instance from a [`EntityComponentRef`].
+ ///
+ /// # Errors
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
{
- self
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .write_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
}
- fn self_is_optional(&self) -> IsOptional
+ pub(crate) fn new(inner: WriteGuard<'comp, Box<dyn Any>>) -> Self
{
- Self::is_optional()
+ Self {
+ inner: inner.map(|component| {
+ component
+ .downcast_mut::<ComponentData>()
+ .unwrap_or_else(|| {
+ panic!(
+ "Failed to downcast component to type {}",
+ type_name::<ComponentData>()
+ );
+ })
+ }),
+ }
}
+}
- fn is_optional() -> IsOptional
+impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData>
+{
+ type Target = ComponentData;
+
+ fn deref(&self) -> &Self::Target
{
- IsOptional::Yes
+ &self.inner
}
}
-impl<ComponentT> TypeName for Option<ComponentT>
-where
- ComponentT: Component,
+impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData>
{
- fn type_name(&self) -> &'static str
+ fn deref_mut(&mut self) -> &mut Self::Target
{
- type_name::<Self>()
+ &mut self.inner
}
}
-impl<ComponentT> SystemInput for Option<ComponentT> where ComponentT: Component {}
+#[derive(Debug, thiserror::Error)]
+pub enum HandleError
+{
+ #[error(transparent)]
+ AcquireLockFailed(#[from] AcquireLockError),
+}
-/// A sequence of components.
-pub trait Sequence
+#[derive(Debug, thiserror::Error)]
+#[error("Failed to acquire component lock")]
+pub struct AcquireLockError(#[source] LockError);
+
+macro_rules! inner {
+ ($c: tt) => {
+ seq!(I in 0..=$c {
+ impl<#(IntoCompParts~I: IntoParts,)*> Sequence for (#(IntoCompParts~I,)*)
+ {
+ const COUNT: usize = $c + 1;
+
+ type PartsArray = [Parts; $c + 1];
+
+ fn into_parts_array(self) -> Self::PartsArray
+ {
+ [#({
+ self.I.into_parts()
+ },)*]
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..=16 {
+ inner!(C);
+});
+
+impl Sequence for ()
{
- type MutRefs<'component>
- where
- Self: 'component;
+ type PartsArray = [Parts; 0];
- type Refs<'component>
- where
- Self: 'component;
-
- fn into_vec(self) -> Vec<Box<dyn Component>>;
-
- fn metadata() -> Vec<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 from_components<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> ReadGuard<'_, Box<dyn Component>>,
- ) -> Self::Refs<'component>;
+ const COUNT: usize = 0;
+
+ fn into_parts_array(self) -> Self::PartsArray
+ {
+ []
+ }
+}
+
+pub trait IntoParts
+{
+ fn into_parts(self) -> Parts;
+}
+
+impl<ComponentT> IntoParts for ComponentT
+where
+ ComponentT: Component,
+{
+ fn into_parts(self) -> Parts
+ {
+ Parts::builder()
+ .name(type_name::<Self>())
+ .build(Self::id(), self)
+ }
}
-/// [`Component`] metadata.
-#[derive(Debug, Clone)]
+/// The parts of a component.
+#[derive(Debug)]
#[non_exhaustive]
-pub struct Metadata
+pub struct Parts
{
- pub id: Uid,
- pub is_optional: IsOptional,
+ id: Uid,
+ name: &'static str,
+ data: Box<dyn Any>,
}
-impl Metadata
+impl Parts
{
- pub fn get<ComponentT: Component + ?Sized>(component: &ComponentT) -> Self
+ #[must_use]
+ pub fn id(&self) -> Uid
{
- Self {
- id: component.self_id(),
- is_optional: component.self_is_optional(),
- }
+ self.id
}
- pub fn of<ComponentT: Component>() -> Self
+ #[must_use]
+ pub fn name(&self) -> &'static str
{
- Self {
- id: ComponentT::id(),
- is_optional: ComponentT::is_optional(),
- }
+ self.name
+ }
+
+ #[must_use]
+ pub fn builder() -> PartsBuilder
+ {
+ PartsBuilder::default()
+ }
+
+ pub(crate) fn into_data(self) -> Box<dyn Any>
+ {
+ self.data
}
}
-/// Whether or not a `Component` is optional.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum IsOptional
+#[derive(Debug)]
+pub struct PartsBuilder
{
- Yes,
- No,
+ name: &'static str,
}
-impl From<bool> for IsOptional
+impl PartsBuilder
{
- fn from(is_optional: bool) -> Self
+ #[must_use]
+ pub fn name(mut self, name: &'static str) -> Self
{
- if is_optional {
- return IsOptional::Yes;
- }
+ self.name = name;
+ self
+ }
- IsOptional::No
+ #[must_use]
+ pub fn build<Data: 'static>(self, id: Uid, data: Data) -> Parts
+ {
+ Parts {
+ id,
+ name: self.name,
+ data: Box::new(data),
+ }
}
}
-pub trait FromOptionalMut<'comp>
+impl Default for PartsBuilder
{
- fn from_optional_mut_component(
- optional_component: Option<WriteGuard<'comp, Box<dyn Component>>>,
- world: &'comp World,
- ) -> Self;
+ fn default() -> Self
+ {
+ Self { name: "(unspecified)" }
+ }
}
-pub trait FromOptional<'comp>
+/// Pending component removals for a entity.
+#[derive(Debug, Clone, Component)]
+pub struct Removals
{
- fn from_optional_component(
- optional_component: Option<ReadGuard<'comp, Box<dyn Component>>>,
- world: &'comp World,
- ) -> Self;
+ component_ids: HashSet<Uid>,
}
-macro_rules! inner {
- ($c: tt) => {
- seq!(I in 0..=$c {
- impl<#(Comp~I: Component,)*> Sequence for (#(Comp~I,)*)
- where
- #(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;
-
- type Refs<'component> = (#(Comp~I::Ref<'component>,)*)
- where Self: 'component;
-
- fn into_vec(self) -> Vec<Box<dyn Component>>
- {
- Vec::from_iter([#(Box::new(self.I) as Box<dyn Component>,)*])
- }
-
- fn metadata() -> Vec<Metadata>
- {
- vec![
- #(
- Metadata {
- id: Comp~I::id(),
- 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>
- {
- #(
- let mut comp_~I: Option<WriteGuard<Box<dyn Component>>> = None;
- )*
-
- for comp in components {
- #(
- if comp.id == Comp~I::Component::id() {
- comp_~I = Some(lock_component(comp));
- continue;
- }
- )*
- }
-
- (#(
- Comp~I::RefMut::from_optional_mut_component(comp_~I, world),
- )*)
- }
+impl Removals
+{
+ pub fn contains<ComponentT: Component>(&self) -> bool
+ {
+ self.contains_id(ComponentT::id())
+ }
- fn from_components<'component>(
- components: impl Iterator<Item = &'component EntityComponent>,
- world: &'component World,
- lock_component: fn(
- entity_component: &EntityComponent,
- ) -> ReadGuard<'_, Box<dyn Component>>,
- ) -> Self::Refs<'component>
- {
+ pub fn contains_id(&self, component_id: Uid) -> bool
+ {
+ self.component_ids.contains(&component_id)
+ }
- #(
- 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),
- )*)
- }
- }
- });
- };
+ pub(crate) fn add_ids(&mut self, ids: impl IntoIterator<Item = Uid>)
+ {
+ self.component_ids.extend(ids)
+ }
}
-seq!(C in 0..=64 {
- inner!(C);
-});
+impl FromIterator<Uid> for Removals
+{
+ fn from_iter<T: IntoIterator<Item = Uid>>(iter: T) -> Self
+ {
+ Self {
+ component_ids: iter.into_iter().collect(),
+ }
+ }
+}
diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs
index 20627bf..0f6f641 100644
--- a/ecs/src/component/local.rs
+++ b/ecs/src/component/local.rs
@@ -1,21 +1,20 @@
use std::ops::{Deref, DerefMut};
-use crate::component::Component;
-use crate::system::{ComponentRefMut, Param as SystemParam, System};
+use crate::component::{Component, HandleMut as ComponentHandleMut};
+use crate::system::{Param as SystemParam, System};
use crate::World;
/// Holds a component which is local to a single system.
#[derive(Debug)]
pub struct Local<'world, LocalComponent: Component>
{
- local_component: ComponentRefMut<'world, LocalComponent>,
+ local_component: ComponentHandleMut<'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>(
@@ -39,7 +38,7 @@ where
}
}
-impl<'world, LocalComponent> Deref for Local<'world, LocalComponent>
+impl<LocalComponent> Deref for Local<'_, LocalComponent>
where
LocalComponent: Component,
{
@@ -51,7 +50,7 @@ where
}
}
-impl<'world, LocalComponent> DerefMut for Local<'world, LocalComponent>
+impl<LocalComponent> DerefMut for Local<'_, LocalComponent>
where
LocalComponent: Component,
{
diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs
index ffd682e..b27b552 100644
--- a/ecs/src/component/storage.rs
+++ b/ecs/src/component/storage.rs
@@ -1,621 +1,787 @@
-use std::any::type_name;
-use std::borrow::Borrow;
+use std::any::Any;
+use std::array::IntoIter as ArrayIter;
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::vec::IntoIter as VecIntoIter;
+
+use hashbrown::HashMap;
+
+use crate::component::storage::archetype::{
+ Archetype,
+ Entity as ArchetypeEntity,
+ EntityComponent as ArchetypeEntityComponent,
+ Id as ArchetypeId,
};
-use crate::type_name::TypeName;
-use crate::uid::Uid;
-use crate::util::Sortable;
-use crate::EntityComponent;
+use crate::component::storage::graph::{
+ ArchetypeAddEdgeDfsIter,
+ ArchetypeAddEdgeDfsIterResult,
+ ArchetypeEdges,
+ Graph,
+};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{BorrowedOrOwned, Either, StreamingIterator, VecExt};
-#[derive(Debug, Default)]
-pub struct Storage
+pub mod archetype;
+
+mod graph;
+
+#[derive(Debug)]
+pub struct ArchetypeSearchTerms<'a>
{
- archetypes: Vec<Archetype>,
- archetype_lookup: RefCell<HashMap<ArchetypeId, ArchetypeLookupEntry>>,
- entity_archetype_lookup: HashMap<Uid, ArchetypeId>,
+ pub required_components: &'a [Uid],
+ pub excluded_components: &'a [Uid],
}
-impl Storage
+impl ArchetypeSearchTerms<'_>
{
- pub fn find_entities<CompMetadataList>(
- &self,
- mut components_metadata: CompMetadataList,
- ) -> ArchetypeRefIter<'_>
- where
- CompMetadataList: Sortable<Item = ComponentMetadata>,
- CompMetadataList: AsRef<[ComponentMetadata]>,
+ fn excluded_contains(&self, comp_id: Uid) -> bool
{
- components_metadata.sort_by_key_b(|component_metadata| component_metadata.id);
+ let comp_id_kind = comp_id.kind();
- let archetype_id = ArchetypeId::from_components_metadata(&components_metadata);
+ debug_assert!(
+ comp_id_kind == UidKind::Component
+ || (comp_id_kind == UidKind::Pair
+ && comp_id.target_component() != Uid::wildcard())
+ );
- // 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);
- }
+ let is_found = self.excluded_components.binary_search(&comp_id).is_ok();
- let comp_ids_set = create_non_opt_component_id_set(components_metadata.as_ref());
+ if !is_found && comp_id_kind == UidKind::Pair {
+ return self.excluded_components.iter().any(|excluded_comp_id| {
+ excluded_comp_id.kind() == UidKind::Pair
+ && excluded_comp_id.has_same_relation_as(comp_id)
+ && excluded_comp_id.target_component() == Uid::wildcard()
+ });
+ }
- let matching_archetype_indices = self
- .archetypes
- .iter()
- .enumerate()
- .filter_map(|(index, archetype)| {
- if archetype.component_ids_is_superset(&comp_ids_set) {
- return Some(index);
- }
+ is_found
+ }
- None
- })
- .collect();
-
- self.archetype_lookup.borrow_mut().insert(
- archetype_id,
- ArchetypeLookupEntry {
- component_ids: comp_ids_set,
- archetype_indices: matching_archetype_indices,
- },
- );
+ fn contains_conflicting(&self) -> bool
+ {
+ self.excluded_components.iter().any(|excluded_comp_id| {
+ self.required_components
+ .binary_search(excluded_comp_id)
+ .is_ok()
+ })
+ }
- self.iter_archetypes_by_lookup(archetype_id)
+ fn archetype_contains_all_required(&self, archetype: &Archetype) -> bool
+ {
+ self.required_components
+ .iter()
+ .all(|comp_id| archetype.contains_matching_component(*comp_id))
}
+}
- pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype>
+#[derive(Debug, Default)]
+pub struct Storage
+{
+ graph: Graph,
+ entity_archetype_lookup: HashMap<Uid, ArchetypeId>,
+ imaginary_archetypes: RefCell<Vec<ImaginaryArchetype>>,
+}
+
+impl Storage
+{
+ pub fn search_archetypes<'search_terms>(
+ &self,
+ search_terms: ArchetypeSearchTerms<'search_terms>,
+ ) -> ArchetypeRefIter<'_, 'search_terms>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ let archetype_id = ArchetypeId::new(search_terms.required_components);
+
+ if search_terms.contains_conflicting() {
+ return ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::B(Vec::new().into_iter()),
+ dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &[]),
+ search_terms,
+ };
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ let Some(add_edge_recursive_iter) =
+ self.graph.dfs_archetype_add_edges(archetype_id)
+ else {
+ self.imaginary_archetypes
+ .borrow_mut()
+ .push(ImaginaryArchetype {
+ id: ArchetypeId::new(search_terms.required_components.iter().filter(
+ |required_comp_id| {
+ required_comp_id.kind() != UidKind::Pair
+ || required_comp_id.target_component() != Uid::wildcard()
+ },
+ )),
+ component_ids: search_terms
+ .required_components
+ .iter()
+ .copied()
+ .filter(|required_comp_id| {
+ required_comp_id.kind() != UidKind::Pair
+ || required_comp_id.target_component() != Uid::wildcard()
+ })
+ .collect(),
+ });
+
+ let found_archetypes = self.find_all_archetype_with_comps(&search_terms);
+
+ return ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::B(found_archetypes.clone().into_iter()),
+ dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &found_archetypes),
+ search_terms,
+ };
+ };
- self.archetypes.get(archetype_index)
+ ArchetypeRefIter {
+ storage: self,
+ pre_iter: Either::A([archetype_id].into_iter()),
+ dfs_iter: add_edge_recursive_iter,
+ search_terms,
+ }
}
- #[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 get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype>
{
- if self.entity_archetype_lookup.contains_key(&entity_uid) {
- return Err(Error::EntityAlreadyExists(entity_uid));
+ Some(self.graph.get_node_by_id(id)?.archetype())
+ }
+
+ pub fn create_entity(&mut self, uid: Uid) -> Result<(), Error>
+ {
+ debug_assert_eq!(uid.kind(), UidKind::Entity);
+
+ if self.entity_archetype_lookup.contains_key(&uid) {
+ return Err(Error::EntityAlreadyExists(uid));
}
- components.sort_by_key(|component| component.self_id());
+ let empty_archetype_id = ArchetypeId::new_empty();
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Pushing entity with components: ({})",
- components
- .iter()
- .map(|component| component.type_name())
- .collect::<Vec<_>>()
- .join(", ")
- );
+ let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]);
- let archetype_id = ArchetypeId::from_components_metadata(
- &components
- .iter()
- .map(|component| ComponentMetadata::get(&**component))
- .collect::<Vec<_>>(),
- );
+ archetype_node
+ .archetype_mut()
+ .push_entity(ArchetypeEntity::new(uid, []));
- let comp_ids_set = create_non_opt_component_id_set(
- components
- .iter()
- .map(|component| ComponentMetadata::get(&**component)),
- );
+ self.entity_archetype_lookup.insert(uid, empty_archetype_id);
- let archetype_index =
- self.get_or_create_archetype(archetype_id, &comp_ids_set, &components);
+ Ok(())
+ }
- self.populate_matching_archetype_lookup_entries(&comp_ids_set, archetype_index);
+ pub fn remove_entity(&mut self, entity_uid: Uid) -> Result<ArchetypeEntity, Error>
+ {
+ let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else {
+ return Err(Error::EntityDoesNotExist(entity_uid));
+ };
- let archetype = self
- .archetypes
- .get_mut(archetype_index)
- .expect("Archetype is gone");
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(*archetype_id)
+ .expect("Archetype should exist");
- archetype.push_entity(entity_uid, components);
+ let entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
- archetype
- .entity_lookup
- .insert(entity_uid, archetype.entities.len() - 1);
+ self.entity_archetype_lookup.remove(&entity_uid);
- self.entity_archetype_lookup
- .insert(entity_uid, archetype_id);
+ Ok(entity)
+ }
- Ok((archetype_id, entity_uid))
+ pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype>
+ {
+ let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+
+ self.get_archetype_by_id(*archetype_id)
}
- pub fn add_components_to_entity(
+ pub fn add_entity_component(
&mut self,
entity_uid: Uid,
- components: Vec<Box<dyn Component>>,
- ) -> Option<()>
+ (component_id, component_name, component): (Uid, &'static str, Box<dyn Any>),
+ ) -> Result<(), Error>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ 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()
+ .contains_component_with_exact_id(component_id)
+ {
+ return Err(Error::ComponentAlreadyInEntity {
+ entity: entity_uid,
+ component: component_id,
+ });
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ let add_edge_archetype_id = if let Some(add_edge_id) = archetype_node
+ .get_or_insert_edges(component_id, ArchetypeEdges::default)
+ .add
+ {
+ if !self.graph.contains_archetype(add_edge_id) {
+ let (_, add_edge_comp_ids) = self
+ .graph
+ .get_node_by_id(archetype_id)
+ .expect("Archetype should exist")
+ .make_add_edge(component_id);
+
+ self.graph.create_node(add_edge_id, &add_edge_comp_ids);
+ }
- let archetype = self.archetypes.get_mut(archetype_index)?;
+ add_edge_id
+ } else {
+ let archetype_node = self
+ .graph
+ .get_node_by_id(archetype_id)
+ .expect("Archetype should exist");
- 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 (add_edge_id, add_edge_comp_ids) =
+ archetype_node.make_add_edge(component_id);
- let entity = archetype.take_entity(entity_uid)?;
+ if !self.graph.contains_archetype(add_edge_id) {
+ self.graph.create_node(add_edge_id, &add_edge_comp_ids);
+ }
- self.entity_archetype_lookup.remove(&entity_uid);
+ add_edge_id
+ };
+
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
+
+ let mut entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
+
+ let add_edge_archetype = self
+ .graph
+ .get_node_by_id_mut(add_edge_archetype_id)
+ .expect("Add edge archetype should exist")
+ .archetype_mut();
+
+ entity.insert_component(
+ component_id,
+ ArchetypeEntityComponent::new(component, component_id, component_name),
+ add_edge_archetype,
+ );
- 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");
+ add_edge_archetype.push_entity(entity);
- Some(())
+ self.entity_archetype_lookup
+ .insert(entity_uid, add_edge_archetype_id);
+
+ Ok(())
}
- pub fn remove_components_from_entity(
+ pub fn remove_entity_component(
&mut self,
entity_uid: Uid,
- component_ids: impl IntoIterator<Item = Uid>,
- ) -> Option<()>
+ component_id: Uid,
+ ) -> Result<(), Error>
{
- let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?;
+ 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()
+ .contains_component_with_exact_id(component_id)
+ {
+ return Err(Error::ComponentNotFoundInEntity {
+ entity: entity_uid,
+ component: component_id,
+ });
+ }
- let archetype_index =
- self.find_archetype_index_with_entity(*archetype_id, entity_uid)?;
+ 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);
+
+ if !self.graph.contains_archetype(remove_edge_id) {
+ self.graph
+ .create_node(remove_edge_id, &remove_edge_comp_ids);
+ }
- let archetype = self.archetypes.get_mut(archetype_index)?;
+ remove_edge_id
+ });
- let entity = archetype.take_entity(entity_uid)?;
+ let archetype_node = self
+ .graph
+ .get_node_by_id_mut(archetype_id)
+ .expect("Archetype should exist");
- let component_ids_set = component_ids.into_iter().collect::<HashSet<_>>();
+ let mut entity = archetype_node
+ .archetype_mut()
+ .remove_entity(entity_uid)
+ .expect("Entity should exist in archetype");
- self.entity_archetype_lookup.remove(&entity_uid);
+ entity.remove_component(component_id, archetype_node.archetype());
- 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");
+ self.graph
+ .get_node_by_id_mut(remove_edge_id)
+ .expect("Remove edge archetype should exist")
+ .archetype_mut()
+ .push_entity(entity);
- Some(())
+ self.entity_archetype_lookup
+ .insert(entity_uid, remove_edge_id);
+
+ Ok(())
}
- fn populate_matching_archetype_lookup_entries(
- &mut self,
- comp_ids_set: &HashSet<Uid>,
- archetype_index: usize,
- )
+ pub fn create_imaginary_archetypes(&mut self)
{
- 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 {
+ for imaginary_archetype in self.imaginary_archetypes.get_mut().drain(..) {
+ if self.graph.contains_archetype(imaginary_archetype.id) {
continue;
}
- // There shouldn't be duplicate archetype indices in the lookup entry
- if lookup_entry.archetype_indices.contains(&archetype_index) {
- continue;
- }
-
- if lookup_entry.component_ids.is_subset(comp_ids_set) {
- lookup_entry.archetype_indices.push(archetype_index);
- }
+ self.graph
+ .create_node(imaginary_archetype.id, &imaginary_archetype.component_ids);
}
}
- fn get_or_create_archetype(
- &mut self,
- archetype_id: ArchetypeId,
- comp_ids_set: &HashSet<Uid>,
- components: &[Box<dyn Component>],
- ) -> usize
+ fn find_all_archetype_with_comps(
+ &self,
+ search_terms: &ArchetypeSearchTerms<'_>,
+ ) -> Vec<ArchetypeId>
{
- let mut archetype_lookup = self.archetype_lookup.borrow_mut();
+ let Some(mut search_iter) =
+ self.graph.dfs_archetype_add_edges(ArchetypeId::new_empty())
+ else {
+ // If the root archetype doesn't exist, no other archetype can exist either
+ //
+ // TODO: The above comment is not true. Cases where imaginary archetypes have
+ // been created should be handled as well
+ return Vec::new();
+ };
+
+ let mut found = Vec::<ArchetypeId>::new();
+
+ while let Some(node_id) = search_iter.streaming_next() {
+ let ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id: node_id,
+ add_edge_component_id,
+ } = node_id
+ else {
+ continue;
+ };
- 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 search_terms.excluded_contains(add_edge_component_id) {
+ search_iter.pop();
+ continue;
+ }
+
+ let node = self
+ .graph
+ .get_node_by_id(node_id)
+ .expect("Graph node found through DFS doesn't exist");
+
+ if node.archetype().component_cnt() < search_terms.required_components.len() {
+ continue;
+ }
+
+ if !search_terms.archetype_contains_all_required(node.archetype()) {
+ continue;
}
- });
- if lookup_entry.archetype_indices.is_empty() {
- self.archetypes.push(Archetype::new(
- components.iter().map(|component| component.self_id()),
- ));
+ found.push(node.archetype().id());
- lookup_entry
- .archetype_indices
- .push(self.archetypes.len() - 1);
+ search_iter.pop();
}
- // SAFETY: Above, we push a archetype index if archetype_indices is empty so this
- // cannot fail
- unsafe { *lookup_entry.archetype_indices.first().unwrap_unchecked() }
+ found
}
+}
- fn find_archetype_index_with_entity(
+#[cfg(feature = "vizoxide")]
+impl Storage
+{
+ pub fn create_vizoxide_archetype_graph(
&self,
- archetype_id: ArchetypeId,
- entity_uid: Uid,
- ) -> Option<usize>
+ graph_name: impl AsRef<str>,
+ params: VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
{
- let archetype_lookup = self.archetype_lookup.borrow_mut();
+ let viz_graph = vizoxide::Graph::builder(graph_name.as_ref())
+ .strict(true)
+ .directed(true)
+ .build()?;
+
+ let mut viz_node_lookup = HashMap::new();
+
+ for node in self.graph.iter_nodes() {
+ let id = node.archetype().id();
+
+ if !viz_node_lookup.contains_key(&id) {
+ let node = self.graph.get_node_by_id(id).unwrap();
+
+ let viz_node = (params.create_node_cb)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build()?;
+
+ viz_node_lookup.insert(id, viz_node);
+ }
- let archetype_lookup_entry = archetype_lookup.get(&archetype_id)?;
+ for (edge_comp_id, edges) in node.iter_edges() {
+ if let Some(add_edge) = edges.add {
+ if !viz_node_lookup.contains_key(&add_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ add_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(add_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Add,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&add_edge).unwrap(),
+ Some(&format!("Add {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
- // 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;
- };
+ if let Some(remove_edge) = edges.remove {
+ if !viz_node_lookup.contains_key(&remove_edge) {
+ let viz_node = self.create_vizoxide_archetype_graph_edge_node(
+ &viz_graph,
+ node,
+ remove_edge,
+ *edge_comp_id,
+ &params,
+ )?;
+
+ viz_node_lookup.insert(remove_edge, viz_node);
+ }
+
+ (params.create_edge_cb)(
+ node.archetype(),
+ *edge_comp_id,
+ VizoxideArchetypeGraphEdgeKind::Remove,
+ viz_graph.create_edge(
+ viz_node_lookup.get(&id).unwrap(),
+ viz_node_lookup.get(&remove_edge).unwrap(),
+ Some(&format!("Remove {}", edge_comp_id.id())),
+ ),
+ )
+ .build()?;
+ }
+ }
+ }
- archetype.has_entity(entity_uid)
- })
- .copied()
+ drop(viz_node_lookup);
+
+ Ok(viz_graph)
}
- fn iter_archetypes_by_lookup(&self, archetype_id: ArchetypeId)
- -> ArchetypeRefIter<'_>
+ fn create_vizoxide_archetype_graph_edge_node<'vizoxide_graph>(
+ &self,
+ viz_graph: &'vizoxide_graph vizoxide::Graph,
+ node: &graph::ArchetypeNode,
+ edge_id: ArchetypeId,
+ edge_comp_id: Uid,
+ params: &VizoxideArchetypeGraphParams,
+ ) -> Result<vizoxide::Node<'vizoxide_graph>, vizoxide::GraphvizError>
{
- let archetype_lookup = self.archetype_lookup.borrow();
-
- // 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();
-
- ArchetypeRefIter {
- indices: archetype_indices.into_iter(),
- archetypes: &self.archetypes,
+ match self.graph.get_node_by_id(edge_id) {
+ Some(edge_node) => (params.create_node_cb)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ viz_graph.create_node(&(params.create_node_name)(
+ edge_node.archetype(),
+ ArchetypeMetadata { is_imaginary: false },
+ )),
+ )
+ .build(),
+ None => {
+ let mut comp_ids =
+ node.archetype().component_ids_sorted().collect::<Vec<_>>();
+
+ let insert_index = comp_ids.partition_point(|cid| *cid <= edge_comp_id);
+
+ comp_ids.insert(insert_index, edge_comp_id);
+
+ let imaginary_edge_archetype = Archetype::new(edge_id, comp_ids);
+
+ (params.create_node_cb)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ viz_graph.create_node(&(params.create_node_name)(
+ &imaginary_edge_archetype,
+ ArchetypeMetadata { is_imaginary: true },
+ )),
+ )
+ .build()
+ }
}
}
}
-/// Component storage error
-#[derive(Debug, Clone, thiserror::Error)]
-pub enum Error
+#[cfg(feature = "vizoxide")]
+pub struct VizoxideArchetypeGraphParams
{
- #[error("Entity already exists")]
- EntityAlreadyExists(Uid),
+ pub create_node_name: fn(&Archetype, ArchetypeMetadata) -> std::borrow::Cow<'_, str>,
+ pub create_node_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ ArchetypeMetadata,
+ vizoxide::NodeBuilder<'graph>,
+ ) -> vizoxide::NodeBuilder<'graph>,
+ pub create_edge_cb: for<'storage, 'graph> fn(
+ &'storage Archetype,
+ Uid,
+ VizoxideArchetypeGraphEdgeKind,
+ vizoxide::EdgeBuilder<'graph>,
+ ) -> vizoxide::EdgeBuilder<'graph>,
}
-impl TypeName for Storage
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone)]
+pub struct ArchetypeMetadata
{
- fn type_name(&self) -> &'static str
- {
- type_name::<Self>()
- }
+ pub is_imaginary: bool,
}
-#[derive(Debug)]
-struct ArchetypeLookupEntry
+#[cfg(feature = "vizoxide")]
+#[derive(Debug, Clone, Copy)]
+pub enum VizoxideArchetypeGraphEdgeKind
{
- component_ids: HashSet<Uid>,
- archetype_indices: Vec<usize>,
+ Add,
+ Remove,
}
#[derive(Debug)]
-pub struct Archetype
+pub struct ArchetypeRefIter<'storage, 'search_terms>
{
- component_ids: HashMap<Uid, usize>,
- entity_lookup: HashMap<Uid, usize>,
- entities: Vec<ArchetypeEntity>,
+ storage: &'storage Storage,
+ pre_iter: Either<ArrayIter<ArchetypeId, 1>, VecIntoIter<ArchetypeId>>,
+ dfs_iter: ArchetypeAddEdgeDfsIter<'storage>,
+ search_terms: ArchetypeSearchTerms<'search_terms>,
}
-impl Archetype
+impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage, '_>
{
- 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(),
- }
- }
+ type Item = &'component_storage Archetype;
- pub fn component_ids_is_superset(&self, other_component_ids: &HashSet<Uid>) -> bool
+ fn next(&mut self) -> Option<Self::Item>
{
- if other_component_ids.len() <= self.component_ids.len() {
- other_component_ids
- .iter()
- .all(|v| self.component_ids.contains_key(v))
- } else {
- false
+ if let Some(pre_iter_archetype_id) = self.pre_iter.next() {
+ return Some(
+ self.storage
+ .get_archetype_by_id(pre_iter_archetype_id)
+ .expect("Archetype should exist"),
+ );
}
- }
-
- pub fn get_entity(&self, entity_uid: Uid) -> Option<&ArchetypeEntity>
- {
- let entity_index = *self.entity_lookup.get(&entity_uid)?;
-
- self.entities.get(entity_index)
- }
-
- pub fn entities(&self) -> EntityIter<'_>
- {
- EntityIter { iter: self.entities.iter() }
- }
-
- pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize>
- {
- self.component_ids.get(&component_id).copied()
- }
-
- 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(),
- });
- }
-
- pub fn take_entity(&mut self, entity_uid: Uid) -> Option<ArchetypeEntity>
- {
- let entity_index = self.entity_lookup.remove(&entity_uid)?;
-
- let entity = self.entities.remove(entity_index);
- for index in self.entity_lookup.values_mut() {
- if *index > entity_index {
- *index -= 1;
+ let archetype_id = loop {
+ match self.dfs_iter.streaming_find(|res| {
+ matches!(
+ res,
+ ArchetypeAddEdgeDfsIterResult::AddEdge { .. }
+ | ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { .. }
+ )
+ })? {
+ ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id,
+ add_edge_component_id,
+ } => {
+ if self.search_terms.excluded_contains(add_edge_component_id) {
+ self.dfs_iter.pop();
+ continue;
+ }
+
+ break add_edge_archetype_id;
+ }
+ ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound {
+ archetype,
+ add_edge_archetype_id,
+ add_edge_component_id,
+ } => {
+ if self.search_terms.excluded_contains(add_edge_component_id) {
+ continue;
+ }
+
+ let mut add_edge_archetype_comps =
+ archetype.component_ids_sorted().collect::<Vec<_>>();
+
+ add_edge_archetype_comps.insert_at_partition_point_by_key(
+ add_edge_component_id,
+ |comp_id| *comp_id,
+ );
+
+ self.storage.imaginary_archetypes.borrow_mut().push(
+ ImaginaryArchetype {
+ id: add_edge_archetype_id,
+ component_ids: add_edge_archetype_comps.clone(),
+ },
+ );
+
+ let found =
+ self.find_edges_of_imaginary_archetype(&add_edge_archetype_comps);
+
+ self.dfs_iter.push((
+ BorrowedOrOwned::Owned(Archetype::new(
+ add_edge_archetype_id,
+ add_edge_archetype_comps.clone(),
+ )),
+ found.into_iter(),
+ ));
+ }
+ _ => {
+ unreachable!();
+ }
}
- }
+ };
- Some(entity)
- }
-
- fn has_entity(&self, entity_uid: Uid) -> bool
- {
- self.entity_lookup.contains_key(&entity_uid)
+ Some(
+ self.storage
+ .get_archetype_by_id(archetype_id)
+ .expect("Archetype should exist"),
+ )
}
}
-#[derive(Debug)]
-pub struct ArchetypeEntity
+impl ArchetypeRefIter<'_, '_>
{
- uid: Uid,
- components: Vec<EntityComponent>,
-}
-
-impl ArchetypeEntity
-{
- pub fn uid(&self) -> Uid
+ fn find_edges_of_imaginary_archetype(
+ &self,
+ imaginary_archetype_comps: &[Uid],
+ ) -> Vec<(Uid, ArchetypeEdges)>
{
- self.uid
- }
+ self.storage
+ .find_all_archetype_with_comps(&ArchetypeSearchTerms {
+ required_components: imaginary_archetype_comps,
+ excluded_components: &[],
+ })
+ .into_iter()
+ .filter_map(|found_id| {
+ let found_archetype = self.storage.get_archetype_by_id(found_id).unwrap();
- pub fn components(&self) -> &[EntityComponent]
- {
- &self.components
- }
+ if found_archetype.component_cnt() < imaginary_archetype_comps.len() + 1 {
+ return None;
+ }
- pub fn get_component(&self, index: usize) -> Option<&EntityComponent>
- {
- self.components.get(index)
- }
-}
+ let unique_comp_id = found_archetype
+ .component_ids_sorted()
+ .find(|found_archetype_comp_id| {
+ !imaginary_archetype_comps.iter().any(
+ |imaginary_archetype_comp_id| {
+ *imaginary_archetype_comp_id == *found_archetype_comp_id
+ },
+ )
+ })
+ .expect("Oh noooo");
-#[derive(Debug)]
-pub struct ArchetypeRefIter<'component_storage>
-{
- indices: OwnedVecIter<usize>,
- archetypes: &'component_storage [Archetype],
-}
+ let mut add_edge_comp_ids = imaginary_archetype_comps.to_vec();
-impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage>
-{
- type Item = &'component_storage Archetype;
+ add_edge_comp_ids
+ .insert_at_partition_point_by_key(unique_comp_id, |id| *id);
- fn next(&mut self) -> Option<Self::Item>
- {
- let archetype_index = self.indices.next()?;
+ let add_edge = ArchetypeId::new(&add_edge_comp_ids);
- Some(
- self.archetypes
- .get(archetype_index)
- .expect("Archetype index in archetype lookup entry was not found"),
- )
+ Some((
+ unique_comp_id,
+ ArchetypeEdges { add: Some(add_edge), remove: None },
+ ))
+ })
+ .collect::<Vec<_>>()
}
}
-#[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:?}")]
+ ComponentAlreadyInEntity
{
- self.iter.next()
- }
+ entity: Uid, component: Uid
+ },
+
+ #[error("Entity with ID {entity:?} does not have component with ID {component:?}")]
+ ComponentNotFoundInEntity
+ {
+ 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>,
+#[derive(Debug)]
+struct ImaginaryArchetype
{
- 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<_>>()
+ id: ArchetypeId,
+ component_ids: Vec<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();
-
- component_storage
- .push_entity(
- Uid::new_unique(UidKind::Entity),
- vec![
- Box::new(HealthPotion { _hp_restoration: 12 }),
- Box::new(Hookshot { _range: 50 }),
- ],
- )
- .expect("Expected Ok");
-
- assert_eq!(component_storage.archetypes.len(), 1);
-
- let archetype = component_storage
- .archetypes
- .first()
- .expect("Expected a archetype in archetypes Vec");
-
- assert_eq!(archetype.component_ids.len(), 2);
+ let mut new_storage = Storage::default();
- // One entity
- assert_eq!(archetype.entities.len(), 1);
+ let uid = Uid::new_unique(UidKind::Entity);
- let entity_components = archetype
- .entities
- .first()
- .expect("Expected a entity in archetype");
+ new_storage.create_entity(uid).expect("Expected Ok");
- assert_eq!(entity_components.components.len(), 2);
+ let archetype_node = new_storage
+ .graph
+ .get_node_by_id(ArchetypeId::new_empty())
+ .expect("Archetype for entities with no component doesn't exist");
- assert_eq!(component_storage.archetype_lookup.borrow().len(), 1);
+ assert_eq!(archetype_node.archetype().component_cnt(), 0);
+ assert_eq!(archetype_node.archetype().entity_cnt(), 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::new_empty())
+ );
}
}
diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs
new file mode 100644
index 0000000..bb29701
--- /dev/null
+++ b/ecs/src/component/storage/archetype.rs
@@ -0,0 +1,387 @@
+use std::any::Any;
+use std::array::IntoIter as ArrayIntoIter;
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::iter::{Enumerate, Filter, Map, RepeatN, Zip};
+use std::option::IntoIter as OptionIntoIter;
+use std::slice::Iter as SliceIter;
+
+use hashbrown::HashMap;
+
+use crate::lock::Lock;
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{Either, HashMapExt};
+
+#[derive(Debug)]
+pub struct Archetype
+{
+ id: Id,
+ entities: Vec<Entity>,
+ entity_index_lookup: HashMap<Uid, usize>,
+ component_index_lookup: HashMap<Uid, usize>,
+ component_ids: Vec<Uid>,
+}
+
+impl Archetype
+{
+ pub fn new(id: Id, component_ids: impl AsRef<[Uid]>) -> Self
+ {
+ Self {
+ id,
+ entities: Vec::new(),
+ entity_index_lookup: HashMap::new(),
+ component_index_lookup: component_ids
+ .as_ref()
+ .iter()
+ .enumerate()
+ .map(|(index, id)| (*id, index))
+ .collect(),
+ component_ids: component_ids.as_ref().to_vec(),
+ }
+ }
+
+ 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 is_subset(&self, other: &Self) -> bool
+ {
+ self.component_index_lookup
+ .keys_is_subset(&other.component_index_lookup)
+ }
+
+ pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&Entity>
+ {
+ 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: Entity)
+ {
+ self.entity_index_lookup
+ .insert(entity.uid, self.entities.len());
+
+ self.entities.push(entity);
+ }
+
+ pub fn remove_entity(&mut self, entity_uid: Uid) -> Option<Entity>
+ {
+ //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_matching_component_indices(
+ &self,
+ component_id: Uid,
+ ) -> MatchingComponentIter
+ {
+ assert!(
+ component_id.kind() == UidKind::Component
+ || component_id.kind() == UidKind::Pair
+ );
+
+ if component_id.kind() == UidKind::Pair
+ && component_id.target_component() == Uid::wildcard()
+ {
+ return MatchingComponentIter {
+ inner: Either::A(
+ self.component_ids
+ .iter()
+ .enumerate()
+ .zip(std::iter::repeat_n(component_id, self.component_ids.len()))
+ .filter(
+ (|((_, other_comp_id), component_id)| {
+ other_comp_id.kind() == UidKind::Pair
+ && other_comp_id.has_same_relation_as(*component_id)
+ })
+ as MatchingComponentIterFilterFn,
+ )
+ .map(|((index, other_comp_id), _)| (*other_comp_id, index)),
+ ),
+ };
+ }
+
+ MatchingComponentIter {
+ inner: Either::B(
+ [component_id]
+ .into_iter()
+ .zip(self.get_index_for_component(component_id)),
+ ),
+ }
+ }
+
+ pub fn get_index_for_component(&self, component_id: Uid) -> Option<usize>
+ {
+ assert!(
+ component_id.kind() == UidKind::Component
+ || (component_id.kind() == UidKind::Pair
+ && component_id.target_component() != Uid::wildcard())
+ );
+
+ 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 component_ids_sorted(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.component_ids.iter().copied()
+ }
+
+ pub fn contains_matching_component(&self, component_id: Uid) -> bool
+ {
+ let component_id_kind = component_id.kind();
+
+ debug_assert!(
+ component_id_kind == UidKind::Component || component_id_kind == UidKind::Pair
+ );
+
+ if component_id.kind() == UidKind::Pair
+ && component_id.target_component() == Uid::wildcard()
+ {
+ return self.component_ids.iter().any(|other_comp_id| {
+ other_comp_id.kind() == UidKind::Pair
+ && other_comp_id.has_same_relation_as(component_id)
+ });
+ }
+
+ self.contains_component_with_exact_id(component_id)
+ }
+
+ pub fn contains_component_with_exact_id(&self, component_id: Uid) -> bool
+ {
+ let component_id_kind = component_id.kind();
+
+ debug_assert!(
+ component_id_kind == UidKind::Component
+ || (component_id_kind == UidKind::Pair
+ && component_id.target_component() != Uid::wildcard())
+ );
+
+ self.component_index_lookup.contains_key(&component_id)
+ }
+}
+
+type MatchingComponentIterFilterFn = fn(&((usize, &Uid), Uid)) -> bool;
+
+type MatchingComponentIterMapFn = fn(((usize, &Uid), Uid)) -> (Uid, usize);
+
+type InnerMatchingComponentIterA<'archetype> = Map<
+ Filter<
+ Zip<Enumerate<SliceIter<'archetype, Uid>>, RepeatN<Uid>>,
+ MatchingComponentIterFilterFn,
+ >,
+ MatchingComponentIterMapFn,
+>;
+
+type InnerMatchingComponentIterB = Zip<ArrayIntoIter<Uid, 1>, OptionIntoIter<usize>>;
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'archetype>
+{
+ inner: Either<InnerMatchingComponentIterA<'archetype>, InnerMatchingComponentIterB>,
+}
+
+impl Iterator for MatchingComponentIter<'_>
+{
+ type Item = (Uid, usize);
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ self.inner.next()
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityIter<'archetype>
+{
+ iter: SliceIter<'archetype, Entity>,
+}
+
+impl<'archetype> Iterator for EntityIter<'archetype>
+{
+ type Item = &'archetype Entity;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ self.iter.next()
+ }
+}
+
+#[derive(Debug)]
+pub struct Entity
+{
+ uid: Uid,
+ components: Vec<EntityComponent>,
+}
+
+impl Entity
+{
+ pub fn new(uid: Uid, components: impl IntoIterator<Item = EntityComponent>) -> Self
+ {
+ Self {
+ uid,
+ components: components.into_iter().collect(),
+ }
+ }
+
+ pub fn uid(&self) -> Uid
+ {
+ self.uid
+ }
+
+ pub fn components(&self) -> &[EntityComponent]
+ {
+ &self.components
+ }
+
+ pub fn remove_component(&mut self, component_id: Uid, archetype: &Archetype)
+ {
+ let index = archetype
+ .get_index_for_component(component_id)
+ .expect("Archetype should contain component");
+
+ self.components.remove(index);
+ }
+
+ pub fn insert_component(
+ &mut self,
+ component_id: Uid,
+ component: EntityComponent,
+ archetype: &Archetype,
+ )
+ {
+ let index = archetype
+ .get_index_for_component(component_id)
+ .expect("Archetype should contain component");
+
+ self.components.insert(index, component);
+ }
+}
+
+#[derive(Debug)]
+pub struct EntityComponent
+{
+ id: Uid,
+ component: Lock<Box<dyn Any>>,
+}
+
+impl EntityComponent
+{
+ pub fn new(
+ component: Box<dyn Any>,
+ component_id: Uid,
+ component_name: &'static str,
+ ) -> Self
+ {
+ Self {
+ id: component_id,
+ component: Lock::new(component, component_name),
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn id(&self) -> Uid
+ {
+ self.id
+ }
+
+ pub fn component(&self) -> &Lock<Box<dyn Any>>
+ {
+ &self.component
+ }
+}
+
+/// Archetype ID.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Id
+{
+ hash: u64,
+}
+
+impl Id
+{
+ pub fn new_empty() -> Self
+ {
+ Self { hash: 0 }
+ }
+
+ pub fn new<'a>(component_ids: impl IntoIterator<Item = &'a Uid>) -> Self
+ {
+ let mut hasher = DefaultHasher::new();
+
+ let mut prev_component_id: Option<Uid> = None;
+
+ let mut component_id_iter = component_ids.into_iter().peekable();
+
+ if component_id_iter.peek().is_none() {
+ return Self::new_empty();
+ }
+
+ for comp_id in component_id_iter {
+ if prev_component_id.is_some_and(|prev_comp_id| *comp_id < prev_comp_id) {
+ panic!(
+ "Cannot create archetype ID from a unsorted component metadata list"
+ );
+ }
+
+ prev_component_id = Some(*comp_id);
+
+ comp_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..29fa937
--- /dev/null
+++ b/ecs/src/component/storage/graph.rs
@@ -0,0 +1,432 @@
+use std::vec::IntoIter as VecIntoIter;
+
+use hashbrown::{HashMap, HashSet};
+
+use crate::component::storage::archetype::{Archetype, Id as ArchetypeId};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::util::{BorrowedOrOwned, StreamingIterator};
+
+#[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 exists_before = self.archetype_index_lookup.contains_key(&id);
+
+ 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
+ });
+
+ if !exists_before {
+ 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")
+ }))
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_nodes(&self) -> impl Iterator<Item = &ArchetypeNode>
+ {
+ self.nodes.iter()
+ }
+
+ pub fn dfs_archetype_add_edges(
+ &self,
+ archetype_id: ArchetypeId,
+ ) -> Option<ArchetypeAddEdgeDfsIter>
+ {
+ let node = self.get_node_by_id(archetype_id)?;
+
+ Some(ArchetypeAddEdgeDfsIter {
+ graph: self,
+ stack: vec![(
+ BorrowedOrOwned::Borrowned(node.archetype()),
+ node.edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ )],
+ visited: HashSet::new(),
+ })
+ }
+
+ fn create_missing_edges(&mut self, archetype_id: ArchetypeId)
+ {
+ 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!();
+ };
+
+ for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut())
+ {
+ if archetype_node.archetype().component_cnt()
+ > other_archetype_node.archetype().component_cnt()
+ && other_archetype_node
+ .archetype()
+ .is_subset(archetype_node.archetype())
+ {
+ Self::create_missing_subset_node_edges(
+ archetype_node,
+ other_archetype_node,
+ );
+
+ continue;
+ }
+
+ if other_archetype_node
+ .archetype()
+ .is_superset(archetype_node.archetype())
+ {
+ Self::create_missing_superset_node_edges(
+ archetype_node,
+ other_archetype_node,
+ );
+ }
+ }
+ }
+
+ fn create_missing_subset_node_edges(
+ target_node: &mut ArchetypeNode,
+ subset_node: &mut ArchetypeNode,
+ )
+ {
+ let uniq_comp_id = target_node
+ .archetype()
+ .component_ids_sorted()
+ .find(|id| {
+ !subset_node
+ .archetype()
+ .contains_component_with_exact_id(*id)
+ })
+ .unwrap();
+
+ subset_node
+ .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default)
+ .add = Some(subset_node.make_add_edge(uniq_comp_id).0);
+
+ if target_node.archetype().component_cnt()
+ == subset_node.archetype().component_cnt() + 1
+ {
+ target_node
+ .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default)
+ .remove = Some(subset_node.archetype().id());
+ }
+ }
+
+ fn create_missing_superset_node_edges(
+ target_node: &mut ArchetypeNode,
+ superset_node: &mut ArchetypeNode,
+ )
+ {
+ if superset_node.archetype().component_cnt()
+ > target_node.archetype().component_cnt() + 1
+ {
+ let first_unique_comp_id = superset_node
+ .archetype()
+ .component_ids_sorted()
+ .find(|other_archetype_comp_id| {
+ !target_node
+ .archetype()
+ .contains_component_with_exact_id(*other_archetype_comp_id)
+ })
+ .or_else(|| {
+ if target_node.archetype().component_cnt() != 0 {
+ return None;
+ }
+
+ superset_node.archetype().component_ids_sorted().next()
+ })
+ .expect("Not possible");
+
+ target_node
+ .get_or_insert_edges(first_unique_comp_id, ArchetypeEdges::default)
+ .add = Some(target_node.make_add_edge(first_unique_comp_id).0);
+
+ return;
+ }
+
+ if superset_node.archetype().component_cnt()
+ != target_node.archetype().component_cnt() + 1
+ {
+ return;
+ }
+
+ let extra_comp_id = superset_node
+ .archetype()
+ .component_ids_unsorted()
+ .find(|comp_id| {
+ !target_node
+ .archetype()
+ .contains_component_with_exact_id(*comp_id)
+ })
+ .expect("Archetype should contain one extra component ID");
+
+ superset_node
+ .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default)
+ .remove = Some(target_node.archetype().id());
+
+ target_node
+ .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default)
+ .add = Some(superset_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!(matches!(
+ component_id.kind(),
+ UidKind::Component | UidKind::Pair
+ ));
+
+ self.edges.entry(component_id).or_insert_with(insert_fn)
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn iter_edges(&self) -> impl Iterator<Item = (&Uid, &ArchetypeEdges)>
+ {
+ self.edges.iter()
+ }
+
+ 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, Clone)]
+pub struct ArchetypeEdges
+{
+ pub add: Option<ArchetypeId>,
+ pub remove: Option<ArchetypeId>,
+}
+
+type ArchetypeAddEdgeDfsIterStackElem<'graph> = (
+ BorrowedOrOwned<'graph, Archetype>,
+ VecIntoIter<(Uid, ArchetypeEdges)>,
+);
+
+#[derive(Debug)]
+pub struct ArchetypeAddEdgeDfsIter<'graph>
+{
+ graph: &'graph Graph,
+ stack: Vec<ArchetypeAddEdgeDfsIterStackElem<'graph>>,
+ visited: HashSet<ArchetypeId>,
+}
+
+impl<'graph> ArchetypeAddEdgeDfsIter<'graph>
+{
+ pub fn new(graph: &'graph Graph, start_nodes: &[ArchetypeId]) -> Self
+ {
+ Self {
+ graph,
+ stack: start_nodes
+ .iter()
+ .map(|start_node_id| {
+ let start_node = graph
+ .get_node_by_id(*start_node_id)
+ .expect("Start node does not exist");
+
+ (
+ BorrowedOrOwned::Borrowned(start_node.archetype()),
+ start_node
+ .edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ )
+ })
+ .collect(),
+ visited: start_nodes.iter().copied().collect::<HashSet<_>>(),
+ }
+ }
+
+ pub fn push(
+ &mut self,
+ item: (
+ BorrowedOrOwned<'graph, Archetype>,
+ VecIntoIter<(Uid, ArchetypeEdges)>,
+ ),
+ )
+ {
+ self.stack.push(item);
+ }
+
+ pub fn pop(&mut self)
+ {
+ self.stack.pop();
+ }
+}
+
+impl<'graph> StreamingIterator for ArchetypeAddEdgeDfsIter<'graph>
+{
+ type Item<'a>
+ = ArchetypeAddEdgeDfsIterResult<'graph, 'a>
+ where
+ Self: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>
+ {
+ let (_, edges_iter) = self.stack.last_mut()?;
+
+ let Some((component_id, edges)) = edges_iter.next() else {
+ self.stack.pop();
+
+ return Some(ArchetypeAddEdgeDfsIterResult::NoEdgesLeftForArchetype);
+ };
+
+ let Some(add_edge) = edges.add else {
+ return Some(ArchetypeAddEdgeDfsIterResult::NoAddEdge);
+ };
+
+ if self.visited.contains(&add_edge) {
+ return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeAlreadyVisited);
+ }
+
+ self.visited.insert(add_edge);
+
+ let Some(add_edge_archetype) = self.graph.get_node_by_id(add_edge) else {
+ return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound {
+ archetype: &self.stack.last().unwrap().0,
+ add_edge_archetype_id: add_edge,
+ add_edge_component_id: component_id,
+ });
+ };
+
+ self.stack.push((
+ BorrowedOrOwned::Borrowned(add_edge_archetype.archetype()),
+ add_edge_archetype
+ .edges
+ .iter()
+ .map(|(comp_id, edges)| (*comp_id, edges.clone()))
+ .collect::<Vec<_>>()
+ .into_iter(),
+ ));
+
+ Some(ArchetypeAddEdgeDfsIterResult::AddEdge {
+ add_edge_archetype_id: add_edge,
+ add_edge_component_id: component_id,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub enum ArchetypeAddEdgeDfsIterResult<'graph, 'iter>
+{
+ AddEdge
+ {
+ add_edge_archetype_id: ArchetypeId,
+ add_edge_component_id: Uid,
+ },
+ NoEdgesLeftForArchetype,
+ NoAddEdge,
+ AddEdgeAlreadyVisited,
+ AddEdgeArchetypeNotFound
+ {
+ archetype: &'iter BorrowedOrOwned<'graph, Archetype>,
+ add_edge_archetype_id: ArchetypeId,
+ add_edge_component_id: Uid,
+ },
+}
diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs
index 3de9cd5..bab3d61 100644
--- a/ecs/src/entity.rs
+++ b/ecs/src/entity.rs
@@ -1,6 +1,133 @@
+use std::any::type_name;
+
use linkme::distributed_slice;
-use crate::World;
+use crate::component::storage::archetype::{
+ Archetype,
+ Entity as ArchetypeEntity,
+ MatchingComponentIter as ArchetypeMatchingComponentIter,
+};
+use crate::component::{
+ Component,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
+};
+use crate::uid::{Kind as UidKind, Uid};
+use crate::{EntityComponentRef, World};
+
+/// A handle to a entity.
+#[derive(Debug)]
+pub struct Handle<'a>
+{
+ archetype: &'a Archetype,
+ entity: &'a ArchetypeEntity,
+}
+
+impl<'a> Handle<'a>
+{
+ /// Returns the [`Uid`] of this entity.
+ #[inline]
+ #[must_use]
+ pub fn uid(&self) -> Uid
+ {
+ self.entity.uid()
+ }
+
+ /// Returns a reference to the specified component in this entity. `None` is
+ /// returned if the component isn't found in the entity.
+ ///
+ /// # Panics
+ /// Will panic if:
+ /// - The component's ID is not a component ID
+ /// - The component is mutably borrowed elsewhere
+ #[must_use]
+ pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'_, ComponentT>>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let component = self.get_matching_components(ComponentT::id()).next()?;
+
+ Some(
+ ComponentHandle::from_entity_component_ref(component).unwrap_or_else(|err| {
+ panic!(
+ "Taking component {} lock failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+
+ /// Returns a mutable reference to the specified component in this entity. `None` is
+ /// returned if the component isn't found in the entity.
+ ///
+ /// # Panics
+ /// Will panic if:
+ /// - The component's ID is not a component ID
+ /// - The component is borrowed elsewhere
+ #[must_use]
+ pub fn get_mut<ComponentT: Component>(
+ &self,
+ ) -> Option<ComponentHandleMut<'_, ComponentT>>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let component = self.get_matching_components(ComponentT::id()).next()?;
+
+ Some(
+ ComponentHandleMut::from_entity_component_ref(component).unwrap_or_else(
+ |err| {
+ panic!(
+ "Taking component {} lock failed: {err}",
+ type_name::<ComponentT>()
+ );
+ },
+ ),
+ )
+ }
+
+ #[inline]
+ #[must_use]
+ pub fn get_matching_components(&self, component_uid: Uid)
+ -> MatchingComponentIter<'a>
+ {
+ MatchingComponentIter {
+ inner: self.archetype.get_matching_component_indices(component_uid),
+ entity: self.entity,
+ }
+ }
+
+ pub fn component_ids(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.archetype.component_ids_sorted()
+ }
+
+ pub(crate) fn new(archetype: &'a Archetype, entity: &'a ArchetypeEntity) -> Self
+ {
+ Self { archetype, entity }
+ }
+}
+
+#[derive(Debug)]
+pub struct MatchingComponentIter<'a>
+{
+ inner: ArchetypeMatchingComponentIter<'a>,
+ entity: &'a ArchetypeEntity,
+}
+
+impl<'a> Iterator for MatchingComponentIter<'a>
+{
+ type Item = EntityComponentRef<'a>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let (matching_component_id, index) = self.inner.next()?;
+
+ Some(EntityComponentRef::new(
+ matching_component_id,
+ self.entity.components().get(index).unwrap(),
+ ))
+ }
+}
#[allow(clippy::module_name_repetitions)]
#[macro_export]
@@ -19,7 +146,7 @@ macro_rules! static_entity {
$crate::entity::CREATE_STATIC_ENTITIES
)]
#[linkme(crate=$crate::private::linkme)]
- static CREATE_STATIC_ENTITY: fn(&$crate::World) = |world| {
+ static CREATE_STATIC_ENTITY: fn(&mut $crate::World) = |world| {
world.create_entity_with_uid($components, *$ident);
};
}
@@ -29,4 +156,4 @@ macro_rules! static_entity {
#[distributed_slice]
#[doc(hidden)]
-pub static CREATE_STATIC_ENTITIES: [fn(&World)];
+pub static CREATE_STATIC_ENTITIES: [fn(&mut World)];
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..72a78a3 100644
--- a/ecs/src/event/component.rs
+++ b/ecs/src/event/component.rs
@@ -1,124 +1,12 @@
//! Component events.
-use std::fmt::{Debug, Formatter};
-use std::marker::PhantomData;
+use std::convert::Infallible;
+use std::fmt::Debug;
-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.
-pub struct Added<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Debug for Added<ComponentT>
-where
- ComponentT: Component,
-{
- fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
- {
- formatter
- .debug_struct("Added")
- .field("_pd", &self._pd)
- .finish()
- }
-}
-
-impl<ComponentT> Default for Added<ComponentT>
-where
- ComponentT: Component,
-{
- fn default() -> Self
- {
- Self { _pd: PhantomData }
- }
-}
-
-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.
-pub struct Removed<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Debug for Removed<ComponentT>
-where
- ComponentT: Component,
-{
- fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
- {
- formatter
- .debug_struct("Removed")
- .field("_pd", &self._pd)
- .finish()
- }
-}
-
-impl<ComponentT> Default for Removed<ComponentT>
-where
- ComponentT: Component,
-{
- fn default() -> Self
- {
- Self { _pd: PhantomData }
- }
-}
-
-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
-{
- Id::new::<Removed<ComponentForId>, _>(Some(component_id))
-}
-
-pub struct TypeTransformComponentsToAddedEvents;
-
-impl<ComponentT: Component, Accumulator>
- TupleReduceElement<Accumulator, TypeTransformComponentsToAddedEvents> for ComponentT
-where
- Accumulator: TupleWith<Added<Self>>,
-{
- type Return = Accumulator::With;
-}
+use crate::Component;
+/// Pair relation for events emitted when:
+/// a) A entity with the target component is spawned.
+/// b) The target component is added to a entity.
#[derive(Debug, Component)]
-struct ComponentForId;
+pub struct Added(Infallible);
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..07b1cba 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -1,65 +1,75 @@
#![deny(clippy::all, clippy::pedantic)]
-use std::any::{type_name, TypeId};
+use std::any::{type_name, Any, TypeId};
use std::cell::RefCell;
-use std::collections::HashMap;
use std::fmt::Debug;
use std::mem::ManuallyDrop;
+use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
+use hashbrown::HashMap;
+
use crate::actions::Action;
+use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent;
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,
+ IntoParts,
+ Parts as ComponentParts,
+ Removals as ComponentRemovals,
+ Sequence as ComponentSequence,
};
-use crate::event::start::Start as StartEvent;
-use crate::event::{Event, Id as EventId, Ids, Sequence as EventSequence};
+use crate::entity::{Handle as EntityHandle, CREATE_STATIC_ENTITIES};
+use crate::event::component::Added as ComponentAddedEvent;
use crate::extension::{Collector as ExtensionCollector, Extension};
-use crate::lock::{Lock, WriteGuard};
-use crate::query::options::Options as QueryOptions;
+use crate::lock::Lock;
+use crate::pair::{ChildOf, DependsOn, Pair};
+use crate::phase::{Phase, START as START_PHASE};
+use crate::query::flexible::Query as FlexibleQuery;
+use crate::query::term::Without;
+use crate::query::{
+ Iter as QueryIter,
+ TermWithFieldTuple as QueryTermWithFieldTuple,
+ TermWithoutFieldTuple as QueryTermWithoutFieldTuple,
+ Terms as QueryTerms,
+ TermsBuilderInterface,
+};
use crate::sole::Sole;
use crate::stats::Stats;
-use crate::system::{System, TypeErased as TypeErasedSystem};
-use crate::tuple::Reduce as TupleReduce;
-use crate::type_name::TypeName;
-use crate::uid::{Kind as UidKind, Uid};
+use crate::system::{System, SystemComponent};
+use crate::uid::{Kind as UidKind, Uid, Wildcard};
pub mod actions;
pub mod component;
pub mod entity;
pub mod event;
pub mod extension;
-pub mod lock;
+pub mod pair;
+pub mod phase;
pub mod query;
-pub mod relationship;
pub mod sole;
pub mod stats;
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;
+mod lock;
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,21 +77,25 @@ 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(&mut world);
+ }
+
world
}
/// Creates a new entity with the given components.
- ///
- /// # Panics
- /// 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 +104,27 @@ 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)
+ pub fn create_entity_with_uid<Comps>(&mut 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}");
-
+ if let Err(err) = self.data.component_storage.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);
+ let added_component_ids = Self::add_entity_components(
+ entity_uid,
+ components.into_parts_array(),
+ &mut self.data.component_storage,
+ );
+
+ for comp_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
@@ -129,28 +139,31 @@ 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() },
+ Pair::new::<DependsOn>(phase_euid),
+ ));
+ }
- drop(event);
+ pub fn register_observer_system<'this, SystemImpl>(
+ &'this mut self,
+ system: impl System<'this, SystemImpl>,
+ event: Pair<Uid, Uid>,
+ )
+ {
+ self.create_entity((
+ SystemComponent { system: system.into_type_erased() },
+ event,
+ ));
}
/// Adds a extensions.
- ///
- /// # Panics
- /// Will panic if mutable internal lock cannot be acquired.
pub fn add_extension(&mut self, extension: impl Extension)
{
let extension_collector = ExtensionCollector::new(self);
@@ -158,34 +171,177 @@ 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<FieldTerms, FieldlessTerms>(&self) -> Query<FieldTerms, FieldlessTerms>
where
- EventT: Event,
+ FieldTerms: QueryTermWithFieldTuple,
+ FieldlessTerms: QueryTermWithoutFieldTuple,
{
- self.emit_event_by_id(EventT::id());
-
- drop(event);
+ Query::new(self)
}
- pub fn query<Comps, OptionsT>(&self) -> Query<Comps, OptionsT>
- where
- Comps: ComponentSequence,
- OptionsT: QueryOptions,
+ pub fn flexible_query<const MAX_TERM_CNT: usize>(
+ &self,
+ terms: QueryTerms<MAX_TERM_CNT>,
+ ) -> FlexibleQuery<'_, MAX_TERM_CNT>
{
- Query::new(self)
+ FlexibleQuery::new(self, terms)
}
- /// Peforms the actions that have been queued up using [`Actions`].
- ///
+ /// Performs a single tick.
/// # 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 mutable internal lock cannot be acquired.
+ pub fn step(&mut 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.data.component_storage.create_imaginary_archetypes();
+
+ let prev_pending_removals = std::mem::take(&mut self.data.pending_removals);
+
+ self.perform_queued_actions();
+
+ self.perform_removals(prev_pending_removals);
+
+ 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
+ }
+
+ /// Starts a loop which calls [`Self::step`] until the world is stopped.
+ pub fn start_loop(&mut self)
+ {
+ while let StepResult::Continue = self.step() {}
+ }
+
+ #[cfg(feature = "vizoxide")]
+ pub fn create_vizoxide_archetype_graph(
+ &self,
+ name: impl AsRef<str>,
+ ) -> Result<vizoxide::Graph, vizoxide::GraphvizError>
+ {
+ use std::borrow::Cow;
+
+ use crate::component::storage::{
+ VizoxideArchetypeGraphEdgeKind,
+ VizoxideArchetypeGraphParams,
+ };
+
+ self.data.component_storage.create_vizoxide_archetype_graph(
+ name,
+ VizoxideArchetypeGraphParams {
+ create_node_name: |archetype, _| {
+ Cow::Owned(format!(
+ "[{}]",
+ archetype
+ .component_ids_sorted()
+ .into_iter()
+ .map(|comp_id| comp_id.to_string())
+ .collect::<Vec<_>>()
+ .join(", ")
+ ))
+ },
+ create_node_cb: |_archetype, archetype_metadata, node_builder| {
+ if archetype_metadata.is_imaginary {
+ return node_builder.attribute("shape", "ellipse");
+ }
+
+ node_builder.attribute("shape", "box")
+ },
+ create_edge_cb: |_, _, edge_kind, edge_builder| {
+ edge_builder.attribute(
+ "color",
+ match edge_kind {
+ VizoxideArchetypeGraphEdgeKind::Add => "green",
+ VizoxideArchetypeGraphEdgeKind::Remove => "red",
+ },
+ )
+ },
+ },
+ )
+ }
+
+ fn query_and_run_systems(&self, phase_euid: Uid)
+ {
+ let system_query = self.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([
+ SystemComponent::id(),
+ Pair::new::<DependsOn>(phase_euid).id(),
+ ])
+ .build(),
+ );
+
+ for (system_component,) in
+ QueryIter::<(&SystemComponent,), _>::new(self, system_query.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.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([
+ Phase::id(),
+ Pair::new::<ChildOf>(parent_phase_euid).id(),
+ ])
+ .build(),
+ );
+
+ for child_phase_entity in &phase_query {
+ self.query_and_run_systems(child_phase_entity.uid());
+ self.perform_child_phases(child_phase_entity.uid());
+ }
+ }
+
+ fn perform_phases(&self)
+ {
+ let phase_query = self.query::<(&Phase,), (Without<Pair<ChildOf, Wildcard>>,)>();
+
+ for (phase_entity_id, _) in phase_query.iter_with_euids() {
+ if phase_entity_id == *START_PHASE {
+ continue;
+ }
+
+ self.query_and_run_systems(phase_entity_id);
+ self.perform_child_phases(phase_entity_id);
+ }
+ }
+
+ #[tracing::instrument(skip_all)]
+ fn perform_queued_actions(&mut self)
{
let mut active_action_queue = match *self.data.action_queue.active_queue.borrow()
{
@@ -202,82 +358,64 @@ impl World
let mut has_swapped_active_queue = false;
+ // TODO: Figure out a good way to handle situations where there are multiple
+ // AddComponents/RemoveComponents actions that affect the same entity.
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<_>>();
+ let new_entity_uid = Uid::new_unique(UidKind::Entity);
- #[allow(unused_variables)]
- if let Err(err) = component_storage_lock
- .push_entity(Uid::new_unique(UidKind::Entity), components)
+ if let Err(err) =
+ self.data.component_storage.create_entity(new_entity_uid)
{
- #[cfg(feature = "debug")]
- tracing::error!("Failed to create entity: {err}");
-
+ tracing::warn!("Failed to create entity: {err}");
continue;
}
- drop(component_storage_lock);
+ let added_component_ids = Self::add_entity_components(
+ new_entity_uid,
+ components,
+ &mut self.data.component_storage,
+ );
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_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
+ Action::Despawn(entity_uid) => {
+ Self::schedule_removal(
+ &mut self.data.component_storage,
+ &mut self.data.pending_removals,
+ entity_uid,
+ PendingRemoval::Entity,
+ );
+ }
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);
-
- drop(component_storage_lock);
+ let added_component_ids = Self::add_entity_components(
+ entity_uid,
+ components,
+ &mut self.data.component_storage,
+ );
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_id in added_component_ids {
+ self.emit_event_by_id::<ComponentAddedEvent>(comp_id);
}
}
- Action::RemoveComponents(entity_uid, components_metadata) => {
- let mut component_storage_lock = self.lock_component_storage_rw();
-
- component_storage_lock.remove_components_from_entity(
+ Action::RemoveComponents(entity_uid, component_ids) => {
+ Self::schedule_removal(
+ &mut self.data.component_storage,
+ &mut self.data.pending_removals,
entity_uid,
- components_metadata
- .iter()
- .map(|component_metadata| component_metadata.id),
+ PendingRemoval::Components(component_ids),
);
-
- drop(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,
- ));
- }
}
Action::Stop => {
self.stop.store(true, Ordering::Relaxed);
@@ -286,60 +424,134 @@ 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)
+ fn perform_removals(&mut self, removals: Vec<(Uid, PendingRemoval)>)
{
- for create_static_entity in CREATE_STATIC_ENTITIES {
- create_static_entity(self);
+ for (entity_id, removal) in removals {
+ match removal {
+ PendingRemoval::Components(component_ids) => {
+ Self::remove_entity_components(
+ entity_id,
+ component_ids.into_iter().chain([ComponentRemovals::id()]),
+ &mut self.data.component_storage,
+ );
+ }
+ PendingRemoval::Entity => {
+ if let Err(err) = self.data.component_storage.remove_entity(entity_id)
+ {
+ tracing::error!("Failed to remove entity {entity_id}: {err}");
+ }
+ }
+ }
}
+ }
- self.emit(StartEvent);
+ #[tracing::instrument(skip(component_storage, pending_removals))]
+ fn schedule_removal(
+ component_storage: &mut ComponentStorage,
+ pending_removals: &mut Vec<(Uid, PendingRemoval)>,
+ entity_uid: Uid,
+ removal: PendingRemoval,
+ )
+ {
+ let Some(ent_handle) = Self::get_entity(component_storage, entity_uid) else {
+ tracing::warn!("Cannot schedule removal. Entity does not exist");
+ return;
+ };
- let event_seq = EventSeq::ids();
+ let component_ids = match removal {
+ PendingRemoval::Components(ref component_ids) => component_ids,
+ PendingRemoval::Entity => &ent_handle.component_ids().collect::<Vec<_>>(),
+ };
- loop {
- for event_id in event_seq.iter() {
- self.emit_event_by_id(*event_id);
- }
+ let Some(mut component_removals) = ent_handle.get_mut::<ComponentRemovals>()
+ else {
+ Self::add_entity_components(
+ entity_uid,
+ [ComponentRemovals::from_iter(component_ids.iter().copied())
+ .into_parts()],
+ component_storage,
+ );
+
+ pending_removals.push((entity_uid, removal));
+
+ return;
+ };
+
+ component_removals.add_ids(component_ids.iter().copied());
- self.perform_queued_actions();
+ drop(component_removals);
- if self.stop.load(Ordering::Relaxed) {
- break;
+ pending_removals.push((entity_uid, removal));
+ }
+
+ fn add_entity_components(
+ entity_uid: Uid,
+ components: impl IntoIterator<Item = ComponentParts>,
+ component_storage: &mut ComponentStorage,
+ ) -> Vec<Uid>
+ {
+ let component_iter = components.into_iter();
+
+ let mut added_component_ids =
+ Vec::<Uid>::with_capacity(component_iter.size_hint().0);
+
+ for component_parts in component_iter {
+ let comp_id = component_parts.id();
+
+ if let Err(err) = component_storage.add_entity_component(
+ entity_uid,
+ (comp_id, component_parts.name(), component_parts.into_data()),
+ ) {
+ tracing::error!("Failed to add component to entity: {err}");
+ continue;
}
- 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");
+ added_component_ids.push(comp_id);
+ }
+
+ added_component_ids
+ }
+
+ fn remove_entity_components(
+ entity_uid: Uid,
+ component_ids: impl IntoIterator<Item = Uid>,
+ component_storage: &mut ComponentStorage,
+ ) -> Vec<Uid>
+ {
+ let component_id_iter = component_ids.into_iter();
+
+ let mut removed_component_ids =
+ Vec::<Uid>::with_capacity(component_id_iter.size_hint().0);
- let stats = stats_lock
- .downcast_mut::<Stats>()
- .expect("Casting stats sole to Stats type failed");
+ for component_id in component_id_iter {
+ if let Err(err) =
+ component_storage.remove_entity_component(entity_uid, component_id)
+ {
+ tracing::error!("Failed to remove component to entity: {err}");
+ continue;
+ }
- stats.current_tick += 1;
+ removed_component_ids.push(component_id);
}
+
+ removed_component_ids
}
- fn emit_event_by_id(&self, event_id: EventId)
+ fn emit_event_by_id<Event: Component>(&self, target: Uid)
{
- let Some(system_indices) = self.data.events.get(&event_id) else {
+ if target.kind() == UidKind::Pair {
return;
- };
+ }
- for system_index in system_indices {
- let system = self.systems.get(*system_index).unwrap();
+ let query = self.flexible_query(
+ QueryTerms::<2>::builder()
+ .with_required([SystemComponent::id(), Pair::new::<Event>(target).id()])
+ .build(),
+ );
- // SAFETY: The world lives long enough
+ for (system,) in QueryIter::<(&SystemComponent,), _>::new(self, query.iter()) {
unsafe {
- system.run(self);
+ system.system.run(self);
}
}
}
@@ -356,45 +568,95 @@ impl World
*has_swapped_active_queue = true;
}
- fn lock_component_storage_rw(&self) -> WriteGuard<'_, ComponentStorage>
+ fn get_entity(
+ component_storage: &mut ComponentStorage,
+ entity_uid: Uid,
+ ) -> Option<EntityHandle<'_>>
{
- self.data
- .component_storage
- .write_nonblock()
- .expect("Failed to acquire read-write component storage lock")
+ let archetype = component_storage.get_entity_archetype(entity_uid)?;
+
+ Some(EntityHandle::new(
+ archetype,
+ archetype
+ .get_entity_by_id(entity_uid)
+ .expect("Not possible"),
+ ))
}
}
-#[derive(Debug, Default)]
-pub struct WorldData
+impl Default for World
{
- events: HashMap<EventId, Vec<usize>>,
- component_storage: Arc<Lock<ComponentStorage>>,
- sole_storage: SoleStorage,
- action_queue: Arc<ActionQueue>,
+ 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)]
-#[non_exhaustive]
-pub struct EntityComponent
+struct WorldData
{
- pub id: Uid,
- pub name: &'static str,
- pub component: Lock<Box<dyn Component>>,
+ component_storage: ComponentStorage,
+ sole_storage: SoleStorage,
+ action_queue: Rc<ActionQueue>,
+ pending_removals: Vec<(Uid, PendingRemoval)>,
}
-impl From<Box<dyn Component>> for EntityComponent
+impl Default for WorldData
{
- fn from(component: Box<dyn Component>) -> Self
+ fn default() -> Self
{
Self {
- id: component.self_id(),
- name: component.type_name(),
- component: Lock::new(component),
+ component_storage: ComponentStorage::default(),
+ sole_storage: SoleStorage::default(),
+ action_queue: Rc::new(ActionQueue::default()),
+ pending_removals: Vec::new(),
}
}
}
+#[derive(Debug)]
+enum PendingRemoval
+{
+ Components(Vec<Uid>),
+ Entity,
+}
+
+#[derive(Debug)]
+pub struct EntityComponentRef<'a>
+{
+ component_id: Uid,
+ component: &'a ArchetypeEntityComponent,
+}
+
+impl<'a> EntityComponentRef<'a>
+{
+ fn component(&self) -> &'a Lock<Box<dyn Any>>
+ {
+ self.component.component()
+ }
+
+ #[must_use]
+ pub fn id(&self) -> Uid
+ {
+ self.component_id
+ }
+
+ fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self
+ {
+ Self { component_id, component: comp }
+ }
+}
+
#[derive(Debug, Default, Clone, Copy)]
enum ActiveActionQueue
{
@@ -403,7 +665,7 @@ enum ActiveActionQueue
B,
}
-#[derive(Debug, Default)]
+#[derive(Debug)]
struct ActionQueue
{
queue_a: Lock<Vec<Action>>,
@@ -430,11 +692,15 @@ impl ActionQueue
}
}
-impl TypeName for ActionQueue
+impl Default for ActionQueue
{
- fn type_name(&self) -> &'static str
+ fn default() -> Self
{
- type_name::<Self>()
+ Self {
+ queue_a: Lock::new(Vec::new(), type_name::<Vec<Action>>()),
+ queue_b: Lock::new(Vec::new(), type_name::<Vec<Action>>()),
+ active_queue: RefCell::new(ActiveActionQueue::default()),
+ }
}
}
@@ -479,7 +745,7 @@ impl SoleStorage
self.storage.insert(
sole_type_id,
ManuallyDrop::new(StoredSole {
- sole: Arc::new(Lock::new(Box::new(sole))),
+ sole: Arc::new(Lock::new(Box::new(sole), type_name::<SoleT>())),
drop_last,
}),
);
@@ -496,34 +762,16 @@ impl Drop for SoleStorage
for sole in self.storage.values_mut() {
if sole.drop_last {
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Sole {} pushed to dropping last queue",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
soles_to_drop_last.push(sole);
continue;
}
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Dropping sole {}",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
unsafe {
ManuallyDrop::drop(sole);
}
}
for sole in &mut soles_to_drop_last {
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Dropping sole {} last",
- sole.sole.read_nonblock().unwrap().type_name()
- );
-
unsafe {
ManuallyDrop::drop(sole);
}
diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs
index fbc6842..0b36922 100644
--- a/ecs/src/lock.rs
+++ b/ecs/src/lock.rs
@@ -1,24 +1,29 @@
-use std::mem::transmute;
+use std::mem::forget;
use std::ops::{Deref, DerefMut};
-use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError};
-use crate::type_name::TypeName;
+use parking_lot::{
+ MappedRwLockReadGuard,
+ MappedRwLockWriteGuard,
+ RwLock,
+ RwLockReadGuard,
+ RwLockWriteGuard,
+};
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct Lock<Value>
-where
- Value: TypeName,
{
inner: RwLock<Value>,
+ value_type_name: &'static str,
}
impl<Value> Lock<Value>
-where
- Value: TypeName,
{
- pub fn new(value: Value) -> Self
+ pub fn new(value: Value, value_type_name: &'static str) -> Self
{
- Self { inner: RwLock::new(value) }
+ Self {
+ inner: RwLock::new(value),
+ value_type_name,
+ }
}
/// Tries to a acquire a handle to the resource with read access.
@@ -27,15 +32,14 @@ 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());
+ tracing::trace!("Acquired lock to value of type {}", self.value_type_name);
- Ok(ReadGuard { inner: guard })
+ Ok(ReadGuard {
+ inner: guard,
+ value_type_name: self.value_type_name,
+ })
}
/// Tries to a acquire a handle to the resource with mutable access.
@@ -44,62 +48,59 @@ 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()
+ self.value_type_name
);
- Ok(WriteGuard { inner: guard })
- }
-
- pub fn into_inner(self) -> Value
- {
- self.inner
- .into_inner()
- .unwrap_or_else(PoisonError::into_inner)
+ Ok(WriteGuard {
+ inner: guard,
+ value_type_name: self.value_type_name,
+ })
}
}
#[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)]
pub struct ReadGuard<'guard, Value>
-where
- Value: TypeName,
{
inner: RwLockReadGuard<'guard, Value>,
+ value_type_name: &'static str,
}
impl<'guard, Value> ReadGuard<'guard, Value>
-where
- Value: TypeName,
{
- /// Converts the `ReadGuard` to a `ReadGuard` with a possibly longer lifetime.
- ///
- /// # Safety
- /// The returned `ReadGuard` must **NOT** be used for longer than the original
- /// lifetime.
- #[must_use]
- pub unsafe fn upgrade_lifetime<'new>(self) -> ReadGuard<'new, Value>
+ pub fn map<NewValue>(
+ self,
+ func: impl FnOnce(&Value) -> &NewValue,
+ ) -> MappedReadGuard<'guard, NewValue>
{
- unsafe { transmute(self) }
+ let value_type_name = self.value_type_name;
+
+ // The 'inner' field cannot be moved out of ReadGuard in a normal way since
+ // ReadGuard implements Drop
+ let inner = unsafe { std::ptr::read(&self.inner) };
+ forget(self);
+
+ MappedReadGuard {
+ inner: RwLockReadGuard::map(inner, func),
+ value_type_name,
+ }
}
}
-impl<'guard, Value> Deref for ReadGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Deref for ReadGuard<'_, Value>
{
type Target = Value;
@@ -109,28 +110,71 @@ where
}
}
-impl<'guard, Value> Drop for ReadGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Drop for ReadGuard<'_, Value>
{
fn drop(&mut self)
{
- #[cfg(feature = "debug")]
- tracing::trace!("Dropped lock to value of type {}", self.type_name());
+ tracing::trace!("Dropped lock to value of type {}", self.value_type_name);
+ }
+}
+
+#[derive(Debug)]
+pub struct MappedReadGuard<'guard, Value>
+{
+ inner: MappedRwLockReadGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<Value> Deref for MappedReadGuard<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ &self.inner
+ }
+}
+
+impl<Value> Drop for MappedReadGuard<'_, Value>
+{
+ fn drop(&mut self)
+ {
+ tracing::trace!(
+ "Dropped mapped lock to value of type {}",
+ self.value_type_name
+ );
}
}
#[derive(Debug)]
pub struct WriteGuard<'guard, Value>
-where
- Value: TypeName,
{
inner: RwLockWriteGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<'guard, Value> WriteGuard<'guard, Value>
+{
+ pub fn map<NewValue>(
+ self,
+ func: impl FnOnce(&mut Value) -> &mut NewValue,
+ ) -> MappedWriteGuard<'guard, NewValue>
+ {
+ let value_type_name = self.value_type_name;
+
+ // The 'inner' field cannot be moved out of ReadGuard in a normal way since
+ // ReadGuard implements Drop
+ let inner = unsafe { std::ptr::read(&self.inner) };
+ forget(self);
+
+ MappedWriteGuard {
+ inner: RwLockWriteGuard::map(inner, func),
+ value_type_name,
+ }
+ }
}
-impl<'guard, Value> Deref for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Deref for WriteGuard<'_, Value>
{
type Target = Value;
@@ -140,9 +184,7 @@ where
}
}
-impl<'guard, Value> DerefMut for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> DerefMut for WriteGuard<'_, Value>
{
fn deref_mut(&mut self) -> &mut Self::Target
{
@@ -150,13 +192,49 @@ where
}
}
-impl<'guard, Value> Drop for WriteGuard<'guard, Value>
-where
- Value: TypeName,
+impl<Value> Drop for WriteGuard<'_, Value>
{
fn drop(&mut self)
{
- #[cfg(feature = "debug")]
- tracing::trace!("Dropped mutable lock to value of type {}", self.type_name());
+ tracing::trace!(
+ "Dropped mutable lock to value of type {}",
+ self.value_type_name
+ );
+ }
+}
+
+#[derive(Debug)]
+pub struct MappedWriteGuard<'guard, Value>
+{
+ inner: MappedRwLockWriteGuard<'guard, Value>,
+ value_type_name: &'static str,
+}
+
+impl<Value> Deref for MappedWriteGuard<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ &self.inner
+ }
+}
+
+impl<Value> DerefMut for MappedWriteGuard<'_, Value>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target
+ {
+ &mut self.inner
+ }
+}
+
+impl<Value> Drop for MappedWriteGuard<'_, Value>
+{
+ fn drop(&mut self)
+ {
+ tracing::trace!(
+ "Dropped mapped mutable lock to value of type {}",
+ self.value_type_name
+ );
}
}
diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs
new file mode 100644
index 0000000..4ff4995
--- /dev/null
+++ b/ecs/src/pair.rs
@@ -0,0 +1,198 @@
+use std::convert::Infallible;
+
+use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts};
+use crate::entity::{
+ Handle as EntityHandle,
+ MatchingComponentIter as EntityMatchingComponentIter,
+};
+use crate::query::{
+ TermWithField as QueryTermWithField,
+ TermsBuilder as QueryTermsBuilder,
+ TermsBuilderInterface,
+};
+use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid};
+use crate::{Component, World};
+
+#[derive(Debug)]
+pub struct Pair<RelationElem: Element, TargetElem: Element>
+{
+ relation: RelationElem,
+ target: TargetElem,
+}
+
+impl Pair<Uid, Uid>
+{
+ #[must_use]
+ pub fn new<Relation: WithUid>(target: Uid) -> Self
+ {
+ Self { relation: Relation::uid(), target }
+ }
+
+ #[must_use]
+ pub fn id(&self) -> Uid
+ {
+ Uid::new_pair(&UidPairParams {
+ relation: self.relation,
+ target: self.target,
+ })
+ }
+}
+
+impl IntoComponentParts for Pair<Uid, Uid>
+{
+ fn into_parts(self) -> ComponentParts
+ {
+ ComponentParts::builder().name("Pair").build(self.id(), ())
+ }
+}
+
+impl<Relation, Target> QueryTermWithField for Pair<Relation, Target>
+where
+ Relation: WithUid,
+ Target: WithUid,
+{
+ type Field<'a> = Handle<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with_required([Self::uid()]);
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ let first_matching_comp = entity_handle
+ .get_matching_components(Self::uid())
+ .next()
+ .expect("Not possible");
+
+ Handle {
+ world,
+ pair_uid: first_matching_comp.id(),
+ }
+ }
+}
+
+impl<Relation, Target> WithUid for Pair<Relation, Target>
+where
+ Relation: WithUid,
+ Target: WithUid,
+{
+ fn uid() -> Uid
+ {
+ Uid::new_pair(&UidPairParams {
+ relation: Relation::uid(),
+ target: Target::uid(),
+ })
+ }
+}
+
+impl<Relation> QueryTermWithField for &'static [Pair<Relation, Wildcard>]
+where
+ Relation: WithUid,
+{
+ type Field<'a> = HandleIter<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with_required([Pair::<Relation, Wildcard>::uid()]);
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ HandleIter {
+ inner: entity_handle
+ .get_matching_components(Pair::<Relation, Wildcard>::uid()),
+ world,
+ }
+ }
+}
+
+pub struct Handle<'world>
+{
+ world: &'world World,
+ pair_uid: Uid,
+}
+
+impl Handle<'_>
+{
+ #[must_use]
+ pub fn get_target_entity(&self) -> Option<EntityHandle<'_>>
+ {
+ let archetype = self
+ .world
+ .data
+ .component_storage
+ .get_entity_archetype(self.pair_uid.target_entity())?;
+
+ let Some(archetype_entity) =
+ archetype.get_entity_by_id(self.pair_uid.target_entity())
+ else {
+ unreachable!();
+ };
+
+ Some(EntityHandle::new(archetype, archetype_entity))
+ }
+}
+
+pub struct HandleIter<'a>
+{
+ inner: EntityMatchingComponentIter<'a>,
+ world: &'a World,
+}
+
+impl<'a> Iterator for HandleIter<'a>
+{
+ type Item = Handle<'a>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let matching_comp = self.inner.next()?;
+
+ Some(Handle {
+ world: self.world,
+ pair_uid: matching_comp.id(),
+ })
+ }
+}
+
+pub trait Element: sealed::Sealed
+{
+ type Value;
+}
+
+impl Element for Uid
+{
+ type Value = Uid;
+}
+
+impl sealed::Sealed for Uid {}
+
+impl<WithUidT: WithUid> Element for WithUidT
+{
+ type Value = Infallible;
+}
+
+impl<WithUidT: WithUid> sealed::Sealed for WithUidT {}
+
+/// Relation denoting a dependency to another entity
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct DependsOn;
+
+/// Relation denoting being the child of another entity.
+#[derive(Debug, Default, Clone, Copy, Component)]
+pub struct ChildOf;
+
+mod sealed
+{
+ pub trait Sealed {}
+}
diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs
new file mode 100644
index 0000000..9f47fb8
--- /dev/null
+++ b/ecs/src/phase.rs
@@ -0,0 +1,13 @@
+use ecs_macros::Component;
+
+use crate::pair::{ChildOf, Pair};
+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, Pair::new::<ChildOf>(*PRE_UPDATE)));
diff --git a/ecs/src/query.rs b/ecs/src/query.rs
index f3318bd..ccb7add 100644
--- a/ecs/src/query.rs
+++ b/ecs/src/query.rs
@@ -1,115 +1,91 @@
-use std::iter::{Filter, Flatten, Map};
+use std::any::type_name;
use std::marker::PhantomData;
-use crate::component::storage::{
- Archetype,
- ArchetypeEntity,
- ArchetypeRefIter,
- EntityIter,
- Storage as ComponentStorage,
-};
+use seq_macro::seq;
+
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,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
};
-use crate::uid::Uid;
-use crate::{EntityComponent, World};
+use crate::entity::Handle as EntityHandle;
+use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery};
+use crate::system::{Param as SystemParam, System};
+use crate::uid::{Kind as UidKind, Uid, With as WithUid};
+use crate::util::array_vec::ArrayVec;
+use crate::util::Array;
+use crate::World;
-pub mod options;
+pub mod flexible;
+pub mod term;
#[derive(Debug)]
-pub struct Query<'world, Comps, OptionsT = ()>
+pub struct Query<'world, FieldTerms, FieldlessTerms = ()>
where
- Comps: ComponentSequence,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
world: &'world World,
- component_storage: ReadGuard<'world, ComponentStorage>,
- _pd: PhantomData<(Comps, OptionsT)>,
+ // A term tuple type can have a maximum of 17 elements
+ inner: FlexibleQuery<'world, 17>,
+ _pd: PhantomData<(FieldTerms, FieldlessTerms)>,
}
-impl<'world, Comps, OptionsT> Query<'world, Comps, OptionsT>
+impl<'world, FieldTerms, FieldlessTerms> Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- /// 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,
+ ) -> Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>
{
- #[cfg(feature = "debug")]
- tracing::debug!("Searching for {}", std::any::type_name::<Comps>());
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
- #[allow(clippy::map_flatten)]
- ComponentIterMut {
+ Iter {
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(),
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 matching 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, FieldTerms, FlexibleQueryIter<'query>>
{
- #[cfg(feature = "debug")]
- tracing::debug!("Searching for {}", std::any::type_name::<Comps>());
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
- #[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(),
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
+ /// [`Iter`] 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,
+ ) -> Iter<'query, 'world, FieldTerms, OutIter>
+ where
+ OutIter: Iterator<Item = EntityHandle<'query>>,
{
- #[cfg(feature = "debug")]
- tracing::debug!(
- "Searching for {} + extra components",
- std::any::type_name::<Comps>()
- );
-
- #[allow(clippy::map_flatten)]
- ComponentIter {
+ tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>());
+
+ Iter {
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()),
comps_pd: PhantomData,
}
}
@@ -118,51 +94,45 @@ 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().nth(entity_index)?.uid())
}
pub(crate) fn new(world: &'world World) -> Self
{
+ let mut terms_builder = Terms::builder();
+
+ FieldTerms::apply_terms_to_builder(&mut terms_builder);
+ FieldlessTerms::apply_terms_to_builder(&mut terms_builder);
+
Self {
world,
- component_storage: world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to acquire read-only component storage lock"),
+ inner: world.flexible_query(terms_builder.build()),
_pd: PhantomData,
}
}
}
-impl<'world, Comps, OptionsT> IntoIterator for &'world Query<'world, Comps, OptionsT>
+impl<'query, 'world, FieldTerms, FieldlessTerms> IntoIterator
+ for &'query Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- type IntoIter = ComponentIterMut<'world, Comps, QueryEntityIter<'world>>;
- type Item = Comps::MutRefs<'world>;
+ type IntoIter = Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>;
+ type Item = FieldTerms::Fields<'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, FieldTerms, FieldlessTerms> SystemParam<'world>
+ for Query<'world, FieldTerms, FieldlessTerms>
where
- Comps: ComponentSequence,
- OptionsT: Options,
+ FieldTerms: TermWithFieldTuple,
+ FieldlessTerms: TermWithoutFieldTuple,
{
- type Flags = NoInitSystemParamFlag;
type Input = ();
fn initialize<SystemImpl>(
@@ -181,93 +151,429 @@ where
}
}
-type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> EntityIter<'a>;
+#[derive(Debug)]
+pub struct Terms<const MAX_TERM_CNT: usize>
+{
+ required_components: ArrayVec<Uid, MAX_TERM_CNT>,
+ excluded_components: ArrayVec<Uid, MAX_TERM_CNT>,
+}
-type ComponentIterFilterFn = for<'a, 'b> fn(&'a &'b ArchetypeEntity) -> bool;
+impl<const MAX_TERM_CNT: usize> Terms<MAX_TERM_CNT>
+{
+ pub fn builder() -> TermsBuilder<MAX_TERM_CNT>
+ {
+ TermsBuilder::default()
+ }
+}
-type QueryEntityIter<'world> = Filter<
- Flatten<Map<ArchetypeRefIter<'world>, ComponentIterMapFn>>,
- ComponentIterFilterFn,
->;
+#[derive(Debug, Default)]
+#[must_use]
+pub struct TermsBuilder<const MAX_TERM_CNT: usize>
+{
+ required_components: ArrayVec<Uid, MAX_TERM_CNT>,
+ excluded_components: ArrayVec<Uid, MAX_TERM_CNT>,
+}
-pub struct ComponentIterMut<'world, Comps, EntityIter>
-where
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+#[allow(clippy::return_self_not_must_use)]
+pub trait TermsBuilderInterface
{
- world: &'world World,
- entities: EntityIter,
- comps_pd: PhantomData<Comps>,
+ fn with<WithUidT: WithUid>(self) -> Self;
+
+ fn without<WithUidT: WithUid>(self) -> Self;
+
+ fn with_required(self, ids: impl Array<Uid>) -> Self;
+
+ fn without_ids(self, ids: impl Array<Uid>) -> Self;
}
-impl<'world, Comps, EntityIter> Iterator for ComponentIterMut<'world, Comps, EntityIter>
-where
- Comps: ComponentSequence + 'world,
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+macro_rules! impl_terms_builder {
+ ($($impl_content: tt)*) => {
+ impl<const MAX_TERM_CNT: usize>
+ TermsBuilderInterface for TermsBuilder<MAX_TERM_CNT>
+ {
+ $($impl_content)*
+ }
+
+ impl<const MAX_TERM_CNT: usize>
+ TermsBuilderInterface for &mut TermsBuilder<MAX_TERM_CNT>
+ {
+ $($impl_content)*
+ }
+ };
+}
+
+impl_terms_builder! {
+ #[allow(unused_mut)]
+ fn with<WithUidT: WithUid>(mut self) -> Self
+ {
+ let insert_index = self.required_components
+ .partition_point(|id| *id <= WithUidT::uid());
+
+ self.required_components
+ .insert(insert_index, WithUidT::uid());
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn without<WithUidT: WithUid>(mut self) -> Self
+ {
+ let insert_index = self.excluded_components
+ .partition_point(|id| *id <= WithUidT::uid());
+
+ self.excluded_components
+ .insert(insert_index, WithUidT::uid());
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn with_required(mut self, mut ids: impl Array<Uid>) -> Self
+ {
+ if !ids.as_ref().is_sorted() {
+ ids.as_mut().sort();
+ }
+
+ if self.required_components.is_empty() {
+ self.required_components.extend(ids);
+ return self;
+ }
+
+ let mut id_iter = ids.into_iter();
+
+ while let Some(id) = id_iter.next() {
+ let insert_index = self.required_components
+ .partition_point(|other_id| *other_id <= id);
+
+ if insert_index == self.required_components.len() {
+ self.required_components.extend([id].into_iter().chain(id_iter));
+
+ return self;
+ }
+
+ self.required_components
+ .insert(insert_index, id);
+
+ }
+
+ self
+ }
+
+ #[allow(unused_mut)]
+ fn without_ids(mut self, mut ids: impl Array<Uid>) -> Self
+ {
+ if !ids.as_ref().is_sorted() {
+ ids.as_mut().sort();
+ }
+
+ if self.excluded_components.is_empty() {
+ self.excluded_components.extend(ids);
+ return self;
+ }
+
+ let mut id_iter = ids.into_iter();
+
+ while let Some(id) = id_iter.next() {
+ let insert_index = self.excluded_components
+ .partition_point(|other_id| *other_id <= id);
+
+ if insert_index == self.excluded_components.len() {
+ self.excluded_components.extend([id].into_iter().chain(id_iter));
+
+ return self;
+ }
+
+ self.excluded_components
+ .insert(insert_index, id);
+
+ }
+
+ self
+ }
+}
+
+impl<const MAX_TERM_CNT: usize> TermsBuilder<MAX_TERM_CNT>
{
- type Item = Comps::MutRefs<'world>;
+ #[must_use]
+ pub fn build(self) -> Terms<MAX_TERM_CNT>
+ {
+ debug_assert!(self.required_components.is_sorted());
+ debug_assert!(self.excluded_components.is_sorted());
- fn next(&mut self) -> Option<Self::Item>
+ Terms {
+ required_components: self.required_components,
+ excluded_components: self.excluded_components,
+ }
+ }
+}
+
+pub trait TermWithoutField
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+}
+
+pub trait TermWithField
+{
+ type Field<'a>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ world: &'world World,
+ ) -> Self::Field<'world>;
+}
+
+impl<ComponentT: Component> TermWithField for &ComponentT
+{
+ type Field<'a> = ComponentHandle<'a, ComponentT>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
{
- Some(Comps::from_components_mut(
- self.entities.next()?.components().iter(),
- self.world,
- lock_component_rw,
- ))
+ terms_builder.with::<ComponentT>();
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ _world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ })
}
}
-fn lock_component_rw(
- entity_component: &EntityComponent,
-) -> WriteGuard<'_, Box<dyn Component>>
+impl<ComponentT: Component> TermWithField for &mut ComponentT
{
- entity_component
- .component
- .write_nonblock()
- .unwrap_or_else(|_| {
+ type Field<'a> = ComponentHandleMut<'a, ComponentT>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with::<ComponentT>();
+ }
+
+ fn get_field<'world>(
+ entity_handle: &EntityHandle<'world>,
+ _world: &'world World,
+ ) -> Self::Field<'world>
+ {
+ assert_eq!(ComponentT::id().kind(), UidKind::Component);
+
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
panic!(
- "Failed to acquire read-write lock to component {}",
- entity_component.name
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
);
})
+ }
+}
+
+pub trait TermWithoutFieldTuple
+{
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+}
+
+pub trait TermWithFieldTuple
+{
+ type Fields<'component>;
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ );
+
+ fn get_fields<'component>(
+ entity_handle: &EntityHandle<'component>,
+ world: &'component World,
+ ) -> Self::Fields<'component>;
+}
+
+pub struct Iter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+{
+ world: &'world World,
+ iter: EntityHandleIter,
+ comps_pd: PhantomData<FieldTerms>,
}
-pub struct ComponentIter<'world, Comps, EntityIter>
+impl<'query, 'world, FieldTerms, EntityHandleIter>
+ Iter<'query, 'world, FieldTerms, EntityHandleIter>
where
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
+{
+ /// Creates a new iterator from the given entity handle iterator.
+ ///
+ /// # Important
+ /// All of the yielded entities of the entity handle iterator should match the
+ /// terms `Terms`. The [`Self::next`] function will panic if it encounters a
+ /// entity that does not match the terms `Terms`.
+ pub fn new(world: &'world World, iter: EntityHandleIter) -> Self
+ {
+ Self { world, iter, comps_pd: PhantomData }
+ }
+}
+
+impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
+ for Iter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
+{
+ type Item = FieldTerms::Fields<'query>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let entity_handle = self.iter.next()?;
+
+ Some(FieldTerms::get_fields(&entity_handle, self.world))
+ }
+}
+
+pub struct ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
+where
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
{
world: &'world World,
- entities: EntityIter,
- comps_pd: PhantomData<Comps>,
+ iter: EntityHandleIter,
+ comps_pd: PhantomData<FieldTerms>,
}
-impl<'world, Comps, EntityIter> Iterator for ComponentIter<'world, Comps, EntityIter>
+impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator
+ for ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter>
where
- Comps: ComponentSequence + 'world,
- EntityIter: Iterator<Item = &'world ArchetypeEntity>,
+ FieldTerms: TermWithFieldTuple,
+ EntityHandleIter: Iterator<Item = EntityHandle<'query>>,
+ 'world: 'query,
{
- type Item = Comps::Refs<'world>;
+ type Item = (Uid, FieldTerms::Fields<'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(),
+ FieldTerms::get_fields(&entity_handle, self.world),
))
}
}
-fn lock_component_ro(
- entity_component: &EntityComponent,
-) -> ReadGuard<'_, Box<dyn Component>>
+macro_rules! impl_term_sequence {
+ ($c: tt) => {
+ seq!(I in 0..=$c {
+ impl<#(Term~I: TermWithoutField,)*> TermWithoutFieldTuple for (#(Term~I,)*)
+ {
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>
+ )
+ {
+ #(
+ Term~I::apply_to_terms_builder(terms_builder);
+ )*
+ }
+ }
+
+ impl<#(Term~I: TermWithField,)*> TermWithFieldTuple for (#(Term~I,)*)
+ {
+ type Fields<'component> = (#(Term~I::Field<'component>,)*);
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>
+ )
+ {
+ #(
+ Term~I::apply_to_terms_builder(terms_builder);
+ )*
+ }
+
+ fn get_fields<'component>(
+ entity_handle: &EntityHandle<'component>,
+ world: &'component World,
+ ) -> Self::Fields<'component>
+ {
+ (#(Term~I::get_field(entity_handle, world),)*)
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..=16 {
+ impl_term_sequence!(C);
+});
+
+impl TermWithoutFieldTuple for ()
+{
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+}
+
+impl TermWithFieldTuple for ()
{
- entity_component
- .component
- .read_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to acquire read-write lock to component {}",
- entity_component.name
- );
- })
+ type Fields<'component> = ();
+
+ fn apply_terms_to_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_fields<'component>(
+ _entity_handle: &EntityHandle<'_>,
+ _world: &'component World,
+ ) -> Self::Fields<'component>
+ {
+ }
}
diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs
new file mode 100644
index 0000000..add30b0
--- /dev/null
+++ b/ecs/src/query/flexible.rs
@@ -0,0 +1,84 @@
+//! Low-level querying.
+use std::iter::{repeat_n, FlatMap, RepeatN, Zip};
+
+use crate::component::storage::archetype::{Archetype, EntityIter};
+use crate::component::storage::{ArchetypeRefIter, ArchetypeSearchTerms};
+use crate::entity::Handle as EntityHandle;
+use crate::query::Terms;
+use crate::World;
+
+/// Low-level entity query structure.
+#[derive(Debug)]
+pub struct Query<'world, const MAX_TERM_CNT: usize>
+{
+ world: &'world World,
+ terms: Terms<MAX_TERM_CNT>,
+}
+
+impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
+{
+ /// Iterates over the entities matching this query.
+ #[must_use]
+ pub fn iter(&self) -> Iter<'_>
+ {
+ Iter {
+ iter: self
+ .world
+ .data
+ .component_storage
+ .search_archetypes(ArchetypeSearchTerms {
+ required_components: &self.terms.required_components,
+ excluded_components: &self.terms.excluded_components,
+ })
+ .flat_map(
+ (|archetype| {
+ repeat_n(archetype, archetype.entity_cnt())
+ .zip(archetype.entities())
+ }) as ComponentIterMapFn,
+ ),
+ }
+ }
+
+ pub(crate) fn new(world: &'world World, terms: Terms<MAX_TERM_CNT>) -> Self
+ {
+ Self { world, terms }
+ }
+}
+
+impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_TERM_CNT>
+{
+ type IntoIter = Iter<'query>;
+ type Item = EntityHandle<'query>;
+
+ fn into_iter(self) -> Self::IntoIter
+ {
+ self.iter()
+ }
+}
+
+pub struct Iter<'query>
+{
+ iter: QueryEntityIter<'query>,
+}
+
+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::new(archetype, entity))
+ }
+}
+
+type ComponentIterMapFnOutput<'a> = Zip<RepeatN<&'a Archetype>, EntityIter<'a>>;
+
+type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> ComponentIterMapFnOutput<'a>;
+
+type QueryEntityIter<'query> = FlatMap<
+ ArchetypeRefIter<'query, 'query>,
+ ComponentIterMapFnOutput<'query>,
+ ComponentIterMapFn,
+>;
diff --git a/ecs/src/query/options.rs b/ecs/src/query/options.rs
deleted file mode 100644
index bbbe0a8..0000000
--- a/ecs/src/query/options.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use std::collections::HashSet;
-use std::marker::PhantomData;
-
-use crate::component::Component;
-use crate::EntityComponent;
-
-/// Query options.
-pub trait Options
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool;
-}
-
-impl Options for ()
-{
- fn entity_filter<'component>(
- _: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- true
- }
-}
-
-pub struct With<ComponentT>
-where
- ComponentT: Component,
-{
- _pd: PhantomData<ComponentT>,
-}
-
-impl<ComponentT> Options for With<ComponentT>
-where
- ComponentT: Component,
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- let ids_set = components
- .into_iter()
- .map(|component| component.id)
- .collect::<HashSet<_>>();
-
- ids_set.contains(&ComponentT::id())
- }
-}
-
-pub struct Not<OptionsT>
-where
- OptionsT: Options,
-{
- _pd: PhantomData<OptionsT>,
-}
-
-impl<OptionsT> Options for Not<OptionsT>
-where
- OptionsT: Options,
-{
- fn entity_filter<'component>(
- components: impl IntoIterator<Item = &'component EntityComponent>,
- ) -> bool
- {
- !OptionsT::entity_filter(components)
- }
-}
diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs
new file mode 100644
index 0000000..9c772da
--- /dev/null
+++ b/ecs/src/query/term.rs
@@ -0,0 +1,115 @@
+use std::any::type_name;
+use std::marker::PhantomData;
+
+use crate::component::{
+ Component,
+ Handle as ComponentHandle,
+ HandleMut as ComponentHandleMut,
+};
+use crate::query::{
+ TermWithField,
+ TermWithoutField,
+ TermsBuilder,
+ TermsBuilderInterface,
+};
+use crate::uid::With as WithUid;
+
+pub struct With<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ _pd: PhantomData<WithUidT>,
+}
+
+impl<WithUidT> TermWithoutField for With<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.with::<WithUidT>();
+ }
+}
+
+pub struct Without<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ _pd: PhantomData<WithUidT>,
+}
+
+impl<WithUidT> TermWithoutField for Without<WithUidT>
+where
+ WithUidT: WithUid,
+{
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ terms_builder.without::<WithUidT>();
+ }
+}
+
+impl<ComponentT: Component> TermWithField for Option<&ComponentT>
+{
+ type Field<'a> = Option<ComponentHandle<'a, ComponentT>>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_field<'world>(
+ entity_handle: &crate::entity::Handle<'world>,
+ _world: &'world crate::World,
+ ) -> Self::Field<'world>
+ {
+ Some(
+ ComponentHandle::<'world, ComponentT>::from_entity_component_ref(
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+}
+
+impl<ComponentT: Component> TermWithField for Option<&mut ComponentT>
+{
+ type Field<'a> = Option<ComponentHandleMut<'a, ComponentT>>;
+
+ fn apply_to_terms_builder<const MAX_TERM_CNT: usize>(
+ _terms_builder: &mut TermsBuilder<MAX_TERM_CNT>,
+ )
+ {
+ }
+
+ fn get_field<'world>(
+ entity_handle: &crate::entity::Handle<'world>,
+ _world: &'world crate::World,
+ ) -> Self::Field<'world>
+ {
+ Some(
+ ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref(
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Creating handle to component {} failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
+ )
+ }
+}
diff --git a/ecs/src/relationship.rs b/ecs/src/relationship.rs
deleted file mode 100644
index 0ebd9c2..0000000
--- a/ecs/src/relationship.rs
+++ /dev/null
@@ -1,418 +0,0 @@
-use std::any::type_name;
-use std::marker::PhantomData;
-
-use ecs_macros::Component;
-
-use crate::component::storage::Storage as ComponentStorage;
-use crate::component::{
- Component,
- FromOptional as FromOptionalComponent,
- FromOptionalMut as FromOptionalMutComponent,
-};
-use crate::lock::ReadGuard;
-use crate::system::{ComponentRef, ComponentRefMut};
-use crate::uid::{Kind as UidKind, Uid};
-use crate::World;
-
-/// A relationship to one or more targets.
-#[derive(Debug, Component)]
-#[component(
- ref_type = Relation<'component, Kind, ComponentT>,
- ref_mut_type = RelationMut<'component, Kind, ComponentT>,
-)]
-pub struct Relationship<Kind, ComponentT: Component>
-where
- Kind: 'static,
-{
- entity_uid: SingleOrMultiple<Uid>,
- _pd: PhantomData<(Kind, ComponentT)>,
-}
-
-impl<Kind, ComponentT> Relationship<Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Creates a new `Relationship` with a single target.
- #[must_use]
- pub fn new(entity_uid: Uid) -> Self
- {
- debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
- Self {
- entity_uid: SingleOrMultiple::Single(entity_uid),
- _pd: PhantomData,
- }
- }
-
- /// Creates a new `Relationship` with multiple targets.
- #[must_use]
- pub fn new_multiple(entity_uids: impl IntoIterator<Item = Uid>) -> Self
- {
- let uids = entity_uids.into_iter().collect::<Vec<_>>();
-
- for euid in &uids {
- debug_assert_eq!(euid.kind(), UidKind::Entity);
- }
-
- Self {
- entity_uid: SingleOrMultiple::Multiple(uids),
- _pd: PhantomData,
- }
- }
-}
-
-pub struct RelationMut<'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- component_storage_lock: ReadGuard<'static, ComponentStorage>,
- relationship_comp: ComponentRefMut<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> FromOptionalMutComponent<'rel_comp>
- for RelationMut<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- fn from_optional_mut_component(
- optional_component: Option<
- crate::lock::WriteGuard<'rel_comp, Box<dyn Component>>,
- >,
- world: &'rel_comp World,
- ) -> Self
- {
- let relationship_comp =
- ComponentRefMut::<Relationship<Kind, ComponentT>>::from_optional_mut_component(
- optional_component,
- world,
- );
-
- let component_storage_lock = world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to aquire read-only component storage lock");
-
- Self {
- relationship_comp,
- // SAFETY: The component lock is not used for longer than the original
- // lifetime
- component_storage_lock: unsafe { component_storage_lock.upgrade_lifetime() },
- }
- }
-}
-
-impl<'rel_comp, Kind, ComponentT> RelationMut<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Returns the component of the target at the specified index.
- ///
- /// # Panics
- /// Will panic if the entity does not exist in the archetype it belongs to. This
- /// should hopefully never happend.
- #[must_use]
- pub fn get(&self, index: usize) -> Option<ComponentRefMut<'_, ComponentT>>
- {
- let target = self.get_target(index)?;
-
- let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
- let entity = archetype
- .get_entity(*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)?
- .component
- .write_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to aquire read-write lock of component {}",
- type_name::<ComponentT>()
- )
- }),
- );
-
- Some(component)
- }
-
- /// Returns a reference to the target at the specified index.
- #[must_use]
- pub fn get_target(&self, index: usize) -> Option<&Uid>
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- /// Returns a mutable reference to the target at the specified index.
- #[must_use]
- pub fn get_target_mut(&mut self, index: usize) -> Option<&mut Uid>
- {
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get_mut(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- /// Adds a target to the relationship.
- pub fn add_target(&mut self, entity_uid: Uid)
- {
- debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
-
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(prev_entity_uid) => {
- self.relationship_comp.entity_uid =
- SingleOrMultiple::Multiple(vec![*prev_entity_uid, entity_uid]);
- }
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.push(entity_uid),
- }
- }
-
- /// Removes a target to the relationship, returning it.
- pub fn remove_target(&mut self, index: usize) -> Option<Uid>
- {
- match &mut self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) => {
- let prev_entity_uid = *entity_uid;
-
- self.relationship_comp.entity_uid =
- SingleOrMultiple::Multiple(Vec::new());
-
- Some(prev_entity_uid)
- }
- SingleOrMultiple::Multiple(entity_uids) => {
- if index >= entity_uids.len() {
- return None;
- }
-
- Some(entity_uids.remove(index))
- }
- }
- }
-
- #[must_use]
- pub fn target_count(&self) -> usize
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(_) => 1,
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
- }
- }
-
- /// Returns a iterator of the components of the targets of this relationship.
- #[must_use]
- pub fn iter(&self) -> TargetComponentIterMut<'_, 'rel_comp, Kind, ComponentT>
- {
- TargetComponentIterMut { relation: self, index: 0 }
- }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
- for &'relationship RelationMut<'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- ComponentT: Component,
-{
- type IntoIter = TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>;
- type Item = ComponentRefMut<'rel_comp, ComponentT>;
-
- fn into_iter(self) -> Self::IntoIter
- {
- self.iter()
- }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- relation: &'relationship RelationMut<'rel_comp, Kind, ComponentT>,
- index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
- for TargetComponentIterMut<'relationship, 'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- Kind: 'static,
- ComponentT: Component,
-{
- type Item = ComponentRefMut<'rel_comp, ComponentT>;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- let index = self.index;
-
- self.index += 1;
-
- self.relation.get(index)
- }
-}
-
-#[derive(Debug)]
-enum SingleOrMultiple<Value>
-{
- Single(Value),
- Multiple(Vec<Value>),
-}
-
-pub struct Relation<'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- component_storage_lock: ReadGuard<'static, ComponentStorage>,
- relationship_comp: ComponentRef<'rel_comp, Relationship<Kind, ComponentT>>,
-}
-
-impl<'rel_comp, Kind, ComponentT> FromOptionalComponent<'rel_comp>
- for 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
- {
- let relationship_comp =
- ComponentRef::<Relationship<Kind, ComponentT>>::from_optional_component(
- optional_component,
- world,
- );
-
- let component_storage_lock = world
- .data
- .component_storage
- .read_nonblock()
- .expect("Failed to aquire read-only component storage lock");
-
- Self {
- relationship_comp,
- // SAFETY: The component lock is not used for longer than the original
- // lifetime
- component_storage_lock: unsafe { component_storage_lock.upgrade_lifetime() },
- }
- }
-}
-
-impl<'rel_comp, Kind, ComponentT> Relation<'rel_comp, Kind, ComponentT>
-where
- ComponentT: Component,
-{
- /// Returns the component of the target at the specified index.
- ///
- /// # Panics
- /// Will panic if the entity does not exist in the archetype it belongs to. This
- /// should hopefully never happend.
- #[must_use]
- pub fn get(&self, index: usize) -> Option<ComponentRef<'_, ComponentT>>
- {
- let target = self.get_target(index)?;
-
- let archetype = self.component_storage_lock.get_entity_archetype(*target)?;
-
- let entity = archetype
- .get_entity(*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)?
- .component
- .read_nonblock()
- .unwrap_or_else(|_| {
- panic!(
- "Failed to aquire read-write lock of component {}",
- type_name::<ComponentT>()
- )
- }),
- );
-
- Some(component)
- }
-
- /// Returns a reference to the target at the specified index.
- #[must_use]
- pub fn get_target(&self, index: usize) -> Option<&Uid>
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(entity_uid) if index == 0 => Some(entity_uid),
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.get(index),
- SingleOrMultiple::Single(_) => None,
- }
- }
-
- #[must_use]
- pub fn target_count(&self) -> usize
- {
- match &self.relationship_comp.entity_uid {
- SingleOrMultiple::Single(_) => 1,
- SingleOrMultiple::Multiple(entity_uids) => entity_uids.len(),
- }
- }
-
- /// Returns a iterator of the components of the targets of this relationship.
- #[must_use]
- pub fn iter(&self) -> TargetComponentIter<'_, 'rel_comp, Kind, ComponentT>
- {
- TargetComponentIter { relation: self, index: 0 }
- }
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> IntoIterator
- for &'relationship Relation<'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- ComponentT: Component,
-{
- type IntoIter = TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>;
- type Item = ComponentRef<'rel_comp, ComponentT>;
-
- fn into_iter(self) -> Self::IntoIter
- {
- self.iter()
- }
-}
-
-/// Iterator of the components of the targets of a relationship.
-pub struct TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
- Kind: 'static,
- ComponentT: Component,
-{
- relation: &'relationship Relation<'rel_comp, Kind, ComponentT>,
- index: usize,
-}
-
-impl<'relationship, 'rel_comp, Kind, ComponentT> Iterator
- for TargetComponentIter<'relationship, 'rel_comp, Kind, ComponentT>
-where
- 'relationship: 'rel_comp,
- Kind: 'static,
- ComponentT: Component,
-{
- type Item = ComponentRef<'rel_comp, ComponentT>;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- let index = self.index;
-
- self.index += 1;
-
- self.relation.get(index)
- }
-}
diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs
index 084a06b..1cce419 100644
--- a/ecs/src/sole.rs
+++ b/ecs/src/sole.rs
@@ -5,12 +5,11 @@ 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::type_name::TypeName;
+use crate::system::{Param as SystemParam, System};
use crate::World;
/// A type which has a single instance and is shared globally.
-pub trait Sole: Any + TypeName
+pub trait Sole: Any
{
fn drop_last(&self) -> bool;
@@ -40,14 +39,6 @@ impl Debug for dyn Sole
}
}
-impl TypeName for Box<dyn Sole>
-{
- fn type_name(&self) -> &'static str
- {
- self.as_ref().type_name()
- }
-}
-
/// Holds a reference to a globally shared singleton value.
#[derive(Debug)]
pub struct Single<'world, SoleT: Sole>
@@ -88,11 +79,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>(
@@ -115,7 +105,7 @@ where
}
}
-impl<'world, SoleT> Deref for Single<'world, SoleT>
+impl<SoleT> Deref for Single<'_, SoleT>
where
SoleT: Sole,
{
@@ -127,7 +117,7 @@ where
}
}
-impl<'world, SoleT> DerefMut for Single<'world, SoleT>
+impl<SoleT> DerefMut for Single<'_, SoleT>
where
SoleT: Sole,
{
@@ -174,7 +164,7 @@ where
_pd: PhantomData<&'weak_ref SoleT>,
}
-impl<'weak_ref, SoleT> SingleRef<'weak_ref, SoleT>
+impl<SoleT> SingleRef<'_, SoleT>
where
SoleT: Sole,
{
diff --git a/ecs/src/system.rs b/ecs/src/system.rs
index 046d25b..603c015 100644
--- a/ecs/src/system.rs
+++ b/ecs/src/system.rs
@@ -1,19 +1,12 @@
-use std::any::{type_name, Any};
+use std::any::Any;
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,
- FromOptional as FromOptionalComponent,
- FromOptionalMut as FromOptionalMutComponent,
-};
-use crate::lock::{ReadGuard, WriteGuard};
-use crate::tuple::{ReduceElement as TupleReduceElement, With as TupleWith};
+use crate::component::{Component, HandleMut as ComponentHandleMut};
+use crate::tuple::{ReduceElement as TupleReduceElement, Tuple};
use crate::World;
pub mod stateful;
@@ -33,7 +26,7 @@ pub trait System<'world, Impl>: 'static
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>;
+ ) -> Option<ComponentHandleMut<LocalComponent>>;
fn set_local_component<LocalComponent: Component>(
&mut self,
@@ -47,8 +40,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;
@@ -92,7 +85,7 @@ macro_rules! impl_system {
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>
+ ) -> Option<ComponentHandleMut<LocalComponent>>
{
panic!("System does not have any local components");
}
@@ -121,7 +114,7 @@ pub trait Into<Impl>
pub struct TypeErased
{
- data: Box<dyn Any + RefUnwindSafe + UnwindSafe>,
+ data: Box<dyn Any>,
run: Box<TypeErasedRunFn>,
}
@@ -149,13 +142,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 +160,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 +169,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 ()
@@ -189,128 +179,8 @@ impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for ()
type Return = Accumulator;
}
-#[derive(Debug)]
-pub struct ComponentRefMut<'a, ComponentT: Component>
-{
- inner: WriteGuard<'a, Box<dyn Component>>,
- _ph: PhantomData<ComponentT>,
-}
-
-impl<'a, ComponentT: Component> ComponentRefMut<'a, ComponentT>
-{
- pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Component>>) -> Self
- {
- Self { inner, _ph: PhantomData }
- }
-}
-
-impl<'component, ComponentT: Component> FromOptionalMutComponent<'component>
- for ComponentRefMut<'component, ComponentT>
+#[derive(Debug, Component)]
+pub(crate) struct SystemComponent
{
- fn from_optional_mut_component(
- inner: Option<WriteGuard<'component, Box<dyn Component>>>,
- _world: &'component World,
- ) -> Self
- {
- Self {
- inner: inner.unwrap_or_else(|| {
- panic!(
- "Component {} was not found in entity",
- type_name::<ComponentT>()
- );
- }),
- _ph: PhantomData,
- }
- }
-}
-
-impl<'comp, ComponentT> FromOptionalMutComponent<'comp>
- for Option<ComponentRefMut<'comp, ComponentT>>
-where
- ComponentT: Component,
-{
- fn from_optional_mut_component(
- optional_component: Option<WriteGuard<'comp, Box<dyn Component>>>,
- _world: &'comp World,
- ) -> Self
- {
- optional_component.map(|component| ComponentRefMut::new(component))
- }
-}
-
-impl<'a, ComponentT: Component> Deref for ComponentRefMut<'a, ComponentT>
-{
- type Target = ComponentT;
-
- fn deref(&self) -> &Self::Target
- {
- self.inner.downcast_ref().unwrap()
- }
-}
-
-impl<'a, ComponentT: Component> DerefMut for ComponentRefMut<'a, ComponentT>
-{
- fn deref_mut(&mut self) -> &mut Self::Target
- {
- self.inner.downcast_mut().unwrap()
- }
-}
-
-#[derive(Debug)]
-pub struct ComponentRef<'a, ComponentT: Component>
-{
- inner: ReadGuard<'a, Box<dyn Component>>,
- _ph: PhantomData<ComponentT>,
-}
-
-impl<'a, ComponentT: Component> ComponentRef<'a, ComponentT>
-{
- pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Component>>) -> Self
- {
- Self { inner, _ph: PhantomData }
- }
-}
-
-impl<'component, ComponentT: Component> FromOptionalComponent<'component>
- for ComponentRef<'component, ComponentT>
-{
- fn from_optional_component(
- inner: Option<ReadGuard<'component, Box<dyn Component>>>,
- _world: &'component World,
- ) -> Self
- {
- Self {
- inner: inner.unwrap_or_else(|| {
- panic!(
- "Component {} was not found in entity",
- type_name::<ComponentT>()
- );
- }),
- _ph: PhantomData,
- }
- }
-}
-
-impl<'comp, ComponentT> FromOptionalComponent<'comp>
- for Option<ComponentRef<'comp, ComponentT>>
-where
- ComponentT: Component,
-{
- fn from_optional_component(
- optional_component: Option<ReadGuard<'comp, Box<dyn Component>>>,
- _world: &'comp World,
- ) -> Self
- {
- optional_component.map(|component| ComponentRef::new(component))
- }
-}
-
-impl<'a, ComponentT: Component> Deref for ComponentRef<'a, ComponentT>
-{
- type Target = ComponentT;
-
- fn deref(&self) -> &Self::Target
- {
- self.inner.downcast_ref().unwrap()
- }
+ pub(crate) system: TypeErased,
}
diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs
index 536e6ed..54f6979 100644
--- a/ecs/src/system/stateful.rs
+++ b/ecs/src/system/stateful.rs
@@ -1,13 +1,12 @@
use std::any::{type_name, Any, TypeId};
-use std::collections::HashMap;
use std::panic::{RefUnwindSafe, UnwindSafe};
+use hashbrown::HashMap;
use seq_macro::seq;
-use crate::component::Component;
+use crate::component::{Component, HandleMut as ComponentHandleMut};
use crate::lock::Lock;
use crate::system::{
- ComponentRefMut,
Into as IntoSystem,
Param,
ParamWithInputFilter,
@@ -15,10 +14,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;
@@ -27,7 +25,7 @@ use crate::World;
pub struct Stateful<Func>
{
func: Func,
- local_components: HashMap<Uid, Lock<Box<dyn Component>>>,
+ local_components: HashMap<Uid, Lock<Box<dyn Any>>>,
}
macro_rules! impl_system {
@@ -39,9 +37,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 +49,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;
+ }
}
)*
@@ -118,14 +109,14 @@ macro_rules! impl_system {
fn get_local_component_mut<LocalComponent: Component>(
&self,
- ) -> Option<ComponentRefMut<LocalComponent>>
+ ) -> Option<ComponentHandleMut<LocalComponent>>
{
let local_component = self.local_components
.get(&LocalComponent::id())?
.write_nonblock()
.expect("Failed to aquire read-write local component lock");
- Some(ComponentRefMut::new(local_component))
+ Some(ComponentHandleMut::new(local_component))
}
fn set_local_component<LocalComponent: Component>(
@@ -136,7 +127,10 @@ macro_rules! impl_system {
self.local_components
.insert(
LocalComponent::id(),
- Lock::new(Box::new(local_component))
+ Lock::new(
+ Box::new(local_component),
+ type_name::<LocalComponent>()
+ )
);
}
}
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/type_name.rs b/ecs/src/type_name.rs
deleted file mode 100644
index 54179be..0000000
--- a/ecs/src/type_name.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use std::any::type_name;
-
-pub trait TypeName
-{
- /// Returns the name of this type.
- fn type_name(&self) -> &'static str;
-}
-
-impl<Item> TypeName for Vec<Item>
-{
- fn type_name(&self) -> &'static str
- {
- type_name::<Self>()
- }
-}
diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs
index 0e5d88a..feed62c 100644
--- a/ecs/src/uid.rs
+++ b/ecs/src/uid.rs
@@ -1,21 +1,29 @@
+use std::fmt::{Debug, Display, Formatter};
use std::mem::transmute;
use std::sync::atomic::{AtomicU32, Ordering};
-static NEXT: AtomicU32 = AtomicU32::new(1);
+use crate::component::Component;
+use crate::util::{gen_mask_64, BitMask, NumberExt};
-// Bit 0 and 1 for the kind
-const KIND_BITS: u64 = 0x03;
+static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID);
+
+static WILDCARD_ID: u32 = 1;
+
+const ID_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(32..=63));
+const RELATION_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(6..=31));
+const KIND_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(0..=1));
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum Kind
{
+ Pair = 3,
Entity = 2,
Component = 1,
}
-/// Unique entity/component ID.
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
+/// A unique identifier.
+#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Uid
{
inner: u64,
@@ -23,21 +31,213 @@ pub struct Uid
impl Uid
{
+ /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are
+ /// reserved.
+ pub const FIRST_UNIQUE_ID: u32 = 5;
+
/// 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: ID_BITS.field_prep(u64::from(id)) | KIND_BITS.field_prep(kind as u64),
+ }
+ }
+ #[must_use]
+ pub fn wildcard() -> Self
+ {
Self {
- inner: (u64::from(id_part) << 32) | kind as u64,
+ inner: ID_BITS.field_prep(u64::from(WILDCARD_ID))
+ | KIND_BITS.field_prep(Kind::Component as u64),
}
}
+ /// Returns a new pair UID.
+ ///
+ /// # Panics
+ /// Will panic if either the given relation or target is a pair UID.
+ #[must_use]
+ pub fn new_pair(params: &PairParams) -> Self
+ {
+ assert_ne!(
+ params.relation.kind(),
+ Kind::Pair,
+ "Pair relation cannot be a pair"
+ );
+
+ assert_ne!(
+ params.target.kind(),
+ Kind::Pair,
+ "Pair target cannot be a pair"
+ );
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(params.target.id()))
+ | RELATION_BITS.field_prep(u64::from(params.relation.id()))
+ | KIND_BITS.field_prep(Kind::Pair as u64),
+ }
+ }
+
+ #[must_use]
+ pub fn id(&self) -> u32
+ {
+ let Ok(id) = u32::try_from(self.inner.field_get(ID_BITS)) else {
+ unreachable!("Uid id does not fit in u32");
+ };
+
+ id
+ }
+
#[must_use]
pub fn kind(&self) -> Kind
{
+ let Ok(kind) = u8::try_from(self.inner.field_get(KIND_BITS)) else {
+ unreachable!("Uid kind does not fit in u8");
+ };
+
// 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::<u8, Kind>(kind) }
+ }
+
+ /// If this `Uid` is a pair, returns the relation as a component `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn relation_component(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.relation()))
+ | KIND_BITS.field_prep(Kind::Component as u64),
+ }
+ }
+
+ #[must_use]
+ pub fn has_same_relation_as(&self, other: Self) -> bool
+ {
+ self.relation() == other.relation()
+ }
+
+ /// If this `Uid` is a pair, returns the relation as a entity `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn relation_entity(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.relation()))
+ | KIND_BITS.field_prep(Kind::Entity as u64),
+ }
+ }
+
+ /// If this `Uid` is a pair, returns the target as a component `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn target_component(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.id()))
+ | KIND_BITS.field_prep(Kind::Component as u64),
+ }
+ }
+
+ /// If this `Uid` is a pair, returns the target as a entity `Uid`.
+ ///
+ /// # Panics
+ /// Will panic if this `Uid` is not a pair.
+ #[must_use]
+ pub fn target_entity(&self) -> Self
+ {
+ assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair");
+
+ Self {
+ inner: ID_BITS.field_prep(u64::from(self.id()))
+ | KIND_BITS.field_prep(Kind::Entity as u64),
+ }
+ }
+
+ fn relation(self) -> u32
+ {
+ let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else {
+ unreachable!("Uid relation does not fit in u32");
+ };
+
+ relation
+ }
+}
+
+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()
+ }
+}
+
+impl Display for Uid
+{
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
+ {
+ if self.kind() == Kind::Pair {
+ return write!(
+ formatter,
+ "({}, {})",
+ self.relation(),
+ self.target_component()
+ );
+ }
+
+ if *self == Uid::wildcard() {
+ return write!(formatter, "*");
+ }
+
+ write!(formatter, "{}", self.id())
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct PairParams
+{
+ pub relation: Uid,
+ pub target: Uid,
+}
+
+pub trait With: 'static
+{
+ fn uid() -> Uid;
+}
+
+impl<ComponentT: Component> With for ComponentT
+{
+ fn uid() -> Uid
+ {
+ Self::id()
+ }
+}
+
+#[derive(Debug)]
+pub enum Wildcard {}
+
+impl With for Wildcard
+{
+ fn uid() -> Uid
+ {
+ Uid::wildcard()
}
}
diff --git a/ecs/src/util.rs b/ecs/src/util.rs
index 4480fc8..9ab4dc6 100644
--- a/ecs/src/util.rs
+++ b/ecs/src/util.rs
@@ -1,3 +1,186 @@
+use std::hash::Hash;
+use std::mem::transmute;
+use std::ops::{BitAnd, Deref};
+
+use hashbrown::HashMap;
+
+pub(crate) mod array_vec;
+
+pub trait VecExt<Item>
+{
+ fn insert_at_partition_point_by_key<Key>(
+ &mut self,
+ item: Item,
+ func: impl FnMut(&Item) -> Key,
+ ) where
+ Key: Ord;
+}
+
+impl<Item> VecExt<Item> for Vec<Item>
+{
+ fn insert_at_partition_point_by_key<Key>(
+ &mut self,
+ item: Item,
+ mut func: impl FnMut(&Item) -> Key,
+ ) where
+ Key: Ord,
+ {
+ let key = func(&item);
+
+ let insert_index = self.partition_point(|other_item| func(other_item) <= key);
+
+ self.insert(insert_index, item);
+ }
+}
+
+pub trait StreamingIterator
+{
+ type Item<'a>
+ where
+ Self: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>;
+
+ fn streaming_map<NewItem, Func>(self, func: Func) -> StreamingMap<Self, Func>
+ where
+ Self: Sized,
+ Func: FnMut(Self::Item<'_>) -> NewItem,
+ {
+ StreamingMap { iter: self, func }
+ }
+
+ fn streaming_find<'this, Predicate>(
+ &'this mut self,
+ mut predicate: Predicate,
+ ) -> Option<Self::Item<'this>>
+ where
+ Self: Sized,
+ Predicate: FnMut(&Self::Item<'this>) -> bool,
+ {
+ while let Some(item) = unsafe {
+ transmute::<Option<Self::Item<'_>>, Option<Self::Item<'_>>>(
+ self.streaming_next(),
+ )
+ } {
+ if predicate(&item) {
+ return Some(item);
+ }
+ }
+
+ None
+ }
+}
+
+pub struct StreamingMap<Iter, Func>
+{
+ iter: Iter,
+ func: Func,
+}
+
+impl<Iter, Func, Item> StreamingIterator for StreamingMap<Iter, Func>
+where
+ Iter: StreamingIterator,
+ Func: FnMut(Iter::Item<'_>) -> Item,
+{
+ type Item<'a>
+ = Item
+ where
+ Iter: 'a,
+ Func: 'a;
+
+ fn streaming_next(&mut self) -> Option<Self::Item<'_>>
+ {
+ Some((self.func)(self.iter.streaming_next()?))
+ }
+}
+
+#[derive(Debug)]
+pub enum BorrowedOrOwned<'a, Value>
+{
+ Borrowned(&'a Value),
+ Owned(Value),
+}
+
+impl<Value> Deref for BorrowedOrOwned<'_, Value>
+{
+ type Target = Value;
+
+ fn deref(&self) -> &Self::Target
+ {
+ match self {
+ Self::Borrowned(value) => value,
+ Self::Owned(value) => value,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Either<A, B>
+{
+ A(A),
+ B(B),
+}
+
+impl<A, B> Iterator for Either<A, B>
+where
+ A: Iterator,
+ B: Iterator<Item = A::Item>,
+{
+ type Item = A::Item;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ match self {
+ Self::A(a) => a.next(),
+ Self::B(b) => b.next(),
+ }
+ }
+}
+
+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 +229,105 @@ 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 }
+ }
+
+ #[must_use]
+ 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
+ {
+ debug_assert!(field_value < 1 << self.mask.count_ones());
+
+ ((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`].
+ #[must_use]
+ 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);
+ }
+
+ #[test]
+ #[should_panic]
+ fn bitmask_field_prep_too_large_value_panics()
+ {
+ let _ = BitMask::new(0b001110).field_prep(9);
+ }
+}
diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs
new file mode 100644
index 0000000..a37b1f9
--- /dev/null
+++ b/ecs/src/util/array_vec.rs
@@ -0,0 +1,131 @@
+use std::mem::MaybeUninit;
+use std::ops::{Deref, DerefMut};
+
+#[derive(Debug)]
+pub struct ArrayVec<Item, const CAPACITY: usize>
+{
+ items: [MaybeUninit<Item>; CAPACITY],
+ len: usize,
+}
+
+impl<Item, const CAPACITY: usize> ArrayVec<Item, CAPACITY>
+{
+ #[inline]
+ #[must_use]
+ pub fn len(&self) -> usize
+ {
+ self.len
+ }
+
+ #[inline]
+ #[must_use]
+ pub fn is_empty(&self) -> bool
+ {
+ self.len == 0
+ }
+
+ pub fn push(&mut self, item: Item)
+ {
+ assert!(self.len < CAPACITY);
+
+ self.items[self.len].write(item);
+
+ self.len += 1;
+ }
+
+ pub fn insert(&mut self, index: usize, item: Item)
+ {
+ assert!(index <= self.len);
+ assert!(self.len < CAPACITY);
+
+ if index == self.len {
+ self.push(item);
+ return;
+ }
+
+ unsafe {
+ std::ptr::copy(
+ &self.items[index],
+ &mut self.items[index + 1],
+ self.len - index,
+ );
+ }
+
+ self.items[index].write(item);
+
+ self.len += 1;
+ }
+}
+
+impl<Item, const CAPACITY: usize> Extend<Item> for ArrayVec<Item, CAPACITY>
+{
+ fn extend<IntoIter: IntoIterator<Item = Item>>(&mut self, iter: IntoIter)
+ {
+ for item in iter {
+ self.push(item);
+ }
+ }
+}
+
+impl<Item, const CAPACITY: usize> AsRef<[Item]> for ArrayVec<Item, CAPACITY>
+{
+ fn as_ref(&self) -> &[Item]
+ {
+ let ptr = &raw const self.items[..self.len];
+
+ unsafe { &*(ptr as *const [Item]) }
+ }
+}
+
+impl<Item, const CAPACITY: usize> AsMut<[Item]> for ArrayVec<Item, CAPACITY>
+{
+ fn as_mut(&mut self) -> &mut [Item]
+ {
+ let ptr = &raw mut self.items[..self.len];
+
+ unsafe { &mut *(ptr as *mut [Item]) }
+ }
+}
+
+impl<Item, const CAPACITY: usize> Deref for ArrayVec<Item, CAPACITY>
+{
+ type Target = [Item];
+
+ fn deref(&self) -> &Self::Target
+ {
+ self.as_ref()
+ }
+}
+
+impl<Item, const CAPACITY: usize> DerefMut for ArrayVec<Item, CAPACITY>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target
+ {
+ self.as_mut()
+ }
+}
+
+impl<Item, const CAPACITY: usize> Default for ArrayVec<Item, CAPACITY>
+{
+ fn default() -> Self
+ {
+ Self {
+ items: [const { MaybeUninit::uninit() }; CAPACITY],
+ len: 0,
+ }
+ }
+}
+
+impl<Item, const CAPACITY: usize> Drop for ArrayVec<Item, CAPACITY>
+{
+ fn drop(&mut self)
+ {
+ for item in &mut self.items[..self.len] {
+ // SAFETY: The items from index 0 to the length index will always be
+ // initialized and satisfy all the invariants of the Item type.
+ unsafe {
+ item.assume_init_drop();
+ }
+ }
+ }
+}
diff --git a/ecs/tests/query.rs b/ecs/tests/query.rs
new file mode 100644
index 0000000..062fd5a
--- /dev/null
+++ b/ecs/tests/query.rs
@@ -0,0 +1,322 @@
+use ecs::component::Component;
+use ecs::query::term::Without;
+use ecs::query::{
+ TermWithFieldTuple as QueryTermWithFieldTuple,
+ TermWithoutFieldTuple as QueryTermWithoutFieldTuple,
+};
+use ecs::uid::Uid;
+use ecs::{Component, Query, World};
+use parking_lot::{Mutex, Once};
+
+pub static SETUP: Once = Once::new();
+
+pub static TEST_LOCK: Mutex<()> = Mutex::new(());
+
+#[derive(Component)]
+struct A;
+
+#[derive(Component)]
+struct B;
+
+#[derive(Component)]
+struct C;
+
+#[derive(Component)]
+struct D;
+
+#[derive(Component)]
+struct E;
+
+#[derive(Component)]
+struct F;
+
+#[derive(Component)]
+struct G;
+
+fn setup()
+{
+ SETUP.call_once_force(|_| {
+ assert_eq!(A::id().id(), Uid::FIRST_UNIQUE_ID);
+ assert_eq!(B::id().id(), Uid::FIRST_UNIQUE_ID + 1);
+ assert_eq!(C::id().id(), Uid::FIRST_UNIQUE_ID + 2);
+ assert_eq!(D::id().id(), Uid::FIRST_UNIQUE_ID + 3);
+ assert_eq!(E::id().id(), Uid::FIRST_UNIQUE_ID + 4);
+ assert_eq!(F::id().id(), Uid::FIRST_UNIQUE_ID + 5);
+ assert_eq!(G::id().id(), Uid::FIRST_UNIQUE_ID + 6);
+ });
+}
+
+fn assert_query_finds_ents<QueryFieldTerms, QueryFieldlessTerms>(
+ query: Query<'_, QueryFieldTerms, QueryFieldlessTerms>,
+ mut expected_ent_ids: Vec<Uid>,
+) where
+ QueryFieldTerms: QueryTermWithFieldTuple,
+ QueryFieldlessTerms: QueryTermWithoutFieldTuple,
+{
+ assert!(
+ query.iter_with_euids().all(|(ent_id, _)| {
+ let Some(index) = expected_ent_ids
+ .iter()
+ .position(|expected_id| *expected_id == ent_id)
+ else {
+ return false;
+ };
+
+ expected_ent_ids.remove(index);
+
+ true
+ }),
+ "Unexpected entity was found. Expected entities left: {expected_ent_ids:?}"
+ );
+
+ assert_eq!(
+ expected_ent_ids.len(),
+ 0,
+ concat!(
+ "Not all entities expected to be found was found. ",
+ "Expected entities left: {:?}"
+ ),
+ expected_ent_ids
+ );
+}
+
+#[test]
+fn query_archetype_exists_with_edges_to_next_archetypes()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C));
+ let ent_2_id = world.create_entity((A, B, C, D, E));
+ let ent_3_id = world.create_entity((A, B, C, E));
+ let ent_4_id = world.create_entity((A, B, C, G, F));
+
+ assert_query_finds_ents(
+ world.query::<(&A, &B, &C), ()>(),
+ vec![ent_1_id, ent_2_id, ent_3_id, ent_4_id],
+ );
+}
+
+#[test]
+fn query_archetype_exists_with_2_comps_diff_to_next_archetype()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C, D, F));
+
+ let ent_2_id = world.create_entity((A, B, F));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_2_comps_diff_to_next_archetype_rev()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, F));
+
+ let ent_2_id = world.create_entity((A, B, C, D, F));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_3_comps_diff_to_next_archetype()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C, D, E, F));
+
+ let ent_2_id = world.create_entity((A, B, F));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_3_comps_diff_to_next_archetype_rev()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, F));
+
+ let ent_2_id = world.create_entity((A, B, C, D, E, F));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_4_comps_diff_to_next_archetype()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C, D, E, F, G));
+
+ let ent_2_id = world.create_entity((A, B, G));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_4_comps_diff_to_next_archetype_rev()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, G));
+
+ let ent_2_id = world.create_entity((A, B, C, D, E, F, G));
+
+ assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]);
+}
+
+#[test]
+fn query_archetype_exists_with_4_comps_diff_to_next_archetype_and_opt_comp()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C, D, E, F, G));
+
+ let ent_2_id = world.create_entity((A, B, G));
+
+ assert_query_finds_ents(
+ world.query::<(&A, Option<&E>, &G), ()>(),
+ vec![ent_1_id, ent_2_id],
+ );
+}
+
+#[test]
+fn query_archetype_nonexistant()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ world.create_entity((A, B, C));
+
+ let ent_2_id = world.create_entity((A, B, C, D, E));
+ let ent_3_id = world.create_entity((A, B, C, E));
+
+ world.create_entity((A, B, C, G, F));
+
+ assert_query_finds_ents(world.query::<(&A, &E), ()>(), vec![ent_2_id, ent_3_id]);
+}
+
+#[test]
+fn query_archetype_nonexistant_and_opt_comp()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ world.create_entity((A, B, C));
+ let ent_2_id = world.create_entity((A, B, C, D, E));
+ let ent_3_id = world.create_entity((A, B, C, E));
+ world.create_entity((A, B, C, G, F));
+
+ assert_query_finds_ents(
+ world.query::<(&A, &E, Option<&D>), ()>(),
+ vec![ent_2_id, ent_3_id],
+ );
+}
+
+#[test]
+fn query_without_comp_and_archetype_exists()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ let ent_1_id = world.create_entity((A, B, C));
+
+ world.create_entity((A, B, C, E));
+ world.create_entity((A, B, C, F, E));
+
+ let ent_2_id = world.create_entity((A, B, C, G));
+ let ent_3_id = world.create_entity((A, B, C, G, F));
+
+ assert_query_finds_ents(
+ world.query::<(&A, &B, &C), (Without<E>,)>(),
+ vec![ent_1_id, ent_2_id, ent_3_id],
+ );
+}
+
+#[test]
+fn query_without_required_comp_and_archetype_exists()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ world.create_entity((A, B, C));
+
+ world.create_entity((A, B, C, E));
+ world.create_entity((A, B, C, F, E));
+
+ world.create_entity((A, B, C, G));
+ world.create_entity((A, B, C, G, F));
+
+ assert_query_finds_ents(world.query::<(&A, &B), (Without<B>,)>(), vec![]);
+}
+
+#[test]
+fn query_without_comp_and_archetype_nonexistant()
+{
+ setup();
+
+ let _test_lock = TEST_LOCK.lock();
+
+ let mut world = World::new();
+
+ world.create_entity((A, B, C));
+
+ let ent_1_id = world.create_entity((A, B, C, E));
+
+ world.create_entity((A, B, C, F, E));
+
+ let ent_2_id = world.create_entity((A, B, C, G, E));
+ world.create_entity((A, B, C, G, F, E));
+
+ assert_query_finds_ents(
+ world.query::<(&A, &E), (Without<F>,)>(),
+ vec![ent_1_id, ent_2_id],
+ );
+}
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index b3868ac..a62f458 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -3,20 +3,19 @@ name = "engine"
version = "0.1.0"
edition = "2021"
-[features]
-debug = ["dep:tracing"]
-
[dependencies]
glfw = { path = "../glfw", features = ["opengl"] }
thiserror = "1.0.49"
gl = "0.14.0"
bitflags = "2.4.0"
-tracing = { version = "0.1.39", optional = true }
+tracing = "0.1.39"
seq-macro = "0.3.5"
paste = "1.0.14"
ecs = { path = "../ecs" }
+util-macros = { path = "../util-macros" }
-[dependencies.image]
+[dependencies.image_rs]
version = "0.24.7"
default-features = false
features = ["png", "jpeg"]
+package = "image"
diff --git a/engine/src/asset.rs b/engine/src/asset.rs
new file mode 100644
index 0000000..db4d23c
--- /dev/null
+++ b/engine/src/asset.rs
@@ -0,0 +1,777 @@
+use std::any::{type_name, Any};
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::Infallible;
+use std::ffi::{OsStr, OsString};
+use std::fmt::{Debug, Display};
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+use std::sync::mpsc::{
+ channel as mpsc_channel,
+ Receiver as MpscReceiver,
+ Sender as MpscSender,
+};
+use std::sync::Arc;
+
+use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE;
+use ecs::sole::Single;
+use ecs::Sole;
+
+use crate::work_queue::{Work, WorkQueue};
+
+/// Asset label.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Label<'a>
+{
+ pub path: Cow<'a, Path>,
+ pub name: Option<Cow<'a, str>>,
+}
+
+impl Label<'_>
+{
+ pub fn to_owned(&self) -> LabelOwned
+ {
+ LabelOwned {
+ path: self.path.to_path_buf(),
+ name: self.name.as_ref().map(|name| name.to_string()),
+ }
+ }
+}
+
+impl<'a> From<&'a Path> for Label<'a>
+{
+ fn from(path: &'a Path) -> Self
+ {
+ Self { path: path.into(), name: None }
+ }
+}
+
+impl From<PathBuf> for Label<'_>
+{
+ fn from(path: PathBuf) -> Self
+ {
+ Self { path: path.into(), name: None }
+ }
+}
+
+impl<'a> From<&'a LabelOwned> for Label<'a>
+{
+ fn from(label: &'a LabelOwned) -> Self
+ {
+ Self {
+ path: (&label.path).into(),
+ name: label.name.as_ref().map(|name| Cow::Borrowed(name.as_str())),
+ }
+ }
+}
+
+impl Display for Label<'_>
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}", self.path.display())?;
+
+ if let Some(name) = &self.name {
+ formatter.write_str("::")?;
+ formatter.write_str(&name)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct LabelOwned
+{
+ pub path: PathBuf,
+ pub name: Option<String>,
+}
+
+impl LabelOwned
+{
+ pub fn to_label(&self) -> Label<'_>
+ {
+ Label {
+ path: (&self.path).into(),
+ name: self.name.as_ref().map(|name| Cow::Borrowed(name.as_str())),
+ }
+ }
+}
+
+impl Display for LabelOwned
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}", self.path.display())?;
+
+ if let Some(name) = &self.name {
+ formatter.write_str("::")?;
+ formatter.write_str(&name)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Sole)]
+pub struct Assets
+{
+ assets: Vec<StoredAsset>,
+ asset_lookup: RefCell<HashMap<LabelHash, LookupEntry>>,
+ importers: Vec<WrappedImporterFn>,
+ importer_lookup: HashMap<OsString, usize>,
+ import_work_queue: WorkQueue<ImportWorkUserData>,
+ import_work_msg_receiver: MpscReceiver<ImportWorkMessage>,
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+}
+
+impl Assets
+{
+ pub fn with_capacity(capacity: usize) -> Self
+ {
+ let (import_work_msg_sender, import_work_msg_receiver) =
+ mpsc_channel::<ImportWorkMessage>();
+
+ Self {
+ assets: Vec::with_capacity(capacity),
+ asset_lookup: RefCell::new(HashMap::with_capacity(capacity)),
+ importers: Vec::new(),
+ importer_lookup: HashMap::new(),
+ import_work_queue: WorkQueue::new(),
+ import_work_msg_receiver,
+ import_work_msg_sender,
+ }
+ }
+
+ pub fn set_importer<'file_ext, AssetSettings, Err>(
+ &mut self,
+ file_extensions: impl IntoIterator<Item: Into<Cow<'file_ext, str>>>,
+ func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>,
+ ) where
+ AssetSettings: 'static,
+ Err: std::error::Error + 'static,
+ {
+ self.importers.push(WrappedImporterFn::new(func));
+
+ let importer_index = self.importers.len() - 1;
+
+ self.importer_lookup
+ .extend(file_extensions.into_iter().map(|file_ext| {
+ let file_ext: Cow<str> = file_ext.into();
+
+ (file_ext.into_owned().into(), importer_index)
+ }));
+ }
+
+ #[tracing::instrument(skip_all, fields(asset_type=type_name::<Asset>()))]
+ pub fn get<'this, 'handle, Asset: 'static + Send + Sync>(
+ &'this self,
+ handle: &'handle Handle<Asset>,
+ ) -> Option<&'handle Asset>
+ where
+ 'this: 'handle,
+ {
+ let LookupEntry::Occupied(asset_index) =
+ *self.asset_lookup.borrow().get(&handle.id.label_hash)?
+ else {
+ return None;
+ };
+
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ let Some(asset) = stored_asset.strong.downcast_ref::<Asset>() else {
+ tracing::error!("Wrong asset type");
+ return None;
+ };
+
+ Some(asset)
+ }
+
+ #[tracing::instrument(skip(self))]
+ pub fn load<'i, Asset: 'static + Send + Sync>(
+ &self,
+ label: impl Into<Label<'i>> + Debug,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ let mut asset_lookup = self.asset_lookup.borrow_mut();
+
+ if Self::is_pending(&asset_lookup, &label) {
+ return Handle::new(label_hash);
+ }
+
+ let Some(lookup_entry) = asset_lookup.get(&label_hash) else {
+ self.add_import_work::<Infallible>(
+ &label,
+ label_hash,
+ None,
+ &mut asset_lookup,
+ );
+
+ return Handle::new(label_hash);
+ };
+
+ match *lookup_entry {
+ LookupEntry::Occupied(asset_index) => {
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ if stored_asset.strong.downcast_ref::<Asset>().is_none() {
+ tracing::error!("Wrong asset type {}", type_name::<Asset>());
+ }
+ }
+ LookupEntry::Pending => {}
+ }
+
+ Handle::new(label_hash)
+ }
+
+ #[tracing::instrument(skip(self))]
+ pub fn load_with_settings<'i, Asset, AssetSettings>(
+ &self,
+ label: impl Into<Label<'i>> + Debug,
+ asset_settings: AssetSettings,
+ ) -> Handle<Asset>
+ where
+ Asset: Send + Sync + 'static,
+ AssetSettings: Send + Sync + Debug + 'static,
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ let mut asset_lookup = self.asset_lookup.borrow_mut();
+
+ if Self::is_pending(&asset_lookup, &label) {
+ return Handle::new(label_hash);
+ }
+
+ let Some(lookup_entry) = asset_lookup.get(&label_hash) else {
+ self.add_import_work::<AssetSettings>(
+ &label,
+ label_hash,
+ Some(asset_settings),
+ &mut asset_lookup,
+ );
+
+ return Handle::new(label_hash);
+ };
+
+ match *lookup_entry {
+ LookupEntry::Occupied(asset_index) => {
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ if stored_asset.strong.downcast_ref::<Asset>().is_none() {
+ tracing::error!(
+ "Wrong asset type {} for asset",
+ type_name::<Asset>()
+ );
+ }
+ }
+ LookupEntry::Pending => {}
+ }
+
+ Handle::new(label_hash)
+ }
+
+ pub fn store_with_name<'name, Asset: 'static + Send + Sync>(
+ &mut self,
+ name: impl Into<Cow<'name, str>>,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ self.store_with_label(
+ Label {
+ path: Path::new("").into(),
+ name: Some(name.into()),
+ },
+ asset,
+ )
+ }
+
+ #[tracing::instrument(skip(self, asset), fields(asset_type=type_name::<Asset>()))]
+ pub fn store_with_label<'i, Asset: 'static + Send + Sync>(
+ &mut self,
+ label: impl Into<Label<'i>> + Debug,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ if matches!(
+ self.asset_lookup.get_mut().get(&label_hash),
+ Some(LookupEntry::Occupied(_))
+ ) {
+ tracing::error!("Asset already exists");
+
+ return Handle::new(label_hash);
+ }
+
+ tracing::debug!("Storing asset");
+
+ self.assets.push(StoredAsset::new(asset));
+
+ let index = self.assets.len() - 1;
+
+ self.asset_lookup
+ .get_mut()
+ .insert(label_hash, LookupEntry::Occupied(index));
+
+ if label.name.is_some() {
+ let parent_asset_label_hash =
+ LabelHash::new(&Label { path: label.path, name: None });
+
+ if matches!(
+ self.asset_lookup.get_mut().get(&parent_asset_label_hash),
+ Some(LookupEntry::Pending)
+ ) {
+ self.asset_lookup.get_mut().remove(&parent_asset_label_hash);
+ } else if self
+ .asset_lookup
+ .get_mut()
+ .get(&parent_asset_label_hash)
+ .is_none()
+ {
+ self.assets
+ .push(StoredAsset::new::<Option<Infallible>>(None));
+
+ self.asset_lookup.get_mut().insert(
+ parent_asset_label_hash,
+ LookupEntry::Occupied(self.assets.len() - 1),
+ );
+ }
+ }
+
+ Handle::new(label_hash)
+ }
+
+ fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool
+ {
+ if label.name.is_some() {
+ if let Some(LookupEntry::Pending) =
+ asset_lookup.get(&LabelHash::new(&Label {
+ path: label.path.as_ref().into(),
+ name: None,
+ }))
+ {
+ return true;
+ }
+ }
+
+ if let Some(LookupEntry::Pending) = asset_lookup.get(&LabelHash::new(label)) {
+ return true;
+ };
+
+ false
+ }
+
+ fn add_import_work<AssetSettings>(
+ &self,
+ label: &Label<'_>,
+ label_hash: LabelHash,
+ asset_settings: Option<AssetSettings>,
+ asset_lookup: &mut HashMap<LabelHash, LookupEntry>,
+ ) where
+ AssetSettings: Any + Send + Sync,
+ {
+ let Some(file_ext) = label.path.extension() else {
+ tracing::error!("Asset file is missing a file extension");
+ return;
+ };
+
+ let Some(importer) = self.get_importer(file_ext) else {
+ tracing::error!(
+ "No importer exists for asset file extension {}",
+ file_ext.to_string_lossy()
+ );
+ return;
+ };
+
+ self.import_work_queue.add_work(Work {
+ func: |ImportWorkUserData {
+ import_work_msg_sender,
+ asset_path,
+ asset_settings,
+ importer,
+ }| {
+ if let Err(err) = importer.call(
+ import_work_msg_sender,
+ asset_path.as_path(),
+ asset_settings.as_deref(),
+ ) {
+ tracing::error!(
+ "Failed to load asset {}: {err}",
+ asset_path.display()
+ );
+ }
+ },
+ user_data: ImportWorkUserData {
+ import_work_msg_sender: self.import_work_msg_sender.clone(),
+ asset_path: label.path.to_path_buf(),
+ asset_settings: asset_settings.map(|asset_settings| {
+ Box::new(asset_settings) as Box<dyn Any + Send + Sync>
+ }),
+ importer: importer.clone(),
+ },
+ });
+
+ asset_lookup.insert(label_hash, LookupEntry::Pending);
+
+ if label.name.is_some() {
+ asset_lookup.insert(
+ LabelHash::new(&Label {
+ path: label.path.as_ref().into(),
+ name: None,
+ }),
+ LookupEntry::Pending,
+ );
+ }
+ }
+
+ fn get_importer(&self, file_ext: &OsStr) -> Option<&WrappedImporterFn>
+ {
+ let index = *self.importer_lookup.get(file_ext)?;
+
+ Some(self.importers.get(index).expect("Not possible"))
+ }
+}
+
+impl Default for Assets
+{
+ fn default() -> Self
+ {
+ Self::with_capacity(0)
+ }
+}
+
+pub struct Submitter<'path>
+{
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &'path Path,
+}
+
+impl Submitter<'_>
+{
+ pub fn submit_load_other<'label, Asset: Send + Sync + 'static>(
+ &self,
+ label: impl Into<Label<'label>>,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load {
+ do_load: |assets, label, _asset_settings| {
+ let _ = assets.load::<Asset>(label);
+ },
+ label: label.to_owned(),
+ asset_settings: None,
+ });
+
+ Handle::new(LabelHash::new(&label))
+ }
+
+ pub fn submit_load_other_with_settings<'label, Asset, AssetSettings>(
+ &self,
+ label: impl Into<Label<'label>>,
+ asset_settings: AssetSettings,
+ ) -> Handle<Asset>
+ where
+ Asset: Send + Sync + 'static,
+ AssetSettings: Send + Sync + Debug + 'static,
+ {
+ let label = label.into();
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load {
+ do_load: |assets, label, asset_settings| {
+ let asset_settings = *asset_settings
+ .expect("Not possible")
+ .downcast::<AssetSettings>()
+ .expect("Not possible");
+
+ let _ = assets
+ .load_with_settings::<Asset, AssetSettings>(label, asset_settings);
+ },
+ label: label.to_owned(),
+ asset_settings: Some(Box::new(asset_settings)),
+ });
+
+ Handle::new(LabelHash::new(&label))
+ }
+
+ pub fn submit_store<Asset: Send + Sync + 'static>(
+ &self,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = LabelOwned {
+ path: self.asset_path.into(),
+ name: None,
+ };
+
+ let label_hash = LabelHash::new(&label.to_label());
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store {
+ do_store: |assets, label, boxed_asset| {
+ let Ok(asset) = boxed_asset.downcast::<Asset>() else {
+ unreachable!();
+ };
+
+ assets.store_with_label::<Asset>(&label, *asset);
+ },
+ label,
+ asset: Box::new(asset),
+ });
+
+ Handle::new(label_hash)
+ }
+
+ pub fn submit_store_named<Asset: Send + Sync + 'static>(
+ &self,
+ name: impl AsRef<str>,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = LabelOwned {
+ path: self.asset_path.into(),
+ name: Some(name.as_ref().into()),
+ };
+
+ let label_hash = LabelHash::new(&label.to_label());
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store {
+ do_store: |assets, label, boxed_asset| {
+ let Ok(asset) = boxed_asset.downcast::<Asset>() else {
+ unreachable!();
+ };
+
+ assets.store_with_label::<Asset>(&label, *asset);
+ },
+ label,
+ asset: Box::new(asset),
+ });
+
+ Handle::new(label_hash)
+ }
+}
+
+/// Asset handle.
+#[derive(Debug)]
+pub struct Handle<Asset: 'static>
+{
+ id: Id,
+ _pd: PhantomData<Asset>,
+}
+
+impl<Asset: 'static> Handle<Asset>
+{
+ pub fn id(&self) -> Id
+ {
+ self.id
+ }
+
+ fn new(label_hash: LabelHash) -> Self
+ {
+ Self {
+ id: Id { label_hash },
+ _pd: PhantomData,
+ }
+ }
+}
+
+impl<Asset: 'static> Clone for Handle<Asset>
+{
+ fn clone(&self) -> Self
+ {
+ Self { id: self.id, _pd: PhantomData }
+ }
+}
+
+/// Asset ID.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Id
+{
+ label_hash: LabelHash,
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ImporterError
+{
+ #[error("Settings has a incorrect type")]
+ IncorrectAssetSettingsType(PathBuf),
+
+ #[error(transparent)]
+ Other(Box<dyn std::error::Error>),
+}
+
+#[derive(Debug, Clone)]
+struct WrappedImporterFn
+{
+ wrapper_func: fn(
+ MpscSender<ImportWorkMessage>,
+ &Path,
+ Option<&(dyn Any + Send + Sync)>,
+ ) -> Result<(), ImporterError>,
+}
+
+impl WrappedImporterFn
+{
+ fn new<InnerFunc, AssetSettings, Err>(inner_func_param: InnerFunc) -> Self
+ where
+ InnerFunc:
+ Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>,
+ AssetSettings: 'static,
+ Err: std::error::Error + 'static,
+ {
+ assert_eq!(size_of::<InnerFunc>(), 0);
+
+ let wrapper_func =
+ |import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &Path,
+ asset_settings: Option<&(dyn Any + Send + Sync)>| {
+ let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() };
+
+ let asset_settings = asset_settings
+ .map(|asset_settings| {
+ asset_settings
+ .downcast_ref::<AssetSettings>()
+ .ok_or_else(|| {
+ ImporterError::IncorrectAssetSettingsType(
+ asset_path.to_path_buf(),
+ )
+ })
+ })
+ .transpose()?;
+
+ inner_func(
+ &mut Submitter { import_work_msg_sender, asset_path },
+ asset_path,
+ asset_settings,
+ )
+ .map_err(|err| ImporterError::Other(Box::new(err)))?;
+
+ Ok(())
+ };
+
+ std::mem::forget(inner_func_param);
+
+ Self { wrapper_func }
+ }
+
+ fn call(
+ &self,
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &Path,
+ asset_settings: Option<&(dyn Any + Send + Sync)>,
+ ) -> Result<(), ImporterError>
+ {
+ (self.wrapper_func)(import_work_msg_sender, asset_path, asset_settings)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct LabelHash(u64);
+
+impl LabelHash
+{
+ fn new(label: &Label<'_>) -> Self
+ {
+ let mut hasher = DefaultHasher::new();
+
+ label.hash(&mut hasher);
+
+ Self(hasher.finish())
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct Extension
+{
+ pub assets: Assets,
+}
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ let _ = collector.add_sole(self.assets);
+
+ collector.add_system(*PRE_UPDATE_PHASE, add_received_assets);
+ }
+}
+
+fn add_received_assets(mut assets: Single<Assets>)
+{
+ while let Some(import_work_msg) = assets.import_work_msg_receiver.try_recv().ok() {
+ match import_work_msg {
+ ImportWorkMessage::Store { do_store, label, asset } => {
+ do_store(&mut assets, label, asset);
+ }
+ ImportWorkMessage::Load { do_load, label, asset_settings } => {
+ do_load(
+ &assets,
+ Label {
+ path: label.path.as_path().into(),
+ name: label.name.as_deref().map(|name| name.into()),
+ },
+ asset_settings,
+ );
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ImportWorkUserData
+{
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: PathBuf,
+ asset_settings: Option<Box<dyn Any + Send + Sync>>,
+ importer: WrappedImporterFn,
+}
+
+#[derive(Debug)]
+enum ImportWorkMessage
+{
+ Store
+ {
+ do_store: fn(&mut Assets, LabelOwned, Box<dyn Any + Send + Sync>),
+ label: LabelOwned,
+ asset: Box<dyn Any + Send + Sync>,
+ },
+
+ Load
+ {
+ do_load: fn(&Assets, Label<'_>, Option<Box<dyn Any + Send + Sync>>),
+ label: LabelOwned,
+ asset_settings: Option<Box<dyn Any + Send + Sync>>,
+ },
+}
+
+#[derive(Debug, Clone, Copy)]
+enum LookupEntry
+{
+ Occupied(usize),
+ Pending,
+}
+
+#[derive(Debug)]
+struct StoredAsset
+{
+ strong: Arc<dyn Any + Send + Sync>,
+}
+
+impl StoredAsset
+{
+ fn new<Asset: Any + Send + Sync>(asset: Asset) -> Self
+ {
+ let strong = Arc::new(asset);
+
+ Self { strong }
+ }
+}
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs
index b6ba7aa..d6eac62 100644
--- a/engine/src/camera/fly.rs
+++ b/engine/src/camera/fly.rs
@@ -1,15 +1,14 @@
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 glfw::window::{Key, KeyState};
use crate::camera::{Active as ActiveCamera, Camera};
use crate::delta_time::DeltaTime;
-use crate::event::Update as UpdateEvent;
-use crate::input::{Cursor, CursorFlags, Keys};
-use crate::transform::Position;
-use crate::util::builder;
+use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys};
+use crate::transform::WorldPosition;
+use crate::builder;
use crate::vector::{Vec2, Vec3};
builder! {
@@ -61,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)),
@@ -76,7 +75,7 @@ pub struct Options
}
fn update(
- camera_query: Query<(Camera, Position, Fly, ActiveCamera)>,
+ camera_query: Query<(&mut Camera, &mut WorldPosition, &mut Fly, &ActiveCamera)>,
keys: Single<Keys>,
cursor: Single<Cursor>,
cursor_flags: Single<CursorFlags>,
@@ -85,9 +84,8 @@ fn update(
options: Local<Options>,
)
{
- for (mut camera, mut camera_pos, mut fly_camera, _) in &camera_query {
+ for (mut camera, mut camera_world_pos, mut fly_camera, _) in &camera_query {
if cursor.has_moved && cursor_flags.is_first_move.flag {
- #[cfg(feature = "debug")]
tracing::debug!("First cursor move");
cursor_state.last_pos = cursor.position;
@@ -123,30 +121,31 @@ fn update(
camera.global_up = cam_right.cross(&direction).normalize();
- if matches!(keys.get_key_state(Key::W), KeyState::Pressed) {
- camera_pos.position +=
+ if keys.get_key_state(Key::W) == KeyState::Pressed {
+ camera_world_pos.position +=
direction * fly_camera.speed * delta_time.as_secs_f32();
}
- if matches!(keys.get_key_state(Key::S), KeyState::Pressed) {
- camera_pos.position -=
+ if keys.get_key_state(Key::S) == KeyState::Pressed {
+ camera_world_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();
+ camera_world_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 +=
+ camera_world_pos.position +=
cam_right * fly_camera.speed * delta_time.as_secs_f32();
}
- camera.target = camera_pos.position + direction;
+ camera.target = camera_world_pos.position + direction;
}
}
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..d8d0247 100644
--- a/engine/src/data_types/dimens.rs
+++ b/engine/src/data_types/dimens.rs
@@ -1,7 +1,56 @@
-/// Dimensions.
+/// 2D dimensions.
#[derive(Debug, Clone, Copy)]
pub struct Dimens<Value>
{
pub width: Value,
pub height: Value,
}
+
+impl<Value: Clone> From<Value> for Dimens<Value>
+{
+ fn from(value: Value) -> Self
+ {
+ Self { width: value.clone(), height: value }
+ }
+}
+
+impl<Value> From<(Value, Value)> for Dimens<Value>
+{
+ fn from(value: (Value, Value)) -> Self
+ {
+ Self { width: value.0, height: value.1 }
+ }
+}
+
+/// 3D dimensions.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+pub struct Dimens3<Value>
+{
+ pub width: Value,
+ pub height: Value,
+ pub depth: Value,
+}
+
+impl<Value: Clone> From<Value> for Dimens3<Value>
+{
+ fn from(value: Value) -> Self
+ {
+ Self {
+ width: value.clone(),
+ height: value.clone(),
+ depth: value,
+ }
+ }
+}
+
+impl<Value: Clone> From<(Value, Value, Value)> for Dimens3<Value>
+{
+ fn from(value: (Value, Value, Value)) -> Self
+ {
+ Self {
+ width: value.0,
+ height: value.1,
+ depth: value.2,
+ }
+ }
+}
diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs
index 17953f4..100c709 100644
--- a/engine/src/data_types/vector.rs
+++ b/engine/src/data_types/vector.rs
@@ -2,7 +2,8 @@ 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)]
+#[repr(C)]
pub struct Vec2<Value>
{
pub x: Value,
@@ -74,7 +75,7 @@ where
}
}
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct Vec3<Value>
{
@@ -85,6 +86,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 +215,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/draw_flags.rs b/engine/src/draw_flags.rs
index df5eed1..426f865 100644
--- a/engine/src/draw_flags.rs
+++ b/engine/src/draw_flags.rs
@@ -1,6 +1,6 @@
use ecs::Component;
-use crate::util::builder;
+use crate::builder;
builder! {
/// Flags for how a object should be drawn.
diff --git a/engine/src/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/mtl.rs b/engine/src/file_format/wavefront/mtl.rs
index ef6e894..f3c7a64 100644
--- a/engine/src/file_format/wavefront/mtl.rs
+++ b/engine/src/file_format/wavefront/mtl.rs
@@ -2,7 +2,7 @@
//!
//! File format documentation: <https://paulbourke.net/dataformats/mtl>
-use std::path::Path;
+use std::path::{Path, PathBuf};
use crate::color::Color;
use crate::file_format::wavefront::common::{
@@ -11,8 +11,6 @@ use crate::file_format::wavefront::common::{
ParsingError,
Statement,
};
-use crate::material::{Builder as MaterialBuilder, Material};
-use crate::texture::{Error as TextureError, Texture};
/// Parses the content of a Wavefront `.mtl`.
///
@@ -44,25 +42,47 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error>
.filter(|(_, statement)| matches!(statement.keyword, Keyword::Newmtl))
.count();
- #[cfg(feature = "debug")]
tracing::debug!("Material count: {material_cnt}");
statements_to_materials(statements, material_cnt)
}
#[derive(Debug, Clone)]
+#[non_exhaustive]
pub struct NamedMaterial
{
pub name: String,
- pub material: Material,
+ pub ambient: Color<f32>,
+ pub diffuse: Color<f32>,
+ pub specular: Color<f32>,
+ pub ambient_map: Option<TextureMap>,
+ pub diffuse_map: Option<TextureMap>,
+ pub specular_map: Option<TextureMap>,
+ pub shininess: f32,
+}
+
+impl Default for NamedMaterial
+{
+ fn default() -> Self
+ {
+ Self {
+ name: String::new(),
+ ambient: Color::WHITE_F32,
+ diffuse: Color::WHITE_F32,
+ specular: Color::WHITE_F32,
+ ambient_map: None,
+ diffuse_map: None,
+ specular_map: None,
+ shininess: 0.0,
+ }
+ }
}
#[derive(Debug, Clone)]
-pub struct UnfinishedNamedMaterial
+#[non_exhaustive]
+pub struct TextureMap
{
- name: String,
- material_builder: MaterialBuilder,
- ready: bool,
+ pub path: PathBuf,
}
#[derive(Debug, thiserror::Error)]
@@ -71,8 +91,14 @@ pub enum Error
#[error(transparent)]
ParsingError(#[from] ParsingError),
- #[error("Failed to open texture")]
- TextureError(#[from] TextureError),
+ #[error(
+ "A material start statement (newmtl) is expected before statement at line {}",
+ line_no
+ )]
+ ExpectedMaterialStartStmtBeforeStmt
+ {
+ line_no: usize
+ },
#[error(
"Unsupported number of arguments ({arg_count}) to {keyword} at line {line_no}"
@@ -93,7 +119,7 @@ pub enum Error
},
}
-#[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
+#[tracing::instrument(skip_all)]
fn statements_to_materials(
statements: impl IntoIterator<Item = (usize, Statement<Keyword>)>,
material_cnt: usize,
@@ -101,63 +127,52 @@ fn statements_to_materials(
{
let mut materials = Vec::<NamedMaterial>::with_capacity(material_cnt);
- let mut curr_material = UnfinishedNamedMaterial {
- name: String::new(),
- material_builder: MaterialBuilder::new(),
- ready: false,
- };
-
for (line_no, statement) in statements {
if statement.keyword == Keyword::Newmtl {
- if curr_material.ready {
- #[cfg(feature = "debug")]
- tracing::debug!("Building material");
-
- let material = curr_material.material_builder.clone().build();
-
- materials.push(NamedMaterial { name: curr_material.name, material });
- }
-
let name = statement.get_text_arg(0, line_no)?;
- curr_material.name = name.to_string();
- curr_material.ready = true;
+ materials.push(NamedMaterial {
+ name: name.to_string(),
+ ..Default::default()
+ });
continue;
}
- if !curr_material.ready {
- // Discard statements not belonging to a material
- continue;
+ let Some(curr_material) = materials.last_mut() else {
+ return Err(Error::ExpectedMaterialStartStmtBeforeStmt { line_no });
};
match statement.keyword {
Keyword::Ka => {
let color = get_color_from_statement(&statement, line_no)?;
- #[cfg(feature = "debug")]
- tracing::debug!("Adding ambient color");
+ tracing::debug!(
+ "Adding ambient color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.ambient(color);
+ curr_material.ambient = color;
}
Keyword::Kd => {
let color = get_color_from_statement(&statement, line_no)?;
- #[cfg(feature = "debug")]
- tracing::debug!("Adding diffuse color");
+ tracing::debug!(
+ "Adding diffuse color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.diffuse(color);
+ curr_material.diffuse = color;
}
Keyword::Ks => {
let color = get_color_from_statement(&statement, line_no)?;
- #[cfg(feature = "debug")]
- tracing::debug!("Adding specular color");
+ tracing::debug!(
+ "Adding specular color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.specular(color);
+ curr_material.specular = color;
}
Keyword::MapKa => {
if statement.arguments.len() > 1 {
@@ -170,55 +185,75 @@ fn statements_to_materials(
let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture = Texture::open(Path::new(texture_file_path))?;
-
- #[cfg(feature = "debug")]
- tracing::debug!("Adding ambient map");
+ tracing::debug!(
+ "Adding ambient map {texture_file_path} to material {}",
+ curr_material.name
+ );
- let texture_id = texture.id();
-
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .ambient_map(texture_id);
+ curr_material.ambient_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKd => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- #[cfg(feature = "debug")]
- tracing::debug!("Adding diffuse map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding diffuse map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .diffuse_map(texture_id);
+ curr_material.diffuse_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKs => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- #[cfg(feature = "debug")]
- tracing::debug!("Adding specular map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding specular map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .specular_map(texture_id);
+ curr_material.specular_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
- Keyword::Newmtl => {}
- }
- }
+ Keyword::Ns => {
+ if statement.arguments.len() != 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- if curr_material.ready {
- #[cfg(feature = "debug")]
- tracing::debug!("Building last material");
+ let shininess = statement.get_float_arg(0, line_no)?;
- let material = curr_material.material_builder.build();
+ tracing::debug!(
+ "Adding shininess {shininess} to material {}",
+ curr_material.name
+ );
- materials.push(NamedMaterial { name: curr_material.name, material });
+ curr_material.shininess = shininess;
+ }
+ Keyword::Newmtl => {}
+ }
}
Ok(materials)
@@ -244,24 +279,6 @@ fn get_color_from_statement(
Ok(Color { red, green, blue })
}
-fn get_map_from_texture(
- statement: &Statement<Keyword>,
- line_no: usize,
-) -> Result<Texture, Error>
-{
- if statement.arguments.len() > 1 {
- return Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no,
- });
- }
-
- let texture_file_path = statement.get_text_arg(0, line_no)?;
-
- Ok(Texture::open(Path::new(texture_file_path))?)
-}
-
keyword! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Keyword {
@@ -280,5 +297,7 @@ keyword! {
#[keyword(rename = "map_Ks")]
MapKs,
+
+ Ns,
}
}
diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs
index 88e6580..88d566c 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;
@@ -12,10 +13,9 @@ use crate::file_format::wavefront::common::{
Statement,
Triplet,
};
-use crate::mesh::Mesh;
+use crate::mesh::{Mesh, Vertex};
use crate::util::try_option;
use crate::vector::{Vec2, Vec3};
-use crate::vertex::{Builder as VertexBuilder, Vertex};
/// Parses the content of a Wavefront `.obj`.
///
@@ -82,26 +82,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 +148,7 @@ pub struct Face
pub material_name: Option<String>,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FaceVertex
{
pub position: u32,
@@ -161,7 +167,7 @@ impl FaceVertex
/// - The face's vertex normal cannot be found in the given [`Obj`]
pub fn to_vertex(&self, obj: &Obj) -> Result<Vertex, Error>
{
- let mut vertex_builder = VertexBuilder::default();
+ let mut vertex_builder = Vertex::builder();
let vertex_pos = *obj.vertex_positions.get(self.position as usize - 1).ok_or(
Error::FaceVertexPositionNotFound { vertex_pos_index: self.position },
diff --git a/engine/src/image.rs b/engine/src/image.rs
new file mode 100644
index 0000000..0e04412
--- /dev/null
+++ b/engine/src/image.rs
@@ -0,0 +1,184 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::path::Path;
+
+use image_rs::GenericImageView as _;
+
+use crate::asset::{Assets, Submitter as AssetSubmitter};
+use crate::color::Color;
+use crate::data_types::dimens::Dimens;
+use crate::builder;
+
+#[derive(Debug)]
+pub struct Image
+{
+ inner: image_rs::DynamicImage,
+}
+
+impl Image
+{
+ pub fn open(path: impl AsRef<Path>) -> Result<Self, Error>
+ {
+ let buffered_reader =
+ BufReader::new(File::open(&path).map_err(Error::ReadFailed)?);
+
+ let image_reader = image_rs::io::Reader::with_format(
+ buffered_reader,
+ image_rs::ImageFormat::from_path(path)
+ .map_err(|_| Error::UnsupportedFormat)?,
+ );
+
+ Ok(Self {
+ inner: image_reader
+ .decode()
+ .map_err(|err| Error::DecodeFailed(DecodeError(err)))?,
+ })
+ }
+
+ pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>)
+ -> Self
+ {
+ let dimens: Dimens<u32> = dimens.into();
+
+ let color: Color<u8> = color.into();
+
+ Self {
+ inner: image_rs::RgbImage::from_pixel(
+ dimens.width,
+ dimens.height,
+ image_rs::Rgb([color.red, color.green, color.blue]),
+ )
+ .into(),
+ }
+ }
+
+ pub fn dimensions(&self) -> Dimens<u32>
+ {
+ self.inner.dimensions().into()
+ }
+
+ pub fn color_type(&self) -> ColorType
+ {
+ self.inner.color().into()
+ }
+
+ pub fn as_bytes(&self) -> &[u8]
+ {
+ self.inner.as_bytes()
+ }
+}
+
+builder! {
+#[builder(name = SettingsBuilder, derives=(Debug, Clone))]
+#[derive(Debug, Default, Clone)]
+#[non_exhaustive]
+pub struct Settings {
+}
+}
+
+impl Settings
+{
+ pub fn builder() -> SettingsBuilder
+ {
+ SettingsBuilder::default()
+ }
+}
+
+impl Default for SettingsBuilder
+{
+ fn default() -> Self
+ {
+ Settings::default().into()
+ }
+}
+
+/// An enumeration over supported color types and bit depths
+#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
+#[non_exhaustive]
+pub enum ColorType
+{
+ /// Pixel is 8-bit luminance
+ L8,
+
+ /// Pixel is 8-bit luminance with an alpha channel
+ La8,
+
+ /// Pixel contains 8-bit R, G and B channels
+ Rgb8,
+
+ /// Pixel is 8-bit RGB with an alpha channel
+ Rgba8,
+
+ /// Pixel is 16-bit luminance
+ L16,
+
+ /// Pixel is 16-bit luminance with an alpha channel
+ La16,
+
+ /// Pixel is 16-bit RGB
+ Rgb16,
+
+ /// Pixel is 16-bit RGBA
+ Rgba16,
+
+ /// Pixel is 32-bit float RGB
+ Rgb32F,
+
+ /// Pixel is 32-bit float RGBA
+ Rgba32F,
+}
+
+impl From<image_rs::ColorType> for ColorType
+{
+ fn from(color_type: image_rs::ColorType) -> Self
+ {
+ match color_type {
+ image_rs::ColorType::L8 => Self::L8,
+ image_rs::ColorType::La8 => Self::La8,
+ image_rs::ColorType::Rgb8 => Self::Rgb8,
+ image_rs::ColorType::Rgba8 => Self::Rgba8,
+ image_rs::ColorType::L16 => Self::L16,
+ image_rs::ColorType::La16 => Self::La16,
+ image_rs::ColorType::Rgb16 => Self::Rgb16,
+ image_rs::ColorType::Rgba16 => Self::Rgba16,
+ image_rs::ColorType::Rgb32F => Self::Rgb32F,
+ image_rs::ColorType::Rgba32F => Self::Rgba32F,
+ _ => {
+ panic!("Unrecognized image_rs::ColorType variant");
+ }
+ }
+ }
+}
+
+pub fn set_asset_importers(assets: &mut Assets)
+{
+ assets.set_importer::<_, _>(["png", "jpg"], import_asset);
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to read image file")]
+ ReadFailed(#[source] std::io::Error),
+
+ #[error("Failed to decode image")]
+ DecodeFailed(DecodeError),
+
+ #[error("Unsupported image format")]
+ UnsupportedFormat,
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct DecodeError(image_rs::ImageError);
+
+fn import_asset(
+ asset_submitter: &mut AssetSubmitter<'_>,
+ path: &Path,
+ _settings: Option<&'_ Settings>,
+) -> Result<(), Error>
+{
+ asset_submitter.submit_store::<Image>(Image::open(path)?);
+
+ Ok(())
+}
diff --git a/engine/src/input.rs b/engine/src/input.rs
index f4166f6..d6a82f6 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::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE};
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,16 @@ mod reexports
pub use reexports::*;
+static_entity!(
+ SET_PREV_KEY_STATE_PHASE,
+ (Phase, Pair::new::<ChildOf>(*WINDOW_UPDATE_PHASE))
+);
+
#[derive(Debug, Sole)]
pub struct Keys
{
map: HashMap<Key, KeyData>,
+ pending: Vec<(Key, KeyState)>,
}
impl Keys
@@ -43,6 +46,7 @@ impl Keys
)
})
.collect(),
+ pending: Vec::with_capacity(Key::KEYS.len()),
}
}
@@ -68,10 +72,6 @@ impl Keys
pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState)
{
- if matches!(new_key_state, KeyState::Repeat) {
- return;
- }
-
let Some(key_data) = self.map.get_mut(&key) else {
unreachable!();
};
@@ -149,9 +149,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();
@@ -173,7 +173,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();
@@ -194,7 +194,6 @@ fn initialize(
let cursor_flags_weak_ref = cursor_flags.to_weak_ref();
window.set_focus_callback(move |is_focused| {
- #[cfg(feature = "debug")]
tracing::trace!("Window is focused: {is_focused}");
let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world");
@@ -209,7 +208,6 @@ fn maybe_clear_cursor_is_first_move(
)
{
if cursor_flags.is_first_move.pending_clear {
- #[cfg(feature = "debug")]
tracing::trace!("Clearing is_first_move");
// This flag was set for the whole previous tick so it can be cleared now
@@ -219,7 +217,6 @@ fn maybe_clear_cursor_is_first_move(
}
if cursor.has_moved && cursor_flags.is_first_move.flag {
- #[cfg(feature = "debug")]
tracing::trace!("Setting flag to clear is_first_move next tick");
// Make this system clear is_first_move the next time it runs
@@ -227,11 +224,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 abf26f5..6ccba53 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -2,46 +2,39 @@
#![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::extension::Extension;
+use ecs::pair::Pair;
+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::asset::{Assets, Extension as AssetExtension};
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 shader_preprocessor;
mod util;
+mod work_queue;
+pub mod asset;
pub mod camera;
+pub mod collision;
pub mod data_types;
pub mod delta_time;
pub mod draw_flags;
-pub mod event;
pub mod file_format;
+pub mod image;
pub mod input;
pub mod lighting;
pub mod material;
pub mod math;
pub mod mesh;
-pub mod performance;
+pub mod model;
pub mod projection;
pub mod renderer;
-pub mod shader;
pub mod texture;
pub mod transform;
-pub mod vertex;
pub mod window;
pub extern crate ecs;
@@ -49,13 +42,7 @@ 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,
-);
+const INITIAL_ASSET_CAPACITY: usize = 128;
#[derive(Debug)]
pub struct Engine
@@ -74,30 +61,45 @@ impl Engine
world.add_sole(DeltaTime::default()).ok();
world.register_system(
- PreUpdateEvent,
+ *PRE_UPDATE_PHASE,
update_delta_time
.into_system()
.initialize((LastUpdate::default(),)),
);
+ let mut assets = Assets::with_capacity(INITIAL_ASSET_CAPACITY);
+
+ crate::model::set_asset_importers(&mut assets);
+ crate::image::set_asset_importers(&mut assets);
+
+ world.add_extension(AssetExtension { assets });
+
Self { world }
}
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(phase_euid, system);
+ }
+
+ pub fn register_observer_system<'this, SystemImpl>(
+ &'this mut self,
system: impl System<'this, SystemImpl>,
+ event: Pair<Uid, Uid>,
)
{
- self.world.register_system(event, system);
+ self.world.register_observer_system(system, event);
}
/// Adds a globally shared singleton value.
@@ -115,9 +117,9 @@ impl Engine
}
/// Runs the event loop.
- pub fn start(&self)
+ pub fn start(&mut self)
{
- self.world.event_loop::<EventOrder>();
+ self.world.start_loop();
}
}
diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs
index 48adb0e..4406ed5 100644
--- a/engine/src/lighting.rs
+++ b/engine/src/lighting.rs
@@ -2,7 +2,7 @@ use ecs::{Component, Sole};
use crate::color::Color;
use crate::data_types::vector::Vec3;
-use crate::util::builder;
+use crate::builder;
builder! {
#[builder(name = PointLightBuilder, derives = (Debug, Clone))]
@@ -10,7 +10,8 @@ builder! {
#[non_exhaustive]
pub struct PointLight
{
- pub position: Vec3<f32>,
+ /// Position in local space.
+ pub local_position: Vec3<f32>,
pub diffuse: Color<f32>,
pub specular: Color<f32>,
pub attenuation_params: AttenuationParams,
@@ -31,7 +32,7 @@ impl Default for PointLight
fn default() -> Self
{
Self {
- position: Vec3::default(),
+ local_position: Vec3::default(),
diffuse: Color { red: 0.5, green: 0.5, blue: 0.5 },
specular: Color { red: 1.0, green: 1.0, blue: 1.0 },
attenuation_params: AttenuationParams::default(),
diff --git a/engine/src/material.rs b/engine/src/material.rs
index aae6003..56ff15f 100644
--- a/engine/src/material.rs
+++ b/engine/src/material.rs
@@ -1,35 +1,48 @@
use ecs::Component;
use crate::color::Color;
-use crate::data_types::dimens::Dimens;
-use crate::texture::{Id as TextureId, Texture};
-use crate::util::builder;
+use crate::texture::Texture;
+use crate::builder;
-#[derive(Debug, Clone, Component)]
+#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Material
{
pub ambient: Color<f32>,
pub diffuse: Color<f32>,
pub specular: Color<f32>,
- pub ambient_map: TextureId,
- pub diffuse_map: TextureId,
- pub specular_map: TextureId,
- pub textures: Vec<Texture>,
+ pub ambient_map: Option<Texture>,
+ pub diffuse_map: Option<Texture>,
+ pub specular_map: Option<Texture>,
pub shininess: f32,
}
+impl Material
+{
+ pub fn builder() -> Builder
+ {
+ Builder::default()
+ }
+}
+
+impl Default for Material
+{
+ fn default() -> Self
+ {
+ Self::builder().build()
+ }
+}
+
/// [`Material`] builder.
#[derive(Debug, Clone)]
pub struct Builder
{
- ambient: Option<Color<f32>>,
- diffuse: Option<Color<f32>>,
- specular: Option<Color<f32>>,
- ambient_map: Option<TextureId>,
- diffuse_map: Option<TextureId>,
- specular_map: Option<TextureId>,
- textures: Vec<Texture>,
+ ambient: Color<f32>,
+ diffuse: Color<f32>,
+ specular: Color<f32>,
+ ambient_map: Option<Texture>,
+ diffuse_map: Option<Texture>,
+ specular_map: Option<Texture>,
shininess: f32,
}
@@ -39,13 +52,12 @@ impl Builder
pub fn new() -> Self
{
Self {
- ambient: None,
- diffuse: None,
- specular: None,
+ ambient: Color::WHITE_F32,
+ diffuse: Color::WHITE_F32,
+ specular: Color::WHITE_F32,
ambient_map: None,
diffuse_map: None,
specular_map: None,
- textures: Vec::new(),
shininess: 32.0,
}
}
@@ -53,7 +65,7 @@ impl Builder
#[must_use]
pub fn ambient(mut self, ambient: Color<f32>) -> Self
{
- self.ambient = Some(ambient);
+ self.ambient = ambient;
self
}
@@ -61,7 +73,7 @@ impl Builder
#[must_use]
pub fn diffuse(mut self, diffuse: Color<f32>) -> Self
{
- self.diffuse = Some(diffuse);
+ self.diffuse = diffuse;
self
}
@@ -69,13 +81,13 @@ impl Builder
#[must_use]
pub fn specular(mut self, specular: Color<f32>) -> Self
{
- self.specular = Some(specular);
+ self.specular = specular;
self
}
#[must_use]
- pub fn ambient_map(mut self, ambient_map: TextureId) -> Self
+ pub fn ambient_map(mut self, ambient_map: Texture) -> Self
{
self.ambient_map = Some(ambient_map);
@@ -83,7 +95,7 @@ impl Builder
}
#[must_use]
- pub fn diffuse_map(mut self, diffuse_map: TextureId) -> Self
+ pub fn diffuse_map(mut self, diffuse_map: Texture) -> Self
{
self.diffuse_map = Some(diffuse_map);
@@ -91,7 +103,7 @@ impl Builder
}
#[must_use]
- pub fn specular_map(mut self, specular_map: TextureId) -> Self
+ pub fn specular_map(mut self, specular_map: Texture) -> Self
{
self.specular_map = Some(specular_map);
@@ -99,22 +111,6 @@ impl Builder
}
#[must_use]
- pub fn textures(mut self, textures: impl IntoIterator<Item = Texture>) -> Self
- {
- self.textures = textures.into_iter().collect();
-
- self
- }
-
- #[must_use]
- pub fn texture(mut self, texture: Texture) -> Self
- {
- self.textures.push(texture);
-
- self
- }
-
- #[must_use]
pub fn shininess(mut self, shininess: f32) -> Self
{
self.shininess = shininess;
@@ -127,43 +123,15 @@ impl Builder
/// # Panics
/// Will panic if no ambient map, diffuse map or specular map is set.
#[must_use]
- pub fn build(mut self) -> Material
+ pub fn build(self) -> Material
{
- let ambient_map = self.ambient_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
- let diffuse_map = self.diffuse_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
- let specular_map = self.specular_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
Material {
- ambient: self.ambient.unwrap_or(Color::WHITE_F32),
- diffuse: self.diffuse.unwrap_or(Color::WHITE_F32),
- specular: self.specular.unwrap_or(Color::WHITE_F32),
- ambient_map,
- diffuse_map,
- specular_map,
- textures: self.textures,
+ ambient: self.ambient,
+ diffuse: self.diffuse,
+ specular: self.specular,
+ ambient_map: self.ambient_map,
+ diffuse_map: self.diffuse_map,
+ specular_map: self.specular_map,
shininess: self.shininess,
}
}
@@ -198,8 +166,3 @@ impl Flags
FlagsBuilder::default()
}
}
-
-fn create_1x1_white_texture() -> Texture
-{
- Texture::new_from_color(&Dimens { width: 1, height: 1 }, &Color::WHITE_U8)
-}
diff --git a/engine/src/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..fb977af 100644
--- a/engine/src/mesh.rs
+++ b/engine/src/mesh.rs
@@ -1,10 +1,9 @@
-use ecs::Component;
-
-use crate::vertex::Vertex;
+use crate::builder;
+use crate::vector::{Vec2, Vec3};
pub mod cube;
-#[derive(Debug, Clone, Component)]
+#[derive(Debug, Clone, Default)]
pub struct Mesh
{
vertices: Vec<Vertex>,
@@ -26,8 +25,140 @@ 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()
+ }
+}
+
+builder! {
+#[builder(name = VertexBuilder, derives = (Debug, Default, Clone))]
+#[derive(Debug, Clone, Default, PartialEq)]
+#[non_exhaustive]
+pub struct Vertex
+{
+ pub pos: Vec3<f32>,
+ pub texture_coords: Vec2<f32>,
+ pub normal: Vec3<f32>,
+}
+}
+
+impl Vertex
+{
+ #[must_use]
+ pub fn builder() -> VertexBuilder
+ {
+ VertexBuilder::default()
+ }
+}
+
+#[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..e91cf0e 100644
--- a/engine/src/mesh/cube.rs
+++ b/engine/src/mesh/cube.rs
@@ -1,8 +1,8 @@
+use crate::data_types::dimens::Dimens3;
use crate::math::calc_triangle_surface_normal;
-use crate::mesh::Mesh;
-use crate::util::builder;
-use crate::vector::Vec3;
-use crate::vertex::{Builder as VertexBuilder, Vertex};
+use crate::mesh::{Mesh, Vertex};
+use crate::builder;
+use crate::vector::{Vec2, Vec3};
builder! {
/// Cube mesh creation specification.
@@ -27,676 +27,467 @@ impl CreationSpec
}
}
-#[derive(Debug)]
+impl CreationSpecBuilder
+{
+ pub fn dimens(mut self, dimens: Dimens3<f32>) -> Self
+ {
+ self.width = dimens.width;
+ self.height = dimens.height;
+ self.depth = dimens.depth;
+
+ self
+ }
+}
+
+/// 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();
+
+ Vertex::builder()
+ .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/model.rs b/engine/src/model.rs
new file mode 100644
index 0000000..9f5840c
--- /dev/null
+++ b/engine/src/model.rs
@@ -0,0 +1,176 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fs::read_to_string;
+use std::path::Path;
+
+use ecs::Component;
+
+use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter};
+use crate::material::Material;
+use crate::mesh::Mesh;
+use crate::texture::Texture;
+
+#[derive(Debug, Clone, Component)]
+#[non_exhaustive]
+pub struct Model
+{
+ pub asset_handle: AssetHandle<Data>,
+}
+
+impl Model
+{
+ pub fn new(asset_handle: AssetHandle<Data>) -> Self
+ {
+ Self { asset_handle }
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+#[non_exhaustive]
+pub struct Data
+{
+ pub mesh: Mesh,
+ pub materials: HashMap<String, Material>,
+}
+
+impl Data
+{
+ pub fn builder() -> DataBuilder
+ {
+ DataBuilder::default()
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct DataBuilder
+{
+ mesh: Mesh,
+ materials: HashMap<String, Material>,
+}
+
+impl DataBuilder
+{
+ pub fn mesh(mut self, mesh: Mesh) -> Self
+ {
+ self.mesh = mesh;
+
+ self
+ }
+
+ pub fn material<'name>(
+ mut self,
+ name: impl Into<Cow<'name, str>>,
+ material: Material,
+ ) -> Self
+ {
+ self.materials.insert(name.into().into_owned(), material);
+
+ self
+ }
+
+ pub fn build(self) -> Data
+ {
+ Data {
+ mesh: self.mesh,
+ materials: self.materials,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct Settings {}
+
+#[derive(Debug, thiserror::Error)]
+enum Error
+{
+ #[error("Failed to read model file")]
+ ReadModelFileFailed(#[source] std::io::Error),
+
+ #[error("Failed to read material file")]
+ ReadMaterialFileFailed(#[source] std::io::Error),
+
+ #[error("Failed to parse model file")]
+ ParsingFailed(#[from] ParsingError),
+}
+
+pub fn set_asset_importers(assets: &mut Assets)
+{
+ assets.set_importer(["obj"], import_wavefront_obj_asset);
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ParsingError
+{
+ #[error(transparent)]
+ Obj(#[from] crate::file_format::wavefront::obj::Error),
+
+ #[error(transparent)]
+ Mtl(#[from] crate::file_format::wavefront::mtl::Error),
+}
+
+fn import_wavefront_obj_asset(
+ asset_submitter: &mut AssetSubmitter<'_>,
+ path: &Path,
+ _settings: Option<&'_ Settings>,
+) -> Result<(), Error>
+{
+ let obj = crate::file_format::wavefront::obj::parse(
+ &read_to_string(path).map_err(Error::ReadModelFileFailed)?,
+ )
+ .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?;
+
+ let mesh = obj
+ .to_mesh()
+ .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?;
+
+ let mut materials =
+ HashMap::<String, Material>::with_capacity(obj.mtl_libs.iter().flatten().count());
+
+ for mtl_lib_path in obj.mtl_libs.iter().flatten() {
+ materials.extend(import_mtl(asset_submitter, &mtl_lib_path)?);
+ }
+
+ asset_submitter.submit_store(Data { mesh, materials });
+
+ Ok(())
+}
+
+fn import_mtl<'a>(
+ asset_submitter: &'a AssetSubmitter<'_>,
+ path: &Path,
+) -> Result<impl Iterator<Item = (String, Material)> + 'a, Error>
+{
+ let named_materials = crate::file_format::wavefront::mtl::parse(
+ &read_to_string(path).map_err(Error::ReadMaterialFileFailed)?,
+ )
+ .map_err(|err| Error::ParsingFailed(ParsingError::Mtl(err)))?;
+
+ Ok(named_materials.into_iter().map(|named_material| {
+ let mut material_builder = Material::builder()
+ .ambient(named_material.ambient)
+ .diffuse(named_material.diffuse)
+ .specular(named_material.specular)
+ .shininess(named_material.shininess);
+
+ if let Some(ambient_map) = named_material.ambient_map {
+ material_builder = material_builder.ambient_map(Texture::new(
+ asset_submitter.submit_load_other(ambient_map.path.as_path()),
+ ));
+ }
+
+ if let Some(diffuse_map) = named_material.diffuse_map {
+ material_builder = material_builder.diffuse_map(Texture::new(
+ asset_submitter.submit_load_other(diffuse_map.path.as_path()),
+ ));
+ }
+
+ if let Some(specular_map) = named_material.specular_map {
+ material_builder = material_builder.specular_map(Texture::new(
+ asset_submitter.submit_load_other(specular_map.path.as_path()),
+ ));
+ }
+
+ (named_material.name, material_builder.build())
+ }))
+}
diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs
index 2be7f12..eded553 100644
--- a/engine/src/opengl/buffer.rs
+++ b/engine/src/opengl/buffer.rs
@@ -1,5 +1,6 @@
use std::marker::PhantomData;
use std::mem::size_of_val;
+use std::ptr::null;
#[derive(Debug)]
pub struct Buffer<Item>
@@ -35,32 +36,40 @@ impl<Item> Buffer<Item>
}
}
- pub fn object(&self) -> gl::types::GLuint
+ pub fn store_mapped<Value>(
+ &mut self,
+ values: &[Value],
+ usage: Usage,
+ mut map_func: impl FnMut(&Value) -> Item,
+ )
{
- self.buf
- }
+ unsafe {
+ #[allow(clippy::cast_possible_wrap)]
+ gl::NamedBufferData(
+ self.buf,
+ (size_of::<Item>() * values.len()) as gl::types::GLsizeiptr,
+ null(),
+ usage.into_gl(),
+ );
+ }
- /// 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 }
+ for (index, value) in values.iter().enumerate() {
+ let item = map_func(value);
+
+ unsafe {
+ gl::NamedBufferSubData(
+ self.buf,
+ (index * size_of::<Item>()) as gl::types::GLintptr,
+ size_of::<Item>() as gl::types::GLsizeiptr,
+ (&raw const item).cast(),
+ );
+ }
+ }
}
-}
-impl<Item> Drop for Buffer<Item>
-{
- fn drop(&mut self)
+ pub fn object(&self) -> gl::types::GLuint
{
- unsafe {
- gl::DeleteBuffers(1, &self.buf);
- }
+ self.buf
}
}
diff --git a/engine/src/shader_preprocessor.rs b/engine/src/opengl/glsl.rs
index 70696ac..6fd5638 100644
--- a/engine/src/shader_preprocessor.rs
+++ b/engine/src/opengl/glsl.rs
@@ -1,183 +1,181 @@
+use std::borrow::Cow;
+use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
use std::string::FromUtf8Error;
const PREINCLUDE_DIRECTIVE: &str = "#preinclude";
-/// Preprocessor for shaders written in the OpenGL Shading Language.
-pub struct ShaderPreprocessor
+pub fn preprocess<'content>(
+ shader_content: impl Into<Cow<'content, str>>,
+ read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>,
+) -> Result<Cow<'content, str>, PreprocessingError>
{
- read_file: fn(&Path) -> Result<Vec<u8>, std::io::Error>,
- base_dir: PathBuf,
+ do_preprocess(shader_content, SpanPath::Original, read_file)
}
-impl ShaderPreprocessor
+fn do_preprocess<'content>(
+ shader_content: impl Into<Cow<'content, str>>,
+ shader_path: SpanPath<'_>,
+ read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>,
+) -> Result<Cow<'content, str>, PreprocessingError>
{
- pub fn new(base_dir: PathBuf) -> Self
- {
- Self {
- read_file: |path| std::fs::read(path),
- base_dir,
- }
- }
+ let shader_content = shader_content.into();
- pub fn preprocess(
- &self,
- shader_content: String,
- shader_file_path: &Path,
- ) -> Result<String, Error>
- {
- let mut preincludes = shader_content
- .match_indices(PREINCLUDE_DIRECTIVE)
- .peekable();
+ let mut preincludes = shader_content
+ .match_indices(PREINCLUDE_DIRECTIVE)
+ .peekable();
- if preincludes.peek().is_none() {
- // Shader content contains no preincludes
- return Ok(shader_content);
- };
+ if preincludes.peek().is_none() {
+ // Shader content contains no preincludes
+ return Ok(shader_content.into());
+ };
- let mut preprocessed = shader_content.clone();
+ let mut preprocessed = shader_content.to_string();
- let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE);
+ let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE);
- let mut last_start = 0;
- let mut span_line_offset = 0;
+ let mut last_start = 0;
+ let mut span_line_offset = 0;
- while let Some(preinclude_start) = curr {
- let replacement_job = self.handle_preinclude(
- &preprocessed,
- shader_file_path,
- preinclude_start,
- span_line_offset,
- )?;
+ while let Some(preinclude_start) = curr {
+ let replacement_job = handle_preinclude(
+ &preprocessed,
+ &shader_path,
+ preinclude_start,
+ span_line_offset,
+ )?;
- let path = replacement_job.path.clone();
+ let path = replacement_job.path.clone();
- let mut included = String::from_utf8(
- (self.read_file)(&self.base_dir.join(replacement_job.path.clone()))
- .map_err(|err| Error::ReadIncludedShaderFailed {
- source: err,
- path: replacement_job.path.clone(),
- })?,
- )
- .map_err(|err| Error::IncludedShaderInvalidUtf8 {
- source: err,
- path: path.clone(),
+ let mut included =
+ String::from_utf8(read_file(&replacement_job.path).map_err(|err| {
+ PreprocessingError::ReadIncludedShaderFailed {
+ source: err,
+ path: replacement_job.path.clone(),
+ }
+ })?)
+ .map_err(|err| {
+ PreprocessingError::IncludedShaderInvalidUtf8 {
+ source: err,
+ path: path.clone(),
+ }
})?;
- if let Some(first_line) = included.lines().next() {
- if first_line.starts_with("#version") {
- included = included
- .chars()
- .skip_while(|character| *character != '\n')
- .collect();
- }
+ if let Some(first_line) = included.lines().next() {
+ if first_line.starts_with("#version") {
+ included = included
+ .chars()
+ .skip_while(|character| *character != '\n')
+ .collect();
}
-
- let included_preprocessed =
- self.preprocess(included, &replacement_job.path)?;
-
- let start = replacement_job.start_index;
- let end = replacement_job.end_index;
-
- preprocessed.replace_range(start..end, &included_preprocessed);
-
- curr = preprocessed[last_start + 1..]
- .find(PREINCLUDE_DIRECTIVE)
- .map(|index| index + 1);
-
- last_start = preinclude_start + included_preprocessed.len();
-
- span_line_offset += included_preprocessed.lines().count();
}
- Ok(preprocessed)
- }
+ let included_preprocessed = do_preprocess(
+ &included,
+ SpanPath::Path(replacement_job.path.as_path().into()),
+ read_file,
+ )?;
- fn handle_preinclude(
- &self,
- shader_content: &str,
- shader_file_path: &Path,
- preinclude_start_index: usize,
- span_line_offset: usize,
- ) -> Result<ReplacementJob, Error>
- {
- let expect_token = |token: char, index: usize| {
- let token_found = shader_content.chars().nth(index).ok_or_else(|| {
- Error::ExpectedToken {
- expected: token,
- span: Span::new(
- shader_content,
- &self.base_dir.join(shader_file_path),
- index,
- span_line_offset,
- preinclude_start_index,
- ),
- }
- })?;
+ let start = replacement_job.start_index;
+ let end = replacement_job.end_index;
- if token_found != token {
- return Err(Error::InvalidToken {
- expected: token,
- found: token_found,
- span: Span::new(
- shader_content,
- &self.base_dir.join(shader_file_path),
- index,
- span_line_offset,
- preinclude_start_index,
- ),
- });
- }
+ preprocessed.replace_range(start..end, &included_preprocessed);
- Ok(())
- };
+ curr = preprocessed[last_start + 1..]
+ .find(PREINCLUDE_DIRECTIVE)
+ .map(|index| index + 1);
- let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len();
- let quote_open_index = space_index + 1;
+ last_start = preinclude_start + included_preprocessed.len();
- expect_token(' ', space_index)?;
- expect_token('"', quote_open_index)?;
+ span_line_offset += included_preprocessed.lines().count();
+ }
- let buf = shader_content[quote_open_index + 1..]
- .chars()
- .take_while(|character| *character != '"')
- .map(|character| character as u8)
- .collect::<Vec<_>>();
+ Ok(preprocessed.into())
+}
- if buf.is_empty() {
- return Err(Error::ExpectedToken {
- expected: '"',
+fn handle_preinclude(
+ shader_content: &str,
+ shader_path: &SpanPath<'_>,
+ preinclude_start_index: usize,
+ span_line_offset: usize,
+) -> Result<ReplacementJob, PreprocessingError>
+{
+ let expect_token = |token: char, index: usize| {
+ let token_found = shader_content.chars().nth(index).ok_or_else(|| {
+ PreprocessingError::ExpectedToken {
+ expected: token,
span: Span::new(
shader_content,
- &self.base_dir.join(shader_file_path),
- shader_content.len() - 1,
+ shader_path.to_owned(),
+ index,
span_line_offset,
preinclude_start_index,
),
- });
- }
-
- let path_len = buf.len();
+ }
+ })?;
- let path = PathBuf::from(String::from_utf8(buf).map_err(|err| {
- Error::PreincludePathInvalidUtf8 {
- source: err,
+ if token_found != token {
+ return Err(PreprocessingError::InvalidToken {
+ expected: token,
+ found: token_found,
span: Span::new(
shader_content,
- &self.base_dir.join(shader_file_path),
- quote_open_index + 1,
+ shader_path.to_owned(),
+ index,
span_line_offset,
preinclude_start_index,
),
- }
- })?);
+ });
+ }
- Ok(ReplacementJob {
- start_index: preinclude_start_index,
- end_index: quote_open_index + 1 + path_len + 1,
- path,
- })
+ Ok(())
+ };
+
+ let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len();
+ let quote_open_index = space_index + 1;
+
+ expect_token(' ', space_index)?;
+ expect_token('"', quote_open_index)?;
+
+ let buf = shader_content[quote_open_index + 1..]
+ .chars()
+ .take_while(|character| *character != '"')
+ .map(|character| character as u8)
+ .collect::<Vec<_>>();
+
+ if buf.is_empty() {
+ return Err(PreprocessingError::ExpectedToken {
+ expected: '"',
+ span: Span::new(
+ shader_content,
+ shader_path.to_owned(),
+ shader_content.len() - 1,
+ span_line_offset,
+ preinclude_start_index,
+ ),
+ });
}
+
+ let path_len = buf.len();
+
+ let path = PathBuf::from(String::from_utf8(buf).map_err(|err| {
+ PreprocessingError::PreincludePathInvalidUtf8 {
+ source: err,
+ span: Span::new(
+ shader_content,
+ shader_path.to_owned(),
+ quote_open_index + 1,
+ span_line_offset,
+ preinclude_start_index,
+ ),
+ }
+ })?);
+
+ Ok(ReplacementJob {
+ start_index: preinclude_start_index,
+ end_index: quote_open_index + 1 + path_len + 1,
+ path,
+ })
}
struct ReplacementJob
@@ -187,15 +185,14 @@ struct ReplacementJob
path: PathBuf,
}
-/// Shader preprocessing error.
#[derive(Debug, thiserror::Error)]
-pub enum Error
+pub enum PreprocessingError
{
#[error(
"Invalid token at line {}, column {} of {}. Expected '{}', found '{}'",
span.line,
span.column,
- span.file.display(),
+ span.path,
expected,
found
)]
@@ -211,7 +208,7 @@ pub enum Error
expected,
span.line,
span.column,
- span.file.display(),
+ span.path
)]
ExpectedToken
{
@@ -222,7 +219,7 @@ pub enum Error
"Preinclude path at line {}, column {} of {} is invalid UTF-8",
span.line,
span.column,
- span.file.display(),
+ span.path
)]
PreincludePathInvalidUtf8
{
@@ -249,18 +246,19 @@ pub enum Error
}
#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
pub struct Span
{
- line: usize,
- column: usize,
- file: PathBuf,
+ pub line: usize,
+ pub column: usize,
+ pub path: SpanPath<'static>,
}
impl Span
{
fn new(
file_content: &str,
- file_path: &Path,
+ path: SpanPath<'static>,
char_index: usize,
line_offset: usize,
line_start_index: usize,
@@ -272,7 +270,49 @@ impl Span
Self {
line,
column: char_index - line_start_index + 1,
- file: file_path.to_path_buf(),
+ path,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum SpanPath<'a>
+{
+ Original,
+ Path(Cow<'a, Path>),
+}
+
+impl<'a> SpanPath<'a>
+{
+ fn to_owned(&self) -> SpanPath<'static>
+ {
+ match self {
+ Self::Original => SpanPath::Original,
+ Self::Path(path) => SpanPath::Path(Cow::Owned(path.to_path_buf().into())),
+ }
+ }
+}
+
+impl<'a> Display for SpanPath<'a>
+{
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
+ {
+ match self {
+ Self::Original => write!(formatter, "original file"),
+ Self::Path(path) => write!(formatter, "file {}", path.display()),
+ }
+ }
+}
+
+impl<'a, PathLike> PartialEq<PathLike> for SpanPath<'a>
+where
+ PathLike: AsRef<Path>,
+{
+ fn eq(&self, other: &PathLike) -> bool
+ {
+ match self {
+ Self::Original => false,
+ Self::Path(path) => path == other.as_ref(),
}
}
}
@@ -290,21 +330,17 @@ fn find_line_of_index(text: &str, index: usize) -> usize
mod tests
{
use std::ffi::OsStr;
- use std::path::{Path, PathBuf};
+ use std::path::Path;
- use super::{Error, ShaderPreprocessor};
+ use super::{preprocess, PreprocessingError};
+ use crate::opengl::glsl::SpanPath;
#[test]
fn preprocess_no_directives_is_same()
{
assert_eq!(
- ShaderPreprocessor {
- read_file: |_| { unreachable!() },
- base_dir: PathBuf::new()
- }
- .preprocess("#version 330 core\n".to_string(), Path::new("foo.glsl"),)
- .unwrap(),
- "#version 330 core\n".to_string()
+ preprocess("#version 330 core\n", &|_| { unreachable!() }).unwrap(),
+ "#version 330 core\n"
);
}
@@ -312,20 +348,15 @@ mod tests
fn preprocess_with_directives_works()
{
assert_eq!(
- ShaderPreprocessor {
- read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) },
- base_dir: PathBuf::new()
- }
- .preprocess(
+ preprocess(
concat!(
"#version 330 core\n",
"\n",
"#preinclude \"foo.glsl\"\n",
"\n",
"void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
+ ),
+ &|_| { Ok(b"out vec4 FragColor;".to_vec()) }
)
.unwrap(),
concat!(
@@ -338,11 +369,7 @@ mod tests
);
assert_eq!(
- ShaderPreprocessor {
- read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) },
- base_dir: PathBuf::new()
- }
- .preprocess(
+ preprocess(
concat!(
"#version 330 core\n",
"\n",
@@ -351,9 +378,8 @@ mod tests
"in vec3 in_frag_color;\n",
"\n",
"void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
+ ),
+ &|_| { Ok(b"out vec4 FragColor;".to_vec()) }
)
.unwrap(),
concat!(
@@ -368,8 +394,19 @@ mod tests
);
assert_eq!(
- ShaderPreprocessor {
- read_file: |path| {
+ preprocess(
+ concat!(
+ "#version 330 core\n",
+ "\n",
+ "#preinclude \"bar.glsl\"\n",
+ "\n",
+ "in vec3 in_frag_color;\n",
+ "\n",
+ "#preinclude \"foo.glsl\"\n",
+ "\n",
+ "void main() {}",
+ ),
+ &|path| {
if path == OsStr::new("bar.glsl") {
Ok(b"out vec4 FragColor;".to_vec())
} else {
@@ -381,22 +418,6 @@ mod tests
.to_vec())
}
},
- base_dir: PathBuf::new()
- }
- .preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "#preinclude \"foo.glsl\"\n",
- "\n",
- "void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
)
.unwrap(),
concat!(
@@ -415,15 +436,9 @@ mod tests
}
#[test]
- fn preprocess_invalid_shader_does_not_work()
+ fn preprocess_invalid_directive_does_not_work()
{
- let path = Path::new("foo.glsl");
-
- let res = ShaderPreprocessor {
- read_file: |_| Ok(b"out vec4 FragColor;".to_vec()),
- base_dir: PathBuf::new(),
- }
- .preprocess(
+ let res = preprocess(
concat!(
"#version 330 core\n",
"\n",
@@ -431,12 +446,11 @@ mod tests
"#preinclude foo.glsl\"\n",
"\n",
"void main() {}",
- )
- .to_string(),
- path,
+ ),
+ &|_| Ok(b"out vec4 FragColor;".to_vec()),
);
- let Err(Error::InvalidToken { expected, found, span }) = res else {
+ let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
panic!(
"Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
);
@@ -446,16 +460,25 @@ mod tests
assert_eq!(found, 'f');
assert_eq!(span.line, 3);
assert_eq!(span.column, 13);
- assert_eq!(span.file, path);
+ assert_eq!(span.path, SpanPath::Original);
}
#[test]
fn preprocess_error_has_correct_span()
{
- let path = Path::new("foo.glsl");
-
- let res = ShaderPreprocessor {
- read_file: |path| {
+ let res = preprocess(
+ concat!(
+ "#version 330 core\n",
+ "\n",
+ "#preinclude \"bar.glsl\"\n",
+ "\n",
+ "#preinclude \"foo.glsl\"\n",
+ "\n",
+ "in vec3 in_frag_color;\n",
+ "\n",
+ "void main() {}",
+ ),
+ &|path| {
if path == OsStr::new("bar.glsl") {
Ok(concat!(
"out vec4 FragColor;\n",
@@ -464,29 +487,25 @@ mod tests
)
.as_bytes()
.to_vec())
+ } else if path == OsStr::new("foo.glsl") {
+ Ok(concat!(
+ "uniform sampler2D input_texture;\n",
+ "\n",
+ // Missing space before first "
+ "#preinclude\"shared_types.glsl\"\n",
+ )
+ .as_bytes()
+ .to_vec())
} else {
- Ok(b"uniform sampler2D input_texture;".to_vec())
+ panic!(concat!(
+ "Expected read function to be called with ",
+ "either path bar.glsl or foo.glsl"
+ ));
}
},
- base_dir: PathBuf::new(),
- }
- .preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "#preinclude\"foo.glsl\"\n",
- "\n",
- "void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
);
- let Err(Error::InvalidToken { expected, found, span }) = res else {
+ let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
panic!(
"Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
);
@@ -494,17 +513,26 @@ mod tests
assert_eq!(expected, ' ');
assert_eq!(found, '"');
- assert_eq!(span.line, 7);
+ assert_eq!(span.line, 3);
assert_eq!(span.column, 12);
- assert_eq!(span.file, path);
+ assert_eq!(span.path, SpanPath::Path(Path::new("foo.glsl").into()));
}
#[test]
fn preprocess_included_shader_with_include_works()
{
assert_eq!(
- ShaderPreprocessor {
- read_file: |path| {
+ preprocess(
+ concat!(
+ "#version 330 core\n",
+ "\n",
+ "#preinclude \"bar.glsl\"\n",
+ "\n",
+ "in vec3 in_frag_color;\n",
+ "\n",
+ "void main() {}",
+ ),
+ &|path| {
if path == OsStr::new("bar.glsl") {
Ok(concat!(
"#preinclude \"foo.glsl\"\n",
@@ -521,21 +549,7 @@ mod tests
.as_bytes()
.to_vec())
}
- },
- base_dir: PathBuf::new()
- }
- .preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
+ }
)
.unwrap(),
concat!(
@@ -556,8 +570,17 @@ mod tests
#[test]
fn preprocess_included_shader_with_include_error_span_is_correct()
{
- let res = ShaderPreprocessor {
- read_file: |path| {
+ let res = preprocess(
+ concat!(
+ "#version 330 core\n",
+ "\n",
+ "#preinclude \"bar.glsl\"\n",
+ "\n",
+ "in vec3 in_frag_color;\n",
+ "\n",
+ "void main() {}",
+ ),
+ &|path| {
if path == OsStr::new("bar.glsl") {
Ok(concat!(
// ' instead of "
@@ -576,23 +599,9 @@ mod tests
.to_vec())
}
},
- base_dir: PathBuf::new(),
- }
- .preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- )
- .to_string(),
- Path::new("foo.glsl"),
);
- let Err(Error::InvalidToken { expected, found, span }) = res else {
+ let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
panic!(
"Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
);
@@ -602,6 +611,6 @@ mod tests
assert_eq!(found, '\'');
assert_eq!(span.line, 1);
assert_eq!(span.column, 13);
- assert_eq!(span.file, Path::new("bar.glsl"));
+ assert_eq!(span.path, Path::new("bar.glsl"));
}
}
diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs
index 0b1bb8a..53e0120 100644
--- a/engine/src/opengl/mod.rs
+++ b/engine/src/opengl/mod.rs
@@ -1,16 +1,17 @@
use bitflags::bitflags;
+use gl::types::GLint;
use crate::data_types::dimens::Dimens;
use crate::vector::Vec2;
pub mod buffer;
+pub mod glsl;
pub mod shader;
pub mod texture;
pub mod vertex_array;
mod util;
-#[cfg(feature = "debug")]
pub mod debug;
pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
@@ -47,6 +48,17 @@ pub fn enable(capacity: Capability)
}
}
+pub fn get_context_flags() -> ContextFlags
+{
+ let mut context_flags: GLint = 0;
+
+ unsafe {
+ gl::GetIntegerv(gl::CONTEXT_FLAGS as u32, &mut context_flags);
+ }
+
+ ContextFlags::from_bits_truncate(context_flags as u32)
+}
+
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct BufferClearMask: u32 {
@@ -105,3 +117,12 @@ impl From<crate::draw_flags::PolygonModeFace> for PolygonModeFace
}
}
}
+
+bitflags! {
+#[derive(Debug, Clone, Copy)]
+pub struct ContextFlags: u32 {
+ const FORWARD_COMPATIBLE = gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;
+ const DEBUG = gl::CONTEXT_FLAG_DEBUG_BIT;
+ const ROBUST_ACCESS = gl::CONTEXT_FLAG_ROBUST_ACCESS_BIT;
+}
+}
diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs
index 070897e..a626fc7 100644
--- a/engine/src/opengl/shader.rs
+++ b/engine/src/opengl/shader.rs
@@ -2,7 +2,6 @@ use std::ffi::CStr;
use std::ptr::null_mut;
use crate::matrix::Matrix;
-use crate::shader::Kind;
use crate::vector::Vec3;
#[derive(Debug)]
@@ -20,7 +19,7 @@ impl Shader
Self { shader }
}
- pub fn set_source(&self, source: &str) -> Result<(), Error>
+ pub fn set_source(&mut self, source: &str) -> Result<(), Error>
{
if !source.is_ascii() {
return Err(Error::SourceNotAscii);
@@ -39,7 +38,7 @@ impl Shader
Ok(())
}
- pub fn compile(&self) -> Result<(), Error>
+ pub fn compile(&mut self) -> Result<(), Error>
{
unsafe {
gl::CompileShader(self.shader);
@@ -90,6 +89,14 @@ impl Drop for Shader
}
}
+/// Shader kind.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Kind
+{
+ Vertex,
+ Fragment,
+}
+
impl Kind
{
fn into_gl(self) -> gl::types::GLenum
@@ -117,14 +124,14 @@ impl Program
Self { program }
}
- pub fn attach(&self, shader: &Shader)
+ pub fn attach(&mut self, shader: &Shader)
{
unsafe {
gl::AttachShader(self.program, shader.shader);
}
}
- pub fn link(&self) -> Result<(), Error>
+ pub fn link(&mut self) -> Result<(), Error>
{
unsafe {
gl::LinkProgram(self.program);
@@ -152,82 +159,105 @@ impl Program
}
}
- pub fn set_uniform_matrix_4fv(&mut self, name: &CStr, matrix: &Matrix<f32, 4, 4>)
+ pub fn set_uniform(&mut self, name: &CStr, var: &impl UniformVariable)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
+ let location = UniformLocation(unsafe {
+ gl::GetUniformLocation(self.program, name.as_ptr().cast())
+ });
+
+ var.set(self, location);
+ }
+
+ fn get_info_log(&self) -> String
+ {
+ let mut buf = vec![gl::types::GLchar::default(); 512];
unsafe {
- gl::ProgramUniformMatrix4fv(
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::GetProgramInfoLog(
self.program,
- uniform_location,
- 1,
- gl::FALSE,
- matrix.as_ptr(),
+ buf.len() as gl::types::GLsizei,
+ null_mut(),
+ buf.as_mut_ptr(),
);
}
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
}
+}
- pub fn set_uniform_vec_3fv(&mut self, name: &CStr, vec: &Vec3<f32>)
+impl Drop for Program
+{
+ fn drop(&mut self)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
-
unsafe {
- gl::ProgramUniform3fv(self.program, uniform_location, 1, vec.as_ptr());
+ gl::DeleteProgram(self.program);
}
}
+}
- pub fn set_uniform_1fv(&mut self, name: &CStr, num: f32)
- {
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
+pub trait UniformVariable
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation);
+}
+impl UniformVariable for f32
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
+ {
unsafe {
- gl::ProgramUniform1fv(self.program, uniform_location, 1, &num);
+ gl::ProgramUniform1f(program.program, uniform_location.0, *self);
}
}
+}
- pub fn set_uniform_1i(&mut self, name: &CStr, num: i32)
+impl UniformVariable for i32
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
-
unsafe {
- gl::ProgramUniform1i(self.program, uniform_location, num);
+ gl::ProgramUniform1i(program.program, uniform_location.0, *self);
}
}
+}
- fn get_info_log(&self) -> String
+impl UniformVariable for Vec3<f32>
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
- let mut buf = vec![gl::types::GLchar::default(); 512];
-
unsafe {
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl::GetProgramInfoLog(
- self.program,
- buf.len() as gl::types::GLsizei,
- null_mut(),
- buf.as_mut_ptr(),
+ gl::ProgramUniform3f(
+ program.program,
+ uniform_location.0,
+ self.x,
+ self.y,
+ self.z,
);
}
-
- let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
-
- unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
}
}
-impl Drop for Program
+impl UniformVariable for Matrix<f32, 4, 4>
{
- fn drop(&mut self)
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
unsafe {
- gl::DeleteProgram(self.program);
+ gl::ProgramUniformMatrix4fv(
+ program.program,
+ uniform_location.0,
+ 1,
+ gl::FALSE,
+ self.as_ptr(),
+ );
}
}
}
+#[derive(Debug)]
+pub struct UniformLocation(gl::types::GLint);
+
/// Shader error.
#[derive(Debug, thiserror::Error)]
pub enum Error
diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs
index 074ade7..80a5f37 100644
--- a/engine/src/opengl/texture.rs
+++ b/engine/src/opengl/texture.rs
@@ -1,5 +1,4 @@
use crate::data_types::dimens::Dimens;
-use crate::texture::{Id, Properties};
#[derive(Debug)]
pub struct Texture
@@ -20,10 +19,10 @@ impl Texture
Self { texture }
}
- pub fn bind(&self)
+ pub fn bind_to_texture_unit(&self, texture_unit: u32)
{
unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.texture);
+ gl::BindTextureUnit(texture_unit, self.texture);
}
}
@@ -41,16 +40,9 @@ impl Texture
}
}
- pub fn apply_properties(&mut self, properties: &Properties)
- {
- self.set_wrap(properties.wrap);
- self.set_magnifying_filter(properties.magnifying_filter);
- self.set_minifying_filter(properties.minifying_filter);
- }
-
pub fn set_wrap(&mut self, wrapping: Wrapping)
{
- let wrapping_gl = wrapping.to_gl();
+ let wrapping_gl = wrapping as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -61,7 +53,7 @@ impl Texture
pub fn set_magnifying_filter(&mut self, filtering: Filtering)
{
- let filtering_gl = filtering.to_gl();
+ let filtering_gl = filtering as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -75,7 +67,7 @@ impl Texture
pub fn set_minifying_filter(&mut self, filtering: Filtering)
{
- let filtering_gl = filtering.to_gl();
+ let filtering_gl = filtering as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -132,43 +124,21 @@ impl Drop for Texture
/// Texture wrapping.
#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
pub enum Wrapping
{
- Repeat,
- MirroredRepeat,
- ClampToEdge,
- ClampToBorder,
-}
-
-impl Wrapping
-{
- fn to_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Repeat => gl::REPEAT,
- Self::MirroredRepeat => gl::MIRRORED_REPEAT,
- Self::ClampToEdge => gl::CLAMP_TO_EDGE,
- Self::ClampToBorder => gl::CLAMP_TO_BORDER,
- }
- }
+ Repeat = gl::REPEAT,
+ MirroredRepeat = gl::MIRRORED_REPEAT,
+ ClampToEdge = gl::CLAMP_TO_EDGE,
+ ClampToBorder = gl::CLAMP_TO_BORDER,
}
#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
pub enum Filtering
{
- Nearest,
- Linear,
-}
-
-impl Filtering
-{
- fn to_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Linear => gl::LINEAR,
- Self::Nearest => gl::NEAREST,
- }
- }
+ Nearest = gl::NEAREST,
+ Linear = gl::LINEAR,
}
/// Texture pixel data format.
@@ -197,44 +167,3 @@ impl PixelDataFormat
}
}
}
-
-pub fn set_active_texture_unit(texture_unit: TextureUnit)
-{
- unsafe {
- gl::ActiveTexture(texture_unit.into_gl());
- }
-}
-
-macro_rules! texture_unit_enum {
- (cnt=$cnt: literal) => {
- seq_macro::seq!(N in 0..$cnt {
- #[derive(Debug, Clone, Copy)]
- pub enum TextureUnit {
- #(
- No~N,
- )*
- }
-
- impl TextureUnit {
- fn into_gl(self) -> gl::types::GLenum {
- match self {
- #(
- Self::No~N => gl::TEXTURE~N,
- )*
- }
- }
-
- pub fn from_texture_id(texture_id: Id) -> Option<Self> {
- match texture_id.into_inner() {
- #(
- N => Some(Self::No~N),
- )*
- _ => None
- }
- }
- }
- });
- };
-}
-
-texture_unit_enum!(cnt = 31);
diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs
index da5d91e..1f8a870 100644
--- a/engine/src/opengl/vertex_array.rs
+++ b/engine/src/opengl/vertex_array.rs
@@ -1,10 +1,6 @@
use std::mem::size_of;
use crate::opengl::buffer::Buffer;
-use crate::vertex::Vertex;
-
-#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
-const VERTEX_STRIDE: i32 = size_of::<Vertex>() as i32;
#[derive(Debug)]
pub struct VertexArray
@@ -59,10 +55,10 @@ impl VertexArray
}
}
- pub fn bind_vertex_buffer(
+ pub fn bind_vertex_buffer<VertexT>(
&mut self,
binding_index: u32,
- vertex_buffer: &Buffer<Vertex>,
+ vertex_buffer: &Buffer<VertexT>,
offset: isize,
)
{
@@ -72,7 +68,7 @@ impl VertexArray
binding_index,
vertex_buffer.object(),
offset,
- VERTEX_STRIDE,
+ size_of::<VertexT>() as i32,
);
}
}
@@ -125,27 +121,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
-{
- fn drop(&mut self)
- {
- unsafe {
- gl::DeleteVertexArrays(1, &self.array);
- }
- }
}
#[derive(Debug)]
diff --git a/engine/src/performance.rs b/engine/src/performance.rs
deleted file mode 100644
index ffc5c27..0000000
--- a/engine/src/performance.rs
+++ /dev/null
@@ -1,59 +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(),)),
- );
- }
-}
-
-#[cfg(feature = "debug")]
-macro_rules! log_perf {
- ($($tt: tt)*) => {
- tracing::info!($($tt)*);
- };
-}
-
-#[cfg(not(feature = "debug"))]
-macro_rules! log_perf {
- ($($tt: tt)*) => {
- println!($($tt)*);
- };
-}
-
-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);
-
- log_perf!(
- "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..115ca39 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::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.rs b/engine/src/renderer.rs
index 2544919..17bc925 100644
--- a/engine/src/renderer.rs
+++ b/engine/src/renderer.rs
@@ -1 +1,7 @@
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
+use ecs::static_entity;
+
pub mod opengl;
+
+static_entity!(pub RENDER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE)));
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index a353c6a..cfd046f 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -2,75 +2,109 @@
use std::collections::HashMap;
use std::ffi::{c_void, CString};
-use std::ops::Deref;
+use std::io::{Error as IoError, ErrorKind as IoErrorKind};
+use std::path::Path;
use std::process::abort;
use ecs::actions::Actions;
use ecs::component::local::Local;
-use ecs::query::options::{Not, With};
+use ecs::component::Handle as ComponentHandle;
+use ecs::phase::START as START_PHASE;
+use ecs::query::term::Without;
use ecs::sole::Single;
use ecs::system::{Into as _, System};
use ecs::{Component, Query};
+use crate::asset::{Assets, Id as AssetId};
use crate::camera::{Active as ActiveCamera, Camera};
use crate::color::Color;
use crate::data_types::dimens::Dimens;
use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig};
-use crate::event::{Present as PresentEvent, Start as StartEvent};
+use crate::image::{ColorType as ImageColorType, Image};
use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
use crate::material::{Flags as MaterialFlags, Material};
use crate::matrix::Matrix;
use crate::mesh::Mesh;
+use crate::model::Model;
use crate::opengl::buffer::{Buffer, Usage as BufferUsage};
-#[cfg(feature = "debug")]
-use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType};
+use crate::opengl::debug::{
+ enable_debug_output,
+ set_debug_message_callback,
+ set_debug_message_control,
+ MessageIdsAction,
+ MessageSeverity,
+ MessageSource,
+ MessageType,
+};
+use crate::opengl::glsl::{
+ preprocess as glsl_preprocess,
+ PreprocessingError as GlslPreprocessingError,
+};
use crate::opengl::shader::{
Error as GlShaderError,
+ Kind as ShaderKind,
Program as GlShaderProgram,
Shader as GlShader,
};
use crate::opengl::texture::{
- set_active_texture_unit,
+ Filtering as GlTextureFiltering,
+ PixelDataFormat as GlTexturePixelDataFormat,
Texture as GlTexture,
- TextureUnit,
+ Wrapping as GlTextureWrapping,
};
use crate::opengl::vertex_array::{
DataType as VertexArrayDataType,
PrimitiveKind,
VertexArray,
};
-use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability};
-use crate::projection::{new_perspective_matrix, Projection};
-use crate::shader::Program as ShaderProgram;
-use crate::texture::{Id as TextureId, Texture};
-use crate::transform::{Position, Scale};
-use crate::util::NeverDrop;
+use crate::opengl::{
+ clear_buffers,
+ enable,
+ get_context_flags as get_opengl_context_flags,
+ BufferClearMask,
+ Capability,
+ ContextFlags,
+};
+use crate::projection::{ClipVolume, Projection};
+use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex};
+use crate::renderer::RENDER_PHASE;
+use crate::texture::{
+ Filtering as TextureFiltering,
+ Properties as TextureProperties,
+ Wrapping as TextureWrapping,
+};
+use crate::transform::{Scale, WorldPosition};
+use crate::util::{defer, Defer, RefOrValue};
use crate::vector::{Vec2, Vec3};
-use crate::vertex::{AttributeComponentType, Vertex};
use crate::window::Window;
-type RenderableEntity = (
- Mesh,
- ShaderProgram,
- Material,
- Option<MaterialFlags>,
- Option<Position>,
- Option<Scale>,
- Option<DrawFlags>,
- Option<GlObjects>,
+mod vertex;
+
+const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0;
+const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1;
+const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2;
+
+type RenderableEntity<'a> = (
+ &'a Model,
+ Option<&'a MaterialFlags>,
+ Option<&'a WorldPosition>,
+ Option<&'a Scale>,
+ Option<&'a DrawFlags>,
+ Option<&'a GlObjects>,
);
#[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(StartEvent, initialize);
+ collector.add_system(*START_PHASE, initialize);
collector.add_system(
- PresentEvent,
+ *RENDER_PHASE,
render
.into_system()
.initialize((GlobalGlObjects::default(),)),
@@ -95,8 +129,9 @@ fn initialize(window: Single<Window>)
}
});
- #[cfg(feature = "debug")]
- initialize_debug();
+ if get_opengl_context_flags().contains(ContextFlags::DEBUG) {
+ initialize_debug();
+ }
let window_size = window.size().expect("Failed to get window size");
@@ -110,78 +145,63 @@ fn initialize(window: Single<Window>)
enable(Capability::MultiSample);
}
+#[tracing::instrument(skip_all)]
#[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<'_>, (Without<NoDraw>,)>,
+ point_light_query: Query<(&PointLight, &WorldPosition)>,
+ directional_lights: Query<(&DirectionalLight,)>,
+ camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
window: Single<Window>,
global_light: Single<GlobalLight>,
+ assets: Single<Assets>,
mut gl_objects: Local<GlobalGlObjects>,
mut actions: Actions,
)
{
- let Some((camera, camera_pos, _)) = camera_query.iter().next() else {
- #[cfg(feature = "debug")]
+ let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else {
tracing::warn!("No current camera. Nothing will be rendered");
return;
};
- let point_lights = point_light_query
- .iter()
- .map(|(point_light,)| point_light)
- .collect::<Vec<_>>();
-
let directional_lights = directional_lights.iter().collect::<Vec<_>>();
let GlobalGlObjects {
- shader_programs: gl_shader_programs,
+ shader_program,
textures: gl_textures,
+ default_1x1_texture: default_1x1_gl_texture,
} = &mut *gl_objects;
+ let shader_program =
+ shader_program.get_or_insert_with(|| create_default_shader_program().unwrap());
+
clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH);
- for (
- entity_index,
- (
- mesh,
- shader_program,
- material,
- material_flags,
- position,
- scale,
- draw_flags,
- gl_objects,
- ),
- ) in query.iter().enumerate()
+ 'subject_loop: for (
+ euid,
+ (model, material_flags, position, scale, draw_flags, gl_objects),
+ ) in query.iter_with_euids()
{
+ let Some(model_data) = assets.get(&model.asset_handle) else {
+ tracing::trace!("Missing model asset");
+ continue;
+ };
+
let material_flags = material_flags
.map(|material_flags| material_flags.clone())
.unwrap_or_default();
- let shader_program = gl_shader_programs
- .entry(shader_program.u64_hash())
- .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap());
-
- 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(&model_data.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,
@@ -189,37 +209,82 @@ fn render(
},
shader_program,
&camera,
- &camera_pos,
+ &camera_world_pos,
window.size().expect("Failed to get window size"),
);
+ if model_data.materials.len() > 1 {
+ tracing::warn!(concat!(
+ "Multiple model materials are not supported ",
+ "so only the first material will be used"
+ ));
+ }
+
+ let material = match model_data.materials.values().next() {
+ Some(material) => material,
+ None => {
+ tracing::warn!("Model has no materials. Using default material");
+
+ &Material::default()
+ }
+ };
+
apply_light(
&material,
&material_flags,
&global_light,
shader_program,
- point_lights.as_slice(),
+ (point_light_query.iter(), point_light_query.iter().count()),
directional_lights
.iter()
.map(|(dir_light,)| &**dir_light)
.collect::<Vec<_>>()
.as_slice(),
- &camera_pos,
+ &camera_world_pos,
);
- for texture in &material.textures {
- let gl_texture = gl_textures
- .entry(texture.id())
- .or_insert_with(|| create_gl_texture(texture));
-
- let texture_unit =
- TextureUnit::from_texture_id(texture.id()).unwrap_or_else(|| {
- panic!("Texture id {} is a invalid texture unit", texture.id());
+ let material_texture_maps = [
+ (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT),
+ (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT),
+ (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT),
+ ];
+
+ for (texture, texture_unit) in material_texture_maps {
+ let Some(texture) = texture else {
+ let gl_texture = default_1x1_gl_texture.get_or_insert_with(|| {
+ create_gl_texture(
+ &Image::from_color(1, Color::WHITE_U8),
+ &TextureProperties::default(),
+ )
});
- set_active_texture_unit(texture_unit);
+ gl_texture.bind_to_texture_unit(texture_unit);
- gl_texture.bind();
+ continue;
+ };
+
+ let texture_image_asset_id = texture.asset_handle.id();
+
+ let gl_texture = match gl_textures.get(&texture_image_asset_id) {
+ Some(gl_texture) => gl_texture,
+ None => {
+ let Some(image) = assets.get::<Image>(&texture.asset_handle) else {
+ tracing::trace!("Missing texture asset");
+ continue 'subject_loop;
+ };
+
+ gl_textures.insert(
+ texture_image_asset_id,
+ create_gl_texture(image, &texture.properties),
+ );
+
+ gl_textures
+ .get(&texture.asset_handle.id())
+ .expect("Not possible")
+ }
+ };
+
+ gl_texture.bind_to_texture_unit(texture_unit);
}
shader_program.activate();
@@ -231,7 +296,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();
@@ -247,8 +312,9 @@ fn render(
#[derive(Debug, Default, Component)]
struct GlobalGlObjects
{
- shader_programs: HashMap<u64, GlShaderProgram>,
- textures: HashMap<TextureId, GlTexture>,
+ shader_program: Option<GlShaderProgram>,
+ textures: HashMap<AssetId, GlTexture>,
+ default_1x1_texture: Option<GlTexture>,
}
fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
@@ -256,20 +322,11 @@ fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
crate::opengl::set_viewport(position, size);
}
-#[cfg(feature = "debug")]
fn initialize_debug()
{
- use crate::opengl::debug::{
- enable_debug_output,
- set_debug_message_callback,
- set_debug_message_control,
- MessageIdsAction,
- };
-
enable_debug_output();
set_debug_message_callback(opengl_debug_message_cb);
-
set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable);
}
@@ -278,72 +335,119 @@ 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);
}
}
-fn create_gl_texture(texture: &Texture) -> GlTexture
+fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> GlTexture
{
let mut gl_texture = GlTexture::new();
gl_texture.generate(
- *texture.dimensions(),
- texture.image().as_bytes(),
- texture.pixel_data_format(),
+ image.dimensions(),
+ image.as_bytes(),
+ match image.color_type() {
+ ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8,
+ ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8,
+ _ => {
+ unimplemented!();
+ }
+ },
);
- gl_texture.apply_properties(texture.properties());
+ gl_texture.set_wrap(texture_wrapping_to_gl(texture_properties.wrap));
+
+ gl_texture.set_magnifying_filter(texture_filtering_to_gl(
+ texture_properties.magnifying_filter,
+ ));
+
+ gl_texture.set_minifying_filter(texture_filtering_to_gl(
+ texture_properties.minifying_filter,
+ ));
gl_texture
}
-fn create_gl_shader_program(
- shader_program: &ShaderProgram,
-) -> Result<GlShaderProgram, GlShaderError>
+const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl");
+const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl");
+
+const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl");
+const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl");
+
+fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError>
{
- let gl_shaders = shader_program
- .shaders()
- .iter()
- .map(|shader| {
- let gl_shader = GlShader::new(shader.kind());
+ let mut vertex_shader = GlShader::new(ShaderKind::Vertex);
- gl_shader.set_source(shader.source())?;
- gl_shader.compile()?;
+ vertex_shader.set_source(&*glsl_preprocess(
+ VERTEX_GLSL_SHADER_SRC,
+ &get_glsl_shader_content,
+ )?)?;
- Ok(gl_shader)
- })
- .collect::<Result<Vec<_>, _>>()?;
+ vertex_shader.compile()?;
- let gl_shader_program = GlShaderProgram::new();
+ let mut fragment_shader = GlShader::new(ShaderKind::Fragment);
- for gl_shader in &gl_shaders {
- gl_shader_program.attach(gl_shader);
- }
+ fragment_shader.set_source(&*glsl_preprocess(
+ FRAGMENT_GLSL_SHADER_SRC,
+ &get_glsl_shader_content,
+ )?)?;
+
+ fragment_shader.compile()?;
+
+ let mut gl_shader_program = GlShaderProgram::new();
+
+ gl_shader_program.attach(&vertex_shader);
+ gl_shader_program.attach(&fragment_shader);
gl_shader_program.link()?;
Ok(gl_shader_program)
}
+#[derive(Debug, thiserror::Error)]
+enum CreateShaderError
+{
+ #[error(transparent)]
+ ShaderError(#[from] GlShaderError),
+
+ #[error(transparent)]
+ PreprocessingError(#[from] GlslPreprocessingError),
+}
+
+fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error>
+{
+ if path == Path::new("vertex_data.glsl") {
+ return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec());
+ }
+
+ if path == Path::new("light.glsl") {
+ return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec());
+ }
+
+ Err(IoError::new(
+ IoErrorKind::NotFound,
+ format!("Content for shader file {} not found", path.display()),
+ ))
+}
+
#[derive(Debug, Component)]
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,
}
impl GlObjects
{
- #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
+ #[tracing::instrument(skip_all)]
fn new(mesh: &Mesh) -> Self
{
- #[cfg(feature = "debug")]
tracing::trace!(
"Creating vertex array, vertex buffer{}",
if mesh.indices().is_some() {
@@ -356,7 +460,13 @@ impl GlObjects
let mut vertex_arr = VertexArray::new();
let mut vertex_buffer = Buffer::new();
- vertex_buffer.store(mesh.vertices(), BufferUsage::Static);
+ vertex_buffer.store_mapped(mesh.vertices(), BufferUsage::Static, |vertex| {
+ Vertex {
+ pos: vertex.pos,
+ texture_coords: vertex.texture_coords,
+ normal: vertex.normal,
+ }
+ });
vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0);
@@ -387,78 +497,77 @@ 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(
transformation: Transformation,
gl_shader_program: &mut GlShaderProgram,
camera: &Camera,
- camera_pos: &Position,
+ camera_world_pos: &WorldPosition,
window_size: Dimens<u32>,
)
{
gl_shader_program
- .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation));
+ .set_uniform(c"model", &create_transformation_matrix(transformation));
- let view = create_view(camera, camera_pos);
+ let view_matrix = create_view_matrix(camera, &camera_world_pos.position);
- gl_shader_program.set_uniform_matrix_4fv(c"view", &view);
+ gl_shader_program.set_uniform(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_world_pos.position, ClipVolume::NegOneToOne),
};
- gl_shader_program.set_uniform_matrix_4fv(c"projection", &projection);
+ gl_shader_program.set_uniform(c"projection", &proj_matrix);
}
-fn apply_light<PointLightHolder>(
+fn apply_light<'point_light>(
material: &Material,
material_flags: &MaterialFlags,
global_light: &GlobalLight,
gl_shader_program: &mut GlShaderProgram,
- point_lights: &[PointLightHolder],
+ (point_light_iter, point_light_cnt): (
+ impl Iterator<
+ Item = (
+ ComponentHandle<'point_light, PointLight>,
+ ComponentHandle<'point_light, WorldPosition>,
+ ),
+ >,
+ usize,
+ ),
directional_lights: &[&DirectionalLight],
- camera_pos: &Position,
-) where
- PointLightHolder: Deref<Target = PointLight>,
+ camera_world_pos: &WorldPosition,
+)
{
debug_assert!(
- point_lights.len() < 64,
+ point_light_cnt < 64,
"Shader cannot handle more than 64 point lights"
);
@@ -468,7 +577,7 @@ fn apply_light<PointLightHolder>(
);
for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
"directional_lights",
dir_light_index,
@@ -488,71 +597,68 @@ fn apply_light<PointLightHolder>(
// There probably won't be more than 2147483648 directional lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
gl_shader_program
- .set_uniform_1i(c"directional_light_cnt", directional_lights.len() as i32);
+ .set_uniform(c"directional_light_cnt", &(directional_lights.len() as i32));
- for (point_light_index, point_light) in point_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
+ for (point_light_index, (point_light, point_light_world_pos)) in
+ point_light_iter.enumerate()
+ {
+ gl_shader_program.set_uniform(
&create_light_uniform_name("point_lights", point_light_index, "position"),
- &point_light.position,
+ &(point_light_world_pos.position + point_light.local_position),
);
set_light_phong_uniforms(
gl_shader_program,
"point_lights",
point_light_index,
- &**point_light,
+ &*point_light,
);
set_light_attenuation_uniforms(
gl_shader_program,
"point_lights",
point_light_index,
- point_light,
+ &*point_light,
);
}
// There probably won't be more than 2147483648 point lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform_1i(c"point_light_cnt", point_lights.len() as i32);
+ gl_shader_program.set_uniform(c"point_light_cnt", &(point_light_cnt as i32));
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
c"material.ambient",
- &if material_flags.use_ambient_color {
+ &Vec3::from(if material_flags.use_ambient_color {
material.ambient.clone()
} else {
global_light.ambient.clone()
- }
- .into(),
+ }),
);
gl_shader_program
- .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into());
+ .set_uniform(c"material.diffuse", &Vec3::from(material.diffuse.clone()));
#[allow(clippy::cast_possible_wrap)]
gl_shader_program
- .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into());
+ .set_uniform(c"material.specular", &Vec3::from(material.specular.clone()));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.ambient_map",
- material.ambient_map.into_inner() as i32,
- );
+ gl_shader_program
+ .set_uniform(c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.diffuse_map",
- material.diffuse_map.into_inner() as i32,
- );
+ gl_shader_program
+ .set_uniform(c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
+ gl_shader_program.set_uniform(
c"material.specular_map",
- material.specular_map.into_inner() as i32,
+ &(SPECULAR_MAP_TEXTURE_UNIT as i32),
);
- gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess);
+ gl_shader_program.set_uniform(c"material.shininess", &material.shininess);
- gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position);
+ gl_shader_program.set_uniform(c"view_pos", &camera_world_pos.position);
}
fn set_light_attenuation_uniforms(
@@ -562,27 +668,27 @@ fn set_light_attenuation_uniforms(
light: &PointLight,
)
{
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
light_array,
light_index,
"attenuation_props.constant",
),
- light.attenuation_params.constant,
+ &light.attenuation_params.constant,
);
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
- light.attenuation_params.linear,
+ &light.attenuation_params.linear,
);
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
light_array,
light_index,
"attenuation_props.quadratic",
),
- light.attenuation_params.quadratic,
+ &light.attenuation_params.quadratic,
);
}
@@ -593,14 +699,14 @@ fn set_light_phong_uniforms(
light: &impl Light,
)
{
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "phong.diffuse"),
- &light.diffuse().clone().into(),
+ &Vec3::from(light.diffuse().clone()),
);
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "phong.specular"),
- &light.specular().clone().into(),
+ &Vec3::from(light.specular().clone()),
);
}
@@ -649,16 +755,15 @@ 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
}
-#[cfg(feature = "debug")]
#[tracing::instrument(skip_all)]
fn opengl_debug_message_cb(
source: MessageSource,
@@ -717,3 +822,23 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4
matrix
}
+
+#[inline]
+fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping
+{
+ match texture_wrapping {
+ TextureWrapping::Repeat => GlTextureWrapping::Repeat,
+ TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat,
+ TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge,
+ TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder,
+ }
+}
+
+#[inline]
+fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering
+{
+ match texture_filtering {
+ TextureFiltering::Linear => GlTextureFiltering::Linear,
+ TextureFiltering::Nearest => GlTextureFiltering::Nearest,
+ }
+}
diff --git a/engine/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl
index 5bf5ff2..5bf5ff2 100644
--- a/engine/fragment.glsl
+++ b/engine/src/renderer/opengl/glsl/fragment.glsl
diff --git a/engine/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl
index 1bc23a4..f12b5fe 100644
--- a/engine/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/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl
index b57caa6..b57caa6 100644
--- a/engine/vertex.glsl
+++ b/engine/src/renderer/opengl/glsl/vertex.glsl
diff --git a/engine/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl
index 486d445..486d445 100644
--- a/engine/vertex_data.glsl
+++ b/engine/src/renderer/opengl/glsl/vertex_data.glsl
diff --git a/engine/src/vertex.rs b/engine/src/renderer/opengl/vertex.rs
index 897ee97..499b94b 100644
--- a/engine/src/vertex.rs
+++ b/engine/src/renderer/opengl/vertex.rs
@@ -1,23 +1,17 @@
-use std::mem::size_of;
-
-use crate::util::builder;
use crate::vector::{Vec2, Vec3};
-builder! {
-#[builder(name = Builder, derives = (Debug, Default))]
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
#[repr(C)]
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>,
}
impl Vertex
{
- pub(crate) fn attrs() -> &'static [Attribute]
+ pub fn attrs() -> &'static [Attribute]
{
#[allow(clippy::cast_possible_truncation)]
&[
@@ -43,15 +37,17 @@ impl Vertex
}
}
-pub(crate) struct Attribute
+#[derive(Debug)]
+pub struct Attribute
{
- pub(crate) index: u32,
- pub(crate) component_type: AttributeComponentType,
- pub(crate) component_cnt: AttributeComponentCnt,
- pub(crate) component_size: u32,
+ pub index: u32,
+ pub component_type: AttributeComponentType,
+ pub component_cnt: AttributeComponentCnt,
+ pub component_size: u32,
}
-pub(crate) enum AttributeComponentType
+#[derive(Debug)]
+pub enum AttributeComponentType
{
Float,
}
@@ -59,7 +55,7 @@ pub(crate) enum AttributeComponentType
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
#[allow(dead_code)]
-pub(crate) enum AttributeComponentCnt
+pub enum AttributeComponentCnt
{
One = 1,
Two = 2,
diff --git a/engine/src/shader.rs b/engine/src/shader.rs
deleted file mode 100644
index 89f7b7c..0000000
--- a/engine/src/shader.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use std::collections::hash_map::DefaultHasher;
-use std::fs::read_to_string;
-use std::hash::{Hash, Hasher};
-use std::path::{Path, PathBuf};
-
-use ecs::Component;
-
-use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor};
-
-const VERTEX_SHADER_FILE: &str = "vertex.glsl";
-const FRAGMENT_SHADER_FILE: &str = "fragment.glsl";
-
-const SHADER_DIR: &str = "engine";
-
-/// Shader program
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Component)]
-pub struct Program
-{
- shaders: Vec<Shader>,
-}
-
-impl Program
-{
- /// Creates a new shader program with the default shaders.
- ///
- /// # Errors
- /// Returns `Err` if:
- /// - Reading a default shader file Fails
- /// - Preprocessing a shader fails.
- pub fn new() -> Result<Self, Error>
- {
- let mut program = Self { shaders: Vec::new() };
-
- program.push_shader(
- Shader::read_shader_file(
- Kind::Vertex,
- &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE),
- )?
- .preprocess()?,
- );
-
- program.push_shader(
- Shader::read_shader_file(
- Kind::Fragment,
- &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE),
- )?
- .preprocess()?,
- );
-
- Ok(program)
- }
-
- pub fn push_shader(&mut self, shader: Shader)
- {
- self.shaders.push(shader);
- }
-
- pub fn append_shaders(&mut self, shaders: impl IntoIterator<Item = Shader>)
- {
- self.shaders.extend(shaders);
- }
-
- #[must_use]
- pub fn shaders(&self) -> &[Shader]
- {
- &self.shaders
- }
-
- pub(crate) fn u64_hash(&self) -> u64
- {
- let mut hasher = DefaultHasher::new();
-
- self.hash(&mut hasher);
-
- hasher.finish()
- }
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub struct Shader
-{
- kind: Kind,
- source: String,
- file: PathBuf,
-}
-
-impl Shader
-{
- /// Reads a shader from the specified source file.
- ///
- /// # Errors
- /// Will return `Err` if:
- /// - Reading the file fails
- /// - The shader source is not ASCII
- pub fn read_shader_file(kind: Kind, shader_file: &Path) -> Result<Self, Error>
- {
- let source = read_to_string(shader_file).map_err(|err| Error::ReadFailed {
- source: err,
- shader_file: shader_file.to_path_buf(),
- })?;
-
- if !source.is_ascii() {
- return Err(Error::SourceNotAscii);
- }
-
- Ok(Self {
- kind,
- source,
- file: shader_file.to_path_buf(),
- })
- }
-
- /// Preprocesses the shaders.
- ///
- /// # Errors
- /// Returns `Err` if preprocessing fails.
- pub fn preprocess(self) -> Result<Self, Error>
- {
- let shader_preprocessor = ShaderPreprocessor::new(
- self.file
- .parent()
- .ok_or(Error::SourcePathHasNoParent)?
- .to_path_buf(),
- );
-
- let source_preprocessed = shader_preprocessor
- .preprocess(self.source, &self.file)
- .map_err(|err| Error::PreprocessFailed {
- source: err,
- shader_file: self.file.clone(),
- })?;
-
- Ok(Self {
- kind: self.kind,
- source: source_preprocessed,
- file: self.file.clone(),
- })
- }
-
- #[must_use]
- pub fn kind(&self) -> Kind
- {
- self.kind
- }
-
- #[must_use]
- pub fn source(&self) -> &str
- {
- &self.source
- }
-}
-
-/// Shader kind.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum Kind
-{
- Vertex,
- Fragment,
-}
-
-/// Shader error
-#[derive(Debug, thiserror::Error)]
-pub enum Error
-{
- #[error("Failed to read shader {}", shader_file.display())]
- ReadFailed
- {
- #[source]
- source: std::io::Error,
- shader_file: PathBuf,
- },
-
- #[error("Shader source is not ASCII")]
- SourceNotAscii,
-
- #[error("Failed to preprocess shader {}", shader_file.display())]
- PreprocessFailed
- {
- #[source]
- source: ShaderPreprocessorError,
- shader_file: PathBuf,
- },
-
- #[error("Shader source path has no parent")]
- SourcePathHasNoParent,
-}
diff --git a/engine/src/texture.rs b/engine/src/texture.rs
index f82b59d..d02b9ff 100644
--- a/engine/src/texture.rs
+++ b/engine/src/texture.rs
@@ -1,152 +1,37 @@
-use std::fmt::Display;
-use std::path::Path;
-use std::sync::atomic::{AtomicU32, Ordering};
-
-use image::io::Reader as ImageReader;
-use image::{DynamicImage, ImageError, Rgb, RgbImage};
-
-use crate::color::Color;
-use crate::data_types::dimens::Dimens;
-use crate::opengl::texture::PixelDataFormat;
-
-static NEXT_ID: AtomicU32 = AtomicU32::new(0);
-
-mod reexports
-{
- pub use crate::opengl::texture::{Filtering, Wrapping};
-}
-
-pub use reexports::*;
+use crate::asset::Handle as AssetHandle;
+use crate::image::Image;
+use crate::builder;
#[derive(Debug, Clone)]
+#[non_exhaustive]
pub struct Texture
{
- id: Id,
- image: DynamicImage,
- pixel_data_format: PixelDataFormat,
- dimensions: Dimens<u32>,
- properties: Properties,
+ pub asset_handle: AssetHandle<Image>,
+ pub properties: Properties,
}
impl Texture
{
- /// 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(),
- })
- }
-
- #[must_use]
- pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self
+ pub fn new(asset_handle: AssetHandle<Image>) -> 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,
+ asset_handle,
properties: Properties::default(),
}
}
- #[must_use]
- pub fn id(&self) -> Id
- {
- self.id
- }
-
- #[must_use]
- pub fn properties(&self) -> &Properties
- {
- &self.properties
- }
-
- pub fn properties_mut(&mut self) -> &mut Properties
- {
- &mut self.properties
- }
-
- #[must_use]
- pub fn dimensions(&self) -> &Dimens<u32>
- {
- &self.dimensions
- }
-
- #[must_use]
- pub fn pixel_data_format(&self) -> PixelDataFormat
- {
- self.pixel_data_format
- }
-
- #[must_use]
- pub fn image(&self) -> &DynamicImage
- {
- &self.image
- }
-}
-
-impl Drop for Texture
-{
- fn drop(&mut self)
+ pub fn with_properties(
+ asset_handle: AssetHandle<Image>,
+ properties: Properties,
+ ) -> Self
{
- NEXT_ID.fetch_sub(1, Ordering::Relaxed);
+ Self { asset_handle, properties }
}
}
-/// Texture error.
-#[derive(Debug, thiserror::Error)]
-pub enum Error
-{
- #[error("Failed to open texture image")]
- OpenImageFailed(#[source] std::io::Error),
-
- #[error("Failed to decode texture image")]
- DecodeImageFailed(#[source] ImageError),
-
- #[error("Unsupported image data kind")]
- UnsupportedImageDataKind,
-}
-
+builder! {
/// Texture properties
+#[builder(name = PropertiesBuilder, derives=(Debug, Clone))]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Properties
@@ -155,6 +40,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,30 +62,29 @@ impl Default for Properties
}
}
-/// Texture ID.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Id
-{
- id: u32,
-}
-
-impl Id
+impl Default for PropertiesBuilder
{
- fn new(id: u32) -> Self
+ fn default() -> Self
{
- Self { id }
+ Properties::default().into()
}
+}
- pub(crate) fn into_inner(self) -> u32
- {
- self.id
- }
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Filtering
+{
+ Nearest,
+ Linear,
}
-impl Display for Id
+/// Texture wrapping.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Wrapping
{
- fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
- {
- self.id.fmt(formatter)
- }
+ Repeat,
+ MirroredRepeat,
+ ClampToEdge,
+ ClampToBorder,
}
diff --git a/engine/src/transform.rs b/engine/src/transform.rs
index 5e5e296..7c0c941 100644
--- a/engine/src/transform.rs
+++ b/engine/src/transform.rs
@@ -2,14 +2,14 @@ use ecs::Component;
use crate::vector::Vec3;
-/// A position in 3D space.
+/// A position in world space.
#[derive(Debug, Default, Clone, Copy, Component)]
-pub struct Position
+pub struct WorldPosition
{
pub position: Vec3<f32>,
}
-impl From<Vec3<f32>> for Position
+impl From<Vec3<f32>> for WorldPosition
{
fn from(position: Vec3<f32>) -> Self
{
diff --git a/engine/src/util.rs b/engine/src/util.rs
index a505a38..cc4677d 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 {
@@ -26,6 +25,18 @@ macro_rules! or {
pub(crate) use or;
+#[macro_export]
+macro_rules! expand_map_opt {
+ ($in: tt, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => {
+ $($occurance)*
+ };
+
+ (, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => {
+ $($no_occurance)*
+ };
+}
+
+#[macro_export]
macro_rules! builder {
(
$(#[doc = $doc: literal])*
@@ -37,7 +48,8 @@ macro_rules! builder {
$visibility: vis struct $name: ident
{
$(
- $(#[$field_attr: meta])*
+ $(#[doc = $field_doc: literal])*
+ $(#[builder(skip_generate_fn$($field_skip_generate_fn: tt)?)])?
$field_visibility: vis $field: ident: $field_type: ty,
)*
}
@@ -47,7 +59,7 @@ macro_rules! builder {
$visibility struct $name
{
$(
- $(#[$field_attr])*
+ $(#[doc = $field_doc])*
$field_visibility $field: $field_type,
)*
}
@@ -63,12 +75,18 @@ macro_rules! builder {
impl $builder_name
{
$(
- #[must_use]
- $visibility fn $field(mut self, $field: $field_type) -> Self
- {
- self.$field = $field;
- self
- }
+ $crate::expand_map_opt!(
+ $(true $($field_skip_generate_fn)?)?,
+ no_occurance=(
+ #[must_use]
+ $visibility fn $field(mut self, $field: $field_type) -> Self
+ {
+ self.$field = $field;
+ self
+ }
+ ),
+ occurance=()
+ );
)*
#[must_use]
@@ -83,6 +101,7 @@ macro_rules! builder {
impl From<$name> for $builder_name
{
+ #[allow(unused_variables)]
fn from(built: $name) -> Self
{
Self {
@@ -95,38 +114,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/window.rs b/engine/src/window.rs
index ccc1b8d..d342341 100644
--- a/engine/src/window.rs
+++ b/engine/src/window.rs
@@ -1,30 +1,25 @@
use std::borrow::Cow;
use std::ffi::{CStr, CString};
+use bitflags::bitflags;
use ecs::actions::Actions;
use ecs::extension::Collector as ExtensionCollector;
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, START as START_PHASE};
use ecs::sole::Single;
-use ecs::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::renderer::RENDER_PHASE;
use crate::vector::Vec2;
-mod reexports
-{
- pub use glfw::window::{
- CursorMode,
- Hint as CreationHint,
- HintValue as CreationHintValue,
- InputMode,
- Key,
- KeyModifiers,
- KeyState,
- };
-}
-
-pub use reexports::*;
+static_entity!(
+ pub UPDATE_PHASE,
+ (Phase, Pair::new::<ChildOf>(*RENDER_PHASE))
+);
#[derive(Debug, Sole)]
/// Has to be dropped last since it holds the OpenGL context.
@@ -53,7 +48,9 @@ impl Window
enabled: bool,
) -> Result<(), Error>
{
- Ok(self.inner.set_input_mode(input_mode, enabled)?)
+ Ok(self
+ .inner
+ .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?)
}
/// Sets the cursor mode.
@@ -62,7 +59,9 @@ impl Window
/// If a platform error occurs.
pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error>
{
- Ok(self.inner.set_cursor_mode(cursor_mode)?)
+ Ok(self
+ .inner
+ .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?)
}
/// Returns whether or not the window should close. Will return true when the user has
@@ -155,7 +154,19 @@ impl Window
callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static,
)
{
- self.inner.set_key_callback(callback);
+ self.inner
+ .set_key_callback(move |key, scancode, key_state, key_modifiers| {
+ let Some(key_state) = KeyState::from_glfw_key_state(key_state) else {
+ return;
+ };
+
+ callback(
+ Key::from_glfw_key(key),
+ scancode,
+ key_state,
+ KeyModifiers::from_bits_truncate(key_modifiers.bits()),
+ )
+ });
}
/// Sets the window's cursor position callback.
@@ -165,6 +176,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)
{
@@ -188,10 +217,26 @@ pub struct Builder
impl Builder
{
- #[must_use]
- pub fn creation_hint(mut self, hint: CreationHint, value: CreationHintValue) -> Self
+ /// Sets whether the OpenGL context should be created in debug mode, which may
+ /// provide additional error and diagnostic reporting functionality.
+ pub fn opengl_debug_context(mut self, enabled: bool) -> Self
+ {
+ self.inner = self.inner.hint(
+ WindowCreationHint::OpenGLDebugContext,
+ WindowCreationHintValue::Bool(enabled),
+ );
+
+ self
+ }
+
+ /// Set the desired number of samples to use for multisampling. Zero disables
+ /// multisampling.
+ pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self
{
- self.inner = self.inner.hint(hint, value);
+ self.inner = self.inner.hint(
+ WindowCreationHint::Samples,
+ WindowCreationHintValue::Number(sample_count as i32),
+ );
self
}
@@ -204,8 +249,8 @@ impl Builder
pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error>
{
let builder = self.inner.clone().hint(
- CreationHint::OpenGLDebugContext,
- CreationHintValue::Bool(cfg!(feature = "debug")),
+ WindowCreationHint::OpenGLDebugContext,
+ WindowCreationHintValue::Bool(true),
);
let window = builder.create(
@@ -220,6 +265,396 @@ impl Builder
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)]
+#[variant_arr(name = KEYS)]
+pub enum Key
+{
+ Space,
+ Apostrophe,
+ Comma,
+ Minus,
+ Period,
+ Slash,
+ Digit0,
+ Digit1,
+ Digit2,
+ Digit3,
+ Digit4,
+ Digit5,
+ Digit6,
+ Digit7,
+ Digit8,
+ Digit9,
+ Semicolon,
+ Equal,
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ LeftBracket,
+ Backslash,
+ RightBracket,
+ GraveAccent,
+ World1,
+ World2,
+ Escape,
+ Enter,
+ Tab,
+ Backspace,
+ Insert,
+ Delete,
+ Right,
+ Left,
+ Down,
+ Up,
+ PageUp,
+ PageDown,
+ Home,
+ End,
+ CapsLock,
+ ScrollLock,
+ NumLock,
+ PrintScreen,
+ Pause,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+ F25,
+ Kp0,
+ Kp1,
+ Kp2,
+ Kp3,
+ Kp4,
+ Kp5,
+ Kp6,
+ Kp7,
+ Kp8,
+ Kp9,
+ KpDecimal,
+ KpDivide,
+ KpMultiply,
+ KpSubtract,
+ KpAdd,
+ KpEnter,
+ KpEqual,
+ LeftShift,
+ LeftControl,
+ LeftAlt,
+ LeftSuper,
+ RightShift,
+ RightControl,
+ RightAlt,
+ RightSuper,
+ Menu,
+}
+
+impl Key
+{
+ fn from_glfw_key(glfw_key: glfw::window::Key) -> Self
+ {
+ match glfw_key {
+ glfw::window::Key::Space => Self::Space,
+ glfw::window::Key::Apostrophe => Self::Apostrophe,
+ glfw::window::Key::Comma => Self::Comma,
+ glfw::window::Key::Minus => Self::Minus,
+ glfw::window::Key::Period => Self::Period,
+ glfw::window::Key::Slash => Self::Slash,
+ glfw::window::Key::Digit0 => Self::Digit0,
+ glfw::window::Key::Digit1 => Self::Digit1,
+ glfw::window::Key::Digit2 => Self::Digit2,
+ glfw::window::Key::Digit3 => Self::Digit3,
+ glfw::window::Key::Digit4 => Self::Digit4,
+ glfw::window::Key::Digit5 => Self::Digit5,
+ glfw::window::Key::Digit6 => Self::Digit6,
+ glfw::window::Key::Digit7 => Self::Digit7,
+ glfw::window::Key::Digit8 => Self::Digit8,
+ glfw::window::Key::Digit9 => Self::Digit9,
+ glfw::window::Key::Semicolon => Self::Semicolon,
+ glfw::window::Key::Equal => Self::Equal,
+ glfw::window::Key::A => Self::A,
+ glfw::window::Key::B => Self::B,
+ glfw::window::Key::C => Self::C,
+ glfw::window::Key::D => Self::D,
+ glfw::window::Key::E => Self::E,
+ glfw::window::Key::F => Self::F,
+ glfw::window::Key::G => Self::G,
+ glfw::window::Key::H => Self::H,
+ glfw::window::Key::I => Self::I,
+ glfw::window::Key::J => Self::J,
+ glfw::window::Key::K => Self::K,
+ glfw::window::Key::L => Self::L,
+ glfw::window::Key::M => Self::M,
+ glfw::window::Key::N => Self::N,
+ glfw::window::Key::O => Self::O,
+ glfw::window::Key::P => Self::P,
+ glfw::window::Key::Q => Self::Q,
+ glfw::window::Key::R => Self::R,
+ glfw::window::Key::S => Self::S,
+ glfw::window::Key::T => Self::T,
+ glfw::window::Key::U => Self::U,
+ glfw::window::Key::V => Self::V,
+ glfw::window::Key::W => Self::W,
+ glfw::window::Key::X => Self::X,
+ glfw::window::Key::Y => Self::Y,
+ glfw::window::Key::Z => Self::Z,
+ glfw::window::Key::LeftBracket => Self::LeftBracket,
+ glfw::window::Key::Backslash => Self::Backslash,
+ glfw::window::Key::RightBracket => Self::RightBracket,
+ glfw::window::Key::GraveAccent => Self::GraveAccent,
+ glfw::window::Key::World1 => Self::World1,
+ glfw::window::Key::World2 => Self::World2,
+ glfw::window::Key::Escape => Self::Escape,
+ glfw::window::Key::Enter => Self::Enter,
+ glfw::window::Key::Tab => Self::Tab,
+ glfw::window::Key::Backspace => Self::Backspace,
+ glfw::window::Key::Insert => Self::Insert,
+ glfw::window::Key::Delete => Self::Delete,
+ glfw::window::Key::Right => Self::Right,
+ glfw::window::Key::Left => Self::Left,
+ glfw::window::Key::Down => Self::Down,
+ glfw::window::Key::Up => Self::Up,
+ glfw::window::Key::PageUp => Self::PageUp,
+ glfw::window::Key::PageDown => Self::PageDown,
+ glfw::window::Key::Home => Self::Home,
+ glfw::window::Key::End => Self::End,
+ glfw::window::Key::CapsLock => Self::CapsLock,
+ glfw::window::Key::ScrollLock => Self::ScrollLock,
+ glfw::window::Key::NumLock => Self::NumLock,
+ glfw::window::Key::PrintScreen => Self::PrintScreen,
+ glfw::window::Key::Pause => Self::Pause,
+ glfw::window::Key::F1 => Self::F1,
+ glfw::window::Key::F2 => Self::F2,
+ glfw::window::Key::F3 => Self::F3,
+ glfw::window::Key::F4 => Self::F4,
+ glfw::window::Key::F5 => Self::F5,
+ glfw::window::Key::F6 => Self::F6,
+ glfw::window::Key::F7 => Self::F7,
+ glfw::window::Key::F8 => Self::F8,
+ glfw::window::Key::F9 => Self::F9,
+ glfw::window::Key::F10 => Self::F10,
+ glfw::window::Key::F11 => Self::F11,
+ glfw::window::Key::F12 => Self::F12,
+ glfw::window::Key::F13 => Self::F13,
+ glfw::window::Key::F14 => Self::F14,
+ glfw::window::Key::F15 => Self::F15,
+ glfw::window::Key::F16 => Self::F16,
+ glfw::window::Key::F17 => Self::F17,
+ glfw::window::Key::F18 => Self::F18,
+ glfw::window::Key::F19 => Self::F19,
+ glfw::window::Key::F20 => Self::F20,
+ glfw::window::Key::F21 => Self::F21,
+ glfw::window::Key::F22 => Self::F22,
+ glfw::window::Key::F23 => Self::F23,
+ glfw::window::Key::F24 => Self::F24,
+ glfw::window::Key::F25 => Self::F25,
+ glfw::window::Key::Kp0 => Self::Kp0,
+ glfw::window::Key::Kp1 => Self::Kp1,
+ glfw::window::Key::Kp2 => Self::Kp2,
+ glfw::window::Key::Kp3 => Self::Kp3,
+ glfw::window::Key::Kp4 => Self::Kp4,
+ glfw::window::Key::Kp5 => Self::Kp5,
+ glfw::window::Key::Kp6 => Self::Kp6,
+ glfw::window::Key::Kp7 => Self::Kp7,
+ glfw::window::Key::Kp8 => Self::Kp8,
+ glfw::window::Key::Kp9 => Self::Kp9,
+ glfw::window::Key::KpDecimal => Self::KpDecimal,
+ glfw::window::Key::KpDivide => Self::KpDivide,
+ glfw::window::Key::KpMultiply => Self::KpMultiply,
+ glfw::window::Key::KpSubtract => Self::KpSubtract,
+ glfw::window::Key::KpAdd => Self::KpAdd,
+ glfw::window::Key::KpEnter => Self::KpEnter,
+ glfw::window::Key::KpEqual => Self::KpEqual,
+ glfw::window::Key::LeftShift => Self::LeftShift,
+ glfw::window::Key::LeftControl => Self::LeftControl,
+ glfw::window::Key::LeftAlt => Self::LeftAlt,
+ glfw::window::Key::LeftSuper => Self::LeftSuper,
+ glfw::window::Key::RightShift => Self::RightShift,
+ glfw::window::Key::RightControl => Self::RightControl,
+ glfw::window::Key::RightAlt => Self::RightAlt,
+ glfw::window::Key::RightSuper => Self::RightSuper,
+ glfw::window::Key::Menu => Self::Menu,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum KeyState
+{
+ Pressed,
+ Released,
+}
+
+impl KeyState
+{
+ fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option<Self>
+ {
+ match glfw_key_state {
+ glfw::window::KeyState::Pressed => Some(Self::Pressed),
+ glfw::window::KeyState::Released => Some(Self::Released),
+ glfw::window::KeyState::Repeat => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MouseButton
+{
+ One,
+ Two,
+ Three,
+ Four,
+ Five,
+ Six,
+ Seven,
+ Eight,
+}
+
+impl MouseButton
+{
+ pub const LEFT: Self = Self::One;
+ pub const MIDDLE: Self = Self::Three;
+ pub const RIGHT: Self = Self::Two;
+
+ fn from_glfw_mouse_button(mouse_button: glfw::window::MouseButton) -> Self
+ {
+ match mouse_button {
+ glfw::window::MouseButton::One => Self::One,
+ glfw::window::MouseButton::Two => Self::Two,
+ glfw::window::MouseButton::Three => Self::Three,
+ glfw::window::MouseButton::Four => Self::Four,
+ glfw::window::MouseButton::Five => Self::Five,
+ glfw::window::MouseButton::Six => Self::Six,
+ glfw::window::MouseButton::Seven => Self::Seven,
+ glfw::window::MouseButton::Eight => Self::Eight,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MouseButtonState
+{
+ Pressed,
+ Released,
+}
+
+impl MouseButtonState
+{
+ fn from_glfw_mouse_button_state(
+ mouse_button_state: glfw::window::MouseButtonState,
+ ) -> Self
+ {
+ match mouse_button_state {
+ glfw::window::MouseButtonState::Pressed => Self::Pressed,
+ glfw::window::MouseButtonState::Released => Self::Released,
+ }
+ }
+}
+
+bitflags! {
+ #[derive(Debug, Clone, Copy)]
+ pub struct KeyModifiers: i32 {
+ const SHIFT = glfw::window::KeyModifiers::SHIFT.bits();
+ const CONTROL = glfw::window::KeyModifiers::CONTROL.bits();
+ const ALT = glfw::window::KeyModifiers::ALT.bits();
+ const SUPER = glfw::window::KeyModifiers::SUPER.bits();
+ const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits();
+ const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits();
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum CursorMode
+{
+ /// Hides and grabs the cursor, providing virtual and unlimited cursor movement.
+ Disabled,
+
+ /// Makes the cursor invisible when it is over the content area of the window but
+ /// does not restrict the cursor from leaving.
+ Hidden,
+
+ /// Makes the cursor visible and behaving normally.
+ Normal,
+}
+
+impl CursorMode
+{
+ fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode
+ {
+ match self {
+ Self::Disabled => glfw::window::CursorMode::Disabled,
+ Self::Hidden => glfw::window::CursorMode::Hidden,
+ Self::Normal => glfw::window::CursorMode::Normal,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum InputMode
+{
+ /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be
+ /// enabled if available.
+ ///
+ /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It
+ /// is not affected by the scaling and acceleration applied to the motion of the
+ /// desktop cursor. That processing is suitable for a cursor while raw motion is
+ /// better for controlling for example a 3D camera. Because of this, raw mouse motion
+ /// is only provided when the cursor is disabled.
+ RawMouseMotion,
+}
+
+impl InputMode
+{
+ fn to_glfw_input_mode(self) -> glfw::window::InputMode
+ {
+ match self {
+ Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion,
+ }
+ }
+}
+
#[derive(Debug)]
pub struct Extension
{
@@ -257,8 +692,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/engine/src/work_queue.rs b/engine/src/work_queue.rs
new file mode 100644
index 0000000..7226c7d
--- /dev/null
+++ b/engine/src/work_queue.rs
@@ -0,0 +1,44 @@
+use std::marker::PhantomData;
+use std::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender};
+use std::thread::JoinHandle as ThreadHandle;
+
+pub struct Work<UserData: Send + Sync + 'static>
+{
+ pub func: fn(UserData),
+ pub user_data: UserData,
+}
+
+#[derive(Debug)]
+pub struct WorkQueue<UserData: Send + Sync + 'static>
+{
+ work_sender: MpscSender<Work<UserData>>,
+ _thread: ThreadHandle<()>,
+ _pd: PhantomData<UserData>,
+}
+
+impl<UserData: Send + Sync + 'static> WorkQueue<UserData>
+{
+ pub fn new() -> Self
+ {
+ let (work_sender, work_receiver) = mpsc_channel::<Work<UserData>>();
+
+ Self {
+ work_sender,
+ _thread: std::thread::spawn(move || {
+ let work_receiver = work_receiver;
+
+ while let Ok(work) = work_receiver.recv() {
+ (work.func)(work.user_data);
+ }
+ }),
+ _pd: PhantomData,
+ }
+ }
+
+ pub fn add_work(&self, work: Work<UserData>)
+ {
+ if self.work_sender.send(work).is_err() {
+ tracing::error!("Cannot add work to work queue. Work queue thread is dead");
+ }
+ }
+}
diff --git a/glfw/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 8710a21..d5154ae 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
use std::error::Error;
-use std::fs::read_to_string;
use std::path::Path;
+use engine::asset::Assets;
use engine::camera::fly::{
Extension as FlyCameraExtension,
Fly as FlyCamera,
@@ -9,29 +9,32 @@ use engine::camera::fly::{
};
use engine::camera::{Active as ActiveCamera, Camera};
use engine::color::Color;
-use engine::data_types::dimens::Dimens;
+use engine::data_types::dimens::{Dimens, Dimens3};
+use engine::ecs::actions::Actions;
+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::material::{Flags as MaterialFlags, Material};
+use engine::mesh::cube::{
+ create as cube_mesh_create,
+ CreationSpec as CubeMeshCreationSpec,
+};
+use engine::model::{Data as ModelData, Model};
use engine::renderer::opengl::Extension as OpenglRendererExtension;
-use engine::shader::Program as ShaderProgram;
-use engine::transform::Position;
+use engine::transform::WorldPosition;
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 };
@@ -45,57 +48,20 @@ 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();
- let teapot_obj =
- parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("teapot.obj"))?)?;
-
- let teapot_mat_name = teapot_obj
- .faces
- .first()
- .and_then(|face| face.material_name.as_ref());
-
- let teapot_mats = teapot_obj.read_and_parse_material_libs(parse_mtl)?;
-
- let teapot_mat = teapot_mats
- .into_iter()
- .find(|mat| Some(&mat.name) == teapot_mat_name)
- .ok_or("Teapot material was not found")?;
-
- engine.spawn((
- teapot_obj.to_mesh()?,
- teapot_mat.material,
- Position::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }),
- ShaderProgram::new()?,
- ));
-
- engine.spawn((
- PointLight::builder()
- .position(Vec3 { x: -6.0, y: 3.0, z: 3.0 })
- .diffuse(YELLOW)
- .attenuation_params(AttenuationParams {
- linear: 0.045,
- quadratic: 0.0075,
- ..Default::default()
- })
- .build(),
- Position::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }),
- parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("cube.obj"))?)?
- .to_mesh()?,
- MaterialBuilder::new().ambient(YELLOW * 5.0).build(),
- ShaderProgram::new()?,
- MaterialFlags::builder().use_ambient_color(true).build(),
- ));
-
engine.spawn((
Camera::default(),
- Position {
+ WorldPosition {
position: Vec3 { x: 0.0, y: 0.0, z: 3.0 },
},
ActiveCamera,
@@ -104,17 +70,14 @@ fn main() -> Result<(), Box<dyn Error>>
engine.add_sole(GlobalLight::default())?;
- engine.register_system(StartEvent, prepare_window);
+ engine.register_system(*START_PHASE, init);
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 {
@@ -128,7 +91,42 @@ fn main() -> Result<(), Box<dyn Error>>
Ok(())
}
-fn prepare_window(window: Single<Window>)
+fn init(window: Single<Window>, mut assets: Single<Assets>, mut actions: Actions)
{
window.set_cursor_mode(CursorMode::Disabled).unwrap();
+
+ actions.spawn((
+ PointLight::builder()
+ .diffuse(YELLOW)
+ .attenuation_params(AttenuationParams {
+ linear: 0.045,
+ quadratic: 0.0075,
+ ..Default::default()
+ })
+ .build(),
+ WorldPosition::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }),
+ Model::new(
+ assets.store_with_name(
+ "light_cube",
+ ModelData::builder()
+ .mesh(cube_mesh_create(
+ CubeMeshCreationSpec::builder()
+ .dimens(Dimens3::from(2.0))
+ .build(),
+ |face_verts, _, _| face_verts,
+ ))
+ .material(
+ "surface",
+ Material::builder().ambient(YELLOW * 5.0).build(),
+ )
+ .build(),
+ ),
+ ),
+ MaterialFlags::builder().use_ambient_color(true).build(),
+ ));
+
+ actions.spawn((
+ Model::new(assets.load::<ModelData>(Path::new(RESOURCE_DIR).join("teapot.obj"))),
+ WorldPosition::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }),
+ ));
}