Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/message/message_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use core::marker::PhantomData;
///
/// Access to the [`Messages<M>`] 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
///
Expand Down
57 changes: 50 additions & 7 deletions crates/bevy_ecs/src/message/message_mutator.rs
Original file line number Diff line number Diff line change
@@ -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
///
Expand All @@ -17,11 +23,17 @@ use crate::{
///
/// #[derive(Message, Debug)]
/// pub struct MyMessage(pub u32); // Custom message type.
/// fn my_system(mut reader: MessageMutator<MyMessage>) {
/// for message in reader.read() {
/// fn my_system(mut mutator: MessageMutator<MyMessage>) {
/// // 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));
/// }
/// ```
///
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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<M> {
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<Item = M>) -> WriteBatchIds<M> {
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<M>
where
M: Default,
{
self.messages.write_default()
}
}
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/message/message_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ use crate::{

/// Writes [`Message`]s of type `T`.
///
/// This system parameter takes exclusive access to the [`Messages<T>`] 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`].
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MyMessage>,
/// MessageWriter<MyMessage>,
/// // PROBLEM: `&World` needs read access to everything, which conflicts with
Expand Down
1 change: 0 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions examples/ecs/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DealDamage>) {
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
//
Expand Down Expand Up @@ -124,6 +145,7 @@ fn main() {
Update,
(
deal_damage_over_time,
apply_extra_damage,
apply_armor_to_damage,
apply_damage_to_health,
)
Expand Down
165 changes: 0 additions & 165 deletions examples/ecs/send_and_receive_messages.rs

This file was deleted.