bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust

Home Page:https://bevyengine.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Scenes

cart opened this issue · comments

This is a Focus Area tracking issue

Bevy Scenes currently do most of what we want, but they need a bit more work before they can be the foundation of Bevy state management. This focus area is also a requirement for the Bevy Editor.

  • Asset Management: Inline assets, asset dependencies, stable ids when loaded from the disk
  • Better Scene Format: Improve the legibility and ergonomics of scene files to make them easy to read and easy to compose by hand. We're shooting for something close to this.
  • Enabling/Disabling Systems: Scenes should be able to toggle their required systems on and off when they are added / removed

Active Crates / Repos

No active crates or repos. Feel free to make one. Link to it in this issue and I'll add it here!

Sub Issues

  • Improved Scene Format: #92

If you would like to discuss a particular topic, look for a pre-existing issue in this repo. If you can't find one, feel free to make one! Link to it in this issue and I'll add it to the index.

Could you elaborate on your vision for "inline assets" and "stable ids when loaded from the disk"?

Inline Assets

Inline assets would be cases like this in scene files:

Entity [
  Handle<Material>: Asset(
     Material(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 })
  )
]

The Material asset would be automatically created when the scene is loaded and the asset's UUID would be assigned to the Entity's Handle component.

Godot, for example, handles them more like this (adapted to fit "potential future Bevy Scene syntax"):

assets: {
  ASSET_UUID:  Material(Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 })
},
entities: [
  Entity [
    Handle<Material>(ASSET_UUID)
  ]
]

The first approach makes composing UIs by hand nicer (but we could also do that by not using assets for colors in the UI, but thats another conversation).

The second approach makes assets re-usable within a scene file and is a bit more organized and explicit. And it would remove the need to define a special "inline" syntax.

Stable Ids Loaded from Disk

"Imported" assets are assigned a unique id, which is saved somewhere in the filesystem (either next to them or in a database file). This assignment could either be manual, during app startup, or in the editor.

(this is very much what atelier-assets already does).

In general I wanted to make sure this focus area included enough asset features to force us to make a decision about atelier-assets.

Is the format intended to eventually be RON, outside of other scene/asset choices?

There is no hard requirement for alignment with pre-existing file formats. Low-boilerplate, legible syntax that supports the features above is the priority.

Atelier assets has a lot of great features, but if it's deemed to not quite fit into bevy, I'd be happy to continue pushing bevy_assets in the right direction.

I left comments in #92 that I think are relevant here.

Good call. I'm adding that as a sub-issue above.

The way I usually do scenes is have my scene stack be the top-level abstraction, with each scene holding it's own local state and sharing some sort of global state, passed as a &mut argument into most of their callback methods.

Scenes can be as small as a context menu (right-click dropdown list at cursor), or as big as the main game scene. The scene trait has multiple callbacks with default implementations, which allow a scene to opt in into having behavior in response to stack-level events (update, update whil top scene, draw, become top scene, stop being top scene...)

The global state contains hardware contexts and any other "singletons" the app needs - the main world and resources, and systems that run at all times. Local states contain scene-specific systems and resources; haven't had a reason to hold a subworld in a scene, but that's possible too.

Scene update callback returns a Transition enum which defines operations on the stack. With those, scenes can be popped, dropping them, or "deactivated", which runs a special callback (for de-regestering observers and the like), then moves them from the stack and into the stash. An "activate" transition can be used to later un-stash a scene and put it back onto the stack, running a different special callback. This allows churning heavy states in and out without having to fully reinitialize them.

Scene is a trait, but the scene stack does not contain trait objects - I took inspiration from enum_dispatch crate and wrote a variant of it's macro to let me easily define an enum of states that the app will be using (the main difference between the two macros is that mine supports more generics; I'm still not done plumbing in all the ones I want, though).

(As a side-side project, I've been slowly polishing this snippet to be released as a general-use crate; if there's urgent interest I can make the repo public, or give access.)

Perhaps something like this could be used for Bevy? The app could still be top-level struct with it's schedule, world and resources; the scene stack could be a field in it, global state for it's scenes being the world and resources of the app, with scenes' local states holding their own schedules. Bevy-provided systems would execute methods of the stack, running scenes' callbacks, at specific points in the global schedule. Perhaps it could/should be wrapped into a plugin.

This would pretty much solve #128 and #279, potentially #125.

I see scenes are a 2 part problem.

First, is scenes are a tree representation of entities. These have types, properties, optionally IDs, and more. And second, scenes are composable, so including scenes should be possible.

For this, using a HTML/JSX-like syntax makes a lot of sense, as that's already well understood, supports the requirements, and can support building UI as scenes in code in the future (with more widgets for example). A React-like rendered could be created for translating UI in the format into a live scene.

