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

bevy_remote #1

Draft
wants to merge 99 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
30c3e4a
Add initial BRP types
coreh Jan 14, 2024
848fa61
Add initial BRP docs
coreh Jan 14, 2024
097f871
Add initial HTTP remote implementation
coreh Jan 14, 2024
0e3a064
Add `RemotePlugin` to `DefaultPlugins`
coreh Jan 14, 2024
a409838
Add logging, fix timeount handling, validate method
coreh Jan 14, 2024
bf51cf7
Add initial pass at system for handling BRP requests
coreh Jan 14, 2024
565ee43
Add automatic id generation for HTTP requests
coreh Jan 14, 2024
fc80e08
Add multiple session support
coreh Jan 14, 2024
4ededa4
Add initial support for query requests
coreh Jan 14, 2024
33f0c9c
Add initial pass at BRP tool
coreh Jan 14, 2024
d4910f5
Fix warnings
coreh Jan 14, 2024
cbe6410
Add more examples
coreh Jan 14, 2024
1d1c143
Add one more example
coreh Jan 14, 2024
2404124
Add error examples
coreh Jan 14, 2024
1e1e192
Add remote http example
coreh Jan 14, 2024
c71c1ec
Cleanup tool code a bit more
coreh Jan 15, 2024
47f679a
Split BRP handling code in multiple functions
coreh Jan 15, 2024
daf3da8
Make `BrpSessions` shared via `Arc<RwLock<T>`
coreh Jan 15, 2024
9660bdf
Rename top level items to use `Remote` instead of `Brp`
coreh Jan 15, 2024
d9c0588
Cleanup and simplify serialization and error handling code
coreh Jan 15, 2024
a061dc5
Add support for `has` and `optional`
coreh Jan 15, 2024
b783b87
Hold `type_registry` thoughout query results processing
coreh Jan 15, 2024
2cf68a9
Use short component names
coreh Jan 15, 2024
62e86d8
Cleanup examples, add more
coreh Jan 15, 2024
97142b3
Start up tool in Ping example request
coreh Jan 15, 2024
32fc1f8
Fix `EntityId` deserialization
coreh Jan 15, 2024
ebb0e92
Add support for the `INSERT` request
coreh Jan 15, 2024
335a2d3
Add more HTTP error codes
coreh Jan 15, 2024
b72dd21
Fix wrong enum variant names
coreh Jan 15, 2024
257d9bf
Add cube transform example
coreh Jan 15, 2024
12e5b39
Fix incorrect serialization, remove spurious log
coreh Jan 17, 2024
3177a39
Remove `BrpEntity` since we can't really use it for nested serialization
coreh Jan 17, 2024
3b772fb
Add predicate-based filtering option
coreh Jan 17, 2024
3534295
Properly update cache with components from predicate
coreh Jan 17, 2024
f34cd35
Fix parent serialization in predicate example
coreh Jan 17, 2024
d929ec5
Make sure components are accessible for predicate evaluation
coreh Jan 17, 2024
4703a4c
Handle network failures
coreh Jan 17, 2024
ed7e5ca
Replace `BrpComponentName` newtype with an alias
coreh Jan 17, 2024
5d43e3a
Make sure the name provided by the client is used on error messages
coreh Jan 17, 2024
e4e4598
Rename predicate variants
coreh Jan 17, 2024
7919075
Add support for fetching all components
coreh Jan 17, 2024
1647230
Add CORS support
coreh Jan 18, 2024
45a02f8
Make `GET` request more like `QUERY` request
coreh Jan 19, 2024
69039aa
Increase timeout to 5s
coreh Jan 19, 2024
36c9a87
Add GET request support
coreh Jan 19, 2024
607d7e1
Make sure `serialize` feature is enabled with `bevy_remote`
coreh Jan 21, 2024
4885a76
Fix segfault on Linux
coreh Jan 27, 2024
15395b2
Allow connections from other hosts
coreh Jan 27, 2024
2994f6f
Add `WasmRemotePlugin`
coreh Jan 29, 2024
42f4249
Add `ReflectDefault` support to BRP
coreh Feb 2, 2024
86ceb3c
Extract deserialization logic to a common function
coreh Feb 2, 2024
e8e586c
Call `.clone_weak()` when serializing strong `Handle`s
coreh Feb 3, 2024
2d42012
Rename `BrpComponent` to `BrpSerializedData`
coreh Feb 3, 2024
4d7eeb9
Add `ReflectHandle::untyped_from_ptr()`
coreh Feb 3, 2024
fc41b67
Remove `ReflectHandle::untyped_from_ptr()`
coreh Feb 3, 2024
3a6331b
Reflect `Default` for `Handle<A>`
coreh Feb 3, 2024
ccc18a0
Register `AssetIndex` type
coreh Feb 3, 2024
d13e135
Add ability to get assets
coreh Feb 3, 2024
e77e16a
Remove unused import
coreh Feb 3, 2024
f06f6f5
Remove unsafe use of `OwningPtr`
coreh Feb 3, 2024
a3c3367
Rename errors to be more general
coreh Feb 3, 2024
b7e6a48
Add JSON5 support
coreh Feb 3, 2024
5c3b7dd
Report deserialization error message
coreh Feb 3, 2024
1355f54
Implement UpdateAsset command
coreh Feb 3, 2024
2fdffcd
Use JSON5 for WASM sessions too
coreh Feb 3, 2024
acabdb0
Insert asset instead of applying it via reflection
coreh Feb 3, 2024
11c90c4
Get rid of `RemoteCache` entirely
coreh Feb 8, 2024
ec67aca
Rename BRP requests
coreh Feb 8, 2024
4bb1a94
Fix errors after rebase
coreh Feb 8, 2024
454d0a7
Add crate docs
coreh Feb 8, 2024
c70c33c
Move some methods to `RemoteSession` impl
coreh Feb 11, 2024
36f27fb
Move all request methods to RemoteSession
coreh Feb 11, 2024
560e5d1
Move serialization method to `BrpSerializedData`
coreh Feb 11, 2024
a035fbf
Pass serialization format instead of session
coreh Feb 11, 2024
86ab1ef
Move more methods to `BrpSerializedData` impl
coreh Feb 11, 2024
9d8631f
Move asset serialization logic to `BrpSerializedData` impl
coreh Feb 11, 2024
e05bf54
Extract shared serialization logic
coreh Feb 11, 2024
b356223
Fix wrong type name in feature gated code
coreh Feb 11, 2024
310beaf
Move session logic and types to separate file
coreh Feb 11, 2024
8283c15
Add process method to `RemoteSessions`
coreh Feb 11, 2024
43f435c
Move predicate processing to `RemoteSession`
coreh Feb 11, 2024
ea92e59
Cleanup after moving things around
coreh Feb 11, 2024
5caf96c
Move serialized data associated methods to separate file
coreh Feb 11, 2024
d2e3ab1
Add conversions from unfiltered to filtered `EntityRef`s
coreh Feb 12, 2024
86a49eb
Add fallible conversions from filtered to unfiltered `EntityRef`s
coreh Feb 12, 2024
db50859
Get rid of `AnyEntityRef`
coreh Feb 12, 2024
4f19515
Fix missing write permission
coreh Feb 12, 2024
8de6ed3
Require mutable reference for conversion to mutable
coreh Feb 13, 2024
fe2a520
Fix build error
coreh Feb 13, 2024
c45071c
Require mutable reference for fallible conversion to mutable
coreh Feb 13, 2024
2a67461
Remove unused imports
coreh Feb 13, 2024
19cd7c6
Move `RemoteSerializationFormat` to same file as sessions
coreh Feb 13, 2024
0ed4ac5
Add method for querying whether a given short type path is ambiguous
coreh Feb 13, 2024
56df4b9
Add documentation for BRP types
coreh Feb 13, 2024
96306c5
Check for ambiguity to return a more accurate error
coreh Feb 13, 2024
5109dd7
Merge branch 'main' into bevy-remote
coreh Feb 20, 2024
673402c
Add resource requests
coreh Feb 20, 2024
b6c1f8f
Actually get resource instead of default value
coreh Feb 20, 2024
069fc04
Add methods for iterating resources
coreh Feb 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions Cargo.toml
Expand Up @@ -66,6 +66,7 @@ default = [
"bevy_sprite",
"bevy_text",
"bevy_ui",
"bevy_remote",
"multi-threaded",
"png",
"hdr",
Expand Down Expand Up @@ -135,6 +136,12 @@ bevy_ui = [
"bevy_sprite",
]

# Provides remote inspection functionality
bevy_remote = ["serialize", "bevy_internal/bevy_remote"]

# Provides remote inspection functionality over HTTP
remote_http = ["bevy_remote", "bevy_internal/remote_http"]

# winit window and input backend
bevy_winit = ["bevy_internal/bevy_winit"]

Expand Down Expand Up @@ -2643,6 +2650,18 @@ description = "Demonstrates creating and using custom Ui materials"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "remote_http"
path = "examples/remote/http.rs"
doc-scrape-examples = true
required-features = ["remote_http"]

[package.metadata.example.remote_http]
name = "Remote via HTTP"
description = "Demonstrate the use of the Remote plugin with HTTP"
category = "Remote"
wasm = false

[[example]]
name = "render_primitives"
path = "examples/math/render_primitives.rs"
Expand Down
21 changes: 19 additions & 2 deletions crates/bevy_asset/src/handle.rs
Expand Up @@ -3,9 +3,10 @@ use crate::{
UntypedAssetId,
};
use bevy_ecs::prelude::*;
use bevy_reflect::{Reflect, TypePath};
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectSerialize, TypePath};
use bevy_utils::{get_short_name, Uuid};
use crossbeam_channel::{Receiver, Sender};
use serde::Serialize;
use std::{
any::TypeId,
hash::{Hash, Hasher},
Expand Down Expand Up @@ -121,7 +122,7 @@ impl std::fmt::Debug for StrongHandle {
///
/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Component, Reflect)]
#[reflect(Component)]
#[reflect(Component, Serialize, Default)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Expand All @@ -131,6 +132,22 @@ pub enum Handle<A: Asset> {
Weak(AssetId<A>),
}

impl<A: Asset> Serialize for Handle<A> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let Handle::Weak(id) = self.clone_weak() else {
// `.clone_weak()` will always return a weak handle
unreachable!();
};

// Always serialize as a weak handle, as the asset is not guaranteed to be alive
// when we later deserialize this handle.
serializer.serialize_newtype_variant(std::any::type_name::<Self>(), 1, "Weak", &id)
}
}

impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
match self {
Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_asset/src/id.rs
@@ -1,7 +1,7 @@
use crate::{Asset, AssetIndex};
use bevy_reflect::Reflect;
use bevy_reflect::{Reflect, ReflectSerialize};
use bevy_utils::Uuid;

use serde::Serialize;
use std::{
any::TypeId,
fmt::{Debug, Display},
Expand All @@ -16,7 +16,8 @@ use thiserror::Error;
/// For an identifier tied to the lifetime of an asset, see [`Handle`](`crate::Handle`).
///
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
#[derive(Reflect)]
#[derive(Reflect, Serialize)]
#[reflect(Serialize)]
pub enum AssetId<A: Asset> {
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
/// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are
Expand All @@ -26,6 +27,7 @@ pub enum AssetId<A: Asset> {
Index {
index: AssetIndex,
#[reflect(ignore)]
#[serde(skip)]
marker: PhantomData<fn() -> A>,
},
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_asset/src/lib.rs
Expand Up @@ -228,6 +228,8 @@ impl Plugin for AssetPlugin {
let mut order = app.world.resource_mut::<MainScheduleOrder>();
order.insert_after(First, UpdateAssets);
order.insert_after(PostUpdate, AssetEvents);

app.register_type::<AssetIndex>();
}
}

Expand Down
17 changes: 13 additions & 4 deletions crates/bevy_ecs/src/storage/resource.rs
Expand Up @@ -50,10 +50,7 @@ impl<const SEND: bool> ResourceData<SEND> {
/// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from.
#[inline]
fn validate_access(&self) {
if SEND {
return;
}
if self.origin_thread_id != Some(std::thread::current().id()) {
if !self.can_access() {
// Panic in tests, as testing for aborting is nearly impossible
panic!(
"Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
Expand All @@ -64,6 +61,18 @@ impl<const SEND: bool> ResourceData<SEND> {
}
}

/// Returns whether the current thread can access a resource or not:
///
/// - For `!Send` resources, this will return `true` if the current thread is the same as the one it was inserted from;
/// - For `Send` resources, this will always return `true`.
#[inline]
pub fn can_access(&self) -> bool {
if SEND {
return true;
}
self.origin_thread_id == Some(std::thread::current().id())
}

/// Returns true if the resource is populated.
#[inline]
pub fn is_present(&self) -> bool {
Expand Down
75 changes: 74 additions & 1 deletion crates/bevy_ecs/src/world/mod.rs
Expand Up @@ -27,7 +27,7 @@ use crate::{
query::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState},
removal_detection::RemovedComponentEvents,
schedule::{Schedule, ScheduleLabel, Schedules},
storage::{ResourceData, Storages},
storage::{ResourceData, Resources, Storages},
system::{Res, Resource},
world::error::TryRunScheduleError,
};
Expand Down Expand Up @@ -2040,6 +2040,79 @@ impl World {
self.storages.resources.clear();
self.storages.non_send_resources.clear();
}

/// Iterates over all resources in the world
pub fn iter_resources(&self) -> impl Iterator<Item = (&ComponentInfo, Ptr<'_>)> {
self.iter_resources_internal(&self.storages.resources)
}

/// Mutably iterates over all resources in the world
pub fn iter_resources_mut(&mut self) -> impl Iterator<Item = (&ComponentInfo, MutUntyped<'_>)> {
// SAFETY: `&mut self` ensures that all accessed data is unaliased
unsafe { self.iter_resources_mut_internal(&self.storages.resources) }
}

/// Iterates over all `!Send` resources in the world that are accessible from the current thread.
///
/// Resources that were inserted from a different thread are skipped.
pub fn iter_non_send(&self) -> impl Iterator<Item = (&ComponentInfo, Ptr<'_>)> {
self.iter_resources_internal(&self.storages.non_send_resources)
}

/// Mutably iterates over all `!Send` resources in the world that are accessible from the current thread.
///
/// Resources that were inserted from a different thread are skipped.
pub fn iter_non_send_mut(&mut self) -> impl Iterator<Item = (&ComponentInfo, MutUntyped<'_>)> {
// SAFETY: `&mut self` ensures that all accessed data is unaliased
unsafe { self.iter_resources_mut_internal(&self.storages.non_send_resources) }
}

fn iter_resources_internal<'w, const SEND: bool>(
&'w self,
resources: &'w Resources<SEND>,
) -> impl Iterator<Item = (&ComponentInfo, Ptr<'_>)> {
resources.iter().filter(|(_, data)| data.can_access()).map(|(component_id, data)| {
let component_info = self.components.get_info(component_id).unwrap_or_else(|| {
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id);
});
let ptr = data.get_data().unwrap_or_else(|| {
panic!(
"When iterating all resources, resource of type {} was supposed to exist, but did not.",
component_info.name()
)
});
(component_info, ptr)
})
}

/// # Safety
/// - Caller must have exclusive access to the world
unsafe fn iter_resources_mut_internal<'w, const SEND: bool>(
&'w self,
resources: &'w Resources<SEND>,
) -> impl Iterator<Item = (&ComponentInfo, MutUntyped<'_>)> {
resources.iter().filter(|(_, data)| data.can_access()).map(|(component_id, data)| {
let component_info = self.components.get_info(component_id).unwrap_or_else(|| {
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id);
});

let (ptr, ticks) = data.get_with_ticks().unwrap_or_else(|| {
panic!(
"When iterating all resources, resource of type {} was supposed to exist, but did not.",
component_info.name()
)
});

let ticks = TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick());

let mut_untyped = MutUntyped {
value: ptr.assert_unique(),
ticks,
};

(component_info, mut_untyped)
})
}
}

impl World {
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_internal/Cargo.toml
Expand Up @@ -137,6 +137,10 @@ bevy_text = ["dep:bevy_text", "bevy_ui?/bevy_text"]

bevy_render = ["dep:bevy_render", "bevy_scene?/bevy_render"]

bevy_remote = ["dep:bevy_remote"]

remote_http = ["bevy_remote/http"]

# Enable assertions to check the validity of parameters passed to glam
glam_assert = ["bevy_math/glam_assert"]

Expand Down Expand Up @@ -197,6 +201,7 @@ bevy_ui = { path = "../bevy_ui", optional = true, version = "0.13.0" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.13.0" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.13.0" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.13.0", default-features = false }
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.13.0" }

[lints]
workspace = true
5 changes: 5 additions & 0 deletions crates/bevy_internal/src/default_plugins.rs
Expand Up @@ -134,6 +134,11 @@ impl PluginGroup for DefaultPlugins {
group = group.add(bevy_gizmos::GizmoPlugin);
}

#[cfg(feature = "bevy_remote")]
{
group = group.add(bevy_remote::RemotePlugin);
}

group = group.add(IgnoreAmbiguitiesPlugin);

group
Expand Down
33 changes: 33 additions & 0 deletions crates/bevy_remote/Cargo.toml
@@ -0,0 +1,33 @@
[package]
name = "bevy_remote"
version = "0.13.0"
edition = "2021"
description = "Provides remote inspection capabilities for Bevy Engine via the BRP (Bevy Remote Protocol)"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

[features]
http = ["dep:rouille"]

[dependencies]
bevy_app = { path = "../bevy_app", version = "0.13.0" }
bevy_asset = { path = "../bevy_asset", version = "0.13.0" }
bevy_utils = { path = "../bevy_utils", version = "0.13.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.13.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.13.0" }
bevy_log = { path = "../bevy_log", version = "0.13.0" }

serde = { version = "1", features = ["derive"] }
serde_json = "1"
ron = "0.8.0"
json5 = "0.4.1"
crossbeam-channel = "0.5.0"
rouille = { version = "3.6.2", optional = true }
wasm-bindgen = "0.2"
serde-wasm-bindgen = "0.6"
js-sys = "0.3"

[lints]
workspace = true