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

Run fixed time-step in an exclusive system #5467

Closed
wants to merge 10 commits into from

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Jul 27, 2022

Objective

  • Allow combining fixed time-steps with run criteria.
  • Lets us yeet looping run criteria in the future.

Solution

  • SubSchedule -- a schedule that does not necessarily run along with the main schedule.
    • Gets stored in a public resource, and then extracted and run in an exclusive system.
    • Useful not just for fixed time-steps -- they could be used to invoke a collection of systems as a sort of callback, at any time. Example: OnEnter or OnExit for states.
    • Analogous to the system sets on schedule v3 ("stageless") #4391 -- only SubSchedule is worse since it's split into stages.
      • Eventually this API should be fully replaced in stageless. The primary motivation of this API is to ease migration.

Changelog

todo

Migration Guide

Before:

App::new()
    .add_system_set(
        SystemSet::new()
            .with_run_criteria(FixedTimestep::step(1.0 / 60.0))
            .with_system(my_system),
    );

After:

App::new()
    .add_fixed_schedule(
        FixedTimestep::step(1.0 / 60.0),
        SystemStage::single_threaded().with_system(my_system),
    );

More complex schedules:

#[derive(ScheduleLabel)]
struct FixedUpdate;

#[derive(StageLabel)]
struct FixedStage {
    A,
    B,
}

App::new()
    .add_fixed_schedule_to_stage(
        CoreStage::PreUpdate,
        FixedTimestep::steps_per_second(60.0).with_label(FixedUpdate),
        Schedule::default()
            .with_stage(FixedStage::A, SystemStage::parallel())
            .with_stage(FixedStage::B, SystemStage::parallel()),
    )
    .sub_schedule(FixedUpdate, |s: &mut Schedule| {
        s.add_system_to_stage(FixedStage::A, foo);
        s.add_system_to_stage(FixedStage::B, bar);
    });

Notes

This PR adds thiserror as a direct dependency for bevy_app, but it was already in the dependency tree.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide X-Controversial There is active debate or serious implications around merging this PR C-Usability A targeted quality-of-life change that makes Bevy easier to use labels Jul 27, 2022
@JoJoJet JoJoJet force-pushed the fixed-timestep-schedule branch 15 times, most recently from d1a58be to 35d9486 Compare July 29, 2022 00:40
@JoJoJet JoJoJet force-pushed the fixed-timestep-schedule branch from 399fd34 to b611dfd Compare July 29, 2022 14:04
@hymm hymm self-requested a review July 29, 2022 18:21
@JoJoJet JoJoJet force-pushed the fixed-timestep-schedule branch from 55d9e7f to b07208b Compare July 30, 2022 01:31
@JoJoJet JoJoJet marked this pull request as ready for review July 31, 2022 03:22
Copy link
Contributor

@inodentry inodentry left a comment

Choose a reason for hiding this comment

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

Okay!! I actually really like this.

The "sub-schedules" seem like a very nice and useful abstraction, more flexible than what I've been doing in iyes_loopless with a custom Stage impl. I can also envision plenty of other uses besides fixed timestep. :)

The new fixed timestep implementation seems more reliable and in line with what stageless and iyes_loopless can offer.

I would be very happy to see this merged into Bevy, before the full Stageless rework.

I approve of the overall design and implementation, but I left a couple of comments for a few things I'd like to be addressed.

{
let label = label.as_label();

// Extract.
Copy link
Contributor

Choose a reason for hiding this comment

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

I worry about the overloading of the term "extract" in this PR, overlapping with the current use of that term in the bevy Rendering architecture. It may lead to confusion.

Copy link
Member Author

Choose a reason for hiding this comment

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

IMO the word extract is general enough that it shouldn't cause confusion, but I wonder what other term we could use. Partition? Isolate?


/// The internal state of each [`FixedTimestep`].
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct FixedTimestepState {
step: f64,
accumulator: f64,
Copy link
Contributor

@inodentry inodentry Aug 8, 2022

Choose a reason for hiding this comment

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

Please consider switching to using Duration instead of floating point values for the internal state of the fixed timestep.

I know that floats are how the existing Bevy code does it, but it was a mistake. Floats are not an accurate or reliable representation of time.

My implementation in iyes_loopless uses Durations instead, allowing it to be more exact.

It should be a very easy and noncontroversial replacement.

Given that this PR is reworking all of FixedTimestep, I believe this small-but-important thing should be addressed, while we are at it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point.

@maniwani
Copy link
Contributor

maniwani commented Aug 8, 2022

I do support the changing fixed timestep from being a run criteria, but I have some reservations about merging this by itself.

Please excuse me as I fumble my way through this.

Eventually this API should be fully replaced in stageless. The primary motivation of this API is to ease migration.

Almost every type and trait involved in this PR is (supposedly temporary) public API that wasn't described in the stageless RFC.

  • SubSchedule
  • SubSchedules
  • IntoSubSchedule
  • TimestepAppExt
  • ExtractError
  • InsertError

I don't mean that negatively, but this PR still weighs in on more than just the fixed timestep. Are we committing to this specific API language for later PRs? I've been wondering if proceeding incrementally is going to create more work as we go.

For example, SubSchedules is similar to the resource described in the stageless RFC, but it's not quite the same. Same goes for the SystemRegistry added in #4090. I have not seen an effort to actually design these bits and pieces to fit together (unlike a big all-in-one PR). What will the "final product" have to inherit from them both when it all comes together?

I am not a fan of migrating multiple times.

I think we've completely avoided answering the "What is the best way to handle the migration process from an organizational perspective?" question from the RFC. None of the people involved with stageless (myself included) are apparently on the same page for how we should get there. I think we should come to a consensus on that now, so that we can coordinate.

Personally, I think we should try to collectively work on single PR or a small handful (e.g. add schedule_v3 module, then port internal plugins and examples) rather than many independent PRs that are each making ad-hoc API decisions.

(I do especially like what you have for ExtractError and InsertError though.)

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 8, 2022

For example, SubSchedules is similar to the resource described in the stageless RFC, but it's not quite the same. Same goes for the SystemRegistry added in #4090. I have not seen an effort to actually design these bits and pieces to fit together (unlike a big all-in-one PR). What will the "final product" have to inherit from them both when it all comes together?

Most of this issue stems from all of these PR's being non-final, and not yet merged. Once the system registry PR gets merged, for example, I can change up this PR to work with that, but I can't really do that until it gets merged. And these are orthogonal IMO so it wouldn't make.

I honestly think it would be best to just merge stageless all it once, but it seems like the consensus is that the migration should be incremental.

e.g. add schedule_v3 module, then port internal plugins and examples

Can you elaborate on how this would work? Everything in schedule is so fundamental, I don't see how schedule_v2 and schedule_v3 could coexist.

@maniwani
Copy link
Contributor

maniwani commented Aug 8, 2022

And these are orthogonal IMO so it wouldn't make [a difference?]

I think the idea was that everything system-related (including system sets and their dependency graphs) would go into a single resource. So your SubSchedules resource and the SystemRegistry are both "halves" of the one talked about in the RFC.

I honestly think it would be best to just merge stageless all it once, but it seems like the consensus is that the migration should be incremental.

I don't think there's a consensus. I think I just turned people off by coming in with a PR that had 10k line changes (in my defense, it included both the changes to the schedule module and porting the rest of the engine).

Can you elaborate on how this would work? Everything in schedule is so fundamental, I don't see how schedule_v2 and schedule_v3 could coexist.

My suggestion would be:

  1. Us stageless folk develop a bevy_ecs::schedule_v3 (can decide name later) module together in a fork/branch, just standalone and not used by the other internals.
  2. Make a PR to bring that into main.
  3. Once merged, we can follow up and port the plugins and examples one-by-one.
  4. Finally, we can formally replace the schedule module.

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 8, 2022

My suggestion would be:

  • Us stageless folk develop a bevy_ecs::schedule_v3 (can decide name later) module together in a fork/branch, just standalone and not used by the other internals.
  • Make a PR to bring that into main.
  • Once merged, we can follow up and port the plugins and examples one-by-one.

I just don't see how that would work cleanly. If we split schedule into schedule_v2 and schedule_v3, wouldn't we also need to split anything that uses them publicly? For example, having AppV2 and AppV3 everywhere doesn't seem great.

If the idea does end up working out, it is probably the easiest path forward though.

@maniwani
Copy link
Contributor

maniwani commented Aug 8, 2022

If we split schedule into schedule_v2 and schedule_v3, wouldn't we also need to split anything that uses them publicly?

I meant keep schedule around as-is, and just add the new one as a preview (I guess). Nothing in schedule now would carry over as-is to schedule_v3, so they could exist together, but you raise a good point about App.

Maybe we could do something similar to what you've done here with TimestepAppExt: make a temporary trait with a temporary set of methods that hooks into schedule_v3 until we can rename them to the normal methods.

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 8, 2022

Maybe we could do something similar to what you've done here with TimestepAppExt: make a temporary trait with a temporary set of methods that hooks into schedule_v3 until we can rename them to the normal methods.

That could work maybe, but I think I prefer the original two options: either merge it all it once, or do it incrementally (like this PR). If your concern is just with some of the API decisions, I can change them. This just happened to be the most comfortable API I found by iterating, but I'm not super attached.

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 8, 2022

Also, the point about migrating twice: as long as this gets replaced before 0.9, there would only really be one migration. The engine itself barely uses fixed timestep, so migration is trivial. This PR would make incremental migration towards stageless significantly easier by removing one of the last few uses of looping run criteria.

@JoJoJet
Copy link
Member Author

JoJoJet commented Aug 9, 2022

We agreed on discord that our migration towards stageless should be more formalized. Closing this out for now.

@JoJoJet JoJoJet closed this Aug 9, 2022
@JoJoJet JoJoJet deleted the fixed-timestep-schedule branch December 21, 2022 15:49
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-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide X-Controversial There is active debate or serious implications around merging this PR
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants