4b1745f813
19 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
4b1745f813
|
BRP resource methods (#17423)
# Objective So far, built-in BRP methods allow users to interact with entities' components, but global resources have remained beyond its reach. The goal of this PR is to take the first steps in rectifying this shortfall. ## Solution Added five new default methods to BRP: - `bevy/get_resource`: Extracts the value of a given resource from the world. - `bevy/insert_resource`: Serializes an input value to a given resource type and inserts it into the world. - `bevy/remove_resource`: Removes the given resource from the world. - `bevy/mutate_resource`: Replaces the value of a field in a given resource with the result of serializing a given input value. - `bevy/list_resources`: Lists all resources in the type registry with an available `ReflectResource`. ## Testing Added a test resource to the `server` example scene that you can use to mess around with the new BRP methods. ## Showcase Resources can now be retrieved and manipulated remotely using a handful of new BRP methods. For example, a resource that looks like this: ```rust #[derive(Resource, Reflect, Serialize, Deserialize)] #[reflect(Resource, Serialize, Deserialize)] pub struct PlayerSpawnSettings { pub location: Vec2, pub lives: u8, } ``` can be manipulated remotely as follows. Retrieving the value of the resource: ```json { "jsonrpc": "2.0", "id": 1, "method": "bevy/get_resource", "params": { "resource": "path::to::my::module::PlayerSpawnSettings" } } ``` Inserting a resource value into the world: ```json { "jsonrpc": "2.0", "id": 2, "method": "bevy/insert_resource", "params": { "resource": "path::to::my::module::PlayerSpawnSettings", "value": { "location": [ 2.5, 2.5 ], "lives": 25 } } } ``` Removing the resource from the world: ```json { "jsonrpc": "2.0", "id": 3, "method": "bevy/remove_resource", "params": { "resource": "path::to::my::module::PlayerSpawnSettings" } } ``` Mutating a field of the resource specified by a path: ```json { "jsonrpc": "2.0", "id": 4, "method": "bevy/mutate_resource", "params": { "resource": "path::to::my::module::PlayerSpawnSettings", "path": ".location.x", "value": -3.0 } } ``` Listing all manipulable resources in the type registry: ```json { "jsonrpc": "2.0", "id": 5, "method": "bevy/list_resources" } ``` |
||
![]() |
9bc0ae33c3
|
Move hashbrown and foldhash out of bevy_utils (#17460)
# Objective - Contributes to #16877 ## Solution - Moved `hashbrown`, `foldhash`, and related types out of `bevy_utils` and into `bevy_platform_support` - Refactored the above to match the layout of these types in `std`. - Updated crates as required. ## Testing - CI --- ## Migration Guide - The following items were moved out of `bevy_utils` and into `bevy_platform_support::hash`: - `FixedState` - `DefaultHasher` - `RandomState` - `FixedHasher` - `Hashed` - `PassHash` - `PassHasher` - `NoOpHash` - The following items were moved out of `bevy_utils` and into `bevy_platform_support::collections`: - `HashMap` - `HashSet` - `bevy_utils::hashbrown` has been removed. Instead, import from `bevy_platform_support::collections` _or_ take a dependency on `hashbrown` directly. - `bevy_utils::Entry` has been removed. Instead, import from `bevy_platform_support::collections::hash_map` or `bevy_platform_support::collections::hash_set` as appropriate. - All of the above equally apply to `bevy::utils` and `bevy::platform_support`. ## Notes - I left `PreHashMap`, `PreHashMapExt`, and `TypeIdMap` in `bevy_utils` as they might be candidates for micro-crating. They can always be moved into `bevy_platform_support` at a later date if desired. |
||
![]() |
44ad3bf62b
|
Move Resource trait to its own file (#17469)
# Objective `bevy_ecs`'s `system` module is something of a grab bag, and *very* large. This is particularly true for the `system_param` module, which is more than 2k lines long! While it could be defensible to put `Res` and `ResMut` there (lol no they're in change_detection.rs, obviously), it doesn't make any sense to put the `Resource` trait there. This is confusing to navigate (and painful to work on and review). ## Solution - Create a root level `bevy_ecs/resource.rs` module to mirror `bevy_ecs/component.rs` - move the `Resource` trait to that module - move the `Resource` derive macro to that module as well (Rust really likes when you pun on the names of the derive macro and trait and put them in the same path) - fix all of the imports ## Notes to reviewers - We could probably move more stuff into here, but I wanted to keep this PR as small as possible given the absurd level of import changes. - This PR is ground work for my upcoming attempts to store resource data on components (resources-as-entities). Splitting this code out will make the work and review a bit easier, and is the sort of overdue refactor that's good to do as part of more meaningful work. ## Testing cargo build works! ## Migration Guide `bevy_ecs::system::Resource` has been moved to `bevy_ecs::resource::Resource`. |
||
![]() |
ba5e71f53d
|
Parent -> ChildOf (#17427)
Fixes #17412 ## Objective `Parent` uses the "has a X" naming convention. There is increasing sentiment that we should use the "is a X" naming convention for relationships (following #17398). This leaves `Children` as-is because there is prevailing sentiment that `Children` is clearer than `ParentOf` in many cases (especially when treating it like a collection). This renames `Parent` to `ChildOf`. This is just the implementation PR. To discuss the path forward, do so in #17412. ## Migration Guide - The `Parent` component has been renamed to `ChildOf`. |
||
![]() |
21f1e3045c
|
Relationships (non-fragmenting, one-to-many) (#17398)
This adds support for one-to-many non-fragmenting relationships (with planned paths for fragmenting and non-fragmenting many-to-many relationships). "Non-fragmenting" means that entities with the same relationship type, but different relationship targets, are not forced into separate tables (which would cause "table fragmentation"). Functionally, this fills a similar niche as the current Parent/Children system. The biggest differences are: 1. Relationships have simpler internals and significantly improved performance and UX. Commands and specialized APIs are no longer necessary to keep everything in sync. Just spawn entities with the relationship components you want and everything "just works". 2. Relationships are generalized. Bevy can provide additional built in relationships, and users can define their own. **REQUEST TO REVIEWERS**: _please don't leave top level comments and instead comment on specific lines of code. That way we can take advantage of threaded discussions. Also dont leave comments simply pointing out CI failures as I can read those just fine._ ## Built on top of what we have Relationships are implemented on top of the Bevy ECS features we already have: components, immutability, and hooks. This makes them immediately compatible with all of our existing (and future) APIs for querying, spawning, removing, scenes, reflection, etc. The fewer specialized APIs we need to build, maintain, and teach, the better. ## Why focus on one-to-many non-fragmenting first? 1. This allows us to improve Parent/Children relationships immediately, in a way that is reasonably uncontroversial. Switching our hierarchy to fragmenting relationships would have significant performance implications. ~~Flecs is heavily considering a switch to non-fragmenting relations after careful considerations of the performance tradeoffs.~~ _(Correction from @SanderMertens: Flecs is implementing non-fragmenting storage specialized for asset hierarchies, where asset hierarchies are many instances of small trees that have a well defined structure)_ 2. Adding generalized one-to-many relationships is currently a priority for the [Next Generation Scene / UI effort](https://github.com/bevyengine/bevy/discussions/14437). Specifically, we're interested in building reactions and observers on top. ## The changes This PR does the following: 1. Adds a generic one-to-many Relationship system 3. Ports the existing Parent/Children system to Relationships, which now lives in `bevy_ecs::hierarchy`. The old `bevy_hierarchy` crate has been removed. 4. Adds on_despawn component hooks 5. Relationships can opt-in to "despawn descendants" behavior, meaning that the entire relationship hierarchy is despawned when `entity.despawn()` is called. The built in Parent/Children hierarchies enable this behavior, and `entity.despawn_recursive()` has been removed. 6. `world.spawn` now applies commands after spawning. This ensures that relationship bookkeeping happens immediately and removes the need to manually flush. This is in line with the equivalent behaviors recently added to the other APIs (ex: insert). 7. Removes the ValidParentCheckPlugin (system-driven / poll based) in favor of a `validate_parent_has_component` hook. ## Using Relationships The `Relationship` trait looks like this: ```rust pub trait Relationship: Component + Sized { type RelationshipSources: RelationshipSources<Relationship = Self>; fn get(&self) -> Entity; fn from(entity: Entity) -> Self; } ``` A relationship is a component that: 1. Is a simple wrapper over a "target" Entity. 2. Has a corresponding `RelationshipSources` component, which is a simple wrapper over a collection of entities. Every "target entity" targeted by a "source entity" with a `Relationship` has a `RelationshipSources` component, which contains every "source entity" that targets it. For example, the `Parent` component (as it currently exists in Bevy) is the `Relationship` component and the entity containing the Parent is the "source entity". The entity _inside_ the `Parent(Entity)` component is the "target entity". And that target entity has a `Children` component (which implements `RelationshipSources`). In practice, the Parent/Children relationship looks like this: ```rust #[derive(Relationship)] #[relationship(relationship_sources = Children)] pub struct Parent(pub Entity); #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent)] pub struct Children(Vec<Entity>); ``` The Relationship and RelationshipSources derives automatically implement Component with the relevant configuration (namely, the hooks necessary to keep everything in sync). The most direct way to add relationships is to spawn entities with relationship components: ```rust let a = world.spawn_empty().id(); let b = world.spawn(Parent(a)).id(); assert_eq!(world.entity(a).get::<Children>().unwrap(), &[b]); ``` There are also convenience APIs for spawning more than one entity with the same relationship: ```rust world.spawn_empty().with_related::<Children>(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` The existing `with_children` API is now a simpler wrapper over `with_related`. This makes this change largely non-breaking for existing spawn patterns. ```rust world.spawn_empty().with_children(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` There are also other relationship APIs, such as `add_related` and `despawn_related`. ## Automatic recursive despawn via the new on_despawn hook `RelationshipSources` can opt-in to "despawn descendants" behavior, which will despawn all related entities in the relationship hierarchy: ```rust #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent, despawn_descendants)] pub struct Children(Vec<Entity>); ``` This means that `entity.despawn_recursive()` is no longer required. Instead, just use `entity.despawn()` and the relevant related entities will also be despawned. To despawn an entity _without_ despawning its parent/child descendants, you should remove the `Children` component first, which will also remove the related `Parent` components: ```rust entity .remove::<Children>() .despawn() ``` This builds on the on_despawn hook introduced in this PR, which is fired when an entity is despawned (before other hooks). ## Relationships are the source of truth `Relationship` is the _single_ source of truth component. `RelationshipSources` is merely a reflection of what all the `Relationship` components say. By embracing this, we are able to significantly improve the performance of the system as a whole. We can rely on component lifecycles to protect us against duplicates, rather than needing to scan at runtime to ensure entities don't already exist (which results in quadratic runtime). A single source of truth gives us constant-time inserts. This does mean that we cannot directly spawn populated `Children` components (or directly add or remove entities from those components). I personally think this is a worthwhile tradeoff, both because it makes the performance much better _and_ because it means theres exactly one way to do things (which is a philosophy we try to employ for Bevy APIs). As an aside: treating both sides of the relationship as "equivalent source of truth relations" does enable building simple and flexible many-to-many relationships. But this introduces an _inherent_ need to scan (or hash) to protect against duplicates. [`evergreen_relations`](https://github.com/EvergreenNest/evergreen_relations) has a very nice implementation of the "symmetrical many-to-many" approach. Unfortunately I think the performance issues inherent to that approach make it a poor choice for Bevy's default relationship system. ## Followup Work * Discuss renaming `Parent` to `ChildOf`. I refrained from doing that in this PR to keep the diff reasonable, but I'm personally biased toward this change (and using that naming pattern generally for relationships). * [Improved spawning ergonomics](https://github.com/bevyengine/bevy/discussions/16920) * Consider adding relationship observers/triggers for "relationship targets" whenever a source is added or removed. This would replace the current "hierarchy events" system, which is unused upstream but may have existing users downstream. I think triggers are the better fit for this than a buffered event queue, and would prefer not to add that back. * Fragmenting relations: My current idea hinges on the introduction of "value components" (aka: components whose type _and_ value determines their ComponentId, via something like Hashing / PartialEq). By labeling a Relationship component such as `ChildOf(Entity)` as a "value component", `ChildOf(e1)` and `ChildOf(e2)` would be considered "different components". This makes the transition between fragmenting and non-fragmenting a single flag, and everything else continues to work as expected. * Many-to-many support * Non-fragmenting: We can expand Relationship to be a list of entities instead of a single entity. I have largely already written the code for this. * Fragmenting: With the "value component" impl mentioned above, we get many-to-many support "for free", as it would allow inserting multiple copies of a Relationship component with different target entities. Fixes #3742 (If this PR is merged, I think we should open more targeted followup issues for the work above, with a fresh tracking issue free of the large amount of less-directed historical context) Fixes #17301 Fixes #12235 Fixes #15299 Fixes #15308 ## Migration Guide * Replace `ChildBuilder` with `ChildSpawnerCommands`. * Replace calls to `.set_parent(parent_id)` with `.insert(Parent(parent_id))`. * Replace calls to `.replace_children()` with `.remove::<Children>()` followed by `.add_children()`. Note that you'll need to manually despawn any children that are not carried over. * Replace calls to `.despawn_recursive()` with `.despawn()`. * Replace calls to `.despawn_descendants()` with `.despawn_related::<Children>()`. * If you have any calls to `.despawn()` which depend on the children being preserved, you'll need to remove the `Children` component first. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
b693362b0c
|
Fix some bevy_remote typos (#17408)
# Objective Contributor expressed distress on Discord over introducing a typo. ## Solution Fix em |
||
![]() |
b0beeab41a
|
Add BRP method to mutate a component (#16940)
# Objective Add a method to mutate components with BRP. Currently the only way to modify a component on an entity with BRP is to insert a new one with the new values. This isn't ideal for several reasons, one reason being that the client has to know what all the fields are of the component and stay in sync with the server. ## Solution Add a new BRP method called `bevy/mutate_component` to mutate a single field in a component on an entity. ## Testing Tested on a simple scene on all `Transform`, `Name`, and a custom component. --- ## Showcase Example JSON-RPC request to change the `Name` of an entity to "New name!" ```json { "jsonrpc": "2.0", "id": 0, "method": "bevy/mutate_component", "params": { "entity": 4294967308, "component": "bevy_ecs::name::Name", "path": ".name", "value": "New name!" } } ``` Or setting the X translation to 10.0 on a Transform: ```json { "jsonrpc": "2.0", "id": 0, "method": "bevy/mutate_component", "params": { "entity": 4294967308, "component": "bevy_transform::components::transform::Transform", "path": ".translation.x", "value": 10.0 } } ``` Clip of my Emacs BRP package using this method: https://github.com/user-attachments/assets/a786b245-5c20-4189-859f-2261c5086a68 --------- Co-authored-by: François Mockers <mockersf@gmail.com> |
||
![]() |
cae2da3cee
|
BRP registry JSON schema endpoint (#16882)
# Objective Resolve #16745 ## Solution Provide a way to map `AppTypeRegistry` types into a JSON Schema that can be used in other applications. I took code from https://github.com/kaosat-dev/Blenvy as a starting point, cleaned up and adapter more for `bevy_remote` needs. Based on feedback and needs it could be improved, I could add some filtering options, etc. ## Testing - I was comparing results with the ones from code in `blenvy` - There is added unit test, could be added more - I was testing it in my game with this code: ```rust fn types_to_file(world: &mut World) { use bevy_remote::builtin_methods::export_registry_types; let Ok(Ok(types_schema)) = world.run_system_cached_with(export_registry_types, None) else { return; }; let registry_save_path = std::path::Path::new("assets").join("registry.json"); let writer = std::fs::File::create(registry_save_path).expect("should have created schema file"); serde_json::to_writer_pretty(writer, &types_schema).expect("Failed to save types to file"); } ``` It can be run by adding it at startup ```rust app.add_systems(Startup, types_to_file); ``` --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> |
||
![]() |
897ffad8af
|
BRP strict field in query (#16725)
# Objective - Allow skiping components that don't have ComponentId yet instead of failing `bevy/query` request. ## Solution - Describe the solution used to achieve the objective above. ## Testing My naive approach boils down to: - bevy/list to get list of all components. - bevy/query with empty components and has fields and a option that contains result of the bevy/list. Before that change I end up with bunch of `Component xxx isn't used in the world` because some of the components wasn't spawned at any moment yet in the game. Now it should work. ## Migration Guide - `BrpQueryParams` now has `strict` boolean field. It serfs as a flag to fail when encountering an invalid component rather than skipping it. Defaults to false. |
||
![]() |
711246aa34
|
Update hashbrown to 0.15 (#15801)
Updating dependencies; adopted version of #15696. (Supercedes #15696.) Long answer: hashbrown is no longer using ahash by default, meaning that we can't use the default-hasher methods with ahasher. So, we have to use the longer-winded versions instead. This takes the opportunity to also switch our default hasher as well, but without actually enabling the default-hasher feature for hashbrown, meaning that we'll be able to change our hasher more easily at the cost of all of these method calls being obnoxious forever. One large change from 0.15 is that `insert_unique_unchecked` is now `unsafe`, and for cases where unsafe code was denied at the crate level, I replaced it with `insert`. ## Migration Guide `bevy_utils` has updated its version of `hashbrown` to 0.15 and now defaults to `foldhash` instead of `ahash`. This means that if you've hard-coded your hasher to `bevy_utils::AHasher` or separately used the `ahash` crate in your code, you may need to switch to `foldhash` to ensure that everything works like it does in Bevy. |
||
![]() |
7662b4fe40
|
Fix crash when component parameters are invalid (#16735)
# Objective Fix the following crash when using BRP to insert a malformed component: ``` thread 'main' panicked at /home/purplg/workspaces/evtc-replay/bevy/crates/bevy_remote/src/builtin_methods.rs:926:18: called `Result::unwrap()` on an `Err` value: Error("invalid type: map, expected f32", line: 0, column: 0) ``` ## Solution Return an error instead of unwrapping. ## Testing Tested by sending this malformed payload before and after implementing the fix: ```json { "jsonrpc": "2.0", "id": 0, "method": "bevy/insert", "params": { "entity": 4294967307, "components": { "bevy_transform::components::transform::Transform": { "rotation": [ 0.0, 0.0, 0.0, 1.0 ], "scale": [ 1.0, 1.0, 1.0 ], "translation": [ {}, 0.0, 0.0 ] } } } } ``` After implementing the fix, I receive the following response instead of a crash: ```json { "jsonrpc": "2.0", "id": 0, "error": { "code": -23402, "message": "bevy_transform::components::transform::Transform is invalid: invalid type: map, expected f32" } } ``` |
||
![]() |
5e26429768
|
BRP serialization tests (#16724)
# Objective Start work on tests in BRP. ## Solution - Adds serialization tests to BRP types |
||
![]() |
f59ae0f5e8
|
BrpQueryRow has field deserialization fix (#16613)
# Objective BrpQueryRow doesn't serialize `has` field if it is empty. That is okay until you try to deserialize it after. Then it will fail to deserialize due to missing field. ## Solution Serde support using default value when field is missing, this PR adds that. |
||
![]() |
f1fbb668f9
|
Watching versions of bevy/get and bevy/list with HTTP SSE (#15608)
## Objective Add a way to stream BRP requests when the data changes. ## Solution #### BRP Side (reusable for other transports) Add a new method handler type that returns a optional value. This handler is run in update and if a value is returned it will be sent on the message channel. Custom watching handlers can be added with `RemotePlugin::with_watching_method`. #### HTTP Side If a request comes in with `+watch` in the method, it will respond with `text/event-stream` rather than a single response. ## Testing I tested with the podman HTTP client. This client has good support for SSE's if you want to test it too. ## Parts I want some opinions on - For separating watching methods I chose to add a `+watch` suffix to the end kind of like `content-type` headers. A get would be `bevy/get+watch`. - Should watching methods send an initial response with everything or only respond when a change happens? Currently the later is what happens. ## Future work - The `bevy/query` method would also benefit from this but that condition will be quite complex so I will leave that to later. --------- Co-authored-by: Zachary Harrold <zac@harrold.com.au> |
||
![]() |
584d14808a
|
Allow World::entity family of functions to take multiple entities and get multiple references back (#15614)
# Objective Following the pattern established in #15593, we can reduce the API surface of `World` by providing a single function to grab both a singular entity reference, or multiple entity references. ## Solution The following functions can now also take multiple entity IDs and will return multiple entity references back: - `World::entity` - `World::get_entity` - `World::entity_mut` - `World::get_entity_mut` - `DeferredWorld::entity_mut` - `DeferredWorld::get_entity_mut` If you pass in X, you receive Y: - give a single `Entity`, receive a single `EntityRef`/`EntityWorldMut` (matches current behavior) - give a `[Entity; N]`/`&[Entity; N]` (array), receive an equally-sized `[EntityRef; N]`/`[EntityMut; N]` - give a `&[Entity]` (slice), receive a `Vec<EntityRef>`/`Vec<EntityMut>` - give a `&EntityHashSet`, receive a `EntityHashMap<EntityRef>`/`EntityHashMap<EntityMut>` Note that `EntityWorldMut` is only returned in the single-entity case, because having multiple at the same time would lead to UB. Also, `DeferredWorld` receives an `EntityMut` in the single-entity case because it does not allow structural access. ## Testing - Added doc-tests on `World::entity`, `World::entity_mut`, and `DeferredWorld::entity_mut` - Added tests for aliased mutability and entity existence --- ## Showcase <details> <summary>Click to view showcase</summary> The APIs for fetching `EntityRef`s and `EntityMut`s from the `World` have been unified. ```rust // This code will be referred to by subsequent code blocks. let world = World::new(); let e1 = world.spawn_empty().id(); let e2 = world.spawn_empty().id(); let e3 = world.spawn_empty().id(); ``` Querying for a single entity remains mostly the same: ```rust // 0.14 let eref: EntityRef = world.entity(e1); let emut: EntityWorldMut = world.entity_mut(e1); let eref: Option<EntityRef> = world.get_entity(e1); let emut: Option<EntityWorldMut> = world.get_entity_mut(e1); // 0.15 let eref: EntityRef = world.entity(e1); let emut: EntityWorldMut = world.entity_mut(e1); let eref: Result<EntityRef, Entity> = world.get_entity(e1); let emut: Result<EntityWorldMut, Entity> = world.get_entity_mut(e1); ``` Querying for multiple entities with an array has changed: ```rust // 0.14 let erefs: [EntityRef; 2] = world.many_entities([e1, e2]); let emuts: [EntityMut; 2] = world.many_entities_mut([e1, e2]); let erefs: Result<[EntityRef; 2], Entity> = world.get_many_entities([e1, e2]); let emuts: Result<[EntityMut; 2], QueryEntityError> = world.get_many_entities_mut([e1, e2]); // 0.15 let erefs: [EntityRef; 2] = world.entity([e1, e2]); let emuts: [EntityMut; 2] = world.entity_mut([e1, e2]); let erefs: Result<[EntityRef; 2], Entity> = world.get_entity([e1, e2]); let emuts: Result<[EntityMut; 2], EntityFetchError> = world.get_entity_mut([e1, e2]); ``` Querying for multiple entities with a slice has changed: ```rust let ids = vec![e1, e2, e3]); // 0.14 let erefs: Result<Vec<EntityRef>, Entity> = world.get_many_entities_dynamic(&ids[..]); let emuts: Result<Vec<EntityMut>, QueryEntityError> = world.get_many_entities_dynamic_mut(&ids[..]); // 0.15 let erefs: Result<Vec<EntityRef>, Entity> = world.get_entity(&ids[..]); let emuts: Result<Vec<EntityMut>, EntityFetchError> = world.get_entity_mut(&ids[..]); let erefs: Vec<EntityRef> = world.entity(&ids[..]); // Newly possible! let emuts: Vec<EntityMut> = world.entity_mut(&ids[..]); // Newly possible! ``` Querying for multiple entities with an `EntityHashSet` has changed: ```rust let set = EntityHashSet::from_iter([e1, e2, e3]); // 0.14 let emuts: Result<Vec<EntityMut>, QueryEntityError> = world.get_many_entities_from_set_mut(&set); // 0.15 let emuts: Result<EntityHashMap<EntityMut>, EntityFetchError> = world.get_entity_mut(&set); let erefs: Result<EntityHashMap<EntityRef>, EntityFetchError> = world.get_entity(&set); // Newly possible! let emuts: EntityHashMap<EntityMut> = world.entity_mut(&set); // Newly possible! let erefs: EntityHashMap<EntityRef> = world.entity(&set); // Newly possible! ``` </details> ## Migration Guide - `World::get_entity` now returns `Result<_, Entity>` instead of `Option<_>`. - Use `world.get_entity(..).ok()` to return to the previous behavior. - `World::get_entity_mut` and `DeferredWorld::get_entity_mut` now return `Result<_, EntityFetchError>` instead of `Option<_>`. - Use `world.get_entity_mut(..).ok()` to return to the previous behavior. - Type inference for `World::entity`, `World::entity_mut`, `World::get_entity`, `World::get_entity_mut`, `DeferredWorld::entity_mut`, and `DeferredWorld::get_entity_mut` has changed, and might now require the input argument's type to be explicitly written when inside closures. - The following functions have been deprecated, and should be replaced as such: - `World::many_entities` -> `World::entity::<[Entity; N]>` - `World::many_entities_mut` -> `World::entity_mut::<[Entity; N]>` - `World::get_many_entities` -> `World::get_entity::<[Entity; N]>` - `World::get_many_entities_dynamic` -> `World::get_entity::<&[Entity]>` - `World::get_many_entities_mut` -> `World::get_entity_mut::<[Entity; N]>` - The equivalent return type has changed from `Result<_, QueryEntityError>` to `Result<_, EntityFetchError>` - `World::get_many_entities_dynamic_mut` -> `World::get_entity_mut::<&[Entity]>1 - The equivalent return type has changed from `Result<_, QueryEntityError>` to `Result<_, EntityFetchError>` - `World::get_many_entities_from_set_mut` -> `World::get_entity_mut::<&EntityHashSet>` - The equivalent return type has changed from `Result<Vec<EntityMut>, QueryEntityError>` to `Result<EntityHashMap<EntityMut>, EntityFetchError>`. If necessary, you can still convert the `EntityHashMap` into a `Vec`. |
||
![]() |
85dfd72631
|
Include errors along side successful components in BRP bevy/get method (#15516)
## Objective I am using BRP for a web inspector. To get components from a entity is first do a `bevy/list` on the specific entity and then use the result in a `bevy/get` request. The problem with this is `bevy/list` returns all components even if they aren't reflect-able (which is what I expect) but when I then do a `bevy/get` request even if all bar one of the components are reflect-able the request will fail. ## Solution Update the `bevy/get` response to include a map of components like it did for successful request and a map of errors. This means if one or more components are not present on the entity or cannot be reflected it will not fail the entire request. I also only did `bevy/get` as I don't think any of the other methods would benefit from this. ## Testing I tested this with my inspector and with a http client and it worked as expected. --------- Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> |
||
![]() |
d70595b667
|
Add core and alloc over std Lints (#15281)
# Objective - Fixes #6370 - Closes #6581 ## Solution - Added the following lints to the workspace: - `std_instead_of_core` - `std_instead_of_alloc` - `alloc_instead_of_core` - Used `cargo +nightly fmt` with [item level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A) to split all `use` statements into single items. - Used `cargo clippy --workspace --all-targets --all-features --fix --allow-dirty` to _attempt_ to resolve the new linting issues, and intervened where the lint was unable to resolve the issue automatically (usually due to needing an `extern crate alloc;` statement in a crate root). - Manually removed certain uses of `std` where negative feature gating prevented `--all-features` from finding the offending uses. - Used `cargo +nightly fmt` with [crate level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A) to re-merge all `use` statements matching Bevy's previous styling. - Manually fixed cases where the `fmt` tool could not re-merge `use` statements due to conditional compilation attributes. ## Testing - Ran CI locally ## Migration Guide The MSRV is now 1.81. Please update to this version or higher. ## Notes - This is a _massive_ change to try and push through, which is why I've outlined the semi-automatic steps I used to create this PR, in case this fails and someone else tries again in the future. - Making this change has no impact on user code, but does mean Bevy contributors will be warned to use `core` and `alloc` instead of `std` where possible. - This lint is a critical first step towards investigating `no_std` options for Bevy. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com> |
||
![]() |
efda7f3f9c
|
Simpler lint fixes: makes ci lints work but disables a lint for now (#15376)
Takes the first two commits from #15375 and adds suggestions from this comment: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300 See #15375 for more reasoning/motivation. ## Rebasing (rerunning) ```rust git switch simpler-lint-fixes git reset --hard main cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "rustfmt" cargo clippy --workspace --all-targets --all-features --fix cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "clippy" git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887 ``` |
||
![]() |
89e98b208f
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective Adopted from #13563. The goal is to implement the Bevy Remote Protocol over HTTP/JSON, allowing the ECS to be interacted with remotely. ## Solution At a high level, there are really two separate things that have been undertaken here: 1. First, `RemotePlugin` has been created, which has the effect of embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint into a Bevy application. 2. Second, the [Bevy Remote Protocol verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md) (excluding `POLL`) have been implemented as remote methods for that JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`, `bevy/list`, etc.). To avoid some repetition, here is the crate-level documentation, which explains the request/response structure, built-in-methods, and custom method configuration: <details> <summary>Click to view crate-level docs</summary> ```rust //! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow //! for remote control of a Bevy app. //! //! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept //! connections over HTTP (by default, on port 15702) while your app is running. //! These *remote clients* can inspect and alter the state of the //! entity-component system. Clients are expected to `POST` JSON requests to the //! root URL; see the `client` example for a trivial example of use. //! //! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol. //! //! ## Request objects //! //! A typical client request might look like this: //! //! ```json //! { //! "method": "bevy/get", //! "id": 0, //! "params": { //! "entity": 4294967298, //! "components": [ //! "bevy_transform::components::transform::Transform" //! ] //! } //! } //! ``` //! //! The `id` and `method` fields are required. The `param` field may be omitted //! for certain methods: //! //! * `id` is arbitrary JSON data. The server completely ignores its contents, //! and the client may use it for any purpose. It will be copied via //! serialization and deserialization (so object property order, etc. can't be //! relied upon to be identical) and sent back to the client as part of the //! response. //! //! * `method` is a string that specifies one of the possible [`BrpRequest`] //! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive. //! //! * `params` is parameter data specific to the request. //! //! For more information, see the documentation for [`BrpRequest`]. //! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde` //! documentation] may be useful to clarify the correspondence between the Rust //! structure and the JSON format. //! //! ## Response objects //! //! A response from the server to the client might look like this: //! //! ```json //! { //! "jsonrpc": "2.0", //! "id": 0, //! "result": { //! "bevy_transform::components::transform::Transform": { //! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }, //! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 }, //! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 } //! } //! } //! } //! ``` //! //! The `id` field will always be present. The `result` field will be present if the //! request was successful. Otherwise, an `error` field will replace it. //! //! * `id` is the arbitrary JSON data that was sent as part of the request. It //! will be identical to the `id` data sent during the request, modulo //! serialization and deserialization. If there's an error reading the `id` field, //! it will be `null`. //! //! * `result` will be present if the request succeeded and will contain the response //! specific to the request. //! //! * `error` will be present if the request failed and will contain an error object //! with more information about the cause of failure. //! //! ## Error objects //! //! An error object might look like this: //! //! ```json //! { //! "code": -32602, //! "message": "Missing \"entity\" field" //! } //! ``` //! //! The `code` and `message` fields will always be present. There may also be a `data` field. //! //! * `code` is an integer representing the kind of an error that happened. Error codes documented //! in the [`error_codes`] module. //! //! * `message` is a short, one-sentence human-readable description of the error. //! //! * `data` is an optional field of arbitrary type containing additional information about the error. //! //! ## Built-in methods //! //! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data //! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for //! BRP built-in methods. //! //! ### bevy/get //! //! Retrieve the values of one or more components from an entity. //! //! `params`: //! - `entity`: The ID of the entity whose components will be fetched. //! - `components`: An array of fully-qualified type names of components to fetch. //! //! `result`: A map associating each type name to its value on the requested entity. //! //! ### bevy/query //! //! Perform a query over components in the ECS, returning all matching entities and their associated //! component values. //! //! All of the arrays that comprise this request are optional, and when they are not provided, they //! will be treated as if they were empty. //! //! `params`: //! `params`: //! - `data`: //! - `components` (optional): An array of fully-qualified type names of components to fetch. //! - `option` (optional): An array of fully-qualified type names of components to fetch optionally. //! - `has` (optional): An array of fully-qualified type names of components whose presence will be //! reported as boolean values. //! - `filter` (optional): //! - `with` (optional): An array of fully-qualified type names of components that must be present //! on entities in order for them to be included in results. //! - `without` (optional): An array of fully-qualified type names of components that must *not* be //! present on entities in order for them to be included in results. //! //! `result`: An array, each of which is an object containing: //! - `entity`: The ID of a query-matching entity. //! - `components`: A map associating each type name from `components`/`option` to its value on the matching //! entity if the component is present. //! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the //! entity has that component. If `has` was empty or omitted, this key will be omitted in the response. //! //! ### bevy/spawn //! //! Create a new entity with the provided components and return the resulting entity ID. //! //! `params`: //! - `components`: A map associating each component's fully-qualified type name with its value. //! //! `result`: //! - `entity`: The ID of the newly spawned entity. //! //! ### bevy/destroy //! //! Despawn the entity with the given ID. //! //! `params`: //! - `entity`: The ID of the entity to be despawned. //! //! `result`: null. //! //! ### bevy/remove //! //! Delete one or more components from an entity. //! //! `params`: //! - `entity`: The ID of the entity whose components should be removed. //! - `components`: An array of fully-qualified type names of components to be removed. //! //! `result`: null. //! //! ### bevy/insert //! //! Insert one or more components into an entity. //! //! `params`: //! - `entity`: The ID of the entity to insert components into. //! - `components`: A map associating each component's fully-qualified type name with its value. //! //! `result`: null. //! //! ### bevy/reparent //! //! Assign a new parent to one or more entities. //! //! `params`: //! - `entities`: An array of entity IDs of entities that will be made children of the `parent`. //! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned. //! If excluded, the given entities will be removed from their parents. //! //! `result`: null. //! //! ### bevy/list //! //! List all registered components or all components present on an entity. //! //! When `params` is not provided, this lists all registered components. If `params` is provided, //! this lists only those components present on the provided entity. //! //! `params` (optional): //! - `entity`: The ID of the entity whose components will be listed. //! //! `result`: An array of fully-qualified type names of components. //! //! ## Custom methods //! //! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom //! methods. This is primarily done during the initialization of [`RemotePlugin`], although the //! methods may also be extended at runtime using the [`RemoteMethods`] resource. //! //! ### Example //! ```ignore //! fn main() { //! App::new() //! .add_plugins(DefaultPlugins) //! .add_plugins( //! // `default` adds all of the built-in methods, while `with_method` extends them //! RemotePlugin::default() //! .with_method("super_user/cool_method".to_owned(), path::to::my:🆒:handler) //! // ... more methods can be added by chaining `with_method` //! ) //! .add_systems( //! // ... standard application setup //! ) //! .run(); //! } //! ``` //! //! The handler is expected to be a system-convertible function which takes optional JSON parameters //! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks //! something like this: //! ``` //! # use serde_json::Value; //! # use bevy_ecs::prelude::{In, World}; //! # use bevy_remote::BrpResult; //! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult { //! todo!() //! } //! ``` //! //! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The //! handler system will always run with exclusive `World` access. //! //! [the `serde` documentation]: https://serde.rs/ ``` </details> ### Message lifecycle At a high level, the lifecycle of client-server interactions is something like this: 1. The client sends one or more `BrpRequest`s. The deserialized version of that is just the Rust representation of a JSON-RPC request, and it looks like this: ```rust pub struct BrpRequest { /// The action to be performed. Parsing is deferred for the sake of error reporting. pub method: Option<Value>, /// Arbitrary data that will be returned verbatim to the client as part of /// the response. pub id: Option<Value>, /// The parameters, specific to each method. /// /// These are passed as the first argument to the method handler. /// Sometimes params can be omitted. pub params: Option<Value>, } ``` 2. These requests are accumulated in a mailbox resource (small lie but close enough). 3. Each update, the mailbox is drained by a system `process_remote_requests`, where each request is processed according to its `method`, which has an associated handler. Each handler is a Bevy system that runs with exclusive world access and returns a result; e.g.: ```rust pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... } ``` 4. The result (or an error) is reported back to the client. ## Testing This can be tested by using the `server` and `client` examples. The `client` example is not particularly exhaustive at the moment (it only creates barebones `bevy/query` requests) but is still informative. Other queries can be made using `curl` with the `server` example running. For example, to make a `bevy/list` request and list all registered components: ```bash curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq . ``` --- ## Future direction There were a couple comments on BRP versioning while this was in draft. I agree that BRP versioning is a good idea, but I think that it requires some consensus on a couple fronts: - First of all, what does the version actually mean? Is it a version for the protocol itself or for the `bevy/*` methods implemented using it? Both? - Where does the version actually live? The most natural place is just where we have `"jsonrpc"` right now (at least if it's versioning the protocol itself), but this means we're not actually conforming to JSON-RPC any more (so, for example, any client library used to construct JSON-RPC requests would stop working). I'm not really against that, but it's at least a real decision. - What do we actually do when we encounter mismatched versions? Adding handling for this would be actual scope creep instead of just a little add-on in my opinion. Another thing that would be nice is making the internal structure of the implementation less JSON-specific. Right now, for example, component values that will appear in server responses are quite eagerly converted to JSON `Value`s, which prevents disentangling the handler logic from the communication medium, but it can probably be done in principle and I imagine it would enable more code reuse (e.g. for custom method handlers) in addition to making the internals more readily usable for other formats. --------- Co-authored-by: Patrick Walton <pcwalton@mimiga.net> Co-authored-by: DragonGamesStudios <margos.michal@gmail.com> Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> |