From 827202c24d229e33a99f8944a9d7a885b86c7461 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 14 Mar 2022 14:33:43 -0400 Subject: [PATCH 1/7] Initial content port --- content/learn/book/ecs/resources/_index.md | 292 ++++++++++++++++++++- 1 file changed, 287 insertions(+), 5 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index c44e17f5ce..e727aedf5a 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -6,12 +6,294 @@ page_template = "book-section.html" insert_anchor_links = "right" +++ -TODO: Explain what a resource is +Not all data is stored in the entity-component data storage. +**Resources** are the blanket term for data that lives outside of entities and their components, but is still accessible by Bevy apps. +Each resource has a unique type (you can only have one resource of each type), and can be accessed in systems by adding either [`Res`] (for immutable read-only access) or [`ResMut`] (for mutable read-write access) as a system parameter. -TODO: Explain why you might want a resource +In effect, resources serve as well-behaved **global singletons**. +They are accessible by any system in the app, and because you can only have one resource of each type, are unique. +You might want to use resources for: -TODO: Show how to create and access Resources +- storing events +- holding configuration data +- storing simple global game state that you only need a single copy of (like a game's store) +- interoperating with other libraries +- providing faster or supplementary look-up data structures for entities (such as indexes or graphs) +- storing reference-counted handles to assets (so then they're not unloaded when the last entity using it is despawned) -TODO: Show how to modify resources +[`Res`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Res.html +[`ResMut`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.ResMut.html -TODO: Compare and contrast with `query.single()` +## Creating resources + +Unlike components, resources do not need their own trait implementation: you can use any new or existing [`'static`] Rust type as a resource (if it is not [`Send + Sync`], you'll need a [`NonSend`] resource instead). + +Like entities and their component data, resources are stored in your [`App`]'s [`World`] struct. +Resources are typically added statically, via [`insert_resouce`] or [`init_resource`]. + +[`insert_resource`] is used when you want to set the value of a resource manually, while [`init_resource`] is used when you want to automatically initialize the resources value using the [`Default`] or [`FromWorld`] trait. + +```rust +use bevy::prelude::*; + +// Resources can be tuple structs +// Default can be derived for many simple resources, +// with the default value of most numeric types being 0 +#[derive(Default)] +struct Score(u64); + +// Resources can be ordinary namedstructs +struct PlayerSupplies { + gold: u64, + wood: u64, +} + +// The Default trait can be manually implemented to control initial values +impl Default for PlayerSupplies { + fn default() -> Self { + PlayerSupplies { + gold: 400, + wood: 200, + } + } +} + +// Resources can be enums +enum Turn { + Allied, + Enemy +} + +fn main(){ + // Resources are typically inserted using AppBuilder methods + App::build() + // Uses the default() value provided by the derived Default trait + .init_resource::() + // Uses the default() value provided by the manual impl of the Default trait + .init_resource::() + // Uses the specific manual value of Turn::Allied to insert a resource of type Turn + .insert_resource(Turn::Allied) + // Sets the value of the standard Bevy resource `WindowDescriptor`, + // leaving the unspecified fields as their default value + .insert_resource(WindowDescriptor { + title: "I am a window!".to_string(), + width: 500., + height: 300., + vsync: true, + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .run() +} +``` + +In rare cases, you may need to add a resource later, once other parts of the world exist to ensure proper initialization. +For that, we can use the equivalent methods on [`Commands`]. +You can add, overwrite and even [`remove_resource`] dynamically in this way. + +Only use [`Commands`] to add resources where it's needed due to the need to initialize a resource with other data from the world. +It's less clear, and like all commands, commands that insert resources are delayed and only take effect at the end of the current stage. + +[`'static`]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html +[`Send + Sync`]: https://doc.rust-lang.org/nomicon/send-and-sync.html +[`App`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html +[`World`]: https://docs.rs/bevy/latest/bevy/ecs/world/struct.World.html +[`insert_resouce`]: https://docs.rs/bevy/latest/bevy/app/struct.AppBuilder.html#method.insert_resource +[`init_resource`]: https://docs.rs/bevy/latest/bevy/app/struct.AppBuilder.html#method.init_resource +[`Default`]: https://doc.rust-lang.org/beta/std/default/trait.Default.html +[`FromWorld`]: https://docs.rs/bevy/latest/bevy/ecs/world/trait.FromWorld.html +[`NonSend`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.NonSend.html +[`Commands`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html +[`remove_resource`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Commands.html#method.remove_resource + +## Reading and writing from resources + +Once our resources have been added to the app, we can read and write to them by adding systems that refer to them using the [`Res`] and [`ResMut`] system parameters. +Let's take a look at how this works in a cohesive context by building a tiny guessing game. + +```rust +use bevy::prelude::*; + +fn main() { + App::build() + .add_plugins(MinimalPlugins) + .init_resource::() + .insert_resource(InputMode::Recording) + .add_system(record_secret.system()) + .add_system(check_secret.system()) + .run(); +} + +/// Resource to store our secret key +#[derive(Default)] +struct Secret { + // The default value of Option fields is always None + val: Option, +} + +/// Resource to control the interaction mode of our game +#[derive(PartialEq, Eq)] +enum InputMode { + /// Stores inputs in the Secret resource + Recording, + /// Compares inputs to the Secret resource + Guessing, +} + +/// Stores the keyboard input in our Secret resource +fn record_secret( + mut input_mode: ResMut, + mut secret: ResMut, + mut input: ResMut>, +) { + // This system should only do work in the Recording input mode + // Note that we need to derefence out of the ResMut smart pointer + // using * to access the underlying InputMode data + if *input_mode == InputMode::Recording { + // Only display the text prompt once, when the input_mode changes + if input_mode.is_changed() { + println!("Press a key to store a secret to be guessed by a friend!") + } + + // Input is stored in resources too! + // Here, we only want one key to store as our secret, + // so we arbitarily grab the first key in case multiple keys are pressed at once + let maybe_keycode = input.get_just_pressed().next(); + + // maybe_keycode may be None, if no key was pressed + // We only care about handling the case where a key was pressed, + // so we use `if let` to destructure our option + if let Some(keycode) = maybe_keycode { + // Storing our input in the Secret resource + secret.val = Some(*keycode); + + // Now that we've stored a Secret, we should swap to guessing it + // Again, we need to derefence our resource to refer to the data rather than the wrapper + *input_mode = InputMode::Guessing; + + // Clear the input so that check_secret doesn't spy on this data the same frame that it's stored! + // Note that we could avoid doing this by ensuring that check_secret always runs before record_secret + // See the page on system ordering for information on how to do this + *input = Input::::default(); + } + } +} + +/// Checks if the new input matches the stored secret +fn check_secret( + // We need to use mut + ResMut for input_mode and secret since their value is changed + mut input_mode: ResMut, + mut secret: ResMut, + // input only needs a Res, since we're only reading the KeyCodes that were pressed + input: Res>, +) { + if *input_mode == InputMode::Guessing { + if input_mode.is_changed() { + println!("Press a key to check if it matches the secret! Only one key will be checked per frame.") + } + + let maybe_keycode = input.get_just_pressed().next(); + if let Some(keycode) = maybe_keycode { + if Some(*keycode) == secret.val { + println!("You've guessed the secret!"); + // Get a new secret if it was guessed successfully + secret.val = None; + *input_mode = InputMode::Recording; + } else { + println!("Nope! Try again.") + } + } + } +} +``` + +## Singleton entity or Resource? + +As discussed in [**Systems access data through queries**](../systems-queries/_index.md), [`Query::single`](https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html#method.single) is a convenient way to get access to the data of an entity when you know that exactly one entity will be returned by a query. +So when should you use a singleton entity, and when should you use a resource? + +Let's list the advantage of each, beginning with resources: + +- fast and simple access model: no need for queries or unwrapping +- will not be accidentally broken by later code that modifies your entity's components or creates more matching entities +- can store data that is not thread-safe using [`NonSend`] resources +- clearly communicates intent + +By contrast, singleton entities are useful because they: + +- can easily share behavior and data types with other entities through systems that operate on their components +- can be extended and contracted at run time by adding or removing components +- have more granular change detection: operating on a per component basis rather than the entire object +- allows you to fetch only the data you immediately need, rather than the entire resource struct + +Overall, resources are a good default for one-off demands: they're clear and very ergonomic to access. +You should turn to singleton entities when you want to share behavior with other entities (i.e. a singleton entity for the player is almost always going to be superior to a monolithic `Player` resource), or for when you want to be able to extend or modify behavior dynamically during gameplay. + +## Complex resource initialization using `FromWorld` + +Sometimes you may need to initialize resources in more complex ways, depending on data from the [`World`] at large. +For this, we can use the [`FromWorld`] trait, which allows you to create a new copy of the type that it's implemented automatically from the world. + +Ordinarily, the [`Default`] trait is used to handle resource initialization, due to the blanket implementation of [`FromWorld`] for `T: Default`. +Note that you cannot manually implement [`FromWorld`] on a type that has the [`Default`] trait, as Rust forbids conflicting implementations of the same trait. + +[`FromWorld`] is commonly used in asset loading to automatically create handles for simple assets, and its use in this case is demonstrated in the [section on loading assets](../../assets/loading-assets/_index.md). +For advice on how to work with the [`World`] exposed by the [`FromWorld::from_world`] method, see the section on [exclusive world access](../exclusive-world-access/_index.md). + +[`FromWorld::from_world`]: https://docs.rs/bevy/latest/bevy/ecs/world/trait.FromWorld.html#tymethod.from_world + +## Optional Resources + +Sometimes, a resource may not exist by the time a regularly scheduled system is called. +We can handle both the case where it exists and the case where it doesn't by requesting `Option>` (or other resource types like [`ResMut`], [`NonSend`] and [`NonSendMut`]) as a system parameter, +and then branching on the resulting `Option` returned. + +Here's a quick runnable example: + +```rust +use bevy::prelude::*; + +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_system(countdown.system()) + .run() +} + +struct Countdown { + time_remaining: u8, +} + +// `countdown` does not need to be marked as `mut` here, +// as destructuring (like via `match`) does not require mutation +// Only the internal data (`validated_countdown`) needs to be marked as `mut` +fn countdown( + countdown: Option>, + mut commands: Commands, + mut app_exit: EventWriter, +) { + match countdown { + // Resources can be inserted at runtime using commands + None => commands.insert_resource(Countdown { time_remaining: 10 }), + Some(mut validated_countdown) => { + info!("{} ticks remaining!", validated_countdown.time_remaining); + if validated_countdown.time_remaining > 1 { + validated_countdown.time_remaining -= 1; + } else { + info!("Ka-BOOM!"); + app_exit.send(AppExit); + } + } + } +} +``` + +[`NonSendMut]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.NonSendMut.html + +## `NonSend` resources + +Non-send resources are used to store data that do not meet the [`Send + Sync`] trait bounds: they cannot be sent safely across threads. +Their use cases are typically quite advanced and tend to involve interfacing with external libraries for things like audio or networking. +`NonSend` and `NonSendMut` can be directly substituted for `Res` and `ResMut` in any system. +The inclusion of one or more non-send resources in your system will force that system to run on the main thread, +rather than being automatically scheduled to the first available thread. From 405d723e690626ac650c17935dc4c43c85e93542 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 14 Mar 2022 15:09:01 -0400 Subject: [PATCH 2/7] Formatting fix Co-authored-by: James Liu --- content/learn/book/ecs/resources/_index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index e727aedf5a..707514fefe 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -127,7 +127,7 @@ fn main() { /// Resource to store our secret key #[derive(Default)] struct Secret { - // The default value of Option fields is always None + // The default value of Option fields is always None val: Option, } @@ -147,8 +147,8 @@ fn record_secret( mut input: ResMut>, ) { // This system should only do work in the Recording input mode - // Note that we need to derefence out of the ResMut smart pointer - // using * to access the underlying InputMode data + // Note that we need to derefence out of the ResMut smart pointer + // using * to access the underlying InputMode data if *input_mode == InputMode::Recording { // Only display the text prompt once, when the input_mode changes if input_mode.is_changed() { From 34aad52a09a482a9c0d7d3fde5264241a82508c9 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 14 May 2022 20:30:41 -0400 Subject: [PATCH 3/7] Revise intro --- content/learn/book/ecs/resources/_index.md | 93 +++++++++++----------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index 707514fefe..f9ef7721b4 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -7,19 +7,19 @@ insert_anchor_links = "right" +++ Not all data is stored in the entity-component data storage. -**Resources** are the blanket term for data that lives outside of entities and their components, but is still accessible by Bevy apps. -Each resource has a unique type (you can only have one resource of each type), and can be accessed in systems by adding either [`Res`] (for immutable read-only access) or [`ResMut`] (for mutable read-write access) as a system parameter. +**Resources** are not associated with a specific entity. +In effect, resources serve as well-behaved global [singletons](https://en.wikipedia.org/wiki/Singleton_pattern). +Each resource has a unique type `R`, and can be accessed in systems by adding either [`Res`] (for immutable read-only access) or [`ResMut`] (for mutable read-write access) as a system parameter. +As a result, you can only have one resource of each type: inserting a resource of a type that already exists will overwrite the existing value. -In effect, resources serve as well-behaved **global singletons**. -They are accessible by any system in the app, and because you can only have one resource of each type, are unique. You might want to use resources for: - storing events -- holding configuration data -- storing simple global game state that you only need a single copy of (like a game's store) -- interoperating with other libraries -- providing faster or supplementary look-up data structures for entities (such as indexes or graphs) -- storing reference-counted handles to assets (so then they're not unloaded when the last entity using it is despawned) +- reading input data +- recording game configuration (such as the current difficulty) +- storing simple global game state that you only need a single copy of (like the player's current score) +- interoperating with other non-Bevy Rust libraries +- storing data structures that help you look up entities by their component values more quickly (such as indexes or graphs) [`Res`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.Res.html [`ResMut`]: https://docs.rs/bevy/latest/bevy/ecs/system/struct.ResMut.html @@ -30,69 +30,68 @@ Unlike components, resources do not need their own trait implementation: you can Like entities and their component data, resources are stored in your [`App`]'s [`World`] struct. Resources are typically added statically, via [`insert_resouce`] or [`init_resource`]. - -[`insert_resource`] is used when you want to set the value of a resource manually, while [`init_resource`] is used when you want to automatically initialize the resources value using the [`Default`] or [`FromWorld`] trait. +[`insert_resource`] is used when you want to set the value of a resource manually, while [`init_resource`] is used when you want to automatically initialize the resource's value using the [`Default`] or [`FromWorld`] trait. ```rust use bevy::prelude::*; -// Resources can be tuple structs // Default can be derived for many simple resources, // with the default value of most numeric types being 0 #[derive(Default)] struct Score(u64); -// Resources can be ordinary namedstructs struct PlayerSupplies { - gold: u64, - wood: u64, + gold: u64, + wood: u64, } // The Default trait can be manually implemented to control initial values impl Default for PlayerSupplies { - fn default() -> Self { - PlayerSupplies { - gold: 400, - wood: 200, - } - } + fn default() -> Self { + PlayerSupplies { + gold: 400, + wood: 200, + } + } } -// Resources can be enums +// Enum resources are a great way to represent game state in a type-safe way enum Turn { - Allied, - Enemy + Allied, + Enemy, } -fn main(){ - // Resources are typically inserted using AppBuilder methods - App::build() - // Uses the default() value provided by the derived Default trait - .init_resource::() - // Uses the default() value provided by the manual impl of the Default trait - .init_resource::() - // Uses the specific manual value of Turn::Allied to insert a resource of type Turn - .insert_resource(Turn::Allied) - // Sets the value of the standard Bevy resource `WindowDescriptor`, - // leaving the unspecified fields as their default value - .insert_resource(WindowDescriptor { - title: "I am a window!".to_string(), - width: 500., - height: 300., - vsync: true, - ..Default::default() - }) - .add_plugins(DefaultPlugins) - .run() +fn main() { + // Resources are typically inserted using methods on `App` + App::build() + // Uses the default() value provided by the derived Default trait + .init_resource::() + // Uses the default() value provided by the manual impl of the Default trait + .init_resource::() + // Uses the specific value supplied (Turn::Allied) to insert a resource of type Turn + .insert_resource(Turn::Allied) + // Sets the value of the standard Bevy resource `WindowDescriptor`, + // leaving the unspecified fields as their default value + .insert_resource(WindowDescriptor { + title: "I am a window!".to_string(), + width: 500., + height: 300., + vsync: true, + ..default() + }) + .add_plugins(DefaultPlugins) + .run() } ``` -In rare cases, you may need to add a resource later, once other parts of the world exist to ensure proper initialization. +In rare cases, you may need to add a resource after app startup. +Generally, this is because other resources or entities must exist to ensure proper initialization. For that, we can use the equivalent methods on [`Commands`]. You can add, overwrite and even [`remove_resource`] dynamically in this way. +Be careful when removing resources: systems will panic if a resource they are expecting is not found! -Only use [`Commands`] to add resources where it's needed due to the need to initialize a resource with other data from the world. -It's less clear, and like all commands, commands that insert resources are delayed and only take effect at the end of the current stage. +Prefer adding resources via the methods on `App` whenever possible. +It's harder to find the resources that your code needs, and like all commands, commands that insert resources are delayed and only take effect at the end of the current stage. [`'static`]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html [`Send + Sync`]: https://doc.rust-lang.org/nomicon/send-and-sync.html From 7dece9454f2b3aa1d625d6abdf27ae8f9a6ae7d6 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 14 May 2022 20:43:31 -0400 Subject: [PATCH 4/7] Revise guessing game example --- content/learn/book/ecs/resources/_index.md | 57 ++++++++++------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index f9ef7721b4..9eb59c72df 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -107,8 +107,8 @@ It's harder to find the resources that your code needs, and like all commands, c ## Reading and writing from resources -Once our resources have been added to the app, we can read and write to them by adding systems that refer to them using the [`Res`] and [`ResMut`] system parameters. -Let's take a look at how this works in a cohesive context by building a tiny guessing game. +Once our resources have been added to the app, we can read and write to them from systems using the [`Res`] and [`ResMut`] system parameters. +Let's take a look at how this works by building a tiny guessing game. ```rust use bevy::prelude::*; @@ -118,50 +118,54 @@ fn main() { .add_plugins(MinimalPlugins) .init_resource::() .insert_resource(InputMode::Recording) - .add_system(record_secret.system()) - .add_system(check_secret.system()) + .add_system(record_secret) + // The system ordering here ensures that we don't spy on the input before it's entered + .add_system(check_secret.before(check_secret)) .run(); } /// Resource to store our secret key #[derive(Default)] struct Secret { - // The default value of Option fields is always None + // The default value of Option fields is always None val: Option, } -/// Resource to control the interaction mode of our game +/// Resource that controls the effect of player input #[derive(PartialEq, Eq)] enum InputMode { - /// Stores inputs in the Secret resource + /// Stores input in the Secret resource Recording, - /// Compares inputs to the Secret resource + /// Compares input to the Secret resource Guessing, } /// Stores the keyboard input in our Secret resource fn record_secret( + // We need to use mut + ResMut for input_mode and secret + // because we change their values in this system mut input_mode: ResMut, mut secret: ResMut, - mut input: ResMut>, + // input only needs a Res, since we're only reading the KeyCodes that were pressed + input: Res>, ) { // This system should only do work in the Recording input mode - // Note that we need to derefence out of the ResMut smart pointer - // using * to access the underlying InputMode data + // Note that we need to derefence out of the ResMut smart pointer + // using * to access the underlying InputMode data if *input_mode == InputMode::Recording { - // Only display the text prompt once, when the input_mode changes + // Only display the text prompt once, when the InputMode resource changes if input_mode.is_changed() { println!("Press a key to store a secret to be guessed by a friend!") } - // Input is stored in resources too! + // Player input is stored in resources too! // Here, we only want one key to store as our secret, // so we arbitarily grab the first key in case multiple keys are pressed at once let maybe_keycode = input.get_just_pressed().next(); // maybe_keycode may be None, if no key was pressed // We only care about handling the case where a key was pressed, - // so we use `if let` to destructure our option + // so we use `if let` to destructure the Option<&Keycode> returned by .next() if let Some(keycode) = maybe_keycode { // Storing our input in the Secret resource secret.val = Some(*keycode); @@ -169,21 +173,14 @@ fn record_secret( // Now that we've stored a Secret, we should swap to guessing it // Again, we need to derefence our resource to refer to the data rather than the wrapper *input_mode = InputMode::Guessing; - - // Clear the input so that check_secret doesn't spy on this data the same frame that it's stored! - // Note that we could avoid doing this by ensuring that check_secret always runs before record_secret - // See the page on system ordering for information on how to do this - *input = Input::::default(); } } } /// Checks if the new input matches the stored secret fn check_secret( - // We need to use mut + ResMut for input_mode and secret since their value is changed mut input_mode: ResMut, mut secret: ResMut, - // input only needs a Res, since we're only reading the KeyCodes that were pressed input: Res>, ) { if *input_mode == InputMode::Guessing { @@ -192,15 +189,15 @@ fn check_secret( } let maybe_keycode = input.get_just_pressed().next(); - if let Some(keycode) = maybe_keycode { - if Some(*keycode) == secret.val { - println!("You've guessed the secret!"); - // Get a new secret if it was guessed successfully - secret.val = None; - *input_mode = InputMode::Recording; - } else { - println!("Nope! Try again.") - } + // maybe_keycode can either be Some(keycode) or None + // If it is None, it will never equal the value of our secret + if maybe_keycode == secret.val { + println!("You've guessed the secret!"); + // Get a new secret if it was guessed successfully + secret.val = None; + *input_mode = InputMode::Recording; + } else { + println!("Nope! Try again.") } } } From 048d609b8302d79c48a36e558ca556394572f643 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 14 May 2022 20:46:59 -0400 Subject: [PATCH 5/7] Revise Singleton entity or resource --- content/learn/book/ecs/resources/_index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index 9eb59c72df..14eec3e0fe 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -203,14 +203,14 @@ fn check_secret( } ``` -## Singleton entity or Resource? +## Singleton entity or resource? -As discussed in [**Systems access data through queries**](../systems-queries/_index.md), [`Query::single`](https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html#method.single) is a convenient way to get access to the data of an entity when you know that exactly one entity will be returned by a query. +As discussed in [**Fetching data with queries**](../systems-queries/_index.md), [`Query::single`](https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html#method.single) is a convenient way to get access to the data of an entity when you know that exactly one entity will be returned by a query. So when should you use a singleton entity, and when should you use a resource? -Let's list the advantage of each, beginning with resources: +Let's list the advantages of each, beginning with resources: -- fast and simple access model: no need for queries or unwrapping +- fast and simple access model: no need for queries - will not be accidentally broken by later code that modifies your entity's components or creates more matching entities - can store data that is not thread-safe using [`NonSend`] resources - clearly communicates intent @@ -222,7 +222,7 @@ By contrast, singleton entities are useful because they: - have more granular change detection: operating on a per component basis rather than the entire object - allows you to fetch only the data you immediately need, rather than the entire resource struct -Overall, resources are a good default for one-off demands: they're clear and very ergonomic to access. +Overall, resources are a good default for relatively isolated parts of your design: they're clear, robust and very ergonomic to access. You should turn to singleton entities when you want to share behavior with other entities (i.e. a singleton entity for the player is almost always going to be superior to a monolithic `Player` resource), or for when you want to be able to extend or modify behavior dynamically during gameplay. ## Complex resource initialization using `FromWorld` From d48039f71cd9acd2aa3af413c9adf593312e4934 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 14 May 2022 20:53:00 -0400 Subject: [PATCH 6/7] Revise optional resources section --- content/learn/book/ecs/resources/_index.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index 14eec3e0fe..96b1c8e2c3 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -238,9 +238,9 @@ For advice on how to work with the [`World`] exposed by the [`FromWorld::from_wo [`FromWorld::from_world`]: https://docs.rs/bevy/latest/bevy/ecs/world/trait.FromWorld.html#tymethod.from_world -## Optional Resources +## Optional resources -Sometimes, a resource may not exist by the time a regularly scheduled system is called. +Sometimes, you may need to write a system that is robust to whether or not a resource exists. We can handle both the case where it exists and the case where it doesn't by requesting `Option>` (or other resource types like [`ResMut`], [`NonSend`] and [`NonSendMut`]) as a system parameter, and then branching on the resulting `Option` returned. @@ -266,16 +266,19 @@ struct Countdown { fn countdown( countdown: Option>, mut commands: Commands, + // Under the hood, this data is stored in a ResMut> mut app_exit: EventWriter, ) { match countdown { - // Resources can be inserted at runtime using commands + // If no Countdown resource exists, add one None => commands.insert_resource(Countdown { time_remaining: 10 }), + // If the resource exists, count it down Some(mut validated_countdown) => { info!("{} ticks remaining!", validated_countdown.time_remaining); if validated_countdown.time_remaining > 1 { validated_countdown.time_remaining -= 1; } else { + // When we reach zero, exit the App info!("Ka-BOOM!"); app_exit.send(AppExit); } From 22520a9f1c0e7f959d0d21d51209a98dd31bb952 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 14 May 2022 20:55:25 -0400 Subject: [PATCH 7/7] Revise NonSend resources section --- content/learn/book/ecs/resources/_index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/learn/book/ecs/resources/_index.md b/content/learn/book/ecs/resources/_index.md index 96b1c8e2c3..e39f3cc07d 100644 --- a/content/learn/book/ecs/resources/_index.md +++ b/content/learn/book/ecs/resources/_index.md @@ -291,8 +291,11 @@ fn countdown( ## `NonSend` resources -Non-send resources are used to store data that do not meet the [`Send + Sync`] trait bounds: they cannot be sent safely across threads. +Non-send resources are used to store data that do not meet the [`Send + Sync`] trait bounds: this data cannot be sent safely across threads. Their use cases are typically quite advanced and tend to involve interfacing with external libraries for things like audio or networking. `NonSend` and `NonSendMut` can be directly substituted for `Res` and `ResMut` in any system. The inclusion of one or more non-send resources in your system will force that system to run on the main thread, rather than being automatically scheduled to the first available thread. +This can negatively impact system-level parallelism: systems with any nonsend parameters are always incompatible with each other as they both need to run on the main thread. + +[`Send + Sync`]: https://doc.rust-lang.org/nomicon/send-and-sync.html \ No newline at end of file