├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── nbody.rs ├── position_update.rs └── scheduling.rs ├── rustfmt.toml └── src ├── entities.rs ├── iter.rs ├── join.rs ├── lib.rs ├── resource.rs └── world.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constellation" 3 | version = "0.2.0" 4 | authors = ["Thomas Gillen "] 5 | description = "A data-oriented entity component system optimized for cache coherent resource access and parallel system execution." 6 | repository = "https://github.com/TomGillen/constellation/" 7 | documentation = "https://docs.rs/constellation" 8 | readme = "README.md" 9 | license = "Apache-2.0" 10 | keywords = ["game", "gamedev", "ecs"] 11 | 12 | [dependencies] 13 | fnv = "1.0.5" 14 | crossbeam = "0.2.10" 15 | rayon = "0.7.0" 16 | arrayvec = "0.3.22" 17 | tuple_utils = "0.2" 18 | atom = "0.3.4" 19 | bitflags = "0.8.2" 20 | hibitset = "0.1.2" 21 | 22 | [dev-dependencies] 23 | serde_derive = "0.9" 24 | serde = "0.9" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Constellation ECS 2 | 3 | A data-oriented entity component system optimized for cache coherent resource access 4 | and parallel system execution. 5 | 6 | Constellation takes a "resource-first" approach to an ECS, where rather than focusing on 7 | components, the library is concerned about ensuring safe concurrent access to shared resources, 8 | while providing direct access to each resource's own APIs. These resources may store per-entity 9 | data such as positions, or may represent application wide services such as asset loaders 10 | or input devices. 11 | 12 | Methods for iterating through entity component data are built on top of the direct resource 13 | access APIs. 14 | 15 | Systems request read or write access to a set of resources, which can then be scheduled to 16 | be potentially executed in parallel by recording them into a `SystemCommandBuffer` and 17 | executing the command buffer within a `World`. 18 | 19 | Constellation is heavily influenced by the [Bitsquid engine](http://bitsquid.blogspot.com) (now Autodesk Stingray), the [Molecule Engine](https://blog.molecular-matters.com) and [Specs](https://github.com/slide-rs/specs). 20 | 21 | [Crates.io](https://crates.io/crates/constellation) 22 | [Documentation](https://docs.rs/constellation) 23 | 24 | ### Similar Projects 25 | * [Specs](https://github.com/slide-rs/specs) 26 | * [ecs-rs](https://github.com/HeroesGrave/ecs-rs) 27 | 28 | # Examples 29 | 30 | Defining Resources: 31 | 32 | ```rust 33 | // Per-entity position data. 34 | struct Position { 35 | x: f32, 36 | y: f32, 37 | z: f32 38 | } 39 | 40 | // Store position data into a vector resource 41 | type Positions = VecResource; 42 | 43 | // Per-entity debug names. 44 | struct DebugName { 45 | name: String 46 | } 47 | 48 | // Store debug names in a map resource 49 | type DebugNames = MapResource; 50 | 51 | let mut world = World::new(); 52 | world.register_resource(Positions::new()); 53 | world.register_resource(DebugNames::new()); 54 | ``` 55 | 56 | Update the world with Systems: 57 | 58 | ```rust 59 | let mut update = SystemCommandBuffer::default(); 60 | update.queue_systems(|scope| { 61 | scope.run_r1w1(|ctx, velocities: &Velocities, positions: &mut Positions| { 62 | println!("Updating positions"); 63 | // iterate through all components for entities with data in both 64 | // position and velocity resources 65 | for (_, p, v) in (velocities, positions).iter().components() { 66 | p.x += v.x; 67 | p.y += v.y; 68 | p.z += v.z; 69 | } 70 | }); 71 | 72 | scope.run_r2w0(|ctx, names: &DebugNames, positions: &Positions| { 73 | println!("Printing positions"); 74 | // iterate through all entity IDs for entities with data in both 75 | // `names` and `positions` 76 | let (entity_iter, n, p) = (names, positions).iter().entities(ctx); 77 | 78 | // `n` and `p` allow (potentially mutable) access to entity data inside 79 | // the resource without the ability to add or remove entities from the resource 80 | // - which would otherwise invalidate the iterator 81 | for e in entity_iter { 82 | println!("Entity {} is at {:?}", 83 | n.get(e).unwrap().name, 84 | p.get(e).unwrap()); 85 | } 86 | }); 87 | }); 88 | 89 | world.run(&mut update); 90 | ``` 91 | 92 | # Parallel System Execution 93 | 94 | Systems queued into a command buffer within a single call to `queue_systems` may be executed 95 | in parallel by the world. 96 | 97 | The order in which systems are queued is significant in one way: the scheduler guarantees that 98 | any changes to resources will always be observed in the same order in which systems were 99 | queued. 100 | 101 | For example, given two systems - `ReadPositions` and `WritePositions` - if *WritePositions* was 102 | queued before *ReadPositions*, then it is guarenteed that *ReadPositions* will see any changes 103 | made by *WritePositions*. Conversely, if the order were to be swapped, then *ReadPositions* 104 | is guaranteed to *not* observe the changes made by *WritePositions*. 105 | 106 | There is one exception to this. Entity deletions are committed when all concurrently executing 107 | systems have completed. This behavior is deterministic, but not always obvious. If you wish to 108 | ensure that entity deletions from one system are always seen by a later system, then queue 109 | the two systems in separate `queue_systems` calls. 110 | -------------------------------------------------------------------------------- /benches/nbody.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | use test::Bencher; 5 | 6 | extern crate constellation; 7 | extern crate rayon; 8 | 9 | use constellation::{SystemCommandBuffer, VecResource, World, SequentialExecute}; 10 | 11 | #[macro_use] 12 | extern crate serde_derive; 13 | extern crate serde; 14 | 15 | pub const TOTAL_ENTITIES: usize = 1000; 16 | pub const G: f32 = 9.81; 17 | 18 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 19 | pub struct RigidBody { 20 | pub mass: f32, 21 | pub x: f32, 22 | pub y: f32 23 | } 24 | 25 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 26 | pub struct DynamicBody { 27 | pub dx: f32, 28 | pub dy: f32, 29 | pub ddx: f32, 30 | pub ddy: f32, 31 | } 32 | 33 | type Bodies = VecResource; 34 | type Motion = VecResource; 35 | 36 | fn setup() -> World { 37 | let mut update = SystemCommandBuffer::default(); 38 | 39 | update.queue_systems(|scope| { 40 | scope.run_r0w2(|ctx, bodies: &mut Bodies, motion: &mut Motion| { 41 | for i in 0..TOTAL_ENTITIES { 42 | let e = ctx.create(); 43 | bodies.add(e, RigidBody { mass: 10.0, x: i as f32, y: i as f32 }); 44 | motion.add(e, DynamicBody { dx: -(i as f32), dy: -(i as f32), ddx: 0.0, ddy: 0.0 }); 45 | } 46 | }); 47 | }); 48 | 49 | let mut world = World::new(); 50 | world.register_resource(Bodies::new()); 51 | world.register_resource(Motion::new()); 52 | 53 | world.run(&mut update); 54 | 55 | world 56 | } 57 | 58 | #[bench] 59 | fn nbody_setup(b: &mut Bencher) { 60 | b.iter(|| setup()); 61 | } 62 | 63 | #[bench] 64 | fn nbody_sequential(b: &mut Bencher) { 65 | let mut world = setup(); 66 | let mut update = SystemCommandBuffer::default(); 67 | 68 | update.queue_systems(|scope| { 69 | // calculate acceleration 70 | scope.run_r1w1(|ctx, bodies: &Bodies, motion: &mut Motion| { 71 | ctx.iter_r1w1(bodies, motion).components(|_, pos, vel| { 72 | for (_, b) in bodies.iter() { 73 | let rx = b.x - pos.x; 74 | let ry = b.y - pos.y; 75 | let r = (rx * rx + ry * ry).sqrt(); 76 | 77 | if r != 0.0 { 78 | let f = (G * b.mass) / r; 79 | vel.ddx += (rx / r) * f; 80 | vel.ddy += (ry / r) * f; 81 | } 82 | } 83 | }); 84 | }); 85 | 86 | // integrate positions 87 | scope.run_r0w2(|ctx, bodies: &mut Bodies, motion: &mut Motion| { 88 | ctx.iter_r0w2(bodies, motion).components(|_, pos, vel| { 89 | vel.dx += vel.ddx; 90 | vel.dy += vel.ddy; 91 | pos.x += vel.dx; 92 | pos.y += vel.dy; 93 | }); 94 | }); 95 | }); 96 | 97 | b.iter(|| world.run_sequential(&mut update, SequentialExecute::ParallelBatchedCommit)); 98 | } 99 | 100 | #[bench] 101 | fn nbody(b: &mut Bencher) { 102 | let mut world = setup(); 103 | let mut update = SystemCommandBuffer::default(); 104 | 105 | update.queue_systems(|scope| { 106 | // calculate acceleration 107 | scope.run_r1w1(|ctx, bodies: &Bodies, motion: &mut Motion| { 108 | ctx.iter_r1w1(bodies, motion).components(|_, pos, vel| { 109 | for (_, b) in bodies.iter() { 110 | let rx = b.x - pos.x; 111 | let ry = b.y - pos.y; 112 | let r = (rx * rx + ry * ry).sqrt(); 113 | 114 | if r != 0.0 { 115 | let f = (G * b.mass) / r; 116 | vel.ddx += (rx / r) * f; 117 | vel.ddy += (ry / r) * f; 118 | } 119 | } 120 | }); 121 | }); 122 | 123 | // integrate positions 124 | scope.run_r0w2(|ctx, bodies: &mut Bodies, motion: &mut Motion| { 125 | ctx.iter_r0w2(bodies, motion).components(|_, pos, vel| { 126 | vel.dx += vel.ddx; 127 | vel.dy += vel.ddy; 128 | pos.x += vel.dx; 129 | pos.y += vel.dy; 130 | }); 131 | }); 132 | }); 133 | 134 | b.iter(|| world.run(&mut update)); 135 | } -------------------------------------------------------------------------------- /benches/position_update.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | use test::Bencher; 5 | 6 | extern crate constellation; 7 | extern crate rayon; 8 | 9 | use constellation::{SystemCommandBuffer, VecResource, World, SequentialExecute}; 10 | 11 | #[macro_use] 12 | extern crate serde_derive; 13 | extern crate serde; 14 | 15 | pub const TOTAL_ENTITIES: usize = 200000; 16 | pub const MOBILE_ENTITIES: usize = 100000; 17 | 18 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 19 | pub struct Position { 20 | pub x: f32, 21 | pub y: f32, 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 25 | pub struct MotionData { 26 | pub dx: f32, 27 | pub dy: f32, 28 | pub ddx: f32, 29 | pub ddy: f32 30 | } 31 | 32 | type Positions = VecResource; 33 | type Mobile = VecResource; 34 | 35 | fn setup() -> World { 36 | let mut update = SystemCommandBuffer::default(); 37 | 38 | update.queue_systems(|scope| { 39 | scope.run_r0w2(|ctx, pos: &mut Positions, vel: &mut Mobile| { 40 | for i in 0..TOTAL_ENTITIES { 41 | let e = ctx.create(); 42 | pos.add(e, Position { x: 0.0, y: 0.0 }); 43 | 44 | if i < MOBILE_ENTITIES { 45 | vel.add(e, MotionData { dx: 1.0, dy: 1.0, ddx: 0.5, ddy: 0.5 }); 46 | } 47 | } 48 | }); 49 | }); 50 | 51 | let mut world = World::new(); 52 | world.register_resource(Positions::new()); 53 | world.register_resource(Mobile::new()); 54 | 55 | world.run(&mut update); 56 | 57 | world 58 | } 59 | 60 | #[bench] 61 | fn position_update_setup(b: &mut Bencher) { 62 | b.iter(|| setup()); 63 | } 64 | 65 | #[bench] 66 | fn position_update_sequential(b: &mut Bencher) { 67 | let mut world = setup(); 68 | let mut update = SystemCommandBuffer::default(); 69 | 70 | update.queue_systems(|scope| { 71 | scope.run_r0w2(|ctx, pos: &mut Positions, vel: &mut Mobile| { 72 | ctx.iter_r0w2(pos, vel).components(|_, pos, vel| { 73 | vel.dx += vel.ddx; 74 | vel.dy += vel.ddy; 75 | pos.x += vel.dx; 76 | pos.y += vel.dy; 77 | }); 78 | }); 79 | }); 80 | 81 | b.iter(|| world.run_sequential(&mut update, SequentialExecute::ParallelBatchedCommit)); 82 | } 83 | 84 | #[bench] 85 | fn position_update(b: &mut Bencher) { 86 | let mut world = setup(); 87 | let mut update = SystemCommandBuffer::default(); 88 | 89 | update.queue_systems(|scope| { 90 | scope.run_r0w2(|ctx, pos: &mut Positions, vel: &mut Mobile| { 91 | ctx.iter_r0w2(pos, vel).components(|_, pos, vel| { 92 | vel.dx += vel.ddx; 93 | vel.dy += vel.ddy; 94 | pos.x += vel.dx; 95 | pos.y += vel.dy; 96 | }); 97 | }); 98 | }); 99 | 100 | b.iter(|| world.run(&mut update)); 101 | } -------------------------------------------------------------------------------- /benches/scheduling.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | use test::Bencher; 5 | 6 | extern crate constellation; 7 | extern crate rayon; 8 | 9 | use constellation::{SystemCommandBuffer, VecResource, MapResource, World, SequentialExecute}; 10 | 11 | #[macro_use] 12 | extern crate serde_derive; 13 | extern crate serde; 14 | 15 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 16 | pub struct A { 17 | pub a: f32 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 21 | pub struct B { 22 | pub b: f32 23 | } 24 | 25 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 26 | pub struct C { 27 | pub c: f32 28 | } 29 | 30 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 31 | pub struct D { 32 | pub d: f32 33 | } 34 | 35 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 36 | pub struct E { 37 | pub e: f32 38 | } 39 | 40 | type As = VecResource; 41 | type Bs = VecResource; 42 | type Cs = MapResource; 43 | type Ds = MapResource; 44 | type Es = MapResource; 45 | 46 | fn setup() -> World { 47 | let mut update = SystemCommandBuffer::default(); 48 | 49 | update.queue_systems(|scope| { 50 | scope.run_r0w5(|ctx, a: &mut As, b: &mut Bs, c: &mut Cs, d: &mut Ds, e: &mut Es| { 51 | for i in 0..1000usize { 52 | let entity = ctx.create(); 53 | a.add(entity, A { a: i as f32 }); 54 | } 55 | 56 | for i in 0..1000usize { 57 | let entity = ctx.create(); 58 | a.add(entity, A { a: i as f32 }); 59 | b.add(entity, B { b: i as f32 }); 60 | } 61 | 62 | for i in 0..1000usize { 63 | let entity = ctx.create(); 64 | a.add(entity, A { a: i as f32 }); 65 | c.add(entity, C { c: i as f32 }); 66 | } 67 | 68 | for i in 0..1000usize { 69 | let entity = ctx.create(); 70 | d.add(entity, D { d: i as f32 }); 71 | e.add(entity, E { e: i as f32 }); 72 | } 73 | }); 74 | }); 75 | 76 | let mut world = World::new(); 77 | world.register_resource(As::new()); 78 | world.register_resource(Bs::new()); 79 | world.register_resource(Cs::new()); 80 | world.register_resource(Ds::new()); 81 | world.register_resource(Es::new()); 82 | 83 | world.run(&mut update); 84 | 85 | world 86 | } 87 | 88 | fn setup_processing() -> SystemCommandBuffer<()> { 89 | let mut update = SystemCommandBuffer::default(); 90 | 91 | update.queue_systems(|scope| { 92 | // read a, write b 93 | scope.run_r1w1(|ctx, a: &As, b: &mut Bs| { 94 | ctx.iter_r1w1(a, b).components(|_, a_component, b_component| { 95 | let mut total = a_component.a; 96 | for (_, other) in a.iter() { 97 | total += other.a; 98 | } 99 | 100 | b_component.b = total; 101 | }); 102 | }); 103 | 104 | // read a, write c 105 | scope.run_r1w1(|ctx, a: &As, c: &mut Cs| { 106 | ctx.iter_r1w1(a, c).components(|_, a_component, c_component| { 107 | let mut total = a_component.a; 108 | for (_, other) in a.iter() { 109 | total += other.a; 110 | } 111 | 112 | c_component.c = total; 113 | }); 114 | }); 115 | 116 | // read d, write e 117 | scope.run_r1w1(|ctx, d: &Ds, e: &mut Es| { 118 | ctx.iter_r1w1(d, e).components(|_, d_component, e_component| { 119 | let mut total = d_component.d; 120 | for (_, other) in d.iter() { 121 | total += other.d; 122 | } 123 | 124 | e_component.e = total; 125 | }); 126 | }); 127 | 128 | // write a 129 | scope.run_r0w1(|ctx, a: &mut As| { 130 | ctx.iter_r0w1(a).components(|_, a_component| { 131 | a_component.a = 0.0; 132 | }); 133 | }); 134 | 135 | // read d, write e 136 | scope.run_r1w1(|ctx, d: &Ds, e: &mut Es| { 137 | ctx.iter_r1w1(d, e).components(|_, d_component, e_component| { 138 | let mut total = d_component.d; 139 | for (_, other) in d.iter() { 140 | total += other.d; 141 | } 142 | 143 | e_component.e = total; 144 | }); 145 | }); 146 | }); 147 | 148 | update 149 | } 150 | 151 | #[bench] 152 | fn scheduling_sequential(b: &mut Bencher) { 153 | let mut world = setup(); 154 | let mut update = setup_processing(); 155 | 156 | b.iter(|| world.run_sequential(&mut update, SequentialExecute::ParallelBatchedCommit)); 157 | } 158 | 159 | #[bench] 160 | fn scheduling(b: &mut Bencher) { 161 | let mut world = setup(); 162 | let mut update = setup_processing(); 163 | 164 | b.iter(|| world.run(&mut update)); 165 | } -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 999 2 | ideal_width = 80 -------------------------------------------------------------------------------- /src/entities.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | use std::fmt; 3 | use crossbeam::sync::SegQueue; 4 | 5 | /// Only one entity with a given index may be alive at a time. 6 | pub type Index = u32; 7 | 8 | /// Generation is incremented each time an index is re-used. 9 | pub type Generation = u8; 10 | 11 | /// A handle is formed out of an Index and a Generation 12 | pub trait Handle: Clone + Copy + fmt::Display { 13 | /// Constructs a new handle. 14 | fn new(index: TIndex, generation: TGeneration) -> Self; 15 | 16 | /// Gets the index component of the handle. 17 | fn index(&self) -> TIndex; 18 | 19 | /// Gets the generation component of the handle. 20 | fn generation(&self) -> TGeneration; 21 | } 22 | 23 | /// A handle onto an entity in a scene. 24 | #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] 25 | pub struct Entity(u32); 26 | 27 | const INDEX_BITS: u8 = 24; 28 | const INDEX_MASK: u32 = (1 << INDEX_BITS) - 1; 29 | const GENERATION_BITS: u8 = 8; 30 | const GENERATION_MASK: u32 = (1 << GENERATION_BITS) - 1; 31 | const MINIMUM_FREE_INDICES: usize = 1024; 32 | 33 | impl Handle for Entity { 34 | fn new(index: Index, generation: Generation) -> Entity { 35 | Entity((index & INDEX_MASK) | ((generation as u32 & GENERATION_MASK) << INDEX_BITS)) 36 | } 37 | 38 | fn index(&self) -> Index { 39 | self.0 & INDEX_MASK 40 | } 41 | 42 | fn generation(&self) -> Generation { 43 | let gen = (self.0 >> INDEX_BITS) & GENERATION_MASK; 44 | gen as u8 45 | } 46 | } 47 | 48 | impl fmt::Display for Entity { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | write!(f, "(index: {}, gen: {})", self.index(), self.generation()) 51 | } 52 | } 53 | 54 | /// Manages the allocation and delection of `Entity` IDs. 55 | pub struct Entities { 56 | free_count_approx: AtomicUsize, 57 | allocated: AtomicUsize, 58 | generations: Vec, 59 | free: SegQueue, 60 | deleted_pool: SegQueue>, 61 | } 62 | 63 | impl Entities { 64 | /// Constructs a new `Entities`. 65 | pub fn new() -> Entities { 66 | Entities { 67 | generations: Vec::new(), 68 | free: SegQueue::new(), 69 | free_count_approx: AtomicUsize::new(0), 70 | allocated: AtomicUsize::new(0), 71 | deleted_pool: SegQueue::new(), 72 | } 73 | } 74 | 75 | /// Creates a new `Entity`. Allocated entities cannot be deleted until after 76 | /// `commit_allocations` has been called. 77 | pub fn allocate(&self) -> Entity { 78 | if self.free_count_approx.load(Ordering::Relaxed) > MINIMUM_FREE_INDICES { 79 | if let Some(index) = self.free.try_pop() { 80 | self.free_count_approx.fetch_sub(1, Ordering::Relaxed); 81 | let generation = self.generations[index as usize]; 82 | return Entity::new(index, generation); 83 | } 84 | } 85 | 86 | let index = self.allocated.fetch_add(1, Ordering::SeqCst); 87 | return Entity::new(index as Index, 0 as Generation); 88 | } 89 | 90 | /// Determines if the specified `Entity` is still alive. 91 | pub fn is_alive(&self, entity: &Entity) -> bool { 92 | let index = entity.index() as usize; 93 | match self.generations.get(index) { 94 | Some(&g) => g == entity.generation(), 95 | None => self.allocated.load(Ordering::Relaxed) > index, 96 | } 97 | } 98 | 99 | /// Gets the count of currently allocated entities. 100 | pub fn count(&self) -> usize { 101 | self.allocated.load(Ordering::Relaxed) 102 | } 103 | 104 | /// Gets the currently living `Entity` with the given `Index`. 105 | pub fn by_index(&self, index: Index) -> Entity { 106 | match self.generations.get(index as usize) { 107 | Some(&g) => Entity::new(index, g), 108 | None => Entity::new(index, 0), 109 | } 110 | } 111 | 112 | /// Creates a new entity transaction. Transactions can be used to allocate 113 | /// or delete entities in multiple threads concurrently. Entity deletions 114 | /// are comitted with the transaction is merged via `merge`. 115 | pub fn transaction(&self) -> EntitiesTransaction { 116 | EntitiesTransaction { 117 | entities: self, 118 | deleted: self.deleted_pool.try_pop().unwrap_or(Vec::new()), 119 | } 120 | } 121 | 122 | /// Merges a set of entity transactions, comitting their allocations and 123 | /// delections. 124 | pub fn merge>(&mut self, changes: T) { 125 | self.commit_allocations(); 126 | 127 | let mut freed = 0; 128 | for set in changes { 129 | let mut deleted = set.deleted; 130 | for e in deleted.drain(..) { 131 | let index = e.index() as usize; 132 | self.generations[index] = self.generations[index] + 1; 133 | self.free.push(e.index()); 134 | freed = freed + 1; 135 | } 136 | 137 | self.deleted_pool.push(deleted); 138 | } 139 | 140 | self.free_count_approx.fetch_add(freed, Ordering::Relaxed); 141 | } 142 | 143 | fn commit_allocations(&mut self) { 144 | let allocated = self.allocated.load(Ordering::Acquire); 145 | let new_entities = allocated - self.generations.len(); 146 | if new_entities > 0 { 147 | self.generations.resize(allocated, 0); 148 | } 149 | } 150 | } 151 | 152 | /// An entity transaction allows concurrent creations and deletions of entities 153 | /// from an `Entities`. 154 | pub struct EntitiesTransaction<'a> { 155 | pub(crate) entities: &'a Entities, 156 | deleted: Vec, 157 | } 158 | 159 | /// Summarises the final changes made during the lifetime of an 160 | /// entity transaction. 161 | pub struct EntityChangeSet { 162 | /// The entities deleted in the transaction. 163 | pub deleted: Vec, 164 | } 165 | 166 | impl<'a> EntitiesTransaction<'a> { 167 | /// Creates a new `Entity`. 168 | /// 169 | /// This `Entity` can immediately be used to register data with resources, 170 | /// and the calling system may destroy the entity, but other systems running 171 | /// concurrently will not observe the entity's creation. 172 | pub fn create(&mut self) -> Entity { 173 | self.entities.allocate() 174 | } 175 | 176 | /// Destroys an `Entity`. 177 | /// 178 | /// Entity destructions are deferred until after the system has completed 179 | /// execution. All related data stored in entity resources will also be 180 | /// removed at this time. 181 | pub fn destroy(&mut self, entity: Entity) { 182 | self.deleted.push(entity); 183 | } 184 | 185 | /// Determines if the given `Entity` is still alive. 186 | pub fn is_alive(&self, entity: &Entity) -> bool { 187 | self.entities.is_alive(entity) 188 | } 189 | 190 | /// Gets the currently living `Entity` with the given `Index`. 191 | pub fn by_index(&self, index: Index) -> Entity { 192 | self.entities.by_index(index) 193 | } 194 | 195 | /// Converts this transaction into a change set, 196 | /// consuming the transaction in the process. 197 | pub fn to_change_set(self) -> EntityChangeSet { 198 | EntityChangeSet { deleted: self.deleted } 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | mod entities_tests { 204 | use super::*; 205 | use std::collections::HashSet; 206 | 207 | #[test] 208 | fn deconstruct_entity() { 209 | let entity = Entity::new(5, 10); 210 | assert!(entity.index() == 5); 211 | assert!(entity.generation() == 10); 212 | } 213 | 214 | #[test] 215 | fn new() { 216 | Entities::new(); 217 | } 218 | 219 | #[test] 220 | fn allocate() { 221 | let em = Entities::new(); 222 | em.allocate(); 223 | } 224 | 225 | #[test] 226 | fn allocated_entity_is_alive() { 227 | let em = Entities::new(); 228 | let entity = em.allocate(); 229 | assert!(em.is_alive(&entity)); 230 | } 231 | 232 | #[test] 233 | fn allocate_many_no_duplicates() { 234 | let em = Entities::new(); 235 | let mut entities: HashSet = HashSet::new(); 236 | 237 | for _ in 0..10000 { 238 | let e = em.allocate(); 239 | assert!(!entities.contains(&e)); 240 | 241 | entities.insert(e); 242 | } 243 | } 244 | 245 | #[test] 246 | fn allocate_many_no_duplicates_comitted() { 247 | let mut em = Entities::new(); 248 | let mut entities: HashSet = HashSet::new(); 249 | 250 | for _ in 0..10000 { 251 | let e = em.allocate(); 252 | assert!(!entities.contains(&e)); 253 | 254 | entities.insert(e); 255 | } 256 | 257 | em.commit_allocations(); 258 | 259 | for _ in 0..10000 { 260 | let e = em.allocate(); 261 | assert!(!entities.contains(&e)); 262 | 263 | entities.insert(e); 264 | } 265 | } 266 | } 267 | 268 | #[cfg(test)] 269 | mod entities_transaction_tests { 270 | use super::*; 271 | use std::collections::HashSet; 272 | 273 | #[test] 274 | fn new() { 275 | let em = Entities::new(); 276 | em.transaction(); 277 | } 278 | 279 | #[test] 280 | fn create() { 281 | let em = Entities::new(); 282 | let mut tx = em.transaction(); 283 | 284 | tx.create(); 285 | } 286 | 287 | #[test] 288 | fn created_is_alive() { 289 | let em = Entities::new(); 290 | let mut tx = em.transaction(); 291 | 292 | let entity = tx.create(); 293 | assert!(tx.is_alive(&entity)); 294 | } 295 | 296 | #[test] 297 | fn merge_allocates() { 298 | let mut em = Entities::new(); 299 | 300 | let entity: Entity; 301 | let cs: EntityChangeSet; 302 | 303 | { 304 | let mut tx = em.transaction(); 305 | entity = tx.create(); 306 | cs = tx.to_change_set(); 307 | } 308 | 309 | em.merge((vec![cs]).into_iter()); 310 | 311 | assert!(em.is_alive(&entity)); 312 | } 313 | 314 | #[test] 315 | fn merge_deletes() { 316 | let mut em = Entities::new(); 317 | let mut entities: HashSet = HashSet::new(); 318 | 319 | for _ in 0..10000 { 320 | let e = em.allocate(); 321 | assert!(!entities.contains(&e)); 322 | 323 | entities.insert(e); 324 | } 325 | 326 | em.commit_allocations(); 327 | 328 | let cs: EntityChangeSet; 329 | 330 | { 331 | let mut tx = em.transaction(); 332 | 333 | for entity in entities.iter() { 334 | tx.destroy(*entity); 335 | } 336 | 337 | cs = tx.to_change_set(); 338 | } 339 | 340 | for entity in entities.iter() { 341 | assert!(em.is_alive(&entity)); 342 | } 343 | 344 | em.merge((vec![cs]).into_iter()); 345 | 346 | for entity in entities { 347 | assert!(!em.is_alive(&entity)); 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::iter::Iterator; 3 | 4 | use hibitset::{BitSetLike, BitIter}; 5 | 6 | use entities::*; 7 | use resource::*; 8 | use join::*; 9 | use world::*; 10 | 11 | /// Stores data needed to iterate through entity data in a 12 | /// set of resources. 13 | pub struct ResourceIterBuilder 14 | { 15 | iter: I, 16 | read_resources: R, 17 | write_resources: W 18 | } 19 | 20 | /// A set of entity resources that can be iterated through. 21 | /// This is implemented for tuples of entity resource references. 22 | pub trait ResourceIter { 23 | /// The type of entity index iterator. 24 | type Iter; 25 | 26 | /// A tuple type containing immutable references to resources. 27 | type Read; 28 | 29 | /// A tuple type containing mutable references to resources. 30 | type Write; 31 | 32 | /// Begins construction of an entity resource iterator. 33 | fn iter(self) -> ResourceIterBuilder; 34 | } 35 | 36 | /// An iterator of entity IDs for entities 37 | /// whom have data stored in a given set of resources. 38 | pub struct EntityIter<'b, I> 39 | where I: 'b 40 | { 41 | index_iter: I, 42 | entities: &'b Entities, 43 | phantom: PhantomData<&'b I> 44 | } 45 | 46 | impl<'b, I> Iterator for EntityIter<'b, I> 47 | where I: Iterator + 'b 48 | { 49 | type Item = Entity; 50 | 51 | #[inline] 52 | fn next(&mut self) -> Option { 53 | self.index_iter.next().map(|i| self.entities.by_index(i)) 54 | } 55 | 56 | #[inline] 57 | fn size_hint(&self) -> (usize, Option) { 58 | self.index_iter.size_hint() 59 | } 60 | } 61 | 62 | /// An iterator of entity components for each 63 | /// entity with data stored in a given set of resources. 64 | pub struct ComponentIter<'b, I, R, W> 65 | where I: 'b 66 | { 67 | index_iter: I, 68 | read_resource_apis: R, 69 | write_resource_apis: W, 70 | phantom: PhantomData<&'b I> 71 | } 72 | 73 | macro_rules! impl_resource_iterators { 74 | ([$($read:ident),*] [$($write:ident),*]) => { 75 | impl <'a, $($read,)* $($write,)*> ResourceIter for ($(&'a $read,)* $(&'a mut $write,)*) 76 | where $($read: EntityResource + 'a,)* 77 | $($write: EntityResource + 'a,)* 78 | { 79 | type Iter = BitIter<<($(&'a $read::Filter,)* $(&'a $write::Filter,)*) as BitAnd>::Value>; 80 | type Read = ($(&'a $read::Api,)*); 81 | type Write = ($(&'a mut $write::Api,)*); 82 | 83 | #[allow(non_snake_case)] 84 | fn iter(self) -> ResourceIterBuilder 85 | { 86 | let ($($read,)* $($write,)*) = self; 87 | $(let $read = $read.pin();)* 88 | $(let $write = $write.pin_mut();)* 89 | 90 | let iter = ($($read.0,)* $($write.0,)*).and().iter(); 91 | 92 | return ResourceIterBuilder { 93 | iter: iter, 94 | read_resources: ($($read.1,)*), 95 | write_resources: ($($write.1,)*) 96 | }; 97 | } 98 | } 99 | 100 | impl <'a, I, $($read,)* $($write,)*> ResourceIterBuilder 101 | where I: Iterator + 'a, 102 | $($read: 'a,)* 103 | $($write: 'a,)* 104 | { 105 | /// Produces an iterator for iterating through all entity IDs for entities with data 106 | /// stored in all given resources. 107 | #[allow(non_snake_case)] 108 | pub fn entities(self, ctx: &'a SystemContext<'a, C>) -> (EntityIter<'a, I>, $(&'a $read,)* $(&'a mut $write,)*) 109 | { 110 | let iter = self.iter; 111 | let ($($read,)*) = self.read_resources; 112 | let ($($write,)*) = self.write_resources; 113 | 114 | return ( 115 | EntityIter { 116 | index_iter: iter, 117 | entities: ctx.entities, 118 | phantom: PhantomData 119 | }, 120 | $($read,)* 121 | $($write,)* 122 | ); 123 | } 124 | } 125 | 126 | impl <'a, I, $($read,)* $($write,)*> ResourceIterBuilder 127 | where I: Iterator + 'a, 128 | $($read: ComponentResourceApi + 'a,)* 129 | $($write: ComponentResourceApi + 'a,)* 130 | { 131 | /// Iterate through all entity components for entities with data 132 | /// stored in all given resources. 133 | #[allow(non_snake_case)] 134 | pub fn components(self) -> ComponentIter<'a, I, ($(*const $read,)*), ($(*mut $write,)*)> 135 | { 136 | let ($($read,)*) = self.read_resources; 137 | let ($($write,)*) = self.write_resources; 138 | 139 | return ComponentIter { 140 | index_iter: self.iter, 141 | read_resource_apis: ($($read as *const $read,)*), 142 | write_resource_apis: ($($write as *mut $write,)*), 143 | phantom: PhantomData 144 | }; 145 | } 146 | } 147 | 148 | impl<'b, I, $($read,)* $($write,)*> Iterator for ComponentIter<'b, I, ($(*const $read,)*), ($(*mut $write,)*)> 149 | where I: Iterator + 'b, 150 | $($read: ComponentResourceApi + 'b,)* 151 | $($write: ComponentResourceApi + 'b,)* 152 | { 153 | type Item = (Index, $(&'b $read::Component,)* $(&'b mut $write::Component,)*); 154 | 155 | #[inline] 156 | #[allow(non_snake_case)] 157 | fn next(&mut self) -> Option { 158 | self.index_iter.next().map(|i| { 159 | let ($($read,)*) = self.read_resource_apis; 160 | let ($($write,)*) = self.write_resource_apis; 161 | unsafe { (i, $((*$read).get_unchecked(i),)* $((*$write).get_unchecked_mut(i),)*) } 162 | }) 163 | } 164 | 165 | #[inline] 166 | fn size_hint(&self) -> (usize, Option) { 167 | self.index_iter.size_hint() 168 | } 169 | } 170 | } 171 | } 172 | 173 | impl_resource_iterators!([R0] []); 174 | impl_resource_iterators!([R0, R1] []); 175 | impl_resource_iterators!([R0, R1, R2] []); 176 | impl_resource_iterators!([R0, R1, R2, R3] []); 177 | impl_resource_iterators!([R0, R1, R2, R3, R4] []); 178 | impl_resource_iterators!([] [W0]); 179 | impl_resource_iterators!([R0] [W0]); 180 | impl_resource_iterators!([R0, R1] [W0]); 181 | impl_resource_iterators!([R0, R1, R2] [W0]); 182 | impl_resource_iterators!([R0, R1, R2, R3] [W0]); 183 | impl_resource_iterators!([R0, R1, R2, R3, R4] [W0]); 184 | impl_resource_iterators!([] [W0, W1]); 185 | impl_resource_iterators!([R0] [W0, W1]); 186 | impl_resource_iterators!([R0, R1] [W0, W1]); 187 | impl_resource_iterators!([R0, R1, R2] [W0, W1]); 188 | impl_resource_iterators!([R0, R1, R2, R3] [W0, W1]); 189 | impl_resource_iterators!([R0, R1, R2, R3, R4] [W0, W1]); 190 | impl_resource_iterators!([] [W0, W1, W2]); 191 | impl_resource_iterators!([R0] [W0, W1, W2]); 192 | impl_resource_iterators!([R0, R1] [W0, W1, W2]); 193 | impl_resource_iterators!([R0, R1, R2] [W0, W1, W2]); 194 | impl_resource_iterators!([R0, R1, R2, R3] [W0, W1, W2]); 195 | impl_resource_iterators!([R0, R1, R2, R3, R4] [W0, W1, W2]); 196 | impl_resource_iterators!([] [W0, W1, W2, W3]); 197 | impl_resource_iterators!([R0] [W0, W1, W2, W3]); 198 | impl_resource_iterators!([R0, R1] [W0, W1, W2, W3]); 199 | impl_resource_iterators!([R0, R1, R2] [W0, W1, W2, W3]); 200 | impl_resource_iterators!([R0, R1, R2, R3] [W0, W1, W2, W3]); 201 | impl_resource_iterators!([R0, R1, R2, R3, R4] [W0, W1, W2, W3]); 202 | impl_resource_iterators!([] [W0, W1, W2, W3, W4]); 203 | impl_resource_iterators!([R0] [W0, W1, W2, W3, W4]); 204 | impl_resource_iterators!([R0, R1] [W0, W1, W2, W3, W4]); 205 | impl_resource_iterators!([R0, R1, R2] [W0, W1, W2, W3, W4]); 206 | impl_resource_iterators!([R0, R1, R2, R3] [W0, W1, W2, W3, W4]); 207 | impl_resource_iterators!([R0, R1, R2, R3, R4] [W0, W1, W2, W3, W4]); 208 | 209 | #[cfg(test)] 210 | mod tests { 211 | use super::*; 212 | 213 | #[test] 214 | fn entities_tuple_entities() { 215 | let mut world = World::new(); 216 | world.register_resource(VecResource::::new()); 217 | world.register_resource(VecResource::::new()); 218 | 219 | let mut buffer = SystemCommandBuffer::default(); 220 | buffer.queue_systems(|scope| { 221 | scope.run_r1w1(|ctx, a: &VecResource, b: &mut VecResource| { 222 | { 223 | let (iter, r, w) = (a, &mut *b).iter().entities(ctx); 224 | for entity in iter { 225 | let x = r.get(entity).unwrap(); 226 | let y = w.get_mut(entity).unwrap(); 227 | println!("x={}, y={}", x, y); 228 | } 229 | } 230 | 231 | b.get(Entity::new(0, 0)); 232 | }); 233 | }); 234 | } 235 | 236 | #[test] 237 | fn entities_tuple_components() { 238 | let mut world = World::new(); 239 | world.register_resource(VecResource::::new()); 240 | world.register_resource(VecResource::::new()); 241 | 242 | let mut buffer = SystemCommandBuffer::default(); 243 | buffer.queue_systems(|scope| { 244 | scope.run_r1w1(|_, a: &VecResource, b: &mut VecResource| { 245 | for (i, x, y) in (a, &mut *b).iter().components() { 246 | println!("{} has x={}, y={}", i, x, y); 247 | } 248 | 249 | b.get(Entity::new(0, 0)); 250 | }); 251 | }); 252 | } 253 | 254 | #[test] 255 | fn entities_tuple_entities_macro() { 256 | let mut world = World::new(); 257 | world.register_resource(VecResource::::new()); 258 | world.register_resource(VecResource::::new()); 259 | world.register_resource(VecResource::::new()); 260 | world.register_resource(VecResource::::new()); 261 | 262 | let mut buffer = SystemCommandBuffer::default(); 263 | buffer.queue_systems(|scope| { 264 | scope.run_r2w2(|ctx, a: &VecResource, b: &VecResource, c: &mut VecResource, d: &mut VecResource| { 265 | { 266 | let (iter, r, _, w, _) = (a, b, &mut *c, &mut *d).iter().entities(ctx); 267 | for entity in iter { 268 | let x = r.get(entity).unwrap(); 269 | let y = w.get_mut(entity).unwrap(); 270 | println!("x={}, y={}", x, y); 271 | } 272 | } 273 | 274 | b.get(Entity::new(0, 0)); 275 | }); 276 | }); 277 | } 278 | 279 | #[test] 280 | fn entities_tuple_components_macro() { 281 | let mut world = World::new(); 282 | world.register_resource(VecResource::::new()); 283 | world.register_resource(VecResource::::new()); 284 | world.register_resource(VecResource::::new()); 285 | world.register_resource(VecResource::::new()); 286 | 287 | let mut buffer = SystemCommandBuffer::default(); 288 | buffer.queue_systems(|scope| { 289 | scope.run_r2w2(|_, a: &VecResource, b: &VecResource, c: &mut VecResource, d: &mut VecResource| { 290 | for (i, x, y, z, w) in (a, b, &mut *c, &mut *d).iter().components() { 291 | println!("{} has x={}, y={} z={} w={}", i, x, y, z, w); 292 | } 293 | 294 | b.get(Entity::new(0, 0)); 295 | }); 296 | }); 297 | } 298 | } -------------------------------------------------------------------------------- /src/join.rs: -------------------------------------------------------------------------------- 1 | // From the specs-rs project: 2 | // https://github.com/slide-rs/specs/ 3 | // Appache 2.0 License 4 | 5 | #![allow(missing_docs)] 6 | 7 | use tuple_utils::Split; 8 | use hibitset::{BitSetAnd, BitSetLike}; 9 | 10 | 11 | /// BitAnd is a helper method to & bitsets togather resulting in a tree 12 | pub trait BitAnd { 13 | type Value: BitSetLike; 14 | fn and(self) -> Self::Value; 15 | } 16 | 17 | /// This needs to be special cased 18 | impl BitAnd for (A,) 19 | where A: BitSetLike 20 | { 21 | type Value = A; 22 | fn and(self) -> Self::Value { 23 | self.0 24 | } 25 | } 26 | 27 | macro_rules! bitset_and { 28 | // use variables to indicate the arity of the tuple 29 | ($($from:ident),*) => { 30 | impl<$($from),*> BitAnd for ($($from),*) 31 | where $($from: BitSetLike),* 32 | { 33 | type Value = BitSetAnd< 34 | <::Left as BitAnd>::Value, 35 | <::Right as BitAnd>::Value 36 | >; 37 | fn and(self) -> Self::Value { 38 | let (l, r) = self.split(); 39 | BitSetAnd(l.and(), r.and()) 40 | } 41 | } 42 | } 43 | } 44 | 45 | bitset_and!{A, B} 46 | bitset_and!{A, B, C} 47 | bitset_and!{A, B, C, D} 48 | bitset_and!{A, B, C, D, E} 49 | bitset_and!{A, B, C, D, E, F} 50 | bitset_and!{A, B, C, D, E, F, G} 51 | bitset_and!{A, B, C, D, E, F, G, H} 52 | bitset_and!{A, B, C, D, E, F, G, H, I} 53 | bitset_and!{A, B, C, D, E, F, G, H, I, J} 54 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K} 55 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K, L} 56 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K, L, M} 57 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K, L, M, N} 58 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O} 59 | bitset_and!{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P} 60 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! # Constellation ECS 4 | //! 5 | //! A data-oriented entity component system optimized for cache coherent resource access 6 | //! and parallel system execution. 7 | //! 8 | //! Constellation does not have any native understanding of a "component". Instead, the library 9 | //! is concerned about ensuring safe concurrent access to shared resources, while providing direct 10 | //! access to each resource's own APIs. These resources may store per-entity data such as 11 | //! positions, or may represent application wide services such as asset loaders or input devices. 12 | //! 13 | //! Systems request read or write access to a set of resources, which can then be scheduled to 14 | //! be potentially executed in parallel by recording them into a `SystemCommandBuffer` and 15 | //! executing the command buffer within a `World`. 16 | //! 17 | //! # Examples 18 | //! 19 | //! Defining Resources: 20 | //! 21 | //! ``` 22 | //! # use self::constellation::*; 23 | //! // Per-entity position data. 24 | //! struct Position { 25 | //! x: f32, 26 | //! y: f32, 27 | //! z: f32 28 | //! } 29 | //! 30 | //! // Store position data into a vector resource 31 | //! type Positions = VecResource; 32 | //! 33 | //! // Per-entity debug names. 34 | //! struct DebugName { 35 | //! name: String 36 | //! } 37 | //! 38 | //! // Store debug names in a map resource 39 | //! type DebugNames = MapResource; 40 | //! 41 | //! let mut world = World::new(); 42 | //! world.register_resource(Positions::new()); 43 | //! world.register_resource(DebugNames::new()); 44 | //! ``` 45 | //! 46 | //! Update the world with Systems: 47 | //! 48 | //! ``` 49 | //! # use self::constellation::*; 50 | //! # #[derive(Debug)] 51 | //! # struct Position { 52 | //! # x: f32, 53 | //! # y: f32, 54 | //! # z: f32 55 | //! # } 56 | //! # type Positions = VecResource; 57 | //! # 58 | //! # struct Velocity { 59 | //! # x: f32, 60 | //! # y: f32, 61 | //! # z: f32 62 | //! # } 63 | //! # type Velocities = VecResource; 64 | //! # 65 | //! # struct DebugName { 66 | //! # name: String 67 | //! # } 68 | //! # type DebugNames = MapResource; 69 | //! # 70 | //! # let mut world = World::new(); 71 | //! # world.register_resource(Positions::new()); 72 | //! # world.register_resource(Velocities::new()); 73 | //! # world.register_resource(DebugNames::new()); 74 | //! let mut update = SystemCommandBuffer::default(); 75 | //! update.queue_systems(|scope| { 76 | //! scope.run_r1w1(|ctx, velocities: &Velocities, positions: &mut Positions| { 77 | //! println!("Updating positions"); 78 | //! 79 | //! // iterate through all components for entities with data in both 80 | //! // position and velocity resources 81 | //! for (_, v, p) in (velocities, positions).iter().components() { 82 | //! p.x += v.x; 83 | //! p.y += v.y; 84 | //! p.z += v.z; 85 | //! }; 86 | //! }); 87 | //! 88 | //! scope.run_r2w0(|ctx, names: &DebugNames, positions: &Positions| { 89 | //! println!("Printing positions"); 90 | //! 91 | //! // iterate through all entity IDs for entities with data in both 92 | //! // `names` and `positions` 93 | //! let (iter, n, p) = (names, positions).iter().entities(ctx); 94 | //! 95 | //! // `n` and `p` allow (potentially mutable) access to entity data inside 96 | //! // the resource without the ability to add or remove entities from the resource 97 | //! // - which would otherwise invalidate the iterator 98 | //! for entity in iter { 99 | //! println!("Entity {} is at {:?}", 100 | //! n.get(entity).unwrap().name, 101 | //! p.get(entity).unwrap()); 102 | //! } 103 | //! }); 104 | //! }); 105 | //! 106 | //! world.run(&mut update); 107 | //! ``` 108 | //! 109 | //! # Parallel System Execution 110 | //! 111 | //! Systems queued into a command buffer within a single call to `queue_systems` may be executed 112 | //! in parallel by the world. 113 | //! 114 | //! The order in which systems are queued is significant in one way: the scheduler guarantees that 115 | //! any changes to resources will always be observed in the same order in which systems were 116 | //! queued. 117 | //! 118 | //! For example, given two systems - `ReadPositions` and `WritePositions` - if *WritePositions* was 119 | //! queued before *ReadPositions*, then it is guarenteed that *ReadPositions* will see any changes 120 | //! made by *WritePositions*. Conversely, if the order were to be swapped, then *ReadPositions* 121 | //! is guaranteed to *not* observe the changes made by *WritePositions*. 122 | //! 123 | //! There is one exception to this. Entity deletions are committed when all concurrently executing 124 | //! systems have completed. This behavior is deterministic, but not always obvious. If you wish to 125 | //! ensure that entity deletions from one system are always seen by a later system, then queue 126 | //! the two systems in separate `queue_systems` calls. 127 | 128 | extern crate fnv; 129 | extern crate crossbeam; 130 | extern crate rayon; 131 | extern crate arrayvec; 132 | extern crate atom; 133 | extern crate tuple_utils; 134 | #[macro_use] 135 | extern crate bitflags; 136 | extern crate hibitset; 137 | 138 | mod entities; 139 | mod world; 140 | mod resource; 141 | mod iter; 142 | 143 | pub mod join; 144 | 145 | pub use entities::*; 146 | pub use world::*; 147 | pub use resource::*; 148 | pub use iter::*; 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | 154 | struct Position { 155 | x: f32, 156 | y: f32, 157 | z: f32, 158 | } 159 | 160 | type Positions = VecResource; 161 | 162 | struct Velocity { 163 | x: f32, 164 | y: f32, 165 | z: f32, 166 | } 167 | 168 | type Velocities = VecResource; 169 | 170 | #[test] 171 | fn example() { 172 | let mut world = World::new(); 173 | world.register_resource(Positions::new()); 174 | world.register_resource(Velocities::new()); 175 | 176 | let mut setup = SystemCommandBuffer::default(); 177 | setup.queue_systems(|scope| { 178 | scope.run_r0w2(|tx, velocities: &mut Velocities, positions: &mut Positions| { 179 | for i in 0..1000 { 180 | let e = tx.create(); 181 | let i = (i as f32) * 10f32; 182 | velocities.add(e, Velocity { x: i, y: i, z: i }); 183 | positions.add(e, Position { x: i, y: i, z: i }); 184 | } 185 | }); 186 | }); 187 | 188 | world.run(&mut setup); 189 | 190 | let mut update = SystemCommandBuffer::default(); 191 | update.queue_systems(|scope| { 192 | scope.run_r1w1(|_, velocities: &Velocities, positions: &mut Positions| { 193 | println!("Updating positions"); 194 | 195 | for (_, v, p) in (velocities, positions).iter().components() { 196 | p.x += v.x; 197 | p.y += v.y; 198 | p.z += v.z; 199 | }; 200 | }); 201 | }); 202 | 203 | world.run(&mut update); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use std::slice::{Iter, IterMut}; 3 | use std::any::Any; 4 | 5 | use fnv::FnvHashMap; 6 | use hibitset::{BitSet, BitSetLike}; 7 | 8 | use entities::*; 9 | use world::*; 10 | 11 | /// A resource whos system access is controlled by the `World`. 12 | pub trait Resource: Any + Send + Sync { 13 | /// Clears all data related to the given entities from the resource. 14 | fn clear_entity_data(&mut self, &[Entity]) {} 15 | 16 | /// Converts this resource into a `ResourceBuilder` for constructing itself. 17 | fn to_builder(self) -> ResourceBuilder 18 | where Self: Sized 19 | { 20 | ResourceBuilder::new(self) 21 | } 22 | } 23 | 24 | impl Resource { 25 | /// Returns a reference to the boxed value, blindly assuming it to be of type `T`. 26 | /// If you are not *absolutely certain* of `T`, you *must not* call this. 27 | #[inline] 28 | pub unsafe fn downcast_ref_unsafe(&self) -> &T { 29 | &*(self as *const Self as *const T) 30 | } 31 | 32 | /// Returns a reference to the boxed value, blindly assuming it to be of type `T`. 33 | /// If you are not *absolutely certain* of `T`, you *must not* call this. 34 | #[inline] 35 | pub unsafe fn downcast_mut_unsafe(&mut self) -> &mut T { 36 | &mut *(self as *mut Self as *mut T) 37 | } 38 | } 39 | 40 | /// An entity resource is a resource which stores data about entities. 41 | pub trait EntityResource: Resource { 42 | /// The type of filter used to mark which entities have associated data 43 | /// stored within the resource. 44 | type Filter: BitSetLike; 45 | 46 | /// The type of API used to access the resource while its filter 47 | /// is write-locked behind a borrow. 48 | type Api; 49 | 50 | // fn deconstruct(&self) -> (&Self::Filter, &Self::Api); 51 | // fn deconstruct_mut(&mut self) -> (&Self::Filter, &mut Self::Api); 52 | 53 | /// Splits the entity resource into a bitset used for entity iteration, 54 | /// and its restricted API. 55 | fn pin(&self) -> (&Self::Filter, &Self::Api); 56 | 57 | /// Splits the entity resource into a bitset used for entity iteration, 58 | /// and its restricted API. 59 | fn pin_mut(&mut self) -> (&Self::Filter, &mut Self::Api); 60 | } 61 | 62 | /// A component resource is a resource which stores data related to entities 63 | /// in the form of a single `Self::Component` per entity. 64 | pub trait ComponentResourceApi { 65 | /// The type of data stored for each entity. 66 | type Component; 67 | 68 | /// Gets a shared reference to the component associated with the given 69 | /// entity, if present. 70 | fn get(&self, Entity) -> Option<&Self::Component>; 71 | 72 | /// Gets a mutable reference to the component associated with the given 73 | /// entity, if present. 74 | fn get_mut(&mut self, Entity) -> Option<&mut Self::Component>; 75 | 76 | /// Gets a shared reference to the component associated with the given 77 | /// entity without performing any bounds or liveness checking. 78 | /// 79 | /// # Safety 80 | /// 81 | /// This function performs no bounds checking. Requesting data for an entity 82 | /// that does not represent a living entity with data in this resource 83 | /// will return an undefined result. 84 | unsafe fn get_unchecked(&self, entity_index: Index) -> &Self::Component; 85 | 86 | /// Gets a mutable reference to the component associated with the given 87 | /// entity without performing any bounds or liveness checking. 88 | /// 89 | /// # Safety 90 | /// 91 | /// This function performs no bounds checking. Requesting data for an entity 92 | /// that does not represent a living entity with data in this resource 93 | /// will return an undefined result. 94 | unsafe fn get_unchecked_mut(&mut self, entity_index: Index) -> &mut Self::Component; 95 | } 96 | 97 | /// A `MapResource` stores per-entity data in a `HashMap`. 98 | /// 99 | /// This entity resource is suitable for data which is only present for a small 100 | /// portion of the total entities in the `World`. 101 | pub struct MapResource { 102 | filter: BitSet, 103 | storage: MapStorage, 104 | } 105 | 106 | impl MapResource { 107 | /// Constructs a new `MapResource`. 108 | pub fn new() -> MapResource { 109 | MapResource { 110 | filter: BitSet::new(), 111 | storage: MapStorage::new(), 112 | } 113 | } 114 | 115 | /// Adds entity data to the resource. 116 | pub fn add(&mut self, entity: Entity, component: T) { 117 | let index = entity.index(); 118 | let generation = entity.generation(); 119 | self.storage.m.insert(index, (component, generation)); 120 | self.filter.add(index); 121 | } 122 | 123 | /// Removes entity data from the resource. 124 | pub fn remove(&mut self, entity: Entity) -> Option { 125 | let index = entity.index(); 126 | match self.storage.m.remove(&index) { 127 | Some((value, generation)) => { 128 | if generation == entity.generation() { 129 | self.filter.remove(index); 130 | Some(value) 131 | } 132 | else { 133 | self.storage.m.insert(index, (value, generation)); 134 | None 135 | } 136 | }, 137 | None => None 138 | } 139 | } 140 | } 141 | 142 | impl Resource for MapResource { 143 | fn clear_entity_data(&mut self, entities: &[Entity]) { 144 | for entity in entities { 145 | self.remove(*entity); 146 | } 147 | } 148 | 149 | fn to_builder(self) -> ResourceBuilder> { 150 | ResourceBuilder::new(self).activate_entity_disposal() 151 | } 152 | } 153 | 154 | impl EntityResource for MapResource { 155 | type Filter = BitSet; 156 | type Api = MapStorage; 157 | 158 | fn pin(&self) -> (&Self::Filter, &Self::Api) { 159 | (&self.filter, &self.storage) 160 | } 161 | 162 | fn pin_mut(&mut self) -> (&Self::Filter, &mut Self::Api) { 163 | (&self.filter, &mut self.storage) 164 | } 165 | } 166 | 167 | impl Deref for MapResource { 168 | type Target = MapStorage; 169 | 170 | fn deref(&self) -> &MapStorage { 171 | &self.storage 172 | } 173 | } 174 | 175 | impl DerefMut for MapResource { 176 | fn deref_mut(&mut self) -> &mut MapStorage { 177 | &mut self.storage 178 | } 179 | } 180 | 181 | /// Provides methods to retrieve and mutate entity data 182 | /// stored inside a `MapResource`. 183 | pub struct MapStorage { 184 | m: FnvHashMap 185 | } 186 | 187 | impl MapStorage { 188 | fn new() -> MapStorage { 189 | MapStorage { 190 | m: FnvHashMap::default() 191 | } 192 | } 193 | 194 | /// Gets an iterator over all entity data stored in the resource. 195 | pub fn iter_components<'a>(&'a self) -> Box + 'a> { 196 | Box::new(self.m.values().map(|&(ref v, _)| v)) 197 | } 198 | 199 | /// Gets an iterator over all entity data stored in the resource. 200 | pub fn iter_components_mut<'a>(&'a mut self) -> Box + 'a> { 201 | Box::new(self.m.values_mut().map(|&mut (ref mut v, _)| v)) 202 | } 203 | 204 | /// Gets an iterator over all entity data stored in the resource. 205 | pub fn iter<'a>(&'a self) -> Box + 'a> { 206 | Box::new(self.m.iter().map(|(i, &(ref v, g))| (Entity::new(*i, g), v))) 207 | } 208 | 209 | /// Gets an iterator over all entity data stored in the resource. 210 | pub fn iter_mut<'a>(&'a mut self) -> Box + 'a> { 211 | Box::new(self.m.iter_mut().map(|(i, &mut (ref mut v, g))| (Entity::new(*i, g), v))) 212 | } 213 | } 214 | 215 | impl ComponentResourceApi for MapStorage { 216 | type Component = T; 217 | 218 | fn get(&self, entity: Entity) -> Option<&T> { 219 | let generation = entity.generation(); 220 | match self.m.get(&entity.index()) { 221 | Some(&(ref value, g)) if g == generation => Some(value), 222 | _ => None 223 | } 224 | } 225 | 226 | #[inline] 227 | unsafe fn get_unchecked(&self, entity_index: Index) -> &T { 228 | let &(ref value, _) = self.m.get(&entity_index).unwrap(); 229 | value 230 | } 231 | 232 | fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { 233 | let generation = entity.generation(); 234 | match self.m.get_mut(&entity.index()) { 235 | Some(&mut (ref mut value, g)) if g == generation => Some(value), 236 | _ => None 237 | } 238 | } 239 | 240 | #[inline] 241 | unsafe fn get_unchecked_mut(&mut self, entity_index: Index) -> &mut T { 242 | let &mut (ref mut value, _) = self.m.get_mut(&entity_index).unwrap(); 243 | value 244 | } 245 | } 246 | 247 | /// A `VecResource` stores per-entity data in a `Vec`. 248 | /// 249 | /// This entity resource is suitable for data which is present for almost all of 250 | /// the total entities in the `World`. The `VecResource` provides fast 251 | /// sequential access as long as it maintains high occupancy. 252 | pub struct VecResource { 253 | filter: BitSet, 254 | storage: VecStorage, 255 | } 256 | 257 | impl Resource for VecResource { 258 | fn clear_entity_data(&mut self, entities: &[Entity]) { 259 | for entity in entities { 260 | self.remove(*entity); 261 | } 262 | } 263 | 264 | fn to_builder(self) -> ResourceBuilder> { 265 | ResourceBuilder::new(self).activate_entity_disposal() 266 | } 267 | } 268 | 269 | impl VecResource { 270 | /// Constructs a new `VecResource`. 271 | pub fn new() -> VecResource { 272 | VecResource { 273 | filter: BitSet::new(), 274 | storage: VecStorage::new(), 275 | } 276 | } 277 | 278 | /// Adds entity data to the resource. 279 | pub fn add(&mut self, entity: Entity, component: T) { 280 | use std::ptr; 281 | 282 | let index = entity.index() as usize; 283 | 284 | // expand storage if needed 285 | if self.storage.v.len() <= index { 286 | let additional = index - self.storage.v.len() + 1; 287 | 288 | // we leave the extra memory uninitialized 289 | self.storage.v.reserve(additional); 290 | unsafe { 291 | self.storage.v.set_len(index + 1); 292 | } 293 | 294 | self.storage.g.reserve(additional); 295 | for _ in 0..additional { 296 | self.storage.g.push(None); 297 | } 298 | } 299 | 300 | if self.storage.g[index] != None { 301 | panic!("VecResource already contains entity data for index {}", 302 | index); 303 | } 304 | 305 | // copy the component into the array without reading/dropping 306 | // the existing (uninitialized) value 307 | unsafe { 308 | ptr::write(self.storage.v.get_unchecked_mut(index), component); 309 | } 310 | self.storage.g[index] = Some(entity.generation()); 311 | self.filter.add(entity.index()); 312 | } 313 | 314 | /// Removes entity data from the resource. 315 | pub fn remove(&mut self, entity: Entity) -> Option { 316 | use std::ptr; 317 | 318 | let index = entity.index() as usize; 319 | if self.storage.v.len() <= index || self.storage.g[index] != Some(entity.generation()) { 320 | return None; 321 | } 322 | 323 | self.filter.remove(entity.index()); 324 | self.storage.g[index] = None; 325 | 326 | // copy the component out of thr array 327 | // - we will now treat this slot as unitialized 328 | Some(unsafe { ptr::read(&self.storage.v[index]) }) 329 | } 330 | } 331 | 332 | impl EntityResource for VecResource { 333 | type Filter = BitSet; 334 | type Api = VecStorage; 335 | 336 | fn pin(&self) -> (&Self::Filter, &Self::Api) { 337 | (&self.filter, &self.storage) 338 | } 339 | 340 | fn pin_mut(&mut self) -> (&Self::Filter, &mut Self::Api) { 341 | (&self.filter, &mut self.storage) 342 | } 343 | } 344 | 345 | impl Deref for VecResource { 346 | type Target = VecStorage; 347 | 348 | fn deref(&self) -> &VecStorage { 349 | &self.storage 350 | } 351 | } 352 | 353 | impl DerefMut for VecResource { 354 | fn deref_mut(&mut self) -> &mut VecStorage { 355 | &mut self.storage 356 | } 357 | } 358 | 359 | /// Provides methods to retrieve and mutate entity data stored inside a `VecResource`. 360 | pub struct VecStorage { 361 | v: Vec, 362 | g: Vec>, 363 | } 364 | 365 | impl VecStorage { 366 | fn new() -> VecStorage { 367 | VecStorage { 368 | v: Vec::new(), 369 | g: Vec::new(), 370 | } 371 | } 372 | 373 | /// Gets an iterator over immutable references to all entity data stored in the resource. 374 | pub fn iter(&self) -> VecStorageIter { 375 | VecStorageIter { 376 | i: 0, 377 | iter: self.v.iter(), 378 | g: &self.g, 379 | } 380 | } 381 | 382 | /// Gets an iterator over mutable references to all entity data stored in the resource. 383 | pub fn iter_mut(&mut self) -> VecStorageIterMut { 384 | VecStorageIterMut { 385 | i: 0, 386 | iter: self.v.iter_mut(), 387 | g: &self.g, 388 | } 389 | } 390 | } 391 | 392 | impl ComponentResourceApi for VecStorage { 393 | type Component = T; 394 | 395 | fn get(&self, entity: Entity) -> Option<&T> { 396 | let index = entity.index() as usize; 397 | if self.g.len() > index && self.g[index] == Some(entity.generation()) { 398 | return Some(&self.v[index]); 399 | } 400 | None 401 | } 402 | 403 | #[inline] 404 | unsafe fn get_unchecked(&self, entity_index: Index) -> &T { 405 | self.v.get_unchecked(entity_index as usize) 406 | } 407 | 408 | fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { 409 | let index = entity.index() as usize; 410 | if self.g.len() > index && self.g[index] == Some(entity.generation()) { 411 | return Some(&mut self.v[index]); 412 | } 413 | None 414 | } 415 | 416 | #[inline] 417 | unsafe fn get_unchecked_mut(&mut self, entity_index: Index) -> &mut T { 418 | self.v.get_unchecked_mut(entity_index as usize) 419 | } 420 | } 421 | 422 | /// An iterator over entity data stored in a `VecResource`. 423 | pub struct VecStorageIter<'a, T: 'a> { 424 | i: usize, 425 | iter: Iter<'a, T>, 426 | g: &'a [Option], 427 | } 428 | 429 | impl<'a, T> Iterator for VecStorageIter<'a, T> { 430 | type Item = (Entity, &'a T); 431 | 432 | #[inline] 433 | fn next(&mut self) -> Option<(Entity, &'a T)> { 434 | for x in self.iter.by_ref() { 435 | let index = self.i; 436 | self.i = self.i + 1; 437 | 438 | if let Some(gen) = self.g[index] { 439 | let entity = Entity::new(index as Index, gen); 440 | return Some((entity, x)); 441 | } 442 | } 443 | None 444 | } 445 | 446 | #[inline] 447 | fn size_hint(&self) -> (usize, Option) { 448 | (0, Some(self.g.len())) 449 | } 450 | } 451 | 452 | /// An iterator over entity data stored in a `VecResource`. 453 | pub struct VecStorageIterMut<'a, T: 'a> { 454 | i: usize, 455 | iter: IterMut<'a, T>, 456 | g: &'a [Option], 457 | } 458 | 459 | impl<'a, T> Iterator for VecStorageIterMut<'a, T> { 460 | type Item = (Entity, &'a mut T); 461 | 462 | #[inline] 463 | fn next(&mut self) -> Option<(Entity, &'a mut T)> { 464 | for x in self.iter.by_ref() { 465 | let index = self.i; 466 | self.i = self.i + 1; 467 | 468 | if let Some(gen) = self.g[index] { 469 | let entity = Entity::new(index as Index, gen); 470 | return Some((entity, x)); 471 | } 472 | } 473 | None 474 | } 475 | 476 | #[inline] 477 | fn size_hint(&self) -> (usize, Option) { 478 | (0, Some(self.g.len())) 479 | } 480 | } 481 | 482 | 483 | #[cfg(test)] 484 | mod tests { 485 | use super::*; 486 | use iter::*; 487 | 488 | #[test] 489 | fn map_deconstruct() { 490 | let map = &mut MapResource::::new(); 491 | let entity = Entity::new(1, 0); 492 | 493 | map.add(entity, 5u32); 494 | assert_eq!(*map.get(entity).unwrap(), 5u32); 495 | 496 | let (bitset, api) = as EntityResource>::pin(&map); 497 | assert!(bitset.contains(entity.index())); 498 | assert_eq!(*api.get(entity).unwrap(), 5u32); 499 | } 500 | 501 | #[test] 502 | fn map_deconstruct_mut() { 503 | let map = &mut MapResource::::new(); 504 | let entity = Entity::new(1, 0); 505 | 506 | map.add(entity, 5u32); 507 | assert_eq!(*map.get(entity).unwrap(), 5u32); 508 | 509 | let (bitset, api) = as EntityResource>::pin_mut(map); 510 | assert!(bitset.contains(entity.index())); 511 | assert_eq!(*api.get(entity).unwrap(), 5u32); 512 | *api.get_mut(entity).unwrap() = 6u32; 513 | assert_eq!(*api.get(entity).unwrap(), 6u32); 514 | } 515 | 516 | #[test] 517 | fn vec_iter() { 518 | let a = Entity::new(1, 0); 519 | let b = Entity::new(2, 0); 520 | let c = Entity::new(4, 0); 521 | 522 | let vec = &mut VecResource::::new(); 523 | vec.add(a, 1); 524 | vec.add(b, 2); 525 | vec.add(c, 3); 526 | 527 | let mut iter = vec.iter(); 528 | assert_eq!(iter.next(), Some((a, &1u32))); 529 | assert_eq!(iter.next(), Some((b, &2u32))); 530 | assert_eq!(iter.next(), Some((c, &3u32))); 531 | } 532 | 533 | #[test] 534 | fn vec_iter_mut() { 535 | let a = Entity::new(1, 0); 536 | let b = Entity::new(2, 0); 537 | let c = Entity::new(4, 0); 538 | 539 | let vec = &mut VecResource::::new(); 540 | vec.add(a, 1); 541 | vec.add(b, 2); 542 | vec.add(c, 3); 543 | 544 | let mut iter = vec.iter_mut(); 545 | assert_eq!(iter.next(), Some((a, &mut 1u32))); 546 | assert_eq!(iter.next(), Some((b, &mut 2u32))); 547 | assert_eq!(iter.next(), Some((c, &mut 3u32))); 548 | } 549 | 550 | #[test] 551 | fn run_iter_entities_r1w1() { 552 | let mut world = World::new(); 553 | world.register_resource(VecResource::::new()); 554 | world.register_resource(MapResource::::new()); 555 | world.register_resource(VecResource::::new()); 556 | 557 | let mut test = SystemCommandBuffer::default(); 558 | test.queue_systems(|scope| { 559 | scope.run_r0w3(|ctx, v: &mut VecResource, m: &mut MapResource, r: &mut VecResource| { 560 | let a = ctx.create(); 561 | let b = ctx.create(); 562 | let c = ctx.create(); 563 | 564 | v.add(a, 1); 565 | v.add(b, 2); 566 | v.add(c, 3); 567 | 568 | m.add(a, 1); 569 | m.add(c, 4); 570 | 571 | r.add(a, 0); 572 | r.add(b, 0); 573 | r.add(c, 0); 574 | }); 575 | 576 | scope.run_r2w1(|ctx, map: &MapResource, vec: &VecResource, out: &mut VecResource| { 577 | let (iter, m, v, o) = (map, vec, out).iter().entities(ctx); 578 | for e in iter { 579 | let x = unsafe { o.get_unchecked_mut(e.index()) }; 580 | *x = (*v.get(e).unwrap() + *m.get(e).unwrap()) as u64; 581 | } 582 | }); 583 | 584 | scope.run_r3w0(|ctx, map: &MapResource, vec: &VecResource, out: &VecResource| { 585 | let mut checked = 0; 586 | 587 | let (iter, m, v, o) = (map, vec, out).iter().entities(ctx); 588 | for e in iter { 589 | assert_eq!(*o.get(e).unwrap(), (m.get(e).unwrap() + v.get(e).unwrap()) as u64); 590 | checked = checked + 1; 591 | } 592 | 593 | assert_eq!(checked, 2); 594 | }); 595 | }); 596 | 597 | world.run_sequential(&mut test, SequentialExecute::SequentialCommit); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /src/world.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::any::TypeId; 4 | use std::cell::UnsafeCell; 5 | use std::mem; 6 | use std::marker::PhantomData; 7 | 8 | use rayon::prelude::*; 9 | use fnv::FnvHashMap; 10 | use arrayvec::ArrayVec; 11 | 12 | use entities::*; 13 | use resource::*; 14 | 15 | bitflags! { 16 | flags ResourceDescription: u32 { 17 | const ENTITY_DATA = 0b00000001 18 | } 19 | } 20 | 21 | struct ResourceCell { 22 | cell: UnsafeCell>, 23 | type_id: TypeId, 24 | desc: ResourceDescription, 25 | } 26 | 27 | impl ResourceCell { 28 | pub unsafe fn get_mut(&self) -> &mut Resource { 29 | (&mut *self.cell.get()).deref_mut() 30 | } 31 | 32 | pub unsafe fn get_as(&self) -> &T { 33 | assert!(self.type_id == TypeId::of::()); 34 | let borrow = &*self.cell.get(); 35 | borrow.downcast_ref_unsafe::() 36 | } 37 | 38 | pub unsafe fn get_as_mut(&self) -> &mut T { 39 | assert!(self.type_id == TypeId::of::()); 40 | let borrow = &mut *self.cell.get(); 41 | borrow.downcast_mut_unsafe::() 42 | } 43 | } 44 | 45 | /// Constructs and configures a world resource. 46 | pub struct ResourceBuilder { 47 | resource: T, 48 | desc: ResourceDescription, 49 | } 50 | 51 | impl ResourceBuilder { 52 | /// Constructs a new `ResourceBuilder`. 53 | pub fn new(resource: T) -> ResourceBuilder { 54 | ResourceBuilder { 55 | resource: resource, 56 | desc: ResourceDescription::empty(), 57 | } 58 | } 59 | 60 | fn to_resource_cell(self) -> ResourceCell { 61 | ResourceCell { 62 | cell: UnsafeCell::new(Box::new(self.resource)), 63 | type_id: TypeId::of::(), 64 | desc: self.desc, 65 | } 66 | } 67 | } 68 | 69 | impl ResourceBuilder { 70 | /// Marks this resource as containing entity data which should be cleared when entities 71 | /// are destroyed. 72 | pub fn activate_entity_disposal(mut self) -> ResourceBuilder { 73 | self.desc.insert(ENTITY_DATA); 74 | self 75 | } 76 | } 77 | 78 | /// Stores entities are resources, and provides mechanisms to update the world state via systems. 79 | pub struct World { 80 | entities: Entities, 81 | resources: FnvHashMap, 82 | entity_resources: Vec, 83 | changes_buffer: Option>, 84 | parallism_threshold: usize, 85 | } 86 | 87 | // resource access is ensured safe by the system scheduler 88 | unsafe impl Sync for World {} 89 | 90 | impl World { 91 | /// Constructs a new `World`. 92 | pub fn new() -> World { 93 | World { 94 | entities: Entities::new(), 95 | resources: FnvHashMap::default(), 96 | entity_resources: Vec::new(), 97 | changes_buffer: Some(Vec::new()), 98 | parallism_threshold: 2, 99 | } 100 | } 101 | 102 | /// Registers a new resource with the `World`, allowing systems to access the resource. 103 | pub fn register(&mut self, builder: ResourceBuilder) -> u8 { 104 | let cell = builder.to_resource_cell(); 105 | let type_id = cell.type_id; 106 | 107 | if cell.desc.contains(ENTITY_DATA) { 108 | self.entity_resources.push(type_id); 109 | } 110 | 111 | let id = self.resources.len() as u8; 112 | let value = (id, cell); 113 | self.resources.insert(type_id, value); 114 | 115 | id 116 | } 117 | 118 | /// Registers a new resource with the `World`, allowing systems to access the resource. 119 | pub fn register_resource(&mut self, resource: T) -> u8 { 120 | self.register(resource.to_builder()) 121 | } 122 | 123 | // Can only be safely called from within a system, and only for the resources the system declares 124 | unsafe fn get_resource(&self) -> Option<(u8, &T)> { 125 | let type_id = TypeId::of::(); 126 | if let Some(&(id, ref resource)) = self.resources.get(&type_id) { 127 | let resource = resource.get_as(); 128 | return Some((id, resource)); 129 | } 130 | return None; 131 | } 132 | 133 | // Can only be safely called from within a system, and only for the resources the system declares 134 | unsafe fn get_resource_mut(&self) -> Option<(u8, &mut T)> { 135 | let type_id = TypeId::of::(); 136 | if let Some(&(id, ref resource)) = self.resources.get(&type_id) { 137 | let resource = resource.get_as_mut(); 138 | return Some((id, resource)); 139 | } 140 | return None; 141 | } 142 | 143 | // Can only be safely called in between systems, on the main thread 144 | unsafe fn clean_deleted_entities(&mut self, changes: &[EntityChangeSet]) { 145 | for id in self.entity_resources.iter() { 146 | let &mut (_, ref mut resource) = self.resources.get_mut(&id).unwrap(); 147 | let resource = resource.get_mut(); 148 | for cs in changes.iter() { 149 | resource.clear_entity_data(&cs.deleted); 150 | } 151 | } 152 | } 153 | } 154 | 155 | trait System: Send { 156 | fn id(&self) -> u32; 157 | fn resource_access(&self) -> (&HashSet, &HashSet); 158 | fn execute(&mut self, &World, &S) -> EntityChangeSet; 159 | } 160 | 161 | struct FnSystem 162 | where F: FnMut(&World, &S) -> EntityChangeSet + Send, 163 | S: Send + Sync + 'static 164 | { 165 | id: u32, 166 | read: HashSet, 167 | write: HashSet, 168 | f: F, 169 | phantom: PhantomData<&'static S>, 170 | } 171 | 172 | impl EntityChangeSet + Send> System for FnSystem { 173 | #[inline] 174 | fn id(&self) -> u32 { 175 | self.id 176 | } 177 | 178 | #[inline] 179 | fn resource_access(&self) -> (&HashSet, &HashSet) { 180 | (&self.read, &self.write) 181 | } 182 | 183 | #[inline] 184 | fn execute(&mut self, world: &World, state: &S) -> EntityChangeSet { 185 | (self.f)(world, state) 186 | } 187 | } 188 | 189 | /// Contains system execution state information. 190 | pub struct SystemContext<'a, S: Send + Sync + 'static> { 191 | transaction: EntitiesTransaction<'a>, 192 | state: &'a S, 193 | } 194 | 195 | impl<'a, S: Send + Sync + 'static> Deref for SystemContext<'a, S> { 196 | type Target = EntitiesTransaction<'a>; 197 | 198 | fn deref(&self) -> &EntitiesTransaction<'a> { 199 | &self.transaction 200 | } 201 | } 202 | 203 | impl<'a, S: Send + Sync + 'static> DerefMut for SystemContext<'a, S> { 204 | fn deref_mut(&mut self) -> &mut EntitiesTransaction<'a> { 205 | &mut self.transaction 206 | } 207 | } 208 | 209 | impl<'a, S: Send + Sync + 'static> SystemContext<'a, S> { 210 | /// Gets the current system execution state. 211 | pub fn state(&self) -> &S { 212 | self.state 213 | } 214 | 215 | fn to_change_set(self) -> EntityChangeSet { 216 | self.transaction.to_change_set() 217 | } 218 | } 219 | 220 | /// Systems queued within a `SystemScope` may be scheduled to run in parallel. 221 | pub struct SystemScope { 222 | systems: Vec>>, 223 | } 224 | 225 | impl SystemScope { 226 | fn new() -> SystemScope { 227 | SystemScope { systems: Vec::new() } 228 | } 229 | } 230 | 231 | /// Records system executions, to be run later within a `World`. 232 | pub struct SystemCommandBuffer { 233 | batches: Vec>>>, 234 | } 235 | 236 | impl SystemCommandBuffer<()> { 237 | /// Constructs a new 'SystemCommandBuffer' with an empty state. 238 | pub fn default() -> SystemCommandBuffer<()> { 239 | SystemCommandBuffer::new() 240 | } 241 | } 242 | 243 | impl SystemCommandBuffer { 244 | /// Constructs a new `SystemCommandBuffer`. 245 | pub fn new() -> SystemCommandBuffer { 246 | SystemCommandBuffer { batches: Vec::new() } 247 | } 248 | 249 | /// Queues a sequence of systems into the command buffer. 250 | /// Each system may potentially be run concurrently. 251 | pub fn queue_systems<'a, F, R>(&mut self, f: F) -> R 252 | where F: FnOnce(&mut SystemScope) -> R + 'a 253 | { 254 | let mut scope = SystemScope::new(); 255 | let result = f(&mut scope); 256 | 257 | let mut reading = HashSet::::new(); 258 | let mut writing = HashSet::::new(); 259 | let mut batch = Vec::>>::new(); 260 | for system in scope.systems.into_iter() { 261 | { 262 | let (system_reading, system_writing) = system.resource_access(); 263 | if !can_batch_system(&reading, &writing, (system_reading, system_writing)) { 264 | self.batches.push(batch); 265 | batch = Vec::>>::new(); 266 | reading.clear(); 267 | writing.clear(); 268 | } 269 | 270 | reading = &reading | system_reading; 271 | writing = &writing | system_writing; 272 | } 273 | batch.push(system); 274 | } 275 | 276 | if batch.len() > 0 { 277 | self.batches.push(batch); 278 | } 279 | 280 | result 281 | } 282 | } 283 | 284 | fn can_batch_system(reading: &HashSet, writing: &HashSet, (system_reads, system_writes): (&HashSet, &HashSet)) -> bool { 285 | // the system does not write to any resources being read (which includes writers) 286 | // the system does not read any resources that are being written 287 | reading.is_disjoint(system_writes) && writing.is_disjoint(system_reads) 288 | } 289 | 290 | macro_rules! impl_run_system { 291 | ($name:ident [$($read:ident),*] [$($write:ident),*]) => ( 292 | impl SystemScope { 293 | /// Queues a new system into the command buffer. 294 | /// 295 | /// Each system queued within a single `SystemScope` may be executed 296 | /// in parallel with each other. See crate documentation for more 297 | /// information. 298 | #[allow(non_snake_case, unused_variables, unused_mut)] 299 | pub fn $name<$($read,)* $($write,)* F>(&mut self, mut f: F) -> u32 300 | where $($read:Resource,)* 301 | $($write:Resource,)* 302 | F: for<'a, 'b> FnMut(&'a mut SystemContext<'b, S>, $(&'b $read,)* $(&'b mut $write,)*) + Send + 'static 303 | { 304 | let system = move |world: &World, state: &S| { 305 | // safety of these gets is ensured by the system scheduler 306 | $(let (_, $read) = unsafe { 307 | world.get_resource::<$read>().expect("World does not contain required resource") 308 | };)* 309 | $(let (_, mut $write) = unsafe { 310 | world.get_resource_mut::<$write>().expect("World does not contain required resource") 311 | };)* 312 | 313 | let mut context = SystemContext { 314 | transaction: world.entities.transaction(), 315 | state: state 316 | }; 317 | 318 | f(&mut context, $($read.deref(),)* $($write.deref_mut(),)*); 319 | context.to_change_set() 320 | }; 321 | 322 | let mut read = HashSet::::new(); 323 | $(read.insert(TypeId::of::<$read>());)* 324 | $(read.insert(TypeId::of::<$write>());)* 325 | 326 | let mut write = HashSet::::new(); 327 | $(write.insert(TypeId::of::<$write>());)* 328 | 329 | let id = self.systems.len() as u32; 330 | let boxed = Box::new(FnSystem { 331 | id: id, 332 | read: read, 333 | write: write, 334 | f: system, 335 | phantom: PhantomData 336 | }); 337 | 338 | self.systems.push(boxed); 339 | id 340 | } 341 | } 342 | ) 343 | } 344 | 345 | impl_run_system!(run_r0w0 [] []); 346 | impl_run_system!(run_r1w0 [R0] []); 347 | impl_run_system!(run_r2w0 [R0, R1] []); 348 | impl_run_system!(run_r3w0 [R0, R1, R2] []); 349 | impl_run_system!(run_r4w0 [R0, R1, R2, R3] []); 350 | impl_run_system!(run_r5w0 [R0, R1, R2, R3, R4] []); 351 | impl_run_system!(run_r6w0 [R0, R1, R2, R3, R4, R5] []); 352 | impl_run_system!(run_r0w1 [] [W0]); 353 | impl_run_system!(run_r1w1 [R0] [W0]); 354 | impl_run_system!(run_r2w1 [R0, R1] [W0]); 355 | impl_run_system!(run_r3w1 [R0, R1, R2] [W0]); 356 | impl_run_system!(run_r4w1 [R0, R1, R2, R3] [W0]); 357 | impl_run_system!(run_r5w1 [R0, R1, R2, R3, R4] [W0]); 358 | impl_run_system!(run_r6w1 [R0, R1, R2, R3, R4, R5] [W0]); 359 | impl_run_system!(run_r0w2 [] [W0, W1]); 360 | impl_run_system!(run_r1w2 [R0] [W0, W1]); 361 | impl_run_system!(run_r2w2 [R0, R1] [W0, W1]); 362 | impl_run_system!(run_r3w2 [R0, R1, R2] [W0, W1]); 363 | impl_run_system!(run_r4w2 [R0, R1, R2, R3] [W0, W1]); 364 | impl_run_system!(run_r5w2 [R0, R1, R2, R3, R4] [W0, W1]); 365 | impl_run_system!(run_r6w2 [R0, R1, R2, R3, R4, R5] [W0, W1]); 366 | impl_run_system!(run_r0w3 [] [W0, W1, W3]); 367 | impl_run_system!(run_r1w3 [R0] [W0, W1, W2]); 368 | impl_run_system!(run_r2w3 [R0, R1] [W0, W1, W2]); 369 | impl_run_system!(run_r3w3 [R0, R1, R2] [W0, W1, W2]); 370 | impl_run_system!(run_r4w3 [R0, R1, R2, R3] [W0, W1, W2]); 371 | impl_run_system!(run_r5w3 [R0, R1, R2, R3, R4] [W0, W1, W2]); 372 | impl_run_system!(run_r6w3 [R0, R1, R2, R3, R4, R5] [W0, W1, W2]); 373 | impl_run_system!(run_r0w4 [] [W0, W1, W2, W3]); 374 | impl_run_system!(run_r1w4 [R0] [W0, W1, W2, W3]); 375 | impl_run_system!(run_r2w4 [R0, R1] [W0, W1, W2, W3]); 376 | impl_run_system!(run_r3w4 [R0, R1, R2] [W0, W1, W2, W3]); 377 | impl_run_system!(run_r4w4 [R0, R1, R2, R3] [W0, W1, W2, W3]); 378 | impl_run_system!(run_r5w4 [R0, R1, R2, R3, R4] [W0, W1, W2, W3]); 379 | impl_run_system!(run_r6w4 [R0, R1, R2, R3, R4, R5] [W0, W1, W2, W3]); 380 | impl_run_system!(run_r0w5 [] [W0, W1, W2, W3, W4]); 381 | impl_run_system!(run_r1w5 [R0] [W0, W1, W2, W3, W4]); 382 | impl_run_system!(run_r2w5 [R0, R1] [W0, W1, W2, W3, W4]); 383 | impl_run_system!(run_r3w5 [R0, R1, R2] [W0, W1, W2, W3, W4]); 384 | impl_run_system!(run_r4w5 [R0, R1, R2, R3] [W0, W1, W2, W3, W4]); 385 | impl_run_system!(run_r5w5 [R0, R1, R2, R3, R4] [W0, W1, W2, W3, W4]); 386 | impl_run_system!(run_r6w5 [R0, R1, R2, R3, R4, R5] [W0, W1, W2, W3, W4]); 387 | 388 | /// Determines the behavior entity transaction commits when running a 389 | /// system command buffer sequentially. 390 | pub enum SequentialExecute { 391 | /// Entity creations and delections are always comitted after each system, 392 | /// guarenteeing that delections will always be observed by later queued 393 | /// systems. 394 | /// 395 | /// This does not always result in the same behavior as parallel command 396 | /// buffer execution. 397 | SequentialCommit, 398 | /// Entity creations and delections are always comitted in the same batches 399 | /// that would otherwise had been scheduled for parallel execution had the 400 | /// command buffer been executed in parallel. 401 | /// 402 | /// This emulates the same behavior as parallel command buffer execution. 403 | ParallelBatchedCommit, 404 | } 405 | 406 | impl World { 407 | /// Executes a `SystemCommandBuffer`, potentially scheduling systems for parallel execution, with the given state. 408 | pub fn run_with_state(&mut self, systems: &mut SystemCommandBuffer, state: &S) { 409 | self.execute_batched(systems, |world, batch, changes| { 410 | if batch.len() > world.parallism_threshold { 411 | batch.par_iter_mut() 412 | .map(|system| system.execute(world, state)) 413 | .collect_into(changes); 414 | } 415 | else { 416 | for system in batch.iter_mut() { 417 | changes.push(system.execute(world, state)); 418 | } 419 | } 420 | }); 421 | } 422 | 423 | /// Executes a `SystemCommandBuffer`, potentially scheduling systems for parallel execution, with a default state value. 424 | pub fn run(&mut self, systems: &mut SystemCommandBuffer) { 425 | let state: S = Default::default(); 426 | self.run_with_state(systems, &state); 427 | } 428 | 429 | /// Executes a `SystemCommandBuffer` sequentially, with the given state. 430 | pub fn run_with_state_sequential(&mut self, systems: &mut SystemCommandBuffer, mode: SequentialExecute, state: &S) { 431 | match mode { 432 | SequentialExecute::SequentialCommit => self.run_sequential_sc(systems, state), 433 | SequentialExecute::ParallelBatchedCommit => self.run_sequential_pc(systems, state), 434 | }; 435 | } 436 | 437 | /// Executes a `SystemCommandBuffer` sequentially, with a default state value. 438 | pub fn run_sequential(&mut self, systems: &mut SystemCommandBuffer, mode: SequentialExecute) { 439 | let state: S = Default::default(); 440 | self.run_with_state_sequential(systems, mode, &state); 441 | } 442 | 443 | fn run_sequential_pc(&mut self, systems: &mut SystemCommandBuffer, state: &S) { 444 | self.execute_batched(systems, |world, batch, changes| { 445 | for system in batch.iter_mut() { 446 | changes.push(system.execute(world, state)); 447 | } 448 | }); 449 | } 450 | 451 | fn run_sequential_sc(&mut self, systems: &mut SystemCommandBuffer, state: &S) { 452 | for batch in systems.batches.iter_mut() { 453 | for system in batch.iter_mut() { 454 | let changes = [system.execute(self, state)]; 455 | unsafe { self.clean_deleted_entities(&changes) }; 456 | self.entities.merge(ArrayVec::from(changes).into_iter()); 457 | } 458 | } 459 | } 460 | 461 | fn execute_batched(&mut self, systems: &mut SystemCommandBuffer, mut f: F) 462 | where F: FnMut(&mut World, 463 | &mut Vec>>, 464 | &mut Vec) 465 | { 466 | let mut changes = mem::replace(&mut self.changes_buffer, None).unwrap(); 467 | for batch in systems.batches.iter_mut() { 468 | let additional = batch.len() - changes.len(); 469 | if additional > 0 { 470 | changes.reserve(additional); 471 | } 472 | 473 | f(self, batch, &mut changes); 474 | 475 | unsafe { self.clean_deleted_entities(&changes) }; 476 | self.entities.merge(changes.drain(..)); 477 | } 478 | 479 | self.changes_buffer = Some(changes); 480 | } 481 | } 482 | 483 | #[cfg(test)] 484 | mod tests { 485 | use super::*; 486 | use iter::*; 487 | use std::collections::HashSet; 488 | use std::sync::Arc; 489 | use std::sync::atomic::{AtomicUsize, Ordering}; 490 | 491 | struct TestResource { 492 | pub x: u8, 493 | } 494 | 495 | struct TestResource2 { } 496 | struct TestResource3 { } 497 | 498 | impl Resource for TestResource {} 499 | impl Resource for TestResource2 {} 500 | impl Resource for TestResource3 {} 501 | 502 | #[test] 503 | fn create_world() { 504 | World::new(); 505 | } 506 | 507 | #[test] 508 | fn register_resource() { 509 | let mut world = World::new(); 510 | world.register_resource(TestResource { x: 1 }); 511 | } 512 | 513 | #[test] 514 | fn register_resources_unique_id() { 515 | let mut world = World::new(); 516 | let mut ids: HashSet = HashSet::new(); 517 | 518 | let id = world.register_resource(TestResource { x: 1 }); 519 | assert!(!ids.contains(&id)); 520 | ids.insert(id); 521 | 522 | let id = world.register_resource(TestResource2 {}); 523 | assert!(!ids.contains(&id)); 524 | ids.insert(id); 525 | 526 | let id = world.register_resource(TestResource3 {}); 527 | assert!(!ids.contains(&id)); 528 | ids.insert(id); 529 | } 530 | 531 | #[test] 532 | fn get_resource() { 533 | let mut world = World::new(); 534 | world.register_resource(TestResource { x: 1 }); 535 | 536 | let (_, test) = unsafe { world.get_resource::().unwrap() }; 537 | assert_eq!(test.x, 1); 538 | } 539 | 540 | #[test] 541 | fn get_resource_mut() { 542 | let mut world = World::new(); 543 | world.register_resource(TestResource { x: 1 }); 544 | 545 | let (_, mut test) = unsafe { world.get_resource_mut::().unwrap() }; 546 | assert_eq!(test.x, 1); 547 | test.x = 2; 548 | } 549 | 550 | #[test] 551 | fn run_system() { 552 | let mut world = World::new(); 553 | 554 | let run_count = Arc::new(AtomicUsize::new(0)); 555 | 556 | let run_count_clone = run_count.clone(); 557 | 558 | let mut buffer = SystemCommandBuffer::default(); 559 | buffer.queue_systems(|scope| { 560 | scope.run_r0w0(move |_| { 561 | run_count_clone.fetch_add(1, Ordering::Relaxed); 562 | }); 563 | }); 564 | 565 | world.run(&mut buffer); 566 | 567 | assert_eq!(run_count.load(Ordering::Relaxed), 1); 568 | } 569 | 570 | #[test] 571 | fn run_multiple_system() { 572 | let mut world = World::new(); 573 | 574 | let run_count = Arc::new(AtomicUsize::new(0)); 575 | 576 | let run_count_clone1 = run_count.clone(); 577 | let run_count_clone2 = run_count.clone(); 578 | let run_count_clone3 = run_count.clone(); 579 | 580 | let mut buffer = SystemCommandBuffer::default(); 581 | buffer.queue_systems(|scope| { 582 | scope.run_r0w0(move |_| { 583 | run_count_clone1.fetch_add(1, Ordering::Relaxed); 584 | }); 585 | scope.run_r0w0(move |_| { 586 | run_count_clone2.fetch_add(1, Ordering::Relaxed); 587 | }); 588 | scope.run_r0w0(move |_| { 589 | run_count_clone3.fetch_add(1, Ordering::Relaxed); 590 | }); 591 | }); 592 | 593 | world.run(&mut buffer); 594 | 595 | assert_eq!(run_count.load(Ordering::Relaxed), 3); 596 | } 597 | 598 | #[test] 599 | fn run_system_exclusive_write_sequential() { 600 | let mut world = World::new(); 601 | world.register_resource(TestResource { x: 1 }); 602 | world.register_resource(TestResource2 {}); 603 | 604 | let mut buffer = SystemCommandBuffer::default(); 605 | buffer.queue_systems(|scope| { 606 | scope.run_r1w0(move |_, r: &TestResource| { 607 | assert_eq!(r.x, 1); 608 | }); 609 | scope.run_r1w0(move |_, r: &TestResource| { 610 | assert_eq!(r.x, 1); 611 | }); 612 | scope.run_r1w1(|_, _: &TestResource2, w: &mut TestResource| { 613 | assert_eq!(w.x, 1); 614 | w.x = 2; 615 | }); 616 | scope.run_r1w0(move |_, r: &TestResource| { 617 | assert_eq!(r.x, 2); 618 | }); 619 | }); 620 | 621 | world.run_sequential(&mut buffer, SequentialExecute::SequentialCommit); 622 | } 623 | 624 | #[test] 625 | fn run_system_exclusive_write_parallel() { 626 | for _ in 0..1000 { 627 | let mut world = World::new(); 628 | world.register_resource(TestResource { x: 1 }); 629 | world.register_resource(TestResource2 {}); 630 | 631 | let mut buffer = SystemCommandBuffer::default(); 632 | let (a, b, c, d) = buffer.queue_systems(|scope| { 633 | let a = scope.run_r1w0(move |_, r: &TestResource| { 634 | assert_eq!(r.x, 1); 635 | }); 636 | let b = scope.run_r1w0(move |_, r: &TestResource| { 637 | assert_eq!(r.x, 1); 638 | }); 639 | let c = scope.run_r1w1(|_, _: &TestResource2, w: &mut TestResource| { 640 | assert_eq!(w.x, 1); 641 | w.x = 2; 642 | }); 643 | let d = scope.run_r1w0(move |_, r: &TestResource| { 644 | assert_eq!(r.x, 2); 645 | }); 646 | 647 | (a, b, c, d) 648 | }); 649 | 650 | assert_eq!(buffer.batches.len(), 3); 651 | assert_eq!(buffer.batches[0].len(), 2); 652 | assert_eq!(buffer.batches[0][0].id(), a); 653 | assert_eq!(buffer.batches[0][1].id(), b); 654 | assert_eq!(buffer.batches[1].len(), 1); 655 | assert_eq!(buffer.batches[1][0].id(), c); 656 | assert_eq!(buffer.batches[2].len(), 1); 657 | assert_eq!(buffer.batches[2][0].id(), d); 658 | 659 | world.run(&mut buffer); 660 | } 661 | } 662 | 663 | #[test] 664 | fn system_batch_all_read_distinct() { 665 | let mut buffer = SystemCommandBuffer::default(); 666 | 667 | buffer.queue_systems(|scope| { 668 | scope.run_r1w0(|_, _: &TestResource| {}); 669 | scope.run_r1w0(|_, _: &TestResource2| {}); 670 | scope.run_r1w0(|_, _: &TestResource3| {}); 671 | }); 672 | 673 | assert_eq!(buffer.batches.len(), 1); 674 | assert_eq!(buffer.batches[0].len(), 3); 675 | } 676 | 677 | #[test] 678 | fn system_batch_all_read() { 679 | let mut buffer = SystemCommandBuffer::default(); 680 | 681 | buffer.queue_systems(|scope| { 682 | scope.run_r1w0(|_, _: &TestResource| {}); 683 | scope.run_r1w0(|_, _: &TestResource| {}); 684 | scope.run_r1w0(|_, _: &TestResource2| {}); 685 | }); 686 | 687 | assert_eq!(buffer.batches.len(), 1); 688 | assert_eq!(buffer.batches[0].len(), 3); 689 | } 690 | 691 | #[test] 692 | fn system_batch_all_write_distinct() { 693 | let mut buffer = SystemCommandBuffer::default(); 694 | 695 | buffer.queue_systems(|scope| { 696 | scope.run_r0w1(|_, _: &mut TestResource| {}); 697 | scope.run_r0w1(|_, _: &mut TestResource2| {}); 698 | scope.run_r0w1(|_, _: &mut TestResource3| {}); 699 | }); 700 | 701 | assert_eq!(buffer.batches.len(), 1); 702 | assert_eq!(buffer.batches[0].len(), 3); 703 | assert_eq!(buffer.batches[0][0].id(), 0); 704 | assert_eq!(buffer.batches[0][1].id(), 1); 705 | assert_eq!(buffer.batches[0][2].id(), 2); 706 | } 707 | 708 | #[test] 709 | fn system_batch_all_write() { 710 | let mut buffer = SystemCommandBuffer::default(); 711 | 712 | buffer.queue_systems(|scope| { 713 | scope.run_r0w1(|_, _: &mut TestResource| {}); 714 | scope.run_r0w1(|_, _: &mut TestResource2| {}); 715 | scope.run_r0w2(|_, _: &mut TestResource, _: &mut TestResource2| {}); 716 | }); 717 | 718 | assert_eq!(buffer.batches.len(), 2); 719 | assert_eq!(buffer.batches[0].len(), 2); 720 | assert_eq!(buffer.batches[0][0].id(), 0); 721 | assert_eq!(buffer.batches[0][1].id(), 1); 722 | assert_eq!(buffer.batches[1].len(), 1); 723 | assert_eq!(buffer.batches[1][0].id(), 2); 724 | } 725 | 726 | #[test] 727 | fn system_batch_all_write_interleaved() { 728 | let mut buffer = SystemCommandBuffer::default(); 729 | 730 | buffer.queue_systems(|scope| { 731 | scope.run_r0w1(|_, _: &mut TestResource| {}); 732 | scope.run_r0w2(|_, _: &mut TestResource, _: &mut TestResource2| {}); 733 | scope.run_r0w1(|_, _: &mut TestResource2| {}); 734 | }); 735 | 736 | assert_eq!(buffer.batches.len(), 3); 737 | assert_eq!(buffer.batches[0].len(), 1); 738 | assert_eq!(buffer.batches[0][0].id(), 0); 739 | assert_eq!(buffer.batches[1].len(), 1); 740 | assert_eq!(buffer.batches[1][0].id(), 1); 741 | assert_eq!(buffer.batches[2].len(), 1); 742 | assert_eq!(buffer.batches[2][0].id(), 2); 743 | } 744 | 745 | #[test] 746 | fn system_batch_mixed() { 747 | let mut buffer = SystemCommandBuffer::default(); 748 | 749 | buffer.queue_systems(|scope| { 750 | scope.run_r1w0(|_, _: &TestResource| {}); 751 | scope.run_r1w1(|_, _: &TestResource, _: &mut TestResource2| {}); 752 | scope.run_r1w0(|_, _: &TestResource| {}); 753 | scope.run_r2w0(|_, _: &TestResource, _: &TestResource2| {}); 754 | scope.run_r1w0(|_, _: &TestResource| {}); 755 | scope.run_r0w1(|_, _: &mut TestResource| {}); 756 | }); 757 | 758 | assert_eq!(buffer.batches.len(), 3); 759 | assert_eq!(buffer.batches[0].len(), 3); 760 | assert_eq!(buffer.batches[0][0].id(), 0); 761 | assert_eq!(buffer.batches[0][1].id(), 1); 762 | assert_eq!(buffer.batches[0][2].id(), 2); 763 | assert_eq!(buffer.batches[1].len(), 2); 764 | assert_eq!(buffer.batches[1][0].id(), 3); 765 | assert_eq!(buffer.batches[1][1].id(), 4); 766 | assert_eq!(buffer.batches[2].len(), 1); 767 | assert_eq!(buffer.batches[2][0].id(), 5); 768 | } 769 | 770 | #[test] 771 | fn system_command_buffer() { 772 | let mut systems = SystemCommandBuffer::default(); 773 | systems.queue_systems(|scope| { 774 | scope.run_r1w1(|_, _: &TestResource, _: &mut TestResource2| { 775 | 776 | }); 777 | 778 | scope.run_r1w1(|_, _: &TestResource, _: &mut TestResource2| { 779 | 780 | }); 781 | }); 782 | 783 | let mut world = World::new(); 784 | world.register_resource(TestResource { x: 1 }); 785 | world.register_resource(TestResource2 {}); 786 | world.run(&mut systems); 787 | } 788 | 789 | #[test] 790 | fn pass_state() { 791 | let mut systems = SystemCommandBuffer::::new(); 792 | systems.queue_systems(|scope| { 793 | scope.run_r0w0(|ctx| { 794 | assert_eq!(ctx.state(), &5u32); 795 | }); 796 | }); 797 | 798 | let mut world = World::new(); 799 | world.run_with_state(&mut systems, &5u32); 800 | } 801 | 802 | #[test] 803 | fn clean_deleted_entities() { 804 | let mut world = World::new(); 805 | world.register_resource(VecResource::::new()); 806 | 807 | let mut buffer = SystemCommandBuffer::default(); 808 | buffer.queue_systems(|scope| { 809 | scope.run_r0w1(|tx, resource: &mut VecResource| { 810 | println!("Creating entities"); 811 | for i in 0..1000 { 812 | let e = tx.create(); 813 | resource.add(e, i); 814 | } 815 | println!("Entities created"); 816 | }); 817 | 818 | scope.run_r1w0(|ctx, resource: &VecResource| { 819 | println!("Verifying entity creation"); 820 | let (iter, r) = (resource,).iter().entities(ctx); 821 | for e in iter { 822 | assert_eq!(e.index(), *r.get(e).unwrap()); 823 | } 824 | println!("Verified entity creation"); 825 | }); 826 | 827 | scope.run_r0w1(|ctx, resource: &mut VecResource| { 828 | println!("Deleting entities"); 829 | let mut entities = Vec::::new(); 830 | { 831 | let (iter, _) = (resource,).iter().entities(ctx); 832 | for e in iter { 833 | entities.push(e); 834 | } 835 | } 836 | 837 | for e in entities { 838 | ctx.destroy(e); 839 | } 840 | println!("Deleted entities"); 841 | }); 842 | 843 | scope.run_r1w0(|ctx, resource: &VecResource| { 844 | println!("Verifying entity deletion"); 845 | let mut entities = Vec::::new(); 846 | let (iter, _) = (resource,).iter().entities(ctx); 847 | for e in iter { 848 | entities.push(e); 849 | } 850 | 851 | assert_eq!(entities.len(), 0); 852 | println!("Verified entity delection"); 853 | }); 854 | }); 855 | 856 | world.run(&mut buffer); 857 | } 858 | } 859 | --------------------------------------------------------------------------------