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

Make Time update_with_instant pub #2549

Closed

Conversation

ostwilkens
Copy link
Contributor

Objective

A lot of systems depend on Time to implement frame-independent logic.
To be able to write tests for these systems, one needs to be able to mock Time. By mocking time, it's possible to write tests that confirm systems are frame-independent.

Solution

To mock Time, one needs to be able to manually update the Time resource with an Instant defined by the developer.
There is a function for this, but it's currently only public within the crate. I propose this function is exposed to the developer, to make it easy to write tests for systems utilizing the Time resource.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Jul 26, 2021
@alice-i-cecile
Copy link
Member

bors try

bors bot added a commit that referenced this pull request Jul 26, 2021
@alice-i-cecile
Copy link
Member

alice-i-cecile commented Jul 26, 2021

Welcome! I agree with the need for this, and think this is an excellent, simple solution.

As updating the time step manually is typically going to be a mistake in end user code outside of tests, we should add careful doc strings to both this method and the Time type to explain the correct usage :)

This would also be a nice addition to testing_systems.rs, found in the root tests folder.

@alice-i-cecile
Copy link
Member

Actually, this may also be useful for running simulations at faster than real time too. I've seen that requested before too; we should try to document that use case too. A full example might be better for that (you'll need to mess with one of the default plugins), but that work could be saved for a later PR.

@LegNeato
Copy link
Contributor

Perhaps only make this pub under #[test] to remove that footgun?

@alice-i-cecile alice-i-cecile added A-Core Common functionality for all bevy apps C-Usability A simple quality-of-life change that makes Bevy easier to use and removed S-Needs-Triage This issue needs to be labelled labels Jul 26, 2021
@bjorn3
Copy link
Contributor

bjorn3 commented Jul 26, 2021

Perhaps only make this pub under #[test] to remove that footgun?

That is not possible. #[cfg(test)] only works when the current crate is being tested, not any dependent crate.

@mockersf
Copy link
Member

mockersf commented Jul 27, 2021

Actually, this may also be useful for running simulations at faster than real time too. I've seen that requested before too; we should try to document that use case too. A full example might be better for that (you'll need to mess with one of the default plugins), but that work could be saved for a later PR.

It's probably a bad idea to manually update with an instant in this case, as it would break all deltas exposed by Time. To change the speed from real time it's much simpler to apply a factor to everything you get from Time, or to recode the resource including the factor, it's not very complicated...

To make this useful outside of test, one would need to disable the time_system system

pub(crate) fn time_system(mut time: ResMut<Time>) {
time.update();
}

I don't think that's a good idea, so I would prefer clear doc comments explaining this is only useful for tests. Could a feature to enable test helpers work?

@alice-i-cecile
Copy link
Member

To make this useful outside of test, one would need to disable the time_system system

This was what I had in mind; it's much more useful in scientific settings but I could see it being useful for accelerated simulation in ordinary games (e.g. for machine learning or automated integration tests).

Out of scope for this though, and I'd want to try testing that strategy on a full game to see if anything breaks before recommending it.

@ostwilkens
Copy link
Contributor Author

In the case of non-linear or scaled time as a feature in games, it might be recommended to keep track of simulated time as a separate resource. In such a case it would be nice to be able to wrap or alias Time, and modify it as needed for your usecase. It's something that should be made possible by this change.

I'll add the doc strings discussed.

@ostwilkens
Copy link
Contributor Author

I'm not sure if it's feasible, or a good idea, but one way to prevent breaking the default Time resource would be to somehow declare the built-in resource as read-only (outside the bevy crate).

@ostwilkens
Copy link
Contributor Author

Sorry about that; rebased from 0.5.0 release.. should I open a new PR?

@mockersf
Copy link
Member

Sorry about that; rebased from 0.5.0 release.. should I open a new PR?

Yup, if you can't clean this PR, it would probably be best to open a new one

@ostwilkens ostwilkens reopened this Jul 27, 2021
@ostwilkens
Copy link
Contributor Author

ostwilkens commented Jul 27, 2021

Sorry about the fuzz, I think it's cleaned up now. Open to input on the doc string and test.

@mdickopp
Copy link
Contributor

This change would be very useful to me as well, so I would like to help. What needs to be done to get this ready to be merged?

@@ -31,7 +31,8 @@ impl Time {
self.update_with_instant(now);
}

