-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Wait until FixedUpdate
can see events before dropping them
#10077
Conversation
Doesn't this create the opposite problem where events could be missed outside of |
Hmm, yeah. I guess it really does need to be an indirect signal after all. I'll change it so that That should work. |
Related to #6183. |
It might be nice to document the exact behavior here, but given that this is a known-stopgap solution, I don't think it's particularly important to do so. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be considered a breaking change. If people are using bevy_time, but using their own main schedule that doesn't run fixedupdate, then this will never update events.
Ideally we'd probably have a FixedUpdate plugin that would do all this. Since we don't, I'd like to keep this a focused change and probably just have a migration guide. I imagine that most people are probably using the default schedule setup. So this probably won't effect many if any users.
I think a good addition here might be to check if the time this event was last sent is really long, and in those cases emit a warning that the user may not be calling this method (for the cases where someone turned off FixedUpdate, and is rolling their own version, but didn't know that calling this was a requirement). The time-based approach would ensure that this is notified to the user at a specific interval regardless of how many events are actually sent. |
57f92f0
to
6fd4f4c
Compare
(Moving to 0.12.1 so we don't forget to consider it, not because I think it should land there) |
I'd like to propose a possibly controversial alternative: inputs should never be read in a fixed update system. This is not just a way to fix this, this is a fundamental design remark: inputs are inherently tied to rendering, in the sense that they're polled in the main loop, that most inputs produce visible reactions, and that in AR/VR inputs determine the camera transform. So querying user input in fixed update I think is a design mistake. Now, I can understand that this is not convenient, in particular when you want physics to react to user inputs. But I think this should be handled by the user on a case by case basis. Note that Unity has exactly the same issue/limitation. This is by design. Only problem is in Unity this is poorly documented, short of a tangential note:
|
Let me add a few more proofs that this is a bad idea:
|
I'm working on a game where all logic is done in What this does not work for is other events, in particular |
(edit: my original tone was too abrasive, sorry about that)
Input is not "inherently tied to rendering". It's not inherently tied to anything. We don't poll input. Input events are continuously delivered to us from the OS (through I think your argument really has no basis. I think you may be misinformed about how state changes make their way from input devices to the application. I tried to reference the long-term vision in the PR description, but let me also point to some existing context on this issue that you maybe aren't familiar with. Since you mentioned AR/VR, AR/VR applications want async reprojection, and that requires being able to, at any arbitrary time, read newer input events that showed up in the middle of the frame. (We can't do this without #9122 though.) Input is really not coupled to frames at all.
Thinking that this is a case-by-case problem shows a lack of understanding.
Unity's legacy input module has said limitation, but their "new input system" does not (API docs here). Even then, Unity can't handle events properly in both In the first place, Unity isn't a particularly well-designed engine. If you're looking for evidence from another engine, look at Godot. They have added a feature addressing this same issue.
This PR is supposed to affect all events. Why do you consider this a problem? It's a clear answer for someone wanting to read an event in
This PR doesn't have a priori knowledge either. If users demanded more clocks, the solution would be the same—keep events alive until they're in the "past" for all clocks. But frankly, that's an unreasonable demand. The new
It's completely sensible. Look at Overwatch's "High Precision Mouse Input" or Counter Strike 2's "Sub-Tick Input".1 Those work by timestamping input events as they're received (from the OS) so that they can be seen on the correct tick (and even account for the exact moment within the tick something happened), which is exactly what I'm proposing / where I'm trying to get us. Does that still seem like a bad design?
There are no "contradicting" inputs. Input events arrive continuously. Grouping them by tick is just as arbitrary as grouping them by frame.
This PR and what I've stated in the (Honestly, with the amount of effort I've invested into Bevy specifically to improve fixed timestep and input, it's really upsetting that I have to spend so much time convincing people that, like, I know what I'm doing. I've written at length about our outstanding issues with input and how to address them. I've even investigated the Footnotes
|
@maniwani I share your frustration and perspective, but your tone is quite abrasive.
I think that we should make events work, by default, for all users, regardless of the architecture they choose. The existing design silently and sporadically fails, for all events (not just input) that are sent between the main schedule and The tradeoffs here for fixing this bug are very mild: events persist slightly longer, which may result in a slight bounded increase in the memory their queues use. I'm not immediately clear how one might integrate this work with other types of user-defined Timestamped input events are an important next step, but I am fully on board with @maniwani's vision of how we can solve this problem. |
Definitely agree: I'm looking to eventually upstream this. Users should very rarely be reading raw inputs themselves in real games. |
I did not say events would be emitted more than once. A system in
Like I said, XR applications want to read inputs in advance, specifically to reduce motion sickness. That's not even possible for us now, but it will be once we merge #9122.
I was not praising Unity. You brought up a significant shortcoming of their legacy system. They attempted to partially address that limitation in their newer system, but still fell short. I'm a bit baffled that you're trying to argue that Unity—with all its half-baked systems (e.g. DOTS)—must have had a good reason for this particular thing being half-baked as well. Am I just misunderstanding Unity and its various shortcomings are all actually clever design points that I just don't understand yet?
And maybe it was an oversight and I'm right and developing our event handling according to what I propose will be better.
They're always disjoint though. (edit: Even if you propose e.g. using the arrow keys for both moving the player and scrolling a menu, you would never have both active at the same time. It's trivial to use a resource or state to control which one is active. With such an obvious fix, I don't buy the footgun argument. There's no need for any complicated "event claiming" system.)
No events will be lost and there's no need to reconcile things. As I've written in the PR description, the
This is pretty apples-to-oranges. Input-action bindings are orthogonal to this discussion about timing.
I am aware of what that PR did (I did help write it). Technically, we had 3 clocks even before the PR; they were just refactored.
Just me speaking as one of the co-authors, but "users can make their own clocks" was not a big priority. The underlying clock type exists because we wanted to minimize code duplication, and it seemed like there was no harm in making it I think it's a bit odd for you to place such an emphasis on it when every other engine doesn't even let you do that. Their users are not clamoring for custom clocks. And a single user inquiring about it is not really a hint that there are many others.
Actual usage patterns take priority over mostly hypothetical ones. Events didn't work in Likewise,
Users will not need to interpret or know any of that. They can write systems that read events normally. There's nothing "hard to use" about it. I really don't see your POV here. From my POV, there is no additional cognitive burden being placed on users.
You are talking about frames in the context of I don't think this was a productive discussion. |
About custom clocks (and schedules)
I'm the guy who asked for a custom clock. Let me explain my use-case here. I was connecting physics engine with bevy (that's bevy_mod_physx now) for a particular project. It needed to be:
I arrived at a custom schedule (called Looking back, what I've got is just slightly different About this actual PRI'm going to agree with @djeedai here: this is a bad idea.
There's also Thinking about this some more, we've got 2 options: Option 1: tie events to schedulesapp.add_event::<EventOne>();
app.add_fixed_event::<EventTwo>();
// this would panic:
app.add_systems(FixedUpdate, |_: EventReader<EventOne>| {}) This is a simple and stupid solution. Downside: Option 2: get creative
or some combinations of those but I'm sure that was all considered before |
No, that's not correct. If you emit an event in This PR improved upon the status quo by making it so events are only "missed" when they're ignored by
True, but it's easy enough for users who are already doing such a fancy thing to either remove this new signalling resource or schedule the signalling system inside their custom schedule.
I'll give you simple and ill-advised. But it's not a solution. I won't support that. It was like the second idea I considered and rejected a year ago. Because it doesn't really solve anything. Us just pretending the problem doesn't exist would be silly. (edit: This is not a problem that can (or should) be fixed by redefining it.
Yes, I did consider all of this. Buffered events have never claimed to be reliable (and they clearly can't be without leaving room for a memory leak). Waiting for an event to be read by all systems with the corresponding reader will suffer from the opposite problem of one forgotten I'm sure it feels like such a longstanding issue shouldn't be fixable by a tiny PR like this, but this is the most concise, least "hacky" solution to this issue that I landed on after months. And the most substantial criticism thus far has been that it doesn't just work seamlessly with the brand-new thing that just came out. When we introduce timestamps, we can make it so that only events with timestamps that are in the past from the POV of the "most behind" (Also using double buffering vs. a single ring buffer can be pretty functionally equivalent. Double buffering will use more memory but it can be cheaper to drop the events.) |
Sorry, you're correct there. Then, this PR is actual improvement, besides the potential infinite buffering case (which we should have a warning for in the future).
Read that as independent schedules. In But if someone calls the same schedule from
They can be reliable (assuming you have a certain definition of reliability):
Those "limits" are called QoS policies, and that's well-developed topic in enterprise systems. "No more than X events" is called This can in theory solve all EventReader problems! In practice, users won't understand it, limits will be misconfigured, memory will be leaked, but we would blame the users. It isn't worth it just for
Okay, sounds great. |
## Objective Currently, events are dropped after two frames. This cadence wasn't *chosen* for a specific reason, double buffering just lets events persist for at least two frames. Events only need to be dropped at a predictable point so that the event queues don't grow forever (i.e. events should never cause a memory leak). Events (and especially input events) need to be observable by systems in `FixedUpdate`, but as-is events are dropped before those systems even get a chance to see them. ## Solution Instead of unconditionally dropping events in `First`, require `FixedUpdate` to first queue the buffer swap (if the `TimePlugin` has been installed). This way, events are only dropped after a frame that runs `FixedUpdate`. ## Future Work In the same way we have independent copies of `Time` for tracking time in `Main` and `FixedUpdate`, we will need independent copies of `Input` for tracking press/release status correctly in `Main` and `FixedUpdate`. -- Every run of `FixedUpdate` covers a specific timespan. For example, if the fixed timestep `Δt` is 10ms, the first three `FixedUpdate` runs cover `[0ms, 10ms)`, `[10ms, 20ms)`, and `[20ms, 30ms)`. `FixedUpdate` can run many times in one frame. For truly framerate-independent behavior, each `FixedUpdate` should only see the events that occurred in its covered timespan, but what happens right now is the first step in the frame reads all pending events. Fixing that will require timestamped events. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
FWIW this change def has impact on certain ways you were able to use events to move the camera for example. I just updated from 0.11 to 0.12.1 and was wondering why my camera suddenly ended up in weird positions. Its not he prettiest code but it got stuff done: Inside the mouse_motion system the |
i have a very similar setup for mouse wheel input. i switch between scrolling and zooming with keyboard presses. every time the buttons are pressed, all the mouse wheel events are replayed and the camera ends up in wild positions.
|
# Objective Fix an issue where events are not being dropped after being read. I believe #10077 introduced this issue. The code currently works as follows: 1. `EventUpdateSignal` is **shared for all event types** 2. During the fixed update phase, `EventUpdateSignal` is set to true 3. `event_update_system`, **unique per event type**, runs to update Events<T> 4. `event_update_system` reads value of `EventUpdateSignal` to check if it should update, and then **resets** the value to false If there are multiple event types, the first `event_update_system` run will reset the shared `EventUpdateSignal` signal, preventing other events from being cleared. ## Solution I've updated the code to have separate signals per event type and added a shared signal to notify all systems that the time plugin is installed. ## Changelog - Fixed bug where events were not being dropped
# Objective Fix an issue where events are not being dropped after being read. I believe bevyengine#10077 introduced this issue. The code currently works as follows: 1. `EventUpdateSignal` is **shared for all event types** 2. During the fixed update phase, `EventUpdateSignal` is set to true 3. `event_update_system`, **unique per event type**, runs to update Events<T> 4. `event_update_system` reads value of `EventUpdateSignal` to check if it should update, and then **resets** the value to false If there are multiple event types, the first `event_update_system` run will reset the shared `EventUpdateSignal` signal, preventing other events from being cleared. ## Solution I've updated the code to have separate signals per event type and added a shared signal to notify all systems that the time plugin is installed. ## Changelog - Fixed bug where events were not being dropped
…13808) # Objective As discovered in Leafwing-Studios/leafwing-input-manager#538, there appears to be some real weirdness going on in how event updates are processed between Bevy 0.13 and Bevy 0.14. To identify the cause and prevent regression, I've added tests to validate the intended behavior. My initial suspicion was that this would be fixed by #13762, but that doesn't seem to be the case. Instead, events appear to never be updated at all when using `bevy_app` by itself. This is part of the problem resolved by #11528, and introduced by #10077. After some investigation, it appears that `signal_event_update_system` is never added using a bare-bones `App`, and so event updates are always skipped. This can be worked around by adding your own copy to a later-in-the-frame schedule, but that's not a very good fix. ## Solution Ensure that if we're not using a `FixedUpdate` schedule, events are always updated every frame. To do this, I've modified the logic of `event_update_condition` and `event_update_system` to clearly and correctly differentiate between the two cases: where we're waiting for a "you should update now" signal and where we simply don't care. To encode this, I've added the `ShouldUpdateEvents` enum, replacing a simple `bool` in `EventRegistry`'s `needs_update` field. Now, both tests pass as expected, without having to manually add a system! ## Testing I've written two parallel unit tests to cover the intended behavior: 1. Test that `iter_current_update_events` works as expected in `bevy_ecs`. 2. Test that `iter_current_update_events` works as expected in `bevy_app` I've also added a test to verify that event updating works correctly in the presence of a fixed main schedule, and a second test to verify that fixed updating works at all to help future authors narrow down failures. ## Outstanding - [x] figure out why the `bevy_app` version of this test fails but the `bevy_ecs` version does not - [x] figure out why `EventRegistry::run_updates` isn't working properly - [x] figure out why `EventRegistry::run_updates` is never getting called - [x] figure out why `event_update_condition` is always returning false - [x] figure out why `EventRegistry::needs_update` is always false - [x] verify that the problem is a missing `signal_events_update_system` --------- Co-authored-by: Mike <mike.hsu@gmail.com>
…evyengine#13808) As discovered in Leafwing-Studios/leafwing-input-manager#538, there appears to be some real weirdness going on in how event updates are processed between Bevy 0.13 and Bevy 0.14. To identify the cause and prevent regression, I've added tests to validate the intended behavior. My initial suspicion was that this would be fixed by bevyengine#13762, but that doesn't seem to be the case. Instead, events appear to never be updated at all when using `bevy_app` by itself. This is part of the problem resolved by bevyengine#11528, and introduced by bevyengine#10077. After some investigation, it appears that `signal_event_update_system` is never added using a bare-bones `App`, and so event updates are always skipped. This can be worked around by adding your own copy to a later-in-the-frame schedule, but that's not a very good fix. Ensure that if we're not using a `FixedUpdate` schedule, events are always updated every frame. To do this, I've modified the logic of `event_update_condition` and `event_update_system` to clearly and correctly differentiate between the two cases: where we're waiting for a "you should update now" signal and where we simply don't care. To encode this, I've added the `ShouldUpdateEvents` enum, replacing a simple `bool` in `EventRegistry`'s `needs_update` field. Now, both tests pass as expected, without having to manually add a system! I've written two parallel unit tests to cover the intended behavior: 1. Test that `iter_current_update_events` works as expected in `bevy_ecs`. 2. Test that `iter_current_update_events` works as expected in `bevy_app` I've also added a test to verify that event updating works correctly in the presence of a fixed main schedule, and a second test to verify that fixed updating works at all to help future authors narrow down failures. - [x] figure out why the `bevy_app` version of this test fails but the `bevy_ecs` version does not - [x] figure out why `EventRegistry::run_updates` isn't working properly - [x] figure out why `EventRegistry::run_updates` is never getting called - [x] figure out why `event_update_condition` is always returning false - [x] figure out why `EventRegistry::needs_update` is always false - [x] verify that the problem is a missing `signal_events_update_system` --------- Co-authored-by: Mike <mike.hsu@gmail.com>
…evyengine#13808) As discovered in Leafwing-Studios/leafwing-input-manager#538, there appears to be some real weirdness going on in how event updates are processed between Bevy 0.13 and Bevy 0.14. To identify the cause and prevent regression, I've added tests to validate the intended behavior. My initial suspicion was that this would be fixed by bevyengine#13762, but that doesn't seem to be the case. Instead, events appear to never be updated at all when using `bevy_app` by itself. This is part of the problem resolved by bevyengine#11528, and introduced by bevyengine#10077. After some investigation, it appears that `signal_event_update_system` is never added using a bare-bones `App`, and so event updates are always skipped. This can be worked around by adding your own copy to a later-in-the-frame schedule, but that's not a very good fix. Ensure that if we're not using a `FixedUpdate` schedule, events are always updated every frame. To do this, I've modified the logic of `event_update_condition` and `event_update_system` to clearly and correctly differentiate between the two cases: where we're waiting for a "you should update now" signal and where we simply don't care. To encode this, I've added the `ShouldUpdateEvents` enum, replacing a simple `bool` in `EventRegistry`'s `needs_update` field. Now, both tests pass as expected, without having to manually add a system! I've written two parallel unit tests to cover the intended behavior: 1. Test that `iter_current_update_events` works as expected in `bevy_ecs`. 2. Test that `iter_current_update_events` works as expected in `bevy_app` I've also added a test to verify that event updating works correctly in the presence of a fixed main schedule, and a second test to verify that fixed updating works at all to help future authors narrow down failures. - [x] figure out why the `bevy_app` version of this test fails but the `bevy_ecs` version does not - [x] figure out why `EventRegistry::run_updates` isn't working properly - [x] figure out why `EventRegistry::run_updates` is never getting called - [x] figure out why `event_update_condition` is always returning false - [x] figure out why `EventRegistry::needs_update` is always false - [x] verify that the problem is a missing `signal_events_update_system` --------- Co-authored-by: Mike <mike.hsu@gmail.com>
…13808) (#13842) # Objective - Related to #13825 ## Solution - Cherry picked the merged PR and performed the necessary changes to adapt it to the 0.14 release branch. --------- As discovered in Leafwing-Studios/leafwing-input-manager#538, there appears to be some real weirdness going on in how event updates are processed between Bevy 0.13 and Bevy 0.14. To identify the cause and prevent regression, I've added tests to validate the intended behavior. My initial suspicion was that this would be fixed by #13762, but that doesn't seem to be the case. Instead, events appear to never be updated at all when using `bevy_app` by itself. This is part of the problem resolved by #11528, and introduced by #10077. After some investigation, it appears that `signal_event_update_system` is never added using a bare-bones `App`, and so event updates are always skipped. This can be worked around by adding your own copy to a later-in-the-frame schedule, but that's not a very good fix. Ensure that if we're not using a `FixedUpdate` schedule, events are always updated every frame. To do this, I've modified the logic of `event_update_condition` and `event_update_system` to clearly and correctly differentiate between the two cases: where we're waiting for a "you should update now" signal and where we simply don't care. To encode this, I've added the `ShouldUpdateEvents` enum, replacing a simple `bool` in `EventRegistry`'s `needs_update` field. Now, both tests pass as expected, without having to manually add a system! I've written two parallel unit tests to cover the intended behavior: 1. Test that `iter_current_update_events` works as expected in `bevy_ecs`. 2. Test that `iter_current_update_events` works as expected in `bevy_app` I've also added a test to verify that event updating works correctly in the presence of a fixed main schedule, and a second test to verify that fixed updating works at all to help future authors narrow down failures. - [x] figure out why the `bevy_app` version of this test fails but the `bevy_ecs` version does not - [x] figure out why `EventRegistry::run_updates` isn't working properly - [x] figure out why `EventRegistry::run_updates` is never getting called - [x] figure out why `event_update_condition` is always returning false - [x] figure out why `EventRegistry::needs_update` is always false - [x] verify that the problem is a missing `signal_events_update_system` Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Mike <mike.hsu@gmail.com>
Objective
Currently, events are dropped after two frames. This cadence wasn't chosen for a specific reason, double buffering just lets events persist for at least two frames. Events only need to be dropped at a predictable point so that the event queues don't grow forever (i.e. events should never cause a memory leak).
Events (and especially input events) need to be observable by systems in
FixedUpdate
, but as-is events are dropped before those systems even get a chance to see them.Solution
Instead of unconditionally dropping events in
First
, requireFixedUpdate
to first queue the buffer swap (if theTimePlugin
has been installed). This way, events are only dropped after a frame that runsFixedUpdate
.Future Work
In the same way we have independent copies of
Time
for tracking time inMain
andFixedUpdate
, we will need independent copies ofInput
for tracking press/release status correctly inMain
andFixedUpdate
.--
Every run of
FixedUpdate
covers a specific timespan. For example, if the fixed timestepΔt
is 10ms, the first threeFixedUpdate
runs cover[0ms, 10ms)
,[10ms, 20ms)
, and[20ms, 30ms)
.FixedUpdate
can run many times in one frame. For truly framerate-independent behavior, eachFixedUpdate
should only see the events that occurred in its covered timespan, but what happens right now is the first step in the frame reads all pending events.Fixing that will require timestamped events.