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