pub(crate) fn update_with_instant(&mut self, instant: Instant) {
/// Update time with a specified [`Instant`], for use in tests. Should never be called on the default Time resource.
Copy link
Member

@alice-i-cecile alice-i-cecile Apr 10, 2022

Choose a reason for hiding this comment

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

Suggested change
/// Update time with a specified [`Instant`], for use in tests. Should never be called on the default Time resource.
/// Update time with a specified [`Instant`]
///
/// This method is for use in tests, and should generally not be called on the [`Time`] resource as part of your app.
/// Doing so will conflict with the `time_system` called by [`CorePlugin`], and result in inaccurate performance measurements.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just inaccurate timekeeping in general. I would keep the recommendation to only use this to write tests.

@alice-i-cecile
Copy link
Member

So to get this merged, the next step is that we need at least two approvals, including from community members, and all blocking concerns addressed.

On my end, I want to see:

  1. Better doc strings, explaining exactly what the risks are when calling this inside an App.
  2. Sign-off from @maniwani, who's been redesigning how Bevy handles time.

Copy link
Contributor

@maniwani maniwani left a comment

Choose a reason for hiding this comment

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

I'm cool with making update_with_instant pub since update already is.

I will say tho that the featured test seems a little misleading.

At the end it highlights the results between 30 FPS and 60 FPS being similar after a single frame as if that would be the case forever (which is false, would diverge pretty quickly). Integrating by time.delta_seconds() does decouple motion from the framerate but not in a way that is repeatable or consistent between different FPS.

You only get real framerate independence if you have a constant step size (and then FixedTimestep or a dedicated thread).

something something running simulations faster than real-time

Is what #3002 is for.

@tim-blackbird
Copy link
Contributor

I believe most people understand framerate-independence to mean that motion is separate from the framerate. i.e. the game does not appear to move in slow-motion when the framerate dips, or vise versa. Rather than separating simulation-time from real-time in a fixed timestep setting.

With that in mind, I don't think the test is pretending to be more accurate than it is.

Comment on lines +162 to +163
let result_30fps = test_fps(30);
let result_60fps = test_fps(60);

This comment was marked as outdated.

@maniwani
Copy link
Contributor

maniwani commented Apr 11, 2022

I believe most people understand framerate-independence to mean [...]

In my experience, time-related things confuse users a lot.

I understand this is the kind of test OP wanted to mock, but I'd prefer that we didn't feature a test with this premise on the off-chance someone interprets it as an "officially-endorsed" way of handling physics.

This is all fine for UI, etc, but if users want stable game physics, they should use a fixed timestep (with interpolation to make it look smooth) because that's the standard and correct way to do it.

With that in mind, I don't think the test is pretending to be more accurate than it is.

I was more pointing out that the test—checking that the motion didn't desync by more than an arbitrary amount under arbitrary FPS values after an arbitrarily small duration—seems pretty convoluted if the point is just to confirm that we didn't forget to multiply by time.delta_seconds().

(Edit: But yeah, IMO we shouldn't have a test whose premise is checking the "consistency" of variable timestep motion.)

@tim-blackbird
Copy link
Contributor

tim-blackbird commented Apr 11, 2022

seems pretty convoluted if the point is just to confirm that we didn't forget to multiply by time.delta_seconds().

Sorry, I probably got too preoccupied with your comment. I agree with you here. what's being tested is basically how much float addition drifts over 30 iterations compared to 60 iterations. I'm in favor of removing the test.

@mdickopp
Copy link
Contributor

I'm not the OP, so I cannot update the OP's branch. Should I open a new PR?

I'm in favor of removing the test.

I would like to rework the test into an example demonstrating how the function can be used in a test. What do you think?

@alice-i-cecile
Copy link
Member

I'm not the OP, so I cannot update the OP's branch. Should I open a new PR?

Yep sounds great: just link this PR in the description and we can close this one out.

I would like to rework the test into an example demonstrating how the function can be used in a test. What do you think?

Yes please! I think a doc test on Time itself would be a nice place for this :) That feels like the most likely place users will look if they're wondering about "how do I mock this".

@ostwilkens
Copy link
Contributor Author

I don't currently have the environment set up to assist on this, I'm glad it's being looked into. Feel free to rework it in a new PR.

@mdickopp
Copy link
Contributor

I opened #4469 as a follow-up/replacement PR to this one.

@alice-i-cecile
Copy link
Member

Closed in favor of #4469

bors bot pushed a commit that referenced this pull request Apr 24, 2022
# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to #2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
bors bot pushed a commit that referenced this pull request Apr 24, 2022
# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to #2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
bors bot pushed a commit that referenced this pull request Apr 24, 2022
# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to #2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
bors bot pushed a commit that referenced this pull request Apr 24, 2022
# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to #2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
bors bot pushed a commit that referenced this pull request Apr 24, 2022
# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to #2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
…4469)

# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to bevyengine#2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
tgecho pushed a commit to tgecho/bevy that referenced this pull request Jun 16, 2022
…4469)

# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to bevyengine#2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests.

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.

Co-authored-by: Martin Dickopp <martin@zero-based.org>
(cherry picked from commit 93cee3b)
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…4469)

# Objective

To test systems that implement frame rate-independent update logic, one needs to be able to mock `Time`. By mocking time, it's possible to write tests that confirm systems are frame rate-independent.

This is a follow-up PR to bevyengine#2549 by @ostwilkens and based on his work.

## Solution

To mock `Time`, one needs to be able to manually update the Time resource with an `Instant` defined by the developer. This can be achieved by making the existing `Time::update_with_instant` method public for use in tests. 

## Changelog

- Make `Time::update_with_instant` public
- Add doc to `Time::update_with_instant` clarifying that the method should not be called outside of tests.
- Add doc test to `Time` demonstrating how to use `update_with_instant` in tests.


Co-authored-by: Martin Dickopp <martin@zero-based.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Core Common functionality for all bevy apps C-Usability A simple 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