├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── macros ├── Cargo.lock ├── Cargo.toml └── src │ ├── data.rs │ ├── generate │ ├── cfg.rs │ ├── mod.rs │ ├── query.rs │ ├── util.rs │ └── world.rs │ ├── lib.rs │ └── parse │ ├── attribute.rs │ ├── cfg.rs │ ├── mod.rs │ ├── query.rs │ ├── util.rs │ └── world.rs ├── src ├── archetype │ ├── components.rs │ ├── iter.rs │ ├── mod.rs │ ├── slices.rs │ ├── slot.rs │ ├── storage.rs │ └── view.rs ├── entity.rs ├── error.rs ├── index.rs ├── iter.rs ├── lib.rs ├── traits.rs ├── util.rs └── version.rs └── tests ├── test_bind.rs ├── test_direct.rs ├── test_entity_wild.rs ├── test_events.rs ├── test_generic.rs ├── test_impls.rs ├── test_iter.rs ├── test_iter_destroy.rs ├── test_multi.rs ├── test_one_of.rs ├── test_query_cfg.rs ├── test_raw.rs ├── test_select.rs ├── test_single.rs ├── test_single_dyn.rs └── test_util.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | - name: Run tests with events 24 | run: cargo test --verbose --features="events" 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # No build artifacts 2 | debug/ 3 | target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # MSVC Windows builds of rustc generate these, which store debugging information 9 | **/*.pdb 10 | 11 | # No editor or IDE files 12 | .vs/ 13 | .idea/ 14 | .vscode/ 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "base64" 13 | version = "0.22.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 16 | 17 | [[package]] 18 | name = "convert_case" 19 | version = "0.6.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 22 | dependencies = [ 23 | "unicode-segmentation", 24 | ] 25 | 26 | [[package]] 27 | name = "gecs" 28 | version = "0.4.0" 29 | dependencies = [ 30 | "gecs_macros", 31 | "seq-macro", 32 | ] 33 | 34 | [[package]] 35 | name = "gecs_macros" 36 | version = "0.4.0" 37 | dependencies = [ 38 | "base64", 39 | "convert_case", 40 | "proc-macro2", 41 | "quote", 42 | "speedy", 43 | "syn", 44 | "xxhash-rust", 45 | ] 46 | 47 | [[package]] 48 | name = "memoffset" 49 | version = "0.9.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 52 | dependencies = [ 53 | "autocfg", 54 | ] 55 | 56 | [[package]] 57 | name = "proc-macro2" 58 | version = "1.0.86" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 61 | dependencies = [ 62 | "unicode-ident", 63 | ] 64 | 65 | [[package]] 66 | name = "quote" 67 | version = "1.0.36" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 70 | dependencies = [ 71 | "proc-macro2", 72 | ] 73 | 74 | [[package]] 75 | name = "seq-macro" 76 | version = "0.3.5" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" 79 | 80 | [[package]] 81 | name = "speedy" 82 | version = "0.8.7" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "da1992073f0e55aab599f4483c460598219b4f9ff0affa124b33580ab511e25a" 85 | dependencies = [ 86 | "memoffset", 87 | "speedy-derive", 88 | ] 89 | 90 | [[package]] 91 | name = "speedy-derive" 92 | version = "0.8.7" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "658f2ca5276b92c3dfd65fa88316b4e032ace68f88d7570b43967784c0bac5ac" 95 | dependencies = [ 96 | "proc-macro2", 97 | "quote", 98 | "syn", 99 | ] 100 | 101 | [[package]] 102 | name = "syn" 103 | version = "2.0.71" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "unicode-ident", 110 | ] 111 | 112 | [[package]] 113 | name = "unicode-ident" 114 | version = "1.0.12" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 117 | 118 | [[package]] 119 | name = "unicode-segmentation" 120 | version = "1.11.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 123 | 124 | [[package]] 125 | name = "xxhash-rust" 126 | version = "0.8.11" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "63658493314859b4dfdf3fb8c1defd61587839def09582db50b8a4e93afca6bb" 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gecs" 3 | version = "0.4.0" 4 | authors = ["recatek"] 5 | description = "A generated entity component system." 6 | edition = "2021" 7 | repository = "https://github.com/recatek/gecs" 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | keywords = ["ecs", "entity"] 11 | categories = ["data-structures", "game-engines"] 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | 16 | [features] 17 | default = [] 18 | 19 | # Allow archetypes to have up to 32 components (default is 16). Worsens compile times. 20 | 32_components = [] 21 | 22 | # Wrap rather than panic when a version number overflows (4,294,967,295 max). Yields some small perf gains when 23 | # creating/destroying entities, but has a (probabilistically insignificant) risk of allowing reuse of stale handles. 24 | wrapping_version = [] 25 | 26 | # Adds event tracking for entity creation/destruction. Has the following perf consequences: 27 | # - Adds additional allocation overhead to the ECS world for each per-archetype event queue. 28 | # - Every entity creation or destruction pushes an event to an archetype's event queue. 29 | # - Event queues will accumulate indefinitely and must be regularly drained using clear_events. 30 | events = ['gecs_macros/events'] 31 | 32 | [dependencies] 33 | gecs_macros = { version = "0.4.0", path = "macros", default-features = false } 34 | 35 | seq-macro = { version = "0.3" } # For building "variadic" storage 36 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gecs 🦎 2 | ------- 3 | A generated entity component system. 4 | 5 | [![Documentation](https://docs.rs/gecs/badge.svg)](https://docs.rs/gecs/) 6 | [![Crates.io](https://img.shields.io/crates/v/gecs.svg)](https://crates.io/crates/gecs) 7 | 8 | The gecs crate provides a compile-time generated, zero-overhead ECS for simulations 9 | on a budget. Unlike other ECS libraries, gecs takes a full ECS world structure 10 | definition from code and precompiles all queries to achieve better performance with 11 | no upfront cost or caching overhead. Queries in gecs can be inspected and checked at 12 | compile-time in order to catch what would otherwise be bugs presenting only in tests 13 | or execution. However, this comes at the cost of requiring all archetypes to be known 14 | and declared at compile-time, so that adding or removing components from entities at 15 | runtime isn't currently possible -- hybrid approaches could solve this in the future. 16 | 17 | The goals for gecs are (in descending priority order): 18 | - Fast iteration and find queries 19 | - Fast entity creation and destruction 20 | - Low, predictable memory overhead 21 | - A user-friendly library interface 22 | - Simplicity and focus in features 23 | 24 | All of the code that gecs generates in user crates is safe, and users of gecs can 25 | use `#![forbid(unsafe_code)]` in their own crates. Note that gecs does use unsafe 26 | code internally to allow for compiler optimizations around known invariants. It is 27 | not a goal of this library to be written entirely in safe Rust. 28 | 29 | # Getting Started 30 | 31 | See the [ecs_world!] macro for information on how to construct an ECS 32 | world, and the [ecs_find!] and [ecs_iter!] macros for 33 | information on how to perform full ECS queries. See the [World] and 34 | [Archetype] traits to see how to interact directly with the world. 35 | 36 | [ecs_world!]: https://docs.rs/gecs/latest/gecs/macro.ecs_world.html 37 | [ecs_find!]: https://docs.rs/gecs/latest/gecs/macro.ecs_find.html 38 | [ecs_iter!]: https://docs.rs/gecs/latest/gecs/macro.ecs_iter.html 39 | [World]: https://docs.rs/gecs/latest/gecs/traits/trait.World.html 40 | [Archetype]: https://docs.rs/gecs/latest/gecs/traits/trait.Archetype.html 41 | 42 | 43 | The following example creates a world with three components and two archetypes: 44 | 45 | ```rust 46 | use gecs::prelude::*; 47 | 48 | // Components -- these must be pub because the world is exported as pub as well. 49 | pub struct CompA(pub u32); 50 | pub struct CompB(pub u32); 51 | pub struct CompC(pub u32); 52 | 53 | ecs_world! { 54 | // Declare two archetypes, ArchFoo and ArchBar. 55 | ecs_archetype!(ArchFoo, CompA, CompB); 56 | ecs_archetype!(ArchBar, CompA, CompC); 57 | } 58 | 59 | fn main() { 60 | let mut world = EcsWorld::default(); // Initialize an empty new ECS world. 61 | 62 | // Add entities to the world by populating their components and receive their handles. 63 | let entity_a = world.create::((CompA(1), CompB(20))); 64 | let entity_b = world.create::((CompA(3), CompC(40))); 65 | 66 | // Each archetype now has one entity. 67 | assert_eq!(world.archetype::().len(), 1); 68 | assert_eq!(world.archetype::().len(), 1); 69 | 70 | // Look up each entity and check its CompB or CompC value. 71 | // We use the is_some() check here to make sure the entity was indeed found. 72 | assert!(ecs_find!(world, entity_a, |c: &CompB| assert_eq!(c.0, 20)).is_some()); 73 | assert!(ecs_find!(world, entity_b, |c: &CompC| assert_eq!(c.0, 40)).is_some()); 74 | 75 | // Add to entity_a's CompA value. 76 | ecs_find!(world, entity_a, |c: &mut CompA| { c.0 += 1; }); 77 | 78 | // Sum both entities' CompA values with one iter despite being different archetypes. 79 | let mut sum = 0; 80 | ecs_iter!(world, |c: &CompA| { sum += c.0 }); 81 | assert_eq!(sum, 5); // Adding 2 + 3 -- recall that we added 1 to entity_a's CompA. 82 | 83 | // Collect both entities that have a CompA component. 84 | let mut found = Vec::new(); 85 | ecs_iter!(world, |entity: &EntityAny, _: &CompA| { found.push(*entity); }); 86 | assert!(found == vec![entity_a.into(), entity_b.into()]); 87 | 88 | // Destroy both entities -- this will return an Option containing their components. 89 | assert!(world.destroy(entity_a).is_some()); 90 | assert!(world.destroy(entity_b).is_some()); 91 | 92 | // Try to look up a stale entity handle -- this will return None. 93 | assert!(ecs_find!(world, entity_a, |_: &Entity| { panic!() }).is_none()); 94 | } 95 | ``` 96 | License 97 | ------- 98 | 99 | This library may be used under your choice of the Apache 2.0 or MIT license. 100 | -------------------------------------------------------------------------------- /macros/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "convert_case" 7 | version = "0.6.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 10 | dependencies = [ 11 | "unicode-segmentation", 12 | ] 13 | 14 | [[package]] 15 | name = "gecs_macros" 16 | version = "0.0.1" 17 | dependencies = [ 18 | "convert_case", 19 | "lazy_static", 20 | "proc-macro2", 21 | "quote", 22 | "syn", 23 | ] 24 | 25 | [[package]] 26 | name = "lazy_static" 27 | version = "1.4.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 30 | 31 | [[package]] 32 | name = "proc-macro2" 33 | version = "1.0.58" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" 36 | dependencies = [ 37 | "unicode-ident", 38 | ] 39 | 40 | [[package]] 41 | name = "quote" 42 | version = "1.0.27" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 45 | dependencies = [ 46 | "proc-macro2", 47 | ] 48 | 49 | [[package]] 50 | name = "syn" 51 | version = "2.0.16" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 54 | dependencies = [ 55 | "proc-macro2", 56 | "quote", 57 | "unicode-ident", 58 | ] 59 | 60 | [[package]] 61 | name = "unicode-ident" 62 | version = "1.0.8" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 65 | 66 | [[package]] 67 | name = "unicode-segmentation" 68 | version = "1.10.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 71 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gecs_macros" 3 | version = "0.4.0" 4 | authors = ["recatek"] 5 | description = "Procedural macros for the gecs crate." 6 | edition = "2021" 7 | repository = "https://github.com/recatek/gecs" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [features] 14 | default = [] 15 | events = [] 16 | 17 | [dependencies] 18 | convert_case = { version = "0.6" } 19 | proc-macro2 = { version = "1.0" } 20 | quote = { version = "1.0" } 21 | syn = { version = "2.0", features = ["full", "extra-traits"] } 22 | 23 | # For unique generation of query macros 24 | xxhash-rust = { version = "0.8", features = ["xxh3"] } 25 | 26 | # Serialization for passing world data to queries 27 | base64 = { version = "0.22" } 28 | speedy = { version = "0.8" } 29 | -------------------------------------------------------------------------------- /macros/src/data.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use base64::Engine as _; 4 | use speedy::{Readable, Writable}; 5 | use syn::Ident; 6 | 7 | use crate::parse::{HasAttributeId, ParseAttributeCfg, ParseCfgDecorated, ParseEcsWorld}; 8 | 9 | #[derive(Debug, Readable, Writable)] 10 | pub struct DataWorld { 11 | pub name: String, 12 | pub archetypes: Vec, 13 | } 14 | 15 | #[derive(Debug, Readable, Writable)] 16 | pub struct DataArchetype { 17 | pub id: u8, 18 | pub name: String, 19 | pub components: Vec, 20 | } 21 | 22 | #[derive(Debug, Readable, Writable)] 23 | pub struct DataComponent { 24 | pub id: u8, 25 | pub name: String, 26 | } 27 | 28 | impl DataWorld { 29 | pub fn new(mut parse: ParseCfgDecorated) -> syn::Result { 30 | let cfg_lookup = parse.cfg_lookup; 31 | 32 | let mut archetypes = Vec::new(); 33 | let mut archetype_ids = HashMap::new(); 34 | let mut last_archetype_id = None; 35 | 36 | for mut archetype in parse.inner.archetypes.drain(..) { 37 | if evaluate_cfgs(&cfg_lookup, &archetype.cfgs) == false { 38 | continue; 39 | } 40 | 41 | // Advance the archetype ID, either implicitly or from the ID attribute 42 | last_archetype_id = advance_attribute_id( 43 | &archetype, //. 44 | &mut archetype_ids, 45 | last_archetype_id, 46 | )?; 47 | 48 | let mut component_ids = HashMap::new(); 49 | let mut last_component_id = None; 50 | 51 | let mut components = Vec::new(); 52 | for component in archetype.components.drain(..) { 53 | if evaluate_cfgs(&cfg_lookup, &component.cfgs) == false { 54 | continue; 55 | } 56 | 57 | // Advance the component ID, either implicitly or from the ID attribute 58 | last_component_id = advance_attribute_id( 59 | &component, //. 60 | &mut component_ids, 61 | last_component_id, 62 | )?; 63 | 64 | components.push(DataComponent { 65 | id: last_component_id.unwrap(), // TODO 66 | name: component.name.to_string(), 67 | }); 68 | } 69 | 70 | archetypes.push(DataArchetype { 71 | id: last_archetype_id.unwrap(), 72 | name: archetype.name.to_string(), 73 | components, 74 | }) 75 | } 76 | 77 | Ok(DataWorld { 78 | name: parse.inner.name.to_string(), 79 | archetypes, 80 | }) 81 | } 82 | 83 | pub fn to_base64(&self) -> String { 84 | base64::engine::general_purpose::STANDARD_NO_PAD 85 | .encode(self.write_to_vec().expect("failed to serialize world")) 86 | } 87 | 88 | pub fn from_base64(base64: &str) -> Self { 89 | Self::read_from_buffer( 90 | &base64::engine::general_purpose::STANDARD_NO_PAD 91 | .decode(base64) 92 | .expect("failed to deserialize world"), 93 | ) 94 | .expect("failed to deserialize world") 95 | } 96 | } 97 | 98 | impl DataArchetype { 99 | pub fn contains_component(&self, name: &Ident) -> bool { 100 | for component in self.components.iter() { 101 | if component.name == name.to_string() { 102 | return true; 103 | } 104 | } 105 | false 106 | } 107 | } 108 | 109 | fn evaluate_cfgs(cfg_lookup: &HashMap, cfgs: &[ParseAttributeCfg]) -> bool { 110 | for cfg in cfgs { 111 | let predicate = cfg.predicate.to_string(); 112 | if *cfg_lookup.get(&predicate).unwrap() == false { 113 | return false; 114 | } 115 | } 116 | true 117 | } 118 | 119 | fn advance_attribute_id( 120 | item: &impl HasAttributeId, 121 | ids: &mut HashMap, 122 | last: Option, 123 | ) -> syn::Result> { 124 | let next = { 125 | if let Some(archetype_id) = item.id() { 126 | Ok(archetype_id) 127 | } else if let Some(last) = last { 128 | if let Some(next) = last.checked_add(1) { 129 | Ok(next) 130 | } else { 131 | let span = item.name().span(); 132 | Err(syn::Error::new(span, "attribute id may not exceed 255")) 133 | } 134 | } else { 135 | Ok(0) // Start counting from 0 136 | } 137 | }?; 138 | 139 | // We can't have an archetype ID of 0 140 | if let Some(name) = ids.insert(next, item.name().to_string()) { 141 | Err(syn::Error::new( 142 | item.name().span(), 143 | format!("attribute id {} is already assigned to {}", next, name,), 144 | )) 145 | } else { 146 | // We have a valid, unused archetype ID 147 | Ok(Some(next)) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /macros/src/generate/cfg.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | 4 | use crate::parse::HasCfgPredicates; 5 | 6 | pub fn generate_cfg_checks_outer( 7 | name: &str, 8 | source: &S, 9 | raw: TokenStream, 10 | ) -> TokenStream { 11 | let predicates = source.collect_all_cfg_predicates(); 12 | let mut macros = Vec::::with_capacity(predicates.len()); 13 | 14 | let start = format_ident!("__cfg_ecs_{}_0", name); 15 | let finish = format_ident!("__impl_ecs_{}", name); 16 | 17 | if predicates.is_empty() { 18 | return quote!(::gecs::__internal::#finish!((), { #raw });); 19 | } 20 | 21 | for (idx, predicate) in predicates.iter().enumerate() { 22 | let this = format_ident!("__cfg_ecs_{}_{}", name, idx); 23 | let next = format_ident!("__cfg_ecs_{}_{}", name, idx + 1); 24 | 25 | let next = if (idx + 1) == predicates.len() { 26 | quote!(::gecs::__internal::#finish) 27 | } else { 28 | quote!(__ecs_cfg_macros::#next) 29 | }; 30 | 31 | macros.push(quote!( 32 | #[cfg(#predicate)] 33 | #[doc(hidden)] 34 | macro_rules! #this { 35 | (($($bools:expr),*), $($args:tt)*) => { 36 | #next!(($($bools,)* true), $($args)*); 37 | } 38 | } 39 | 40 | #[cfg(not(#predicate))] 41 | #[doc(hidden)] 42 | macro_rules! #this { 43 | (($($bools:expr),*), $($args:tt)*) => { 44 | #next!(($($bools,)* false), $($args)*); 45 | } 46 | } 47 | 48 | pub(super) use #this; 49 | )); 50 | } 51 | 52 | quote!( 53 | mod __ecs_cfg_macros { #(#macros)* } 54 | __ecs_cfg_macros::#start!((), { #raw }); 55 | ) 56 | } 57 | 58 | pub fn generate_cfg_checks_inner( 59 | name: &str, 60 | source: &S, 61 | raw: TokenStream, 62 | ) -> TokenStream { 63 | let predicates = source.collect_all_cfg_predicates(); 64 | let mut macros = Vec::::with_capacity(predicates.len()); 65 | 66 | let start = format_ident!("__cfg_ecs_{}_0", name); 67 | let finish = format_ident!("__impl_ecs_{}", name); 68 | 69 | if predicates.is_empty() { 70 | return quote!( 71 | { 72 | ::gecs::__internal::#finish!((), { #raw }) 73 | } 74 | ); 75 | } 76 | 77 | for (idx, predicate) in predicates.iter().enumerate() { 78 | let this = format_ident!("__cfg_ecs_{}_{}", name, idx); 79 | let next = format_ident!("__cfg_ecs_{}_{}", name, idx + 1); 80 | 81 | let next = if (idx + 1) == predicates.len() { 82 | quote!(::gecs::__internal::#finish) 83 | } else { 84 | quote!(#next) 85 | }; 86 | 87 | macros.push(quote!( 88 | #[cfg(#predicate)] 89 | #[doc(hidden)] 90 | macro_rules! #this { 91 | (($($bools:expr),*), $($args:tt)*) => { 92 | #next!(($($bools,)* true), $($args)*) 93 | } 94 | } 95 | 96 | #[cfg(not(#predicate))] 97 | #[doc(hidden)] 98 | macro_rules! #this { 99 | (($($bools:expr),*), $($args:tt)*) => { 100 | #next!(($($bools,)* false), $($args)*) 101 | } 102 | } 103 | )); 104 | } 105 | 106 | quote!( 107 | { 108 | // Generate the macros as part of the expression inline 109 | #(#macros)* 110 | 111 | #start!((), { #raw }) 112 | } 113 | ) 114 | } 115 | -------------------------------------------------------------------------------- /macros/src/generate/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] // Allow for type-like names to make quote!() clearer 2 | 3 | mod cfg; 4 | mod query; 5 | mod util; 6 | mod world; 7 | 8 | pub use cfg::*; 9 | pub use query::*; 10 | pub use world::*; 11 | -------------------------------------------------------------------------------- /macros/src/generate/query.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use convert_case::{Case, Casing}; 4 | use proc_macro2::{Ident, Span, TokenStream}; 5 | use quote::{format_ident, quote, quote_spanned, ToTokens}; 6 | 7 | use crate::data::{DataArchetype, DataWorld}; 8 | use crate::generate::util::to_snake; 9 | use crate::parse::ParseEcsComponentId; 10 | 11 | use crate::parse::{ 12 | ParseCfgDecorated, 13 | ParseQueryFind, //. 14 | ParseQueryIter, 15 | ParseQueryIterDestroy, 16 | ParseQueryParam, 17 | ParseQueryParamType, 18 | }; 19 | 20 | #[allow(non_snake_case)] 21 | pub fn generate_ecs_component_id(util: ParseEcsComponentId) -> TokenStream { 22 | let Component = &util.component; 23 | let Archetype = match util.archetype { 24 | Some(archetype) => archetype.into_token_stream(), 25 | None => quote!(MatchedArchetype), 26 | }; 27 | 28 | quote!(<#Archetype as ArchetypeHas<#Component>>::COMPONENT_ID) 29 | } 30 | 31 | // NOTE: We should avoid using panics to express errors in queries when generating. 32 | // Doing so will attribute the error to the ecs_world! declaration (due to the redirect 33 | // macro) rather than to the query macro itself. Always use an Err result where possible. 34 | 35 | #[derive(Clone, Copy, Debug)] 36 | pub enum FetchMode { 37 | Mut, 38 | Borrow, 39 | } 40 | 41 | #[allow(non_snake_case)] 42 | pub fn generate_query_find( 43 | mode: FetchMode, //. 44 | query: ParseCfgDecorated, 45 | ) -> syn::Result { 46 | let mut query_data = query.inner; 47 | let world_data = DataWorld::from_base64(&query_data.world_data); 48 | 49 | // Precompute the cfg-enabled status of any parameter in the predicate. 50 | for param in query_data.params.iter_mut() { 51 | param.is_cfg_enabled = is_cfg_enabled(param, &query.cfg_lookup); 52 | } 53 | 54 | let bound_params = bind_query_params(&world_data, &query_data.params)?; 55 | // NOTE: Beyond this point, query.params is only safe to use for information that 56 | // does not change depending on the type of the parameter (e.g. mutability). Anything 57 | // that might change after OneOf binding etc. must use the bound query params in 58 | // bound_params for the given archetype. Note that it's faster to use query.params 59 | // where available, since it avoids redundant computation for each archetype. 60 | 61 | // TODO PERF: We could avoid binding entirely if we know that the params have no OneOf. 62 | 63 | // Types 64 | let __WorldSelectTotal = format_ident!("__{}SelectTotal", world_data.name); 65 | 66 | // Variables and fields 67 | let world = &query_data.world; 68 | let entity = &query_data.entity; 69 | let body = &query_data.body; 70 | let arg = query_data.params.iter().map(to_name).collect::>(); 71 | let attrs = query_data 72 | .params 73 | .iter() 74 | .map(to_attributes) 75 | .collect::>(); 76 | 77 | // We want this to be hygenic because it's declared above the closure. 78 | let resolved_entity = quote_spanned!(Span::mixed_site() => entity); 79 | 80 | // Keywords 81 | let maybe_mut = query_data 82 | .params 83 | .iter() 84 | .map(to_maybe_mut) 85 | .collect::>(); 86 | 87 | // Explicit return value on the query 88 | let ret = match &query_data.ret { 89 | Some(ret) => quote!(-> #ret), 90 | None => quote!(), 91 | }; 92 | 93 | let mut queries = Vec::::new(); 94 | for archetype in world_data.archetypes { 95 | if let Some(bound_params) = bound_params.get(&archetype.name) { 96 | // Types and traits 97 | let Archetype = format_ident!("{}", archetype.name); 98 | let ArchetypeDirect = format_ident!("{}Direct", archetype.name); 99 | let Type = bound_params 100 | .iter() 101 | .map(|p| to_type(p, &archetype)) 102 | .collect::>(); // Bind-dependent! 103 | 104 | // Variables 105 | let archetype = format_ident!("{}", to_snake(&archetype.name)); 106 | 107 | // Fetch the archetype directly to allow queries to be sneaky with 108 | // direct archetype access to get cross-archetype nested mutability 109 | let get_archetype = match mode { 110 | FetchMode::Borrow => quote!(&#world.#archetype), 111 | FetchMode::Mut => quote!(&mut #world.#archetype), 112 | }; 113 | 114 | let fetch = match mode { 115 | FetchMode::Borrow => quote!(archetype.borrow(#resolved_entity)), 116 | FetchMode::Mut => quote!(archetype.view(#resolved_entity)), 117 | }; 118 | 119 | #[rustfmt::skip] 120 | let bind = match mode { 121 | FetchMode::Borrow => bound_params.iter().map(find_bind_borrow).collect::>(), 122 | FetchMode::Mut => bound_params.iter().map(find_bind_mut).collect::>(), 123 | }; 124 | 125 | queries.push(quote!( 126 | #__WorldSelectTotal::#Archetype(#resolved_entity) => { 127 | // Alias the current archetype for use in the closure. 128 | type MatchedArchetype = #Archetype; 129 | // The closure needs to be made per-archetype because of OneOf types. 130 | let mut closure = |#(#attrs #arg: &#maybe_mut #Type),*| #ret #body; 131 | 132 | let archetype = #get_archetype; 133 | let version = archetype.version(); 134 | 135 | #fetch.map(|found| closure(#(#attrs #bind),*)) 136 | } 137 | #__WorldSelectTotal::#ArchetypeDirect(#resolved_entity) => { 138 | // Alias the current archetype for use in the closure. 139 | type MatchedArchetype = #Archetype; 140 | // The closure needs to be made per-archetype because of OneOf types. 141 | let mut closure = |#(#attrs #arg: &#maybe_mut #Type),*| #ret #body; 142 | 143 | let archetype = #get_archetype; 144 | let version = archetype.version(); 145 | 146 | #fetch.map(|found| closure(#(#attrs #bind),*)) 147 | } 148 | )); 149 | } 150 | } 151 | 152 | if queries.is_empty() { 153 | Err(syn::Error::new_spanned( 154 | world, 155 | "query matched no archetypes in world", 156 | )) 157 | } else { 158 | Ok(quote!( 159 | { 160 | match #__WorldSelectTotal::try_from(#entity).expect("invalid entity type") { 161 | #(#queries)* 162 | _ => None, 163 | } 164 | } 165 | )) 166 | } 167 | } 168 | 169 | #[rustfmt::skip] 170 | fn find_bind_mut(param: &ParseQueryParam) -> TokenStream { 171 | match ¶m.param_type { 172 | ParseQueryParamType::Component(ident) => { 173 | let ident = to_snake_ident(ident); quote!(found.#ident) 174 | } 175 | ParseQueryParamType::Entity(_) => { 176 | quote!(found.entity) 177 | } 178 | ParseQueryParamType::EntityAny => { 179 | quote!(&(*found.entity).into()) 180 | } 181 | ParseQueryParamType::EntityWild => { 182 | quote!(found.entity) 183 | } 184 | ParseQueryParamType::EntityDirect(_) => { 185 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version)) 186 | } 187 | ParseQueryParamType::EntityDirectAny => { 188 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version).into()) 189 | } 190 | ParseQueryParamType::EntityDirectWild => { 191 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version)) 192 | } 193 | ParseQueryParamType::OneOf(_) => { 194 | panic!("must unpack OneOf first") 195 | } 196 | ParseQueryParamType::Option(_) => { 197 | todo!() // Not yet implemented 198 | } 199 | ParseQueryParamType::With(_) => { 200 | todo!() // Not yet implemented 201 | } 202 | ParseQueryParamType::Without(_) => { 203 | todo!() // Not yet implemented 204 | } 205 | } 206 | } 207 | 208 | #[rustfmt::skip] 209 | fn find_bind_borrow(param: &ParseQueryParam) -> TokenStream { 210 | match ¶m.param_type { 211 | ParseQueryParamType::Component(ident) => { 212 | match param.is_mut { 213 | true => quote!(&mut found.component_mut::<#ident>()), 214 | false => quote!(&found.component::<#ident>()), 215 | } 216 | } 217 | ParseQueryParamType::Entity(_) => { 218 | quote!(found.entity()) 219 | } 220 | ParseQueryParamType::EntityAny => { 221 | quote!(&(*found.entity()).into()) 222 | } 223 | ParseQueryParamType::EntityWild => { 224 | quote!(found.entity()) 225 | } 226 | ParseQueryParamType::EntityDirect(_) => { 227 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version)) 228 | } 229 | ParseQueryParamType::EntityDirectAny => { 230 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version).into()) 231 | } 232 | ParseQueryParamType::EntityDirectWild => { 233 | quote!(&::gecs::__internal::new_entity_direct::(found.index(), version)) 234 | } 235 | ParseQueryParamType::OneOf(_) => { 236 | panic!("must unpack OneOf first") 237 | } 238 | ParseQueryParamType::Option(_) => { 239 | todo!() // Not yet implemented 240 | } 241 | ParseQueryParamType::With(_) => { 242 | todo!() // Not yet implemented 243 | } 244 | ParseQueryParamType::Without(_) => { 245 | todo!() // Not yet implemented 246 | } 247 | } 248 | } 249 | 250 | #[allow(non_snake_case)] 251 | pub fn generate_query_iter( 252 | mode: FetchMode, //. 253 | query: ParseCfgDecorated, 254 | ) -> syn::Result { 255 | let mut query_data = query.inner; 256 | let world_data = DataWorld::from_base64(&query_data.world_data); 257 | 258 | // Precompute the cfg-enabled status of any parameter in the predicate. 259 | for param in query_data.params.iter_mut() { 260 | param.is_cfg_enabled = is_cfg_enabled(param, &query.cfg_lookup); 261 | } 262 | 263 | let bound_params = bind_query_params(&world_data, &query_data.params)?; 264 | // NOTE: Beyond this point, query.params is only safe to use for information that 265 | // does not change depending on the type of the parameter (e.g. mutability). Anything 266 | // that might change after OneOf binding etc. must use the bound query params in 267 | // bound_params for the given archetype. Note that it's faster to use query.params 268 | // where available, since it avoids redundant computation for each archetype. 269 | 270 | // TODO PERF: We could avoid binding entirely if we know that the params have no OneOf. 271 | 272 | // Variables and fields 273 | let world = &query_data.world; 274 | let body = &query_data.body; 275 | let arg = query_data.params.iter().map(to_name).collect::>(); 276 | let attrs = query_data 277 | .params 278 | .iter() 279 | .map(to_attributes) 280 | .collect::>(); 281 | 282 | // Special cases 283 | let maybe_mut = query_data 284 | .params 285 | .iter() 286 | .map(to_maybe_mut) 287 | .collect::>(); 288 | 289 | let mut queries = Vec::::new(); 290 | for archetype in world_data.archetypes { 291 | if let Some(bound_params) = bound_params.get(&archetype.name) { 292 | // Types and traits 293 | let Archetype = format_ident!("{}", archetype.name); 294 | let Type = bound_params 295 | .iter() 296 | .map(|p| to_type(p, &archetype)) 297 | .collect::>(); // Bind-dependent! 298 | 299 | // Variables 300 | let archetype = format_ident!("{}", to_snake(&archetype.name)); 301 | 302 | // Fetch the archetype directly to allow queries to be sneaky with 303 | // direct archetype access to get cross-archetype nested mutability 304 | #[rustfmt::skip] 305 | let get_archetype = match mode { 306 | FetchMode::Borrow => quote!(&#world.#archetype), 307 | FetchMode::Mut => quote!(&mut #world.#archetype), 308 | }; 309 | 310 | #[rustfmt::skip] 311 | let get_slices = match mode { 312 | FetchMode::Borrow => quote!(()), 313 | FetchMode::Mut => quote!(archetype.get_all_slices_mut()), 314 | }; 315 | 316 | #[rustfmt::skip] 317 | let bind = match mode { 318 | FetchMode::Borrow => bound_params.iter().map(iter_bind_borrow).collect::>(), 319 | FetchMode::Mut => bound_params.iter().map(iter_bind_mut).collect::>(), 320 | }; 321 | 322 | queries.push(quote!( 323 | { 324 | // Alias the current archetype for use in the closure 325 | type MatchedArchetype = #Archetype; 326 | // The closure needs to be made per-archetype because of OneOf types 327 | let mut closure = |#(#attrs #arg: &#maybe_mut #Type),*| #body; 328 | 329 | let archetype = #get_archetype; 330 | let version = archetype.version(); 331 | let len = archetype.len(); 332 | let slices = #get_slices; 333 | 334 | for idx in 0..len { 335 | match closure(#(#attrs #bind),*).into() { 336 | EcsStep::Continue => { 337 | // Continue 338 | }, 339 | EcsStep::Break => { 340 | return; 341 | }, 342 | } 343 | } 344 | } 345 | )); 346 | } 347 | } 348 | 349 | if queries.is_empty() { 350 | Err(syn::Error::new_spanned( 351 | world, 352 | "query matched no archetypes in world", 353 | )) 354 | } else { 355 | Ok(quote!( 356 | // Use a closure so we can use return to cancel other archetype iterations 357 | (||{#(#queries)*})() 358 | )) 359 | } 360 | } 361 | 362 | #[allow(non_snake_case)] 363 | pub fn generate_query_iter_destroy( 364 | mode: FetchMode, 365 | query: ParseCfgDecorated, 366 | ) -> syn::Result { 367 | let mut query_data = query.inner; 368 | let world_data = DataWorld::from_base64(&query_data.world_data); 369 | 370 | // Precompute the cfg-enabled status of any parameter in the predicate. 371 | for param in query_data.params.iter_mut() { 372 | param.is_cfg_enabled = is_cfg_enabled(param, &query.cfg_lookup); 373 | } 374 | 375 | let bound_params = bind_query_params(&world_data, &query_data.params)?; 376 | // NOTE: Beyond this point, query.params is only safe to use for information that 377 | // does not change depending on the type of the parameter (e.g. mutability). Anything 378 | // that might change after OneOf binding etc. must use the bound query params in 379 | // bound_params for the given archetype. Note that it's faster to use query.params 380 | // where available, since it avoids redundant computation for each archetype. 381 | 382 | // TODO PERF: We could avoid binding entirely if we know that the params have no OneOf. 383 | 384 | // Variables and fields 385 | let world = &query_data.world; 386 | let body = &query_data.body; 387 | let arg = query_data.params.iter().map(to_name).collect::>(); 388 | let attrs = query_data 389 | .params 390 | .iter() 391 | .map(to_attributes) 392 | .collect::>(); 393 | 394 | // Special cases 395 | let maybe_mut = query_data 396 | .params 397 | .iter() 398 | .map(to_maybe_mut) 399 | .collect::>(); 400 | 401 | let mut queries = Vec::::new(); 402 | for archetype in world_data.archetypes { 403 | if let Some(bound_params) = bound_params.get(&archetype.name) { 404 | // Types and traits 405 | let Archetype = format_ident!("{}", archetype.name); 406 | let Type = bound_params 407 | .iter() 408 | .map(|p| to_type(p, &archetype)) 409 | .collect::>(); // Bind-dependent! 410 | 411 | // Variables 412 | let archetype = format_ident!("{}", to_snake(&archetype.name)); 413 | 414 | // Fetch the archetype directly to allow queries to be sneaky with 415 | // direct archetype access to get cross-archetype nested mutability 416 | #[rustfmt::skip] 417 | let get_archetype = match mode { 418 | FetchMode::Borrow => quote!(&#world.#archetype), 419 | FetchMode::Mut => quote!(&mut #world.#archetype), 420 | }; 421 | 422 | #[rustfmt::skip] 423 | let get_slices = match mode { 424 | FetchMode::Borrow => panic!("borrow unsupported for iter_remove"), 425 | FetchMode::Mut => quote!(archetype.get_all_slices_mut()), 426 | }; 427 | 428 | #[rustfmt::skip] 429 | let bind = match mode { 430 | FetchMode::Borrow => panic!("borrow unsupported for iter_remove"), 431 | FetchMode::Mut => bound_params.iter().map(iter_bind_mut).collect::>(), 432 | }; 433 | 434 | queries.push(quote!( 435 | { 436 | // Alias the current archetype for use in the closure 437 | type MatchedArchetype = #Archetype; 438 | // The closure needs to be made per-archetype because of OneOf types 439 | let mut closure = |#(#attrs #arg: &#maybe_mut #Type),*| #body; 440 | 441 | let archetype = #get_archetype; 442 | let version = archetype.version(); 443 | let len = archetype.len(); 444 | 445 | // Iterate in reverse order to still visit each entity once. 446 | // Note: This assumes that we remove entities by swapping. 447 | for idx in (0..len).rev() { 448 | let slices = #get_slices; 449 | match closure(#(#attrs #bind),*).into() { 450 | EcsStepDestroy::Continue => { 451 | // Continue 452 | }, 453 | EcsStepDestroy::Break => { 454 | return; 455 | }, 456 | EcsStepDestroy::ContinueDestroy => { 457 | let entity = slices.entity[idx]; 458 | archetype.destroy(entity); 459 | }, 460 | EcsStepDestroy::BreakDestroy => { 461 | let entity = slices.entity[idx]; 462 | archetype.destroy(entity); 463 | return; 464 | }, 465 | } 466 | } 467 | } 468 | )); 469 | } 470 | } 471 | 472 | if queries.is_empty() { 473 | Err(syn::Error::new_spanned( 474 | world, 475 | "query matched no archetypes in world", 476 | )) 477 | } else { 478 | Ok(quote!( 479 | // Use a closure so we can use return to cancel other archetype iterations 480 | (||{#(#queries)*})() 481 | )) 482 | } 483 | } 484 | 485 | #[rustfmt::skip] 486 | fn iter_bind_mut(param: &ParseQueryParam) -> TokenStream { 487 | match ¶m.param_type { 488 | ParseQueryParamType::Component(ident) => { 489 | let ident = to_snake_ident(ident); 490 | match param.is_mut { 491 | true => quote!(&mut slices.#ident[idx]), 492 | false => quote!(&slices.#ident[idx]), 493 | } 494 | } 495 | ParseQueryParamType::Entity(_) => { 496 | quote!(&slices.entity[idx]) 497 | } 498 | ParseQueryParamType::EntityAny => { 499 | quote!(&slices.entity[idx].into()) 500 | } 501 | ParseQueryParamType::EntityWild => { 502 | quote!(&slices.entity[idx]) 503 | } 504 | ParseQueryParamType::EntityDirect(_) => { 505 | quote!(&::gecs::__internal::new_entity_direct::(idx, version)) 506 | } 507 | ParseQueryParamType::EntityDirectAny => { 508 | quote!(&::gecs::__internal::new_entity_direct::(idx, version).into()) 509 | } 510 | ParseQueryParamType::EntityDirectWild => { 511 | quote!(&::gecs::__internal::new_entity_direct::(idx, version)) 512 | } 513 | ParseQueryParamType::OneOf(_) => { 514 | panic!("must unpack OneOf first") 515 | } 516 | ParseQueryParamType::Option(_) => { 517 | todo!() // Not yet implemented 518 | } 519 | ParseQueryParamType::With(_) => { 520 | todo!() // Not yet implemented 521 | } 522 | ParseQueryParamType::Without(_) => { 523 | todo!() // Not yet implemented 524 | } 525 | } 526 | } 527 | 528 | #[rustfmt::skip] 529 | fn iter_bind_borrow(param: &ParseQueryParam) -> TokenStream { 530 | match ¶m.param_type { 531 | ParseQueryParamType::Component(ident) => { 532 | match param.is_mut { 533 | true => quote!(&mut archetype.borrow_slice_mut::<#ident>()[idx]), 534 | false => quote!(&archetype.borrow_slice::<#ident>()[idx]), 535 | } 536 | } 537 | ParseQueryParamType::Entity(_) => { 538 | quote!(&archetype.entities()[idx]) 539 | } 540 | ParseQueryParamType::EntityAny => { 541 | quote!(&archetype.entities()[idx].into()) 542 | } 543 | ParseQueryParamType::EntityWild => { 544 | quote!(&archetype.entities()[idx]) 545 | } 546 | ParseQueryParamType::EntityDirect(_) => { 547 | quote!(&::gecs::__internal::new_entity_direct::(idx, version)) 548 | } 549 | ParseQueryParamType::EntityDirectAny => { 550 | quote!(&::gecs::__internal::new_entity_direct::(idx, version).into()) 551 | } 552 | ParseQueryParamType::EntityDirectWild => { 553 | quote!(&::gecs::__internal::new_entity_direct::(idx, version)) 554 | } 555 | ParseQueryParamType::OneOf(_) => { 556 | panic!("must unpack OneOf first") 557 | } 558 | ParseQueryParamType::Option(_) => { 559 | todo!() // Not yet implemented 560 | } 561 | ParseQueryParamType::With(_) => { 562 | todo!() // Not yet implemented 563 | } 564 | ParseQueryParamType::Without(_) => { 565 | todo!() // Not yet implemented 566 | } 567 | } 568 | } 569 | 570 | fn to_name(param: &ParseQueryParam) -> TokenStream { 571 | let name = ¶m.name; 572 | quote!(#name) 573 | } 574 | 575 | #[rustfmt::skip] 576 | fn to_type(param: &ParseQueryParam, archetype: &DataArchetype) -> TokenStream { 577 | let archetype_name = format_ident!("{}", archetype.name); 578 | match ¶m.param_type { 579 | ParseQueryParamType::Component(ident) => quote!(#ident), 580 | ParseQueryParamType::Entity(ident) => quote!(Entity<#ident>), 581 | ParseQueryParamType::EntityAny => quote!(EntityAny), 582 | ParseQueryParamType::EntityWild => quote!(Entity<#archetype_name>), 583 | ParseQueryParamType::EntityDirect(ident) => quote!(EntityDirect<#ident>), 584 | ParseQueryParamType::EntityDirectAny => quote!(EntityDirectAny), 585 | ParseQueryParamType::EntityDirectWild => quote!(EntityDirect<#archetype_name>), 586 | ParseQueryParamType::OneOf(_) => panic!("must unpack OneOf first"), 587 | ParseQueryParamType::Option(_) => todo!(), 588 | ParseQueryParamType::With(_) => todo!(), 589 | ParseQueryParamType::Without(_) => todo!(), 590 | } 591 | } 592 | 593 | fn to_maybe_mut(param: &ParseQueryParam) -> TokenStream { 594 | match ¶m.is_mut { 595 | true => quote!(mut), 596 | false => quote!(), 597 | } 598 | } 599 | 600 | fn to_snake_ident(ident: &Ident) -> Ident { 601 | Ident::new(&to_snake_str(&ident.to_string()), ident.span()) 602 | } 603 | 604 | fn to_snake_str(name: &String) -> String { 605 | name.from_case(Case::Pascal).to_case(Case::Snake) 606 | } 607 | 608 | fn to_attributes(param: &ParseQueryParam) -> TokenStream { 609 | let mut attrs = TokenStream::new(); 610 | for cfg in param.cfgs.iter() { 611 | let predicate = &cfg.predicate; 612 | attrs.extend(quote!(#[cfg(#predicate)])); 613 | } 614 | attrs 615 | } 616 | 617 | fn is_cfg_enabled(param: &ParseQueryParam, cfg_lookup: &HashMap) -> bool { 618 | for cfg in param.cfgs.iter() { 619 | if *cfg_lookup.get(&cfg.predicate.to_string()).unwrap() == false { 620 | return false; 621 | } 622 | } 623 | return true; 624 | } 625 | 626 | fn bind_query_params( 627 | world_data: &DataWorld, 628 | params: &[ParseQueryParam], 629 | ) -> syn::Result>> { 630 | let mut result = HashMap::new(); 631 | let mut bound = Vec::new(); 632 | 633 | for archetype in world_data.archetypes.iter() { 634 | bound.clear(); 635 | 636 | for param in params { 637 | match ¶m.param_type { 638 | ParseQueryParamType::EntityAny => { 639 | bound.push(param.clone()); // Always matches 640 | } 641 | ParseQueryParamType::EntityDirectAny => { 642 | bound.push(param.clone()); // Always matches 643 | } 644 | ParseQueryParamType::EntityWild => { 645 | bound.push(param.clone()); // Always matches 646 | } 647 | ParseQueryParamType::EntityDirectWild => { 648 | bound.push(param.clone()); // Always matches 649 | } 650 | ParseQueryParamType::Component(name) => { 651 | if param.is_cfg_enabled == false || archetype.contains_component(name) { 652 | bound.push(param.clone()); 653 | } else { 654 | continue; // No need to check more 655 | } 656 | } 657 | ParseQueryParamType::Entity(name) => { 658 | if param.is_cfg_enabled == false || archetype.name == name.to_string() { 659 | bound.push(param.clone()); 660 | } else { 661 | continue; // No need to check more 662 | } 663 | } 664 | ParseQueryParamType::EntityDirect(name) => { 665 | if param.is_cfg_enabled == false || archetype.name == name.to_string() { 666 | bound.push(param.clone()); 667 | } else { 668 | continue; // No need to check more 669 | } 670 | } 671 | ParseQueryParamType::OneOf(args) => { 672 | if param.cfgs.len() > 0 { 673 | return Err(syn::Error::new( 674 | param.name.span(), 675 | "cfg attributes not currently supported on OneOf", 676 | )); 677 | } 678 | 679 | if let Some(found) = bind_one_of(archetype, args)? { 680 | // Convert this to a new Component type 681 | bound.push(ParseQueryParam { 682 | cfgs: param.cfgs.clone(), 683 | name: param.name.clone(), 684 | is_mut: param.is_mut, 685 | param_type: found, 686 | is_cfg_enabled: param.is_cfg_enabled, 687 | }); 688 | } else { 689 | continue; // No need to check more 690 | } 691 | } 692 | ParseQueryParamType::Option(_) => { 693 | todo!() // Not yet implemented 694 | } 695 | ParseQueryParamType::With(_) => { 696 | todo!() // Not yet implemented 697 | } 698 | ParseQueryParamType::Without(_) => { 699 | todo!() // Not yet implemented 700 | } 701 | } 702 | } 703 | 704 | // Did we remap everything? 705 | if bound.len() == params.len() { 706 | result.insert(archetype.name.clone(), bound.clone()); 707 | } 708 | } 709 | 710 | Ok(result) 711 | } 712 | 713 | fn bind_one_of( 714 | archetype: &DataArchetype, //. 715 | one_of_args: &[Ident], 716 | ) -> syn::Result> { 717 | let mut found: Option = None; 718 | 719 | for arg in one_of_args.iter() { 720 | if archetype.contains_component(arg) { 721 | // An OneOf can only match one component in a given archetype 722 | if let Some(found) = found { 723 | return Err(syn::Error::new( 724 | arg.span(), 725 | format!( 726 | "OneOf parameter is ambiguous for {}, matching both {} and {}", 727 | archetype.name, 728 | found.to_string(), 729 | arg.to_string(), 730 | ), 731 | )); 732 | } 733 | 734 | // We found at least one match for this archetype 735 | found = Some(arg.clone()); 736 | } 737 | } 738 | 739 | // TODO: What about OneOf, Entity>? 740 | Ok(found.map(|ident| ParseQueryParamType::Component(ident))) 741 | } 742 | -------------------------------------------------------------------------------- /macros/src/generate/util.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | 3 | pub fn to_snake(name: &String) -> String { 4 | name.from_case(Case::Pascal).to_case(Case::Snake) 5 | } 6 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod data; 2 | mod generate; 3 | mod parse; 4 | 5 | use data::DataWorld; 6 | use proc_macro::TokenStream; 7 | use syn::{self, parse_macro_input}; 8 | 9 | use generate::FetchMode; 10 | 11 | use parse::*; 12 | 13 | /// See `ecs_component_id` in the `gecs` docs for more information. 14 | #[proc_macro] 15 | pub fn ecs_component_id(args: TokenStream) -> TokenStream { 16 | let util = parse_macro_input!(args as ParseEcsComponentId); 17 | generate::generate_ecs_component_id(util).into() 18 | } 19 | 20 | /// See `ecs_world` in the `gecs` docs for more information. 21 | #[proc_macro] 22 | pub fn ecs_world(args: TokenStream) -> TokenStream { 23 | __expand_ecs_world(args) // Redirect for consistency 24 | } 25 | 26 | #[proc_macro] 27 | #[doc(hidden)] 28 | pub fn __expand_ecs_world(args: TokenStream) -> TokenStream { 29 | let raw = args.clone().into(); // We'll need to parse twice 30 | let world_parse = parse_macro_input!(args as ParseEcsWorld); 31 | 32 | generate::generate_cfg_checks_outer("world", &world_parse, raw).into() 33 | } 34 | 35 | #[proc_macro] 36 | #[doc(hidden)] 37 | pub fn __impl_ecs_world(args: TokenStream) -> TokenStream { 38 | let raw_input = args.to_string(); 39 | 40 | match DataWorld::new(parse_macro_input!(args as ParseCfgDecorated)) { 41 | Ok(world_data) => generate::generate_world(&world_data, &raw_input).into(), 42 | Err(err) => err.into_compile_error().into(), 43 | } 44 | } 45 | 46 | #[proc_macro] 47 | #[doc(hidden)] 48 | pub fn __expand_ecs_find(args: TokenStream) -> TokenStream { 49 | let raw = args.clone().into(); // We'll need to parse twice 50 | let query_parse = parse_macro_input!(args as ParseQueryFind); 51 | generate::generate_cfg_checks_inner("find", &query_parse, raw).into() 52 | } 53 | 54 | #[proc_macro] 55 | #[doc(hidden)] 56 | pub fn __impl_ecs_find(args: TokenStream) -> TokenStream { 57 | let query_parse = parse_macro_input!(args as ParseCfgDecorated); 58 | 59 | match generate::generate_query_find(FetchMode::Mut, query_parse) { 60 | Ok(tokens) => tokens.into(), 61 | Err(err) => err.into_compile_error().into(), 62 | } 63 | } 64 | 65 | #[proc_macro] 66 | #[doc(hidden)] 67 | pub fn __expand_ecs_find_borrow(args: TokenStream) -> TokenStream { 68 | let raw = args.clone().into(); // We'll need to parse twice 69 | let query_parse = parse_macro_input!(args as ParseQueryFind); 70 | generate::generate_cfg_checks_inner("find_borrow", &query_parse, raw).into() 71 | } 72 | 73 | #[proc_macro] 74 | #[doc(hidden)] 75 | pub fn __impl_ecs_find_borrow(args: TokenStream) -> TokenStream { 76 | let query_parse = parse_macro_input!(args as ParseCfgDecorated); 77 | 78 | match generate::generate_query_find(FetchMode::Borrow, query_parse) { 79 | Ok(tokens) => tokens.into(), 80 | Err(err) => err.into_compile_error().into(), 81 | } 82 | } 83 | 84 | #[proc_macro] 85 | #[doc(hidden)] 86 | pub fn __expand_ecs_iter(args: TokenStream) -> TokenStream { 87 | let raw = args.clone().into(); // We'll need to parse twice 88 | let query_parse = parse_macro_input!(args as ParseQueryIter); 89 | generate::generate_cfg_checks_inner("iter", &query_parse, raw).into() 90 | } 91 | 92 | #[proc_macro] 93 | #[doc(hidden)] 94 | pub fn __impl_ecs_iter(args: TokenStream) -> TokenStream { 95 | let query_parse = parse_macro_input!(args as ParseCfgDecorated); 96 | 97 | match generate::generate_query_iter(FetchMode::Mut, query_parse) { 98 | Ok(tokens) => tokens.into(), 99 | Err(err) => err.into_compile_error().into(), 100 | } 101 | } 102 | 103 | #[proc_macro] 104 | #[doc(hidden)] 105 | pub fn __expand_ecs_iter_borrow(args: TokenStream) -> TokenStream { 106 | let raw = args.clone().into(); // We'll need to parse twice 107 | let query_parse = parse_macro_input!(args as ParseQueryIter); 108 | generate::generate_cfg_checks_inner("iter_borrow", &query_parse, raw).into() 109 | } 110 | 111 | #[proc_macro] 112 | #[doc(hidden)] 113 | pub fn __impl_ecs_iter_borrow(args: TokenStream) -> TokenStream { 114 | let query_parse = parse_macro_input!(args as ParseCfgDecorated); 115 | 116 | match generate::generate_query_iter(FetchMode::Borrow, query_parse) { 117 | Ok(tokens) => tokens.into(), 118 | Err(err) => err.into_compile_error().into(), 119 | } 120 | } 121 | 122 | #[proc_macro] 123 | #[doc(hidden)] 124 | pub fn __expand_ecs_iter_destroy(args: TokenStream) -> TokenStream { 125 | let raw = args.clone().into(); // We'll need to parse twice 126 | let query_parse = parse_macro_input!(args as ParseQueryIterDestroy); 127 | generate::generate_cfg_checks_inner("iter_destroy", &query_parse, raw).into() 128 | } 129 | 130 | #[proc_macro] 131 | #[doc(hidden)] 132 | pub fn __impl_ecs_iter_destroy(args: TokenStream) -> TokenStream { 133 | let query_parse = parse_macro_input!(args as ParseCfgDecorated); 134 | 135 | match generate::generate_query_iter_destroy(FetchMode::Mut, query_parse) { 136 | Ok(tokens) => tokens.into(), 137 | Err(err) => err.into_compile_error().into(), 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /macros/src/parse/attribute.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use syn::parse::{Parse, ParseStream}; 3 | use syn::{bracketed, parenthesized, Ident, LitInt, Token}; 4 | 5 | use super::*; 6 | 7 | mod kw { 8 | syn::custom_keyword!(cfg); 9 | 10 | syn::custom_keyword!(archetype_id); 11 | syn::custom_keyword!(component_id); 12 | } 13 | 14 | pub(super) fn parse_attributes(input: ParseStream) -> syn::Result> { 15 | let mut attrs = Vec::new(); 16 | while input.peek(Token![#]) { 17 | attrs.push(input.parse()?); 18 | } 19 | Ok(attrs) 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct ParseAttribute { 24 | pub span: Span, 25 | pub data: ParseAttributeData, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub enum ParseAttributeData { 30 | Cfg(ParseAttributeCfg), 31 | ArchetypeId(ParseAttributeId), 32 | ComponentId(ParseAttributeId), 33 | } 34 | 35 | #[derive(Clone, Debug)] 36 | pub struct ParseAttributeCfg { 37 | pub predicate: TokenStream, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct ParseAttributeId { 42 | pub value: u8, 43 | } 44 | 45 | impl Parse for ParseAttribute { 46 | fn parse(input: ParseStream) -> syn::Result { 47 | input.parse::()?; 48 | let content; 49 | bracketed!(content in input); 50 | 51 | let span = content.span(); 52 | let lookahead = content.lookahead1(); 53 | let data = if lookahead.peek(kw::cfg) { 54 | content.parse::()?; 55 | ParseAttributeData::Cfg(content.parse()?) 56 | } else if lookahead.peek(kw::archetype_id) { 57 | content.parse::()?; 58 | ParseAttributeData::ArchetypeId(content.parse()?) 59 | } else if lookahead.peek(kw::component_id) { 60 | content.parse::()?; 61 | ParseAttributeData::ComponentId(content.parse()?) 62 | } else { 63 | return Err(lookahead.error()); 64 | }; 65 | 66 | Ok(Self { span, data }) 67 | } 68 | } 69 | 70 | impl Parse for ParseAttributeId { 71 | fn parse(input: ParseStream) -> syn::Result { 72 | let args; 73 | parenthesized!(args in input); 74 | 75 | // Grab the int literal and make sure it's the right type 76 | let value = args.parse::()?.base10_parse()?; 77 | 78 | Ok(Self { value }) 79 | } 80 | } 81 | 82 | impl HasAttributeId for ParseArchetype { 83 | fn name(&self) -> &Ident { 84 | &self.name 85 | } 86 | 87 | fn id(&self) -> Option { 88 | self.id 89 | } 90 | } 91 | 92 | impl HasAttributeId for ParseComponent { 93 | fn name(&self) -> &Ident { 94 | &self.name 95 | } 96 | 97 | fn id(&self) -> Option { 98 | self.id 99 | } 100 | } 101 | 102 | impl Parse for ParseAttributeCfg { 103 | fn parse(input: ParseStream) -> syn::Result { 104 | let args; 105 | parenthesized!(args in input); 106 | 107 | // Don't care about parsing the predicate contents 108 | let predicate = args.parse::()?; 109 | 110 | Ok(Self { predicate }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /macros/src/parse/cfg.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use proc_macro2::TokenStream; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::punctuated::Punctuated; 6 | use syn::token::Comma; 7 | use syn::{braced, parenthesized, LitBool}; 8 | 9 | pub trait HasCfgPredicates { 10 | fn collect_all_cfg_predicates(&self) -> Vec; 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct ParseCfgDecorated { 15 | pub cfg_lookup: HashMap, 16 | pub inner: T, 17 | } 18 | 19 | impl Parse for ParseCfgDecorated { 20 | fn parse(input: ParseStream) -> syn::Result { 21 | // Parse the deduced states for each cfg in the world definition 22 | let states; 23 | parenthesized!(states in input); 24 | let states: Vec = Punctuated::::parse_terminated(&states)? 25 | .into_iter() 26 | .map(|bool| bool.value) 27 | .collect(); 28 | input.parse::()?; 29 | 30 | // Re-parse the world itself (TODO: This is wasteful!) 31 | let inner; 32 | braced!(inner in input); 33 | let inner = inner.parse::()?; 34 | 35 | // Create a map of each cfg to its deduced state 36 | let mut predicates = inner.collect_all_cfg_predicates(); 37 | let mut cfg_lookup = HashMap::with_capacity(states.len()); 38 | 39 | assert!(predicates.len() == states.len()); 40 | for (predicate, state) in predicates.drain(..).zip(states) { 41 | cfg_lookup.insert(predicate.to_string(), state); 42 | } 43 | 44 | Ok(Self { cfg_lookup, inner }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /macros/src/parse/mod.rs: -------------------------------------------------------------------------------- 1 | mod attribute; 2 | mod cfg; 3 | mod query; 4 | mod util; 5 | mod world; 6 | 7 | pub use attribute::*; 8 | pub use cfg::*; 9 | pub use query::*; 10 | pub use util::*; 11 | pub use world::*; 12 | 13 | fn is_allowed_component_name(name: &str) -> bool { 14 | match name { 15 | "Entity" => false, 16 | "EntityAny" => false, 17 | "OneOf" => false, 18 | "AnyOf" => false, // Reserved 19 | "Option" => false, // Reserved 20 | _ => true, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /macros/src/parse/query.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::TokenStream; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::token::{Colon, Comma, Gt, Lt, Mut}; 6 | use syn::{Expr, Ident, LitStr, Token, Type}; 7 | 8 | use super::{parse_attributes, HasCfgPredicates, ParseAttributeCfg, ParseAttributeData}; 9 | 10 | mod kw { 11 | syn::custom_keyword!(archetype); 12 | syn::custom_keyword!(cfg); 13 | 14 | syn::custom_keyword!(Entity); 15 | syn::custom_keyword!(EntityAny); 16 | syn::custom_keyword!(EntityDirect); 17 | syn::custom_keyword!(EntityDirectAny); 18 | 19 | syn::custom_keyword!(OneOf); 20 | syn::custom_keyword!(Option); 21 | syn::custom_keyword!(With); 22 | syn::custom_keyword!(Without); 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct ParseQueryFind { 27 | pub world_data: String, 28 | pub world: Expr, 29 | pub entity: Expr, 30 | pub params: Vec, 31 | pub ret: Option, 32 | pub body: Expr, 33 | } 34 | 35 | #[derive(Debug)] 36 | pub struct ParseQueryIter { 37 | pub world_data: String, 38 | pub world: Expr, 39 | pub params: Vec, 40 | pub body: Expr, 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct ParseQueryIterDestroy { 45 | pub world_data: String, 46 | pub world: Expr, 47 | pub params: Vec, 48 | pub body: Expr, 49 | } 50 | 51 | #[derive(Clone, Debug)] 52 | pub struct ParseQueryParam { 53 | pub cfgs: Vec, 54 | pub name: Ident, 55 | pub is_mut: bool, 56 | pub param_type: ParseQueryParamType, 57 | 58 | // Set during generation 59 | pub is_cfg_enabled: bool, 60 | } 61 | 62 | #[derive(Clone, Debug)] 63 | #[allow(dead_code)] 64 | pub enum ParseQueryParamType { 65 | Component(Ident), // CompFoo 66 | 67 | // Entity Types 68 | Entity(Ident), // Entity 69 | EntityWild, // Entity<_> 70 | EntityAny, // EntityAny 71 | EntityDirect(Ident), // EntityDirect 72 | EntityDirectWild, // EntityDirect<_> 73 | EntityDirectAny, // EntityDirectAny 74 | 75 | // Special Types 76 | OneOf(Box<[Ident]>), // OneOf 77 | Option(Ident), // Option -- TODO: RESERVED 78 | With(Ident), // With -- TODO: RESERVED 79 | Without(Ident), // Without -- TODO: RESERVED 80 | } 81 | 82 | impl Parse for ParseQueryFind { 83 | fn parse(input: ParseStream) -> syn::Result { 84 | // Parse out the hidden serialized world data 85 | let world_data = input.parse::()?; 86 | input.parse::()?; 87 | 88 | // Parse out the meta-arguments for the query 89 | let world = input.parse()?; 90 | input.parse::()?; 91 | let entity = input.parse()?; 92 | input.parse::()?; 93 | 94 | // Parse out the closure arguments 95 | input.parse::()?; 96 | let params = parse_params(&input)?; 97 | input.parse::()?; 98 | 99 | // Parse a return type, if there is one 100 | let ret = match input.parse::]>>()? { 101 | Some(_) => Some(input.parse::()?), 102 | None => None, 103 | }; 104 | 105 | // Parse the rest of the body, including the braces (if any) 106 | let body = input.parse::()?; 107 | 108 | Ok(Self { 109 | world_data: world_data.value(), 110 | world, 111 | entity, 112 | params, 113 | ret, 114 | body, 115 | }) 116 | } 117 | } 118 | 119 | impl Parse for ParseQueryIter { 120 | fn parse(input: ParseStream) -> syn::Result { 121 | // Parse out the hidden serialized world data 122 | let world_data = input.parse::()?; 123 | input.parse::()?; 124 | 125 | // Parse out the meta-arguments for the query 126 | let world = input.parse()?; 127 | input.parse::()?; 128 | 129 | // Parse out the closure arguments 130 | input.parse::()?; 131 | let params = parse_params(&input)?; 132 | input.parse::()?; 133 | 134 | // Parse the rest of the body, including the braces (if any) 135 | let body = input.parse::()?; 136 | 137 | Ok(Self { 138 | world_data: world_data.value(), 139 | world, 140 | params, 141 | body, 142 | }) 143 | } 144 | } 145 | 146 | impl Parse for ParseQueryIterDestroy { 147 | fn parse(input: ParseStream) -> syn::Result { 148 | // Parse out the hidden serialized world data 149 | let world_data = input.parse::()?; 150 | input.parse::()?; 151 | 152 | // Parse out the meta-arguments for the query 153 | let world = input.parse()?; 154 | input.parse::()?; 155 | 156 | // Parse out the closure arguments 157 | input.parse::()?; 158 | let params = parse_params(&input)?; 159 | input.parse::()?; 160 | 161 | // Parse the rest of the body, including the braces (if any) 162 | let body = input.parse::()?; 163 | 164 | Ok(Self { 165 | world_data: world_data.value(), 166 | world, 167 | params, 168 | body, 169 | }) 170 | } 171 | } 172 | 173 | impl Parse for ParseQueryParam { 174 | fn parse(input: ParseStream) -> syn::Result { 175 | let mut attributes = Vec::new(); 176 | 177 | // Pull out the cfg attributes from those decorating the param 178 | for attribute in parse_attributes(input)?.drain(..) { 179 | if let ParseAttributeData::Cfg(cfg) = attribute.data { 180 | attributes.push(cfg); 181 | } else { 182 | return Err(syn::Error::new( 183 | attribute.span, 184 | "invalid attribute for this position", 185 | )); 186 | } 187 | } 188 | 189 | // Parse the name and following : token 190 | let name = parse_param_name(input)?; 191 | input.parse::()?; 192 | 193 | input.parse::()?; 194 | let is_mut = input.parse::>()?.is_some(); 195 | let check_span = input.span(); 196 | let ty = input.parse::()?; 197 | 198 | // Enforce mutability rules 199 | match ty { 200 | ParseQueryParamType::Entity(_) 201 | | ParseQueryParamType::EntityAny 202 | | ParseQueryParamType::EntityWild 203 | | ParseQueryParamType::EntityDirect(_) 204 | | ParseQueryParamType::EntityDirectAny 205 | | ParseQueryParamType::EntityDirectWild 206 | if is_mut => 207 | { 208 | Err(syn::Error::new( 209 | check_span, 210 | "mut entity access is forbidden", 211 | )) 212 | } 213 | _ => Ok(Self { 214 | cfgs: attributes, 215 | name, 216 | is_mut, 217 | param_type: ty, 218 | is_cfg_enabled: true, // Default to true 219 | }), 220 | } 221 | } 222 | } 223 | 224 | impl Parse for ParseQueryParamType { 225 | fn parse(input: ParseStream) -> syn::Result { 226 | let lookahead = input.lookahead1(); 227 | if lookahead.peek(kw::Entity) { 228 | // Entity<...> 229 | input.parse::()?; 230 | input.parse::()?; 231 | 232 | // Entity or Entity<_> 233 | let lookahead = input.lookahead1(); 234 | if lookahead.peek(Token![_]) { 235 | input.parse::()?; 236 | input.parse::()?; 237 | Ok(ParseQueryParamType::EntityWild) 238 | } else if lookahead.peek(Ident) { 239 | let archetype = input.parse::()?; 240 | input.parse::()?; 241 | Ok(ParseQueryParamType::Entity(archetype)) 242 | } else { 243 | Err(lookahead.error()) 244 | } 245 | } else if lookahead.peek(kw::EntityDirect) { 246 | // EntityDirect<...> 247 | input.parse::()?; 248 | input.parse::()?; 249 | 250 | // EntityDirect or EntityDirect<_> 251 | let lookahead = input.lookahead1(); 252 | if lookahead.peek(Token![_]) { 253 | input.parse::()?; 254 | input.parse::()?; 255 | Ok(ParseQueryParamType::EntityDirectWild) 256 | } else if lookahead.peek(Ident) { 257 | let archetype = input.parse::()?; 258 | input.parse::()?; 259 | Ok(ParseQueryParamType::EntityDirect(archetype)) 260 | } else { 261 | Err(lookahead.error()) 262 | } 263 | } else if lookahead.peek(kw::EntityAny) { 264 | // EntityAny 265 | input.parse::()?; 266 | Ok(ParseQueryParamType::EntityAny) 267 | } else if lookahead.peek(kw::EntityDirectAny) { 268 | // EntityDirectAny 269 | input.parse::()?; 270 | Ok(ParseQueryParamType::EntityDirectAny) 271 | } else if lookahead.peek(kw::OneOf) { 272 | // OneOf 273 | input.parse::()?; 274 | input.parse::()?; 275 | let mut result = Vec::::new(); 276 | loop { 277 | let lookahead = input.lookahead1(); 278 | if lookahead.peek(Ident) { 279 | result.push(input.parse::()?); 280 | input.parse::>()?; 281 | } else if lookahead.peek(Token![>]) { 282 | input.parse::]>()?; 283 | break Ok(ParseQueryParamType::OneOf(result.into_boxed_slice())); 284 | } else { 285 | break Err(lookahead.error()); 286 | } 287 | } 288 | } else if lookahead.peek(kw::Option) { 289 | Err(syn::Error::new( 290 | input.span(), 291 | "reserved special 'Option' not yet implemented", 292 | )) 293 | } else if lookahead.peek(kw::With) { 294 | Err(syn::Error::new( 295 | input.span(), 296 | "reserved special 'With' not yet implemented", 297 | )) 298 | } else if lookahead.peek(kw::Without) { 299 | Err(syn::Error::new( 300 | input.span(), 301 | "reserved special 'Without' not yet implemented", 302 | )) 303 | } else if lookahead.peek(Ident) { 304 | // Component 305 | Ok(ParseQueryParamType::Component(input.parse()?)) 306 | } else { 307 | Err(lookahead.error()) 308 | } 309 | } 310 | } 311 | 312 | impl HasCfgPredicates for ParseQueryFind { 313 | fn collect_all_cfg_predicates(&self) -> Vec { 314 | get_cfg_predicates(&self.params) 315 | } 316 | } 317 | 318 | impl HasCfgPredicates for ParseQueryIter { 319 | fn collect_all_cfg_predicates(&self) -> Vec { 320 | get_cfg_predicates(&self.params) 321 | } 322 | } 323 | 324 | impl HasCfgPredicates for ParseQueryIterDestroy { 325 | fn collect_all_cfg_predicates(&self) -> Vec { 326 | get_cfg_predicates(&self.params) 327 | } 328 | } 329 | 330 | fn parse_params(input: &ParseStream) -> syn::Result> { 331 | let mut result = Vec::::new(); 332 | loop { 333 | let lookahead = input.lookahead1(); 334 | if lookahead.peek(Token![|]) { 335 | return Ok(result); 336 | } else if lookahead.peek(Ident) || lookahead.peek(Token![_]) || lookahead.peek(Token![#]) { 337 | result.push(input.parse::()?); 338 | input.parse::>()?; 339 | } else { 340 | return Err(lookahead.error()); 341 | } 342 | } 343 | } 344 | 345 | fn parse_param_name(input: ParseStream) -> syn::Result { 346 | let lookahead = input.lookahead1(); 347 | if lookahead.peek(Token![_]) { 348 | let token = input.parse::()?; 349 | Ok(Ident::new("_", token.span)) 350 | } else if lookahead.peek(Ident) { 351 | Ok(input.parse::()?) 352 | } else { 353 | Err(lookahead.error()) 354 | } 355 | } 356 | 357 | fn get_cfg_predicates(params: &Vec) -> Vec { 358 | let mut filter = HashSet::new(); 359 | let mut result = Vec::new(); 360 | 361 | // Filter duplicates while keeping order for determinism 362 | for param in params.iter() { 363 | for cfg in param.cfgs.iter() { 364 | let predicate_tokens = cfg.predicate.clone(); 365 | let predicate_string = predicate_tokens.to_string(); 366 | 367 | if filter.insert(predicate_string) { 368 | result.push(predicate_tokens); 369 | } 370 | } 371 | } 372 | 373 | result 374 | } 375 | -------------------------------------------------------------------------------- /macros/src/parse/util.rs: -------------------------------------------------------------------------------- 1 | use syn::parse::{Parse, ParseStream}; 2 | use syn::token::Comma; 3 | use syn::{Ident, Path}; 4 | 5 | #[derive(Debug)] 6 | pub struct ParseEcsComponentId { 7 | pub component: Ident, 8 | pub archetype: Option, 9 | } 10 | 11 | impl Parse for ParseEcsComponentId { 12 | fn parse(input: ParseStream) -> syn::Result { 13 | let component = input.parse::()?; 14 | 15 | if input.parse::>()?.is_some() { 16 | let archetype = Some(input.parse::()?); 17 | input.parse::>()?; 18 | 19 | Ok(Self { 20 | component, 21 | archetype, 22 | }) 23 | } else { 24 | Ok(Self { 25 | component, 26 | archetype: None, 27 | }) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /macros/src/parse/world.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::format_ident; 5 | use syn::parse::{Parse, ParseStream}; 6 | use syn::punctuated::Punctuated; 7 | use syn::token::{Comma, Semi}; 8 | use syn::{parenthesized, Ident, Token}; 9 | 10 | use super::*; 11 | 12 | mod kw { 13 | syn::custom_keyword!(cfg); 14 | 15 | syn::custom_keyword!(archetype_id); 16 | syn::custom_keyword!(component_id); 17 | 18 | syn::custom_keyword!(ecs_archetype); 19 | syn::custom_keyword!(ecs_name); 20 | } 21 | 22 | pub trait HasAttributeId { 23 | fn name(&self) -> &Ident; 24 | fn id(&self) -> Option; 25 | } 26 | 27 | #[derive(Debug)] 28 | pub struct ParseEcsWorld { 29 | pub name: Ident, 30 | pub archetypes: Vec, 31 | } 32 | 33 | #[derive(Debug)] 34 | pub enum ParseItem { 35 | ParseName(ParseName), 36 | ParseArchetype(ParseArchetype), 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct ParseName { 41 | pub name: Ident, 42 | } 43 | 44 | #[derive(Debug)] 45 | pub struct ParseArchetype { 46 | pub cfgs: Vec, 47 | pub id: Option, 48 | pub name: Ident, 49 | pub components: Vec, 50 | } 51 | 52 | #[derive(Debug)] 53 | pub struct ParseComponent { 54 | pub cfgs: Vec, 55 | pub id: Option, 56 | pub name: Ident, 57 | } 58 | 59 | impl HasCfgPredicates for ParseEcsWorld { 60 | fn collect_all_cfg_predicates(&self) -> Vec { 61 | let mut filter = HashSet::new(); 62 | let mut result = Vec::new(); 63 | 64 | // Filter duplicates while keeping order for determinism 65 | for archetype in self.archetypes.iter() { 66 | for cfg in archetype.cfgs.iter() { 67 | let predicate_tokens = cfg.predicate.clone(); 68 | let predicate_string = predicate_tokens.to_string(); 69 | 70 | if filter.insert(predicate_string) { 71 | result.push(predicate_tokens); 72 | } 73 | } 74 | 75 | for component in archetype.components.iter() { 76 | for cfg in component.cfgs.iter() { 77 | let predicate_tokens = cfg.predicate.clone(); 78 | let predicate_string = predicate_tokens.to_string(); 79 | 80 | if filter.insert(predicate_string) { 81 | result.push(predicate_tokens); 82 | } 83 | } 84 | } 85 | } 86 | 87 | result 88 | } 89 | } 90 | 91 | impl Parse for ParseEcsWorld { 92 | fn parse(input: ParseStream) -> syn::Result { 93 | let items = Punctuated::::parse_terminated(input)? 94 | .into_iter() 95 | .collect::>(); 96 | 97 | let mut name = format_ident!("EcsWorld"); 98 | let mut archetypes = Vec::new(); 99 | 100 | for item in items { 101 | match item { 102 | ParseItem::ParseArchetype(item) => { 103 | // Collect all the archetypes 104 | archetypes.push(item); 105 | } 106 | ParseItem::ParseName(item) => { 107 | // TODO: Check for duplicates? 108 | name = item.name; 109 | } 110 | } 111 | } 112 | 113 | // TODO: Check for duplicates? 114 | 115 | if archetypes.is_empty() { 116 | return Err(syn::Error::new( 117 | input.span(), 118 | "ecs world must have at least one archetype", 119 | )); 120 | } 121 | 122 | Ok(Self { name, archetypes }) 123 | } 124 | } 125 | 126 | impl Parse for ParseItem { 127 | fn parse(input: ParseStream) -> syn::Result { 128 | let attributes = input 129 | .call(parse_attributes)? 130 | .into_iter() 131 | .collect::>(); 132 | 133 | let lookahead = input.lookahead1(); 134 | if lookahead.peek(kw::ecs_archetype) { 135 | parse_item_archetype(input, attributes) 136 | } else if lookahead.peek(kw::ecs_name) { 137 | parse_item_name(input, attributes) 138 | } else { 139 | Err(lookahead.error()) 140 | } 141 | } 142 | } 143 | 144 | impl Parse for ParseName { 145 | fn parse(input: ParseStream) -> syn::Result { 146 | input.parse::()?; 147 | input.parse::()?; 148 | 149 | let content; 150 | parenthesized!(content in input); 151 | 152 | let name: Ident = content.parse()?; 153 | 154 | // TODO: Check for duplicates? 155 | 156 | Ok(Self { name }) 157 | } 158 | } 159 | 160 | impl Parse for ParseArchetype { 161 | fn parse(input: ParseStream) -> syn::Result { 162 | let cfgs = Vec::new(); // This will be filled at the item level 163 | 164 | input.parse::()?; 165 | input.parse::()?; 166 | 167 | let content; 168 | parenthesized!(content in input); 169 | 170 | let name: Ident = content.parse()?; 171 | 172 | content.parse::()?; 173 | 174 | let components: Vec = 175 | Punctuated::::parse_terminated(&content)? 176 | .into_iter() 177 | .collect(); 178 | 179 | // TODO: Check for duplicates? 180 | 181 | Ok(Self { 182 | cfgs, 183 | id: None, 184 | name, 185 | components, 186 | }) 187 | } 188 | } 189 | 190 | impl Parse for ParseComponent { 191 | fn parse(input: ParseStream) -> syn::Result { 192 | let mut cfgs = Vec::new(); 193 | 194 | let attributes = input 195 | .call(parse_attributes)? 196 | .into_iter() 197 | .collect::>(); 198 | 199 | let name = input.parse::()?; 200 | 201 | // Don't allow special keyword names as component types 202 | if is_allowed_component_name(&name.to_string()) == false { 203 | return Err(syn::Error::new_spanned(name, "illegal component name")); 204 | } 205 | 206 | // See if we have a manually-assigned component ID 207 | let mut component_id = None; 208 | 209 | for attribute in attributes.into_iter() { 210 | match attribute.data { 211 | ParseAttributeData::Cfg(cfg) => cfgs.push(cfg), 212 | ParseAttributeData::ComponentId(id) => { 213 | if component_id.is_some() { 214 | return Err(syn::Error::new( 215 | attribute.span, 216 | "duplicate component id assignments", 217 | )); 218 | } 219 | component_id = Some(id.value); 220 | } 221 | _ => { 222 | return Err(syn::Error::new( 223 | attribute.span, 224 | "this attribute is not supported here", 225 | )) 226 | } 227 | } 228 | } 229 | 230 | Ok(Self { 231 | cfgs, 232 | id: component_id, 233 | name, 234 | }) 235 | } 236 | } 237 | 238 | fn parse_item_archetype( 239 | input: ParseStream, 240 | attributes: Vec, 241 | ) -> syn::Result { 242 | let mut archetype = input.parse::()?; 243 | 244 | for attribute in attributes.into_iter() { 245 | match attribute.data { 246 | ParseAttributeData::Cfg(cfg) => { 247 | // We need to collect all cfgs in the world body 248 | archetype.cfgs.push(cfg); 249 | } 250 | ParseAttributeData::ArchetypeId(id) => { 251 | if archetype.id.is_some() { 252 | return Err(syn::Error::new( 253 | attribute.span, 254 | "duplicate archetype id assignments", 255 | )); 256 | } 257 | archetype.id = Some(id.value); 258 | } 259 | _ => { 260 | return Err(syn::Error::new( 261 | attribute.span, 262 | "this attribute is not supported here", 263 | )); 264 | } 265 | } 266 | } 267 | 268 | Ok(ParseItem::ParseArchetype(archetype)) 269 | } 270 | 271 | fn parse_item_name( 272 | input: ParseStream, //. 273 | attributes: Vec, 274 | ) -> syn::Result { 275 | if attributes.is_empty() == false { 276 | return Err(syn::Error::new( 277 | attributes[0].span, 278 | "this attribute is not supported here", 279 | )); 280 | } 281 | 282 | let name = input.parse::()?; 283 | Ok(ParseItem::ParseName(name)) 284 | } 285 | -------------------------------------------------------------------------------- /src/archetype/components.rs: -------------------------------------------------------------------------------- 1 | use seq_macro::seq; 2 | 3 | macro_rules! declare_components_n { 4 | ( 5 | $name:ident, 6 | $n:literal 7 | ) => { 8 | seq!(I in 0..$n { 9 | pub trait $name<#(T~I,)*> { 10 | #[doc(hidden)] 11 | fn raw_new(#(c~I: T~I,)*) -> Self; 12 | #[doc(hidden)] 13 | fn raw_get(self) -> (#(T~I,)*); 14 | } 15 | }); 16 | } 17 | } 18 | 19 | seq!(N in 1..=16 { declare_components_n!(Components~N, N); }); 20 | #[cfg(feature = "32_components")] 21 | seq!(N in 17..=32 { declare_components_n!(Components~N, N); }); 22 | -------------------------------------------------------------------------------- /src/archetype/iter.rs: -------------------------------------------------------------------------------- 1 | use seq_macro::seq; 2 | 3 | use crate::entity::Entity; 4 | use crate::traits::Archetype; 5 | 6 | macro_rules! declare_iter_n { 7 | ($iter:ident, $iter_mut:ident, $n:literal) => { 8 | seq!(I in 0..$n { 9 | pub struct $iter<'a, A: Archetype, #(T~I,)*> { 10 | pub(crate) remaining: usize, 11 | pub(crate) ptr_entity: *const Entity, 12 | #(pub(crate) ptr_d~I: *const T~I,)* 13 | pub(crate) phantom: std::marker::PhantomData<&'a mut A>, 14 | } 15 | 16 | pub struct $iter_mut<'a, A: Archetype, #(T~I,)*> { 17 | pub(crate) remaining: usize, 18 | pub(crate) ptr_entity: *const Entity, 19 | #(pub(crate) ptr_d~I: *mut T~I,)* 20 | pub(crate) phantom: std::marker::PhantomData<&'a mut A>, 21 | } 22 | 23 | impl<'a, A: Archetype + 'a, #(T~I: 'a,)*> Iterator for $iter<'a, A, #(T~I,)*> { 24 | type Item = (&'a Entity, #(&'a T~I,)*); 25 | 26 | fn next(&mut self) -> Option { 27 | if self.remaining == 0 { 28 | return None; 29 | } 30 | 31 | unsafe { 32 | let result = (&*self.ptr_entity, #(&*self.ptr_d~I,)*); 33 | 34 | self.ptr_entity = self.ptr_entity.offset(1); 35 | #(self.ptr_d~I = self.ptr_d~I.offset(1);)* 36 | 37 | self.remaining -= 1; 38 | Some(result) 39 | } 40 | } 41 | } 42 | 43 | impl<'a, A: Archetype + 'a, #(T~I: 'a,)*> Iterator for $iter_mut<'a, A, #(T~I,)*> { 44 | type Item = (&'a Entity, #(&'a mut T~I,)*); 45 | 46 | fn next(&mut self) -> Option { 47 | if self.remaining == 0 { 48 | return None; 49 | } 50 | 51 | unsafe { 52 | let result = (&*self.ptr_entity, #(&mut *self.ptr_d~I,)*); 53 | 54 | self.ptr_entity = self.ptr_entity.offset(1); 55 | #(self.ptr_d~I = self.ptr_d~I.offset(1);)* 56 | 57 | self.remaining -= 1; 58 | Some(result) 59 | } 60 | } 61 | } 62 | }); 63 | }; 64 | } 65 | 66 | seq!(N in 1..=16 { declare_iter_n!(Iter~N, IterMut~N, N); }); 67 | #[cfg(feature = "32_components")] 68 | seq!(N in 17..=32 { declare_iter_n!(Iter~N, IterMut~N, N); }); 69 | -------------------------------------------------------------------------------- /src/archetype/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod iter; 2 | pub(crate) mod slices; 3 | pub(crate) mod slot; 4 | pub(crate) mod storage; 5 | pub(crate) mod view; 6 | pub(crate) mod components; 7 | -------------------------------------------------------------------------------- /src/archetype/slices.rs: -------------------------------------------------------------------------------- 1 | use seq_macro::seq; 2 | 3 | use crate::entity::Entity; 4 | use crate::traits::Archetype; 5 | 6 | macro_rules! declare_slices_n { 7 | ($slices:ident, $n:literal) => { 8 | seq!(I in 0..$n { 9 | pub trait $slices<'a, A: Archetype, #(T~I,)*> { 10 | fn new(entities: &'a [Entity], #(s~I: &'a mut [T~I],)*) -> Self; 11 | } 12 | }); 13 | }; 14 | } 15 | 16 | seq!(N in 1..=16 { declare_slices_n!(Slices~N, N); }); 17 | #[cfg(feature = "32_components")] 18 | seq!(N in 17..=32 { declare_slices_n!(Slices~N, N); }); 19 | -------------------------------------------------------------------------------- /src/archetype/slot.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | 3 | use crate::index::{TrimmedIndex, MAX_DATA_INDEX}; 4 | use crate::version::SlotVersion; 5 | 6 | // We use the highest order bit to mark which indices are free list. 7 | // This is necessary because we need to catch if a bad entity handle 8 | // (say, from a different ECS world) tries to access a freed slot and 9 | // treat it as a live data slot, which could cause OOB memory access. 10 | const FREE_BIT: u32 = 1 << 31; 11 | 12 | // This is a reserved index marking the end of the free list. It should 13 | // always be bigger than the maximum index we can store in an entity/slot. 14 | // This index has the FREE_BIT baked into it. 15 | const FREE_LIST_END: u32 = (FREE_BIT - 1) | FREE_BIT; 16 | 17 | /// The data index stored in a slot. 18 | /// 19 | /// Can point to the dense list (entity data) if the slot is live, or to 20 | /// the sparse list (other slots) if the slot is a member of the free list. 21 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 22 | pub(crate) struct SlotIndex(u32); 23 | 24 | impl SlotIndex { 25 | /// Assigns this index to some non-free data index. 26 | /// This may be a reassignment of an already live slot. 27 | #[inline(always)] 28 | pub(crate) fn new_data(index: TrimmedIndex) -> Self { 29 | Self(Into::::into(index)) 30 | } 31 | 32 | /// Assigns this index to some free slot index. 33 | #[inline(always)] 34 | pub(crate) fn new_free(index: TrimmedIndex) -> Self { 35 | // Make sure that there is room in the index for the free bit. 36 | const { assert!((MAX_DATA_INDEX as usize) < (FREE_BIT as usize)) }; 37 | 38 | Self(Into::::into(index) | FREE_BIT) 39 | } 40 | 41 | /// Creates a new SlotIndex at the end of the free list. 42 | #[inline(always)] 43 | pub(crate) const fn free_end() -> Self { 44 | Self(FREE_LIST_END) 45 | } 46 | 47 | /// Returns true if this slot index points to the free list. 48 | #[inline(always)] 49 | pub(crate) const fn is_free(&self) -> bool { 50 | (FREE_BIT & self.0) != 0 51 | } 52 | 53 | /// Returns true if this is points to the end of the free list. 54 | #[inline(always)] 55 | pub(crate) const fn is_free_end(&self) -> bool { 56 | self.0 == FREE_LIST_END 57 | } 58 | 59 | /// Returns the data index this slot points to, if valid (e.g. not free). 60 | #[inline(always)] 61 | pub(crate) fn index_data(&self) -> Option { 62 | debug_assert!(self.is_free() == false); 63 | match self.is_free_end() { 64 | true => None, 65 | // SAFETY: If this isn't free, then we know it must be a valid `TrimmedIndex` 66 | false => unsafe { Some(TrimmedIndex::new_u32(self.0).unwrap_unchecked()) }, 67 | } 68 | } 69 | 70 | /// Returns the free list entry this slot points to, if valid (e.g. not free list end). 71 | #[inline(always)] 72 | pub(crate) fn index_free(&self) -> Option { 73 | debug_assert!(self.is_free()); 74 | match self.is_free_end() { 75 | true => None, 76 | // SAFETY: If this isn't the free end, then we know it must be a valid `TrimmedIndex` 77 | false => unsafe { Some(TrimmedIndex::new_u32(self.0 & !FREE_BIT).unwrap_unchecked()) }, 78 | } 79 | } 80 | } 81 | 82 | // TODO: Seal this 83 | #[derive(Clone, Copy)] 84 | pub struct Slot { 85 | index: SlotIndex, 86 | version: SlotVersion, 87 | } 88 | 89 | impl Slot { 90 | pub(crate) fn populate_free_list( 91 | start: TrimmedIndex, // Index of where the unset section of the slot array begins 92 | slots: &mut [MaybeUninit], // Complete slot array, including old slots 93 | ) -> SlotIndex { 94 | if slots.len() > 0 { 95 | let start_idx = start.into(); 96 | let end_idx = slots.len() - 1; 97 | 98 | // Go to the second-to-last slot 99 | for idx in start_idx..end_idx { 100 | let next = TrimmedIndex::new_usize(idx + 1).unwrap(); 101 | let slot = Slot::new_free(SlotIndex::new_free(next)); 102 | slots.get_mut(idx).unwrap().write(slot); 103 | } 104 | 105 | // Set the last slot to point off the end of the free list. 106 | let last_slot = Slot::new_free(SlotIndex::free_end()); 107 | slots.get_mut(end_idx).unwrap().write(last_slot); 108 | 109 | // Point the free list head to the front of the list. 110 | SlotIndex::new_free(start) 111 | } else { 112 | // Otherwise, we have nothing, so point the free list head to the end. 113 | SlotIndex::free_end() 114 | } 115 | } 116 | 117 | #[inline(always)] 118 | pub(crate) fn new_free(next_free: SlotIndex) -> Self { 119 | debug_assert!(next_free.is_free()); 120 | 121 | Self { 122 | index: next_free, 123 | version: SlotVersion::start(), 124 | } 125 | } 126 | 127 | /// Returns this slot's index. May point to data or a free list entry. 128 | #[inline(always)] 129 | pub(crate) fn index(&self) -> SlotIndex { 130 | self.index 131 | } 132 | 133 | /// Returns true if this slot is freed. 134 | #[inline(always)] 135 | pub(crate) fn is_free(&self) -> bool { 136 | self.index.is_free() 137 | } 138 | 139 | /// Get the slot's generational version. 140 | #[inline(always)] 141 | pub(crate) fn version(&self) -> SlotVersion { 142 | self.version 143 | } 144 | 145 | /// Assigns a slot to some data. This does not increment the version. 146 | #[inline(always)] 147 | pub(crate) fn assign(&mut self, index_data: TrimmedIndex) { 148 | self.index = SlotIndex::new_data(index_data); 149 | 150 | // NOTE: We increment the version on release, not assignment. 151 | } 152 | 153 | /// Releases a slot and increments its version, invalidating all handles. 154 | /// Returns an `EcsError::VersionOverflow` if the version increment overflows. 155 | #[inline(always)] 156 | pub(crate) fn release(&mut self, index_next_free: SlotIndex) { 157 | debug_assert!(self.is_free() == false); 158 | self.index = index_next_free; 159 | self.version = self.version.next(); 160 | } 161 | } 162 | 163 | // Need to enforce this invariant here just in case. 164 | // If this isn't true, then we can't trust the FREE_LIST_END value. 165 | #[test] 166 | fn verify_free_list_end_is_invalid_data_index() { 167 | assert!(TrimmedIndex::new_u32(!FREE_BIT & FREE_LIST_END).is_none()); 168 | } 169 | -------------------------------------------------------------------------------- /src/archetype/view.rs: -------------------------------------------------------------------------------- 1 | use seq_macro::seq; 2 | 3 | use crate::entity::Entity; 4 | use crate::traits::Archetype; 5 | 6 | macro_rules! declare_view_n { 7 | ($view:ident, $n:literal) => { 8 | seq!(I in 0..$n { 9 | pub trait $view<'a, A: Archetype, #(T~I,)*> { 10 | fn new(index: usize, entity: &'a Entity, #(e~I: &'a mut T~I,)*) -> Self; 11 | } 12 | }); 13 | }; 14 | } 15 | 16 | seq!(N in 1..=16 { declare_view_n!(View~N, N); }); 17 | #[cfg(feature = "32_components")] 18 | seq!(N in 17..=32 { declare_view_n!(View~N, N); }); 19 | -------------------------------------------------------------------------------- /src/entity.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::marker::PhantomData; 5 | use std::mem; 6 | use std::num::NonZeroU32; 7 | 8 | use crate::error::EcsError; 9 | use crate::index::{TrimmedIndex, MAX_DATA_INDEX}; 10 | use crate::traits::{Archetype, ArchetypeCanResolve, EntityKey, EntityKeyTyped}; 11 | use crate::version::{ArchetypeVersion, SlotVersion}; 12 | 13 | // NOTE: While this is extremely unlikely to change, if it does, the proc 14 | // macros need to be updated manually with the new type assumptions. 15 | pub type ArchetypeId = u8; 16 | 17 | // How many bits of a u32 entity index are reserved for the archetype ID. 18 | pub(crate) const ARCHETYPE_ID_BITS: u32 = ArchetypeId::BITS; 19 | 20 | /// A statically typed handle to an entity of a specific archetype. 21 | /// 22 | /// On its own, this key does very little. Its primary purpose is to provide 23 | /// indexed access to component data within an ECS world and its archetypes. 24 | /// Entity handles are opaque and can't be accessed beyond type information. 25 | /// 26 | /// As a data structure, an entity has two parts -- a slot index and a 27 | /// generational version number. The slot index is used by the archetype data 28 | /// structure to find the entity's component data, and the version number is 29 | /// used to safely avoid attempts to access data for a stale `Entity` handle. 30 | /// 31 | /// By default, when this version overflows (i.e., a single entity slot 32 | /// was destroyed and reused for a new entity u32::MAX times), it will panic. 33 | /// If instead you would like to allow entity slot versions to wrap, you can 34 | /// enable the `wrapping_version` crate feature instead. Note that this could 35 | /// allow invalid entity access, but doing so will not access invalid memory, 36 | /// and the chances of this happening are infinitesimally small. 37 | #[repr(transparent)] 38 | pub struct Entity { 39 | inner: EntityAny, 40 | _type: PhantomData A>, 41 | } 42 | 43 | /// A statically typed, versioned direct entity index for accelerated lookup. 44 | /// 45 | /// Unlike [`Entity`], this key points directly to the component index in 46 | /// the archetype, rather than the version-checked slot. This skips one level 47 | /// of indirection on lookup, but is sensitive to archetype order changes (e.g. 48 | /// when removing an entity). Because of this, this handle keeps a version 49 | /// number on the archetype itself, rather than the entity's slot. Attempting 50 | /// to use a direct entity handle on an archetype that has added or removed an 51 | /// entity since the handle was created will fail access due to invalidation, 52 | /// even if the index may still be correct. 53 | /// 54 | /// By default, when this version overflows (i.e., an archetype has added or 55 | /// removed an entity `u32::MAX` times), it will cause a panic. If instead 56 | /// you would like to allow archetype versions to wrap, you can enable the 57 | /// `wrapping_version` crate feature instead. Note that this could allow 58 | /// invalid entity access, but doing so will not access invalid memory, and 59 | /// the chances of this happening are infinitesimally small. 60 | #[repr(transparent)] 61 | pub struct EntityDirect { 62 | inner: EntityDirectAny, 63 | _type: PhantomData A>, 64 | } 65 | 66 | /// A dynamically typed handle to an entity of some runtime archetype. 67 | /// 68 | /// This behaves like an [`Entity`] key, but its type is only known at runtime. 69 | /// To determine its type, use `archetype_id()`, or use the `resolve()` method 70 | /// generated by the `ecs_world!` declaration to convert the `EntityAny` into 71 | /// an enum with each possible archetype outcome. 72 | #[derive(Clone, Copy, Eq, PartialEq)] 73 | pub struct EntityAny { 74 | key: u32, // [ slot_index (u24) | archetype_id (u8) ] 75 | version: SlotVersion, 76 | } 77 | 78 | /// A dynamically typed, versioned direct entity index of some runtime archetype. 79 | /// 80 | /// This behaves like an [`EntityDirect`] key, but its type is only known at runtime. 81 | /// To determine its type, use `archetype_id()`, or use the `resolve()` method 82 | /// generated by the `ecs_world!` declaration to convert the `EntityDirectAny` into 83 | /// an enum with each possible archetype outcome. 84 | #[derive(Clone, Copy, Eq, PartialEq)] 85 | pub struct EntityDirectAny { 86 | key: u32, // [ dense_index (u24) | archetype_id (u8) ] 87 | version: ArchetypeVersion, 88 | } 89 | 90 | impl Entity { 91 | #[inline(always)] 92 | pub(crate) fn new( 93 | slot_index: TrimmedIndex, //. 94 | version: SlotVersion, 95 | ) -> Self { 96 | Self { 97 | inner: EntityAny::new(slot_index, A::ARCHETYPE_ID, version), 98 | _type: PhantomData, 99 | } 100 | } 101 | 102 | /// Creates a new typed [`Entity`] from an [`EntityAny`]. 103 | /// 104 | /// In match statements, this tends to optimize better than [`TryFrom`]. 105 | /// 106 | /// # Panics 107 | /// 108 | /// Panics if the given [`EntityAny`] is not an entity of this type. 109 | #[inline(always)] 110 | pub fn from_any(entity: EntityAny) -> Self { 111 | if entity.archetype_id() != A::ARCHETYPE_ID { 112 | panic!("invalid entity conversion"); 113 | } 114 | 115 | Self { 116 | inner: entity, 117 | _type: PhantomData, 118 | } 119 | } 120 | 121 | /// Creates new a typed [`Entity`] from an [`EntityAny`] without checking its archetype. 122 | /// 123 | /// While this is not an unsafe operation in the Rust sense (all bounds checks are still 124 | /// enforced), this should generally be avoided when possible. The intended use of this 125 | /// function is to skip redundant checks when using direct archetype IDs in a `match` 126 | /// statement. Improper use may result in logic errors from incorrect data access. 127 | #[inline(always)] 128 | pub fn from_any_unchecked(entity: EntityAny) -> Self { 129 | debug_assert!(entity.archetype_id() == A::ARCHETYPE_ID); 130 | 131 | Self { 132 | inner: entity, 133 | _type: PhantomData, 134 | } 135 | } 136 | 137 | /// Returns this entity's stored `ARCHETYPE_ID` value. 138 | /// 139 | /// This is the same `ARCHETYPE_ID` as the archetype this entity belongs to. 140 | #[inline(always)] 141 | pub const fn archetype_id(self) -> ArchetypeId { 142 | A::ARCHETYPE_ID 143 | } 144 | 145 | /// Converts this [`Entity`] directly into an [`EntityAny`]. 146 | /// 147 | /// Useful for situations where [`Into`] type inference can't deduce a conversion. 148 | #[inline(always)] 149 | pub fn into_any(self) -> EntityAny { 150 | self.inner 151 | } 152 | 153 | #[inline(always)] 154 | pub(crate) fn version(&self) -> SlotVersion { 155 | self.inner.version() 156 | } 157 | 158 | #[inline(always)] 159 | pub(crate) fn slot_index(&self) -> TrimmedIndex { 160 | self.inner.slot_index() 161 | } 162 | } 163 | 164 | impl EntityAny { 165 | #[inline(always)] 166 | pub(crate) fn new( 167 | slot_index: TrimmedIndex, //. 168 | archetype_id: ArchetypeId, 169 | version: SlotVersion, 170 | ) -> Self { 171 | let archetype_id: u32 = archetype_id.into(); 172 | let slot_index: u32 = slot_index.into(); 173 | let key = (slot_index << ARCHETYPE_ID_BITS) | archetype_id; 174 | Self { key, version } 175 | } 176 | 177 | /// Creates a new entity handle from raw inner data. Useful for applications like FFI. 178 | /// 179 | /// # Safety 180 | /// 181 | /// Despite the use of raw unchecked values, this is still an inherently memory-safe 182 | /// operation and produces a memory-safe handle. Entity access in gecs is validated 183 | /// to prevent issues from bad raw entity creation and other bugs like using an entity 184 | /// handle from a different ECS world. While altering the values in a key might cause 185 | /// access to unexpected data, it won't cause access to uninitialized memory, invalid 186 | /// entities, or result in other undefined behavior. 187 | #[inline(always)] 188 | pub fn from_raw(raw: (u32, u32)) -> Result { 189 | let (key, version) = raw; 190 | // This really only checks that the version is nonzero. All other key/versions are "valid". 191 | let version = SlotVersion::new(NonZeroU32::new(version).ok_or(EcsError::InvalidRawEntity)?); 192 | Ok(Self { key, version }) 193 | } 194 | 195 | /// Returns the raw inner data of this entity handle. Useful for applications like FFI. 196 | /// 197 | /// The data returned has no inherent value and should not be modified or reordered. 198 | /// The only use for this data is for creating a handle using [`EntityAny::from_raw`]. 199 | #[inline(always)] 200 | pub fn raw(&self) -> (u32, u32) { 201 | (self.key, self.version.get().get()) 202 | } 203 | 204 | /// Returns this entity's stored `ARCHETYPE_ID` value. 205 | /// 206 | /// This is the same `ARCHETYPE_ID` as the archetype this entity belongs to. 207 | #[inline(always)] 208 | pub const fn archetype_id(self) -> ArchetypeId { 209 | self.key as ArchetypeId // Trim off the bottom to get the ID 210 | } 211 | 212 | /// Returns self. 213 | #[inline(always)] 214 | pub fn into_any(self) -> EntityAny { 215 | self 216 | } 217 | 218 | #[inline(always)] 219 | pub(crate) const fn version(&self) -> SlotVersion { 220 | self.version 221 | } 222 | 223 | #[inline(always)] 224 | pub(crate) fn slot_index(&self) -> TrimmedIndex { 225 | unsafe { 226 | // SAFETY: We know the remaining data can fit in a DataIndex 227 | debug_assert!(self.key >> ARCHETYPE_ID_BITS <= MAX_DATA_INDEX); 228 | TrimmedIndex::new_u32(self.key >> ARCHETYPE_ID_BITS).unwrap_unchecked() 229 | } 230 | } 231 | } 232 | 233 | impl EntityDirect { 234 | #[inline(always)] 235 | pub(crate) fn new( 236 | dense_index: TrimmedIndex, //. 237 | version: ArchetypeVersion, 238 | ) -> Self { 239 | Self { 240 | inner: EntityDirectAny::new(dense_index, A::ARCHETYPE_ID, version), 241 | _type: PhantomData, 242 | } 243 | } 244 | 245 | /// Creates a new typed [`EntityDirect`] from an [`EntityDirectAny`]. 246 | /// 247 | /// In match statements, this tends to optimize better than [`TryFrom`]. 248 | /// 249 | /// # Panics 250 | /// 251 | /// Panics if the given [`EntityDirectAny`] is not an entity of this type. 252 | #[inline(always)] 253 | pub fn from_any(entity: EntityDirectAny) -> Self { 254 | if entity.archetype_id() != A::ARCHETYPE_ID { 255 | panic!("invalid entity conversion"); 256 | } 257 | 258 | Self { 259 | inner: entity, 260 | _type: PhantomData, 261 | } 262 | } 263 | 264 | /// Creates new a typed `EntityDirect` from an `EntityDirectAny` without checking its archetype. 265 | /// 266 | /// While this is not an unsafe operation in the Rust sense (all bounds checks are still 267 | /// enforced), this should generally be avoided when possible. The intended use of this 268 | /// function is to skip redundant checks when using stored archetype IDs in a `match` 269 | /// statement. Improper use may result in logic errors from incorrect data access. 270 | #[inline(always)] 271 | pub fn from_any_unchecked(entity: EntityDirectAny) -> Self { 272 | debug_assert!(entity.archetype_id() == A::ARCHETYPE_ID); 273 | 274 | Self { 275 | inner: entity, 276 | _type: PhantomData, 277 | } 278 | } 279 | 280 | /// Returns this entity's stored `ARCHETYPE_ID` value. 281 | /// 282 | /// This is the same `ARCHETYPE_ID` as the archetype this entity belongs to. 283 | #[inline(always)] 284 | pub const fn archetype_id(self) -> ArchetypeId { 285 | A::ARCHETYPE_ID 286 | } 287 | 288 | /// Converts this [`EntityDirect`] directly into an [`EntityDirectAny`]. 289 | /// 290 | /// Useful for situations where [`Into`] type inference can't deduce a conversion. 291 | #[inline(always)] 292 | pub fn into_any(self) -> EntityDirectAny { 293 | self.inner 294 | } 295 | 296 | #[inline(always)] 297 | pub(crate) fn version(&self) -> ArchetypeVersion { 298 | self.inner.version() 299 | } 300 | 301 | #[inline(always)] 302 | pub(crate) fn dense_index(&self) -> TrimmedIndex { 303 | self.inner.dense_index() 304 | } 305 | } 306 | 307 | impl EntityDirectAny { 308 | #[inline(always)] 309 | pub(crate) fn new( 310 | dense_index: TrimmedIndex, //. 311 | archetype_id: ArchetypeId, 312 | version: ArchetypeVersion, 313 | ) -> Self { 314 | let archetype_id: u32 = archetype_id.into(); 315 | let dense_index: u32 = dense_index.into(); 316 | let key = (dense_index << ARCHETYPE_ID_BITS) | archetype_id; 317 | Self { key, version } 318 | } 319 | 320 | /// Returns this entity's stored `ARCHETYPE_ID` value. 321 | /// 322 | /// This is the same `ARCHETYPE_ID` as the archetype this entity belongs to. 323 | #[inline(always)] 324 | pub const fn archetype_id(self) -> ArchetypeId { 325 | self.key as ArchetypeId // Trim off the bottom to get the ID 326 | } 327 | 328 | /// Returns self. 329 | #[inline(always)] 330 | pub fn into_any(self) -> EntityDirectAny { 331 | self 332 | } 333 | 334 | #[inline(always)] 335 | pub(crate) const fn version(&self) -> ArchetypeVersion { 336 | self.version 337 | } 338 | 339 | #[inline(always)] 340 | pub(crate) fn dense_index(&self) -> TrimmedIndex { 341 | unsafe { 342 | // SAFETY: We know the remaining data can fit in a DataIndex 343 | debug_assert!(self.key >> ARCHETYPE_ID_BITS <= MAX_DATA_INDEX); 344 | TrimmedIndex::new_u32(self.key >> ARCHETYPE_ID_BITS).unwrap_unchecked() 345 | } 346 | } 347 | } 348 | 349 | impl Hash for EntityAny { 350 | #[inline(always)] 351 | fn hash(&self, state: &mut H) { 352 | // Hash as a single u64 rather than two u32s. 353 | let index: u64 = self.key.into(); 354 | let version: u64 = self.version.get().get().into(); 355 | let combined = (index << 32) | version; 356 | combined.hash(state); 357 | } 358 | } 359 | 360 | impl Hash for EntityDirectAny { 361 | #[inline(always)] 362 | fn hash(&self, state: &mut H) { 363 | // Hash as a single u64 rather than two u32s. 364 | let index: u64 = self.key.into(); 365 | let version: u64 = self.version.get().get().into(); 366 | let combined = (index << 32) | version; 367 | combined.hash(state); 368 | } 369 | } 370 | 371 | impl From> for EntityAny { 372 | #[inline(always)] 373 | fn from(entity: Entity) -> Self { 374 | entity.inner 375 | } 376 | } 377 | 378 | impl From> for EntityDirectAny { 379 | #[inline(always)] 380 | fn from(entity: EntityDirect) -> Self { 381 | entity.inner 382 | } 383 | } 384 | 385 | impl TryFrom for Entity { 386 | type Error = EcsError; 387 | 388 | #[inline(always)] 389 | fn try_from(entity: EntityAny) -> Result { 390 | if entity.archetype_id() == A::ARCHETYPE_ID { 391 | Ok(Self { 392 | inner: entity, 393 | _type: PhantomData, 394 | }) 395 | } else { 396 | Err(EcsError::InvalidEntityType) 397 | } 398 | } 399 | } 400 | 401 | impl TryFrom for EntityDirect { 402 | type Error = EcsError; 403 | 404 | #[inline(always)] 405 | fn try_from(entity: EntityDirectAny) -> Result { 406 | if entity.archetype_id() == A::ARCHETYPE_ID { 407 | Ok(Self { 408 | inner: entity, 409 | _type: PhantomData, 410 | }) 411 | } else { 412 | Err(EcsError::InvalidEntityType) 413 | } 414 | } 415 | } 416 | 417 | // Derive boilerplate until https://github.com/rust-lang/rust/issues/26925 is resolved 418 | 419 | impl Clone for Entity { 420 | #[inline(always)] 421 | fn clone(&self) -> Entity { 422 | *self 423 | } 424 | } 425 | 426 | impl Clone for EntityDirect { 427 | #[inline(always)] 428 | fn clone(&self) -> EntityDirect { 429 | *self 430 | } 431 | } 432 | 433 | impl PartialEq for Entity { 434 | fn eq(&self, other: &Self) -> bool { 435 | self.inner == other.inner 436 | } 437 | } 438 | 439 | impl PartialEq for EntityDirect { 440 | fn eq(&self, other: &Self) -> bool { 441 | self.inner == other.inner 442 | } 443 | } 444 | 445 | impl Hash for Entity { 446 | fn hash(&self, state: &mut H) { 447 | self.inner.hash(state) 448 | } 449 | } 450 | 451 | impl Hash for EntityDirect { 452 | fn hash(&self, state: &mut H) { 453 | self.inner.hash(state) 454 | } 455 | } 456 | 457 | impl Copy for Entity {} 458 | impl Copy for EntityDirect {} 459 | 460 | impl Eq for Entity {} 461 | impl Eq for EntityDirect {} 462 | 463 | impl EntityKey for Entity { 464 | type DestroyOutput = Option; 465 | type DirectOutput = EntityDirect; 466 | } 467 | 468 | impl EntityKey for EntityDirect { 469 | type DestroyOutput = Option; 470 | type DirectOutput = EntityDirect; 471 | } 472 | 473 | impl> EntityKeyTyped for Entity { 474 | type Archetype = A; 475 | } 476 | 477 | impl> EntityKeyTyped for EntityDirect { 478 | type Archetype = A; 479 | } 480 | 481 | impl EntityKey for EntityAny { 482 | type DestroyOutput = Option<()>; 483 | type DirectOutput = EntityDirectAny; 484 | } 485 | 486 | impl EntityKey for EntityDirectAny { 487 | type DestroyOutput = Option<()>; 488 | type DirectOutput = EntityDirectAny; 489 | } 490 | 491 | impl<'a, A: Archetype> From<&'a Entity> for &'a EntityAny { 492 | #[inline(always)] 493 | fn from(value: &'a Entity) -> Self { 494 | unsafe { 495 | // SAFETY: Entity is a transparent struct containing only an EntityAny, 496 | // which guarantees that they have the same representation in memory 497 | mem::transmute(value) 498 | } 499 | } 500 | } 501 | 502 | impl<'a, A: Archetype> From<&'a EntityDirect> for &'a EntityDirectAny { 503 | #[inline(always)] 504 | fn from(value: &'a EntityDirect) -> Self { 505 | unsafe { 506 | // SAFETY: EntityDirect is a transparent struct containing only an EntityDirectAny, 507 | // which guarantees that they have the same representation in memory 508 | mem::transmute(value) 509 | } 510 | } 511 | } 512 | 513 | impl<'a, A: Archetype> From<&'a mut Entity> for &'a mut EntityAny { 514 | #[inline(always)] 515 | fn from(value: &'a mut Entity) -> Self { 516 | unsafe { 517 | // SAFETY: Entity is a transparent struct containing only an EntityAny, 518 | // which guarantees that they have the same representation in memory 519 | mem::transmute(value) 520 | } 521 | } 522 | } 523 | 524 | impl<'a, A: Archetype> From<&'a mut EntityDirect> for &'a mut EntityDirectAny { 525 | #[inline(always)] 526 | fn from(value: &'a mut EntityDirect) -> Self { 527 | unsafe { 528 | // SAFETY: EntityDirect is a transparent struct containing only an EntityDirectAny, 529 | // which guarantees that they have the same representation in memory 530 | mem::transmute(value) 531 | } 532 | } 533 | } 534 | 535 | impl Display for Entity { 536 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 537 | fmt_entity(&self.inner, f) 538 | } 539 | } 540 | 541 | impl Display for EntityDirect { 542 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 543 | fmt_entity_direct(&self.inner, f) 544 | } 545 | } 546 | 547 | impl Display for EntityAny { 548 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 549 | fmt_entity(&self, f) 550 | } 551 | } 552 | 553 | impl Display for EntityDirectAny { 554 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 555 | fmt_entity_direct(&self, f) 556 | } 557 | } 558 | 559 | impl Debug for Entity { 560 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 561 | write!( 562 | f, 563 | "Entity<_> {{ archetype_id: {}, slot_index: {}, version: {} }}", 564 | self.archetype_id(), 565 | u32::from(self.slot_index()), 566 | self.version().get(), 567 | ) 568 | } 569 | } 570 | 571 | impl Debug for EntityDirect { 572 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 573 | write!( 574 | f, 575 | "EntityDirect<_> {{ archetype_id: {}, dense_index: {}, version: {} }}", 576 | self.archetype_id(), 577 | u32::from(self.dense_index()), 578 | self.version().get(), 579 | ) 580 | } 581 | } 582 | 583 | impl Debug for EntityAny { 584 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 585 | write!( 586 | f, 587 | "EntityAny {{ archetype_id: {}, slot_index: {}, version: {} }}", 588 | self.archetype_id(), 589 | u32::from(self.slot_index()), 590 | self.version().get(), 591 | ) 592 | } 593 | } 594 | 595 | impl Debug for EntityDirectAny { 596 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 597 | write!( 598 | f, 599 | "EntityDirectAny {{ archetype_id: {}, dense_index: {}, version: {} }}", 600 | self.archetype_id(), 601 | u32::from(self.dense_index()), 602 | self.version().get(), 603 | ) 604 | } 605 | } 606 | 607 | fn fmt_entity(entity: &EntityAny, f: &mut Formatter<'_>) -> FmtResult { 608 | write!( 609 | f, 610 | "({}, S{}, {})", 611 | entity.archetype_id(), 612 | u32::from(entity.slot_index()), 613 | entity.version().get(), 614 | ) 615 | } 616 | 617 | fn fmt_entity_direct(entity: &EntityDirectAny, f: &mut Formatter<'_>) -> FmtResult { 618 | write!( 619 | f, 620 | "({}, D{}, {})", 621 | entity.archetype_id(), 622 | u32::from(entity.dense_index()), 623 | entity.version().get(), 624 | ) 625 | } 626 | 627 | #[doc(hidden)] 628 | pub mod __internal { 629 | use super::*; 630 | 631 | #[doc(hidden)] 632 | #[inline(always)] 633 | pub fn new_entity_direct( 634 | index: usize, 635 | version: ArchetypeVersion, 636 | ) -> EntityDirect { 637 | EntityDirect::new(TrimmedIndex::new_usize(index).unwrap(), version) 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result}; 2 | 3 | /// Error reporting enum for ECS operation failure. 4 | #[derive(Debug, Clone, PartialEq)] 5 | #[non_exhaustive] 6 | pub enum EcsError { 7 | /// A runtime entity did not meet the expected type for this operation. 8 | InvalidEntityType, 9 | /// We failed to construct a locally-valid entity handle from raw data. 10 | InvalidRawEntity, 11 | } 12 | 13 | impl std::error::Error for EcsError {} 14 | 15 | impl Display for EcsError { 16 | #[cold] 17 | fn fmt(&self, f: &mut Formatter) -> Result { 18 | match self { 19 | EcsError::InvalidEntityType => write!(f, "invalid type for entity"), 20 | EcsError::InvalidRawEntity => write!(f, "invalid raw entity data"), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.rs: -------------------------------------------------------------------------------- 1 | use crate::entity::ARCHETYPE_ID_BITS; 2 | use crate::util::debug_checked_assume; 3 | 4 | pub(crate) const MAX_DATA_CAPACITY: u32 = 1 << (u32::BITS - ARCHETYPE_ID_BITS); 5 | pub(crate) const MAX_DATA_INDEX: u32 = MAX_DATA_CAPACITY - 1; 6 | 7 | /// A size-checked index that can always fit in an entity and live slot. 8 | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 9 | pub(crate) struct TrimmedIndex(u32); 10 | 11 | impl TrimmedIndex { 12 | /// Creates a `TrimmedIndex` pointing to zero. 13 | #[inline(always)] 14 | pub(crate) const fn zero() -> Self { 15 | Self(0) 16 | } 17 | 18 | /// Creates a new `TrimmedIndex` if the given index is within bounds. 19 | #[inline(always)] 20 | pub(crate) const fn new_u32(index: u32) -> Option { 21 | match index < MAX_DATA_CAPACITY { 22 | true => Some(Self(index)), 23 | false => None, 24 | } 25 | } 26 | 27 | /// Creates a new `TrimmedIndex` if the given index is within bounds. 28 | #[inline(always)] 29 | pub(crate) const fn new_usize(index: usize) -> Option { 30 | match index < MAX_DATA_CAPACITY as usize { 31 | true => Some(Self(index as u32)), 32 | false => None, 33 | } 34 | } 35 | } 36 | 37 | impl From for u32 { 38 | fn from(value: TrimmedIndex) -> Self { 39 | // SAFETY: This is verified at creation 40 | unsafe { debug_checked_assume!(value.0 < MAX_DATA_CAPACITY) }; 41 | value.0 42 | } 43 | } 44 | 45 | impl From for usize { 46 | fn from(value: TrimmedIndex) -> Self { 47 | // SAFETY: This is verified at creation 48 | unsafe { debug_checked_assume!(value.0 < MAX_DATA_CAPACITY) }; 49 | value.0.try_into().unwrap() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub enum EcsStep { 3 | #[default] 4 | Continue, 5 | Break, 6 | } 7 | 8 | #[derive(Default)] 9 | pub enum EcsStepDestroy { 10 | #[default] 11 | Continue, 12 | Break, 13 | ContinueDestroy, 14 | BreakDestroy, 15 | } 16 | 17 | impl EcsStepDestroy { 18 | #[inline(always)] 19 | pub fn is_destroy(&self) -> bool { 20 | match self { 21 | EcsStepDestroy::Continue => false, 22 | EcsStepDestroy::Break => false, 23 | EcsStepDestroy::ContinueDestroy => true, 24 | EcsStepDestroy::BreakDestroy => true, 25 | } 26 | } 27 | } 28 | 29 | impl From<()> for EcsStep { 30 | #[inline(always)] 31 | fn from(_: ()) -> Self { 32 | EcsStep::Continue 33 | } 34 | } 35 | 36 | impl From<()> for EcsStepDestroy { 37 | #[inline(always)] 38 | fn from(_: ()) -> Self { 39 | EcsStepDestroy::Continue 40 | } 41 | } 42 | 43 | impl From for EcsStepDestroy { 44 | #[inline(always)] 45 | fn from(step: EcsStep) -> Self { 46 | match step { 47 | EcsStep::Continue => EcsStepDestroy::Continue, 48 | EcsStep::Break => EcsStepDestroy::Break, 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_macros)] 3 | #![allow(unused_imports)] 4 | 5 | pub(crate) struct NumAssert; 6 | 7 | #[allow(clippy::erasing_op)] 8 | impl NumAssert { 9 | pub const LEQ: usize = R - L; 10 | pub const LT: usize = R - L - 1; 11 | } 12 | 13 | macro_rules! num_assert_leq { 14 | ($a:expr, $b:expr) => { 15 | #[allow(path_statements)] 16 | #[allow(clippy::no_effect)] 17 | { 18 | $crate::util::NumAssert::<{ $a }, { $b }>::LEQ; 19 | } 20 | }; 21 | } 22 | 23 | macro_rules! num_assert_lt { 24 | ($a:expr, $b:expr) => { 25 | #[allow(path_statements)] 26 | #[allow(clippy::no_effect)] 27 | { 28 | $crate::util::NumAssert::<{ $a }, { $b }>::LT; 29 | } 30 | }; 31 | } 32 | 33 | pub(crate) use num_assert_leq; 34 | pub(crate) use num_assert_lt; 35 | 36 | /// Hints the compiler that the given predicate will always be true. 37 | /// 38 | /// # Safety 39 | /// 40 | /// If the given predicate is ever not true, this will result in UB. 41 | /// This is checked only in builds with debug assertions enabled. 42 | macro_rules! debug_checked_assume { 43 | ($ex:expr) => { 44 | if (!$ex) { 45 | debug_assert!(false); 46 | ::std::hint::unreachable_unchecked(); 47 | } 48 | }; 49 | } 50 | 51 | /// Hints the compiler that the given line of code can never be reached. 52 | /// 53 | /// # Safety 54 | /// 55 | /// If this line of code is ever reached, this will result in UB. 56 | /// This is checked only in builds with debug assertions enabled. 57 | macro_rules! debug_checked_unreachable { 58 | () => { 59 | debug_assert!(false); 60 | ::std::hint::unreachable_unchecked(); 61 | }; 62 | } 63 | 64 | pub(crate) use debug_checked_assume; 65 | pub(crate) use debug_checked_unreachable; 66 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroU32; 2 | 3 | // Starting version number. Must convert to a NonZeroU32. 4 | const VERSION_START: NonZeroU32 = NonZeroU32::MIN; 5 | 6 | #[repr(transparent)] 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub struct SlotVersion { 9 | version: NonZeroU32, 10 | } 11 | 12 | #[repr(transparent)] 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 14 | pub struct ArchetypeVersion { 15 | version: NonZeroU32, 16 | } 17 | 18 | impl SlotVersion { 19 | #[inline(always)] 20 | pub(crate) fn new(version: NonZeroU32) -> Self { 21 | Self { 22 | version, // Direct set 23 | } 24 | } 25 | 26 | #[inline(always)] 27 | pub(crate) fn start() -> Self { 28 | Self { 29 | version: VERSION_START, 30 | } 31 | } 32 | 33 | #[inline(always)] 34 | pub(crate) fn get(&self) -> NonZeroU32 { 35 | self.version 36 | } 37 | 38 | #[inline(always)] 39 | pub(crate) fn next(&self) -> SlotVersion { 40 | SlotVersion { 41 | #[cfg(feature = "wrapping_version")] 42 | version: NonZeroU32::new(self.version.get().wrapping_add(1)).unwrap_or(VERSION_START), 43 | #[cfg(not(feature = "wrapping_version"))] 44 | version: self.version.checked_add(1).expect("slot version overflow"), 45 | } 46 | } 47 | } 48 | 49 | impl ArchetypeVersion { 50 | #[inline(always)] 51 | pub(crate) fn start() -> Self { 52 | Self { 53 | version: VERSION_START, 54 | } 55 | } 56 | 57 | #[inline(always)] 58 | pub(crate) fn get(&self) -> NonZeroU32 { 59 | self.version 60 | } 61 | 62 | #[inline(always)] 63 | pub(crate) fn next(&self) -> ArchetypeVersion { 64 | ArchetypeVersion { 65 | #[cfg(feature = "wrapping_version")] 66 | version: NonZeroU32::new(self.version.get().wrapping_add(1)).unwrap_or(VERSION_START), 67 | #[cfg(not(feature = "wrapping_version"))] 68 | version: self.version.checked_add(1).expect("arch version overflow"), 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/test_bind.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompB(pub u32); 5 | pub struct CompZ; // ZST 6 | 7 | ecs_world! { 8 | ecs_archetype!( 9 | ArchFoo, 10 | CompA, 11 | CompZ, 12 | ); 13 | 14 | ecs_archetype!( 15 | ArchBar, 16 | CompB, 17 | CompZ, 18 | ); 19 | } 20 | 21 | pub fn test_bind() { 22 | let mut world = EcsWorld::default(); 23 | 24 | let entity = world.archetype_mut::().create((CompA(1), CompZ)); 25 | 26 | ecs_iter!(world, |_: &Entity| {}); 27 | ecs_iter_borrow!(world, |_: &Entity| {}); 28 | ecs_find!(world, entity, |_: &Entity| {}); 29 | ecs_find_borrow!(world, entity, |_: &Entity| {}); 30 | ecs_find!(world, entity.into_any(), |_: &Entity| {}); 31 | ecs_find_borrow!(world, entity.into_any(), |_: &Entity| {}); 32 | 33 | ecs_iter!(world, |_: &Entity<_>| {}); 34 | ecs_iter_borrow!(world, |_: &Entity<_>| {}); 35 | ecs_find!(world, entity, |_: &Entity<_>| {}); 36 | ecs_find_borrow!(world, entity, |_: &Entity<_>| {}); 37 | ecs_find!(world, entity.into_any(), |_: &Entity<_>| {}); 38 | ecs_find_borrow!(world, entity.into_any(), |_: &Entity<_>| {}); 39 | 40 | ecs_iter!(world, |_: &EntityDirect| {}); 41 | ecs_iter_borrow!(world, |_: &EntityDirect| {}); 42 | ecs_find!(world, entity, |_: &EntityDirect| {}); 43 | ecs_find_borrow!(world, entity, |_: &EntityDirect| {}); 44 | ecs_find!(world, entity.into_any(), |_: &EntityDirect| {}); 45 | ecs_find_borrow!(world, entity.into_any(), |_: &EntityDirect| {}); 46 | 47 | ecs_iter!(world, |_: &EntityDirect<_>| {}); 48 | ecs_iter_borrow!(world, |_: &EntityDirect<_>| {}); 49 | ecs_find!(world, entity, |_: &EntityDirect<_>| {}); 50 | ecs_find_borrow!(world, entity, |_: &EntityDirect<_>| {}); 51 | ecs_find!(world, entity.into_any(), |_: &EntityDirect<_>| {}); 52 | ecs_find_borrow!(world, entity.into_any(), |_: &EntityDirect<_>| {}); 53 | 54 | ecs_iter!(world, |_: &EntityAny| {}); 55 | ecs_iter_borrow!(world, |_: &EntityAny| {}); 56 | ecs_find!(world, entity, |_: &EntityAny| {}); 57 | ecs_find_borrow!(world, entity, |_: &EntityAny| {}); 58 | ecs_find!(world, entity.into_any(), |_: &EntityAny| {}); 59 | ecs_find_borrow!(world, entity.into_any(), |_: &EntityAny| {}); 60 | 61 | ecs_iter!(world, |_: &EntityDirectAny| {}); 62 | ecs_iter_borrow!(world, |_: &EntityDirectAny| {}); 63 | ecs_find!(world, entity, |_: &EntityDirectAny| {}); 64 | ecs_find_borrow!(world, entity, |_: &EntityDirectAny| {}); 65 | ecs_find!(world, entity.into_any(), |_: &EntityDirectAny| {}); 66 | ecs_find_borrow!(world, entity.into_any(), |_: &EntityDirectAny| {}); 67 | } 68 | -------------------------------------------------------------------------------- /tests/test_direct.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub struct CompA(pub u32); 5 | #[derive(Debug, PartialEq)] 6 | pub struct CompB(pub u32); 7 | #[derive(Debug, PartialEq)] 8 | pub struct CompC(pub u32); 9 | 10 | ecs_world! { 11 | #[archetype_id(3)] 12 | ecs_archetype!( 13 | ArchFoo, 14 | CompA, 15 | CompB, 16 | ); 17 | 18 | ecs_archetype!( 19 | ArchBar, 20 | CompA, 21 | CompC, 22 | ); 23 | } 24 | 25 | #[test] 26 | #[rustfmt::skip] 27 | pub fn test_direct_basic() { 28 | let mut world = EcsWorld::default(); 29 | 30 | let entity_a = world.create::((CompA(1), CompB(10))); 31 | let entity_b = world.create::((CompA(2), CompC(20))); 32 | 33 | let entity_direct_a = ecs_find!(world, entity_a, |direct: &EntityDirect| { 34 | *direct 35 | }).unwrap(); 36 | 37 | let entity_direct_b = ecs_find!(world, entity_b, |direct: &EntityDirect| { 38 | *direct 39 | }).unwrap(); 40 | 41 | assert!(ecs_find!(world, entity_direct_a, |a: &CompA, b: &CompB| { 42 | assert_eq!(a.0, 1); 43 | assert_eq!(b.0, 10); 44 | }).is_some()); 45 | 46 | assert!(ecs_find!(world, entity_direct_b, |a: &CompA, c: &CompC| { 47 | assert_eq!(a.0, 2); 48 | assert_eq!(c.0, 20); 49 | }).is_some()); 50 | 51 | // Adding a new entity doesn't invalidate dense indices 52 | let entity_c = world.create::((CompA(3), CompB(30))); 53 | let entity_d = world.create::((CompA(4), CompC(40))); 54 | 55 | assert!(ecs_find!(world, entity_direct_a, |a: &CompA, b: &CompB| { 56 | assert_eq!(a.0, 1); 57 | assert_eq!(b.0, 10); 58 | }).is_some()); 59 | 60 | assert!(ecs_find!(world, entity_direct_b, |a: &CompA, c: &CompC| { 61 | assert_eq!(a.0, 2); 62 | assert_eq!(c.0, 20); 63 | }).is_some()); 64 | 65 | // Destroying an entity invalidates dense indices 66 | world.destroy(entity_c); 67 | world.destroy(entity_d); 68 | 69 | assert!(ecs_find!(world, entity_direct_a, |_: &CompA| {}).is_none()); 70 | assert!(ecs_find!(world, entity_direct_b, |_: &CompA| {}).is_none()); 71 | } 72 | 73 | #[test] 74 | #[rustfmt::skip] 75 | pub fn test_direct_destroy() { 76 | let mut world = EcsWorld::default(); 77 | 78 | let entity_a = world.create::((CompA(1), CompB(10))); 79 | let entity_b = world.create::((CompA(1), CompB(10))); 80 | 81 | let entity_direct_a = ecs_find!(world, entity_a, |direct: &EntityDirect| { 82 | *direct 83 | }).unwrap(); 84 | 85 | world.destroy(entity_direct_a); 86 | 87 | assert!(ecs_find!(world, entity_direct_a, |_: &CompA| {}).is_none()); 88 | 89 | let entity_direct_b = ecs_find!(world, entity_b, |direct: &EntityDirectAny| { 90 | *direct 91 | }).unwrap(); 92 | 93 | world.destroy(entity_direct_b); 94 | 95 | assert!(ecs_find!(world, entity_direct_a, |_: &CompA| {}).is_none()); 96 | assert!(ecs_find!(world, entity_direct_b, |_: &CompA| {}).is_none()); 97 | } 98 | 99 | #[test] 100 | #[rustfmt::skip] 101 | pub fn test_direct_destroy_archetype() { 102 | let mut world = EcsWorld::default(); 103 | 104 | let entity_a = world.create::((CompA(1), CompB(10))); 105 | let entity_b = world.create::((CompA(1), CompB(10))); 106 | 107 | let entity_direct_a = ecs_find!(world, entity_a, |direct: &EntityDirect| { 108 | *direct 109 | }).unwrap(); 110 | 111 | world.arch_foo.destroy(entity_direct_a); 112 | 113 | assert!(ecs_find!(world, entity_direct_a, |_: &CompA| {}).is_none()); 114 | 115 | let entity_direct_b = ecs_find!(world, entity_b, |direct: &EntityDirectAny| { 116 | *direct 117 | }).unwrap(); 118 | 119 | world.arch_foo.destroy(entity_direct_b); 120 | 121 | assert!(ecs_find!(world, entity_direct_a, |_: &CompA| {}).is_none()); 122 | assert!(ecs_find!(world, entity_direct_b, |_: &CompA| {}).is_none()); 123 | } 124 | -------------------------------------------------------------------------------- /tests/test_entity_wild.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub struct CompA(pub u32); 5 | #[derive(Debug, PartialEq)] 6 | pub struct CompB(pub u32); 7 | #[derive(Debug, PartialEq)] 8 | pub struct CompC(pub u32); 9 | 10 | ecs_world! { 11 | #[archetype_id(3)] 12 | ecs_archetype!( 13 | ArchFoo, 14 | CompA, 15 | CompB, 16 | ); 17 | 18 | ecs_archetype!( 19 | ArchBar, 20 | CompA, 21 | CompC, 22 | ); 23 | } 24 | 25 | #[test] 26 | #[rustfmt::skip] 27 | fn test_one_of_basic() { 28 | let mut world = EcsWorld::default(); 29 | 30 | let entity_a = world.archetype_mut::().create((CompA(1), CompB(10))); 31 | let entity_b = world.archetype_mut::().create((CompA(1), CompC(10))); 32 | 33 | ecs_iter!(world, |entity: &Entity<_>| { 34 | match entity.into() { 35 | SelectEntity::ArchFoo(entity) => check_entity_type_a(entity), 36 | SelectEntity::ArchBar(entity) => check_entity_type_b(entity), 37 | } 38 | }); 39 | 40 | ecs_find!(world, entity_a, |entity: &Entity<_>, _: &CompB| { 41 | check_entity_type_a(*entity); 42 | }); 43 | 44 | ecs_find!(world, entity_b, |entity: &Entity<_>, _: &CompC| { 45 | check_entity_type_b(*entity); 46 | }); 47 | 48 | ecs_iter!(world, |entity: &Entity<_>, _: &CompB| { 49 | check_entity_type_a(*entity); 50 | }); 51 | 52 | ecs_iter!(world, |entity: &Entity<_>, _: &CompC| { 53 | check_entity_type_b(*entity); 54 | }); 55 | } 56 | 57 | fn check_entity_type_a(_: Entity) {} 58 | fn check_entity_type_b(_: Entity) {} 59 | -------------------------------------------------------------------------------- /tests/test_events.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA; 4 | 5 | ecs_world! { 6 | ecs_archetype!(ArchFoo, CompA); 7 | ecs_archetype!(ArchBar, CompA); 8 | ecs_archetype!(ArchBaz, CompA); 9 | ecs_archetype!(ArchQux, CompA); 10 | } 11 | 12 | #[test] 13 | #[cfg(feature = "events")] 14 | fn test_size_hint() { 15 | let mut world = EcsWorld::new(); 16 | 17 | for _ in 0..4 { 18 | world.create::((CompA,)); 19 | } 20 | 21 | for _ in 0..11 { 22 | world.create::((CompA,)); 23 | } 24 | 25 | for _ in 0..23 { 26 | world.create::((CompA,)); 27 | } 28 | 29 | for _ in 0..6 { 30 | world.create::((CompA,)); 31 | } 32 | 33 | assert_eq!(world.iter_created().count(), 4 + 11 + 23 + 6); 34 | 35 | let (min, max) = world.iter_created().size_hint(); 36 | assert!(max.is_some_and(|max| max >= min)); 37 | assert_eq!(min, 4 + 11 + 23 + 6); 38 | assert_eq!(max, Some(4 + 11 + 23 + 6)); 39 | 40 | let mut iter = world.iter_created(); 41 | 42 | for _ in 0..10 { 43 | iter.next(); 44 | } 45 | 46 | let (min, max) = iter.size_hint(); 47 | assert!(max.is_some_and(|max| max >= min)); 48 | assert_eq!(min, 4 + 11 + 23 + 6 - 10); 49 | assert_eq!(max, Some(4 + 11 + 23 + 6 - 10)); 50 | } 51 | -------------------------------------------------------------------------------- /tests/test_generic.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompB(pub u32); 5 | pub struct CompC(pub u32); 6 | 7 | ecs_world! { 8 | ecs_archetype!(ArchFoo, CompA, CompB); 9 | ecs_archetype!(ArchBar, CompA, CompC); 10 | } 11 | 12 | #[test] 13 | pub fn test_generic_view() { 14 | let mut world = EcsWorld::new(); 15 | 16 | let entity = world.create::((CompA(1), CompB(1))); 17 | 18 | let mut view = world.view(entity).unwrap(); 19 | view_increment(&mut view); 20 | 21 | assert_eq!(view.component::().0, 2); 22 | assert_eq!(view.component::().0, 2); 23 | } 24 | 25 | #[test] 26 | pub fn test_generic_view_get() { 27 | let mut world = EcsWorld::new(); 28 | 29 | let entity = world.create::((CompA(1), CompB(1))); 30 | 31 | view_get_increment(&mut world, entity); 32 | 33 | let view = world.view(entity).unwrap(); 34 | assert_eq!(view.component::().0, 2); 35 | assert_eq!(view.component::().0, 2); 36 | } 37 | 38 | #[test] 39 | pub fn test_generic_borrow() { 40 | let mut world = EcsWorld::new(); 41 | 42 | let entity = world.create::((CompA(1), CompB(1))); 43 | 44 | let mut borrow = world.borrow(entity).unwrap(); 45 | borrow_increment(&mut borrow); 46 | 47 | assert_eq!(borrow.component::().0, 2); 48 | assert_eq!(borrow.component::().0, 2); 49 | } 50 | 51 | #[test] 52 | pub fn test_generic_borrow_get() { 53 | let mut world = EcsWorld::new(); 54 | 55 | let entity = world.create::((CompA(1), CompB(1))); 56 | 57 | borrow_get_increment(&mut world, entity); 58 | 59 | let borrow = world.borrow(entity).unwrap(); 60 | assert_eq!(borrow.component::().0, 2); 61 | assert_eq!(borrow.component::().0, 2); 62 | } 63 | 64 | fn view_increment<'a, V: View<'a>>(view: &mut V) 65 | where 66 | V::Archetype: ArchetypeHas + ArchetypeHas, 67 | { 68 | view.component_mut::().0 += 1; 69 | view.component_mut::().0 += 1; 70 | } 71 | 72 | fn view_get_increment(world: &mut W, entity: Entity) 73 | where 74 | W: WorldHas, 75 | A: ArchetypeHas + ArchetypeHas, 76 | { 77 | let mut view = world.view(entity).unwrap(); 78 | view.component_mut::().0 += 1; 79 | view.component_mut::().0 += 1; 80 | } 81 | 82 | fn borrow_increment<'a, B: Borrow<'a>>(borrow: &mut B) 83 | where 84 | B::Archetype: ArchetypeHas + ArchetypeHas, 85 | { 86 | borrow.component_mut::().0 += 1; 87 | borrow.component_mut::().0 += 1; 88 | } 89 | 90 | fn borrow_get_increment(world: &mut W, entity: Entity) 91 | where 92 | W: WorldHas, 93 | A: ArchetypeHas + ArchetypeHas, 94 | { 95 | let borrow = world.borrow(entity).unwrap(); 96 | borrow.component_mut::().0 += 1; 97 | borrow.component_mut::().0 += 1; 98 | } 99 | -------------------------------------------------------------------------------- /tests/test_iter.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompZ; // ZST 5 | 6 | ecs_world! { 7 | ecs_archetype!( 8 | ArchFoo, 9 | CompA, 10 | CompZ, 11 | ); 12 | } 13 | 14 | #[test] 15 | #[rustfmt::skip] 16 | pub fn test_single_iter() { 17 | let mut world = EcsWorld::default(); 18 | 19 | world.arch_foo.create((CompA(0), CompZ,)); 20 | world.arch_foo.create((CompA(1), CompZ,)); 21 | world.arch_foo.create((CompA(2), CompZ,)); 22 | world.arch_foo.create((CompA(3), CompZ,)); 23 | world.arch_foo.create((CompA(4), CompZ,)); 24 | 25 | let mut vec = Vec::new(); 26 | 27 | for (_, a, _) in world.arch_foo.iter() { 28 | vec.push(a.0); 29 | } 30 | 31 | assert_eq!(vec, vec![0, 1, 2, 3, 4]); 32 | 33 | vec.clear(); 34 | 35 | for (_, a, _) in world.arch_foo.iter_mut() { 36 | a.0 += 1; 37 | } 38 | 39 | for (_, a, _) in world.arch_foo.iter() { 40 | vec.push(a.0); 41 | } 42 | 43 | assert_eq!(vec, vec![1, 2, 3, 4, 5]); 44 | } 45 | -------------------------------------------------------------------------------- /tests/test_iter_destroy.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub struct CompA(pub u32); 5 | #[derive(Debug, PartialEq)] 6 | pub struct CompB(pub u32); 7 | #[derive(Debug, PartialEq)] 8 | pub struct CompC(pub u32); 9 | 10 | ecs_world! { 11 | #[archetype_id(3)] 12 | ecs_archetype!( 13 | ArchFoo, 14 | CompA, 15 | CompB, 16 | ); 17 | 18 | ecs_archetype!( 19 | ArchBar, 20 | CompA, 21 | CompC, 22 | ); 23 | } 24 | 25 | #[test] 26 | #[rustfmt::skip] 27 | fn test_one_of_basic() { 28 | let mut world = EcsWorld::default(); 29 | 30 | world.archetype_mut::().create((CompA(1), CompB(10))); 31 | world.archetype_mut::().create((CompA(2), CompB(20))); 32 | world.archetype_mut::().create((CompA(3), CompB(30))); 33 | 34 | world.archetype_mut::().create((CompA(4), CompC(10))); 35 | world.archetype_mut::().create((CompA(5), CompC(10))); 36 | world.archetype_mut::().create((CompA(6), CompC(10))); 37 | 38 | let mut vec_a = Vec::::new(); 39 | let mut vec_b = Vec::::new(); 40 | 41 | ecs_iter_destroy!(world, |comp_a: &CompA| { 42 | if comp_a.0 & 1 == 0 { 43 | vec_a.push(comp_a.0); 44 | EcsStepDestroy::ContinueDestroy 45 | } else { 46 | EcsStepDestroy::Continue 47 | } 48 | }); 49 | 50 | ecs_iter!(world, |comp_a: &CompA| { 51 | vec_b.push(comp_a.0); 52 | }); 53 | 54 | assert_eq!(vec_a.iter().copied().sum::(), 2 + 4 + 6); 55 | assert_eq!(vec_b.iter().copied().sum::(), 1 + 3 + 5); 56 | } 57 | -------------------------------------------------------------------------------- /tests/test_one_of.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub struct CompA(pub u32); 5 | #[derive(Debug, PartialEq)] 6 | pub struct CompB(pub u32); 7 | #[derive(Debug, PartialEq)] 8 | pub struct CompC(pub u32); 9 | 10 | ecs_world! { 11 | #[archetype_id(3)] 12 | ecs_archetype!( 13 | ArchFoo, 14 | CompA, 15 | CompB, 16 | ); 17 | 18 | ecs_archetype!( 19 | ArchBar, 20 | CompA, 21 | CompC, 22 | ); 23 | } 24 | 25 | #[test] 26 | #[rustfmt::skip] 27 | fn test_one_of_basic() { 28 | let mut world = EcsWorld::default(); 29 | 30 | let entity_a = world.archetype_mut::().create((CompA(1), CompB(10))); 31 | let entity_b = world.archetype_mut::().create((CompA(1), CompC(10))); 32 | 33 | let mut sum_a = 0; 34 | let mut sum_b = 0; 35 | 36 | ecs_find!(world, entity_a, |v: &mut OneOf| { 37 | v.0 += 1; 38 | }); 39 | 40 | ecs_find!(world, entity_b, |v: &mut OneOf| { 41 | v.0 += 1; 42 | }); 43 | 44 | ecs_iter!(world, |u: &CompA, v: &OneOf| { 45 | sum_a += u.0; 46 | sum_b += v.0; 47 | }); 48 | 49 | assert_eq!(sum_a, 2); 50 | assert_eq!(sum_b, 22); 51 | } 52 | -------------------------------------------------------------------------------- /tests/test_query_cfg.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompB(pub u32); 5 | pub struct CompZ; // ZST 6 | 7 | ecs_world! { 8 | ecs_archetype!( 9 | ArchFoo, 10 | CompA, 11 | #[cfg(any())] CompZ, 12 | ); 13 | 14 | ecs_archetype!( 15 | ArchBar, 16 | CompB, 17 | #[cfg(any())] CompZ, 18 | ); 19 | } 20 | 21 | #[test] 22 | #[rustfmt::skip] 23 | pub fn test_query_cfg() { 24 | let mut world = EcsWorld::default(); 25 | 26 | world.arch_foo.create((CompA(0), #[cfg(any())] CompZ,)); 27 | world.arch_foo.create((CompA(1), #[cfg(any())] CompZ,)); 28 | world.arch_foo.create((CompA(2), #[cfg(any())] CompZ,)); 29 | world.arch_foo.create((CompA(3), #[cfg(any())] CompZ,)); 30 | 31 | world.arch_bar.create((CompB(0), #[cfg(any())] CompZ,)); 32 | 33 | let entity = world.arch_foo.create((CompA(4), #[cfg(any())] CompZ,)); 34 | 35 | let mut sum = 0; 36 | ecs_iter!(world, |a: &CompA, #[cfg(any())] b: &CompZ| { 37 | sum += a.0; 38 | }); 39 | 40 | let foo = ecs_find!(world, entity, |#[cfg(any())] e: &EntityAny, a: &CompA, #[cfg(any())] b: &CompZ| { 41 | sum += a.0; 42 | 1234 43 | }); 44 | 45 | let mut matches = 0; 46 | ecs_iter!(world, |#[cfg(any())] e: &Entity| { // Should hit all entities 47 | matches += 1; 48 | }); 49 | 50 | assert_eq!(foo, Some(1234)); 51 | assert_eq!(sum, 1 + 2 + 3 + 4 + 4); 52 | assert_eq!(matches, 6); 53 | assert_eq!(world.arch_foo.len(), 5); 54 | } 55 | -------------------------------------------------------------------------------- /tests/test_raw.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub struct CompA(pub u32); 5 | 6 | ecs_world! { 7 | ecs_archetype!( 8 | ArchFoo, 9 | CompA, 10 | ); 11 | } 12 | 13 | #[test] 14 | fn test_raw() { 15 | let mut world = EcsWorld::default(); 16 | 17 | // Filler to check for bad region access 18 | world.create::((CompA(0xFEEEEEED),)); 19 | 20 | let entity = world.create::((CompA(0xDEADBEEF),)); 21 | 22 | // Filler to check for bad region access 23 | world.create::((CompA(0xBEEEEEEF),)); 24 | 25 | assert_eq!(ecs_find!(world, entity, |a: &CompA| a.0), Some(0xDEADBEEF)); 26 | 27 | let entity = EntityAny::from_raw(entity.into_any().raw()).unwrap(); 28 | assert_eq!(ecs_find!(world, entity, |a: &CompA| a.0), Some(0xDEADBEEF)); 29 | } 30 | -------------------------------------------------------------------------------- /tests/test_select.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompB(pub u32); 5 | pub struct CompZ; // ZST 6 | 7 | ecs_world! { 8 | ecs_archetype!( 9 | ArchFoo, 10 | CompA, 11 | CompZ, 12 | ); 13 | 14 | ecs_archetype!( 15 | ArchBar, 16 | CompB, 17 | CompZ, 18 | ); 19 | } 20 | 21 | #[test] 22 | fn test_select() { 23 | let mut world = EcsWorld::default(); 24 | 25 | let entity: Entity = world 26 | .archetype_mut::() 27 | .create((CompA(1), CompZ)); 28 | 29 | match entity.try_into() { 30 | Ok(SelectEntity::ArchFoo(entity)) => assert!(world.contains(entity)), 31 | Ok(SelectEntity::ArchBar(_)) => panic!(), 32 | Err(_) => panic!(), 33 | }; 34 | } 35 | 36 | #[test] 37 | fn test_select_direct() { 38 | let mut world = EcsWorld::default(); 39 | 40 | let entity = world 41 | .archetype_mut::() 42 | .create((CompA(1), CompZ)); 43 | let entity: EntityDirect = world.resolve_direct(entity).unwrap(); 44 | 45 | match entity.try_into() { 46 | Ok(SelectEntityDirect::ArchFoo(entity)) => assert!(world.contains(entity)), 47 | Ok(SelectEntityDirect::ArchBar(_)) => panic!(), 48 | Err(_) => panic!(), 49 | }; 50 | } 51 | 52 | 53 | #[test] 54 | fn test_select_any() { 55 | let mut world = EcsWorld::default(); 56 | 57 | let entity: EntityAny = world 58 | .archetype_mut::() 59 | .create((CompA(1), CompZ)) 60 | .into_any(); 61 | 62 | match entity.try_into() { 63 | Ok(SelectEntity::ArchFoo(entity)) => assert!(world.contains(entity)), 64 | Ok(SelectEntity::ArchBar(_)) => panic!(), 65 | Err(_) => panic!(), 66 | }; 67 | } 68 | 69 | #[test] 70 | fn test_select_any_direct() { 71 | let mut world = EcsWorld::default(); 72 | 73 | let entity = world 74 | .archetype_mut::() 75 | .create((CompA(1), CompZ)); 76 | let entity: EntityDirectAny = world.resolve_direct(entity).unwrap().into_any(); 77 | 78 | match entity.try_into() { 79 | Ok(SelectEntityDirect::ArchFoo(entity)) => assert!(world.contains(entity)), 80 | Ok(SelectEntityDirect::ArchBar(_)) => panic!(), 81 | Err(_) => panic!(), 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /tests/test_single.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompZ; // ZST 5 | 6 | ecs_world! { 7 | ecs_archetype!( 8 | ArchFoo, 9 | CompA, 10 | CompZ, 11 | ); 12 | } 13 | 14 | #[test] 15 | #[rustfmt::skip] 16 | pub fn test_archetype_id() { 17 | assert_eq!(ArchFoo::ARCHETYPE_ID, 0); // Implicit 18 | } 19 | 20 | #[test] 21 | #[rustfmt::skip] 22 | pub fn test_single_create() { 23 | let mut world = EcsWorld::default(); 24 | 25 | world.arch_foo.create((CompA(0), CompZ,)); 26 | world.arch_foo.create((CompA(1), CompZ,)); 27 | world.arch_foo.create((CompA(2), CompZ,)); 28 | world.arch_foo.create((CompA(3), CompZ,)); 29 | world.arch_foo.create((CompA(4), CompZ,)); 30 | 31 | assert_eq!(world.arch_foo.len(), 5); 32 | } 33 | 34 | #[test] 35 | #[rustfmt::skip] 36 | pub fn test_single_entity() { 37 | let mut world = EcsWorld::default(); 38 | 39 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 40 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 41 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 42 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 43 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 44 | 45 | assert!(ecs_find!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 46 | assert!(ecs_find!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 47 | assert!(ecs_find!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 48 | assert!(ecs_find!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 49 | assert!(ecs_find!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 50 | 51 | assert!(ecs_find_borrow!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 52 | assert!(ecs_find_borrow!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 53 | assert!(ecs_find_borrow!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 54 | assert!(ecs_find_borrow!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 55 | assert!(ecs_find_borrow!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 56 | 57 | assert!(world.arch_foo.destroy(entity_0).is_some()); 58 | assert!(world.arch_foo.destroy(entity_1).is_some()); 59 | assert!(world.arch_foo.destroy(entity_2).is_some()); 60 | assert!(world.arch_foo.destroy(entity_3).is_some()); 61 | assert!(world.arch_foo.destroy(entity_4).is_some()); 62 | 63 | let entity_0b = world.arch_foo.create((CompA(0), CompZ,)); 64 | let entity_1b = world.arch_foo.create((CompA(1), CompZ,)); 65 | let entity_2b = world.arch_foo.create((CompA(2), CompZ,)); 66 | let entity_3b = world.arch_foo.create((CompA(3), CompZ,)); 67 | let entity_4b = world.arch_foo.create((CompA(4), CompZ,)); 68 | 69 | assert!(entity_0 != entity_0b); 70 | assert!(entity_1 != entity_1b); 71 | assert!(entity_2 != entity_2b); 72 | assert!(entity_3 != entity_3b); 73 | assert!(entity_4 != entity_4b); 74 | 75 | assert!(ecs_find!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 76 | assert!(ecs_find!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 77 | assert!(ecs_find!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 78 | assert!(ecs_find!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 79 | assert!(ecs_find!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 80 | 81 | assert!(ecs_find_borrow!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 82 | assert!(ecs_find_borrow!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 83 | assert!(ecs_find_borrow!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 84 | assert!(ecs_find_borrow!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 85 | assert!(ecs_find_borrow!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 86 | } 87 | 88 | #[test] 89 | #[rustfmt::skip] 90 | pub fn test_single_find() { 91 | let mut world = EcsWorld::default(); 92 | 93 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 94 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 95 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 96 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 97 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 98 | 99 | assert!(ecs_find!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 100 | assert!(ecs_find!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 101 | assert!(ecs_find!(world, entity_2, |v: &CompA| assert_eq!(v.0, 2)).is_some()); 102 | assert!(ecs_find!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 103 | assert!(ecs_find!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 104 | 105 | assert!(ecs_find_borrow!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 106 | assert!(ecs_find_borrow!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 107 | assert!(ecs_find_borrow!(world, entity_2, |v: &CompA| assert_eq!(v.0, 2)).is_some()); 108 | assert!(ecs_find_borrow!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 109 | assert!(ecs_find_borrow!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 110 | 111 | assert!(ecs_find!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 112 | assert!(ecs_find!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 113 | assert!(ecs_find!(world, entity_2, |v: &mut CompA| assert_eq!(v.0, 2)).is_some()); 114 | assert!(ecs_find!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 115 | assert!(ecs_find!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 116 | 117 | assert!(ecs_find_borrow!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 118 | assert!(ecs_find_borrow!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 119 | assert!(ecs_find_borrow!(world, entity_2, |v: &mut CompA| assert_eq!(v.0, 2)).is_some()); 120 | assert!(ecs_find_borrow!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 121 | assert!(ecs_find_borrow!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 122 | 123 | world.arch_foo.destroy(entity_2).unwrap(); 124 | 125 | assert!(ecs_find!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 126 | assert!(ecs_find!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 127 | assert!(ecs_find!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 128 | assert!(ecs_find!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 129 | 130 | assert!(ecs_find_borrow!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 131 | assert!(ecs_find_borrow!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 132 | assert!(ecs_find_borrow!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 133 | assert!(ecs_find_borrow!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 134 | 135 | assert!(ecs_find!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 136 | assert!(ecs_find!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 137 | assert!(ecs_find!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 138 | assert!(ecs_find!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 139 | 140 | assert!(ecs_find_borrow!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 141 | assert!(ecs_find_borrow!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 142 | assert!(ecs_find_borrow!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 143 | assert!(ecs_find_borrow!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 144 | 145 | assert!(ecs_find!(world, entity_2, |_: &CompA| panic!()).is_none()); 146 | assert!(ecs_find!(world, entity_2, |_: &mut CompA| panic!()).is_none()); 147 | 148 | assert!(ecs_find_borrow!(world, entity_2, |_: &CompA| panic!()).is_none()); 149 | assert!(ecs_find_borrow!(world, entity_2, |_: &mut CompA| panic!()).is_none()); 150 | } 151 | 152 | #[test] 153 | #[rustfmt::skip] 154 | pub fn test_single_iter() { 155 | let mut world = EcsWorld::default(); 156 | 157 | let _entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 158 | let _entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 159 | let _entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 160 | let _entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 161 | let _entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 162 | 163 | let mut sum = 0; 164 | ecs_iter!(world, |v: &CompA| sum += v.0); 165 | assert_eq!(sum, 0+1+2+3+4); 166 | 167 | let mut sum = 0; 168 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 169 | assert_eq!(sum, 0+1+2+3+4); 170 | 171 | let mut sum = 0; 172 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 173 | assert_eq!(sum, 0+1+2+3+4); 174 | 175 | let mut sum = 0; 176 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 177 | assert_eq!(sum, 0+1+2+3+4); 178 | 179 | world.arch_foo.destroy(_entity_2).unwrap(); 180 | 181 | let mut sum = 0; 182 | ecs_iter!(world, |v: &CompA| sum += v.0); 183 | assert_eq!(sum, 0+1+3+4); 184 | 185 | let mut sum = 0; 186 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 187 | assert_eq!(sum, 0+1+3+4); 188 | 189 | let mut sum = 0; 190 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 191 | assert_eq!(sum, 0+1+3+4); 192 | 193 | let mut sum = 0; 194 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 195 | assert_eq!(sum, 0+1+3+4); 196 | } 197 | 198 | #[test] 199 | #[rustfmt::skip] 200 | pub fn test_single_iter_write() { 201 | let mut world = EcsWorld::default(); 202 | 203 | let _entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 204 | let _entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 205 | let _entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 206 | let _entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 207 | let _entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 208 | 209 | ecs_iter!(world, |v: &mut CompA| v.0 += 100); 210 | 211 | let mut sum = 0; 212 | ecs_iter!(world, |v: &CompA| sum += v.0); 213 | assert_eq!(sum, 100+101+102+103+104); 214 | 215 | let mut sum = 0; 216 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 217 | assert_eq!(sum, 100+101+102+103+104); 218 | 219 | let mut sum = 0; 220 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 221 | assert_eq!(sum, 100+101+102+103+104); 222 | 223 | let mut sum = 0; 224 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 225 | assert_eq!(sum, 100+101+102+103+104); 226 | 227 | world.arch_foo.destroy(_entity_2).unwrap(); 228 | 229 | let mut sum = 0; 230 | ecs_iter!(world, |v: &CompA| sum += v.0); 231 | assert_eq!(sum, 100+101+103+104); 232 | 233 | let mut sum = 0; 234 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 235 | assert_eq!(sum, 100+101+103+104); 236 | 237 | let mut sum = 0; 238 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 239 | assert_eq!(sum, 100+101+103+104); 240 | 241 | let mut sum = 0; 242 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 243 | assert_eq!(sum, 100+101+103+104); 244 | } 245 | 246 | #[test] 247 | #[rustfmt::skip] 248 | pub fn test_single_destroy_replace() { 249 | let mut world = EcsWorld::default(); 250 | 251 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 252 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 253 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 254 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 255 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 256 | 257 | assert_eq!(world.arch_foo.len(), 5); 258 | 259 | assert_eq!(world.arch_foo.destroy(entity_4).unwrap().comp_a.0, 4); 260 | assert_eq!(world.arch_foo.len(), 4); 261 | 262 | assert_eq!(world.arch_foo.destroy(entity_1).unwrap().comp_a.0, 1); 263 | assert_eq!(world.arch_foo.len(), 3); 264 | 265 | assert_eq!(world.arch_foo.destroy(entity_2).unwrap().comp_a.0, 2); 266 | assert_eq!(world.arch_foo.len(), 2); 267 | 268 | assert_eq!(world.arch_foo.destroy(entity_3).unwrap().comp_a.0, 3); 269 | assert_eq!(world.arch_foo.len(), 1); 270 | 271 | assert_eq!(world.arch_foo.destroy(entity_0).unwrap().comp_a.0, 0); 272 | assert_eq!(world.arch_foo.len(), 0); 273 | 274 | assert!(ecs_find!(world, entity_0, |_: &CompA| panic!()).is_none()); 275 | assert!(ecs_find!(world, entity_1, |_: &CompA| panic!()).is_none()); 276 | assert!(ecs_find!(world, entity_2, |_: &CompA| panic!()).is_none()); 277 | assert!(ecs_find!(world, entity_3, |_: &CompA| panic!()).is_none()); 278 | assert!(ecs_find!(world, entity_4, |_: &CompA| panic!()).is_none()); 279 | 280 | assert!(ecs_find_borrow!(world, entity_0, |_: &CompA| panic!()).is_none()); 281 | assert!(ecs_find_borrow!(world, entity_1, |_: &CompA| panic!()).is_none()); 282 | assert!(ecs_find_borrow!(world, entity_2, |_: &CompA| panic!()).is_none()); 283 | assert!(ecs_find_borrow!(world, entity_3, |_: &CompA| panic!()).is_none()); 284 | assert!(ecs_find_borrow!(world, entity_4, |_: &CompA| panic!()).is_none()); 285 | 286 | assert!(world.arch_foo.destroy(entity_0).is_none()); 287 | assert!(world.arch_foo.destroy(entity_1).is_none()); 288 | assert!(world.arch_foo.destroy(entity_2).is_none()); 289 | assert!(world.arch_foo.destroy(entity_3).is_none()); 290 | assert!(world.arch_foo.destroy(entity_4).is_none()); 291 | 292 | let entity_0b = world.arch_foo.create((CompA(1000), CompZ,)); 293 | let entity_1b = world.arch_foo.create((CompA(1001), CompZ,)); 294 | let entity_2b = world.arch_foo.create((CompA(1002), CompZ,)); 295 | let entity_3b = world.arch_foo.create((CompA(1003), CompZ,)); 296 | let entity_4b = world.arch_foo.create((CompA(1004), CompZ,)); 297 | 298 | assert!(ecs_find!(world, entity_0b, |v: &CompA| assert_eq!(v.0, 1000)).is_some()); 299 | assert!(ecs_find!(world, entity_1b, |v: &CompA| assert_eq!(v.0, 1001)).is_some()); 300 | assert!(ecs_find!(world, entity_2b, |v: &CompA| assert_eq!(v.0, 1002)).is_some()); 301 | assert!(ecs_find!(world, entity_3b, |v: &CompA| assert_eq!(v.0, 1003)).is_some()); 302 | assert!(ecs_find!(world, entity_4b, |v: &CompA| assert_eq!(v.0, 1004)).is_some()); 303 | 304 | assert!(ecs_find_borrow!(world, entity_0b, |v: &CompA| assert_eq!(v.0, 1000)).is_some()); 305 | assert!(ecs_find_borrow!(world, entity_1b, |v: &CompA| assert_eq!(v.0, 1001)).is_some()); 306 | assert!(ecs_find_borrow!(world, entity_2b, |v: &CompA| assert_eq!(v.0, 1002)).is_some()); 307 | assert!(ecs_find_borrow!(world, entity_3b, |v: &CompA| assert_eq!(v.0, 1003)).is_some()); 308 | assert!(ecs_find_borrow!(world, entity_4b, |v: &CompA| assert_eq!(v.0, 1004)).is_some()); 309 | } 310 | 311 | #[test] 312 | #[rustfmt::skip] 313 | pub fn test_within_capacity() { 314 | let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 2 }); 315 | 316 | assert!(world.create_within_capacity::((CompA(1), CompZ)).is_ok()); 317 | assert!(world.arch_foo.create_within_capacity((CompA(1), CompZ)).is_ok()); 318 | 319 | assert!(world.create_within_capacity::((CompA(1), CompZ)).is_err()); 320 | assert!(world.arch_foo.create_within_capacity((CompA(1), CompZ)).is_err()); 321 | } 322 | -------------------------------------------------------------------------------- /tests/test_single_dyn.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA(pub u32); 4 | pub struct CompZ; // ZST 5 | 6 | ecs_world! { 7 | ecs_archetype!( 8 | ArchFoo, 9 | CompA, 10 | CompZ, 11 | ); 12 | } 13 | 14 | #[test] 15 | #[rustfmt::skip] 16 | pub fn test_single_dyn_create() { 17 | let mut world = EcsWorld::default(); 18 | 19 | world.arch_foo.create((CompA(0), CompZ,)); 20 | world.arch_foo.create((CompA(1), CompZ,)); 21 | world.arch_foo.create((CompA(2), CompZ,)); 22 | world.arch_foo.create((CompA(3), CompZ,)); 23 | world.arch_foo.create((CompA(4), CompZ,)); 24 | 25 | assert_eq!(world.arch_foo.len(), 5); 26 | } 27 | 28 | #[test] 29 | #[rustfmt::skip] 30 | pub fn test_single_dyn_create_with_capacity_zero() { 31 | let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 0 }); 32 | 33 | world.arch_foo.create((CompA(0), CompZ,)); 34 | world.arch_foo.create((CompA(1), CompZ,)); 35 | world.arch_foo.create((CompA(2), CompZ,)); 36 | world.arch_foo.create((CompA(3), CompZ,)); 37 | world.arch_foo.create((CompA(4), CompZ,)); 38 | 39 | assert_eq!(world.arch_foo.len(), 5); 40 | } 41 | 42 | #[test] 43 | #[rustfmt::skip] 44 | pub fn test_single_dyn_create_with_capacity_all() { 45 | let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 5 }); 46 | 47 | world.arch_foo.create((CompA(0), CompZ,)); 48 | world.arch_foo.create((CompA(1), CompZ,)); 49 | world.arch_foo.create((CompA(2), CompZ,)); 50 | world.arch_foo.create((CompA(3), CompZ,)); 51 | world.arch_foo.create((CompA(4), CompZ,)); 52 | 53 | assert_eq!(world.arch_foo.len(), 5); 54 | } 55 | 56 | #[test] 57 | #[rustfmt::skip] 58 | pub fn test_single_dyn_entity() { 59 | let mut world = EcsWorld::default(); 60 | 61 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 62 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 63 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 64 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 65 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 66 | 67 | assert!(ecs_find!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 68 | assert!(ecs_find!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 69 | assert!(ecs_find!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 70 | assert!(ecs_find!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 71 | assert!(ecs_find!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 72 | 73 | assert!(ecs_find_borrow!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 74 | assert!(ecs_find_borrow!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 75 | assert!(ecs_find_borrow!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 76 | assert!(ecs_find_borrow!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 77 | assert!(ecs_find_borrow!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 78 | 79 | assert!(world.arch_foo.destroy(entity_0).is_some()); 80 | assert!(world.arch_foo.destroy(entity_1).is_some()); 81 | assert!(world.arch_foo.destroy(entity_2).is_some()); 82 | assert!(world.arch_foo.destroy(entity_3).is_some()); 83 | assert!(world.arch_foo.destroy(entity_4).is_some()); 84 | 85 | let entity_0b = world.arch_foo.create((CompA(0), CompZ,)); 86 | let entity_1b = world.arch_foo.create((CompA(1), CompZ,)); 87 | let entity_2b = world.arch_foo.create((CompA(2), CompZ,)); 88 | let entity_3b = world.arch_foo.create((CompA(3), CompZ,)); 89 | let entity_4b = world.arch_foo.create((CompA(4), CompZ,)); 90 | 91 | assert!(entity_0 != entity_0b); 92 | assert!(entity_1 != entity_1b); 93 | assert!(entity_2 != entity_2b); 94 | assert!(entity_3 != entity_3b); 95 | assert!(entity_4 != entity_4b); 96 | 97 | assert!(ecs_find!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 98 | assert!(ecs_find!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 99 | assert!(ecs_find!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 100 | assert!(ecs_find!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 101 | assert!(ecs_find!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 102 | 103 | assert!(ecs_find_borrow!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 104 | assert!(ecs_find_borrow!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 105 | assert!(ecs_find_borrow!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 106 | assert!(ecs_find_borrow!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 107 | assert!(ecs_find_borrow!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 108 | } 109 | 110 | #[test] 111 | #[rustfmt::skip] 112 | pub fn test_single_dyn_entity_with_capacity() { 113 | let mut world = EcsWorld::with_capacity(EcsWorldCapacity { arch_foo: 5 }); 114 | 115 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 116 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 117 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 118 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 119 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 120 | 121 | assert!(ecs_find!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 122 | assert!(ecs_find!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 123 | assert!(ecs_find!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 124 | assert!(ecs_find!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 125 | assert!(ecs_find!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 126 | 127 | assert!(ecs_find_borrow!(world, entity_0, |v: &Entity| assert!(*v == entity_0)).is_some()); 128 | assert!(ecs_find_borrow!(world, entity_1, |v: &Entity| assert!(*v == entity_1)).is_some()); 129 | assert!(ecs_find_borrow!(world, entity_2, |v: &Entity| assert!(*v == entity_2)).is_some()); 130 | assert!(ecs_find_borrow!(world, entity_3, |v: &Entity| assert!(*v == entity_3)).is_some()); 131 | assert!(ecs_find_borrow!(world, entity_4, |v: &Entity| assert!(*v == entity_4)).is_some()); 132 | 133 | assert!(world.arch_foo.destroy(entity_0).is_some()); 134 | assert!(world.arch_foo.destroy(entity_1).is_some()); 135 | assert!(world.arch_foo.destroy(entity_2).is_some()); 136 | assert!(world.arch_foo.destroy(entity_3).is_some()); 137 | assert!(world.arch_foo.destroy(entity_4).is_some()); 138 | 139 | let entity_0b = world.arch_foo.create((CompA(0), CompZ,)); 140 | let entity_1b = world.arch_foo.create((CompA(1), CompZ,)); 141 | let entity_2b = world.arch_foo.create((CompA(2), CompZ,)); 142 | let entity_3b = world.arch_foo.create((CompA(3), CompZ,)); 143 | let entity_4b = world.arch_foo.create((CompA(4), CompZ,)); 144 | 145 | assert!(entity_0 != entity_0b); 146 | assert!(entity_1 != entity_1b); 147 | assert!(entity_2 != entity_2b); 148 | assert!(entity_3 != entity_3b); 149 | assert!(entity_4 != entity_4b); 150 | 151 | assert!(ecs_find!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 152 | assert!(ecs_find!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 153 | assert!(ecs_find!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 154 | assert!(ecs_find!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 155 | assert!(ecs_find!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 156 | 157 | assert!(ecs_find_borrow!(world, entity_0b, |v: &Entity| assert!(*v == entity_0b)).is_some()); 158 | assert!(ecs_find_borrow!(world, entity_1b, |v: &Entity| assert!(*v == entity_1b)).is_some()); 159 | assert!(ecs_find_borrow!(world, entity_2b, |v: &Entity| assert!(*v == entity_2b)).is_some()); 160 | assert!(ecs_find_borrow!(world, entity_3b, |v: &Entity| assert!(*v == entity_3b)).is_some()); 161 | assert!(ecs_find_borrow!(world, entity_4b, |v: &Entity| assert!(*v == entity_4b)).is_some()); 162 | } 163 | 164 | #[test] 165 | #[rustfmt::skip] 166 | pub fn test_single_dyn_find() { 167 | let mut world = EcsWorld::default(); 168 | 169 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 170 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 171 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 172 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 173 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 174 | 175 | assert!(ecs_find!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 176 | assert!(ecs_find!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 177 | assert!(ecs_find!(world, entity_2, |v: &CompA| assert_eq!(v.0, 2)).is_some()); 178 | assert!(ecs_find!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 179 | assert!(ecs_find!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 180 | 181 | assert!(ecs_find_borrow!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 182 | assert!(ecs_find_borrow!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 183 | assert!(ecs_find_borrow!(world, entity_2, |v: &CompA| assert_eq!(v.0, 2)).is_some()); 184 | assert!(ecs_find_borrow!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 185 | assert!(ecs_find_borrow!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 186 | 187 | assert!(ecs_find!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 188 | assert!(ecs_find!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 189 | assert!(ecs_find!(world, entity_2, |v: &mut CompA| assert_eq!(v.0, 2)).is_some()); 190 | assert!(ecs_find!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 191 | assert!(ecs_find!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 192 | 193 | assert!(ecs_find_borrow!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 194 | assert!(ecs_find_borrow!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 195 | assert!(ecs_find_borrow!(world, entity_2, |v: &mut CompA| assert_eq!(v.0, 2)).is_some()); 196 | assert!(ecs_find_borrow!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 197 | assert!(ecs_find_borrow!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 198 | 199 | world.arch_foo.destroy(entity_2).unwrap(); 200 | 201 | assert!(ecs_find!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 202 | assert!(ecs_find!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 203 | assert!(ecs_find!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 204 | assert!(ecs_find!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 205 | 206 | assert!(ecs_find_borrow!(world, entity_0, |v: &CompA| assert_eq!(v.0, 0)).is_some()); 207 | assert!(ecs_find_borrow!(world, entity_1, |v: &CompA| assert_eq!(v.0, 1)).is_some()); 208 | assert!(ecs_find_borrow!(world, entity_3, |v: &CompA| assert_eq!(v.0, 3)).is_some()); 209 | assert!(ecs_find_borrow!(world, entity_4, |v: &CompA| assert_eq!(v.0, 4)).is_some()); 210 | 211 | assert!(ecs_find!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 212 | assert!(ecs_find!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 213 | assert!(ecs_find!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 214 | assert!(ecs_find!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 215 | 216 | assert!(ecs_find_borrow!(world, entity_0, |v: &mut CompA| assert_eq!(v.0, 0)).is_some()); 217 | assert!(ecs_find_borrow!(world, entity_1, |v: &mut CompA| assert_eq!(v.0, 1)).is_some()); 218 | assert!(ecs_find_borrow!(world, entity_3, |v: &mut CompA| assert_eq!(v.0, 3)).is_some()); 219 | assert!(ecs_find_borrow!(world, entity_4, |v: &mut CompA| assert_eq!(v.0, 4)).is_some()); 220 | 221 | assert!(ecs_find!(world, entity_2, |_: &CompA| panic!()).is_none()); 222 | assert!(ecs_find!(world, entity_2, |_: &mut CompA| panic!()).is_none()); 223 | 224 | assert!(ecs_find_borrow!(world, entity_2, |_: &CompA| panic!()).is_none()); 225 | assert!(ecs_find_borrow!(world, entity_2, |_: &mut CompA| panic!()).is_none()); 226 | } 227 | 228 | #[test] 229 | #[rustfmt::skip] 230 | pub fn test_single_dyn_iter() { 231 | let mut world = EcsWorld::default(); 232 | 233 | let _entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 234 | let _entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 235 | let _entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 236 | let _entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 237 | let _entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 238 | 239 | let mut sum = 0; 240 | ecs_iter!(world, |v: &CompA| sum += v.0); 241 | assert_eq!(sum, 0+1+2+3+4); 242 | 243 | let mut sum = 0; 244 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 245 | assert_eq!(sum, 0+1+2+3+4); 246 | 247 | let mut sum = 0; 248 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 249 | assert_eq!(sum, 0+1+2+3+4); 250 | 251 | let mut sum = 0; 252 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 253 | assert_eq!(sum, 0+1+2+3+4); 254 | 255 | world.arch_foo.destroy(_entity_2).unwrap(); 256 | 257 | let mut sum = 0; 258 | ecs_iter!(world, |v: &CompA| sum += v.0); 259 | assert_eq!(sum, 0+1+3+4); 260 | 261 | let mut sum = 0; 262 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 263 | assert_eq!(sum, 0+1+3+4); 264 | 265 | let mut sum = 0; 266 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 267 | assert_eq!(sum, 0+1+3+4); 268 | 269 | let mut sum = 0; 270 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 271 | assert_eq!(sum, 0+1+3+4); 272 | } 273 | 274 | #[test] 275 | #[rustfmt::skip] 276 | pub fn test_single_dyn_iter_write() { 277 | let mut world = EcsWorld::default(); 278 | 279 | let _entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 280 | let _entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 281 | let _entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 282 | let _entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 283 | let _entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 284 | 285 | ecs_iter!(world, |v: &mut CompA| v.0 += 100); 286 | 287 | let mut sum = 0; 288 | ecs_iter!(world, |v: &CompA| sum += v.0); 289 | assert_eq!(sum, 100+101+102+103+104); 290 | 291 | let mut sum = 0; 292 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 293 | assert_eq!(sum, 100+101+102+103+104); 294 | 295 | let mut sum = 0; 296 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 297 | assert_eq!(sum, 100+101+102+103+104); 298 | 299 | let mut sum = 0; 300 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 301 | assert_eq!(sum, 100+101+102+103+104); 302 | 303 | world.arch_foo.destroy(_entity_2).unwrap(); 304 | 305 | let mut sum = 0; 306 | ecs_iter!(world, |v: &CompA| sum += v.0); 307 | assert_eq!(sum, 100+101+103+104); 308 | 309 | let mut sum = 0; 310 | ecs_iter_borrow!(world, |v: &CompA| sum += v.0); 311 | assert_eq!(sum, 100+101+103+104); 312 | 313 | let mut sum = 0; 314 | ecs_iter!(world, |v: &mut CompA| sum += v.0); 315 | assert_eq!(sum, 100+101+103+104); 316 | 317 | let mut sum = 0; 318 | ecs_iter_borrow!(world, |v: &mut CompA| sum += v.0); 319 | assert_eq!(sum, 100+101+103+104); 320 | } 321 | 322 | #[test] 323 | #[rustfmt::skip] 324 | pub fn test_single_dyn_destroy_replace() { 325 | let mut world = EcsWorld::default(); 326 | 327 | let entity_0 = world.arch_foo.create((CompA(0), CompZ,)); 328 | let entity_1 = world.arch_foo.create((CompA(1), CompZ,)); 329 | let entity_2 = world.arch_foo.create((CompA(2), CompZ,)); 330 | let entity_3 = world.arch_foo.create((CompA(3), CompZ,)); 331 | let entity_4 = world.arch_foo.create((CompA(4), CompZ,)); 332 | 333 | assert_eq!(world.arch_foo.len(), 5); 334 | 335 | assert_eq!(world.arch_foo.destroy(entity_4).unwrap().comp_a.0, 4); 336 | assert_eq!(world.arch_foo.len(), 4); 337 | 338 | assert_eq!(world.arch_foo.destroy(entity_1).unwrap().comp_a.0, 1); 339 | assert_eq!(world.arch_foo.len(), 3); 340 | 341 | assert_eq!(world.arch_foo.destroy(entity_2).unwrap().comp_a.0, 2); 342 | assert_eq!(world.arch_foo.len(), 2); 343 | 344 | assert_eq!(world.arch_foo.destroy(entity_3).unwrap().comp_a.0, 3); 345 | assert_eq!(world.arch_foo.len(), 1); 346 | 347 | assert_eq!(world.arch_foo.destroy(entity_0).unwrap().comp_a.0, 0); 348 | assert_eq!(world.arch_foo.len(), 0); 349 | 350 | assert!(ecs_find!(world, entity_0, |_: &CompA| panic!()).is_none()); 351 | assert!(ecs_find!(world, entity_1, |_: &CompA| panic!()).is_none()); 352 | assert!(ecs_find!(world, entity_2, |_: &CompA| panic!()).is_none()); 353 | assert!(ecs_find!(world, entity_3, |_: &CompA| panic!()).is_none()); 354 | assert!(ecs_find!(world, entity_4, |_: &CompA| panic!()).is_none()); 355 | 356 | assert!(ecs_find_borrow!(world, entity_0, |_: &CompA| panic!()).is_none()); 357 | assert!(ecs_find_borrow!(world, entity_1, |_: &CompA| panic!()).is_none()); 358 | assert!(ecs_find_borrow!(world, entity_2, |_: &CompA| panic!()).is_none()); 359 | assert!(ecs_find_borrow!(world, entity_3, |_: &CompA| panic!()).is_none()); 360 | assert!(ecs_find_borrow!(world, entity_4, |_: &CompA| panic!()).is_none()); 361 | 362 | assert!(world.arch_foo.destroy(entity_0).is_none()); 363 | assert!(world.arch_foo.destroy(entity_1).is_none()); 364 | assert!(world.arch_foo.destroy(entity_2).is_none()); 365 | assert!(world.arch_foo.destroy(entity_3).is_none()); 366 | assert!(world.arch_foo.destroy(entity_4).is_none()); 367 | 368 | let entity_0b = world.arch_foo.create((CompA(1000), CompZ,)); 369 | let entity_1b = world.arch_foo.create((CompA(1001), CompZ,)); 370 | let entity_2b = world.arch_foo.create((CompA(1002), CompZ,)); 371 | let entity_3b = world.arch_foo.create((CompA(1003), CompZ,)); 372 | let entity_4b = world.arch_foo.create((CompA(1004), CompZ,)); 373 | 374 | assert!(ecs_find!(world, entity_0b, |v: &CompA| assert_eq!(v.0, 1000)).is_some()); 375 | assert!(ecs_find!(world, entity_1b, |v: &CompA| assert_eq!(v.0, 1001)).is_some()); 376 | assert!(ecs_find!(world, entity_2b, |v: &CompA| assert_eq!(v.0, 1002)).is_some()); 377 | assert!(ecs_find!(world, entity_3b, |v: &CompA| assert_eq!(v.0, 1003)).is_some()); 378 | assert!(ecs_find!(world, entity_4b, |v: &CompA| assert_eq!(v.0, 1004)).is_some()); 379 | 380 | assert!(ecs_find_borrow!(world, entity_0b, |v: &CompA| assert_eq!(v.0, 1000)).is_some()); 381 | assert!(ecs_find_borrow!(world, entity_1b, |v: &CompA| assert_eq!(v.0, 1001)).is_some()); 382 | assert!(ecs_find_borrow!(world, entity_2b, |v: &CompA| assert_eq!(v.0, 1002)).is_some()); 383 | assert!(ecs_find_borrow!(world, entity_3b, |v: &CompA| assert_eq!(v.0, 1003)).is_some()); 384 | assert!(ecs_find_borrow!(world, entity_4b, |v: &CompA| assert_eq!(v.0, 1004)).is_some()); 385 | } 386 | -------------------------------------------------------------------------------- /tests/test_util.rs: -------------------------------------------------------------------------------- 1 | use gecs::prelude::*; 2 | 3 | pub struct CompA; 4 | pub struct CompB; 5 | pub struct CompC; 6 | 7 | ecs_world! { 8 | ecs_archetype!( 9 | ArchFoo, 10 | CompA, // = 0 11 | CompC, // = 1 12 | ); 13 | 14 | ecs_archetype!( 15 | ArchBar, 16 | #[component_id(6)] 17 | CompA, // = 6 18 | CompB, // = 7 (Implicit) 19 | CompC, // = 8 (Implicit) 20 | ); 21 | 22 | ecs_archetype!( 23 | ArchBaz, 24 | CompA, // = 0 (Implicit) 25 | CompB, // = 1 (Implicit) 26 | #[component_id(200)] 27 | CompC, // = 200 28 | ); 29 | } 30 | 31 | #[test] 32 | #[rustfmt::skip] 33 | fn test_component_id() { 34 | let mut world = EcsWorld::default(); 35 | 36 | let entity_a = world.archetype_mut::().create((CompA, CompC)); 37 | let entity_b = world.archetype_mut::().create((CompA, CompB, CompC)); 38 | let entity_c = world.archetype_mut::().create((CompA, CompB, CompC)); 39 | 40 | ecs_find!(world, entity_a, |_: &CompC| { 41 | assert_eq!(ecs_component_id!(CompC), 1); 42 | }); 43 | 44 | ecs_find!(world, entity_b, |_: &CompC| { 45 | assert_eq!(ecs_component_id!(CompC), 8); 46 | }); 47 | 48 | ecs_find!(world, entity_c, |_: &CompC| { 49 | assert_eq!(ecs_component_id!(CompC), 200); 50 | }); 51 | 52 | assert_eq!(ecs_component_id!(CompC, ArchFoo), 1); 53 | assert_eq!(ecs_component_id!(CompC, ArchBar), 8); 54 | assert_eq!(ecs_component_id!(CompC, ArchBaz), 200); 55 | } 56 | --------------------------------------------------------------------------------