Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add a clear() method to the EventReader that consumes the iterator #4693

Closed
wants to merge 15 commits into from

Conversation

IceSentry
Copy link
Contributor

@IceSentry IceSentry commented May 7, 2022

Objective

  • It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
  • The current approach is to do something like events.iter().count() > 0 or events.iter().last().is_some(). It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

Solution

  • Add a .clear() method that consumes the iterator.
    • It takes the EventReader by value to make sure it isn't used again after it has been called.

Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}

@IceSentry IceSentry added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use labels May 7, 2022
Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good

We should link to any from is_empty. I'd say the docs for is_empty seem a bit backwarrds too, since e.any()===!e.is_empty(), but they both have the same docs.

I'm not sure I like how hazardous this is, but this makes it better

@IceSentry
Copy link
Contributor Author

Yeah, that's why I would like a better name, but I couldn't find anything. Or at least, anything that isn't overly verbose.

  • I thought of something like is_empty_consume() but it feels a bit weird.

I added a bit more docs to clarify the differences

@Nilirad
Copy link
Contributor

Nilirad commented May 8, 2022

It's hard to find a non verbose name since we want to get two outcomes from the function:

  • yields a bool whether or not the event queue contains any event.
  • clears the event queue.

As there is no word that encompasses those two actions, I'm OK with making the function name verbose.

@vacuus
Copy link

vacuus commented May 9, 2022

I'm not entirely satisfied with this name, but perhaps something like query_new_unread? My main concern is that it overlaps with how "query" is used in Bevy.
/bikeshed

Copy link
Contributor

@Nilirad Nilirad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, any doesn't give the idea of what the function does.

I think we can do of the following things:

  • keep using .iter().last().is_some() as it is the most idiomatic method and is quite understandable, especially if one knows that last consumes the iterator.
  • renaming any to something else, like consume_and_report, which is not 100% clear, but it's something that can be searched up and invites the user to read the docs, while any is obscure and completely undiscoverable.

crates/bevy_ecs/src/event.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/event.rs Outdated Show resolved Hide resolved
@tim-blackbird
Copy link
Contributor

tim-blackbird commented May 9, 2022

It seems difficult to convey the semantics of .iter().last().is_some() in a single name, even a verbose one.
How about adding a clear() method to EventReader that consumes the iterator(i.e. .iter().last()), and using it together with is_empty().

system_a should be easy to read, and familiar with anyone that has used a Vec.

fn system_a(mut events: EventReader<Event>) {
    if !events.is_empty() {
        events.clear();
        // Do the thing!
    }
}

system_b might be easy to read if you're familiar with this pattern and it saves one line, but is not any less verbose than system_a.

fn system_b(mut events: EventReader<Event>) {
    if events.iter().last().is_some() {
        // Do the thing!
    }
}

@alice-i-cecile
Copy link
Member

I really like @devil-ira's suggestion here. I like that it's so much more explicit, and the boilerplate isn't too bad.

@alice-i-cecile alice-i-cecile added X-Controversial There is active debate or serious implications around merging this PR and removed C-Feature A new feature, making something new possible X-Controversial There is active debate or serious implications around merging this PR labels May 9, 2022
@DJMcNab
Copy link
Member

DJMcNab commented May 9, 2022

Should EventReader panic (on drop?) if it still has events when the system ends?

That is, recognise that it's a logic error to not clear it? Note that people could still use ManualEventReader (which EventReader really should be created in terms - that's on the todo list) for a version which doesn't have that behaviour

@alice-i-cecile
Copy link
Member

Should EventReader panic (on drop?) if it still has events when the system ends?

Hmm. I like this idea: it's a good additional layer of safety, and I agree with you that this is almost always a logic error. That said, I'm nervous about the mechanism: the addition of panics in edge cases is not fun.

What about a debug assert on drop?

crates/bevy_ecs/src/event.rs Outdated Show resolved Hide resolved
@Nilirad
Copy link
Contributor

Nilirad commented May 9, 2022

I wonder if we could also change the breakout example to use the new approach.

@IceSentry IceSentry force-pushed the event-reader-consume-check branch from c7545e6 to 70a7467 Compare May 9, 2022 17:20
@IceSentry
Copy link
Contributor Author

IceSentry commented May 9, 2022

I decided to use @devil-ira suggestion of using a clear method with no returns. It's a bit verbose but it makes the pattern a lot clearer.

I also used it in the breakout example as @Nilirad suggested.

I also added a simple test.

Is it possible to reset the reviews? I wouldn't consider them valid after this change.

crates/bevy_ecs/src/event.rs Outdated Show resolved Hide resolved
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach much better.

IMO this is ready to merge once:

  1. Nits are addressed.
  2. A doc example is added.
  3. PR title and description are updated.

@Nilirad
Copy link
Contributor

Nilirad commented May 9, 2022

The API doc example can be easily added by copying it from the test.

@IceSentry
Copy link
Contributor Author

IceSentry commented May 9, 2022

Should I just use the test directly in the doc example and remove the test?

@IceSentry IceSentry changed the title Add a check to the EventReader that consumes the iterator Add a clear() method to the EventReader that consumes the iterator May 9, 2022
@Nilirad
Copy link
Contributor

Nilirad commented May 9, 2022

I would prefer the example being under a level 2 “Example” header. The example code should also be inside a common system function and boilerplate lines (arguably everything except the system function) should be hidden with #. The function can be tested as a system with assert_is_system.

@SUPERCILEX
Copy link
Contributor

Can we have clear return true if there was anything to clear? I've been doing something similar:

    if level_unloaded.iter().count() > 0 {
        **selected_piece = None;
    }

It might be a little weird, but it's not too different from HashSet::remove for example:

    if level_unloaded.clear() {
        **selected_piece = None;
    }

@alice-i-cecile
Copy link
Member

bors r+

bors bot pushed a commit that referenced this pull request May 12, 2022
…4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
@bors
Copy link
Contributor

bors bot commented May 12, 2022

Build failed:

@IceSentry
Copy link
Contributor Author

bors retry

bors bot pushed a commit that referenced this pull request May 13, 2022
…4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
@bors bors bot changed the title Add a clear() method to the EventReader that consumes the iterator [Merged by Bors] - Add a clear() method to the EventReader that consumes the iterator May 13, 2022
@bors bors bot closed this May 13, 2022
@IceSentry IceSentry deleted the event-reader-consume-check branch May 13, 2022 05:02
robtfm pushed a commit to robtfm/bevy that referenced this pull request May 17, 2022
…evyengine#4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
…evyengine#4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…evyengine#4693)

# Objective

- It's pretty common to want to check if an EventReader has received one or multiple events while also needing to consume the iterator to "clear" the EventReader.
- The current approach is to do something like `events.iter().count() > 0` or `events.iter().last().is_some()`. It's not immediately obvious that the purpose of that is to consume the events and check if there were any events. My solution doesn't really solve that part, but it encapsulates the pattern.

## Solution

- Add a `.clear()` method that consumes the iterator.
	- It takes the EventReader by value to make sure it isn't used again after it has been called.

---

## Migration Guide

Not a breaking change, but if you ever found yourself in a situation where you needed to consume the EventReader and check if there was any events you can now use

```rust
fn system(events: EventReader<MyEvent>) {
	if !events.is_empty {
		events.clear();
		// Process the fact that one or more event was received
	}
}
```


Co-authored-by: Charles <IceSentry@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants