use bevy::ecs::reflect::ReflectComponent;
use bevy::ecs::world::EntityRef;
use bevy::ecs::world::EntityWorldMut;
use bevy::prelude::Reflect;
use bevy::reflect::PartialReflect;
use bevy::reflect::TypeRegistry;
use thiserror::Error;
#[derive(Default)]
pub struct Capabilities(Vec<Box<dyn Reflect>>);
impl Capabilities {
#[must_use]
pub fn new() -> Self { Self::default() }
pub fn add(&mut self, capability: impl Reflect) { self.0.push(Box::new(capability)); }
#[must_use]
pub fn with(mut self, capability: impl Reflect) -> Self {
self.add(capability);
self
}
pub(crate) fn declarations(&self) -> impl Iterator<Item = &dyn Reflect> {
self.0.iter().map(AsRef::as_ref)
}
}
pub(crate) fn attach_declarations<'a>(
entity: &mut EntityWorldMut,
type_registry: &TypeRegistry,
declarations: impl IntoIterator<Item = &'a dyn Reflect>,
) -> Result<(), CapabilityAttachError> {
let resolved = declarations
.into_iter()
.map(|capability| {
reflect_component_for(capability.as_partial_reflect(), type_registry)
.map(|reflect_component| (capability, reflect_component))
})
.collect::<Result<Vec<_>, _>>()?;
for (capability, reflect_component) in resolved {
let already_attached = reflect_component
.reflect(EntityRef::from(&*entity))
.is_some_and(|attached| {
attached.reflect_partial_eq(capability.as_partial_reflect()) == Some(true)
});
if already_attached {
continue;
}
reflect_component.insert(entity, capability.as_partial_reflect(), type_registry);
}
Ok(())
}
pub(crate) fn detach_declarations<'a>(
entity: &mut EntityWorldMut,
type_registry: &TypeRegistry,
declarations: impl IntoIterator<Item = &'a dyn Reflect>,
) -> Result<(), CapabilityAttachError> {
let resolved = declarations
.into_iter()
.map(|capability| reflect_component_for(capability.as_partial_reflect(), type_registry))
.collect::<Result<Vec<_>, _>>()?;
for reflect_component in resolved {
if reflect_component
.reflect(EntityRef::from(&*entity))
.is_some()
{
reflect_component.remove(entity);
}
}
Ok(())
}
pub(crate) fn reflect_component_for<'a>(
value: &dyn PartialReflect,
type_registry: &'a TypeRegistry,
) -> Result<&'a ReflectComponent, CapabilityAttachError> {
let type_path = value.reflect_type_path().to_owned();
let Some(type_id) = value.try_as_reflect().map(|value| value.as_any().type_id()) else {
return Err(CapabilityAttachError::NotConcrete { type_path });
};
if !type_registry.contains(type_id) {
return Err(CapabilityAttachError::Unregistered { type_path });
}
type_registry
.get_type_data::<ReflectComponent>(type_id)
.ok_or(CapabilityAttachError::NotAComponent { type_path })
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub(crate) enum CapabilityAttachError {
#[error("capability `{type_path}` is not registered")]
Unregistered {
type_path: String,
},
#[error("capability `{type_path}` is a dynamic value with no concrete type")]
NotConcrete {
type_path: String,
},
#[error("capability `{type_path}` is not a reflected component")]
NotAComponent {
type_path: String,
},
}
#[cfg(test)]
mod tests {
use bevy::ecs::component::Component;
use bevy::ecs::reflect::ReflectComponent;
use bevy::ecs::world::World;
use bevy::prelude::Reflect;
use bevy::reflect::TypeRegistry;
use super::Capabilities;
use super::CapabilityAttachError;
use super::attach_declarations;
#[derive(Component, Debug, PartialEq, Reflect)]
#[reflect(Component, PartialEq)]
struct ChannelCount(u8);
#[derive(Component, Reflect)]
#[reflect(Component)]
struct UnregisteredCapability;
#[derive(Reflect)]
struct RegisteredNonComponent;
#[test]
fn attach_inserts_registered_capability_through_reflection() -> Result<(), CapabilityAttachError>
{
let capabilities = Capabilities::new().with(ChannelCount(2));
let mut type_registry = TypeRegistry::default();
type_registry.register::<ChannelCount>();
let mut world = World::new();
let mut entity = world.spawn_empty();
attach_declarations(&mut entity, &type_registry, capabilities.declarations())?;
assert_eq!(entity.get::<ChannelCount>(), Some(&ChannelCount(2)));
Ok(())
}
#[test]
fn attach_rejects_unregistered_capability() {
let capabilities = Capabilities::new().with(UnregisteredCapability);
let type_registry = TypeRegistry::default();
let mut world = World::new();
let mut entity = world.spawn_empty();
assert!(matches!(
attach_declarations(&mut entity, &type_registry, capabilities.declarations()),
Err(CapabilityAttachError::Unregistered { .. })
));
}
#[test]
fn a_declaration_naming_one_unregistered_type_attaches_none_of_it() {
let capabilities = Capabilities::new()
.with(ChannelCount(2))
.with(UnregisteredCapability);
let mut type_registry = TypeRegistry::default();
type_registry.register::<ChannelCount>();
let mut world = World::new();
let mut entity = world.spawn_empty();
assert!(matches!(
attach_declarations(&mut entity, &type_registry, capabilities.declarations()),
Err(CapabilityAttachError::Unregistered { .. })
));
assert_eq!(entity.get::<ChannelCount>(), None);
}
#[test]
fn attach_rejects_registered_capability_without_component_reflection() {
let capabilities = Capabilities::new().with(RegisteredNonComponent);
let mut type_registry = TypeRegistry::default();
type_registry.register::<RegisteredNonComponent>();
let mut world = World::new();
let mut entity = world.spawn_empty();
assert!(matches!(
attach_declarations(&mut entity, &type_registry, capabilities.declarations()),
Err(CapabilityAttachError::NotAComponent { .. })
));
}
}