This could follow https://gist.github.com/Moxinilian/c45a1858eca7e918b5728ee5c117f649 and https://gist.github.com/cart/3e77d6537e1a0979a69de5c6749b6bcb syntax as a better scene format.

Additionally, compiled scenes is a must be compilable and includable in or outside binaries. Having a binary format that can be compiled using a macro would be a big win, as this would reduce reading/parsing time, which will be essential during production games

commented

For this, using a HTML/JSX-like syntax makes a lot of sense, as that's already well understood, supports the requirements, and can support building UI as scenes in code in the future

Well there were some talks about this and XML is just ugly and nobody wants to write XML if they can. Also it's too verbose. indenting and bracket highlighting make it easy to read it without specifying closing tags with </tagname>

Actually what about replacing and pushing over scenes?

Like amethyst's Game States, although I don't like the way they are replaced. Returning Trans::type is clunky and sometimes unnecessary (for example when every time checking if state should be changed running if ~"every frame"), we could instead call a function replace_scene or a add_scene and remove_scene with the scene_id or scene_name as a parameter. This can be either:

  • simply managed in the World as a resource struct SceneMachine's children
  • or unmanaged in that World can hold scenes separately as similar to entities in a sense that scene can be added and removed from the world at will. And world will execute scene setup function when scene is loaded and scene itself is responsible for cleaning scenes from before from the World and can have the enabled bool to manage them

I'm not actually sure if the @Ratysz solution isn't better, but I didn't like it as it is the way for amethyst and thought maybe we could use a different approach

Oh, of course - it should be perfectly fine to somewhat invert control in the abstraction I described; the suggested way of integrating it into Bevy comes from that description alone.

It could indeed do without the callbacks returning a Transition - since a scene has access to the global world it could just write the changes it wants into it. The only thing it can't do that way is move or remove itself directly, that will likely have to be queued akin to Commands for entities, or passed as a parameter to next scene's initialization.

So to attempt to define more of what a scene is, as there seems to be some confusion about the terminology here:

A scene file is a file that defines how to spawn a set of entities with components in an ECS world, and have them as "ready to go" as possible (i.e. no special code to patch up data loaded from a specific file).

Inline Assets

I'll claim that it's possible to implement this within an Importer in atelier-assets.

commented

@kabergstrom but scenes are also a struct that holds data for the game state, like main menu etc. Aren't they?

