Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub mod prelude {
#[doc(hidden)]
#[cfg(feature = "bevy_reflect")]
pub use crate::reflect::{
AppTypeRegistry, ReflectComponent, ReflectFromWorld, ReflectResource,
AppTypeRegistry, ReflectComponent, ReflectEvent, ReflectFromWorld, ReflectResource,
};

#[doc(hidden)]
Expand Down
77 changes: 77 additions & 0 deletions crates/bevy_ecs/src/reflect/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Definitions for [`Event`] reflection.
//! This allows triggering events whose type is only known at runtime.
//!
//! This module exports two types: [`ReflectEventFns`] and [`ReflectEvent`].
//!
//! Same as [`component`](`super::component`), but for events.

use crate::{event::Event, reflect::from_reflect_with_fallback, world::World};
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry};

/// A struct used to operate on reflected [`Event`] trait of a type.
/// Also works for [`EntityEvent`].
///
/// A [`ReflectEvent`] for type `T` can be obtained via
/// [`bevy_reflect::TypeRegistration::data`].
#[derive(Clone)]
pub struct ReflectEvent(ReflectEventFns);

/// The raw function pointers needed to make up a [`ReflectEvent`].
///
/// This is used when creating custom implementations of [`ReflectEvent`] with
/// [`ReflectEvent::new()`].
///
/// > **Note:**
/// > Creating custom implementations of [`ReflectEvent`] is an advanced feature that most users
/// > will not need.
/// > Usually a [`ReflectEvent`] is created for a type by deriving [`Reflect`]
/// > and adding the `#[reflect(Event)]` attribute.
/// > After adding the component to the [`TypeRegistry`],
/// > its [`ReflectEvent`] can then be retrieved when needed.
///
/// Creating a custom [`ReflectEvent`] may be useful if you need to create new component types
/// at runtime, for example, for scripting implementations.
///
/// By creating a custom [`ReflectEvent`] and inserting it into a type's
/// [`TypeRegistration`][bevy_reflect::TypeRegistration],
/// you can modify the way that reflected event of that type will be triggered in the Bevy
/// world.
#[derive(Clone)]
pub struct ReflectEventFns {
trigger: fn(&mut World, &dyn PartialReflect, &TypeRegistry),
}

impl ReflectEventFns {
/// Get the default set of [`ReflectEventFns`] for a specific event type using its
/// [`FromType`] implementation.
///
/// This is useful if you want to start with the default implementation before overriding some
/// of the functions to create a custom implementation.
pub fn new<'a, T: Event + FromReflect + TypePath>() -> Self
where
T::Trigger<'a>: Default,
{
<ReflectEvent as FromType<T>>::from_type().0
}
}

impl ReflectEvent {
/// Triggers a reflected [`Event`] like [`trigger()`](World::trigger).
pub fn trigger(&self, world: &mut World, event: &dyn PartialReflect, registry: &TypeRegistry) {
(self.0.trigger)(world, event, registry);
}
}

impl<'a, E: Event + Reflect + TypePath> FromType<E> for ReflectEvent
where
<E as Event>::Trigger<'a>: Default,
{
fn from_type() -> Self {
ReflectEvent(ReflectEventFns {
trigger: |world, reflected_event, registry| {
let event = from_reflect_with_fallback::<E>(reflected_event, world, registry);
world.trigger(event);
},
})
}
}
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use bevy_reflect::{
mod bundle;
mod component;
mod entity_commands;
mod event;
mod from_world;
mod map_entities;
mod resource;
Expand All @@ -22,6 +23,7 @@ use bevy_utils::prelude::DebugName;
pub use bundle::{ReflectBundle, ReflectBundleFns};
pub use component::{ReflectComponent, ReflectComponentFns};
pub use entity_commands::ReflectCommandExt;
pub use event::{ReflectEvent, ReflectEventFns};
pub use from_world::{ReflectFromWorld, ReflectFromWorldFns};
pub use map_entities::ReflectMapEntities;
pub use resource::{ReflectResource, ReflectResourceFns};
Expand Down
98 changes: 94 additions & 4 deletions crates/bevy_remote/src/builtin_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ use bevy_ecs::{
lifecycle::RemovedComponentEntity,
message::MessageCursor,
query::QueryBuilder,
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
reflect::{AppTypeRegistry, ReflectComponent, ReflectEvent, ReflectResource},
system::{In, Local},
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
world::{EntityRef, EntityWorldMut, FilteredEntityRef, Mut, World},
};
use bevy_log::warn_once;
use bevy_platform::collections::HashMap;
use bevy_reflect::{
serde::{ReflectSerializer, TypedReflectDeserializer},
GetPath, PartialReflect, TypeRegistration, TypeRegistry,
DynamicStruct, GetPath, PartialReflect, TypeRegistration, TypeRegistry,
};
use serde::{de::DeserializeSeed as _, Deserialize, Serialize};
use serde::{de::DeserializeSeed as _, de::IntoDeserializer, Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::{
Expand Down Expand Up @@ -83,6 +83,9 @@ pub const BRP_MUTATE_RESOURCE_METHOD: &str = "world.mutate_resources";
/// The method path for a `world.list_resources` request.
pub const BRP_LIST_RESOURCES_METHOD: &str = "world.list_resources";

/// The method path for a `world.trigger_event` request.
pub const BRP_TRIGGER_EVENT_METHOD: &str = "world.trigger_event";

/// The method path for a `registry.schema` request.
pub const BRP_REGISTRY_SCHEMA_METHOD: &str = "registry.schema";

Expand Down Expand Up @@ -299,6 +302,19 @@ pub struct BrpMutateResourcesParams {
pub value: Value,
}

/// `world.trigger_event`:
///
/// The server responds with a null.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct BrpTriggerEventParams {
/// The [full path] of the event to trigger.
///
/// [full path]: bevy_reflect::TypePath::type_path
pub event: String,
/// The serialized value of the event to be triggered, if any.
pub value: Option<Value>,
}

/// Describes the data that is to be fetched in a query.
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
pub struct BrpQuery {
Expand Down Expand Up @@ -1348,6 +1364,44 @@ pub fn process_remote_list_components_watching_request(
}
}

/// Handles a `world.trigger_event` request coming from a client.
pub fn process_remote_trigger_event_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpTriggerEventParams { event, value } = parse_some(params)?;

world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
let registry = registry.read();

let Some(registration) = registry.get_with_type_path(&event) else {
return Err(BrpError::resource_error(format!(
"Unknown event type: `{event}`"
)));
};
let Some(reflect_event) = registration.data::<ReflectEvent>() else {
return Err(BrpError::resource_error(format!(
"Event `{event}` is not reflectable"
)));
};

if let Some(payload) = value {
let payload: Box<dyn PartialReflect> =
TypedReflectDeserializer::new(registration, &registry)
.deserialize(payload.into_deserializer())
.map_err(|err| {
BrpError::resource_error(format!("{event} is invalid: {err}"))
})?;
reflect_event.trigger(world, &*payload, &registry);
} else {
let payload = DynamicStruct::default();
reflect_event.trigger(world, &payload, &registry);
}

Ok(Value::Null)
})
}

/// Handles a `registry.schema` request (list all registry types in form of schema) coming from a client.
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
let filter: BrpJsonSchemaQueryFilter = match params {
Expand Down Expand Up @@ -1628,6 +1682,11 @@ mod tests {
}

use super::*;
use bevy_ecs::{
component::Component, event::Event, observer::On, resource::Resource, system::ResMut,
};
use bevy_reflect::Reflect;
use serde_json::Value::Null;

#[test]
fn insert_reflect_only_component() {
Expand Down Expand Up @@ -1659,6 +1718,37 @@ mod tests {
insert_reflected_components(e, deserialized_components).expect("FAIL");
}

#[test]
fn trigger_reflect_only_event() {
#[derive(Event, Reflect)]
#[reflect(Event)]
struct Pass;

#[derive(Resource)]
struct TestResult(pub bool);

let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<Pass>();
}
let mut world = World::new();
world.add_observer(move |_event: On<Pass>, mut result: ResMut<TestResult>| result.0 = true);
world.insert_resource(TestResult(false));
world.insert_resource(atr);

let params = serde_json::to_value(&BrpTriggerEventParams {
event: "bevy_remote::builtin_methods::tests::Pass".to_owned(),
value: None,
})
.expect("FAIL");
assert_eq!(
process_remote_trigger_event_request(In(Some(params)), &mut world),
Ok(Null)
);
assert!(world.resource::<TestResult>().0);
}

#[test]
fn serialization_tests() {
test_serialize_deserialize(BrpQueryRow {
Expand Down
16 changes: 15 additions & 1 deletion crates/bevy_remote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,16 @@
//!
//! `result`: An array of [fully-qualified type names] of registered resource types.
//!
//! ### `world.trigger_event`
//!
//! Triggers an event.
//!
//! `params`:
//! - `event`: The [fully-qualified type name] of the event to trigger.
//! - `value`: The value of the event to trigger.
//!
//! `result`: null.
//!
//! ### `registry.schema`
//!
//! Retrieve schema information about registered types in the Bevy app's type registry.
Expand Down Expand Up @@ -669,6 +679,10 @@ impl Default for RemotePlugin {
builtin_methods::BRP_LIST_RESOURCES_METHOD,
builtin_methods::process_remote_list_resources_request,
)
.with_method(
builtin_methods::BRP_TRIGGER_EVENT_METHOD,
builtin_methods::process_remote_trigger_event_request,
)
.with_method(
builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,
builtin_methods::export_registry_types,
Expand Down Expand Up @@ -915,7 +929,7 @@ impl From<BrpResult> for BrpPayload {
}

/// An error a request might return.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpError {
/// Defines the general type of the error.
pub code: i16,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_remote/src/schemas/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Module with schemas used for various BRP endpoints
use bevy_ecs::{
reflect::{ReflectComponent, ReflectResource},
reflect::{ReflectComponent, ReflectEvent, ReflectResource},
resource::Resource,
};
use bevy_platform::collections::HashMap;
Expand Down Expand Up @@ -29,6 +29,7 @@ impl Default for SchemaTypesMetadata {
};
data_types.map_type_data::<ReflectComponent>("Component");
data_types.map_type_data::<ReflectResource>("Resource");
data_types.map_type_data::<ReflectEvent>("Event");
data_types.map_type_data::<ReflectDefault>("Default");
#[cfg(feature = "bevy_asset")]
data_types.map_type_data::<bevy_asset::ReflectAsset>("Asset");
Expand Down
Loading