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

Consider adding non-draining event reader iterator #4083

Closed
djeedai opened this issue Mar 2, 2022 · 12 comments
Closed

Consider adding non-draining event reader iterator #4083

djeedai opened this issue Mar 2, 2022 · 12 comments
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible

Comments

@djeedai
Copy link
Contributor

djeedai commented Mar 2, 2022

What problem does this solve or what need does it fill?

Trying to loop over received events of an EventReader<E> twice or more in the same system on the same frame. Currently iter() drains, so all but the first iteration time return 0 event.

This is also confusing since iter() on say, Vev<T>, will not drain, and since EventReader::iter() is the only method available it's easy to not think and misuse it.

What solution would you like?

Add a non-draining iterator with an explicit name, making it obvious this should not be your default choice for new users.
Something like:

EventReader::iter_peek();

or

EventReader::peek().iter();

fn peek() -> PeekingEventIterator;

This not only solves the non-draining issue, but also shows in docs that there are 2 types of iterators, which prompt the user to question which one they need, avoiding the confusion with the non-draining iter() found in the standard library.

What alternative(s) have you considered?

Manual event clearing. This is overkill and error-prone, much more so than calling (N-1) times the non-draining iterator proposed and one last time the draining one. Or doing N non-draining and a manual clear().

Additional context

I understand the default iter() being draining to avoid user error, so avoiding on purpose any method name already existing on standard Rust iterators like drain() to avoid confusion. iter_peek() sounds good and longer (so less enticing) than iter() but we can even make it even less likely to be used like iter_without_draining_advanced_use_only(), as long as we provide an escape hatch out of the always-drain default we have now.

@djeedai djeedai added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Mar 2, 2022
@MrGVSV
Copy link
Member

MrGVSV commented Mar 2, 2022

EventReader::peek().iter();

One thing with this method actually is that we might be able to use a Drop impl on PeekingEventIterator to automatically handle the drain and event counter once dropped (if the user hasn't already).

@djeedai
Copy link
Contributor Author

djeedai commented Mar 2, 2022

One thing with this method actually is that we might be able to use a Drop impl on PeekingEventIterator to automatically handle the drain and event counter once dropped (if the user hasn't already).

That would be strange, if I scope my peek, then I cannot peek again after that because the Drop will have cleared the reader. I don't see how the reader would know that this peek was the last one for the scope. Or are you suggesting that the clear be in the Drop of EventReader itself? Or something else?

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events and removed S-Needs-Triage This issue needs to be labelled labels Mar 2, 2022
@alice-i-cecile
Copy link
Member

What if we implemented Clone on the EventReader?

@DJMcNab
Copy link
Member

DJMcNab commented Mar 2, 2022

Indeed, I have just started a slight rework of EventReader related infrastructure, which as a side point adds Clone to ManualEventReader. This use case is the perfect case to drop down to ManualEventReader

@MrGVSV
Copy link
Member

MrGVSV commented Mar 2, 2022

That would be strange, if I scope my peek, then I cannot peek again after that because the Drop will have cleared the reader. I don't see how the reader would know that this peek was the last one for the scope.

That makes sense. I guess I was thinking more for simple systems. But yeah, this wouldn't work if you created the peek in an inner function call, for example.

Or are you suggesting that the clear be in the Drop of EventReader itself?

That might be possible too, since the reader should only drop at the end of the main system. Although, that might be bad too if you need to take advantage of the 1-frame delay...

@MrGVSV
Copy link
Member

MrGVSV commented Mar 2, 2022

What if we implemented Clone on the EventReader?

Would that require T be Clone as well?

@alice-i-cecile
Copy link
Member

Would that require T be Clone as well?

I don't think so: we could clone the internal state, and the references to the events. The events themselves are never moved into the EventReader at all: they stay put in Res<Events<T>> (which, incidentally, is the current workaround).

@djeedai
Copy link
Contributor Author

djeedai commented Mar 4, 2022

which, incidentally, is the current workaround

@alice-i-cecile can you explain that statement please?

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Mar 4, 2022

The underlying event information can be accessed through Res<Events<T>>. This allows users to create multiple ManualEventReader within a single system, avoiding the need to drain the events.

The other workaround that comes to mind is a bit sillier, but quite a lot simpler: simply add two EventReader<T> system parameters. Each will have their own internal state, and will be able to be iterated independently.

@MrGVSV
Copy link
Member

MrGVSV commented Mar 4, 2022

The other workaround that comes to mind is a bit sillier, but quite a lot simpler: simply add two EventReader<T> system parameters. Each will have their own internal state, and will be able to be iterated independently.

Doesn't it rely on Local, though? So having two should still affect the same local system state, right? Or does Local work differently than the way I think it does?

@DJMcNab
Copy link
Member

DJMcNab commented Mar 5, 2022

Each local is a different value. Consider:

fn sys(a: Local<u32>, b: Local<u32>){
    let a: &mut u32 = &mut *a;
    let b: &mut u32 = &mut *b;
}

Which compiles and runs fine.

@rparrett
Copy link
Contributor

Looks like this was resolved by #12777.

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-Feature A new feature, making something new possible
Projects
None yet
Development

No branches or pull requests

5 participants