├── .cargo └── config ├── .github └── dependabot.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── benchmarks.yaml ├── clippy.toml ├── crates ├── apecs-derive │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── apecs │ ├── .DS_Store │ ├── Cargo.toml │ ├── src │ │ ├── entity.rs │ │ ├── facade.rs │ │ ├── lib.rs │ │ ├── storage │ │ │ ├── archetype │ │ │ │ ├── archetype.rs │ │ │ │ ├── bundle.rs │ │ │ │ ├── components.rs │ │ │ │ ├── mod.rs │ │ │ │ └── query.rs │ │ │ └── mod.rs │ │ └── world.rs │ └── tests │ │ ├── regression.rs │ │ ├── unit.rs │ │ └── wasm.rs ├── benchmarks │ ├── Cargo.toml │ ├── benches │ │ ├── add_remove.rs │ │ ├── benchmarks.rs │ │ ├── bevy │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── heavy_compute.rs │ │ │ ├── mod.rs │ │ │ ├── schedule.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ │ ├── frag_iter.rs │ │ ├── heavy_compute.rs │ │ ├── hecs │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── heavy_compute.rs │ │ │ ├── mod.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ │ ├── legion │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── heavy_compute.rs │ │ │ ├── mod.rs │ │ │ ├── schedule.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ │ ├── planck_ecs │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── mod.rs │ │ │ ├── schedule.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ │ ├── schedule.rs │ │ ├── shipyard │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── heavy_compute.rs │ │ │ ├── mod.rs │ │ │ ├── schedule.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ │ ├── simple_insert.rs │ │ ├── simple_iter.rs │ │ └── specs │ │ │ ├── add_remove.rs │ │ │ ├── frag_iter.rs │ │ │ ├── heavy_compute.rs │ │ │ ├── mod.rs │ │ │ ├── schedule.rs │ │ │ ├── simple_insert.rs │ │ │ └── simple_iter.rs │ └── src │ │ └── main.rs └── xtask │ ├── Cargo.toml │ └── src │ ├── bench.rs │ └── main.rs ├── manual ├── .gitignore ├── book.toml ├── src │ ├── SUMMARY.md │ ├── apecs.md │ ├── chapter_1.md │ └── introduction.md └── theme │ ├── book.js │ ├── css │ ├── chrome.css │ ├── general.css │ ├── print.css │ └── variables.css │ ├── favicon.png │ ├── favicon.svg │ ├── highlight.css │ ├── highlight.js │ └── index.hbs └── rustfmt.toml /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | # Check for updates every Monday 6 | schedule: 7 | interval: "weekly" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *~undo-tree~ 4 | flamegraph.svg 5 | .#* 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/apecs", 4 | "crates/apecs-derive", 5 | "crates/benchmarks", 6 | "crates/xtask", 7 | ] 8 | 9 | resolver = "2" 10 | 11 | [profile.bench] 12 | debug = true 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apecs 2 | **A**sync-friendly and **P**leasant **E**ntity **C**omponent **S**ystem 3 | 4 | `apecs` is an entity-component system written in Rust that can share world resources with 5 | futures run in any async runtime. This makes it great for general applications, 6 | quick game prototypes, DIY engines and any simulation that has discrete steps in time. 7 | 8 | ## Why 9 | 10 | Most ECS libraries (and game main-loops in general) are polling based. 11 | This is great for certain tasks, but things get complicated when programming in the time domain. 12 | Async / await is great for programming in the time domain without explicitly spawning new threads 13 | or blocking, but it isn't supported by ECS libraries. 14 | 15 | `apecs` was designed to to be an ECS that plays nice with async / await. 16 | 17 | ## What and How 18 | 19 | At its core `apecs` is a library for sharing resources across disparate polling and async loops. 20 | It uses derivable traits and channels to orchestrate systems' access to resources and uses rayon 21 | (where available) for concurrency. 22 | 23 | ## Goals 24 | * productivity 25 | * flexibility 26 | * observability 27 | * very well rounded performance, competitive with inspirational ECS libraries 28 | - like `specs`, `bevy_ecs`, `hecs`, `legion`, `shipyard`, `planck_ecs` 29 | - backed by criterion benchmarks 30 | 31 | ## Features 32 | 33 | Here is a quick table of features compared to other ECSs. 34 | 35 | | Feature | apecs | bevy_ecs | hecs | legion | planck_ecs | shipyard | specs | 36 | |-------------------|----------|----------|----------|----------|------------|----------|-----------| 37 | | storage |archetypal| hybrid |archetypal|archetypal| separated | sparse | separated | 38 | | system scheduling | ✔️ | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ | 39 | | early exit systems| ✔️ | | | | | | | 40 | | parallel systems | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | ✔️ | 41 | | change tracking | ✔️ | ✔️ | | kinda | | ✔️ | ✔️ | 42 | | async support | ✔️ | | | | | | | 43 | 44 | ### Feature examples 45 | 46 | - systems with early exit and failure 47 | ```rust 48 | use apecs::*; 49 | 50 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 51 | struct Number(u32); 52 | 53 | fn demo_system(mut u32_number: ViewMut) -> Result<(), GraphError> { 54 | u32_number.0 += 1; 55 | if u32_number.0 == 3 { 56 | end() 57 | } else { 58 | ok() 59 | } 60 | } 61 | 62 | let mut world = World::default(); 63 | world.add_subgraph(graph!(demo_system)); 64 | world.run().unwrap(); 65 | assert_eq!(Number(3), *world.get_resource::().unwrap()); 66 | ``` 67 | 68 | - async support 69 | - futures visit world resources through `Facade` using a closure. 70 | - resources are acquired without lifetimes 71 | - plays well with any async runtime 72 | ```rust 73 | use apecs::*; 74 | 75 | #[derive(Clone, Copy, Debug, Default, PartialEq)] 76 | struct Number(u32); 77 | 78 | let mut world = World::default(); 79 | let mut facade = world.facade(); 80 | 81 | let task = smol::spawn(async move { 82 | loop { 83 | let i = facade 84 | .visit(|mut u32_number: ViewMut| { 85 | u32_number.0 += 1; 86 | u32_number.0 87 | }) 88 | .await 89 | .unwrap(); 90 | if i > 5 { 91 | break; 92 | } 93 | } 94 | }); 95 | 96 | while !task.is_finished() { 97 | world.tick().unwrap(); 98 | world.get_facade_schedule().unwrap().run().unwrap(); 99 | } 100 | 101 | assert_eq!(Number(6), *world.get_resource::().unwrap()); 102 | ``` 103 | 104 | - system data derive macros 105 | ```rust 106 | use apecs::*; 107 | 108 | #[derive(Edges)] 109 | struct MyData { 110 | entities: View, 111 | u32_number: ViewMut, 112 | } 113 | 114 | let mut world = World::default(); 115 | world 116 | .visit(|mut my_data: MyData| { 117 | *my_data.u32_number = 1; 118 | }) 119 | .unwrap(); 120 | ``` 121 | 122 | - system scheduling 123 | - compatible systems are placed in parallel batches (a batch is a group of systems 124 | that can run in parallel, ie they don't have conflicting borrows) 125 | - systems may depend on other systems running before or after 126 | - barriers 127 | ```rust 128 | use apecs::*; 129 | 130 | fn one(mut u32_number: ViewMut) -> Result<(), GraphError> { 131 | *u32_number += 1; 132 | end() 133 | } 134 | 135 | fn two(mut u32_number: ViewMut) -> Result<(), GraphError> { 136 | *u32_number += 1; 137 | end() 138 | } 139 | 140 | fn exit_on_three(mut f32_number: ViewMut) -> Result<(), GraphError> { 141 | *f32_number += 1.0; 142 | if *f32_number == 3.0 { 143 | end() 144 | } else { 145 | ok() 146 | } 147 | } 148 | 149 | fn lastly((u32_number, f32_number): (View, View)) -> Result<(), GraphError> { 150 | if *u32_number == 2 && *f32_number == 3.0 { 151 | end() 152 | } else { 153 | ok() 154 | } 155 | } 156 | 157 | let mut world = World::default(); 158 | world.add_subgraph( 159 | graph!( 160 | // one should run before two 161 | one < two, 162 | // exit_on_three has no dependencies 163 | exit_on_three 164 | ) 165 | // add a barrier 166 | .with_barrier() 167 | .with_subgraph( 168 | // all systems after a barrier run after the systems before a barrier 169 | graph!(lastly), 170 | ), 171 | ); 172 | 173 | assert_eq!( 174 | vec![vec!["exit_on_three", "one"], vec!["two"], vec!["lastly"]], 175 | world.get_schedule_names() 176 | ); 177 | 178 | world.tick().unwrap(); 179 | 180 | assert_eq!( 181 | vec![vec!["exit_on_three"], vec!["lastly"]], 182 | world.get_schedule_names() 183 | ); 184 | 185 | world.tick().unwrap(); 186 | world.tick().unwrap(); 187 | assert!(world.get_schedule_names().is_empty()); 188 | ``` 189 | - component storage 190 | - optimized for space and iteration time as archetypes 191 | - queries with "maybe" and "without" semantics 192 | - queries can find a single entity without iteration or filtering 193 | - add and modified time tracking 194 | - parallel queries (inner parallelism) 195 | ```rust 196 | use apecs::*; 197 | 198 | // Make a type for tracking changes 199 | #[derive(Default)] 200 | struct MyTracker(u64); 201 | 202 | fn create(mut entities: ViewMut) -> Result<(), GraphError> { 203 | for mut entity in (0..100).map(|_| entities.create()) { 204 | entity.insert_bundle((0.0f32, 0u32, format!("{}:0", entity.id()))); 205 | } 206 | end() 207 | } 208 | 209 | fn progress(q_f32s: Query<&mut f32>) -> Result<(), GraphError> { 210 | for f32 in q_f32s.query().iter_mut() { 211 | **f32 += 1.0; 212 | } 213 | ok() 214 | } 215 | 216 | fn sync( 217 | (q_others, mut tracker): (Query<(&f32, &mut String, &mut u32)>, ViewMut), 218 | ) -> Result<(), GraphError> { 219 | for (f32, string, u32) in q_others.query().iter_mut() { 220 | if f32.was_modified_since(tracker.0) { 221 | **u32 = **f32 as u32; 222 | **string = format!("{}:{}", f32.id(), **u32); 223 | } 224 | } 225 | tracker.0 = apecs::current_iteration(); 226 | ok() 227 | } 228 | 229 | // Entities and Components (which stores components) are default 230 | // resources 231 | let mut world = World::default(); 232 | world.add_subgraph(graph!( 233 | create < progress < sync 234 | )); 235 | 236 | assert_eq!( 237 | vec![vec!["create"], vec!["progress"], vec!["sync"]], 238 | world.get_schedule_names() 239 | ); 240 | 241 | world.tick().unwrap(); // entities are created, components applied lazily 242 | world.tick().unwrap(); // f32s are modified, u32s and strings are synced 243 | world.tick().unwrap(); // f32s are modified, u32s and strings are synced 244 | 245 | world 246 | .visit(|q_bundle: Query<(&f32, &u32, &String)>| { 247 | assert_eq!( 248 | (2.0f32, 2u32, "13:2".to_string()), 249 | q_bundle 250 | .query() 251 | .find_one(13) 252 | .map(|(f, u, s)| (**f, **u, s.to_string())) 253 | .unwrap() 254 | ); 255 | }) 256 | .unwrap(); 257 | ``` 258 | - outer parallelism (running systems in parallel) 259 | - parallel system scheduling 260 | - parallel execution of async futures 261 | - parallelism is configurable (can be automatic or a requested number of threads, including 1) 262 | ```rust 263 | use apecs::*; 264 | 265 | #[derive(Default)] 266 | struct F32(f32); 267 | 268 | let mut world = World::default(); 269 | 270 | fn one(mut f32_number: ViewMut) -> Result<(), GraphError> { 271 | f32_number.0 += 1.0; 272 | ok() 273 | } 274 | 275 | fn two(f32_number: View) -> Result<(), GraphError> { 276 | println!("system two reads {}", f32_number.0); 277 | ok() 278 | } 279 | 280 | fn three(f32_number: View) -> Result<(), GraphError> { 281 | println!("system three reads {}", f32_number.0); 282 | ok() 283 | } 284 | 285 | world 286 | .add_subgraph(graph!(one, two, three)) 287 | .with_parallelism(Parallelism::Automatic); 288 | 289 | world.tick().unwrap(); 290 | ``` 291 | 292 | - fully compatible with WASM and runs in the browser 293 | 294 | ## Roadmap 295 | - your ideas go here 296 | 297 | ## Tests 298 | ```bash 299 | cargo test 300 | wasm-pack test --firefox crates/apecs 301 | ``` 302 | 303 | I like firefox, but you can use different browsers for the wasm tests. The tests 304 | make sure apecs works on wasm. 305 | 306 | ## Benchmarks 307 | The `apecs` benchmarks measure itself against my favorite ECS libs: 308 | `specs`, `bevy`, `hecs`, `legion`, `shipyard` and `planck_ecs`. 309 | 310 | ```bash 311 | cargo bench -p benchmarks 312 | ``` 313 | 314 | # Minimum supported Rust version 1.65 315 | `apecs` uses generic associated types for its component iteration traits. 316 | -------------------------------------------------------------------------------- /benchmarks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | benchmarks: 3 | - date: "2022-03-14T04:18:22.070062Z" 4 | benchmark: 5 | name: create_move_print/1000_ticks/async_2 6 | ns: 2137296 7 | range: 18656 8 | tags: [] 9 | - date: "2022-03-14T04:18:22.070062Z" 10 | benchmark: 11 | name: create_move_print/1000_ticks/async_4 12 | ns: 2129592 13 | range: 20987 14 | tags: [] 15 | - date: "2022-03-14T04:18:22.070062Z" 16 | benchmark: 17 | name: create_move_print/1000_ticks/async_8 18 | ns: 2238145 19 | range: 2697 20 | tags: [] 21 | - date: "2022-03-14T04:18:22.070062Z" 22 | benchmark: 23 | name: create_move_print/1000_ticks/async_16 24 | ns: 2399222 25 | range: 20987 26 | tags: [] 27 | - date: "2022-03-14T04:18:22.070062Z" 28 | benchmark: 29 | name: create_move_print/1000_ticks/async_32 30 | ns: 2846626 31 | range: 3924 32 | tags: [] 33 | - date: "2022-03-14T04:18:22.070062Z" 34 | benchmark: 35 | name: create_move_print/1000_ticks/async_64 36 | ns: 3593584 37 | range: 19994 38 | tags: [] 39 | - date: "2022-03-14T04:18:22.070062Z" 40 | benchmark: 41 | name: create_move_print/1000_ticks/async_128 42 | ns: 5106435 43 | range: 43654 44 | tags: [] 45 | - date: "2022-03-14T04:18:22.070062Z" 46 | benchmark: 47 | name: create_move_print/1000_ticks/async_256 48 | ns: 8279595 49 | range: 39989 50 | tags: [] 51 | - date: "2022-03-14T04:18:22.070062Z" 52 | benchmark: 53 | name: create_move_print/1000_ticks/async_512 54 | ns: 14614588 55 | range: 54168 56 | tags: [] 57 | - date: "2022-03-14T04:18:22.070062Z" 58 | benchmark: 59 | name: create_move_print/1000_ticks/async_1024 60 | ns: 27494916 61 | range: 219793 62 | tags: [] 63 | - date: "2022-03-14T04:18:22.070062Z" 64 | benchmark: 65 | name: create_move_print/1000_ticks/async_2048 66 | ns: 53426541 67 | range: 426742 68 | tags: [] 69 | - date: "2022-03-14T04:36:35.857090Z" 70 | benchmark: 71 | name: create_move_print/1000_ticks/async_2 72 | ns: 2062093 73 | range: 9614 74 | tags: [] 75 | - date: "2022-03-14T04:36:35.857090Z" 76 | benchmark: 77 | name: create_move_print/1000_ticks/async_4 78 | ns: 2156394 79 | range: 32825 80 | tags: [] 81 | - date: "2022-03-14T04:36:35.857090Z" 82 | benchmark: 83 | name: create_move_print/1000_ticks/async_8 84 | ns: 2210451 85 | range: 15762 86 | tags: [] 87 | - date: "2022-03-14T04:36:35.857090Z" 88 | benchmark: 89 | name: create_move_print/1000_ticks/async_16 90 | ns: 2408396 91 | range: 2107 92 | tags: [] 93 | - date: "2022-03-14T04:36:35.857090Z" 94 | benchmark: 95 | name: create_move_print/1000_ticks/async_32 96 | ns: 2804656 97 | range: 5577 98 | tags: [] 99 | - date: "2022-03-14T04:36:35.857090Z" 100 | benchmark: 101 | name: create_move_print/1000_ticks/async_64 102 | ns: 3571062 103 | range: 5764 104 | tags: [] 105 | - date: "2022-03-14T04:36:35.857090Z" 106 | benchmark: 107 | name: create_move_print/1000_ticks/async_128 108 | ns: 5113175 109 | range: 12540 110 | tags: [] 111 | - date: "2022-03-14T04:36:35.857090Z" 112 | benchmark: 113 | name: create_move_print/1000_ticks/async_256 114 | ns: 8262386 115 | range: 32518 116 | tags: [] 117 | - date: "2022-03-14T04:36:35.857090Z" 118 | benchmark: 119 | name: create_move_print/1000_ticks/async_512 120 | ns: 14625718 121 | range: 102934 122 | tags: [] 123 | - date: "2022-03-14T04:36:35.857090Z" 124 | benchmark: 125 | name: create_move_print/1000_ticks/async_1024 126 | ns: 27537656 127 | range: 155197 128 | tags: [] 129 | - date: "2022-03-14T04:36:35.857090Z" 130 | benchmark: 131 | name: create_move_print/1000_ticks/async_2048 132 | ns: 53528270 133 | range: 509321 134 | tags: [] 135 | - date: "2022-03-14T06:07:21.903336Z" 136 | benchmark: 137 | name: create_move_print/1000_ticks/async_2 138 | ns: 2042183 139 | range: 841 140 | tags: [] 141 | - date: "2022-03-14T06:07:21.903336Z" 142 | benchmark: 143 | name: create_move_print/1000_ticks/async_4 144 | ns: 2082002 145 | range: 38448 146 | tags: [] 147 | - date: "2022-03-14T06:07:21.903336Z" 148 | benchmark: 149 | name: create_move_print/1000_ticks/async_8 150 | ns: 2201820 151 | range: 10289 152 | tags: [] 153 | - date: "2022-03-14T06:07:21.903336Z" 154 | benchmark: 155 | name: create_move_print/1000_ticks/async_16 156 | ns: 2403414 157 | range: 20062 158 | tags: [] 159 | - date: "2022-03-14T06:07:21.903336Z" 160 | benchmark: 161 | name: create_move_print/1000_ticks/async_32 162 | ns: 2814378 163 | range: 14372 164 | tags: [] 165 | - date: "2022-03-14T06:07:21.903336Z" 166 | benchmark: 167 | name: create_move_print/1000_ticks/async_64 168 | ns: 3569233 169 | range: 23616 170 | tags: [] 171 | - date: "2022-03-14T06:07:21.903336Z" 172 | benchmark: 173 | name: create_move_print/1000_ticks/async_128 174 | ns: 5106006 175 | range: 45312 176 | tags: [] 177 | - date: "2022-03-14T06:07:21.903336Z" 178 | benchmark: 179 | name: create_move_print/1000_ticks/async_256 180 | ns: 8242276 181 | range: 42565 182 | tags: [] 183 | - date: "2022-03-14T06:07:21.903336Z" 184 | benchmark: 185 | name: create_move_print/1000_ticks/async_512 186 | ns: 14685927 187 | range: 265976 188 | tags: [] 189 | - date: "2022-03-14T06:07:21.903336Z" 190 | benchmark: 191 | name: create_move_print/1000_ticks/async_1024 192 | ns: 27460104 193 | range: 204826 194 | tags: [] 195 | - date: "2022-03-14T06:07:21.903336Z" 196 | benchmark: 197 | name: create_move_print/1000_ticks/async_2048 198 | ns: 53451937 199 | range: 449469 200 | tags: [] 201 | - date: "2022-03-14T07:23:49.392613Z" 202 | benchmark: 203 | name: create_move_print/1000_ticks/async_2 204 | ns: 1769979 205 | range: 6943 206 | tags: [] 207 | - date: "2022-03-14T07:23:49.392613Z" 208 | benchmark: 209 | name: create_move_print/1000_ticks/async_4 210 | ns: 1843944 211 | range: 13539 212 | tags: [] 213 | - date: "2022-03-14T07:23:49.392613Z" 214 | benchmark: 215 | name: create_move_print/1000_ticks/async_8 216 | ns: 1960194 217 | range: 8975 218 | tags: [] 219 | - date: "2022-03-14T07:23:49.392613Z" 220 | benchmark: 221 | name: create_move_print/1000_ticks/async_16 222 | ns: 2155579 223 | range: 9273 224 | tags: [] 225 | - date: "2022-03-14T07:23:49.392613Z" 226 | benchmark: 227 | name: create_move_print/1000_ticks/async_32 228 | ns: 2597797 229 | range: 12529 230 | tags: [] 231 | - date: "2022-03-14T07:23:49.392613Z" 232 | benchmark: 233 | name: create_move_print/1000_ticks/async_64 234 | ns: 3442434 235 | range: 16517 236 | tags: [] 237 | - date: "2022-03-14T07:23:49.392613Z" 238 | benchmark: 239 | name: create_move_print/1000_ticks/async_128 240 | ns: 5097295 241 | range: 30979 242 | tags: [] 243 | - date: "2022-03-14T07:23:49.392613Z" 244 | benchmark: 245 | name: create_move_print/1000_ticks/async_256 246 | ns: 8527736 247 | range: 39784 248 | tags: [] 249 | - date: "2022-03-14T07:23:49.392613Z" 250 | benchmark: 251 | name: create_move_print/1000_ticks/async_512 252 | ns: 15350463 253 | range: 73481 254 | tags: [] 255 | - date: "2022-03-14T07:23:49.392613Z" 256 | benchmark: 257 | name: create_move_print/1000_ticks/async_1024 258 | ns: 29042604 259 | range: 189330 260 | tags: [] 261 | - date: "2022-03-14T07:23:49.392613Z" 262 | benchmark: 263 | name: create_move_print/1000_ticks/async_2048 264 | ns: 57055895 265 | range: 324366 266 | tags: [] 267 | - date: "2022-03-17T07:59:00.725474Z" 268 | benchmark: 269 | name: create_move_print/1000_ticks/async_2 270 | ns: 2253905 271 | range: 22007 272 | tags: [] 273 | - date: "2022-03-17T07:59:00.725474Z" 274 | benchmark: 275 | name: create_move_print/1000_ticks/async_4 276 | ns: 2199460 277 | range: 19564 278 | tags: [] 279 | - date: "2022-03-17T07:59:00.725474Z" 280 | benchmark: 281 | name: create_move_print/1000_ticks/async_8 282 | ns: 2292374 283 | range: 10653 284 | tags: [] 285 | - date: "2022-03-17T07:59:00.725474Z" 286 | benchmark: 287 | name: create_move_print/1000_ticks/async_16 288 | ns: 2329876 289 | range: 12204 290 | tags: [] 291 | - date: "2022-03-17T07:59:00.725474Z" 292 | benchmark: 293 | name: create_move_print/1000_ticks/async_32 294 | ns: 2488839 295 | range: 17284 296 | tags: [] 297 | - date: "2022-03-17T07:59:00.725474Z" 298 | benchmark: 299 | name: create_move_print/1000_ticks/async_64 300 | ns: 2718698 301 | range: 17264 302 | tags: [] 303 | - date: "2022-03-17T07:59:00.725474Z" 304 | benchmark: 305 | name: create_move_print/1000_ticks/async_128 306 | ns: 3203385 307 | range: 13552 308 | tags: [] 309 | - date: "2022-03-17T07:59:00.725474Z" 310 | benchmark: 311 | name: create_move_print/1000_ticks/async_256 312 | ns: 4212125 313 | range: 18768 314 | tags: [] 315 | - date: "2022-03-17T07:59:00.725474Z" 316 | benchmark: 317 | name: create_move_print/1000_ticks/async_512 318 | ns: 6139847 319 | range: 33053 320 | tags: [] 321 | - date: "2022-03-17T07:59:00.725474Z" 322 | benchmark: 323 | name: create_move_print/1000_ticks/async_1024 324 | ns: 10078812 325 | range: 47832 326 | tags: [] 327 | - date: "2022-03-17T07:59:00.725474Z" 328 | benchmark: 329 | name: create_move_print/1000_ticks/async_2048 330 | ns: 17920881 331 | range: 115970 332 | tags: [] 333 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"] 2 | -------------------------------------------------------------------------------- /crates/apecs-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apecs-derive" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | keywords = ["gamedev", "ecs", "async"] 7 | documentation = "https://docs.rs/apecs/" 8 | repository = "https://github.com/schell/apecs" 9 | categories = ["concurrency", "game-engines"] 10 | description = "derive and other macros for the apecs library" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true 16 | bench = false 17 | 18 | [dependencies] 19 | moongraph-macros-syntax = "0.1.0" 20 | proc-macro2 = "1.0" 21 | quote = "1.0" 22 | syn = { version = "2.0", features = ["full"] } 23 | -------------------------------------------------------------------------------- /crates/apecs-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use quote::quote; 3 | use syn::{parse_macro_input, DeriveInput, Ident, TypeTuple}; 4 | 5 | #[proc_macro_derive(Edges, attributes(apecs))] 6 | pub fn derive_edges(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 7 | let input: DeriveInput = parse_macro_input!(input); 8 | 9 | let maybe_path = match moongraph_macros_syntax::find_path("apecs", &input) { 10 | Err(e) => return e.into_compile_error().into(), 11 | Ok(mp) => mp, 12 | }; 13 | 14 | let path = maybe_path.unwrap_or_else(|| { 15 | // UNWRAP: safe because we know this will parse 16 | let path: syn::Path = syn::parse_str("apecs").unwrap(); 17 | path 18 | }); 19 | moongraph_macros_syntax::derive_edges(input, path).into() 20 | } 21 | 22 | #[proc_macro] 23 | pub fn impl_isquery_tuple(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 24 | let tuple: TypeTuple = parse_macro_input!(input); 25 | let tys = tuple.elems.iter().collect::>(); 26 | let nvars: Vec = (0..tys.len()) 27 | .map(|n| Ident::new(&format!("n{}", n), Span::call_site())) 28 | .collect(); 29 | let mvars: Vec = (0..tys.len()) 30 | .map(|n| Ident::new(&format!("m{}", n), Span::call_site())) 31 | .collect(); 32 | let extend_impl = tys 33 | .iter() 34 | .zip(nvars.iter().zip(mvars.iter())) 35 | .skip(1) 36 | .map(|(ty, (n, m))| { 37 | quote! { 38 | #ty::extend_locked_columns(#n, #m, None); 39 | } 40 | }); 41 | let query_result_zip = tys 42 | .iter() 43 | .fold(None, |prev, ty| { 44 | Some(prev.map_or_else( 45 | || quote! {#ty::QueryResult<'a>}, 46 | |p| { 47 | quote! {std::iter::Zip<#p, #ty::QueryResult<'a>>} 48 | }, 49 | )) 50 | }) 51 | .unwrap(); 52 | let query_result_fn_param = tys 53 | .iter() 54 | .fold(None, |prev, ty| { 55 | Some(prev.map_or_else( 56 | || quote! {#ty::QueryRow<'a>}, 57 | |p| quote! {(#p, #ty::QueryRow<'a>)}, 58 | )) 59 | }) 60 | .unwrap(); 61 | let par_query_result_zip = tys 62 | .iter() 63 | .fold(None, |prev, ty| { 64 | Some(prev.map_or_else( 65 | || quote! {#ty::ParQueryResult<'a>}, 66 | |p| { 67 | quote! {rayon::iter::Zip<#p, #ty::ParQueryResult<'a>>} 68 | }, 69 | )) 70 | }) 71 | .unwrap(); 72 | let iter_mut_impl_zip = tys 73 | .iter() 74 | .zip(nvars.iter()) 75 | .fold(None, |prev, (ty, n)| { 76 | Some(prev.map_or_else( 77 | || quote! {#ty::iter_mut(#n)}, 78 | |p| quote! {#p.zip(#ty::iter_mut(#n))}, 79 | )) 80 | }) 81 | .unwrap(); 82 | let iter_one_impl_zip = tys 83 | .iter() 84 | .zip(nvars.iter()) 85 | .fold(None, |prev, (ty, n)| { 86 | Some(prev.map_or_else( 87 | || quote! {#ty::iter_one(#n, index)}, 88 | |p| quote! {#p.zip(#ty::iter_one(#n, index))}, 89 | )) 90 | }) 91 | .unwrap(); 92 | let par_iter_mut_impl_zip = tys 93 | .iter() 94 | .zip(nvars.iter()) 95 | .fold(None, |prev, (ty, n)| { 96 | Some(prev.map_or_else( 97 | || quote! {#ty::par_iter_mut(len, #n)}, 98 | |p| quote! {#p.zip(#ty::par_iter_mut(len, #n))}, 99 | )) 100 | }) 101 | .unwrap(); 102 | let nvar_tuple_list = nvars 103 | .iter() 104 | .fold(None, |prev, n| { 105 | Some(prev.map_or_else(|| quote! {#n}, |p| quote! {(#p, #n)})) 106 | }) 107 | .unwrap(); 108 | let iter_mut_impl = quote! { 109 | #iter_mut_impl_zip.map(|#nvar_tuple_list| (#(#nvars),*)) 110 | }; 111 | let iter_one_impl = quote! { 112 | #iter_one_impl_zip.map(|#nvar_tuple_list| (#(#nvars),*)) 113 | }; 114 | let par_iter_mut_impl = quote! { 115 | #par_iter_mut_impl_zip.map(|#nvar_tuple_list| (#(#nvars),*)) 116 | }; 117 | 118 | let isquery_for_tuple = quote! { 119 | impl <#(#tys),*> apecs::storage::archetype::IsQuery for #tuple 120 | where 121 | #(#tys: IsQuery),* 122 | { 123 | type LockedColumns<'a> = ( 124 | #(#tys::LockedColumns<'a>),* 125 | ); 126 | 127 | type ExtensionColumns = ( 128 | #(#tys::ExtensionColumns),* 129 | ); 130 | 131 | type QueryResult<'a> = std::iter::Map< 132 | #query_result_zip, 133 | fn( 134 | #query_result_fn_param, 135 | ) -> (#(#tys::QueryRow<'a>),*), 136 | >; 137 | 138 | type ParQueryResult<'a> = rayon::iter::Map< 139 | #par_query_result_zip, 140 | fn( 141 | #query_result_fn_param, 142 | ) -> (#(#tys::QueryRow<'a>),*), 143 | >; 144 | 145 | type QueryRow<'a> = (#(#tys::QueryRow<'a>),*); 146 | 147 | #[inline] 148 | fn reads() -> Vec { 149 | let mut bs = vec![]; 150 | #(bs.extend(#tys::reads());)* 151 | bs 152 | } 153 | 154 | #[inline] 155 | fn writes() -> Vec { 156 | let mut bs = vec![]; 157 | #(bs.extend(#tys::writes());)* 158 | bs 159 | } 160 | 161 | #[inline] 162 | fn lock_columns<'t>(arch: &'t Archetype) -> Self::LockedColumns<'t> { 163 | (#(#tys::lock_columns(arch)),*) 164 | } 165 | 166 | fn extend_locked_columns<'a, 'b>( 167 | (#(#nvars),*): &'b mut Self::LockedColumns<'a>, 168 | (#(#mvars),*): Self::ExtensionColumns, 169 | output_ids: Option<(&mut Vec, &mut usize)>, 170 | ) { 171 | A::extend_locked_columns(n0, m0, output_ids); 172 | #(#extend_impl)* 173 | } 174 | 175 | #[inline] 176 | fn iter_mut<'a, 'b>( 177 | (#(#nvars),*): &'b mut Self::LockedColumns<'a> 178 | ) -> Self::QueryResult<'b> { 179 | #iter_mut_impl 180 | } 181 | 182 | #[inline] 183 | fn iter_one<'a, 'b>( 184 | (#(#nvars),*): &'b mut Self::LockedColumns<'a>, 185 | index: usize, 186 | ) -> Self::QueryResult<'b> { 187 | #iter_one_impl 188 | } 189 | 190 | #[inline] 191 | fn par_iter_mut<'a, 'b>( 192 | len: usize, 193 | (#(#nvars),*): &'b mut Self::LockedColumns<'a> 194 | ) -> Self::ParQueryResult<'b> { 195 | #par_iter_mut_impl 196 | } 197 | } 198 | }; 199 | 200 | isquery_for_tuple.into() 201 | } 202 | 203 | #[proc_macro] 204 | pub fn impl_isbundle_tuple(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 205 | let tuple: TypeTuple = parse_macro_input!(input); 206 | let tys = tuple.elems.iter().collect::>(); 207 | let ns = (0..tys.len()) 208 | .map(|n| syn::Member::Unnamed(n.into())) 209 | .collect::>(); 210 | let isbundle_for_tuple = quote! { 211 | impl <#(#tys),*> apecs::storage::archetype::IsBundle for #tuple 212 | where 213 | #(#tys: Send + Sync + 'static),* 214 | { 215 | type EntryBundle = ( 216 | #(Entry<#tys>),* 217 | ); 218 | type MutBundle = ( 219 | #(&'static mut #tys),* 220 | ); 221 | 222 | fn unordered_types() -> SmallVec<[TypeId; 4]> { 223 | smallvec![ 224 | #(TypeId::of::<#tys>()),* 225 | ] 226 | } 227 | 228 | fn empty_vecs() -> SmallVec<[AnyVec; 4]> { 229 | smallvec![ 230 | #(AnyVec::new::<#tys>()),* 231 | ] 232 | } 233 | 234 | fn try_from_any_bundle(mut bundle: AnyBundle) -> Result { 235 | Ok(( 236 | #(bundle.remove::<#tys>(&TypeId::of::<#tys>())?),* 237 | )) 238 | } 239 | 240 | fn into_vecs(self) -> SmallVec<[AnyVec; 4]> { 241 | smallvec![ 242 | #(AnyVec::wrap(self.#ns)),* 243 | ] 244 | } 245 | 246 | fn into_entry_bundle(self, entity_id: usize) -> Self::EntryBundle { 247 | ( 248 | #(Entry::new(entity_id, self.#ns)),* 249 | ) 250 | } 251 | 252 | fn from_entry_bundle(entry_bundle: Self::EntryBundle) -> Self { 253 | ( 254 | #(entry_bundle.#ns.into_inner()),* 255 | ) 256 | } 257 | 258 | } 259 | }; 260 | 261 | isbundle_for_tuple.into() 262 | } 263 | -------------------------------------------------------------------------------- /crates/apecs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schell/apecs/c45239fc439804438a3cf2da8391362e27e0efdc/crates/apecs/.DS_Store -------------------------------------------------------------------------------- /crates/apecs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "apecs" 3 | version = "0.8.4" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | keywords = ["gamedev", "ecs", "async"] 7 | readme = "../../README.md" 8 | documentation = "https://docs.rs/apecs/" 9 | repository = "https://github.com/schell/apecs" 10 | categories = ["concurrency", "game-engines"] 11 | description = "An asyncronous and parallel entity-component system" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | default = ["derive", "panic_on_async_holdup"] 17 | panic_on_async_holdup = [] 18 | derive = [] 19 | 20 | [lib] 21 | name = "apecs" 22 | path = "src/lib.rs" 23 | bench = false 24 | 25 | [dependencies] 26 | any_vec = "0.10" 27 | apecs-derive = { version = "0.3.0", path = "../apecs-derive" } 28 | async-channel = "1.6" 29 | moongraph = { version = "0.4.3", default-features = false, features = ["parallel"] } 30 | itertools = "0.10" 31 | log = "0.4" 32 | parking_lot = "0.12" 33 | rayon = "1.5" 34 | smallvec = "1.9" 35 | snafu = "0.8" 36 | 37 | [dev-dependencies] 38 | async-broadcast = "0.4" 39 | env_logger = "0.9" 40 | futures-lite = "1.12.0" 41 | smol = "2.0" 42 | wasm-bindgen-test = "0.3" 43 | wasm-bindgen-futures = "0.4" 44 | -------------------------------------------------------------------------------- /crates/apecs/src/entity.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{Any, TypeId}, 3 | collections::VecDeque, 4 | ops::Deref, 5 | sync::Arc, 6 | }; 7 | 8 | use moongraph::GraphError; 9 | 10 | use crate::{world::LazyOp, Entry, IsBundle, IsQuery, World}; 11 | 12 | /// Components associated by a common identifier. 13 | /// 14 | /// Entities are mostly just `usize` identifiers that help locate components, 15 | /// but [`Entity`] comes with some extra conveniences. 16 | /// 17 | /// After an entity is created you can attach and remove 18 | /// bundles of components asynchronously (or "lazily" if not in an async 19 | /// context). 20 | /// ```rust 21 | /// use std::sync::Arc; 22 | /// use apecs::*; 23 | /// 24 | /// let mut world = World::default(); 25 | /// let mut facade = world.facade(); 26 | /// 27 | /// // here we use `smol` but `apecs` works with any async runtime 28 | /// // asynchronously create new entities from within a future 29 | /// let task = smol::spawn(async move { 30 | /// let mut b = facade 31 | /// .visit(|mut ents: ViewMut| { 32 | /// ents.create().with_bundle((123, "entity B", true)) 33 | /// }) 34 | /// .await 35 | /// .unwrap(); 36 | /// // after this await, `b` has its bundle 37 | /// b.updates().await; 38 | /// // visit a component or bundle 39 | /// let did_visit = b 40 | /// .visit::<&&str, ()>(|name| assert_eq!("entity B", *name.value())) 41 | /// .await 42 | /// .is_some(); 43 | /// assert!(did_visit); 44 | /// }); 45 | /// 46 | /// while !task.is_finished() { 47 | /// // tick the world 48 | /// world.tick().unwrap(); 49 | /// // send resources to the facade 50 | /// world.get_facade_schedule().unwrap().run().unwrap(); 51 | /// } 52 | /// ``` 53 | /// Alternatively, if you have access to the [`Components`](crate::Components) resource 54 | /// you can attach components directly and immediately. 55 | pub struct Entity { 56 | id: usize, 57 | gen: usize, 58 | op_sender: async_channel::Sender, 59 | op_receivers: Vec>>, 60 | } 61 | 62 | impl std::fmt::Debug for Entity { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | f.debug_struct("Entity") 65 | .field("id", &self.id) 66 | .field("gen", &self.gen) 67 | .finish() 68 | } 69 | } 70 | 71 | /// You may clone entities, but each one does its own lazy updates, 72 | /// separate from all other clones. 73 | impl Clone for Entity { 74 | fn clone(&self) -> Self { 75 | Self { 76 | id: self.id, 77 | gen: self.gen, 78 | op_sender: self.op_sender.clone(), 79 | op_receivers: Default::default(), 80 | } 81 | } 82 | } 83 | 84 | impl Deref for Entity { 85 | type Target = usize; 86 | 87 | fn deref(&self) -> &Self::Target { 88 | &self.id 89 | } 90 | } 91 | 92 | impl AsRef for Entity { 93 | fn as_ref(&self) -> &usize { 94 | &self.id 95 | } 96 | } 97 | 98 | impl Entity { 99 | pub fn id(&self) -> usize { 100 | self.id 101 | } 102 | 103 | /// Lazily add a component bundle to archetype storage. 104 | /// 105 | /// This entity will have the associated components after the next tick. 106 | pub fn with_bundle(mut self, bundle: B) -> Self { 107 | self.insert_bundle(bundle); 108 | self 109 | } 110 | 111 | /// Lazily add a component bundle to archetype storage. 112 | /// 113 | /// This entity will have the associated components after the next tick. 114 | pub fn insert_bundle(&mut self, bundle: B) { 115 | let id = self.id; 116 | let (tx, rx) = async_channel::bounded(1); 117 | let op = LazyOp { 118 | op: Box::new(move |world: &mut World| { 119 | let all = world.get_components_mut(); 120 | all.insert_bundle(id, bundle); 121 | Ok(Arc::new(()) as Arc) 122 | }), 123 | tx, 124 | }; 125 | self.op_sender 126 | .try_send(op) 127 | .expect("could not send entity op"); 128 | self.op_receivers.push(rx); 129 | } 130 | 131 | /// Lazily add a component bundle to archetype storage. 132 | /// 133 | /// This entity will have the associated component after the next tick. 134 | pub fn insert_component(&mut self, component: T) { 135 | let id = self.id; 136 | let (tx, rx) = async_channel::bounded(1); 137 | let op = LazyOp { 138 | op: Box::new(move |world: &mut World| { 139 | let all = world.get_components_mut(); 140 | let _ = all.insert_component(id, component); 141 | Ok(Arc::new(()) as Arc) 142 | }), 143 | tx, 144 | }; 145 | self.op_sender 146 | .try_send(op) 147 | .expect("could not send entity op"); 148 | self.op_receivers.push(rx); 149 | } 150 | 151 | /// Lazily remove a component bundle from archetype storage. 152 | /// 153 | /// This entity will have lost the associated component after the next tick. 154 | pub fn remove_component(&mut self) { 155 | let id = self.id; 156 | let (tx, rx) = async_channel::bounded(1); 157 | let op = LazyOp { 158 | op: Box::new(move |world: &mut World| { 159 | let all = world.get_components_mut(); 160 | let _ = all.remove_component::(id); 161 | Ok(Arc::new(()) as Arc) 162 | }), 163 | tx, 164 | }; 165 | // UNWRAP: safe because this channel is unbounded 166 | self.op_sender.try_send(op).unwrap(); 167 | self.op_receivers.push(rx); 168 | } 169 | 170 | /// Await a future that completes after all lazy updates have been 171 | /// performed. 172 | pub async fn updates(&mut self) { 173 | let updates: Vec>> = 174 | std::mem::take(&mut self.op_receivers); 175 | for update in updates.into_iter() { 176 | // TODO: explain why this unwrap is safe... 177 | let _ = update.recv().await.unwrap(); 178 | } 179 | } 180 | 181 | /// Visit this entity's query row in storage, if it exists. 182 | /// 183 | /// ## Panics 184 | /// Panics if the query is malformed (the bundle is not unique). 185 | pub async fn visit( 186 | &self, 187 | f: impl FnOnce(Q::QueryRow<'_>) -> T + Send + Sync + 'static, 188 | ) -> Option { 189 | let id = self.id(); 190 | let (tx, rx) = async_channel::bounded(1); 191 | // UNWRAP: safe because this channel is unbounded 192 | self.op_sender 193 | .try_send(LazyOp { 194 | op: Box::new(move |world: &mut World| -> Result<_, GraphError> { 195 | let storage = world.get_components_mut(); 196 | let mut q = storage.query::(); 197 | Ok(Arc::new(q.find_one(id).map(f)) as Arc) 198 | }), 199 | tx, 200 | }) 201 | .unwrap(); 202 | let arc: Arc = rx 203 | .recv() 204 | .await 205 | .map_err(|_| log::error!("could not receive get request")) 206 | .unwrap(); 207 | let arc_c: Arc> = arc 208 | .downcast() 209 | .map_err(|_| log::error!("could not downcast '{}'", std::any::type_name::())) 210 | .unwrap(); 211 | let c: Option = Arc::try_unwrap(arc_c) 212 | .map_err(|_| log::error!("could not unwrap '{}'", std::any::type_name::())) 213 | .unwrap(); 214 | c 215 | } 216 | } 217 | 218 | /// Creates, destroys and recycles entities. 219 | /// 220 | /// The most common reason to interact with `Entities` is to 221 | /// [`create`](Entities::create) or [`destroy`](Entities::destroy) an 222 | /// [`Entity`]. 223 | /// ``` 224 | /// # use apecs::*; 225 | /// let mut world = World::default(); 226 | /// let entities = world.get_entities_mut(); 227 | /// let ent = entities.create(); 228 | /// entities.destroy(ent); 229 | /// ``` 230 | pub struct Entities { 231 | pub(crate) next_k: usize, 232 | pub(crate) generations: Vec, 233 | pub(crate) recycle: Vec, 234 | // unbounded 235 | pub(crate) delete_tx: async_channel::Sender, 236 | // unbounded 237 | pub(crate) delete_rx: async_channel::Receiver, 238 | pub(crate) deleted: VecDeque<(u64, Vec<(usize, smallvec::SmallVec<[TypeId; 4]>)>)>, 239 | // unbounded 240 | pub(crate) lazy_op_sender: async_channel::Sender, 241 | } 242 | 243 | impl Default for Entities { 244 | fn default() -> Self { 245 | let (delete_tx, delete_rx) = async_channel::unbounded(); 246 | Self { 247 | next_k: Default::default(), 248 | generations: vec![], 249 | recycle: Default::default(), 250 | delete_rx, 251 | delete_tx, 252 | deleted: Default::default(), 253 | lazy_op_sender: async_channel::unbounded().0, 254 | } 255 | } 256 | } 257 | 258 | impl Entities { 259 | pub(crate) fn new(lazy_op_sender: async_channel::Sender) -> Self { 260 | Self { 261 | lazy_op_sender, 262 | ..Default::default() 263 | } 264 | } 265 | 266 | fn dequeue(&mut self) -> usize { 267 | if let Some(id) = self.recycle.pop() { 268 | self.generations[id] += 1; 269 | id 270 | } else { 271 | let id = self.next_k; 272 | self.generations.push(0); 273 | self.next_k += 1; 274 | id 275 | } 276 | } 277 | 278 | /// Return an iterator over all alive entities. 279 | pub fn alive_iter(&self) -> impl Iterator + '_ { 280 | self.generations 281 | .iter() 282 | .enumerate() 283 | .filter_map(|(id, _gen)| self.hydrate(id)) 284 | } 285 | 286 | /// Returns the number of entities that are currently alive. 287 | pub fn alive_len(&self) -> usize { 288 | self.generations.len() - self.recycle.len() 289 | } 290 | 291 | /// Create many entities at once, returning a list of their ids. 292 | /// 293 | /// An [`Entity`] can be made from its `usize` id using `Entities::hydrate`. 294 | pub fn create_many(&mut self, mut how_many: usize) -> Vec { 295 | let mut ids = vec![]; 296 | while let Some(id) = self.recycle.pop() { 297 | self.generations[id] += 1; 298 | ids.push(id); 299 | how_many -= 1; 300 | if how_many == 0 { 301 | return ids; 302 | } 303 | } 304 | 305 | let last_id = self.next_k + (how_many - 1); 306 | self.generations.resize_with(last_id, || 0); 307 | ids.extend(self.next_k..=last_id); 308 | self.next_k = last_id + 1; 309 | ids 310 | } 311 | 312 | /// Create one entity and return it. 313 | pub fn create(&mut self) -> Entity { 314 | let id = self.dequeue(); 315 | Entity { 316 | id, 317 | gen: self.generations[id], 318 | op_sender: self.lazy_op_sender.clone(), 319 | op_receivers: Default::default(), 320 | } 321 | } 322 | 323 | /// Lazily destroy an entity, removing its components and recycling it 324 | /// at the end of this tick. 325 | /// 326 | /// ## NOTE: 327 | /// Destroyed entities will have their components removed 328 | /// automatically during upkeep, which happens each `World::tick_lazy`. 329 | pub fn destroy(&self, mut entity: Entity) { 330 | entity.op_receivers = Default::default(); 331 | self.delete_tx.try_send(entity.id()).unwrap(); 332 | } 333 | 334 | /// Destroys all entities. 335 | pub fn destroy_all(&mut self) { 336 | for id in 0..self.next_k { 337 | if let Some(entity) = self.hydrate(id) { 338 | self.destroy(entity); 339 | } 340 | } 341 | } 342 | 343 | /// Produce an iterator of deleted entities as entries. 344 | /// 345 | /// This iterator should be filtered at the callsite for the latest changed 346 | /// entries since a stored iteration timestamp. 347 | pub fn deleted_iter(&self) -> impl Iterator> + '_ { 348 | self.deleted.iter().flat_map(|(changed, ids)| { 349 | ids.iter().map(|(id, _)| Entry { 350 | value: (), 351 | key: *id, 352 | changed: *changed, 353 | added: true, 354 | }) 355 | }) 356 | } 357 | 358 | /// Produce an iterator of deleted entities that had a component of the 359 | /// given type, as entries. 360 | /// 361 | /// This iterator should be filtered at the callsite for the latest changed 362 | /// entries since a stored iteration timestamp. 363 | pub fn deleted_iter_of(&self) -> impl Iterator> + '_ { 364 | let ty = TypeId::of::>(); 365 | self.deleted.iter().flat_map(move |(changed, ids)| { 366 | ids.iter().filter_map(move |(id, tys)| { 367 | if tys.contains(&ty) { 368 | Some(Entry { 369 | value: (), 370 | key: *id, 371 | changed: *changed, 372 | added: true, 373 | }) 374 | } else { 375 | None 376 | } 377 | }) 378 | }) 379 | } 380 | 381 | /// Hydrate an `Entity` from an id. 382 | /// 383 | /// Returns `None` the entity with the given id does not exist, or has 384 | /// been destroyed. 385 | pub fn hydrate(&self, entity_id: usize) -> Option { 386 | if self.recycle.contains(&entity_id) { 387 | return None; 388 | } 389 | let gen = self.generations.get(entity_id)?; 390 | Some(Entity { 391 | id: entity_id, 392 | gen: *gen, 393 | op_sender: self.lazy_op_sender.clone(), 394 | op_receivers: Default::default(), 395 | }) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /crates/apecs/src/facade.rs: -------------------------------------------------------------------------------- 1 | //! Access the [`World`]s resources from async futures. 2 | use std::{any::Any, sync::Arc}; 3 | 4 | use moongraph::{Edges, Function, GraphError, Node, Resource, TypeKey, TypeMap}; 5 | 6 | use crate::{world::LazyOp, World}; 7 | 8 | pub(crate) struct Request { 9 | pub(crate) reads: Vec, 10 | pub(crate) writes: Vec, 11 | pub(crate) moves: Vec, 12 | pub(crate) prepare: fn(&mut TypeMap) -> Result, 13 | pub(crate) deploy_tx: async_channel::Sender, 14 | } 15 | 16 | impl From for Node { 17 | fn from( 18 | Request { 19 | reads, 20 | writes, 21 | moves, 22 | prepare, 23 | deploy_tx, 24 | }: Request, 25 | ) -> Self { 26 | Node::new(Function::new( 27 | prepare, 28 | move |rez| { 29 | // Ignore any sending errors as the requesting async could have been 30 | // dropped while awaiting, which is perfectly legal. 31 | let _ = deploy_tx.try_send(rez); 32 | Err(GraphError::TrimNode) 33 | }, 34 | |_, _| Ok(()), 35 | )) 36 | .with_reads(reads) 37 | .with_writes(writes) 38 | .with_moves(moves) 39 | } 40 | } 41 | 42 | /// A [`Facade`] visits world resources from async futures. 43 | /// 44 | /// A facade is a window into the world, by which an async future can interact 45 | /// with the world's resources through [`Facade::visit`], without causing resource 46 | /// contention with each other or the world's systems. 47 | #[derive(Clone)] 48 | pub struct Facade { 49 | // Unbounded. Sending a request from the facade will not yield the future. 50 | // In other words, it will not cause the async to stop at an await point. 51 | pub(crate) request_tx: async_channel::Sender, 52 | pub(crate) lazy_tx: async_channel::Sender, 53 | } 54 | 55 | impl Facade { 56 | /// Asyncronously visit system resources using a closure. 57 | /// 58 | /// The closure may return data to the caller. 59 | /// 60 | /// **Note**: Using a closure ensures that no fetched system resources are 61 | /// held over an await point, which would preclude world systems and other 62 | /// [`Facade`]s from accessing them and susequently being able to run. 63 | pub async fn visit( 64 | &mut self, 65 | f: impl FnOnce(D) -> T, 66 | ) -> Result { 67 | // request the resources from the world 68 | let (deploy_tx, deploy_rx) = async_channel::bounded(1); 69 | let reads = D::reads(); 70 | let writes = D::writes(); 71 | let moves = D::moves(); 72 | // UNWRAP: safe because the request channel is unbounded 73 | self.request_tx 74 | .try_send(Request { 75 | reads, 76 | writes, 77 | moves, 78 | prepare: |resources: &mut TypeMap| { 79 | log::debug!( 80 | "request got resources - constructing {}", 81 | std::any::type_name::() 82 | ); 83 | let my_d = D::construct(resources)?; 84 | let my_d_in_a_box: Box = Box::new(my_d); 85 | Ok(my_d_in_a_box) 86 | }, 87 | deploy_tx, 88 | }) 89 | .unwrap(); 90 | let rez: Resource = deploy_rx.recv().await.map_err(GraphError::other)?; 91 | // UNWRAP: safe because we know we will only receive the type we expect. 92 | let box_d: Box = rez.downcast().unwrap(); 93 | let d = *box_d; 94 | let t = f(d); 95 | log::debug!("request for {} done", std::any::type_name::()); 96 | Ok(t) 97 | } 98 | 99 | /// Return the total number of facades. 100 | pub fn count(&self) -> usize { 101 | self.request_tx.sender_count() 102 | } 103 | 104 | pub async fn visit_world_mut( 105 | &mut self, 106 | f: impl FnOnce(&mut World) -> T + Send + Sync + 'static, 107 | ) -> Result 108 | where 109 | T: Any + Send + Sync, 110 | { 111 | let (tx, rx) = async_channel::bounded(1); 112 | // UNWRAP: safe because this channel is unbounded 113 | self.lazy_tx 114 | .try_send(LazyOp { 115 | op: Box::new(|world| { 116 | let t = f(world); 117 | let any_t = Arc::new(t); 118 | Ok(any_t) 119 | }), 120 | tx, 121 | }) 122 | .unwrap(); 123 | let any_t: Arc<_> = rx.recv().await.map_err(GraphError::other)?; 124 | // UNWRAP: safe because we know we will only receive the type we expect, as we packed it 125 | // in the closure above. 126 | let arc_t: Arc = any_t.downcast().unwrap(); 127 | // UNWRAP: safe because we know nothing has cloned this arc 128 | Ok(Arc::try_unwrap(arc_t).unwrap_or_else(|_| unreachable!("something cloned the arc"))) 129 | } 130 | } 131 | 132 | /// A fulfillment schedule of requests for world resources, 133 | /// coming from all the [`World`](crate::World)'s [`Facade`]s. 134 | pub struct FacadeSchedule<'a> { 135 | pub(crate) batches: moongraph::Batches<'a>, 136 | } 137 | 138 | impl<'a> FacadeSchedule<'a> { 139 | /// Send out resources for the next batch of the schedule and return `true` when 140 | /// **either** of these conditions are met: 141 | /// * request batches are still queued 142 | /// * resources are still loaned 143 | pub fn tick(&mut self) -> Result { 144 | // try to unify 145 | let resources_unified = self.batches.unify(); 146 | if !resources_unified { 147 | // we can't run a new batch without unified resources 148 | log::trace!("cannot run next async request batch - resources still on loan"); 149 | return Ok(true); 150 | } else { 151 | log::trace!("ready to run next async request batch"); 152 | } 153 | 154 | if let Some(batch) = self.batches.next_batch() { 155 | let mut local: Option Result> = None; 156 | let results = batch.run(&mut local)?; 157 | results.save(true, false)?; 158 | Ok(true) 159 | } else { 160 | log::trace!("async request batches exhausted"); 161 | Ok(false) 162 | } 163 | } 164 | 165 | /// Run the schedule by calling [`FacadeSchedule::tick`] in a loop until all 166 | /// requests are fulfilled and system resources are unified. 167 | pub fn run(&mut self) -> Result<(), GraphError> { 168 | while self.tick()? {} 169 | Ok(()) 170 | } 171 | 172 | /// Return the number of batches left in the schedule. 173 | pub fn len(&self) -> usize { 174 | self.batches.len() 175 | } 176 | 177 | pub fn is_empty(&self) -> bool { 178 | self.len() == 0 179 | } 180 | 181 | /// Attempt to unify resources, returning `true` if resources are unified, `false` if not. 182 | pub fn unify(&mut self) -> bool { 183 | self.batches.unify() 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /crates/apecs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Welcome to the *A*syncronous and *P*leasant *E*ntity *C*omponent *S*ystem 😊 2 | //! 3 | //! `apecs` is a flexible and well-performing entity-component-system that plays nice with async runtimes. 4 | //! 5 | //! It's best to start learning about `apecs` from the [`World`], but if you 6 | //! just want some examples please check out the [readme](https://github.com/schell/apecs#readme) 7 | #![allow(clippy::type_complexity)] 8 | 9 | mod entity; 10 | mod facade; 11 | mod storage; 12 | mod world; 13 | 14 | pub use apecs_derive::Edges; 15 | pub use entity::*; 16 | pub use facade::{Facade, FacadeSchedule}; 17 | pub use moongraph::{ 18 | end, err, graph, ok, Edges, Graph, GraphError, Move, NodeResults, TypeKey, TypeMap, View, 19 | ViewMut, NoDefault, 20 | }; 21 | pub use storage::{ 22 | Components, Entry, IsBundle, IsQuery, LazyComponents, Maybe, MaybeMut, MaybeRef, Mut, Query, 23 | QueryGuard, QueryIter, Ref, Without, 24 | }; 25 | pub use world::{current_iteration, Parallelism, World}; 26 | 27 | #[cfg(doctest)] 28 | pub mod doctest { 29 | #[doc = include_str!("../../../README.md")] 30 | pub struct ReadmeDoctests; 31 | } 32 | -------------------------------------------------------------------------------- /crates/apecs/src/storage/archetype/bundle.rs: -------------------------------------------------------------------------------- 1 | //! Bundle trait for decomposing component tuples. 2 | use std::any::TypeId; 3 | 4 | use any_vec::AnyVec; 5 | use smallvec::{smallvec, SmallVec}; 6 | use snafu::prelude::*; 7 | 8 | use crate as apecs; 9 | use crate::storage::Entry; 10 | 11 | /// Component bundle errors. 12 | #[derive(Debug, Snafu)] 13 | pub enum BundleError { 14 | #[snafu(display("Attempted to allocate entity with duplicate components"))] 15 | DuplicateComponent, 16 | 17 | #[snafu(display("Type info is unsorted"))] 18 | Unsorted, 19 | 20 | #[snafu(display("No index of {type_name}"))] 21 | MissingIndex { type_name: &'static str }, 22 | 23 | #[snafu(display("Could not downcast to {type_name}"))] 24 | Downcast { type_name: &'static str }, 25 | 26 | #[snafu(display("Missing '{type_name}' component"))] 27 | MissingComponent { type_name: &'static str }, 28 | } 29 | 30 | pub(crate) trait AnyVecExt: Sized { 31 | fn anyvec_from_iter(iter: I) -> Self 32 | where 33 | C: Send + Sync + 'static, 34 | I: IntoIterator, 35 | I::IntoIter: ExactSizeIterator; 36 | 37 | fn wrap(c: impl Send + Sync + Sized + 'static) -> Self { 38 | Self::anyvec_from_iter(Some(c)) 39 | } 40 | } 41 | 42 | impl AnyVecExt for AnyVec { 43 | fn anyvec_from_iter(iter: I) -> Self 44 | where 45 | C: Send + Sync + 'static, 46 | I: IntoIterator, 47 | I::IntoIter: ExactSizeIterator, 48 | { 49 | let iter = iter.into_iter(); 50 | let mut anyvec: Self = AnyVec::with_capacity::(iter.len()); 51 | { 52 | let mut vs = anyvec.downcast_mut().unwrap(); 53 | for c in iter { 54 | vs.push(c); 55 | } 56 | } 57 | anyvec 58 | } 59 | } 60 | 61 | /// Provides runtime type info about bundles and more. 62 | pub trait IsBundle: Sized { 63 | /// A bundle where each of this bundle's types are wrapped in `Entry`. 64 | type EntryBundle: IsBundle; 65 | /// A bundle where each of this bundle's types are prefixed with &'static 66 | /// mut. 67 | type MutBundle: IsBundle; 68 | 69 | /// Produces a list of types of a bundle, ordered by their 70 | /// position in the tuple. 71 | fn unordered_types() -> SmallVec<[TypeId; 4]>; 72 | 73 | /// Produces a list of types in a bundle, ordered ascending. 74 | /// 75 | /// Errors if the bundle contains duplicate types. 76 | fn ordered_types() -> Result, BundleError> { 77 | let mut types = Self::unordered_types(); 78 | types.sort(); 79 | ensure_type_info(&types)?; 80 | Ok(types) 81 | } 82 | 83 | /// Produces an unordered list of empty `AnyVec`s to store 84 | /// components, with the given capacity. 85 | fn empty_vecs() -> SmallVec<[AnyVec; 4]>; 86 | 87 | fn empty_any_bundle() -> Result { 88 | let types = Self::ordered_types()?; 89 | let mut vecs = Self::empty_vecs(); 90 | vecs.sort_by(|a, b| a.element_typeid().cmp(&b.element_typeid())); 91 | Ok(AnyBundle(types, vecs)) 92 | } 93 | 94 | /// Produces a list of `AnyVec`s of length 1, each containing 95 | /// its corresponding component. The list is ordered by each component's 96 | /// position in the tuple. 97 | fn into_vecs(self) -> SmallVec<[AnyVec; 4]>; 98 | 99 | /// Converts the tuple into a type-erased `AnyBundle`. 100 | /// 101 | /// ## Errs 102 | /// Errs if the tuple contains duplicate types. 103 | fn try_into_any_bundle(self) -> Result { 104 | let types = Self::ordered_types()?; 105 | let mut vecs = self.into_vecs(); 106 | vecs.sort_by(|a, b| a.element_typeid().cmp(&b.element_typeid())); 107 | Ok(AnyBundle(types, vecs)) 108 | } 109 | 110 | fn try_from_any_bundle(bundle: AnyBundle) -> Result; 111 | 112 | fn into_entry_bundle(self, entity_id: usize) -> Self::EntryBundle; 113 | 114 | fn from_entry_bundle(entry_bundle: Self::EntryBundle) -> Self; 115 | } 116 | 117 | impl IsBundle for (A,) { 118 | type EntryBundle = (Entry,); 119 | type MutBundle = (&'static mut A,); 120 | 121 | fn unordered_types() -> SmallVec<[TypeId; 4]> { 122 | smallvec![TypeId::of::()] 123 | } 124 | 125 | fn empty_vecs() -> SmallVec<[AnyVec; 4]> { 126 | smallvec![AnyVec::new::()] 127 | } 128 | 129 | fn into_vecs(self) -> SmallVec<[AnyVec; 4]> { 130 | smallvec![AnyVec::wrap(self.0)] 131 | } 132 | 133 | fn try_from_any_bundle(mut bundle: AnyBundle) -> Result { 134 | Ok((bundle.remove::(&TypeId::of::())?,)) 135 | } 136 | 137 | fn into_entry_bundle(self, entity_id: usize) -> Self::EntryBundle { 138 | (Entry::new(entity_id, self.0),) 139 | } 140 | 141 | fn from_entry_bundle(entry_bundle: Self::EntryBundle) -> Self { 142 | (entry_bundle.0.into_inner(),) 143 | } 144 | } 145 | 146 | apecs_derive::impl_isbundle_tuple!((A, B)); 147 | apecs_derive::impl_isbundle_tuple!((A, B, C)); 148 | apecs_derive::impl_isbundle_tuple!((A, B, C, D)); 149 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E)); 150 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F)); 151 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G)); 152 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G, H)); 153 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G, H, I)); 154 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G, H, I, J)); 155 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G, H, I, J, K)); 156 | apecs_derive::impl_isbundle_tuple!((A, B, C, D, E, F, G, H, I, J, K, L)); 157 | 158 | fn ensure_type_info(types: &[TypeId]) -> Result<(), BundleError> { 159 | for x in types.windows(2) { 160 | match x[0].cmp(&x[1]) { 161 | core::cmp::Ordering::Less => (), 162 | core::cmp::Ordering::Equal => return DuplicateComponentSnafu.fail(), 163 | core::cmp::Ordering::Greater => return UnsortedSnafu.fail(), 164 | } 165 | } 166 | Ok(()) 167 | } 168 | 169 | /// A type erased bundle of components. It is assumed that the elements are 170 | /// ordered by typeid (ascending) at all times. 171 | #[derive(Default)] 172 | pub struct AnyBundle( 173 | pub SmallVec<[TypeId; 4]>, 174 | pub SmallVec<[AnyVec; 4]>, 175 | ); 176 | 177 | impl AnyBundle { 178 | /// Insert the component vec at the given type 179 | pub fn insert( 180 | &mut self, 181 | ty: TypeId, 182 | mut comp: AnyVec, 183 | ) -> Option> { 184 | for (i, t) in self.0.clone().into_iter().enumerate() { 185 | if ty < t { 186 | self.0.insert(i, ty); 187 | self.1.insert(i, comp); 188 | return None; 189 | } else if ty == t { 190 | std::mem::swap(&mut self.1[i], &mut comp); 191 | return Some(comp); 192 | } 193 | } 194 | 195 | self.0.push(ty); 196 | self.1.push(comp); 197 | None 198 | } 199 | 200 | pub fn is_empty(&self) -> bool { 201 | self.0.is_empty() 202 | } 203 | 204 | /// Merge the given bundle into `self`, returning any key+values in 205 | /// `self` that match `other`. 206 | pub fn union(&mut self, other: AnyBundle) -> Option { 207 | let mut to_caller = AnyBundle(smallvec![], smallvec![]); 208 | for (ty, v) in other.0.into_iter().zip(other.1.into_iter()) { 209 | if let Some(w) = self.insert(ty, v) { 210 | to_caller.insert(ty, w); 211 | } 212 | } 213 | if to_caller.is_empty() { 214 | None 215 | } else { 216 | Some(to_caller) 217 | } 218 | } 219 | 220 | fn index_of(&self, ty: &TypeId) -> Option { 221 | for (i, t) in self.0.iter().enumerate() { 222 | if ty == t { 223 | return Some(i); 224 | } 225 | } 226 | None 227 | } 228 | 229 | /// Remove a type from the bundle and return it as an `AnyBundle`. 230 | pub fn remove_any(&mut self, ty: &TypeId) -> Option { 231 | let index = self.index_of(ty)?; 232 | let ty = self.0.remove(index); 233 | let elem = self.1.remove(index); 234 | Some(AnyBundle(smallvec![ty], smallvec![elem])) 235 | } 236 | 237 | /// Remove a component from the bundle and return it. 238 | pub fn remove(&mut self, ty: &TypeId) -> Result { 239 | // find the index 240 | let index = self.index_of(ty).with_context(|| MissingIndexSnafu { 241 | type_name: std::any::type_name::(), 242 | })?; 243 | // remove the type 244 | let _ = self.0.remove(index); 245 | // remove the component type-erased vec 246 | let mut any_head_vec = self.1.remove(index); 247 | // remove the component from the vec 248 | let mut head_vec = any_head_vec 249 | .downcast_mut::() 250 | .with_context(|| DowncastSnafu { 251 | type_name: std::any::type_name::(), 252 | })?; 253 | let head = head_vec.pop().with_context(|| MissingComponentSnafu { 254 | type_name: std::any::type_name::(), 255 | })?; 256 | Ok(head) 257 | } 258 | 259 | /// Get the type info of the bundle 260 | pub fn type_info(&self) -> &[TypeId] { 261 | &self.0 262 | } 263 | 264 | pub fn try_into_tuple(self) -> Result { 265 | B::try_from_any_bundle(self) 266 | } 267 | 268 | pub fn try_from_tuple(tuple: B) -> Result { 269 | tuple.try_into_any_bundle() 270 | } 271 | } 272 | 273 | #[cfg(test)] 274 | mod test { 275 | use super::*; 276 | 277 | #[test] 278 | fn bundle_union_returns_prev() { 279 | let mut left = AnyBundle::try_from_tuple(("one".to_string(), 1.0f32, true)).unwrap(); 280 | let right = AnyBundle::try_from_tuple(("two".to_string(), false)).unwrap(); 281 | let leftover: AnyBundle = left.union(right).unwrap(); 282 | let (a, b) = leftover.try_into_tuple::<(String, bool)>().unwrap(); 283 | assert_eq!("one", a.as_str()); 284 | assert_eq!(b, true); 285 | 286 | let (a, b, c) = left.try_into_tuple::<(String, f32, bool)>().unwrap(); 287 | assert_eq!("two", &a); 288 | assert_eq!(1.0, b); 289 | assert_eq!(false, c); 290 | } 291 | 292 | #[test] 293 | fn bundle_type_info() { 294 | let bundle = (0.0f32,); 295 | let any = AnyBundle::try_from_tuple(bundle).unwrap(); 296 | assert_eq!(&[TypeId::of::()], any.0.as_slice()); 297 | } 298 | 299 | #[test] 300 | fn empty_bundle_type_info() { 301 | let bundle = <(f32, bool, String)>::empty_any_bundle().unwrap(); 302 | assert!(!bundle.0.is_empty()); 303 | let tys = <(f32, bool, String)>::ordered_types().unwrap(); 304 | assert_eq!(tys, bundle.0); 305 | } 306 | 307 | #[test] 308 | fn basic_type_info() { 309 | let tys = <(f32,)>::ordered_types().unwrap(); 310 | assert_eq!(&[TypeId::of::()], tys.as_slice()); 311 | 312 | let tys_a = <(f32, bool, String)>::ordered_types().unwrap(); 313 | assert!(!tys_a.is_empty(), "types are empty"); 314 | 315 | let tys_b = <(bool, f32, String)>::ordered_types().unwrap(); 316 | assert_eq!(tys_a, tys_b); 317 | assert!(tys_a == tys_b); 318 | } 319 | 320 | #[test] 321 | fn bundle_types_match_component_vec_types() { 322 | let anybundle = (0.0f32, false, "abc").try_into_any_bundle().unwrap(); 323 | for (ty, vec) in anybundle.0.into_iter().zip(anybundle.1.into_iter()) { 324 | assert_eq!(ty, vec.element_typeid()); 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /crates/apecs/src/storage/archetype/mod.rs: -------------------------------------------------------------------------------- 1 | //! Components stored together in contiguous arrays. 2 | mod archetype; 3 | mod bundle; 4 | mod components; 5 | mod query; 6 | 7 | pub use archetype::*; 8 | pub use bundle::*; 9 | pub use components::*; 10 | pub use query::*; 11 | -------------------------------------------------------------------------------- /crates/apecs/src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | //! Component storage. 2 | //! 3 | //! APECs provides an archetypal storage strategy. 4 | //! To read more about the difference between separated and archetypal storage 5 | //! check out [this article](https://csherratt.github.io/blog/posts/specs-and-legion/). 6 | mod archetype; 7 | pub use archetype::*; 8 | -------------------------------------------------------------------------------- /crates/apecs/tests/regression.rs: -------------------------------------------------------------------------------- 1 | //! Tests for bugs we've encountered. 2 | use apecs::{graph, ok, Facade, Graph, GraphError, View, ViewMut, World}; 3 | 4 | struct Channel { 5 | tx: async_broadcast::Sender<()>, 6 | rx: async_broadcast::Receiver<()>, 7 | ok_to_drop: bool, 8 | } 9 | 10 | impl Default for Channel { 11 | fn default() -> Self { 12 | let (tx, rx) = async_broadcast::broadcast(3); 13 | Channel { 14 | tx, 15 | rx, 16 | ok_to_drop: false, 17 | } 18 | } 19 | } 20 | 21 | #[test] 22 | fn system_batch_drops_resources_after_racing_asyncs() { 23 | // ensures that resources are dropped after being sent to 24 | // an async that is awaiting Facade::visit, but then gets 25 | // cancelled 26 | 27 | let _ = env_logger::builder() 28 | .is_test(true) 29 | .filter_level(log::LevelFilter::Trace) 30 | .try_init(); 31 | 32 | // * each tick this increments a counter by 1 33 | // * when the counter reaches 3 it fires an event 34 | fn ticker((chan, mut tick): (ViewMut, ViewMut)) -> Result<(), GraphError> { 35 | *tick += 1; 36 | log::info!("ticked {}", *tick); 37 | if *tick == 3 { 38 | chan.tx.try_broadcast(()).unwrap(); 39 | } else if *tick > 100 { 40 | panic!("really shouldn't have taken this long"); 41 | } 42 | ok() 43 | } 44 | 45 | // This function infinitely loops, acquiring a unit resource and reading it 46 | async fn loser(facade: &mut Facade) { 47 | log::info!("running loser"); 48 | loop { 49 | log::info!("loser awaiting View<()>"); 50 | facade 51 | .visit(|unit: View<()>| { 52 | log::info!("loser got View<()>"); 53 | let () = *unit; 54 | }) 55 | .await 56 | .unwrap(); 57 | } 58 | } 59 | 60 | // * races the losing async against awaiting an event from ticker 61 | // * after ticker wins the race, we should be able to access the unit 62 | // resource, because the async executor has dropped the future which 63 | // required it 64 | async fn race(mut facade: Facade) { 65 | log::info!("starting the race, awaiting View>"); 66 | let mut rx = facade 67 | .visit(|chan: View| chan.rx.clone()) 68 | .await 69 | .unwrap(); 70 | log::info!("race got View> and is done with it"); 71 | { 72 | futures_lite::future::or( 73 | async { 74 | rx.recv().await.expect("tx was dropped"); 75 | }, 76 | async { 77 | loser(&mut facade).await; 78 | panic!("this was supposed to lose the race"); 79 | }, 80 | ) 81 | .await; 82 | } 83 | 84 | log::info!("race is over"); 85 | 86 | facade 87 | .visit(|(unit, mut channel): (View<()>, ViewMut)| { 88 | let () = *unit; 89 | channel.ok_to_drop = true; 90 | }) 91 | .await 92 | .unwrap(); 93 | 94 | log::info!("async exiting"); 95 | } 96 | 97 | let mut world = World::default(); 98 | let facade = world.facade(); 99 | let task = smol::spawn(race(facade)); 100 | world.add_subgraph(graph!(ticker)); 101 | 102 | while !task.is_finished() { 103 | println!("tick"); 104 | world.tick().unwrap(); 105 | println!("tock"); 106 | world.get_facade_schedule().unwrap().run().unwrap(); 107 | } 108 | log::info!("executor is empty, ending the test"); 109 | } 110 | 111 | #[test] 112 | fn readme() {} 113 | -------------------------------------------------------------------------------- /crates/apecs/tests/unit.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | 3 | #[derive(Default)] 4 | struct MyMap(std::collections::HashMap); 5 | 6 | #[derive(Default)] 7 | struct Number(u32); 8 | 9 | #[test] 10 | fn can_closure_system() { 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | struct F32s(f32, f32); 13 | 14 | #[derive(Edges)] 15 | struct StatefulSystemData { 16 | positions: Query<&'static F32s>, 17 | } 18 | 19 | fn mk_stateful_system( 20 | tx: async_channel::Sender, 21 | ) -> impl FnMut(StatefulSystemData) -> Result<(), GraphError> { 22 | println!("making stateful system"); 23 | let mut highest_pos: F32s = F32s(0.0, f32::NEG_INFINITY); 24 | 25 | move |data: StatefulSystemData| { 26 | println!("running stateful system: highest_pos:{:?}", highest_pos); 27 | for pos in data.positions.query().iter_mut() { 28 | if pos.1 > highest_pos.1 { 29 | highest_pos = *pos.value(); 30 | println!("set new highest_pos: {:?}", highest_pos); 31 | } 32 | } 33 | 34 | println!("sending highest_pos: {:?}", highest_pos); 35 | tx.try_send(highest_pos).map_err(GraphError::other)?; 36 | 37 | Ok(()) 38 | } 39 | } 40 | 41 | let (tx, rx) = async_channel::bounded(1); 42 | 43 | let mut world = World::default(); 44 | let stateful = mk_stateful_system(tx); 45 | world.add_subgraph(graph!(stateful)); 46 | world 47 | .visit(|mut archset: ViewMut| { 48 | archset.insert_component(0, F32s(20.0, 30.0)); 49 | archset.insert_component(1, F32s(0.0, 0.0)); 50 | archset.insert_component(2, F32s(100.0, 100.0)); 51 | }) 52 | .unwrap(); 53 | 54 | world.tock(); 55 | 56 | let highest = rx.try_recv().unwrap(); 57 | assert_eq!(F32s(100.0, 100.0), highest); 58 | } 59 | 60 | #[test] 61 | fn async_run_and_return_resources() { 62 | let _ = env_logger::builder() 63 | .is_test(true) 64 | .filter_level(log::LevelFilter::Trace) 65 | .try_init(); 66 | 67 | fn maintain_map(mut data: (Query<(&String, &u32)>, ViewMut)) -> Result<(), GraphError> { 68 | for (name, number) in data.0.query().iter_mut() { 69 | if !data.1 .0.contains_key(name.value()) { 70 | let _ = data.1 .0.insert(name.to_string(), *number.value()); 71 | } 72 | } 73 | 74 | Ok(()) 75 | } 76 | 77 | let (tx, rx) = async_channel::bounded(1); 78 | let mut world = World::default(); 79 | let mut facade = world.facade(); 80 | 81 | let task = smol::spawn(async move { 82 | log::info!("create running"); 83 | tx.try_send(()).unwrap(); 84 | facade 85 | .visit( 86 | |(mut entities, mut archset): (ViewMut, ViewMut)| { 87 | for n in 0..100u32 { 88 | let e = entities.create(); 89 | archset.insert_bundle(e.id(), (format!("entity_{}", n), n)); 90 | } 91 | }, 92 | ) 93 | .await 94 | .unwrap(); 95 | log::info!("async done"); 96 | }); 97 | 98 | world.add_subgraph(graph!(maintain_map)); 99 | 100 | // should tick once and yield for the facade request 101 | world.run().unwrap(); 102 | rx.try_recv().unwrap(); 103 | log::info!("received"); 104 | 105 | while !task.is_finished() { 106 | // world sends resources to the "create" async which makes 107 | // entities+components 108 | { 109 | let mut facade_schedule = world.get_facade_schedule().unwrap(); 110 | loop { 111 | let tick_again = facade_schedule.tick().unwrap(); 112 | if !tick_again { 113 | log::info!("schedule exhausted"); 114 | break; 115 | } 116 | } 117 | } 118 | 119 | world.tock(); 120 | } 121 | 122 | world 123 | .visit(|book: View| { 124 | for n in 0..100 { 125 | assert_eq!(book.0.get(&format!("entity_{}", n)), Some(&n)); 126 | } 127 | }) 128 | .unwrap(); 129 | } 130 | 131 | #[test] 132 | fn can_create_entities_and_build_convenience() { 133 | struct DataA(f32); 134 | struct DataB(f32); 135 | 136 | let mut world = World::default(); 137 | assert!(world.contains_resource::(), "missing entities"); 138 | let e = world 139 | .get_entities_mut() 140 | .create() 141 | .with_bundle((DataA(0.0), DataB(0.0))); 142 | let id = e.id(); 143 | 144 | let task = smol::spawn(async move { 145 | println!("updating entity"); 146 | e.with_bundle((DataA(666.0), DataB(666.0))).updates().await; 147 | println!("done!"); 148 | }); 149 | 150 | while !task.is_finished() { 151 | world.tock(); 152 | } 153 | // just in case the executor really beat us to the punch 154 | world.tock(); 155 | world 156 | .visit(|data: Query<(&DataA, &DataB)>| { 157 | let mut q = data.query(); 158 | let (a, b) = q.find_one(id).unwrap(); 159 | assert_eq!(666.0, a.0); 160 | assert_eq!(666.0, b.0); 161 | }) 162 | .unwrap(); 163 | } 164 | 165 | #[test] 166 | fn entities_can_lazy_add_and_get() { 167 | #[derive(Debug, Clone, PartialEq)] 168 | struct Name(&'static str); 169 | 170 | #[derive(Debug, Clone, PartialEq)] 171 | struct Age(u32); 172 | 173 | // this tests that we can use an Entity to set components and 174 | // await those component updates. 175 | let mut world = World::default(); 176 | let mut facade = world.facade(); 177 | let task = smol::spawn(async move { 178 | let mut e = { 179 | let e = facade 180 | .visit(|mut entities: ViewMut| entities.create()) 181 | .await 182 | .unwrap(); 183 | e 184 | }; 185 | 186 | e.insert_bundle((Name("ada"), Age(666))); 187 | e.updates().await; 188 | 189 | let (name, age) = e 190 | .visit::<(&Name, &Age), _>(|(name, age)| (name.value().clone(), age.value().clone())) 191 | .await 192 | .unwrap(); 193 | assert_eq!(Name("ada"), name); 194 | assert_eq!(Age(666), age); 195 | 196 | println!("done!"); 197 | }); 198 | 199 | while !task.is_finished() { 200 | world.tick().unwrap(); 201 | world.get_facade_schedule().unwrap().run().unwrap(); 202 | } 203 | 204 | world 205 | .visit(|ages: Query<&Age>| { 206 | let mut q = ages.query(); 207 | let age = q.find_one(0).unwrap(); 208 | assert_eq!(&Age(666), age.value()); 209 | }) 210 | .unwrap(); 211 | } 212 | 213 | #[test] 214 | fn can_query_empty_ref_archetypes_in_same_batch() { 215 | let _ = env_logger::builder() 216 | .is_test(true) 217 | .filter_level(log::LevelFilter::Trace) 218 | .try_init(); 219 | 220 | let mut world = World::default(); 221 | let one = |q: Query<(&f32, &bool)>| { 222 | for (_f, _b) in q.query().iter_mut() {} 223 | ok() 224 | }; 225 | let two = |q: Query<(&f32, &bool)>| { 226 | for (_f, _b) in q.query().iter_mut() {} 227 | ok() 228 | }; 229 | world.add_subgraph(graph!(one, two)); 230 | world.tock(); 231 | let schedule = world.get_schedule_names(); 232 | assert_eq!(vec![vec!["one", "two"]], schedule); 233 | } 234 | 235 | #[test] 236 | fn deleted_entities() { 237 | let _ = env_logger::builder() 238 | .is_test(true) 239 | .filter_level(log::LevelFilter::Trace) 240 | .try_init(); 241 | 242 | let mut world = World::default(); 243 | { 244 | let entities: &mut Entities = world.get_entities_mut(); 245 | (0..10u32).for_each(|i| { 246 | entities 247 | .create() 248 | .insert_bundle(("hello".to_string(), false, i)) 249 | }); 250 | assert_eq!(10, entities.alive_len()); 251 | } 252 | world.tock(); 253 | world 254 | .visit(|q: Query<&u32>| { 255 | assert_eq!(9, **q.query().find_one(9).unwrap()); 256 | }) 257 | .unwrap(); 258 | world 259 | .visit(|entities: View| { 260 | let entity = entities.hydrate(9).unwrap(); 261 | entities.destroy(entity); 262 | }) 263 | .unwrap(); 264 | world.tock(); 265 | world 266 | .visit(|entities: View| { 267 | assert_eq!(9, entities.alive_len()); 268 | let deleted_strings = entities.deleted_iter_of::().collect::>(); 269 | assert_eq!(9, deleted_strings[0].id()); 270 | }) 271 | .unwrap(); 272 | } 273 | 274 | #[test] 275 | fn system_barrier() { 276 | let _ = env_logger::builder() 277 | .is_test(true) 278 | .filter_level(log::LevelFilter::Trace) 279 | .try_init(); 280 | 281 | fn one(mut u32_number: ViewMut) -> Result<(), GraphError> { 282 | *u32_number += 1; 283 | end() 284 | } 285 | 286 | fn two(mut u32_number: ViewMut) -> Result<(), GraphError> { 287 | *u32_number += 1; 288 | end() 289 | } 290 | 291 | fn exit_on_three(mut f32_number: ViewMut) -> Result<(), GraphError> { 292 | *f32_number += 1.0; 293 | if *f32_number == 3.0 { 294 | end() 295 | } else { 296 | ok() 297 | } 298 | } 299 | 300 | fn lastly((u32_number, f32_number): (View, View)) -> Result<(), GraphError> { 301 | if *u32_number == 2 && *f32_number == 3.0 { 302 | end() 303 | } else { 304 | ok() 305 | } 306 | } 307 | 308 | let system_graph = graph!( 309 | // one should run before two 310 | one < two, 311 | // exit_on_three has no dependencies 312 | exit_on_three 313 | ) 314 | .with_barrier() 315 | // all systems after a barrier run after the systems before a barrier 316 | .with_subgraph(graph!(lastly)); 317 | 318 | let mut world = World::default(); 319 | world.add_subgraph(system_graph); 320 | 321 | assert_eq!( 322 | vec![vec!["exit_on_three", "one",], vec!["two"], vec!["lastly"],], 323 | world.get_schedule_names() 324 | ); 325 | 326 | world.tock(); 327 | 328 | assert_eq!( 329 | vec![vec!["exit_on_three"], vec!["lastly"],], 330 | world.get_schedule_names() 331 | ); 332 | 333 | world.tock(); 334 | world.tock(); 335 | assert!(world.get_schedule_names().is_empty()); 336 | } 337 | 338 | #[test] 339 | fn can_compile_write_without_trydefault() { 340 | let mut world = World::default(); 341 | world.add_resource(0.0f32); 342 | { 343 | let _f32_num = world.get_resource_mut::().unwrap(); 344 | } 345 | { 346 | let _f32_num = world.get_resource_mut::().unwrap(); 347 | } 348 | } 349 | 350 | #[test] 351 | fn can_tick_facades() { 352 | let mut world = World::default(); 353 | world.add_resource(0u32); 354 | 355 | fn increment(mut counter: ViewMut) { 356 | *counter += 1; 357 | } 358 | 359 | for _ in 0..3 { 360 | let mut facade = world.facade(); 361 | smol::spawn(async move { 362 | facade.visit(increment).await.unwrap(); 363 | }) 364 | .detach(); 365 | } 366 | 367 | // give some time to accumulate all the requests 368 | std::thread::sleep(std::time::Duration::from_millis(10)); 369 | 370 | world.run().unwrap(); 371 | { 372 | println!("first tick"); 373 | let mut s = world.get_facade_schedule().unwrap(); 374 | assert_eq!(3, s.len()); 375 | s.tick().unwrap(); 376 | while !s.unify() {} 377 | } 378 | 379 | world.run().unwrap(); 380 | { 381 | println!("second tick"); 382 | let mut s = world.get_facade_schedule().unwrap(); 383 | assert_eq!(2, s.len()); 384 | s.tick().unwrap(); 385 | while !s.unify() {} 386 | } 387 | 388 | world.run().unwrap(); 389 | { 390 | println!("third tick"); 391 | let mut s = world.get_facade_schedule().unwrap(); 392 | assert_eq!(1, s.len()); 393 | s.tick().unwrap(); 394 | while !s.unify() {} 395 | } 396 | 397 | assert_eq!(0, world.get_facade_schedule().unwrap().len()); 398 | assert_eq!(3, world.visit(|counter: View| *counter).unwrap()); 399 | } 400 | 401 | #[test] 402 | fn can_derive_moongraph_edges() { 403 | #[derive(Edges)] 404 | pub struct MyData { 405 | pub u: View, 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /crates/apecs/tests/wasm.rs: -------------------------------------------------------------------------------- 1 | use apecs::{ViewMut, World}; 2 | use wasm_bindgen_test::*; 3 | 4 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 5 | 6 | #[wasm_bindgen_test] 7 | fn can_run() { 8 | #[derive(Default)] 9 | pub struct Number(pub u32); 10 | 11 | let mut world = World::default(); 12 | 13 | let mut facade = world.facade(); 14 | wasm_bindgen_futures::spawn_local(async move { 15 | facade 16 | .visit(|mut number: ViewMut| { 17 | number.0 = 1; 18 | }) 19 | .await 20 | .unwrap() 21 | }); 22 | 23 | let mut facade = world.facade(); 24 | wasm_bindgen_futures::spawn_local(async move { 25 | for _ in 0..2 { 26 | facade 27 | .visit(|mut number: ViewMut| { 28 | number.0 = 2; 29 | }) 30 | .await 31 | .unwrap(); 32 | } 33 | }); 34 | 35 | let mut facade = world.facade(); 36 | wasm_bindgen_futures::spawn_local(async move { 37 | for _ in 0..3 { 38 | facade 39 | .visit(|mut number: ViewMut| { 40 | number.0 = 3; 41 | }) 42 | .await 43 | .unwrap(); 44 | } 45 | }); 46 | 47 | while world.facade_count() > 1 { 48 | world.run().unwrap(); 49 | } 50 | 51 | println!("done!"); 52 | } 53 | -------------------------------------------------------------------------------- /crates/benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarks" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dev-dependencies] 9 | apecs = { path = "../apecs" } 10 | bevy_ecs = "^0.8" 11 | bevy_tasks = "^0.8" 12 | cgmath = { version = "0.17", features = ["serde"] } 13 | criterion = "^0.3" 14 | hecs = { version = "^0.9", features = ["column-serialize", "row-serialize"] } 15 | legion = "^0.4" 16 | planck_ecs = { version = "^1.2", features = ["parallel"] } 17 | rayon = "^1.5" 18 | shipyard = "^0.5" 19 | specs = {version = "0.16.1", features = ["serde"] } 20 | specs-derive = "0.4.1" 21 | 22 | [[bench]] 23 | name = "benchmarks" 24 | harness = false 25 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/add_remove.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | 3 | pub struct A(f32); 4 | pub struct B(f32); 5 | 6 | pub struct Benchmark(Components); 7 | 8 | impl Benchmark { 9 | pub fn new() -> Self { 10 | let mut all = Components::default(); 11 | all.extend::<(A,)>(Box::new((0..10_000).map(|id| Entry::new(id, A(0.0))))); 12 | Self(all) 13 | } 14 | 15 | pub fn run(&mut self) { 16 | for id in 0..10000 { 17 | let _ = self.0.insert_component(id, B(0.0)); 18 | } 19 | 20 | for id in 0..10000 { 21 | let _ = self.0.remove_component::(id); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/benchmarks.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | mod add_remove; 4 | mod frag_iter; 5 | mod heavy_compute; 6 | mod schedule; 7 | mod simple_insert; 8 | mod simple_iter; 9 | 10 | mod bevy; 11 | mod hecs; 12 | mod legion; 13 | mod planck_ecs; 14 | mod shipyard; 15 | mod specs; 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq)] 18 | pub enum Syncronicity { 19 | Async, 20 | Sync, 21 | } 22 | 23 | impl std::fmt::Display for Syncronicity { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.write_str(match self { 26 | Syncronicity::Async => "async", 27 | Syncronicity::Sync => "sync", 28 | }) 29 | } 30 | } 31 | 32 | fn bench_simple_insert(c: &mut Criterion) { 33 | let mut group = c.benchmark_group("simple_insert"); 34 | // let plot_config = 35 | // criterion::PlotConfiguration::default(). 36 | // summary_scale(criterion::AxisScale::Logarithmic); 37 | // group.plot_config(plot_config); 38 | group.bench_function("apecs", |b| { 39 | let mut bench = simple_insert::Benchmark::new(); 40 | b.iter(move || bench.run()); 41 | }); 42 | group.bench_function("legion", |b| { 43 | let mut bench = legion::simple_insert::Benchmark::new(); 44 | b.iter(move || bench.run()); 45 | }); 46 | group.bench_function("bevy", |b| { 47 | let mut bench = bevy::simple_insert::Benchmark::new(); 48 | b.iter(move || bench.run()); 49 | }); 50 | group.bench_function("hecs", |b| { 51 | let mut bench = hecs::simple_insert::Benchmark::new(); 52 | b.iter(move || bench.run()); 53 | }); 54 | group.bench_function("planck_ecs", |b| { 55 | let mut bench = planck_ecs::simple_insert::Benchmark::new(); 56 | b.iter(move || bench.run()); 57 | }); 58 | group.bench_function("shipyard", |b| { 59 | let mut bench = shipyard::simple_insert::Benchmark::new(); 60 | b.iter(move || bench.run()); 61 | }); 62 | group.bench_function("specs", |b| { 63 | let mut bench = specs::simple_insert::Benchmark::new(); 64 | b.iter(move || bench.run()); 65 | }); 66 | } 67 | 68 | fn bench_add_remove(c: &mut Criterion) { 69 | let mut group = c.benchmark_group("add_remove_component"); 70 | // let plot_config = 71 | // criterion::PlotConfiguration::default(). 72 | // summary_scale(criterion::AxisScale::Logarithmic); 73 | // group.plot_config(plot_config); 74 | group.throughput(criterion::Throughput::Elements(10_000)); 75 | group.bench_function("apecs", |b| { 76 | let mut bench = add_remove::Benchmark::new(); 77 | b.iter(move || bench.run()); 78 | }); 79 | group.bench_function("legion", |b| { 80 | let mut bench = legion::add_remove::Benchmark::new(); 81 | b.iter(move || bench.run()); 82 | }); 83 | group.bench_function("hecs", |b| { 84 | let mut bench = hecs::add_remove::Benchmark::new(); 85 | b.iter(move || bench.run()); 86 | }); 87 | group.bench_function("planck_ecs", |b| { 88 | let mut bench = planck_ecs::add_remove::Benchmark::new(); 89 | b.iter(move || bench.run()); 90 | }); 91 | group.bench_function("shipyard", |b| { 92 | let mut bench = shipyard::add_remove::Benchmark::new(); 93 | b.iter(move || bench.run()); 94 | }); 95 | group.bench_function("specs", |b| { 96 | let mut bench = specs::add_remove::Benchmark::new(); 97 | b.iter(move || bench.run()); 98 | }); 99 | group.bench_function("bevy", |b| { 100 | let mut bench = bevy::add_remove::Benchmark::new(); 101 | b.iter(move || bench.run()); 102 | }); 103 | 104 | group.finish(); 105 | } 106 | 107 | fn bench_simple_iter(c: &mut Criterion) { 108 | let mut group = c.benchmark_group("simple_iter"); 109 | 110 | group.bench_function("legion", |b| { 111 | let mut bench = legion::simple_iter::Benchmark::new(); 112 | b.iter(move || bench.run()); 113 | }); 114 | group.bench_function("apecs", |b| { 115 | let mut bench = simple_iter::Benchmark::new().unwrap(); 116 | b.iter(move || bench.run()); 117 | }); 118 | group.bench_function("bevy", |b| { 119 | let mut bench = bevy::simple_iter::Benchmark::new(); 120 | b.iter(move || bench.run()); 121 | }); 122 | group.bench_function("hecs", |b| { 123 | let mut bench = hecs::simple_iter::Benchmark::new(); 124 | b.iter(move || bench.run()); 125 | }); 126 | group.bench_function("planck_ecs", |b| { 127 | let mut bench = planck_ecs::simple_iter::Benchmark::new(); 128 | b.iter(move || bench.run()); 129 | }); 130 | group.bench_function("shipyard", |b| { 131 | let mut bench = shipyard::simple_iter::Benchmark::new(); 132 | b.iter(move || bench.run()); 133 | }); 134 | group.bench_function("specs", |b| { 135 | let mut bench = specs::simple_iter::Benchmark::new(); 136 | b.iter(move || bench.run()); 137 | }); 138 | 139 | group.finish(); 140 | } 141 | 142 | fn bench_frag_iter(c: &mut Criterion) { 143 | let mut group = c.benchmark_group("frag_iter"); 144 | 145 | group.bench_function("apecs", |b| { 146 | let mut store = frag_iter::arch(); 147 | b.iter(move || frag_iter::tick_arch(&mut store)) 148 | }); 149 | group.bench_function("legion", |b| { 150 | let mut bench = legion::frag_iter::Benchmark::new(); 151 | b.iter(move || bench.run()); 152 | }); 153 | group.bench_function("bevy", |b| { 154 | let mut bench = bevy::frag_iter::Benchmark::new(); 155 | b.iter(move || bench.run()); 156 | }); 157 | group.bench_function("hecs", |b| { 158 | let mut bench = hecs::frag_iter::Benchmark::new(); 159 | b.iter(move || bench.run()); 160 | }); 161 | group.bench_function("planck_ecs", |b| { 162 | let mut bench = planck_ecs::frag_iter::Benchmark::new(); 163 | b.iter(move || bench.run()); 164 | }); 165 | group.bench_function("shipyard", |b| { 166 | let mut bench = shipyard::frag_iter::Benchmark::new(); 167 | b.iter(move || bench.run()); 168 | }); 169 | group.bench_function("specs", |b| { 170 | let mut bench = specs::frag_iter::Benchmark::new(); 171 | b.iter(move || bench.run()); 172 | }); 173 | 174 | group.finish(); 175 | } 176 | 177 | fn bench_schedule(c: &mut Criterion) { 178 | let mut group = c.benchmark_group("schedule"); 179 | 180 | group.bench_function("apecs", |b| { 181 | let mut bench = schedule::Benchmark::new(); 182 | b.iter(move || bench.run()) 183 | }); 184 | group.bench_function("legion", |b| { 185 | let mut bench = legion::schedule::Benchmark::new(); 186 | b.iter(move || bench.run()); 187 | }); 188 | group.bench_function("bevy", |b| { 189 | let mut bench = bevy::schedule::Benchmark::new(); 190 | b.iter(move || bench.run()); 191 | }); 192 | group.bench_function("planck_ecs", |b| { 193 | let mut bench = planck_ecs::schedule::Benchmark::new(); 194 | b.iter(move || bench.run()); 195 | }); 196 | group.bench_function("shipyard", |b| { 197 | let mut bench = shipyard::schedule::Benchmark::new(); 198 | b.iter(move || bench.run()); 199 | }); 200 | group.bench_function("specs", |b| { 201 | let mut bench = specs::schedule::Benchmark::new(); 202 | b.iter(move || bench.run()); 203 | }); 204 | 205 | group.finish(); 206 | } 207 | 208 | fn bench_heavy_compute(c: &mut Criterion) { 209 | let mut group = c.benchmark_group("heavy_compute"); 210 | 211 | group.bench_function("apecs", |b| { 212 | let mut bench = heavy_compute::Benchmark::new().unwrap(); 213 | b.iter(move || bench.run()); 214 | }); 215 | group.bench_function("legion", |b| { 216 | let mut bench = legion::heavy_compute::Benchmark::new(); 217 | b.iter(move || bench.run()); 218 | }); 219 | group.bench_function("bevy", |b| { 220 | let mut bench = bevy::heavy_compute::Benchmark::new(); 221 | b.iter(move || bench.run()); 222 | }); 223 | group.bench_function("hecs", |b| { 224 | let mut bench = hecs::heavy_compute::Benchmark::new(); 225 | b.iter(move || bench.run()); 226 | }); 227 | group.bench_function("shipyard", |b| { 228 | let mut bench = shipyard::heavy_compute::Benchmark::new(); 229 | b.iter(move || bench.run()); 230 | }); 231 | group.bench_function("specs", |b| { 232 | let mut bench = specs::heavy_compute::Benchmark::new(); 233 | b.iter(move || bench.run()); 234 | }); 235 | } 236 | 237 | criterion_group!( 238 | benches, 239 | bench_add_remove, 240 | bench_simple_iter, 241 | bench_simple_insert, 242 | bench_frag_iter, 243 | bench_schedule, 244 | bench_heavy_compute 245 | ); 246 | criterion_main!(benches); 247 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/add_remove.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | 3 | #[derive(Component)] 4 | struct A(f32); 5 | #[derive(Component)] 6 | struct B(f32); 7 | 8 | pub struct Benchmark(World, Vec); 9 | 10 | impl Benchmark { 11 | pub fn new() -> Self { 12 | let mut world = World::default(); 13 | 14 | let entities = world 15 | .spawn_batch((0..10000).map(|_| (A(0.0),))) 16 | .collect::>(); 17 | 18 | Self(world, entities) 19 | } 20 | 21 | pub fn run(&mut self) { 22 | for entity in &self.1 { 23 | self.0.entity_mut(*entity).insert(B(0.0)); 24 | } 25 | 26 | for entity in &self.1 { 27 | self.0.entity_mut(*entity).remove::(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | 3 | macro_rules! create_entities { 4 | ($world:ident; $( $variants:ident ),*) => { 5 | $( 6 | #[derive(Component)] 7 | struct $variants(f32); 8 | $world.spawn_batch((0..20).map(|_| ($variants(0.0), Data(1.0)))); 9 | )* 10 | }; 11 | } 12 | 13 | #[derive(Component)] 14 | struct Data(f32); 15 | 16 | pub struct Benchmark(World); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let mut world = World::default(); 21 | 22 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 23 | 24 | Self(world) 25 | } 26 | 27 | pub fn run(&mut self) { 28 | let mut query = self.0.query::<&mut Data>(); 29 | 30 | for mut data in query.iter_mut(&mut self.0) { 31 | data.0 *= 2.0; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use cgmath::*; 3 | 4 | #[derive(Copy, Clone, Component)] 5 | struct Position(Vector3); 6 | 7 | #[derive(Copy, Clone, Component)] 8 | struct Rotation(Vector3); 9 | 10 | #[derive(Copy, Clone, Component)] 11 | struct Velocity(Vector3); 12 | 13 | #[derive(Copy, Clone, Component)] 14 | struct Transform(Matrix4); 15 | 16 | pub struct Benchmark(World); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let mut world = World::default(); 21 | 22 | world.spawn_batch((0..1000).map(|_| { 23 | ( 24 | Transform(Matrix4::::from_angle_x(Rad(1.2))), 25 | Position(Vector3::unit_x()), 26 | Rotation(Vector3::unit_x()), 27 | Velocity(Vector3::unit_x()), 28 | ) 29 | })); 30 | 31 | Self(world) 32 | } 33 | 34 | pub fn run(&mut self) { 35 | let mut query = self.0.query::<(&mut Position, &mut Transform)>(); 36 | 37 | query.par_for_each_mut(&mut self.0, 64, |(mut pos, mut mat)| { 38 | use cgmath::Transform; 39 | for _ in 0..100 { 40 | mat.0 = mat.0.invert().unwrap(); 41 | } 42 | 43 | pos.0 = mat.0.transform_vector(pos.0); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | pub mod heavy_compute; 4 | pub mod schedule; 5 | pub mod simple_insert; 6 | pub mod simple_iter; 7 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/schedule.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::{prelude::*, schedule::Schedule}; 2 | 3 | #[derive(Component)] 4 | struct A(f32); 5 | #[derive(Component)] 6 | struct B(f32); 7 | #[derive(Component)] 8 | struct C(f32); 9 | #[derive(Component)] 10 | struct D(f32); 11 | #[derive(Component)] 12 | struct E(f32); 13 | 14 | fn ab(mut query: Query<(&mut A, &mut B)>) { 15 | for (mut a, mut b) in query.iter_mut() { 16 | std::mem::swap(&mut a.0, &mut b.0); 17 | } 18 | } 19 | 20 | fn cd(mut query: Query<(&mut C, &mut D)>) { 21 | for (mut c, mut d) in query.iter_mut() { 22 | std::mem::swap(&mut c.0, &mut d.0); 23 | } 24 | } 25 | 26 | fn ce(mut query: Query<(&mut C, &mut E)>) { 27 | for (mut c, mut e) in query.iter_mut() { 28 | std::mem::swap(&mut c.0, &mut e.0); 29 | } 30 | } 31 | 32 | pub struct Benchmark(World, Schedule); 33 | 34 | impl Benchmark { 35 | pub fn new() -> Self { 36 | let mut world = World::default(); 37 | 38 | world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0)))); 39 | 40 | world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0)))); 41 | 42 | world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); 43 | 44 | world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); 45 | 46 | let mut schedule = Schedule::default(); 47 | schedule.add_stage("main", SystemStage::parallel()); 48 | schedule.add_system_to_stage("main", ab); 49 | schedule.add_system_to_stage("main", cd); 50 | schedule.add_system_to_stage("main", ce); 51 | 52 | Self(world, schedule) 53 | } 54 | 55 | pub fn run(&mut self) { 56 | self.1.run(&mut self.0); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use cgmath::*; 3 | 4 | #[derive(Copy, Clone, Component)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone, Component)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone, Component)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone, Component)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark; 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | Self 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let mut world = World::new(); 25 | world.spawn_batch((0..10_000).map(|_| { 26 | ( 27 | Transform(Matrix4::from_scale(1.0)), 28 | Position(Vector3::unit_x()), 29 | Rotation(Vector3::unit_x()), 30 | Velocity(Vector3::unit_x()), 31 | ) 32 | })); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/bevy/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use cgmath::*; 3 | 4 | #[derive(Copy, Clone, Component)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone, Component)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone, Component)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone, Component)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark(World); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let mut world = World::new(); 21 | world.spawn_batch((0..10_000).map(|_| { 22 | ( 23 | Transform(Matrix4::from_scale(1.0)), 24 | Position(Vector3::unit_x()), 25 | Rotation(Vector3::unit_x()), 26 | Velocity(Vector3::unit_x()), 27 | ) 28 | })); 29 | 30 | Self(world) 31 | } 32 | 33 | pub fn run(&mut self) { 34 | let mut query = self.0.query::<(&Velocity, &mut Position)>(); 35 | 36 | for (velocity, mut position) in query.iter_mut(&mut self.0) { 37 | position.0 += velocity.0; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | 3 | pub struct Data(f32); 4 | 5 | macro_rules! create_entities { 6 | ($all:ident; $ent:ident; $( $variants:ident ),*) => { 7 | $( 8 | struct $variants(f32); 9 | (0..20) 10 | .for_each(|n| { 11 | let _ = $all.insert_bundle($ent, ($variants(0.0), Data(1.0))); 12 | $ent += n; 13 | }); 14 | )* 15 | }; 16 | } 17 | 18 | pub fn arch() -> Components { 19 | let mut all: Components = Components::default(); 20 | let mut id = 0; 21 | create_entities!(all; id; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 22 | all 23 | } 24 | 25 | pub fn tick_arch(all: &mut Components) { 26 | let mut q = all.query::<&mut Data>(); 27 | q.iter_mut().for_each(|data| data.0 *= 2.0); 28 | } 29 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | use cgmath::*; 3 | use rayon::prelude::*; 4 | 5 | #[derive(Copy, Clone)] 6 | pub struct Transform(Matrix4); 7 | 8 | #[derive(Copy, Clone)] 9 | pub struct Position(Vector3); 10 | 11 | #[derive(Copy, Clone)] 12 | pub struct Rotation(Vector3); 13 | 14 | #[derive(Copy, Clone)] 15 | pub struct Velocity(Vector3); 16 | 17 | pub struct Benchmark(Components); 18 | 19 | impl Benchmark { 20 | pub fn new() -> Result { 21 | let mut archs = Components::default(); 22 | archs.extend::<(Transform, Position, Rotation, Velocity)>(( 23 | Box::new( 24 | (0..1000) 25 | .map(|id| Entry::new(id, Transform(Matrix4::::from_angle_x(Rad(1.2))))), 26 | ), 27 | Box::new((0..1000).map(|id| Entry::new(id, Position(Vector3::unit_x())))), 28 | Box::new((0..1000).map(|id| Entry::new(id, Rotation(Vector3::unit_x())))), 29 | Box::new((0..1000).map(|id| Entry::new(id, Velocity(Vector3::unit_x())))), 30 | )); 31 | 32 | Ok(Self(archs)) 33 | } 34 | 35 | pub fn run(&mut self) { 36 | let mut q = self.0.query::<(&mut Position, &mut Transform)>(); 37 | q.par_iter_mut().for_each(|(pos, mat)| { 38 | use cgmath::Transform; 39 | for _ in 0..100 { 40 | mat.0 = mat.0.invert().unwrap(); 41 | } 42 | pos.0 = mat.0.transform_vector(pos.0); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/add_remove.rs: -------------------------------------------------------------------------------- 1 | use hecs::*; 2 | 3 | struct A(f32); 4 | struct B(f32); 5 | 6 | pub struct Benchmark(World, Vec); 7 | 8 | impl Benchmark { 9 | pub fn new() -> Self { 10 | let mut world = World::default(); 11 | 12 | let entities = world 13 | .spawn_batch((0..10000).map(|_| (A(0.0),))) 14 | .collect::>(); 15 | 16 | Self(world, entities) 17 | } 18 | 19 | pub fn run(&mut self) { 20 | for entity in &self.1 { 21 | self.0.insert_one(*entity, B(0.0)).unwrap(); 22 | } 23 | 24 | for entity in &self.1 { 25 | self.0.remove_one::(*entity).unwrap(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use hecs::*; 2 | 3 | macro_rules! create_entities { 4 | ($world:ident; $( $variants:ident ),*) => { 5 | $( 6 | struct $variants(f32); 7 | $world.spawn_batch((0..20).map(|_| ($variants(0.0), Data(1.0)))); 8 | )* 9 | }; 10 | } 11 | 12 | struct Data(f32); 13 | 14 | pub struct Benchmark(World); 15 | 16 | impl Benchmark { 17 | pub fn new() -> Self { 18 | let mut world = World::default(); 19 | 20 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 21 | 22 | Self(world) 23 | } 24 | 25 | pub fn run(&mut self) { 26 | for (_, data) in self.0.query_mut::<&mut Data>() { 27 | data.0 *= 2.0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use hecs::*; 3 | use rayon::prelude::*; 4 | 5 | #[derive(Copy, Clone)] 6 | struct Position(Vector3); 7 | 8 | #[derive(Copy, Clone)] 9 | struct Rotation(Vector3); 10 | 11 | #[derive(Copy, Clone)] 12 | struct Velocity(Vector3); 13 | 14 | pub struct Benchmark(World); 15 | 16 | impl Benchmark { 17 | pub fn new() -> Self { 18 | let mut world = World::default(); 19 | 20 | world.spawn_batch((0..1000).map(|_| { 21 | ( 22 | Matrix4::::from_angle_x(Rad(1.2)), 23 | Position(Vector3::unit_x()), 24 | Rotation(Vector3::unit_x()), 25 | Velocity(Vector3::unit_x()), 26 | ) 27 | })); 28 | 29 | Self(world) 30 | } 31 | 32 | pub fn run(&mut self) { 33 | self.0 34 | .query::<(&mut Position, &mut Matrix4)>() 35 | .iter_batched(64) 36 | .par_bridge() 37 | .for_each(|batch| { 38 | for (_, (pos, mat)) in batch { 39 | for _ in 0..100 { 40 | *mat = mat.invert().unwrap(); 41 | } 42 | 43 | pos.0 = mat.transform_vector(pos.0); 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | pub mod heavy_compute; 4 | pub mod simple_insert; 5 | pub mod simple_iter; 6 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use hecs::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark; 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | Self 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let mut world = World::new(); 25 | 26 | world.spawn_batch((0..10_000).map(|_| { 27 | ( 28 | Transform(Matrix4::from_scale(1.0)), 29 | Position(Vector3::unit_x()), 30 | Rotation(Vector3::unit_x()), 31 | Velocity(Vector3::unit_x()), 32 | ) 33 | })); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/hecs/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use hecs::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark(World); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let mut world = World::new(); 21 | world.spawn_batch((0..10_000).map(|_| { 22 | ( 23 | Transform(Matrix4::from_scale(1.0)), 24 | Position(Vector3::unit_x()), 25 | Rotation(Vector3::unit_x()), 26 | Velocity(Vector3::unit_x()), 27 | ) 28 | })); 29 | 30 | Self(world) 31 | } 32 | 33 | pub fn run(&mut self) { 34 | for (_, (velocity, position)) in self.0.query_mut::<(&Velocity, &mut Position)>() { 35 | position.0 += velocity.0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/add_remove.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | 3 | struct A(f32); 4 | struct B(f32); 5 | 6 | pub struct Benchmark(World, Vec); 7 | 8 | impl Benchmark { 9 | pub fn new() -> Self { 10 | let mut world = World::default(); 11 | 12 | let entities = world.extend((0..10000).map(|_| (A(0.0),))).to_vec(); 13 | 14 | Self(world, entities) 15 | } 16 | 17 | pub fn run(&mut self) { 18 | for entity in &self.1 { 19 | self.0.entry(*entity).unwrap().add_component(B(0.0)); 20 | } 21 | 22 | for entity in &self.1 { 23 | self.0.entry(*entity).unwrap().remove_component::(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use query::Query; 3 | use storage::PackOptions; 4 | 5 | macro_rules! create_entities { 6 | ($world:ident; $( $variants:ident ),*) => { 7 | $( 8 | struct $variants(f32); 9 | $world.extend((0..20).map(|_| ($variants(0.0), Data(1.0)))); 10 | )* 11 | }; 12 | } 13 | 14 | struct Data(f32); 15 | 16 | pub struct Benchmark(World, Query>); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let mut world = World::default(); 21 | 22 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 23 | world.pack(PackOptions::force()); 24 | 25 | let query = Write::::query(); 26 | 27 | Self(world, query) 28 | } 29 | 30 | pub fn run(&mut self) { 31 | self.1.for_each_mut(&mut self.0, |data| { 32 | data.0 *= 2.0; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use legion::*; 3 | use query::Query; 4 | use storage::PackOptions; 5 | 6 | #[derive(Copy, Clone)] 7 | struct Position(Vector3); 8 | 9 | #[derive(Copy, Clone)] 10 | struct Rotation(Vector3); 11 | 12 | #[derive(Copy, Clone)] 13 | struct Velocity(Vector3); 14 | 15 | pub struct Benchmark(World, Query<(Write, Write>)>); 16 | 17 | impl Benchmark { 18 | pub fn new() -> Self { 19 | let mut world = World::default(); 20 | 21 | world.extend((0..1000).map(|_| { 22 | ( 23 | Matrix4::::from_angle_x(Rad(1.2)), 24 | Position(Vector3::unit_x()), 25 | Rotation(Vector3::unit_x()), 26 | Velocity(Vector3::unit_x()), 27 | ) 28 | })); 29 | world.pack(PackOptions::force()); 30 | 31 | let query = <(Write, Write>)>::query(); 32 | 33 | Self(world, query) 34 | } 35 | 36 | pub fn run(&mut self) { 37 | self.1.par_for_each_mut(&mut self.0, |(pos, mat)| { 38 | for _ in 0..100 { 39 | *mat = mat.invert().unwrap(); 40 | } 41 | pos.0 = mat.transform_vector(pos.0); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | pub mod heavy_compute; 4 | pub mod schedule; 5 | pub mod simple_insert; 6 | pub mod simple_iter; 7 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/schedule.rs: -------------------------------------------------------------------------------- 1 | use legion::*; 2 | use storage::PackOptions; 3 | 4 | struct A(f32); 5 | struct B(f32); 6 | struct C(f32); 7 | struct D(f32); 8 | struct E(f32); 9 | 10 | #[system(for_each)] 11 | fn ab(a: &mut A, b: &mut B) { 12 | std::mem::swap(&mut a.0, &mut b.0); 13 | } 14 | 15 | #[system(for_each)] 16 | fn cd(c: &mut C, d: &mut D) { 17 | std::mem::swap(&mut c.0, &mut d.0); 18 | } 19 | 20 | #[system(for_each)] 21 | fn ce(c: &mut C, e: &mut E) { 22 | std::mem::swap(&mut c.0, &mut e.0); 23 | } 24 | 25 | pub struct Benchmark(World, Resources, Schedule); 26 | 27 | impl Benchmark { 28 | pub fn new() -> Self { 29 | let mut world = World::default(); 30 | 31 | world.extend((0..10000).map(|_| (A(0.0), B(0.0)))); 32 | 33 | world.extend((0..10000).map(|_| (A(0.0), B(0.0), C(0.0)))); 34 | 35 | world.extend((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); 36 | 37 | world.extend((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); 38 | 39 | world.pack(PackOptions::force()); 40 | 41 | let schedule = Schedule::builder() 42 | .add_system(ab_system()) 43 | .add_system(cd_system()) 44 | .add_system(ce_system()) 45 | .build(); 46 | 47 | Self(world, Resources::default(), schedule) 48 | } 49 | 50 | pub fn run(&mut self) { 51 | self.2.execute(&mut self.0, &mut self.1); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use legion::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark; 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | Self 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let mut world = World::default(); 25 | 26 | world.extend( 27 | ( 28 | vec![Transform(Matrix4::from_scale(1.0)); 10000], 29 | vec![Position(Vector3::unit_x()); 10000], 30 | vec![Rotation(Vector3::unit_x()); 10000], 31 | vec![Velocity(Vector3::unit_x()); 10000], 32 | ) 33 | .into_soa(), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/legion/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use legion::*; 3 | use query::Query; 4 | use storage::PackOptions; 5 | 6 | #[derive(Copy, Clone)] 7 | struct Transform(Matrix4); 8 | 9 | #[derive(Copy, Clone)] 10 | struct Position(Vector3); 11 | 12 | #[derive(Copy, Clone)] 13 | struct Rotation(Vector3); 14 | 15 | #[derive(Copy, Clone)] 16 | struct Velocity(Vector3); 17 | 18 | pub struct Benchmark(World, Query<(Read, Write)>); 19 | 20 | impl Benchmark { 21 | pub fn new() -> Self { 22 | let mut world = World::default(); 23 | 24 | world.extend( 25 | ( 26 | vec![Transform(Matrix4::from_scale(1.0)); 10000], 27 | vec![Position(Vector3::unit_x()); 10000], 28 | vec![Rotation(Vector3::unit_x()); 10000], 29 | vec![Velocity(Vector3::unit_x()); 10000], 30 | ) 31 | .into_soa(), 32 | ); 33 | world.pack(PackOptions::force()); 34 | 35 | let query = <(Read, Write)>::query(); 36 | 37 | Self(world, query) 38 | } 39 | 40 | pub fn run(&mut self) { 41 | self.1.for_each_mut(&mut self.0, |(velocity, position)| { 42 | position.0 += velocity.0; 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/add_remove.rs: -------------------------------------------------------------------------------- 1 | use planck_ecs::*; 2 | struct A(f32); 3 | struct B(f32); 4 | 5 | pub struct Benchmark(Vec, Components, Components); 6 | 7 | impl Benchmark { 8 | pub fn new() -> Self { 9 | let mut entities = Entities::default(); 10 | let mut comp1 = Components::::default(); 11 | let comp2 = Components::::default(); 12 | 13 | let entities = (0..10000) 14 | .map(|_| { 15 | let e = entities.create(); 16 | comp1.insert(e, A(0.0)); 17 | e 18 | }) 19 | .collect(); 20 | Self(entities, comp1, comp2) 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let b_storage = &mut self.2; 25 | for entity in &self.0 { 26 | b_storage.insert(*entity, B(0.0)); 27 | } 28 | 29 | for entity in &self.0 { 30 | b_storage.remove(*entity); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use planck_ecs::*; 2 | 3 | macro_rules! create_entities { 4 | ($world:ident; $( $variants:ident ),*) => { 5 | $( 6 | struct $variants(f32); 7 | $world.initialize::>(); 8 | (0..20) 9 | .for_each(|_| { 10 | let e = $world.get_mut::().unwrap().create(); 11 | $world.get_mut::>().unwrap().insert(e, $variants(0.0)); 12 | $world.get_mut::>().unwrap().insert(e, Data(1.0)); 13 | }); 14 | )* 15 | }; 16 | } 17 | struct Data(f32); 18 | 19 | fn frag_iter_system(data_storage: &mut Components) -> SystemResult { 20 | for data in join!(&mut data_storage) { 21 | data.0 *= 2.0; 22 | } 23 | Ok(()) 24 | } 25 | 26 | pub struct Benchmark(World, System); 27 | 28 | impl Benchmark { 29 | pub fn new() -> Self { 30 | let mut world = World::default(); 31 | world.initialize::(); 32 | world.initialize::>(); 33 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 34 | 35 | Self(world, frag_iter_system.system()) 36 | } 37 | 38 | pub fn run(&mut self) { 39 | self.1.run(&self.0).unwrap(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | // We don't have inner parallelism, only outer. 4 | // pub mod heavy_compute; 5 | pub mod schedule; 6 | pub mod simple_insert; 7 | pub mod simple_iter; 8 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/schedule.rs: -------------------------------------------------------------------------------- 1 | use planck_ecs::*; 2 | struct A(f32); 3 | struct B(f32); 4 | struct C(f32); 5 | struct D(f32); 6 | struct E(f32); 7 | 8 | fn ab_system(a_store: &mut Components, b_store: &mut Components) -> SystemResult { 9 | for (a, b) in join!(&mut a_store && &mut b_store) { 10 | std::mem::swap(&mut a.unwrap().0, &mut b.unwrap().0); 11 | } 12 | Ok(()) 13 | } 14 | 15 | fn cd_system(c_store: &mut Components, d_store: &mut Components) -> SystemResult { 16 | for (c, d) in join!(&mut c_store && &mut d_store) { 17 | std::mem::swap(&mut c.unwrap().0, &mut d.unwrap().0); 18 | } 19 | Ok(()) 20 | } 21 | 22 | fn ce_system(c_store: &mut Components, e_store: &mut Components) -> SystemResult { 23 | for (c, e) in join!(&mut c_store && &mut e_store) { 24 | std::mem::swap(&mut c.unwrap().0, &mut e.unwrap().0); 25 | } 26 | Ok(()) 27 | } 28 | 29 | pub struct Benchmark(World, Dispatcher); 30 | 31 | impl Benchmark { 32 | pub fn new() -> Self { 33 | let mut world = World::default(); 34 | world.initialize::(); 35 | world.initialize::>(); 36 | world.initialize::>(); 37 | world.initialize::>(); 38 | world.initialize::>(); 39 | world.initialize::>(); 40 | (0..10000).for_each(|_| { 41 | let e = world.get_mut::().unwrap().create(); 42 | world.get_mut::>().unwrap().insert(e, A(0.0)); 43 | world.get_mut::>().unwrap().insert(e, B(0.0)); 44 | }); 45 | (0..10000).for_each(|_| { 46 | let e = world.get_mut::().unwrap().create(); 47 | world.get_mut::>().unwrap().insert(e, A(0.0)); 48 | world.get_mut::>().unwrap().insert(e, B(0.0)); 49 | world.get_mut::>().unwrap().insert(e, C(0.0)); 50 | }); 51 | (0..10000).for_each(|_| { 52 | let e = world.get_mut::().unwrap().create(); 53 | world.get_mut::>().unwrap().insert(e, A(0.0)); 54 | world.get_mut::>().unwrap().insert(e, B(0.0)); 55 | world.get_mut::>().unwrap().insert(e, C(0.0)); 56 | world.get_mut::>().unwrap().insert(e, D(0.0)); 57 | }); 58 | (0..10000).for_each(|_| { 59 | let e = world.get_mut::().unwrap().create(); 60 | world.get_mut::>().unwrap().insert(e, A(0.0)); 61 | world.get_mut::>().unwrap().insert(e, B(0.0)); 62 | world.get_mut::>().unwrap().insert(e, C(0.0)); 63 | world.get_mut::>().unwrap().insert(e, E(0.0)); 64 | }); 65 | 66 | let dispatcher = DispatcherBuilder::new() 67 | .add(ab_system) 68 | .add(cd_system) 69 | .add(ce_system) 70 | .build(&mut world); 71 | 72 | Self(world, dispatcher) 73 | } 74 | 75 | pub fn run(&mut self) { 76 | self.1.run_par(&self.0).unwrap(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use planck_ecs::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | #[derive(Copy, Clone)] 7 | struct Position(Vector3); 8 | #[derive(Copy, Clone)] 9 | struct Rotation(Vector3); 10 | #[derive(Copy, Clone)] 11 | struct Velocity(Vector3); 12 | 13 | pub struct Benchmark; 14 | 15 | impl Benchmark { 16 | pub fn new() -> Self { 17 | Self 18 | } 19 | 20 | pub fn run(&mut self) { 21 | let mut entities = Entities::default(); 22 | let mut comp1 = Components::::default(); 23 | let mut comp2 = Components::::default(); 24 | let mut comp3 = Components::::default(); 25 | let mut comp4 = Components::::default(); 26 | 27 | let en = (0..10000).map(|_| entities.create()).collect::>(); 28 | en.iter().for_each(|e| { 29 | comp1.insert(*e, Transform(Matrix4::::from_scale(1.0))); 30 | }); 31 | en.iter().for_each(|e| { 32 | comp2.insert(*e, Position(Vector3::unit_x())); 33 | }); 34 | en.iter().for_each(|e| { 35 | comp3.insert(*e, Rotation(Vector3::unit_x())); 36 | }); 37 | en.iter().for_each(|e| { 38 | comp4.insert(*e, Velocity(Vector3::unit_x())); 39 | }); 40 | 41 | // (0..10000).for_each(|_| { 42 | // let e = entities.create(); 43 | // comp1.insert(e, Transform(Matrix4::::from_scale(1.0))); 44 | // comp2.insert(e, Position(Vector3::unit_x())); 45 | // comp3.insert(e, Rotation(Vector3::unit_x())); 46 | // comp4.insert(e, Velocity(Vector3::unit_x())); 47 | // }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/planck_ecs/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use planck_ecs::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Position(Vector3); 6 | #[derive(Copy, Clone)] 7 | struct Velocity(Vector3); 8 | 9 | pub struct Benchmark(Components, Components); 10 | 11 | impl Benchmark { 12 | pub fn new() -> Self { 13 | let mut entities = Entities::default(); 14 | let mut position_storage = Components::::default(); 15 | let mut velocity_storage = Components::::default(); 16 | (0..10000).for_each(|_| { 17 | let e = entities.create(); 18 | position_storage.insert(e, Position(Vector3::unit_x())); 19 | velocity_storage.insert(e, Velocity(Vector3::unit_x())); 20 | }); 21 | 22 | Self(velocity_storage, position_storage) 23 | } 24 | 25 | pub fn run(&mut self) { 26 | let velocity_storage = &mut self.0; 27 | let position_storage = &mut self.1; 28 | for (velocity, mut position) in join!(&mut velocity_storage && &mut position_storage) { 29 | position.as_mut().unwrap().0 += velocity.unwrap().0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/schedule.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | 3 | struct A(f32); 4 | struct B(f32); 5 | struct C(f32); 6 | struct D(f32); 7 | struct E(f32); 8 | 9 | fn ab_system(query: Query<(&mut A, &mut B)>) -> Result<(), GraphError> { 10 | let mut lock = query.query(); 11 | lock.iter_mut().for_each(|(a, b)| { 12 | std::mem::swap(&mut a.0, &mut b.0); 13 | }); 14 | ok() 15 | } 16 | 17 | fn cd_system(query: Query<(&mut C, &mut D)>) -> Result<(), GraphError> { 18 | let mut lock = query.query(); 19 | lock.iter_mut().for_each(|(c, d)| { 20 | std::mem::swap(&mut c.0, &mut d.0); 21 | }); 22 | ok() 23 | } 24 | 25 | fn ce_system(query: Query<(&mut C, &mut E)>) -> Result<(), GraphError> { 26 | let mut lock = query.query(); 27 | lock.iter_mut().for_each(|(c, e)| { 28 | std::mem::swap(&mut c.0, &mut e.0); 29 | }); 30 | ok() 31 | } 32 | 33 | pub struct Benchmark(World); 34 | 35 | impl Benchmark { 36 | pub fn new() -> Self { 37 | let mut world = World::default(); 38 | world 39 | .visit( 40 | |(mut comps, mut entities): (ViewMut, ViewMut)| { 41 | let ids = entities.create_many(10_000); 42 | comps.extend::<(A, B)>(( 43 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, A(0.0)))), 44 | Box::new(ids.into_iter().map(|id| Entry::new(id, B(0.0)))), 45 | )); 46 | let ids = entities.create_many(10_000); 47 | comps.extend::<(A, B, C)>(( 48 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, A(0.0)))), 49 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, B(0.0)))), 50 | Box::new(ids.into_iter().map(|id| Entry::new(id, C(0.0)))), 51 | )); 52 | let ids = entities.create_many(10_000); 53 | comps.extend::<(A, B, C, D)>(( 54 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, A(0.0)))), 55 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, B(0.0)))), 56 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, C(0.0)))), 57 | Box::new(ids.into_iter().map(|id| Entry::new(id, D(0.0)))), 58 | )); 59 | let ids = entities.create_many(10_000); 60 | comps.extend::<(A, B, C, D, E)>(( 61 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, A(0.0)))), 62 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, B(0.0)))), 63 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, C(0.0)))), 64 | Box::new(ids.clone().into_iter().map(|id| Entry::new(id, D(0.0)))), 65 | Box::new(ids.into_iter().map(|id| Entry::new(id, E(0.0)))), 66 | )); 67 | }, 68 | ) 69 | .unwrap(); 70 | 71 | world 72 | .add_subgraph(graph!(ab_system, cd_system, ce_system)) 73 | .with_parallelism(Parallelism::Automatic); 74 | 75 | Self(world) 76 | } 77 | 78 | pub fn run(&mut self) { 79 | self.0.tick().unwrap() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/add_remove.rs: -------------------------------------------------------------------------------- 1 | use shipyard::*; 2 | 3 | struct A(f32); 4 | struct B(f32); 5 | 6 | pub struct Benchmark(World, Vec); 7 | 8 | impl Benchmark { 9 | pub fn new() -> Self { 10 | let world = World::default(); 11 | 12 | let entities = world 13 | .run(|mut entities: EntitiesViewMut, mut a: ViewMut| { 14 | let mut entity_ids = Vec::new(); 15 | for _ in 0..10_000 { 16 | let entity = entities.add_entity(&mut a, A(0.0)); 17 | entity_ids.push(entity); 18 | } 19 | entity_ids 20 | }) 21 | .unwrap(); 22 | 23 | Self(world, entities) 24 | } 25 | 26 | pub fn run(&mut self) { 27 | self.0 28 | .run(|entities: EntitiesViewMut, mut b: ViewMut| { 29 | for entity in &self.1 { 30 | entities.add_component(*entity, &mut b, B(0.0)); 31 | } 32 | }) 33 | .unwrap(); 34 | 35 | self.0 36 | .run(|mut b: ViewMut| { 37 | for entity in &self.1 { 38 | b.remove(*entity); 39 | } 40 | }) 41 | .unwrap(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use shipyard::*; 2 | 3 | macro_rules! create_entities { 4 | ($world:ident; $( $variants:ident ),*) => { 5 | $( 6 | struct $variants(f32); 7 | $world.run( 8 | | mut entities: EntitiesViewMut, 9 | mut data: ViewMut, 10 | mut variants: ViewMut<$variants> | { 11 | for _ in (0..20) { 12 | entities.add_entity( 13 | (&mut variants, &mut data), 14 | ($variants(0.0), Data(1.0)), 15 | ); 16 | } 17 | }).unwrap(); 18 | )* 19 | }; 20 | } 21 | 22 | struct Data(f32); 23 | 24 | pub struct Benchmark(World); 25 | 26 | impl Benchmark { 27 | pub fn new() -> Self { 28 | let world = World::default(); 29 | 30 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 31 | 32 | Self(world) 33 | } 34 | 35 | pub fn run(&mut self) { 36 | self.0 37 | .run(|mut data: ViewMut| { 38 | (&mut data).iter().for_each(|mut data| { 39 | data.0 *= 2.0; 40 | }) 41 | }) 42 | .unwrap(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use rayon::prelude::*; 3 | use shipyard::*; 4 | 5 | #[derive(Copy, Clone)] 6 | struct Position(Vector3); 7 | 8 | #[derive(Copy, Clone)] 9 | struct Rotation(Vector3); 10 | 11 | #[derive(Copy, Clone)] 12 | struct Velocity(Vector3); 13 | 14 | pub struct Benchmark(World); 15 | 16 | impl Benchmark { 17 | pub fn new() -> Self { 18 | let world = World::default(); 19 | 20 | world 21 | .run( 22 | |mut entities: EntitiesViewMut, 23 | mut transforms: ViewMut>, 24 | mut positions: ViewMut, 25 | mut rotations: ViewMut, 26 | mut velocities: ViewMut| { 27 | for _ in 0..1000 { 28 | entities.add_entity( 29 | ( 30 | &mut transforms, 31 | &mut positions, 32 | &mut rotations, 33 | &mut velocities, 34 | ), 35 | ( 36 | Matrix4::::from_angle_x(Rad(1.2)), 37 | Position(Vector3::unit_x()), 38 | Rotation(Vector3::unit_x()), 39 | Velocity(Vector3::unit_x()), 40 | ), 41 | ); 42 | } 43 | }, 44 | ) 45 | .unwrap(); 46 | 47 | Self(world) 48 | } 49 | 50 | pub fn run(&mut self) { 51 | self.0 52 | .run( 53 | |mut positions: ViewMut, mut transforms: ViewMut>| { 54 | (&mut positions, &mut transforms) 55 | .par_iter() 56 | .for_each(|(mut pos, mut mat)| { 57 | for _ in 0..100 { 58 | *mat = mat.invert().unwrap(); 59 | } 60 | pos.0 = mat.transform_vector(pos.0); 61 | }); 62 | }, 63 | ) 64 | .unwrap(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | pub mod heavy_compute; 4 | pub mod schedule; 5 | pub mod simple_insert; 6 | pub mod simple_iter; 7 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/schedule.rs: -------------------------------------------------------------------------------- 1 | use shipyard::*; 2 | 3 | struct A(f32); 4 | struct B(f32); 5 | struct C(f32); 6 | struct D(f32); 7 | struct E(f32); 8 | 9 | fn ab(mut a: ViewMut, mut b: ViewMut) { 10 | (&mut a, &mut b).iter().for_each(|(mut a, mut b)| { 11 | std::mem::swap(&mut a.0, &mut b.0); 12 | }) 13 | } 14 | 15 | fn cd(mut c: ViewMut, mut d: ViewMut) { 16 | (&mut c, &mut d).iter().for_each(|(mut c, mut d)| { 17 | std::mem::swap(&mut c.0, &mut d.0); 18 | }) 19 | } 20 | 21 | fn ce(mut c: ViewMut, mut e: ViewMut) { 22 | (&mut c, &mut e).iter().for_each(|(mut c, mut e)| { 23 | std::mem::swap(&mut c.0, &mut e.0); 24 | }) 25 | } 26 | 27 | pub struct Benchmark(World); 28 | 29 | impl Benchmark { 30 | pub fn new() -> Self { 31 | let world = World::default(); 32 | 33 | world 34 | .run( 35 | |mut entities: EntitiesViewMut, mut a: ViewMut, mut b: ViewMut| { 36 | for _ in 0..10_000 { 37 | entities.add_entity((&mut a, &mut b), (A(0.0), B(0.0))); 38 | } 39 | }, 40 | ) 41 | .unwrap(); 42 | 43 | world 44 | .run( 45 | |mut entities: EntitiesViewMut, 46 | mut a: ViewMut, 47 | mut b: ViewMut, 48 | mut c: ViewMut| { 49 | for _ in 0..10_000 { 50 | entities.add_entity((&mut a, &mut b, &mut c), (A(0.0), B(0.0), C(0.0))); 51 | } 52 | }, 53 | ) 54 | .unwrap(); 55 | 56 | world 57 | .run( 58 | |mut entities: EntitiesViewMut, 59 | mut a: ViewMut, 60 | mut b: ViewMut, 61 | mut c: ViewMut, 62 | mut d: ViewMut| { 63 | for _ in 0..10_000 { 64 | entities.add_entity( 65 | (&mut a, &mut b, &mut c, &mut d), 66 | (A(0.0), B(0.0), C(0.0), D(0.0)), 67 | ); 68 | } 69 | }, 70 | ) 71 | .unwrap(); 72 | 73 | world 74 | .run( 75 | |mut entities: EntitiesViewMut, 76 | mut a: ViewMut, 77 | mut b: ViewMut, 78 | mut c: ViewMut, 79 | mut e: ViewMut| { 80 | for _ in 0..10_000 { 81 | entities.add_entity( 82 | (&mut a, &mut b, &mut c, &mut e), 83 | (A(0.0), B(0.0), C(0.0), E(0.0)), 84 | ); 85 | } 86 | }, 87 | ) 88 | .unwrap(); 89 | 90 | Workload::builder("run") 91 | .with_system(&ab) 92 | .with_system(&cd) 93 | .with_system(&ce) 94 | .add_to_world(&world) 95 | .unwrap(); 96 | 97 | Self(world) 98 | } 99 | 100 | pub fn run(&mut self) { 101 | self.0.run_workload("run").unwrap(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use shipyard::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark; 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | Self 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let world = World::default(); 25 | 26 | world 27 | .run( 28 | |mut entities: EntitiesViewMut, 29 | mut transforms: ViewMut, 30 | mut positions: ViewMut, 31 | mut rotations: ViewMut, 32 | mut velocities: ViewMut| { 33 | for _ in 0..10_000 { 34 | entities.add_entity( 35 | ( 36 | &mut transforms, 37 | &mut positions, 38 | &mut rotations, 39 | &mut velocities, 40 | ), 41 | ( 42 | Transform(Matrix4::from_scale(1.0)), 43 | Position(Vector3::unit_x()), 44 | Rotation(Vector3::unit_x()), 45 | Velocity(Vector3::unit_x()), 46 | ), 47 | ); 48 | } 49 | }, 50 | ) 51 | .unwrap(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/shipyard/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use shipyard::*; 3 | 4 | #[derive(Copy, Clone)] 5 | struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone)] 8 | struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone)] 11 | struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone)] 14 | struct Velocity(Vector3); 15 | 16 | pub struct Benchmark(World); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Self { 20 | let world = World::default(); 21 | 22 | world 23 | .run( 24 | |mut entities: EntitiesViewMut, 25 | mut transforms: ViewMut, 26 | mut positions: ViewMut, 27 | mut rotations: ViewMut, 28 | mut velocities: ViewMut| { 29 | for _ in 0..10_000 { 30 | entities.add_entity( 31 | ( 32 | &mut transforms, 33 | &mut positions, 34 | &mut rotations, 35 | &mut velocities, 36 | ), 37 | ( 38 | Transform(Matrix4::from_scale(1.0)), 39 | Position(Vector3::unit_x()), 40 | Rotation(Vector3::unit_x()), 41 | Velocity(Vector3::unit_x()), 42 | ), 43 | ); 44 | } 45 | }, 46 | ) 47 | .unwrap(); 48 | 49 | Self(world) 50 | } 51 | 52 | pub fn run(&mut self) { 53 | self.0 54 | .run( 55 | |velocities: View, mut positions: ViewMut| { 56 | (&velocities, &mut positions) 57 | .iter() 58 | .for_each(|(velocity, mut position)| { 59 | position.0 += velocity.0; 60 | }) 61 | }, 62 | ) 63 | .unwrap(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | use cgmath::*; 3 | 4 | pub struct Transform(Matrix4); 5 | pub struct Position(Vector3); 6 | pub struct Rotation(Vector3); 7 | pub struct Velocity(Vector3); 8 | 9 | pub struct Benchmark; 10 | 11 | impl Benchmark { 12 | pub fn new() -> Self { 13 | Self 14 | } 15 | 16 | pub fn run(&mut self) { 17 | let mut all = Components::default(); 18 | let ts = Box::new( 19 | (0..10000).map(|id| Entry::new(id, Transform(Matrix4::::from_scale(1.0)))), 20 | ); 21 | let ps = Box::new((0..10000).map(|id| Entry::new(id, Position(Vector3::unit_x())))); 22 | let rs = Box::new((0..10000).map(|id| Entry::new(id, Rotation(Vector3::unit_x())))); 23 | let vs = Box::new((0..10000).map(|id| Entry::new(id, Velocity(Vector3::unit_x())))); 24 | all.extend::<(Transform, Position, Rotation, Velocity)>((ts, ps, rs, vs)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use apecs::*; 2 | use cgmath::*; 3 | 4 | #[derive(Copy, Clone, Debug)] 5 | pub struct Transform(Matrix4); 6 | 7 | #[derive(Copy, Clone, Debug)] 8 | pub struct Position(Vector3); 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | pub struct Rotation(Vector3); 12 | 13 | #[derive(Copy, Clone, Debug)] 14 | pub struct Velocity(Vector3); 15 | 16 | pub struct Benchmark(Components); 17 | 18 | impl Benchmark { 19 | pub fn new() -> Result { 20 | let ts = Box::new( 21 | (0..10000).map(|id| Entry::new(id, Transform(Matrix4::::from_scale(1.0)))), 22 | ); 23 | let ps = Box::new((0..10000).map(|id| Entry::new(id, Position(Vector3::unit_x())))); 24 | let rs = Box::new((0..10000).map(|id| Entry::new(id, Rotation(Vector3::unit_x())))); 25 | let vs = Box::new((0..10000).map(|id| Entry::new(id, Velocity(Vector3::unit_x())))); 26 | let mut archs = Components::default(); 27 | archs.extend::<(Transform, Position, Rotation, Velocity)>((ts, ps, rs, vs)); 28 | Ok(Self(archs)) 29 | } 30 | 31 | pub fn run(&mut self) { 32 | for (velocity, position) in self.0.query::<(&Velocity, &mut Position)>().iter_mut() { 33 | position.0 += velocity.0; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/add_remove.rs: -------------------------------------------------------------------------------- 1 | use specs::prelude::*; 2 | use specs_derive::*; 3 | #[derive(Component)] 4 | #[storage(VecStorage)] 5 | struct A(f32); 6 | #[derive(Component)] 7 | #[storage(VecStorage)] 8 | struct B(f32); 9 | 10 | pub struct Benchmark(World, Vec); 11 | 12 | impl Benchmark { 13 | pub fn new() -> Self { 14 | let mut world = World::new(); 15 | world.register::(); 16 | world.register::(); 17 | let entities = (0..10000) 18 | .map(|_| world.create_entity().with(A(0.0)).build()) 19 | .collect(); 20 | Self(world, entities) 21 | } 22 | 23 | pub fn run(&mut self) { 24 | let mut b_storage = self.0.write_storage::(); 25 | for entity in &self.1 { 26 | b_storage.insert(*entity, B(0.0)).unwrap(); 27 | } 28 | 29 | for entity in &self.1 { 30 | b_storage.remove(*entity); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/frag_iter.rs: -------------------------------------------------------------------------------- 1 | use specs::prelude::*; 2 | use specs_derive::*; 3 | 4 | macro_rules! create_entities { 5 | ($world:ident; $( $variants:ident ),*) => { 6 | $( 7 | #[derive(Component)] 8 | #[storage(VecStorage)] 9 | struct $variants(f32); 10 | $world.register::<$variants>(); 11 | (0..20) 12 | .for_each(|_| {$world.create_entity().with($variants(0.0)).with(Data(1.0)).build();}); 13 | )* 14 | }; 15 | } 16 | #[derive(Component)] 17 | #[storage(VecStorage)] 18 | struct Data(f32); 19 | 20 | struct FragIterSystem; 21 | 22 | impl<'a> System<'a> for FragIterSystem { 23 | type SystemData = WriteStorage<'a, Data>; 24 | 25 | fn run(&mut self, mut data_storage: Self::SystemData) { 26 | for data in (&mut data_storage).join() { 27 | data.0 *= 2.0; 28 | } 29 | } 30 | } 31 | pub struct Benchmark(World, FragIterSystem); 32 | 33 | impl Benchmark { 34 | pub fn new() -> Self { 35 | let mut world = World::new(); 36 | world.register::(); 37 | create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); 38 | 39 | Self(world, FragIterSystem) 40 | } 41 | 42 | pub fn run(&mut self) { 43 | self.1.run_now(&self.0) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/heavy_compute.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use rayon::prelude::*; 3 | use specs::{prelude::*, ParJoin}; 4 | use specs_derive::*; 5 | 6 | #[derive(Copy, Clone, Component)] 7 | #[storage(VecStorage)] 8 | struct Transform(Matrix4); 9 | #[derive(Copy, Clone, Component)] 10 | #[storage(VecStorage)] 11 | struct Position(Vector3); 12 | 13 | #[derive(Copy, Clone, Component)] 14 | #[storage(VecStorage)] 15 | struct Rotation(Vector3); 16 | 17 | #[derive(Copy, Clone, Component)] 18 | #[storage(VecStorage)] 19 | struct Velocity(Vector3); 20 | 21 | struct HeavyComputeSystem; 22 | 23 | impl<'a> System<'a> for HeavyComputeSystem { 24 | type SystemData = (WriteStorage<'a, Position>, WriteStorage<'a, Transform>); 25 | 26 | fn run(&mut self, (mut pos_store, mut mat_store): Self::SystemData) { 27 | use cgmath::Transform; 28 | (&mut pos_store, &mut mat_store) 29 | .par_join() 30 | .for_each(|(pos, mat)| { 31 | for _ in 0..100 { 32 | mat.0 = mat.0.invert().unwrap(); 33 | } 34 | pos.0 = mat.0.transform_vector(pos.0); 35 | }); 36 | } 37 | } 38 | 39 | pub struct Benchmark(World, HeavyComputeSystem); 40 | 41 | impl Benchmark { 42 | pub fn new() -> Self { 43 | let mut world = World::new(); 44 | world.register::(); 45 | world.register::(); 46 | world.register::(); 47 | world.register::(); 48 | (0..1000).for_each(|_| { 49 | world 50 | .create_entity() 51 | .with(Transform(Matrix4::::from_angle_x(Rad(1.2)))) 52 | .with(Position(Vector3::unit_x())) 53 | .with(Rotation(Vector3::unit_x())) 54 | .with(Velocity(Vector3::unit_x())) 55 | .build(); 56 | }); 57 | 58 | Self(world, HeavyComputeSystem) 59 | } 60 | 61 | pub fn run(&mut self) { 62 | self.1.run_now(&self.0); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_remove; 2 | pub mod frag_iter; 3 | pub mod heavy_compute; 4 | pub mod schedule; 5 | pub mod simple_insert; 6 | pub mod simple_iter; 7 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/schedule.rs: -------------------------------------------------------------------------------- 1 | use specs::prelude::*; 2 | use specs_derive::*; 3 | #[derive(Component)] 4 | #[storage(VecStorage)] 5 | struct A(f32); 6 | #[derive(Component)] 7 | #[storage(VecStorage)] 8 | struct B(f32); 9 | #[derive(Component)] 10 | #[storage(VecStorage)] 11 | struct C(f32); 12 | #[derive(Component)] 13 | #[storage(VecStorage)] 14 | struct D(f32); 15 | #[derive(Component)] 16 | #[storage(VecStorage)] 17 | struct E(f32); 18 | 19 | struct ABSystem; 20 | 21 | impl<'a> System<'a> for ABSystem { 22 | type SystemData = (WriteStorage<'a, A>, WriteStorage<'a, B>); 23 | 24 | fn run(&mut self, (mut a_store, mut b_store): Self::SystemData) { 25 | for (a, b) in (&mut a_store, &mut b_store).join() { 26 | std::mem::swap(&mut a.0, &mut b.0); 27 | } 28 | } 29 | } 30 | 31 | struct CDSystem; 32 | 33 | impl<'a> System<'a> for CDSystem { 34 | type SystemData = (WriteStorage<'a, C>, WriteStorage<'a, D>); 35 | 36 | fn run(&mut self, (mut c_store, mut d_store): Self::SystemData) { 37 | for (c, d) in (&mut c_store, &mut d_store).join() { 38 | std::mem::swap(&mut c.0, &mut d.0); 39 | } 40 | } 41 | } 42 | struct CESystem; 43 | 44 | impl<'a> System<'a> for CESystem { 45 | type SystemData = (WriteStorage<'a, C>, WriteStorage<'a, E>); 46 | 47 | fn run(&mut self, (mut c_store, mut e_store): Self::SystemData) { 48 | for (c, e) in (&mut c_store, &mut e_store).join() { 49 | std::mem::swap(&mut c.0, &mut e.0); 50 | } 51 | } 52 | } 53 | 54 | pub struct Benchmark<'a>(World, Dispatcher<'a, 'a>); 55 | 56 | impl Benchmark<'_> { 57 | pub fn new() -> Self { 58 | let mut world = World::new(); 59 | world.register::(); 60 | world.register::(); 61 | world.register::(); 62 | world.register::(); 63 | world.register::(); 64 | (0..10000).for_each(|_| { 65 | world.create_entity().with(A(0.0)).with(B(0.0)).build(); 66 | }); 67 | (0..10000).for_each(|_| { 68 | world 69 | .create_entity() 70 | .with(A(0.0)) 71 | .with(B(0.0)) 72 | .with(C(0.0)) 73 | .build(); 74 | }); 75 | (0..10000).for_each(|_| { 76 | world 77 | .create_entity() 78 | .with(A(0.0)) 79 | .with(B(0.0)) 80 | .with(C(0.0)) 81 | .with(D(0.0)) 82 | .build(); 83 | }); 84 | (0..10000).for_each(|_| { 85 | world 86 | .create_entity() 87 | .with(A(0.0)) 88 | .with(B(0.0)) 89 | .with(C(0.0)) 90 | .with(E(0.0)) 91 | .build(); 92 | }); 93 | 94 | let dispatcher = DispatcherBuilder::new() 95 | .with(ABSystem, "ab", &[]) 96 | .with(CDSystem, "cd", &[]) 97 | .with(CESystem, "ce", &[]) 98 | .build(); 99 | 100 | Self(world, dispatcher) 101 | } 102 | 103 | pub fn run(&mut self) { 104 | self.1.dispatch_par(&self.0) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/simple_insert.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use specs::prelude::*; 3 | use specs_derive::*; 4 | 5 | #[derive(Copy, Clone, Component)] 6 | #[storage(VecStorage)] 7 | struct Transform(Matrix4); 8 | #[derive(Copy, Clone, Component)] 9 | #[storage(VecStorage)] 10 | struct Position(Vector3); 11 | 12 | #[derive(Copy, Clone, Component)] 13 | #[storage(VecStorage)] 14 | struct Rotation(Vector3); 15 | 16 | #[derive(Copy, Clone, Component)] 17 | #[storage(VecStorage)] 18 | struct Velocity(Vector3); 19 | 20 | pub struct Benchmark; 21 | 22 | impl Benchmark { 23 | pub fn new() -> Self { 24 | Self 25 | } 26 | 27 | pub fn run(&mut self) { 28 | let mut world = World::new(); 29 | world.register::(); 30 | world.register::(); 31 | world.register::(); 32 | world.register::(); 33 | (0..10000).for_each(|_| { 34 | world 35 | .create_entity() 36 | .with(Transform(Matrix4::::from_scale(1.0))) 37 | .with(Position(Vector3::unit_x())) 38 | .with(Rotation(Vector3::unit_x())) 39 | .with(Velocity(Vector3::unit_x())) 40 | .build(); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/benchmarks/benches/specs/simple_iter.rs: -------------------------------------------------------------------------------- 1 | use cgmath::*; 2 | use specs::prelude::*; 3 | use specs_derive::*; 4 | 5 | #[derive(Copy, Clone, Component)] 6 | #[storage(VecStorage)] 7 | struct Transform(Matrix4); 8 | #[derive(Copy, Clone, Component)] 9 | #[storage(VecStorage)] 10 | struct Position(Vector3); 11 | 12 | #[derive(Copy, Clone, Component)] 13 | #[storage(VecStorage)] 14 | struct Rotation(Vector3); 15 | 16 | #[derive(Copy, Clone, Component)] 17 | #[storage(VecStorage)] 18 | struct Velocity(Vector3); 19 | struct SimpleIterSystem; 20 | 21 | impl<'a> System<'a> for SimpleIterSystem { 22 | type SystemData = (WriteStorage<'a, Velocity>, WriteStorage<'a, Position>); 23 | 24 | fn run(&mut self, (mut velocity_storage, mut position_storage): Self::SystemData) { 25 | for (velocity, position) in (&mut velocity_storage, &mut position_storage).join() { 26 | position.0 += velocity.0; 27 | } 28 | } 29 | } 30 | pub struct Benchmark(World, SimpleIterSystem); 31 | 32 | impl Benchmark { 33 | pub fn new() -> Self { 34 | let mut world = World::new(); 35 | world.register::(); 36 | world.register::(); 37 | world.register::(); 38 | world.register::(); 39 | (0..10000).for_each(|_| { 40 | world 41 | .create_entity() 42 | .with(Transform(Matrix4::::from_angle_x(Rad(1.2)))) 43 | .with(Position(Vector3::unit_x())) 44 | .with(Rotation(Vector3::unit_x())) 45 | .with(Velocity(Vector3::unit_x())) 46 | .build(); 47 | }); 48 | 49 | Self(world, SimpleIterSystem) 50 | } 51 | 52 | pub fn run(&mut self) { 53 | self.1.run_now(&self.0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/benchmarks/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("hello. this is unused"); 3 | } 4 | 5 | #[cfg(test)] 6 | mod test { 7 | #[test] 8 | #[should_panic] 9 | fn hecs_repeated_component() { 10 | use hecs::*; 11 | 12 | let mut world = World::new(); 13 | let e = world.spawn((0.0f32, 1.0f32, 2.0f32)); 14 | assert_eq!(2.0f32, *world.get::<&f32>(e).unwrap()); 15 | } 16 | 17 | #[test] 18 | #[should_panic] 19 | fn legion_repeated_component() { 20 | use legion::*; 21 | 22 | let mut world = World::default(); 23 | let e = world.push((0.0f32, 1.0f32, 2.0f32)); 24 | let entry = world.entry(e).unwrap(); 25 | assert_eq!(2.0f32, *entry.get_component::().unwrap()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "^1.0" 10 | chrono = { version = "^0.4", features = ["serde"] } 11 | clap = { version = "^3.0", features = ["derive"] } 12 | duct = "^0.13" 13 | nom = "^7.1" 14 | serde = { version = "^1.0", features = ["derive"] } 15 | serde_yaml = "^0.8" 16 | tracing = "^0.1" 17 | tracing-subscriber = "^0.3" 18 | -------------------------------------------------------------------------------- /crates/xtask/src/bench.rs: -------------------------------------------------------------------------------- 1 | //! Build Maxwell and its dependencies. 2 | use anyhow::Context; 3 | use chrono::{offset::Utc, DateTime}; 4 | use clap::Parser; 5 | use nom::{bytes::complete as bytes, character::complete as character, combinator, IResult}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::path::{Path, PathBuf}; 8 | 9 | #[derive(Parser)] 10 | #[clap(trailing_var_arg = true)] 11 | /// Benchmark Maxwell 12 | pub struct Bench { 13 | /// Path to the long-lived benchmark yaml file 14 | #[clap(long)] 15 | benchmark_file: Option, 16 | 17 | /// Whether to append this run to the historical benchmark file 18 | #[clap(long)] 19 | append: bool, 20 | } 21 | 22 | impl Bench { 23 | fn get_store(path: impl AsRef) -> anyhow::Result> { 24 | if path.as_ref().is_file() { 25 | let contents = std::fs::read_to_string(path.as_ref()).context(format!( 26 | "could not read benchmark file: {}", 27 | path.as_ref().display() 28 | ))?; 29 | let benchmark_store: HistoricalBenchmarks = 30 | serde_yaml::from_str(&contents).context("could not parse benchmark file")?; 31 | Ok(Some(benchmark_store)) 32 | } else { 33 | Ok(None) 34 | } 35 | } 36 | 37 | pub fn run(&self) -> anyhow::Result<()> { 38 | tracing::info!("running benchmarks",); 39 | 40 | let mut store = if let Some(file) = self.benchmark_file.as_ref() { 41 | Self::get_store(file)?.unwrap_or_default() 42 | } else { 43 | HistoricalBenchmarks::default() 44 | }; 45 | store.sort(); 46 | 47 | let date = Utc::now(); 48 | if let Some(prev) = store.find_last() { 49 | let elapsed = date - prev.date; 50 | let days = elapsed.num_minutes() as f32 / (24.0 * 60.0); 51 | tracing::info!("{:.2}days since last stored event", days); 52 | } 53 | 54 | let output = duct::cmd!( 55 | "cargo", 56 | "bench", 57 | "--bench", 58 | "benchmark", 59 | "--", 60 | "--output-format", 61 | "bencher" 62 | ) 63 | .stdout_capture() 64 | .run() 65 | .context("could not run benchmarks")?; 66 | let parser_input = String::from_utf8(output.stdout).context("output is not utf8")?; 67 | 68 | let (_, results) = parse_benchmarks(&parser_input).unwrap(); 69 | tracing::debug!("parsed {} results", results.len()); 70 | 71 | for benchmark in results.into_iter() { 72 | let historical_benchmark = HistoricalBenchmark { 73 | date, 74 | benchmark, 75 | tags: vec![], 76 | }; 77 | if let Some(prev) = store.find_latest_by_name(&historical_benchmark.benchmark.name) { 78 | let change_ns = historical_benchmark.benchmark.ns - prev.benchmark.ns; 79 | let change_percent = (change_ns as f32 / prev.benchmark.ns as f32) * 100.0; 80 | if change_percent >= 10.0 { 81 | tracing::error!( 82 | "{} {} +{:.2}%", 83 | historical_benchmark.benchmark.name, 84 | historical_benchmark.benchmark.ns, 85 | change_percent 86 | ); 87 | } else if change_percent >= 4.0 { 88 | tracing::warn!( 89 | "{} {} +{:.2}%", 90 | historical_benchmark.benchmark.name, 91 | historical_benchmark.benchmark.ns, 92 | change_percent 93 | ); 94 | } else { 95 | tracing::info!( 96 | "{} {} {:.2}%", 97 | historical_benchmark.benchmark.name, 98 | historical_benchmark.benchmark.ns, 99 | change_percent 100 | ); 101 | } 102 | } 103 | store.benchmarks.push(historical_benchmark); 104 | } 105 | 106 | if self.append { 107 | if let Some(file) = self.benchmark_file.as_ref() { 108 | let contents = 109 | serde_yaml::to_string(&store).context("could not serialize store")?; 110 | std::fs::write(file, contents).context("could not write benchmark file")?; 111 | tracing::info!("saved benchmark file '{}'", file.display()); 112 | } 113 | } 114 | 115 | Ok(()) 116 | } 117 | } 118 | 119 | /// Parse the beginning of a benchmark result and return the benchmark 120 | /// name. 121 | fn parse_bench_result_start(i: &str) -> IResult<&str, &str> { 122 | let (i, _) = bytes::tag("test ")(i)?; 123 | let (i, name) = bytes::take_while(|c| c != ' ')(i)?; 124 | let (i, _) = character::char(' ')(i)?; 125 | let (i, _) = bytes::tag("... ")(i)?; 126 | Ok((i, name)) 127 | } 128 | 129 | /// Parse the rest of a benchmark result and return the benchmark 130 | /// nanos per iter and range. 131 | fn parse_bench_result_rest(i: &str) -> IResult<&str, (i64, i64)> { 132 | let (i, _) = bytes::tag("bench:")(i)?; 133 | let (i, _) = bytes::take_while(|c: char| c == ' ' || c == '\t')(i)?; 134 | let (i, ns) = character::i64(i)?; 135 | let (i, _) = bytes::tag(" ns/iter (+/- ")(i)?; 136 | let (i, range) = character::i64(i)?; 137 | let (i, _) = character::char(')')(i)?; 138 | Ok((i, (ns, range))) 139 | } 140 | 141 | /// Parse a bench result. 142 | fn parse_bench_result(i: &str) -> IResult<&str, (&str, i64, i64)> { 143 | let (i, _) = bytes::take_until("test ")(i)?; 144 | let (i, name) = parse_bench_result_start(i)?; 145 | let (i, _) = bytes::take_until("bench:")(i)?; 146 | let (i, (ns, range)) = parse_bench_result_rest(i)?; 147 | Ok((i, (name, ns, range))) 148 | } 149 | 150 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 151 | pub struct Benchmark { 152 | pub name: String, 153 | pub ns: i64, 154 | pub range: i64, 155 | } 156 | 157 | fn parse_benchmarks(i: &str) -> IResult<&str, Vec> { 158 | let mut it = combinator::iterator(i, parse_bench_result); 159 | let parsed = it 160 | .map(|(name, ns, range)| Benchmark { 161 | name: name.to_string(), 162 | ns, 163 | range, 164 | }) 165 | .collect::>(); 166 | let (i, _) = it.finish()?; 167 | Ok((i, parsed)) 168 | } 169 | 170 | #[derive(Serialize, Deserialize)] 171 | pub struct HistoricalBenchmark { 172 | date: DateTime, 173 | benchmark: Benchmark, 174 | tags: Vec<(String, String)>, 175 | } 176 | 177 | #[derive(Default, Serialize, Deserialize)] 178 | pub struct HistoricalBenchmarks { 179 | benchmarks: Vec, 180 | } 181 | 182 | impl HistoricalBenchmarks { 183 | fn sort(&mut self) { 184 | self.benchmarks.sort_by(|a, b| a.date.cmp(&b.date)); 185 | } 186 | 187 | fn find_latest_by_name(&self, name: &str) -> Option<&HistoricalBenchmark> { 188 | for benchmark in self.benchmarks.iter().rev() { 189 | if benchmark.benchmark.name == name { 190 | return Some(benchmark); 191 | } 192 | } 193 | 194 | None 195 | } 196 | 197 | fn find_last(&self) -> Option<&HistoricalBenchmark> { 198 | self.benchmarks.last() 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | mod test { 204 | use super::*; 205 | 206 | const BENCH_LINE: &str = 207 | "test presize_cubic_cpu/400 ... bench: 17041704 ns/iter (+/- 349284)"; 208 | 209 | #[test] 210 | fn parse_bench() { 211 | let (i, name) = parse_bench_result_start(BENCH_LINE).unwrap(); 212 | assert_eq!(name, "presize_cubic_cpu/400"); 213 | 214 | let (_, (ns, range)) = parse_bench_result_rest(i).unwrap(); 215 | assert_eq!(ns, 17041704); 216 | assert_eq!(range, 349284); 217 | } 218 | 219 | #[test] 220 | fn parse_bench_lines() { 221 | let lines:String = vec![ 222 | "Gnuplot not found, using plotters backend", 223 | "test presize_cubic_cpu/400 ... bench: 17041704 ns/iter (+/- 349284)", 224 | r#"test narrative-maxwell/projects/create ... initializing datadog metrics, writing to stdout: [("service", "maxwell"), ("maxwell.inference.backend", "TFLite"), ("maxwell.cargo.version", "3.2.0"), ("maxwell.git.hash", "7578147d"), ("maxwell.session.id", "65f99ca8-be65-4760-99d8-a15a7bc6b5c5"), ("maxwell.environment", "DEVELOPMENT-STANDALONE")]"#, 225 | r#"initializing datadog metrics, writing to stdout: [("service", "maxwell"), ("maxwell.inference.backend", "TFLite"), ("maxwell.cargo.version", "3.2.0"), ("maxwell.git.hash", "7578147d"), ("maxwell.session.id", "3822e37c-9106-4d30-805a-35ea2f433b24"), ("maxwell.environment", "DEVELOPMENT-STANDALONE")]"#, 226 | "bench: 4049764 ns/iter (+/- 3608244)", 227 | ].join("\n"); 228 | 229 | let (_, results) = parse_benchmarks(&lines).unwrap(); 230 | assert_eq!(results.len(), 2); 231 | assert_eq!( 232 | results[0], 233 | Benchmark { 234 | name: "presize_cubic_cpu/400".to_string(), 235 | ns: 17041704, 236 | range: 349284 237 | } 238 | ); 239 | assert_eq!( 240 | results[1], 241 | Benchmark { 242 | name: "narrative-maxwell/projects/create".to_string(), 243 | ns: 4049764, 244 | range: 3608244 245 | } 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /crates/xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Helper tasks for the `apecs` project. 2 | //! 3 | //! Run `cargo xtask help` for more info. 4 | use clap::{Parser, Subcommand}; 5 | use tracing::Level; 6 | use tracing_subscriber::FmtSubscriber; 7 | 8 | mod bench; 9 | use bench::Bench; 10 | 11 | #[derive(Parser)] 12 | #[clap(author, version, about, subcommand_required = true)] 13 | struct Cli { 14 | /// Sets the verbosity level 15 | #[clap(short, parse(from_occurrences))] 16 | verbosity: usize, 17 | /// The task to run 18 | #[clap(subcommand)] 19 | command: Command, 20 | } 21 | 22 | #[derive(Subcommand)] 23 | enum Command { 24 | /// Run benchmarks 25 | Bench(Bench), 26 | } 27 | 28 | fn main() -> anyhow::Result<()> { 29 | let cli = Cli::parse(); 30 | 31 | let level = match cli.verbosity { 32 | 0 => Level::WARN, 33 | 1 => Level::INFO, 34 | 2 => Level::DEBUG, 35 | _ => Level::TRACE, 36 | }; 37 | let subscriber = FmtSubscriber::builder().with_max_level(level).finish(); 38 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 39 | 40 | match cli.command { 41 | Command::Bench(bench) => bench.run()?, 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /manual/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | *~undotree~ 3 | -------------------------------------------------------------------------------- /manual/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Schell Carl Scivally"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The APECS Manual" 7 | -------------------------------------------------------------------------------- /manual/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Introduction](./introduction.md) 4 | - [APECS](./apecs.md) 5 | -------------------------------------------------------------------------------- /manual/src/apecs.md: -------------------------------------------------------------------------------- 1 | # APECS 2 | 3 | APECS is an **A**syncronous **P**arallel **ECS**. 4 | 5 | This supports traditional synchronous systems and less traditional asyncronous 6 | systems and can spawn asynchronous operations. 7 | 8 | Component storage is archetypal with a nice query API. 9 | 10 | ## Asyncronous 11 | 12 | Most ECSs are polling (`legion` example code): 13 | 14 | ```rust,ignore 15 | // a system fn which loops through Position and Velocity components, and reads 16 | // the Time shared resource. 17 | #[system(for_each)] 18 | fn update_positions(pos: &mut Position, vel: &Velocity, #[resource] time: &Time) { 19 | pos.x += vel.dx * time.elapsed_seconds; 20 | pos.y += vel.dy * time.elapsed_seconds; 21 | } 22 | 23 | // construct a schedule (you should do this on init) 24 | let mut schedule = Schedule::builder() 25 | .add_system(update_positions_system()) 26 | .build(); 27 | 28 | // run our schedule (you should do this each update) 29 | schedule.execute(&mut world, &mut resources); 30 | ``` 31 | 32 | * these systems cannot change over time (without public mutable access to `schedule`) 33 | 34 | APECS supports permanent and temporary polling systems by returning `ShouldContinue`: 35 | ```rust,ignore 36 | use apecs::*; 37 | 38 | #[derive(Default)] 39 | struct DataOne(u32); 40 | 41 | let mut world = world::World::default(); 42 | world 43 | .with_resource(DataOne(0)) 44 | .unwrap() 45 | .with_system("sys", |mut data: Write| -> system::ShouldContinue { 46 | log::info!("running sys"); 47 | data.0 += 1; 48 | system::ShouldContinue::Ok 49 | }) 50 | .unwrap(); 51 | 52 | world.tick(); 53 | world.tick(); 54 | 55 | let data: Read = world.fetch().unwrap(); 56 | assert_eq!(2, *data.0); 57 | ``` 58 | 59 | APECS supports permanent or temporary **async systems** as well: 60 | ```rust,ignore 61 | use apecs::*; 62 | 63 | #[derive(Default)] 64 | struct DataOne(u32); 65 | 66 | let mut world = world::World::default(); 67 | world 68 | .with_resource(DataOne(0)) 69 | .unwrap() 70 | .with_async_system("sys", |facade: Facade| async move { 71 | log::info!("running sys"); 72 | 73 | loop { 74 | facade.visit(|mut data: Write| { 75 | data.0 += 1; 76 | Ok(()) 77 | }).await?; 78 | } 79 | 80 | Ok(()) 81 | }); 82 | 83 | world.tick(); 84 | world.tick(); 85 | world.tick(); 86 | 87 | let data: Read = world.fetch().unwrap(); 88 | assert_eq!(2, *data.0); 89 | ``` 90 | 91 | Note the **three** world ticks here instead of the previous two. 92 | 93 | APECS also supports running plain old futures: 94 | ```rust,ignore 95 | use apecs::*; 96 | let mut world = world::World::default(); 97 | world.with_async(async { 98 | log::info!("going to wait for a bit"); 99 | wait_for(std::time::Duration::from_secs(20)).await; 100 | log::info!("that's enough! i'm done!"); 101 | }); 102 | ``` 103 | 104 | ## Parallel 105 | 106 | APECS supports "outer parallelism" (that is, running systems in parallel) for syncronous systems: 107 | ```rust,ignore 108 | // ... 109 | 110 | let mut world = World::default(); 111 | world 112 | .with_resource(a_store) 113 | .unwrap() 114 | .with_resource(b_store) 115 | .unwrap() 116 | //... 117 | .with_system("ab", ab_system) 118 | .unwrap() 119 | .with_system("cd", cd_system) 120 | .unwrap() 121 | .with_system("ce", ce_system) 122 | .unwrap() 123 | .with_sync_systems_run_in_parallel(true); 124 | ``` 125 | 126 | It also supports "inner parallelism" (that is, querying entities and components within a system in parallel): 127 | ```rust,ignore 128 | fn system(data: Query<(&mut String, &u32)>) -> ShouldContinue { 129 | data.query().par_iter_mut().for_each(|(abc, num)| { 130 | *abc = format!("{:.2}", num); 131 | }); 132 | } 133 | ``` 134 | 135 | ## Examples 136 | 137 | see benchmarks 138 | 139 | ## Benchmarks 140 | 141 | [local file benchmarks](file:///Users/schell/code/apecs/target/criterion/report/index.html) 142 | -------------------------------------------------------------------------------- /manual/src/chapter_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 2 | -------------------------------------------------------------------------------- /manual/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | Welcome to APECS! 3 | 4 | APECS is an ECS library written in Rust. 5 | 6 | ## What is an ECS library? 7 | 8 | ECS stands for Entity Component System. 9 | 10 | * mostly about program architecture 11 | * breaks up the program into entities+components and then systems/functions 12 | that operate on them separately 13 | * all state is stored in the "world" and a subset is accessed by each system 14 | 15 | ### entities & components 16 | 17 | * entities are simple ids, ie `usize` 18 | 19 | * a component is one type of data 20 | - `struct Postion { x: f32, y: f32 }` 21 | - `struct Velocity { x: f32, y: f32 }` 22 | - `struct Acceleration { x: f32, y: f32 }` 23 | 24 | * joins - like a database, join columns of components that share the same entity into an 25 | iterator: 26 | ```rust,ignore 27 | for (camera, may_name, layers, stage) in ( 28 | &mut self.cameras, 29 | (&self.names).maybe(), 30 | (&self.layers).maybe(), 31 | &self.stages, 32 | ).join() { 33 | //use `camera` mutably etc... 34 | } 35 | ``` 36 | * parallel joins (inner parallelism) 37 | 38 | #### Why not use an in-memory database? 39 | 40 | * iteration speed 41 | - special entity+component stores in contiguous memory 42 | * sharing access 43 | * you would have to roll-your-own systems, scheduling and data sharing (more on that later) 44 | 45 | ### systems 46 | 47 | * at its core, a system is just a function that mutates the world 48 | 49 | The "system" in ECS is doing a _lot_ of heavy lifting. We're really talking about _running_ systems, 50 | and everything it takes to support running many systems efficiently. 51 | 52 | * many ECS libraries provide a data sharing setup to support running systems 53 | * each datum accessed by a system is called a "resource" 54 | * component columns can be a "resource", therefore **mutable** queries of disjoint columns are separate "resources" and can 55 | run in parallel 56 | * queries without mutation can be run in parallel even with overlapping columns 57 | * system scheduling (outer parallelism) 58 | 59 | ## Why use an ECS library? 60 | 61 | * supports lots and lots of loosely related objects (entities+components) 62 | * supports complex data sharing without locks 63 | * very flexible, easier to add features and change systems 64 | * coming from games it offers very predictable performance and it's **easy 65 | to profile** 66 | 67 | ### More on-topic reading 68 | * [ECS FAQ](https://github.com/SanderMertens/ecs-faq) 69 | -------------------------------------------------------------------------------- /manual/theme/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | @import 'variables.css'; 4 | 5 | ::-webkit-scrollbar { 6 | background: var(--bg); 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | background: var(--scrollbar); 10 | } 11 | html { 12 | scrollbar-color: var(--scrollbar) var(--bg); 13 | } 14 | #searchresults a, 15 | .content a:link, 16 | a:visited, 17 | a > .hljs { 18 | color: var(--links); 19 | } 20 | 21 | /* Menu Bar */ 22 | 23 | #menu-bar, 24 | #menu-bar-hover-placeholder { 25 | z-index: 101; 26 | margin: auto calc(0px - var(--page-padding)); 27 | } 28 | #menu-bar { 29 | position: relative; 30 | display: flex; 31 | flex-wrap: wrap; 32 | background-color: var(--bg); 33 | border-bottom-color: var(--bg); 34 | border-bottom-width: 1px; 35 | border-bottom-style: solid; 36 | } 37 | #menu-bar.sticky, 38 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 39 | .js #menu-bar:hover, 40 | .js.sidebar-visible #menu-bar { 41 | position: -webkit-sticky; 42 | position: sticky; 43 | top: 0 !important; 44 | } 45 | #menu-bar-hover-placeholder { 46 | position: sticky; 47 | position: -webkit-sticky; 48 | top: 0; 49 | height: var(--menu-bar-height); 50 | } 51 | #menu-bar.bordered { 52 | border-bottom-color: var(--table-border-color); 53 | } 54 | #menu-bar i, #menu-bar .icon-button { 55 | position: relative; 56 | padding: 0 8px; 57 | z-index: 10; 58 | line-height: var(--menu-bar-height); 59 | cursor: pointer; 60 | transition: color 0.5s; 61 | } 62 | @media only screen and (max-width: 420px) { 63 | #menu-bar i, #menu-bar .icon-button { 64 | padding: 0 5px; 65 | } 66 | } 67 | 68 | .icon-button { 69 | border: none; 70 | background: none; 71 | padding: 0; 72 | color: inherit; 73 | } 74 | .icon-button i { 75 | margin: 0; 76 | } 77 | 78 | .right-buttons { 79 | margin: 0 15px; 80 | } 81 | .right-buttons a { 82 | text-decoration: none; 83 | } 84 | 85 | .left-buttons { 86 | display: flex; 87 | margin: 0 5px; 88 | } 89 | .no-js .left-buttons { 90 | display: none; 91 | } 92 | 93 | .menu-title { 94 | display: inline-block; 95 | font-weight: 200; 96 | font-size: 2.4rem; 97 | line-height: var(--menu-bar-height); 98 | text-align: center; 99 | margin: 0; 100 | flex: 1; 101 | white-space: nowrap; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | } 105 | .js .menu-title { 106 | cursor: pointer; 107 | } 108 | 109 | .menu-bar, 110 | .menu-bar:visited, 111 | .nav-chapters, 112 | .nav-chapters:visited, 113 | .mobile-nav-chapters, 114 | .mobile-nav-chapters:visited, 115 | .menu-bar .icon-button, 116 | .menu-bar a i { 117 | color: var(--icons); 118 | } 119 | 120 | .menu-bar i:hover, 121 | .menu-bar .icon-button:hover, 122 | .nav-chapters:hover, 123 | .mobile-nav-chapters i:hover { 124 | color: var(--icons-hover); 125 | } 126 | 127 | /* Nav Icons */ 128 | 129 | .nav-chapters { 130 | font-size: 2.5em; 131 | text-align: center; 132 | text-decoration: none; 133 | 134 | position: fixed; 135 | top: 0; 136 | bottom: 0; 137 | margin: 0; 138 | max-width: 150px; 139 | min-width: 90px; 140 | 141 | display: flex; 142 | justify-content: center; 143 | align-content: center; 144 | flex-direction: column; 145 | 146 | transition: color 0.5s, background-color 0.5s; 147 | } 148 | 149 | .nav-chapters:hover { 150 | text-decoration: none; 151 | background-color: var(--theme-hover); 152 | transition: background-color 0.15s, color 0.15s; 153 | } 154 | 155 | .nav-wrapper { 156 | margin-top: 50px; 157 | display: none; 158 | } 159 | 160 | .mobile-nav-chapters { 161 | font-size: 2.5em; 162 | text-align: center; 163 | text-decoration: none; 164 | width: 90px; 165 | border-radius: 5px; 166 | background-color: var(--sidebar-bg); 167 | } 168 | 169 | .previous { 170 | float: left; 171 | } 172 | 173 | .next { 174 | float: right; 175 | right: var(--page-padding); 176 | } 177 | 178 | @media only screen and (max-width: 1080px) { 179 | .nav-wide-wrapper { display: none; } 180 | .nav-wrapper { display: block; } 181 | } 182 | 183 | @media only screen and (max-width: 1380px) { 184 | .sidebar-visible .nav-wide-wrapper { display: none; } 185 | .sidebar-visible .nav-wrapper { display: block; } 186 | } 187 | 188 | /* Inline code */ 189 | 190 | :not(pre) > .hljs { 191 | display: inline; 192 | padding: 0.1em 0.3em; 193 | border-radius: 3px; 194 | } 195 | 196 | :not(pre):not(a) > .hljs { 197 | color: var(--inline-code-color); 198 | overflow-x: initial; 199 | } 200 | 201 | a:hover > .hljs { 202 | text-decoration: underline; 203 | } 204 | 205 | pre { 206 | position: relative; 207 | } 208 | pre > .buttons { 209 | position: absolute; 210 | z-index: 100; 211 | right: 5px; 212 | top: 5px; 213 | 214 | color: var(--sidebar-fg); 215 | cursor: pointer; 216 | } 217 | pre > .buttons :hover { 218 | color: var(--sidebar-active); 219 | } 220 | pre > .buttons i { 221 | margin-left: 8px; 222 | } 223 | pre > .buttons button { 224 | color: inherit; 225 | background: transparent; 226 | border: none; 227 | cursor: inherit; 228 | } 229 | pre > .result { 230 | margin-top: 10px; 231 | } 232 | 233 | /* Search */ 234 | 235 | #searchresults a { 236 | text-decoration: none; 237 | } 238 | 239 | mark { 240 | border-radius: 2px; 241 | padding: 0 3px 1px 3px; 242 | margin: 0 -3px -1px -3px; 243 | background-color: var(--search-mark-bg); 244 | transition: background-color 300ms linear; 245 | cursor: pointer; 246 | } 247 | 248 | mark.fade-out { 249 | background-color: rgba(0,0,0,0) !important; 250 | cursor: auto; 251 | } 252 | 253 | .searchbar-outer { 254 | margin-left: auto; 255 | margin-right: auto; 256 | max-width: var(--content-max-width); 257 | } 258 | 259 | #searchbar { 260 | width: 100%; 261 | margin: 5px auto 0px auto; 262 | padding: 10px 16px; 263 | transition: box-shadow 300ms ease-in-out; 264 | border: 1px solid var(--searchbar-border-color); 265 | border-radius: 3px; 266 | background-color: var(--searchbar-bg); 267 | color: var(--searchbar-fg); 268 | } 269 | #searchbar:focus, 270 | #searchbar.active { 271 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 272 | } 273 | 274 | .searchresults-header { 275 | font-weight: bold; 276 | font-size: 1em; 277 | padding: 18px 0 0 5px; 278 | color: var(--searchresults-header-fg); 279 | } 280 | 281 | .searchresults-outer { 282 | margin-left: auto; 283 | margin-right: auto; 284 | max-width: var(--content-max-width); 285 | border-bottom: 1px dashed var(--searchresults-border-color); 286 | } 287 | 288 | ul#searchresults { 289 | list-style: none; 290 | padding-left: 20px; 291 | } 292 | ul#searchresults li { 293 | margin: 10px 0px; 294 | padding: 2px; 295 | border-radius: 2px; 296 | } 297 | ul#searchresults li.focus { 298 | background-color: var(--searchresults-li-bg); 299 | } 300 | ul#searchresults span.teaser { 301 | display: block; 302 | clear: both; 303 | margin: 5px 0 0 20px; 304 | font-size: 0.8em; 305 | } 306 | ul#searchresults span.teaser em { 307 | font-weight: bold; 308 | font-style: normal; 309 | } 310 | 311 | /* Sidebar */ 312 | 313 | .sidebar { 314 | position: fixed; 315 | left: 0; 316 | top: 0; 317 | bottom: 0; 318 | width: var(--sidebar-width); 319 | font-size: 0.875em; 320 | box-sizing: border-box; 321 | -webkit-overflow-scrolling: touch; 322 | overscroll-behavior-y: contain; 323 | background-color: var(--sidebar-bg); 324 | color: var(--sidebar-fg); 325 | } 326 | .sidebar-resizing { 327 | -moz-user-select: none; 328 | -webkit-user-select: none; 329 | -ms-user-select: none; 330 | user-select: none; 331 | } 332 | .js:not(.sidebar-resizing) .sidebar { 333 | transition: transform 0.3s; /* Animation: slide away */ 334 | } 335 | .sidebar code { 336 | line-height: 2em; 337 | } 338 | .sidebar .sidebar-scrollbox { 339 | overflow-y: auto; 340 | position: absolute; 341 | top: 0; 342 | bottom: 0; 343 | left: 0; 344 | right: 0; 345 | padding: 10px 10px; 346 | } 347 | .sidebar .sidebar-resize-handle { 348 | position: absolute; 349 | cursor: col-resize; 350 | width: 0; 351 | right: 0; 352 | top: 0; 353 | bottom: 0; 354 | } 355 | .js .sidebar .sidebar-resize-handle { 356 | cursor: col-resize; 357 | width: 5px; 358 | } 359 | .sidebar-hidden .sidebar { 360 | transform: translateX(calc(0px - var(--sidebar-width))); 361 | } 362 | .sidebar::-webkit-scrollbar { 363 | background: var(--sidebar-bg); 364 | } 365 | .sidebar::-webkit-scrollbar-thumb { 366 | background: var(--scrollbar); 367 | } 368 | 369 | .sidebar-visible .page-wrapper { 370 | transform: translateX(var(--sidebar-width)); 371 | } 372 | @media only screen and (min-width: 620px) { 373 | .sidebar-visible .page-wrapper { 374 | transform: none; 375 | margin-left: var(--sidebar-width); 376 | } 377 | } 378 | 379 | .chapter { 380 | list-style: none outside none; 381 | padding-left: 0; 382 | line-height: 2.2em; 383 | } 384 | 385 | .chapter ol { 386 | width: 100%; 387 | } 388 | 389 | .chapter li { 390 | display: flex; 391 | color: var(--sidebar-non-existant); 392 | } 393 | .chapter li a { 394 | display: block; 395 | padding: 0; 396 | text-decoration: none; 397 | color: var(--sidebar-fg); 398 | } 399 | 400 | .chapter li a:hover { 401 | color: var(--sidebar-active); 402 | } 403 | 404 | .chapter li a.active { 405 | color: var(--sidebar-active); 406 | } 407 | 408 | .chapter li > a.toggle { 409 | cursor: pointer; 410 | display: block; 411 | margin-left: auto; 412 | padding: 0 10px; 413 | user-select: none; 414 | opacity: 0.68; 415 | } 416 | 417 | .chapter li > a.toggle div { 418 | transition: transform 0.5s; 419 | } 420 | 421 | /* collapse the section */ 422 | .chapter li:not(.expanded) + li > ol { 423 | display: none; 424 | } 425 | 426 | .chapter li.chapter-item { 427 | line-height: 1.5em; 428 | margin-top: 0.6em; 429 | } 430 | 431 | .chapter li.expanded > a.toggle div { 432 | transform: rotate(90deg); 433 | } 434 | 435 | .spacer { 436 | width: 100%; 437 | height: 3px; 438 | margin: 5px 0px; 439 | } 440 | .chapter .spacer { 441 | background-color: var(--sidebar-spacer); 442 | } 443 | 444 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 445 | .chapter li a { padding: 5px 0; } 446 | .spacer { margin: 10px 0; } 447 | } 448 | 449 | .section { 450 | list-style: none outside none; 451 | padding-left: 20px; 452 | line-height: 1.9em; 453 | } 454 | 455 | /* Theme Menu Popup */ 456 | 457 | .theme-popup { 458 | position: absolute; 459 | left: 10px; 460 | top: var(--menu-bar-height); 461 | z-index: 1000; 462 | border-radius: 4px; 463 | font-size: 0.7em; 464 | color: var(--fg); 465 | background: var(--theme-popup-bg); 466 | border: 1px solid var(--theme-popup-border); 467 | margin: 0; 468 | padding: 0; 469 | list-style: none; 470 | display: none; 471 | } 472 | .theme-popup .default { 473 | color: var(--icons); 474 | } 475 | .theme-popup .theme { 476 | width: 100%; 477 | border: 0; 478 | margin: 0; 479 | padding: 2px 10px; 480 | line-height: 25px; 481 | white-space: nowrap; 482 | text-align: left; 483 | cursor: pointer; 484 | color: inherit; 485 | background: inherit; 486 | font-size: inherit; 487 | } 488 | .theme-popup .theme:hover { 489 | background-color: var(--theme-hover); 490 | } 491 | .theme-popup .theme:hover:first-child, 492 | .theme-popup .theme:hover:last-child { 493 | border-top-left-radius: inherit; 494 | border-top-right-radius: inherit; 495 | } 496 | -------------------------------------------------------------------------------- /manual/theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | -webkit-text-size-adjust: none; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | font-size: 1.6rem; 21 | overflow-x: hidden; 22 | } 23 | 24 | code { 25 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 26 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ 27 | } 28 | 29 | /* Don't change font size in headers. */ 30 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 31 | font-size: unset; 32 | } 33 | 34 | .left { float: left; } 35 | .right { float: right; } 36 | .boring { opacity: 0.6; } 37 | .hide-boring .boring { display: none; } 38 | .hidden { display: none !important; } 39 | 40 | h2, h3 { margin-top: 2.5em; } 41 | h4, h5 { margin-top: 2em; } 42 | 43 | .header + .header h3, 44 | .header + .header h4, 45 | .header + .header h5 { 46 | margin-top: 1em; 47 | } 48 | 49 | h1:target::before, 50 | h2:target::before, 51 | h3:target::before, 52 | h4:target::before, 53 | h5:target::before, 54 | h6:target::before { 55 | display: inline-block; 56 | content: "»"; 57 | margin-left: -30px; 58 | width: 30px; 59 | } 60 | 61 | /* This is broken on Safari as of version 14, but is fixed 62 | in Safari Technology Preview 117 which I think will be Safari 14.2. 63 | https://bugs.webkit.org/show_bug.cgi?id=218076 64 | */ 65 | :target { 66 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 67 | } 68 | 69 | .page { 70 | outline: 0; 71 | padding: 0 var(--page-padding); 72 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 73 | } 74 | .page-wrapper { 75 | box-sizing: border-box; 76 | } 77 | .js:not(.sidebar-resizing) .page-wrapper { 78 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 79 | } 80 | 81 | .content { 82 | overflow-y: auto; 83 | padding: 0 15px; 84 | padding-bottom: 50px; 85 | } 86 | .content main { 87 | margin-left: auto; 88 | margin-right: auto; 89 | max-width: var(--content-max-width); 90 | } 91 | .content p { line-height: 1.45em; } 92 | .content ol { line-height: 1.45em; } 93 | .content ul { line-height: 1.45em; } 94 | .content a { text-decoration: none; } 95 | .content a:hover { text-decoration: underline; } 96 | .content img, .content video { max-width: 100%; } 97 | .content .header:link, 98 | .content .header:visited { 99 | color: var(--fg); 100 | } 101 | .content .header:link, 102 | .content .header:visited:hover { 103 | text-decoration: none; 104 | } 105 | 106 | table { 107 | margin: 0 auto; 108 | border-collapse: collapse; 109 | } 110 | table td { 111 | padding: 3px 20px; 112 | border: 1px var(--table-border-color) solid; 113 | } 114 | table thead { 115 | background: var(--table-header-bg); 116 | } 117 | table thead td { 118 | font-weight: 700; 119 | border: none; 120 | } 121 | table thead th { 122 | padding: 3px 20px; 123 | } 124 | table thead tr { 125 | border: 1px var(--table-header-bg) solid; 126 | } 127 | /* Alternate background colors for rows */ 128 | table tbody tr:nth-child(2n) { 129 | background: var(--table-alternate-bg); 130 | } 131 | 132 | 133 | blockquote { 134 | margin: 20px 0; 135 | padding: 0 20px; 136 | color: var(--fg); 137 | background-color: var(--quote-bg); 138 | border-top: .1em solid var(--quote-border); 139 | border-bottom: .1em solid var(--quote-border); 140 | } 141 | 142 | 143 | :not(.footnote-definition) + .footnote-definition, 144 | .footnote-definition + :not(.footnote-definition) { 145 | margin-top: 2em; 146 | } 147 | .footnote-definition { 148 | font-size: 0.9em; 149 | margin: 0.5em 0; 150 | } 151 | .footnote-definition p { 152 | display: inline; 153 | } 154 | 155 | .tooltiptext { 156 | position: absolute; 157 | visibility: hidden; 158 | color: #fff; 159 | background-color: #333; 160 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 161 | left: -8px; /* Half of the width of the icon */ 162 | top: -35px; 163 | font-size: 0.8em; 164 | text-align: center; 165 | border-radius: 6px; 166 | padding: 5px 8px; 167 | margin: 5px; 168 | z-index: 1000; 169 | } 170 | .tooltipped .tooltiptext { 171 | visibility: visible; 172 | } 173 | 174 | .chapter li.part-title { 175 | color: var(--sidebar-fg); 176 | margin: 5px 0px; 177 | font-weight: bold; 178 | } 179 | 180 | .result-no-output { 181 | font-style: italic; 182 | } 183 | -------------------------------------------------------------------------------- /manual/theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /manual/theme/css/variables.css: -------------------------------------------------------------------------------- 1 | 2 | /* Globals */ 3 | 4 | :root { 5 | --sidebar-width: 300px; 6 | --page-padding: 15px; 7 | --content-max-width: 750px; 8 | --menu-bar-height: 50px; 9 | } 10 | 11 | /* Themes */ 12 | 13 | .ayu { 14 | --bg: hsl(210, 25%, 8%); 15 | --fg: #c5c5c5; 16 | 17 | --sidebar-bg: #14191f; 18 | --sidebar-fg: #c8c9db; 19 | --sidebar-non-existant: #5c6773; 20 | --sidebar-active: #ffb454; 21 | --sidebar-spacer: #2d334f; 22 | 23 | --scrollbar: var(--sidebar-fg); 24 | 25 | --icons: #737480; 26 | --icons-hover: #b7b9cc; 27 | 28 | --links: #0096cf; 29 | 30 | --inline-code-color: #ffb454; 31 | 32 | --theme-popup-bg: #14191f; 33 | --theme-popup-border: #5c6773; 34 | --theme-hover: #191f26; 35 | 36 | --quote-bg: hsl(226, 15%, 17%); 37 | --quote-border: hsl(226, 15%, 22%); 38 | 39 | --table-border-color: hsl(210, 25%, 13%); 40 | --table-header-bg: hsl(210, 25%, 28%); 41 | --table-alternate-bg: hsl(210, 25%, 11%); 42 | 43 | --searchbar-border-color: #848484; 44 | --searchbar-bg: #424242; 45 | --searchbar-fg: #fff; 46 | --searchbar-shadow-color: #d4c89f; 47 | --searchresults-header-fg: #666; 48 | --searchresults-border-color: #888; 49 | --searchresults-li-bg: #252932; 50 | --search-mark-bg: #e3b171; 51 | } 52 | 53 | .coal { 54 | --bg: hsl(200, 7%, 8%); 55 | --fg: #98a3ad; 56 | 57 | --sidebar-bg: #292c2f; 58 | --sidebar-fg: #a1adb8; 59 | --sidebar-non-existant: #505254; 60 | --sidebar-active: #3473ad; 61 | --sidebar-spacer: #393939; 62 | 63 | --scrollbar: var(--sidebar-fg); 64 | 65 | --icons: #43484d; 66 | --icons-hover: #b3c0cc; 67 | 68 | --links: #2b79a2; 69 | 70 | --inline-code-color: #c5c8c6; 71 | 72 | --theme-popup-bg: #141617; 73 | --theme-popup-border: #43484d; 74 | --theme-hover: #1f2124; 75 | 76 | --quote-bg: hsl(234, 21%, 18%); 77 | --quote-border: hsl(234, 21%, 23%); 78 | 79 | --table-border-color: hsl(200, 7%, 13%); 80 | --table-header-bg: hsl(200, 7%, 28%); 81 | --table-alternate-bg: hsl(200, 7%, 11%); 82 | 83 | --searchbar-border-color: #aaa; 84 | --searchbar-bg: #b7b7b7; 85 | --searchbar-fg: #000; 86 | --searchbar-shadow-color: #aaa; 87 | --searchresults-header-fg: #666; 88 | --searchresults-border-color: #98a3ad; 89 | --searchresults-li-bg: #2b2b2f; 90 | --search-mark-bg: #355c7d; 91 | } 92 | 93 | .light { 94 | --bg: hsl(0, 0%, 100%); 95 | --fg: hsl(0, 0%, 0%); 96 | 97 | --sidebar-bg: #fafafa; 98 | --sidebar-fg: hsl(0, 0%, 0%); 99 | --sidebar-non-existant: #aaaaaa; 100 | --sidebar-active: #1f1fff; 101 | --sidebar-spacer: #f4f4f4; 102 | 103 | --scrollbar: #8F8F8F; 104 | 105 | --icons: #747474; 106 | --icons-hover: #000000; 107 | 108 | --links: #20609f; 109 | 110 | --inline-code-color: #301900; 111 | 112 | --theme-popup-bg: #fafafa; 113 | --theme-popup-border: #cccccc; 114 | --theme-hover: #e6e6e6; 115 | 116 | --quote-bg: hsl(197, 37%, 96%); 117 | --quote-border: hsl(197, 37%, 91%); 118 | 119 | --table-border-color: hsl(0, 0%, 95%); 120 | --table-header-bg: hsl(0, 0%, 80%); 121 | --table-alternate-bg: hsl(0, 0%, 97%); 122 | 123 | --searchbar-border-color: #aaa; 124 | --searchbar-bg: #fafafa; 125 | --searchbar-fg: #000; 126 | --searchbar-shadow-color: #aaa; 127 | --searchresults-header-fg: #666; 128 | --searchresults-border-color: #888; 129 | --searchresults-li-bg: #e4f2fe; 130 | --search-mark-bg: #a2cff5; 131 | } 132 | 133 | .navy { 134 | --bg: hsl(226, 23%, 11%); 135 | --fg: #bcbdd0; 136 | 137 | --sidebar-bg: #282d3f; 138 | --sidebar-fg: #c8c9db; 139 | --sidebar-non-existant: #505274; 140 | --sidebar-active: #2b79a2; 141 | --sidebar-spacer: #2d334f; 142 | 143 | --scrollbar: var(--sidebar-fg); 144 | 145 | --icons: #737480; 146 | --icons-hover: #b7b9cc; 147 | 148 | --links: #2b79a2; 149 | 150 | --inline-code-color: #c5c8c6; 151 | 152 | --theme-popup-bg: #161923; 153 | --theme-popup-border: #737480; 154 | --theme-hover: #282e40; 155 | 156 | --quote-bg: hsl(226, 15%, 17%); 157 | --quote-border: hsl(226, 15%, 22%); 158 | 159 | --table-border-color: hsl(226, 23%, 16%); 160 | --table-header-bg: hsl(226, 23%, 31%); 161 | --table-alternate-bg: hsl(226, 23%, 14%); 162 | 163 | --searchbar-border-color: #aaa; 164 | --searchbar-bg: #aeaec6; 165 | --searchbar-fg: #000; 166 | --searchbar-shadow-color: #aaa; 167 | --searchresults-header-fg: #5f5f71; 168 | --searchresults-border-color: #5c5c68; 169 | --searchresults-li-bg: #242430; 170 | --search-mark-bg: #a2cff5; 171 | } 172 | 173 | .rust { 174 | --bg: hsl(60, 9%, 87%); 175 | --fg: #262625; 176 | 177 | --sidebar-bg: #3b2e2a; 178 | --sidebar-fg: #c8c9db; 179 | --sidebar-non-existant: #505254; 180 | --sidebar-active: #e69f67; 181 | --sidebar-spacer: #45373a; 182 | 183 | --scrollbar: var(--sidebar-fg); 184 | 185 | --icons: #737480; 186 | --icons-hover: #262625; 187 | 188 | --links: #2b79a2; 189 | 190 | --inline-code-color: #6e6b5e; 191 | 192 | --theme-popup-bg: #e1e1db; 193 | --theme-popup-border: #b38f6b; 194 | --theme-hover: #99908a; 195 | 196 | --quote-bg: hsl(60, 5%, 75%); 197 | --quote-border: hsl(60, 5%, 70%); 198 | 199 | --table-border-color: hsl(60, 9%, 82%); 200 | --table-header-bg: #b3a497; 201 | --table-alternate-bg: hsl(60, 9%, 84%); 202 | 203 | --searchbar-border-color: #aaa; 204 | --searchbar-bg: #fafafa; 205 | --searchbar-fg: #000; 206 | --searchbar-shadow-color: #aaa; 207 | --searchresults-header-fg: #666; 208 | --searchresults-border-color: #888; 209 | --searchresults-li-bg: #dec2a2; 210 | --search-mark-bg: #e69f67; 211 | } 212 | 213 | @media (prefers-color-scheme: dark) { 214 | .light.no-js { 215 | --bg: hsl(200, 7%, 8%); 216 | --fg: #98a3ad; 217 | 218 | --sidebar-bg: #292c2f; 219 | --sidebar-fg: #a1adb8; 220 | --sidebar-non-existant: #505254; 221 | --sidebar-active: #3473ad; 222 | --sidebar-spacer: #393939; 223 | 224 | --scrollbar: var(--sidebar-fg); 225 | 226 | --icons: #43484d; 227 | --icons-hover: #b3c0cc; 228 | 229 | --links: #2b79a2; 230 | 231 | --inline-code-color: #c5c8c6; 232 | 233 | --theme-popup-bg: #141617; 234 | --theme-popup-border: #43484d; 235 | --theme-hover: #1f2124; 236 | 237 | --quote-bg: hsl(234, 21%, 18%); 238 | --quote-border: hsl(234, 21%, 23%); 239 | 240 | --table-border-color: hsl(200, 7%, 13%); 241 | --table-header-bg: hsl(200, 7%, 28%); 242 | --table-alternate-bg: hsl(200, 7%, 11%); 243 | 244 | --searchbar-border-color: #aaa; 245 | --searchbar-bg: #b7b7b7; 246 | --searchbar-fg: #000; 247 | --searchbar-shadow-color: #aaa; 248 | --searchresults-header-fg: #666; 249 | --searchresults-border-color: #98a3ad; 250 | --searchresults-li-bg: #2b2b2f; 251 | --search-mark-bg: #355c7d; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /manual/theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schell/apecs/c45239fc439804438a3cf2da8391362e27e0efdc/manual/theme/favicon.png -------------------------------------------------------------------------------- /manual/theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /manual/theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * An increased contrast highlighting scheme loosely based on the 3 | * "Base16 Atelier Dune Light" theme by Bram de Haan 4 | * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) 5 | * Original Base16 color scheme by Chris Kempson 6 | * (https://github.com/chriskempson/base16) 7 | */ 8 | 9 | /* Comment */ 10 | .hljs-comment, 11 | .hljs-quote { 12 | color: #575757; 13 | } 14 | 15 | /* Red */ 16 | .hljs-variable, 17 | .hljs-template-variable, 18 | .hljs-attribute, 19 | .hljs-tag, 20 | .hljs-name, 21 | .hljs-regexp, 22 | .hljs-link, 23 | .hljs-name, 24 | .hljs-selector-id, 25 | .hljs-selector-class { 26 | color: #d70025; 27 | } 28 | 29 | /* Orange */ 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #b21e00; 38 | } 39 | 40 | /* Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet { 44 | color: #008200; 45 | } 46 | 47 | /* Blue */ 48 | .hljs-title, 49 | .hljs-section { 50 | color: #0030f2; 51 | } 52 | 53 | /* Purple */ 54 | .hljs-keyword, 55 | .hljs-selector-tag { 56 | color: #9d00ec; 57 | } 58 | 59 | .hljs { 60 | display: block; 61 | overflow-x: auto; 62 | background: #f6f7f6; 63 | color: #000; 64 | padding: 0.5em; 65 | } 66 | 67 | .hljs-emphasis { 68 | font-style: italic; 69 | } 70 | 71 | .hljs-strong { 72 | font-weight: bold; 73 | } 74 | 75 | .hljs-addition { 76 | color: #22863a; 77 | background-color: #f0fff4; 78 | } 79 | 80 | .hljs-deletion { 81 | color: #b31d28; 82 | background-color: #ffeef0; 83 | } 84 | -------------------------------------------------------------------------------- /manual/theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | 16 | {{> head}} 17 | 18 | 19 | 20 | 21 | 22 | 23 | {{#if favicon_svg}} 24 | 25 | {{/if}} 26 | {{#if favicon_png}} 27 | 28 | {{/if}} 29 | 30 | 31 | 32 | {{#if print_enable}} 33 | 34 | {{/if}} 35 | 36 | 37 | 38 | {{#if copy_fonts}} 39 | 40 | {{/if}} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {{#each additional_css}} 49 | 50 | {{/each}} 51 | 52 | {{#if mathjax_support}} 53 | 54 | 55 | {{/if}} 56 | 57 | 58 | 59 | 63 | 64 | 65 | 79 | 80 | 81 | 91 | 92 | 93 | 103 | 104 | 110 | 111 | 221 | 222 | {{#if livereload}} 223 | 224 | 237 | {{/if}} 238 | 239 | {{#if google_analytics}} 240 | 241 | 256 | {{/if}} 257 | 258 | {{#if playground_line_numbers}} 259 | 262 | {{/if}} 263 | 264 | {{#if playground_copyable}} 265 | 268 | {{/if}} 269 | 270 | {{#if playground_js}} 271 | 272 | 273 | 274 | 275 | 276 | {{/if}} 277 | 278 | {{#if search_js}} 279 | 280 | 281 | 282 | {{/if}} 283 | 284 | 285 | 286 | 287 | 288 | 289 | {{#each additional_js}} 290 | 291 | {{/each}} 292 | 293 | {{#if is_print}} 294 | {{#if mathjax_support}} 295 | 302 | {{else}} 303 | 308 | {{/if}} 309 | {{/if}} 310 | 311 | 312 | 313 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true 2 | format_code_in_doc_comments = true 3 | normalize_comments = true 4 | format_strings = true 5 | --------------------------------------------------------------------------------
112 | 113 |
114 | {{> header}} 115 | 116 | 159 | 160 | {{#if search_enabled}} 161 | 171 | {{/if}} 172 | 173 | 174 | 181 | 182 |
183 |
184 | {{{ content }}} 185 |
186 | 187 | 203 |
204 |
205 | 206 | 219 | 220 |