diff --git a/Cargo.toml b/Cargo.toml index 877811d197aa9..f1ee2bb2165f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2362,17 +2362,6 @@ description = "Illustrates message creation, activation, and reception" category = "ECS (Entity Component System)" wasm = false -[[example]] -name = "send_and_receive_messages" -path = "examples/ecs/send_and_receive_messages.rs" -doc-scrape-examples = true - -[package.metadata.example.send_and_receive_messages] -name = "Send and receive messages" -description = "Demonstrates how to send and receive messages of the same type in a single system" -category = "ECS (Entity Component System)" -wasm = false - [[example]] name = "entity_disabling" path = "examples/ecs/entity_disabling.rs" diff --git a/crates/bevy_ecs/src/message/message_cursor.rs b/crates/bevy_ecs/src/message/message_cursor.rs index 65bb1f55ccc46..23fd850bafbb0 100644 --- a/crates/bevy_ecs/src/message/message_cursor.rs +++ b/crates/bevy_ecs/src/message/message_cursor.rs @@ -10,11 +10,11 @@ use core::marker::PhantomData; /// /// Access to the [`Messages`] resource is required to read any incoming messages. /// -/// In almost all cases, you should just use a [`MessageReader`] or [`MessageMutator`], +/// In almost all cases, you should just use a [`MessageReader`] to read messages, +/// or a [`MessageMutator`] to modify messages or to read and write messages in the same system, /// which will automatically manage the state for you. /// -/// However, this type can be useful if you need to manually track messages, -/// such as when you're attempting to send and receive messages of the same type in the same system. +/// However, this type can be useful if you need to manually track messages. /// /// # Example /// diff --git a/crates/bevy_ecs/src/message/message_mutator.rs b/crates/bevy_ecs/src/message/message_mutator.rs index caeb6688eaeae..0893708367e5e 100644 --- a/crates/bevy_ecs/src/message/message_mutator.rs +++ b/crates/bevy_ecs/src/message/message_mutator.rs @@ -1,13 +1,19 @@ #[cfg(feature = "multi_threaded")] use crate::message::MessageMutParIter; use crate::{ - message::{Message, MessageCursor, MessageMutIterator, MessageMutIteratorWithId, Messages}, + message::{ + Message, MessageCursor, MessageId, MessageMutIterator, MessageMutIteratorWithId, Messages, + WriteBatchIds, + }, system::{Local, ResMut, SystemParam}, }; -/// Mutably reads messages of type `T` keeping track of which messages have already been read -/// by each system allowing multiple systems to read the same messages. Ideal for chains of systems -/// that all want to modify the same messages. +/// Reads and writes [`Message`]s of type `T`, keeping track of which messages have already been read. +/// +/// This can be used if a system needs to both read and write messages of the same type. +/// +/// Since it has exclusive access to the underlying messages, it also permits messages to be modified as they are read. +/// This is ideal for chains of systems that all want to modify the same messages. /// /// # Usage /// @@ -17,11 +23,17 @@ use crate::{ /// /// #[derive(Message, Debug)] /// pub struct MyMessage(pub u32); // Custom message type. -/// fn my_system(mut reader: MessageMutator) { -/// for message in reader.read() { +/// fn my_system(mut mutator: MessageMutator) { +/// // This message will be read immediately by this system, +/// // and will then be visible to other systems. +/// mutator.write(MyMessage(0)); +/// for message in mutator.read() { /// message.0 += 1; /// println!("received message: {:?}", message); /// } +/// // This message will be read on the next run of this system, +/// // but will be visible immediately to other systems. +/// mutator.write(MyMessage(0)); /// } /// ``` /// @@ -56,7 +68,7 @@ impl<'w, 's, M: Message> MessageMutator<'w, 's, M> { self.reader.read_mut(&mut self.messages) } - /// Like [`read`](Self::read), except also returning the [`MessageId`](super::MessageId) of the messages. + /// Like [`read`](Self::read), except also returning the [`MessageId`] of the messages. pub fn read_with_id(&mut self) -> MessageMutIteratorWithId<'_, M> { self.reader.read_mut_with_id(&mut self.messages) } @@ -140,4 +152,35 @@ impl<'w, 's, M: Message> MessageMutator<'w, 's, M> { pub fn clear(&mut self) { self.reader.clear(&self.messages); } + + /// Writes an `message`, which can later be read by [`MessageReader`](super::MessageReader)s. + /// This method returns the [ID](`MessageId`) of the written `message`. + /// + /// See [`Messages`] for details. + #[track_caller] + pub fn write(&mut self, message: M) -> MessageId { + self.messages.write(message) + } + + /// Writes a list of `messages` all at once, which can later be read by [`MessageReader`](super::MessageReader)s. + /// This is more efficient than writing each message individually. + /// This method returns the [IDs](`MessageId`) of the written `messages`. + /// + /// See [`Messages`] for details. + #[track_caller] + pub fn write_batch(&mut self, messages: impl IntoIterator) -> WriteBatchIds { + self.messages.write_batch(messages) + } + + /// Writes the default value of the message. Useful when the message is an empty struct. + /// This method returns the [ID](`MessageId`) of the written `message`. + /// + /// See [`Messages`] for details. + #[track_caller] + pub fn write_default(&mut self) -> MessageId + where + M: Default, + { + self.messages.write_default() + } } diff --git a/crates/bevy_ecs/src/message/message_writer.rs b/crates/bevy_ecs/src/message/message_writer.rs index 402f9ca571244..bd284783818bc 100644 --- a/crates/bevy_ecs/src/message/message_writer.rs +++ b/crates/bevy_ecs/src/message/message_writer.rs @@ -5,6 +5,11 @@ use crate::{ /// Writes [`Message`]s of type `T`. /// +/// This system parameter takes exclusive access to the [`Messages`] resource, +/// so a system cannot also have a [`MessageReader`](super::MessageReader) parameter of the same type. +/// If you need to both read and write messages of the same type, +/// use [`MessageMutator`](super::MessageMutator). +/// /// # Usage /// /// `MessageWriter`s are usually declared as a [`SystemParam`]. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 53a7c56b98e16..43fbf244271d4 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -526,6 +526,8 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re /// // because they both need access to the same message queue. /// // SOLUTION: `ParamSet` allows these conflicting parameters to be used safely /// // by ensuring only one is accessed at a time. +/// // Note that a better solution here is to use `MessageMutator`, +/// // which both reads and writes messages with a single parameter. /// MessageReader, /// MessageWriter, /// // PROBLEM: `&World` needs read access to everything, which conflicts with diff --git a/examples/README.md b/examples/README.md index d52964858c231..7685e74712490 100644 --- a/examples/README.md +++ b/examples/README.md @@ -351,7 +351,6 @@ Example | Description [Relationships](../examples/ecs/relationships.rs) | Define and work with custom relationships between entities [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame [Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met -[Send and receive messages](../examples/ecs/send_and_receive_messages.rs) | Demonstrates how to send and receive messages of the same type in a single system [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [State Scoped](../examples/ecs/state_scoped.rs) | Shows how to spawn entities that are automatically despawned either when entering or exiting specific game states. [System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state diff --git a/examples/ecs/message.rs b/examples/ecs/message.rs index 5306132017e20..c5d3874bd5475 100644 --- a/examples/ecs/message.rs +++ b/examples/ecs/message.rs @@ -48,6 +48,27 @@ fn deal_damage_over_time( } } +// This system both reads and writes `DealDamage` messages. +// +// Trying to do this with a `MessageReader` and a `MessageWriter` will fail with conflicts, +// since they both use the same underlying resource. +// Instead, you may use a `MessageMutator`, which allows both reading and writing. +fn apply_extra_damage(mut dmg_messages: MessageMutator) { + let mut extra = Vec::new(); + for message in dmg_messages.read() { + if message.amount >= 10 { + extra.push(DealDamage { + amount: message.amount / 10, + }); + } + } + // Note that this system will read messages it wrote itself! + // These are written after the `read()`, so they will be read the next time it runs. + for message in extra { + dmg_messages.write(message); + } +} + // This system mutates the 'DealDamage' messages to apply some armor value // It also sends an 'ArmorBlockedDamage' message if the value of 'DealDamage' is zero // @@ -124,6 +145,7 @@ fn main() { Update, ( deal_damage_over_time, + apply_extra_damage, apply_armor_to_damage, apply_damage_to_health, ) diff --git a/examples/ecs/send_and_receive_messages.rs b/examples/ecs/send_and_receive_messages.rs deleted file mode 100644 index 83b7945bcbf04..0000000000000 --- a/examples/ecs/send_and_receive_messages.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! From time to time, you may find that you want to both send and receive a message of the same type in a single system. -//! -//! Of course, this results in an error: the borrows of [`MessageWriter`] and [`MessageReader`] overlap, -//! if and only if the [`Message`] type is the same. -//! One system parameter borrows the [`Messages`] resource mutably, and another system parameter borrows the [`Messages`] resource immutably. -//! If Bevy allowed this, this would violate Rust's rules against aliased mutability. -//! In other words, this would be Undefined Behavior (UB)! -//! -//! There are two ways to solve this problem: -//! -//! 1. Use [`ParamSet`] to check out the [`MessageWriter`] and [`MessageReader`] one at a time. -//! 2. Use a [`Local`] [`MessageCursor`] instead of a [`MessageReader`], and use [`ResMut`] to access [`Messages`]. -//! -//! In the first case, you're being careful to only check out only one of the [`MessageWriter`] or [`MessageReader`] at a time. -//! By "temporally" separating them, you avoid the overlap. -//! -//! In the second case, you only ever have one access to the underlying [`Messages`] resource at a time. -//! But in exchange, you have to manually keep track of which messages you've already read. -//! -//! Let's look at an example of each. - -use bevy::{diagnostic::FrameCount, ecs::message::MessageCursor, prelude::*}; - -fn main() { - let mut app = App::new(); - app.add_plugins(MinimalPlugins) - .add_message::() - .add_message::() - .add_message::() - .add_systems(Update, read_and_write_different_message_types) - .add_systems( - Update, - ( - send_messages, - debug_messages, - send_and_receive_param_set, - debug_messages, - send_and_receive_manual_message_reader, - debug_messages, - ) - .chain(), - ); - // We're just going to run a few frames, so we can see and understand the output. - app.update(); - // By running for longer than one frame, we can see that we're caching our cursor in the message queue properly. - app.update(); -} - -#[derive(Message)] -struct A; - -#[derive(Message)] -struct B; - -// This works fine, because the types are different, -// so the borrows of the `MessageWriter` and `MessageReader` don't overlap. -// Note that these borrowing rules are checked at system initialization time, -// not at compile time, as Bevy uses internal unsafe code to split the `World` into disjoint pieces. -fn read_and_write_different_message_types(mut a: MessageWriter, mut b: MessageReader) { - for _ in b.read() {} - a.write(A); -} - -/// A dummy message type. -#[derive(Debug, Clone, Message)] -struct DebugMessage { - resend_from_param_set: bool, - resend_from_local_message_reader: bool, - times_sent: u8, -} - -/// A system that sends all combinations of messages. -fn send_messages(mut debug_messages: MessageWriter, frame_count: Res) { - println!("Sending messages for frame {}", frame_count.0); - - debug_messages.write(DebugMessage { - resend_from_param_set: false, - resend_from_local_message_reader: false, - times_sent: 1, - }); - debug_messages.write(DebugMessage { - resend_from_param_set: true, - resend_from_local_message_reader: false, - times_sent: 1, - }); - debug_messages.write(DebugMessage { - resend_from_param_set: false, - resend_from_local_message_reader: true, - times_sent: 1, - }); - debug_messages.write(DebugMessage { - resend_from_param_set: true, - resend_from_local_message_reader: true, - times_sent: 1, - }); -} - -/// A system that prints all messages sent since the last time this system ran. -/// -/// Note that some messages will be printed twice, because they were sent twice. -fn debug_messages(mut messages: MessageReader) { - for message in messages.read() { - println!("{message:?}"); - } -} - -/// A system that both sends and receives messages using [`ParamSet`]. -fn send_and_receive_param_set( - mut param_set: ParamSet<(MessageReader, MessageWriter)>, - frame_count: Res, -) { - println!( - "Sending and receiving messages for frame {} with a `ParamSet`", - frame_count.0 - ); - - // We must collect the messages to resend, because we can't access the writer while we're iterating over the reader. - let mut messages_to_resend = Vec::new(); - - // This is p0, as the first parameter in the `ParamSet` is the reader. - for message in param_set.p0().read() { - if message.resend_from_param_set { - messages_to_resend.push(message.clone()); - } - } - - // This is p1, as the second parameter in the `ParamSet` is the writer. - for mut message in messages_to_resend { - message.times_sent += 1; - param_set.p1().write(message); - } -} - -/// A system that both sends and receives messages using a [`Local`] [`MessageCursor`]. -fn send_and_receive_manual_message_reader( - // The `Local` `SystemParam` stores state inside the system itself, rather than in the world. - // `MessageCursor` is the internal state of `MessageReader`, which tracks which messages have been seen. - mut local_message_reader: Local>, - // We can access the `Messages` resource mutably, allowing us to both read and write its contents. - mut messages: ResMut>, - frame_count: Res, -) { - println!( - "Sending and receiving messages for frame {} with a `Local", - frame_count.0 - ); - - // We must collect the messages to resend, because we can't mutate messages while we're iterating over the messages. - let mut messages_to_resend = Vec::new(); - - for message in local_message_reader.read(&messages) { - if message.resend_from_local_message_reader { - // For simplicity, we're cloning the message. - // In this case, since we have mutable access to the `Messages` resource, - // we could also just mutate the message in-place, - // or drain the message queue into our `messages_to_resend` vector. - messages_to_resend.push(message.clone()); - } - } - - for mut message in messages_to_resend { - message.times_sent += 1; - messages.write(message); - } -}