@cart would have to fill in with his intention for the issue, but the definition in Godot for scenes and prefabs in Unity does not include any extra state outside of the data being spawned into the World (ECS world in bevy's case).

@WesterWest Re: what kabergstrom said -

... the definition in Godot for scenes and prefabs in Unity does not include any extra state outside of the data being spawned into the World (ECS world in bevy's case).

Yeah, while in the Godot editor, you can instance scenes inside other scenes, and they show up with a little scene button you can click to go edit the scene file they are defined in, at run-time they just get added to the SceneTree as nodes. Any Node can have any number of children, and can be added as a child of any other node. Any Node can also be saved as a Scene which will include all of its children, and when you instance a PackedScene in code, you get a reference to its root Node which you can then add to the SceneTree wherever you want.

I think the reason this is hard to relate to Bevy is that Entities don't have built-in references to their parent or children, and such associations have to essentially be added to the world as Components. Therefore, "removing" a Scene requires somehow remembering all of the Entities that were added as part of that Scene, thus holding on to a Scene handle at run-time, blurring the line between Scenes and Nodes that Godot has.

@Waridley, do scenes in Godot also include systems? Right now the scenes in Bevy feel more like what other engines I've worked with call prefabs, which just work with components and entities. I think this is a good abstraction to keep since addressing systems takes a bit more architectural thinking as I'll explain below. Scenes as prefabs would allow for the sort of 'scenes within scenes' that you're talking about, but I still don't see how to resolve the system management problem.

I actually think that creating a good scenes api would look more like the following:

  • Refactor bevy scenes into 'prefabs' and make whatever formatting and asset management changes we want to there.
  • Create a Dispatcher class that manages systems
  • Create a scene manager along the lines of what @Ratysz suggested using the Dispatcher API

My reasoning is this would hold more true to Bevy's design philosophy of being pluggable and modular, and make for a clearer API. Right now it feels that the system part of the ECS is tied too closely with the execution context itself. The ECS in Bevy is great, but users might want to sub in their own ECS depending on their needs. Similarly, writing a one size fits all scene manager could be very difficult and would tie very closely with the built in ECS.

Rather than trying to tie these all together, the dispatcher class would allow control over the systems and what is running or not, but it disconnects that from the core engine. If dispatcher is made into a trait, then users could write their own dispatchers using other ECSs as long as some built in main_loop() method is present.

If the dispatcher is separated from the scene manager, again, it allows for a straightforward, well designed scene manager to be created, but one that can be subbed out if needed.

I think that overall, splitting these concerns will make the overall api cleaner and easier to write by making clearer separation of concerns while also making the overall library more flexible.

do scenes in Godot also include systems?

@chrisburnor In Godot, I think the closest thing to the concept of "systems" is the handful of "servers" that handle the most performance-critical stuff (PhysicsServer, AudioServer, VisualServer, etc.). There is a fairly deep inheritance hierarchy to all of the Node types, and under the hood the C++ class definitions end up talking to Servers a lot. But as far as what is saved in the scene system, the only place persistent executable code can live is in the script reference on every Node. It ends up being empty for most nodes, but it can be a GDNative (custom interpreted language) script, a C# script, or a GDNative library. Most of the user-land code ends up being executed in various callback functions that can be overridden (_ready, _update, _process, _physics_process, etc.) So the engine iterates through all the Nodes, checks if they have a script, then if they do, checks if the current callback function is present in the script, and if so, calls it. Obviously not ideal for anything remotely performance-critical. There is documentation for "optimizing" games using servers directly, but it's not a very ergonomic API.

I am contemplating making a somewhat similar sort of scripting system, but I think at least one benefit Bevy or any ECS-centric engine would have over Godot is that scripts can be added as components, so you can just iterate over all of the scripts, instead of iterating over all of the Entities and checking for a null pointer. Also I feel like it would be a little easier to extend the scripting system to add custom callback functions by making a system that calls the function on every script.

@Waridley official scripting is a non-goal for bevy at this point. That being said, those ideas do sound cool and I'm sure some people would be interested. But I would want it to be a third party plugin (at least for now).

@cart Oh yeah, I meant for my own use. If I did decide to release it as a plugin, I would definitely make it a separate, unofficial crate.

Is it unthinkable to just activate systems when scenes that require them are loaded, and then leave them running when the scene is removed? If the system has a query such that it only acts on components from that specific scene, it should be pretty much a noop. Or does that lead to poor performance too quickly? I can imagine that unnecessary systems can cause problems for advanced system scheduling, but maybe it's worth investigating.

I just started a discussion on this (#654) for a more in depth discussion than I think we can do in a single issue.

Re stable ids: What about treating asset ids as interned paths to the assets? You wouldvtheb serialize something like Handle<Kind>(/path/to/asset) where every handle with the same path would get the same id when deserializing. Something kind of similar is done in rustc with DefId being an id that can change between incremental compilations, but always references the same DefPath.

Re stable ids: What about treating asset ids as interned paths to the assets? You wouldvtheb serialize something like Handle<Kind>(/path/to/asset) where every handle with the same path would get the same id when deserializing. Something kind of similar is done in rustc with DefId being an id that can change between incremental compilations, but always references the same DefPath.

The current bevy_asset already does something like this:

AssetPathId(AssetPathId),

oops accidentally closed this via shortcut :)

I believe atelier_assets does something similar as well. The interning happens in the AssetServer, so you can convert handles back to full paths. Is that similar to what you would do?

AssetPathId only contains a hash of the path. During serialization there would need to be some way to convert it back to a string representing the path in such a way that the asset loader can load it again. Maybe the asset server could become a static, as I don't see nay reason why using a single asset server would cause any conflicts, even for multiple apps. In addition HandleId has a variant with an Uuid for "anonymous" resources. Their data would somehow need to be stored somewhere. The most elegant way would probably be inline, but then you would need to handle the same asset being used at several places somehow.

AssetPathId only contains a hash of the path. During serialization there would need to be some way to convert it back to a string representing the path in such a way that the asset loader can load it again. Maybe the asset server could become a static, as I don't see nay reason why using a single asset server would cause any conflicts, even for multiple apps.

I'm not sure 'static is a requirement here. We already use "stateful" / seeded serializers for scenes so we can include the TypeRegistry. The AssetServer is also thread-safe and cloneable, so using it for serialization should be fine.

In addition HandleId has a variant with an Uuid for "anonymous" resources. Their data would somehow need to be stored somewhere. The most elegant way would probably be inline, but then you would need to handle the same asset being used at several places somehow.

Its not a full solve (and im not sure anything is for dynamic scenarios), but if someone wants to use "anonymous" asset handles in scenes, they can declare a const Handle and use that during loading. We do that for inlined pipelines / shaders not loaded by the asset server:

pub const UI_PIPELINE_HANDLE: HandleUntyped =