├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets └── Sansation-Regular.ttf ├── examples ├── bevy-compat.rs ├── conditions.rs ├── fixedtimestep.rs └── menu.rs └── src ├── condition.rs ├── fixedtimestep.rs ├── lib.rs └── state.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .idea/ 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable user-facing changes with each release version will be described in this file. 4 | 5 | ## [0.9.1]: 2022-11-20 6 | 7 | ### Fixed 8 | - Conditional exclusive systems no longer panic when ran 9 | 10 | ## [0.9.0]: 2022-11-19 11 | 12 | ### Changed 13 | - Bevy 0.9 compatibility 14 | 15 | ## [0.8.0]: 2022-10-24 16 | 17 | ### Added 18 | - All missing API docs 19 | - Extension traits for a nice API on App/Schedule for working with fixed timesteps, similar to states 20 | - `FixedTimesteps` resource: allows any system to access the properties of any fixed timestep 21 | - Fixed timesteps can be paused/unpaused 22 | 23 | ### Changed 24 | - `FixedTimestepInfo` is now accessed via a `FixedTimesteps` resource 25 | - Fixed timestep APIs use string names to identify fixed timesteps 26 | - Create a conditional exclusive system by calling `.into_conditional_exclusive()`. 27 | No more conflicting traits. No need for special imports, prelude just works. 28 | 29 | ### Removed 30 | - `FixedTimestepInfo` is no longer directly provided as a resource 31 | 32 | ### Fixed 33 | - `run_on_event` run condition no longer fires twice under some edge cases 34 | - WASM compatibility for fixed timestep: use `bevy_utils::Duration` instead of `std::time::Duration` 35 | 36 | ## [0.7.1]: 2022-08-18 37 | 38 | ### Added 39 | - Optional `bevy-inspector-egui` support (thanks @jakobhellermann) 40 | 41 | ### Changed 42 | - Using bare system function names with `before`/`after` is now a compile error instead of runtime warning. 43 | (this was always broken and unsupported) 44 | 45 | ## [0.7.0]: 2022-07-31 46 | 47 | ### Added 48 | - API helper extension methods for `Schedule`, analogous to those for `App`. (thanks @NiklasEi) 49 | 50 | ### Changed 51 | - Bevy 0.8 support 52 | - `FixedTimestepInfo.accumulator` is now `pub`; mutations also affect the internal accumulator 53 | 54 | ## [0.6.1]: 2022-06-20 55 | 56 | ### Changed 57 | - The `step` field in `FixedTimestepInfo` is now `pub`. This was a mistake in 0.6.0. 58 | 59 | ## [0.6.0]: 2022-06-15 60 | 61 | ### Added 62 | - `add_{enter,exit}_system_set` helpers for adding multiple systems to the enter/exit stages of states. 63 | - `run_if_resource_added` and `run_if_resource_removed` run conditions (thanks @Shatur) 64 | 65 | ### Changed 66 | - It is now possible to reconfigure fixed timestep durations at runtime, by modifying the `step` field in `FixedTimestepInfo`. 67 | 68 | ## [0.5.1]: 2022-04-24 69 | 70 | ### Added 71 | - Support for labels/ordering on `ConditionSet` 72 | 73 | ## [0.5.0]: 2022-04-22 74 | 75 | ### Added 76 | - Support for conditional exclusive systems, using the `IntoConditionalExclusiveSystem` trait 77 | 78 | ### Changed 79 | - `.add_{enter,exit}_system` App helpers no longer use a `&` reference to the state 80 | - The `.run_if*` methods are now in trait `ConditionHelpers`, not inherent on the type 81 | 82 | ## [0.4.0]: 2022-04-16 83 | 84 | ### Added 85 | - Extension trait to add ergonomic helpers to `App` for using states. 86 | - (optional behind `app` feature, adds `bevy_app` dependency) 87 | 88 | ### Changed 89 | - Updated for Bevy 0.7 90 | 91 | ## [0.3.0]: 2022-04-13 92 | 93 | ### Changed 94 | - Reverted the `NextState` behavior to how it was in `0.1.x`. The resource has to be inserted/removed. 95 | In retrospect, this is better UX and avoids bugs. 96 | - However, support transitioning to the same state as the current. 97 | 98 | ## [0.2.1]: 2022-04-11 99 | 100 | ### Added 101 | 102 | - Fixed Timestep: optional EXPERIMENTAL "rate lock" algorithm (see api docs) 103 | 104 | ## [0.2.0]: 2022-04-06 105 | 106 | ### Added 107 | 108 | - `ConditionSet`: makes it easy to add run conditions to many systems at once. 109 | - `FixedTimestepInfo` resource: allows your fixed timestep systems to know about the parameters of the current fixed timestep. 110 | 111 | ### Changed 112 | - Behavior of `NextState`: Checked using Bevy Change Detection 113 | - Present at all times, not removed on state transition. 114 | - No longer required to be inserted using `Commands`; you can also mutate it directly. Either way works. 115 | - Supports "transitioning" to the same state as the current, to "reset" it. 116 | - Conditional systems are now boxed, not generic. 117 | 118 | ### Removed 119 | - Conditional systems no longer support `In` and `Out` parameters. 120 | 121 | ## [0.1.1]: 2022-03-23 122 | 123 | ### Added 124 | - Run Condition adapters for compatibility with legacy Bevy States (`.run_in_bevy_state()`/`.run_not_in_bevy_state()`) 125 | 126 | ### Changed 127 | - Manually calling `.into_conditional()` on systems, to add conditions, is no longer required. 128 | 129 | ## [0.1.0]: 2022-03-21 130 | 131 | Initial Release 132 | 133 | [0.9.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.9.1 134 | [0.9.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.9.0 135 | [0.8.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.8.0 136 | [0.7.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.7.1 137 | [0.7.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.7.0 138 | [0.6.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.6.1 139 | [0.6.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.6.0 140 | [0.5.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.5.1 141 | [0.5.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.5.0 142 | [0.4.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.4.0 143 | [0.3.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.3.0 144 | [0.2.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.2.1 145 | [0.2.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.2.0 146 | [0.1.1]: https://github.com/IyesGames/iyes_loopless/tree/v0.1.1 147 | [0.1.0]: https://github.com/IyesGames/iyes_loopless/tree/v0.1.0 148 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iyes_loopless" 3 | version = "0.9.1" 4 | description = "Composable alternatives to Bevy's States/FixedTimestep/RunCriteria" 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/IyesGames/iyes_loopless" 9 | keywords = ["gamedev", "bevy"] 10 | categories = ["game-engines"] 11 | exclude = ["assets"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | default = [ "fixedtimestep", "states", "bevy-compat", "app" ] 17 | fixedtimestep = [ 18 | "bevy_time", 19 | "bevy_utils", 20 | ] 21 | states = [ 22 | "bevy_utils", 23 | ] 24 | # provide adapters for Bevy APIs, like the `.run_in_bevy_state` RC 25 | bevy-compat = [] 26 | # provide extension traits with convenient App builder methods 27 | app = [ 28 | "bevy_app", 29 | ] 30 | 31 | [dependencies] 32 | bevy_ecs = "0.9" 33 | bevy_app = { version = "0.9", optional = true } 34 | bevy_utils = { version = "0.9", optional = true } 35 | bevy_time = { version = "0.9", optional = true } 36 | bevy-inspector-egui = { version = "0.14", optional = true, default-features = false } 37 | 38 | [dev-dependencies] 39 | bevy = "0.9" 40 | rand = "0.8.5" 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Dual-licensed under either 2 | 3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT) 4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This crate is now OBSOLETE! 2 | 3 | Yes! Finally! Bevy has officially merged the "Stageless Rework". Coming 4 | with the Bevy 0.10 release! 5 | 6 | The purpose of this crate was to provide some of the features that are part 7 | of Bevy 0.10 in a form that was compatible with older versions of Bevy. 8 | 9 | This repo will be archived as soon as Bevy 0.10 is released. 10 | 11 | ## Migration Guide 12 | 13 | That said, here is some example code to help you migrate from `iyes_loopless` 14 | to Bevy 0.10 ("Stageless"): 15 | 16 | ### Run Conditions 17 | 18 | Creating them is the same: make a system that returns `bool`. 19 | Bevy 0.10 run conditions can only have read-only data access! 20 | 21 | ```rust 22 | fn my_condition(/* system params */) -> bool { 23 | // ... 24 | } 25 | 26 | // loopless: 27 | app.add_system( 28 | my_system 29 | .run_if(my_good_condition) 30 | .run_if_not(my_bad_condition) 31 | ); 32 | 33 | // Bevy 0.10: 34 | app.add_system( 35 | my_system 36 | .run_if(my_good_condition) 37 | .run_if(not(my_bad_condition)) 38 | ) 39 | ``` 40 | 41 | Common (predefined) run conditions: 42 | 43 | ```rust 44 | // loopless: 45 | app.add_system( 46 | my_system 47 | .run_if_resource_exists::() 48 | .run_unless_resource_exists::() 49 | 50 | .run_if_resource_equals(MyResource::Value) 51 | .run_unless_resource_equals(MyResource2::Value) 52 | 53 | .run_on_event::() 54 | .run_if_resource_added::() 55 | .run_if_resource_removed::() 56 | ); 57 | 58 | // Bevy 0.10: 59 | app.add_system( 60 | my_system 61 | .run_if(resource_exists::()) 62 | .run_if(not(resource_exists::())) 63 | 64 | // panics if resources don't exist 65 | .run_if(resource_equals(MyResource::Value)) 66 | .run_if(not(resource_equals(MyResource2::Value))) 67 | 68 | // does not panic if resources do not exist 69 | .run_if(resource_exists_and_equals(MyResource::Value)) 70 | .run_if(not(resource_exists_and_equals(MyResource2::Value))) 71 | 72 | .run_if(on_event::()) 73 | .run_if(resource_added::()) 74 | .run_if(resource_removed::()) 75 | 76 | // see: https://dev-docs.bevyengine.org/bevy/ecs/schedule/common_conditions/index.html 77 | ); 78 | ``` 79 | 80 | ### States 81 | 82 | The mental model and general feature set is the same. 83 | 84 | If you need to apply state transitions elsewhere in the schedule (other than the 85 | default location, before `Update`), in Bevy 0.10, you can add state transition 86 | points by adding `apply_state_transition` systems. 87 | 88 | You don't need to do anything different for the default configuration. 89 | 90 | The `CurrentState` resource is renamed to `State`. 91 | `NextState` is the same. 92 | 93 | ```rust 94 | // Loopless: 95 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 96 | enum MyState { 97 | Loading, 98 | MainMenu, 99 | InGame, 100 | } 101 | app.add_loopless_state(MyState::Loading); 102 | app.add_enter_system(MyState::MainMenu, setup_menu); 103 | app.add_exit_system(MyState::MainMenu, cleanup_menu); 104 | app.add_system(menu_buttons.run_in_state(MyState::MainMenu)); 105 | 106 | // Bevy 0.10: 107 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)] 108 | enum MyState { 109 | #[default] 110 | Loading, 111 | MainMenu, 112 | InGame, 113 | } 114 | app.add_state::(); 115 | app.add_system_to_schedule(OnEnter(MyState::MainMenu), setup_menu); 116 | app.add_system_to_schedule(OnExit(MyState::MainMenu), cleanup_menu); 117 | app.add_system(menu_buttons.in_set(OnUpdate(MyState::MainMenu))); 118 | // or alternatively 119 | app.add_system(menu_buttons.run_if(in_state(MyState::MainMenu))); 120 | ``` 121 | 122 | ### Fixed Timestep 123 | 124 | Loopless allows you to add any number of independent fixed timesteps and place 125 | them where you like. If you need to, you can position the stage where you like, 126 | relative to other stages. The default is before `CoreStage::Update`. Fixed 127 | timesteps use string labels as an identifier, which you use to add systems. 128 | 129 | Bevy 0.10 only has one fixed timestep. Its systems are in 130 | `CoreSchedule::FixedUpdate`, which is run by an exclusive system before 131 | `CoreSet::Update`. Simply add your systems there. If you need anything else, you 132 | are on your own. 133 | 134 | Loopless supports "child stages"/sub-stages, so you can apply Commands 135 | in the middle of the fixed update. They are identified by integer ids. 136 | The combination of string labels and integer ids makes the api not very 137 | user-friendly. 138 | 139 | Bevy 0.10 allows you to add your own Commands application points anywhere 140 | (regardless of fixed timestep or not) and systems are no longer organized 141 | into "stages". Just do that. 142 | 143 | ```rust 144 | // Loopless: 145 | 146 | // create a new fixed timestep 147 | app.add_fixed_timestep(Duration::from_millis(100), "my_timestep_label"); 148 | // create a second sub-stage so we can have a Commands application point 149 | app.add_fixed_timestep_child_stage("my_timestep_label"); 150 | // add a system to the first sub-stage 151 | app.add_fixed_timestep_system("my_timestep_label", 0, spawn_entities); 152 | // add a system to the second sub-stage 153 | app.add_fixed_timestep_system("my_timestep_label", 1, move_entities); 154 | 155 | // Bevy 0.10: 156 | 157 | // configure the fixed timestep 158 | app.insert_resource(FixedTime::new(Duration::from_millis(100))); 159 | // add our two systems, with Commands application in between 160 | app.add_systems_to_schedule( 161 | CoreSchedule::FixedUpdate, 162 | // quick and dirty; you should probably use sets and labels in larger projects 163 | // so you dont end up with many copies of `apply_system_buffers` 164 | ( 165 | spawn_entities, 166 | apply_system_buffers, 167 | move_entities, 168 | ).chain() 169 | ); 170 | ``` 171 | 172 | --- 173 | 174 | Here is the old README: 175 | 176 | # Composable Alternatives to Bevy's RunCriteria, States, FixedTimestep 177 | 178 | This crate offers alternatives to the Run Criteria, States, and FixedTimestep 179 | scheduling features currently offered by the Bevy game engine. 180 | 181 | The ones provided by this crate do not use "looping stages", and can therefore 182 | be combined/composed together elegantly, solving some of the most annoying 183 | usability limitations of the respective APIs in Bevy. 184 | 185 | Version Compatibility Table: 186 | 187 | |Bevy Version|Crate Version | 188 | |------------|-------------------| 189 | |`main` |`bevy_main` | 190 | |`0.9` |`main` | 191 | |`0.9` |`0.9` | 192 | |`0.8` |`0.7`, `0.8` | 193 | |`0.7` |`0.4`, `0.5`, `0.6`| 194 | |`0.6` |`0.1`, `0.2`, `0.3`| 195 | 196 | ## How does this relate to the Bevy Stageless RFC? 197 | 198 | This crate draws *very heavy* inspiration from the ["Stageless 199 | RFC"](https://github.com/bevyengine/rfcs/pull/45) proposal for Bevy. 200 | 201 | Big thanks to all the authors that have worked on that RFC and the designs 202 | described there. 203 | 204 | I am making this crate, because I believe the APIs currently in Bevy are 205 | sorely in need of a usability improvement. 206 | 207 | I figured out a way to implement the ideas from the Stageless RFC in a way 208 | that works within the existing framework of current Bevy, without requiring 209 | the complete scheduling API overhaul that the RFC proposes. 210 | 211 | This way we can have something usable *now*, while the remaining Stageless 212 | work is still in progress. 213 | 214 | ## Dependencies and Cargo Feature Flags 215 | 216 | The "run conditions" functionality is always enabled, and depends only on 217 | `bevy_ecs`. 218 | 219 | The "fixed timestep" functionality is optional (`"fixedtimestep"` cargo 220 | feature) and adds these dependencies: 221 | - `bevy_time` 222 | - `bevy_utils` 223 | 224 | The "states" functionality is optional (`"states"` cargo feature) and adds 225 | these dependencies: 226 | - `bevy_utils` 227 | 228 | The `"app"` cargo feature enables extension traits that add new builder 229 | methods to `App`, allowing more ergonomic access to the features of this 230 | crate. Adds a dependency on `bevy_app`. 231 | 232 | The `"bevy-compat"` feature adds Run Conditions for compatibility with 233 | Bevy's legacy states implementation. 234 | 235 | All of the optional cargo features are enabled by default. 236 | 237 | ## Run Conditions 238 | 239 | This crate provides an alternative to Bevy Run Criteria, called "Run Conditions". 240 | 241 | The different name was chosen to avoid naming conflicts and confusion with 242 | the APIs in Bevy. Bevy Run Criteria are pretty deeply integrated into Bevy's 243 | scheduling model, and this crate does not touch/replace them. They are 244 | technically still there and usable. 245 | 246 | ### How Run Conditions Work? 247 | 248 | You can convert any Bevy system into a "conditional system". This allows you 249 | to add any number of "conditions" on it, by repeatedly calling the `.run_if` 250 | builder method. 251 | 252 | Each condition is just a Bevy system that outputs (returns) a `bool`. 253 | 254 | The conditional system will present itself to 255 | Bevy as a single big system (similar to Bevy's [system 256 | piping](https://bevy-cheatbook.github.io/programming/system-chaining.html)), 257 | combining the system it was created from with all the condition systems 258 | attached. 259 | 260 | When it runs, it will run each condition, and abort if any of them returns 261 | `false`. The main system will run only if all the conditions return `true`. 262 | 263 | (see `examples/conditions.rs` for a more complete example) 264 | 265 | ```rust 266 | use bevy::prelude::*; 267 | use iyes_loopless::prelude::*; 268 | 269 | fn main() { 270 | App::new() 271 | .add_plugins(DefaultPlugins) 272 | .add_system( 273 | notify_server 274 | .run_if(in_multiplayer) 275 | .run_if(on_mytimer) 276 | ) 277 | .run(); 278 | } 279 | 280 | /// Condition checking our timer 281 | fn on_mytimer(mytimer: Res) -> bool { 282 | mytimer.timer.just_finished() 283 | } 284 | 285 | /// Condition checking if we are connected to multiplayer server 286 | fn in_multiplayer(gamemode: Res, connected: Res) -> bool { 287 | *gamemode == GameMode::Multiplayer && 288 | connected.is_active() 289 | } 290 | 291 | /// Some system that should only run on a timer in multiplayer 292 | fn notify_server(/* ... */) { 293 | // ... 294 | } 295 | ``` 296 | 297 | It is highly recommended that all your condition systems only access data 298 | immutably. Avoid mutable access or locals in condition systems, unless you are 299 | really sure about what you are doing. If you add the same condition to many 300 | systems, it *will run with each one*. 301 | 302 | There are also some helper methods for easily adding common kinds of Run Conditions: 303 | - `.run_if_not`: invert the output of the condition 304 | - `.run_on_event::()`: run if there are events of a given type 305 | - `.run_if_resource_exists::()`: run if a resource of a given type exists 306 | - `.run_unless_resource_exists::()`: run if a resource of a given type does not exist 307 | - `.run_if_resource_equals(value)`: run if the value of a resource equals the one provided 308 | - `.run_unless_resource_equals(value)`: run if the value of a resource does not equal the one provided 309 | 310 | And if you are using [States](#states): 311 | - `.run_in_state(state)` 312 | - `.run_not_in_state(state)` 313 | 314 | If you need to use classic Bevy States, you can use these adapters to check them with run conditions: 315 | - `.run_in_bevy_state(state)` 316 | - `.run_not_in_bevy_state(state)` 317 | 318 | You can use Bevy labels for system ordering, as usual. 319 | 320 | **Note:** conditional systems currently only support explicit labels, you cannot use 321 | Bevy's "ordering by function name" syntax. E.g: `.after(another_system)` does *not* work, 322 | you need to create a label. 323 | 324 | There is also `ConditionSet` (similar to Bevy `SystemSet`): syntax sugar for 325 | easily applying conditions and labels that are common to many systems: 326 | 327 | ```rust 328 | use bevy::prelude::*; 329 | use iyes_loopless::prelude::*; 330 | 331 | fn main() { 332 | App::new() 333 | .add_plugins(DefaultPlugins) 334 | .add_system( 335 | notify_server 336 | .run_if(in_multiplayer) 337 | .run_if(on_mytimer) 338 | // use bevy labels for ordering, as usual :) 339 | // (must be added at the end, after the conditions) 340 | .label("thing") 341 | .before("thing2") 342 | ) 343 | // You can easily apply many conditions to many systems 344 | // using a `ConditionSet`: 345 | .add_system_set(ConditionSet::new() 346 | // all the conditions, and any labels/ordering 347 | // must be added before adding the systems 348 | // (helps avoid confusion and accidents) 349 | // (makes it clear they apply to all systems in the set) 350 | .run_if(in_multiplayer) 351 | .run_if(other_condition) 352 | .label("thing2") 353 | .after("stuff") 354 | .with_system(system1) 355 | .with_system(system2) 356 | .with_system(system3) 357 | .into() // Converts into Bevy `SystemSet` (to add to App) 358 | ) 359 | .run(); 360 | } 361 | ``` 362 | 363 | **NOTE:** Due to some limitations with Bevy, `label`/`before`/`after` are 364 | *not* supported on individual systems within a `ConditionSet`. You can only 365 | use labels and ordering on the entire set, to apply them to all member 366 | systems. If some systems need different ordering, just add them individually 367 | with `.add_system`. 368 | 369 | ## Fixed Timestep 370 | 371 | This crate offers a fixed timestep implementation that runs as a separate 372 | Stage in the Bevy schedule. This way, it does not conflict with any other 373 | functionality. You can easily use [run conditions](#run-conditions) and 374 | [states](#states) to control your fixed timestep systems. 375 | 376 | It is possible to add multiple "sub-stages" within a fixed timestep, allowing 377 | you to apply `Commands` within a single timestep run. For example, if you want 378 | to spawn entities and then do something with them, on the same tick. 379 | 380 | It is also possible to have multiple independent fixed timesteps, should you 381 | need to. 382 | 383 | (see `examples/fixedtimestep.rs` for a more complex working example) 384 | 385 | ```rust 386 | use bevy::prelude::*; 387 | use iyes_loopless::prelude::*; 388 | 389 | fn main() { 390 | App::new() 391 | .add_plugins(DefaultPlugins) 392 | // add the fixed timestep stage: 393 | // (in the default position, before CoreStage::Update) 394 | .add_fixed_timestep( 395 | Duration::from_millis(250), 396 | // we need to give it a string name, to refer to it 397 | "my_fixed_update", 398 | ) 399 | // add fixed timestep systems: 400 | .add_fixed_timestep_system( 401 | "my_fixed_update", 0, // fixed timestep name, sub-stage index 402 | // it can be a conditional system! 403 | my_simulation 404 | .run_if(some_condition) 405 | .run_in_state(AppState::InGame) 406 | .after("some_label") 407 | ) 408 | .run(); 409 | } 410 | ``` 411 | 412 | Every frame, the `FixedTimestepStage` will accumulate the time delta. When 413 | it goes over the set timestep value, it will run all the child stages. It 414 | will repeat the sequence of child stages multiple times if needed, if 415 | more than one timestep has accumulated. 416 | 417 | ### Fixed Timestep Control 418 | 419 | You can use the `FixedTimesteps` resource (make sure it is the one from this 420 | crate, not the one from Bevy with the same name) to access information about a 421 | fixed timestep and to control its parameters, like the timestep duration. 422 | 423 | ```rust 424 | fn timestep_control(mut timesteps: ResMut) { 425 | // we can access our timestep by name 426 | let info = timesteps.get_mut("my_fixed_update").unwrap(); 427 | // set a different duration 428 | info.step = Duration::from_millis(125); 429 | // pause it 430 | info.paused = true; 431 | } 432 | 433 | /// Print info about the fixed timestep this system runs in 434 | fn debug_fixed(timesteps: Res) { 435 | // from within a system that runs inside the fixed timestep, 436 | // you can use `.get_current`, no need for the timestep name: 437 | let info = timesteps.get_current().unwrap(); 438 | println!("Fixed timestep duration: {:?} ({} Hz).", info.timestep(), info.rate()); 439 | println!("Overstepped by {:?} ({}%).", info.remaining(), info.overstep() * 100.0); 440 | } 441 | ``` 442 | 443 | ## States 444 | 445 | (see `examples/menu.rs` for a complete example) 446 | 447 | This crate offers a states abstraction that works as follows: 448 | 449 | You create one (or more) state types, usually enums, just like when using 450 | Bevy States. 451 | 452 | However, here we track states using two resource types: 453 | - `CurrentState(T)`: the current state you are in 454 | - `NextState(T)`: insert this (using `Commands`) whenever you want to change state 455 | 456 | ### Registering the state type 457 | 458 | You need to add the state to your `App` using `.add_loopless_state(value)` 459 | with the initial state value. This helper method adds a special stage type 460 | (`StateTransitionStage`) that is responsible for performing state transitions. 461 | By default, it is added before `CoreStage::Update`. If you would like the 462 | transitions to be executed elsewhere in the app schedule, there are other 463 | helper methods that let you specify the position. 464 | 465 | For advanced use cases, you could construct and add the `StateTransitionStage` 466 | manually, without the helper method. 467 | 468 | ### Enter/Exit Systems 469 | 470 | You can add enter/exit systems to be executed on state transitions, using 471 | `.add_enter_system(state, system)` and `.add_exit_system(state, system)`. 472 | 473 | For advanced scenarios, you could add a custom stage type instead, using 474 | `.set_enter_stage(state, stage)` and `.set_exit_stage(state, stage)`. 475 | 476 | ### State Transition 477 | 478 | When the `StateTransitionStage` runs, it will check if a `NextState` resource 479 | exists. If yes, it will remove it and perform a transition: 480 | - run the "exit stage" (if any) for the current state 481 | - change the value of `CurrentState` 482 | - run the "enter stage" (if any) for the next state 483 | 484 | If you want to perform a state transition, simply insert a `NextState`. 485 | If you mutate `CurrentState`, you will effectively change state without 486 | running the exit/enter systems (you probably don't want to do this). 487 | 488 | Multiple state transitions can be performed in a single frame, if you insert 489 | a new instance of `NextState` from within an exit/enter stage. 490 | 491 | ### Update systems 492 | 493 | For the systems that you want to run every frame, we provide 494 | a `.run_in_state(state)` and `.run_not_in_state(state)` [run 495 | conditions](#run-conditions). 496 | 497 | You can add systems anywhere, to any stage (incl. behind [fixed 498 | timestep](#fixed-timestep)), and make them conditional on one or more states, 499 | using those helper methods. 500 | 501 | ```rust 502 | use bevy::prelude::*; 503 | use iyes_loopless::prelude::*; 504 | 505 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 506 | enum GameState { 507 | MainMenu, 508 | InGame, 509 | } 510 | 511 | fn main() { 512 | App::new() 513 | .add_plugins(DefaultPlugins) 514 | // Add our state type 515 | .add_loopless_state(GameState::MainMenu) 516 | // If we had more state types, we would add them too... 517 | 518 | // Add a FixedTimestep, cuz we can! 519 | .add_fixed_timestep( 520 | Duration::from_millis(250), 521 | "my_fixed_update", 522 | ) 523 | .add_fixed_timestep_system( 524 | "my_fixed_update", 0, 525 | my_simulation 526 | .run_in_state(AppState::InGame) 527 | ) 528 | 529 | // Add our various systems 530 | .add_system(menu_stuff.run_in_state(GameState::MainMenu)) 531 | .add_system(animate.run_in_state(GameState::InGame)) 532 | 533 | // On states Enter and Exit 534 | .add_enter_system(GameState::MainMenu, setup_menu) 535 | .add_exit_system(GameState::MainMenu, despawn_menu) 536 | .add_enter_system(GameState::InGame, setup_game) 537 | 538 | .run(); 539 | } 540 | 541 | ``` 542 | 543 | ### State transitions under fixed timestep 544 | 545 | If you have a state type that you are using for controlling fixed timestep 546 | stuff, you might want state transitions to happen only on fixed timestep 547 | (not just on any frame). 548 | 549 | To accomplish that, you can add the `StateTransitionStage` as a child stage 550 | at the beginning of your `FixedTimestepStage`. 551 | 552 | The stage types from this crate are composable like that! :) They accept 553 | any stage type. 554 | -------------------------------------------------------------------------------- /assets/Sansation-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IyesGames/iyes_loopless/372edb97892c52b4bbd595cee5f67efa7f0a4761/assets/Sansation-Regular.ttf -------------------------------------------------------------------------------- /examples/bevy-compat.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use bevy::prelude::*; 4 | use iyes_loopless::prelude::*; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | enum BevyState { 8 | A, 9 | B, 10 | } 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | enum IyesState { 14 | C, 15 | D, 16 | } 17 | 18 | fn main() { 19 | App::new() 20 | .add_plugins(DefaultPlugins) 21 | .add_state(BevyState::B) 22 | .add_stage_after( 23 | CoreStage::PreUpdate, 24 | "IyesState", 25 | StateTransitionStage::new(IyesState::D) 26 | ) 27 | .add_system( 28 | ping.run_not_in_bevy_state(BevyState::A).run_in_state(IyesState::D) 29 | ) 30 | .run(); 31 | } 32 | 33 | fn ping() { 34 | println!("ping"); 35 | } 36 | -------------------------------------------------------------------------------- /examples/conditions.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use iyes_loopless::prelude::*; 3 | 4 | fn main() { 5 | App::new() 6 | .add_plugins(DefaultPlugins) 7 | .insert_resource(MyTimer::new()) 8 | .insert_resource(GameMode::Multiplayer) 9 | .init_resource::() 10 | .add_system( 11 | notify 12 | .run_if(in_multiplayer) 13 | .run_if(on_mytimer) 14 | // labels and ordering must come at the end 15 | .after("tick"), 16 | ) 17 | .add_system( 18 | tick_mytimer 19 | .run_if(in_multiplayer) 20 | .run_if(spacebar_pressed) 21 | .label("tick") 22 | ) 23 | .run(); 24 | } 25 | 26 | /// Condition checking our timer 27 | fn on_mytimer(mytimer: Res) -> bool { 28 | mytimer.timer.just_finished() 29 | } 30 | 31 | /// Condition checking if we are connected to multiplayer server 32 | fn in_multiplayer(gamemode: Res, connected: Res) -> bool { 33 | *gamemode == GameMode::Multiplayer && connected.is_active() 34 | } 35 | 36 | /// Condition checking if spacebar is pressed 37 | fn spacebar_pressed(kbd: Res>) -> bool { 38 | kbd.pressed(KeyCode::Space) 39 | } 40 | 41 | fn notify() { 42 | println!("BAM!"); 43 | } 44 | 45 | /// Timers gotta be ticked 46 | fn tick_mytimer(mut mytimer: ResMut, time: Res