use std::{alloc::Layout, any::TypeId};
use anyhow::{Result, anyhow};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::{ComponentDescriptor, ComponentId},
prelude::*,
reflect::ReflectCommandExt,
world::{FilteredEntityMut, FilteredEntityRef},
};
use bevy_platform::collections::HashMap;
use bevy_reflect::{Reflect, ReflectFromPtr};
use crate::serialize::CodecResource;
pub type TypePath = String;
#[derive(Default, Clone, Debug, Resource, Deref, DerefMut)]
pub struct WasmComponentRegistry(HashMap<TypePath, ComponentId>);
#[derive(Component, Reflect)]
pub struct WasmComponent {
pub serialized_value: Vec<u8>,
}
struct InsertWasmComponent {
component: WasmComponent,
entity: Entity,
type_path: String,
}
impl Command for InsertWasmComponent {
fn apply(self, world: &mut World) {
let component_id = get_wasm_component_id(&self.type_path, world);
let mut commands = world.commands();
let mut entity_commands = commands.entity(self.entity);
unsafe { entity_commands.insert_by_id(component_id, self.component) };
}
}
pub(crate) fn insert_component(
commands: &mut Commands,
type_registry: &AppTypeRegistry,
codec: &CodecResource,
entity: Entity,
type_path: String,
serialized_value: Vec<u8>,
) -> Result<()> {
let type_registry = type_registry.read();
if let Some(type_registration) = type_registry.get_with_type_path(&type_path) {
let output = codec.decode_reflect(&serialized_value, type_registration, &type_registry)?;
commands.entity(entity).insert_reflect(output);
}
else {
commands.queue(InsertWasmComponent {
component: WasmComponent { serialized_value },
entity,
type_path,
});
}
Ok(())
}
pub(crate) fn remove_component(
commands: &mut Commands,
wasm_registry: &WasmComponentRegistry,
entity: Entity,
type_path: String,
) -> Result<()> {
if let Some(component_id) = wasm_registry.0.get(&type_path) {
commands.entity(entity).remove_by_id(*component_id);
}
else {
commands.entity(entity).remove_reflect(type_path);
}
Ok(())
}
#[derive(Clone)]
pub(crate) struct ComponentRef {
component_id: ComponentId,
type_id: Option<TypeId>,
type_path: TypePath,
}
impl ComponentRef {
pub(crate) fn new(type_path: &str, world: &mut World) -> Result<Self> {
let type_registry = world
.get_resource::<AppTypeRegistry>()
.expect("there to be an AppTypeRegistry")
.read();
if let Some(type_registration) = type_registry.get_with_type_path(type_path) {
let type_id = type_registration.type_id();
let component_id = world
.components()
.get_id(type_id)
.ok_or(anyhow!("{type_path} is not a component"))?;
Ok(Self {
component_id,
type_id: Some(type_id),
type_path: type_path.to_string(),
})
}
else {
drop(type_registry);
let component_id = get_wasm_component_id(type_path, world);
Ok(Self {
component_id,
type_id: None,
type_path: type_path.to_string(),
})
}
}
pub(crate) fn component_id(&self) -> ComponentId {
self.component_id
}
pub(crate) fn type_path(&self) -> &str {
&self.type_path
}
}
fn get_wasm_component_id(type_path: &str, world: &mut World) -> ComponentId {
let component_registry = world.get_resource_or_init::<WasmComponentRegistry>();
if let Some(id) = component_registry.get(type_path) {
*id
}
else {
let type_path = type_path.to_string();
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
type_path.clone(),
WasmComponent::STORAGE_TYPE,
Layout::new::<WasmComponent>(),
Some(|ptr| {
ptr.drop_as::<WasmComponent>();
}),
true,
WasmComponent::clone_behavior(),
None,
)
};
let id = world.register_component_with_descriptor(descriptor);
let mut component_registry = world
.get_resource_mut::<WasmComponentRegistry>()
.expect("this method initialized it");
component_registry.insert(type_path, id);
id
}
}
pub(crate) fn get_component(
entity: &FilteredEntityRef,
component: &ComponentRef,
type_registry: &AppTypeRegistry,
codec: &CodecResource,
) -> Result<Vec<u8>> {
let val = entity
.get_by_id(component.component_id)
.expect("to be able to find this component id on the entity");
if let Some(type_id) = component.type_id {
let type_registry = type_registry.read();
let type_registration = type_registry
.get(type_id)
.expect("ComponentRef type_id be registered");
let reflect_from_ptr = type_registration
.data::<ReflectFromPtr>()
.expect("ReflectFromPtr to be registered");
let reflect = unsafe { reflect_from_ptr.as_reflect(val) };
let value = codec.encode_reflect(reflect, &type_registry)?;
Ok(value)
}
else {
let value = unsafe { val.deref::<WasmComponent>() };
Ok(value.serialized_value.clone())
}
}
pub(crate) fn set_component(
entity: &mut FilteredEntityMut,
component_ref: &ComponentRef,
serialized_value: Vec<u8>,
type_registry: &AppTypeRegistry,
codec: &CodecResource,
) -> Result<()> {
let mut val = entity
.get_mut_by_id(component_ref.component_id)
.expect("to be able to find this component id on the entity");
if let Some(type_id) = component_ref.type_id {
let type_registry = type_registry.read();
let type_registration = type_registry
.get(type_id)
.expect("ComponentRef type_id be registered");
let reflect_from_ptr = type_registration
.data::<ReflectFromPtr>()
.expect("ReflectFromPtr to be registered");
let boxed_dyn_reflect =
codec.decode_reflect(&serialized_value, type_registration, &type_registry)?;
let reflect = unsafe { reflect_from_ptr.as_reflect_mut(val.as_mut()) };
reflect.apply(boxed_dyn_reflect.as_partial_reflect());
Ok(())
}
else {
let component = unsafe { val.as_mut().deref_mut::<WasmComponent>() };
component.serialized_value = serialized_value;
Ok(())
}
}
pub(crate) fn with_component_ref<R>(
entity: &FilteredEntityRef,
component_ref: &ComponentRef,
type_registry: &AppTypeRegistry,
f: impl FnOnce(&dyn Reflect) -> Result<R>,
) -> Result<R> {
let val = entity
.get_by_id(component_ref.component_id)
.expect("to be able to find this component id on the entity");
let Some(type_id) = component_ref.type_id else {
return Err(anyhow!(
"Component {} does not support method invocation",
component_ref.type_path
));
};
let type_registry = type_registry.read();
let type_registration = type_registry
.get(type_id)
.expect("ComponentRef type_id be registered");
let reflect_from_ptr = type_registration
.data::<ReflectFromPtr>()
.expect("ReflectFromPtr to be registered");
let reflect = unsafe { reflect_from_ptr.as_reflect(val) };
f(reflect)
}
pub(crate) fn with_component_mut<R>(
entity: &mut FilteredEntityMut,
component_ref: &ComponentRef,
type_registry: &AppTypeRegistry,
f: impl FnOnce(&mut dyn Reflect) -> Result<R>,
) -> Result<R> {
let mut val = entity
.get_mut_by_id(component_ref.component_id)
.expect("to be able to find this component id on the entity");
let Some(type_id) = component_ref.type_id else {
return Err(anyhow!(
"Component {} does not support method invocation",
component_ref.type_path
));
};
let type_registry = type_registry.read();
let type_registration = type_registry
.get(type_id)
.expect("ComponentRef type_id be registered");
let reflect_from_ptr = type_registration
.data::<ReflectFromPtr>()
.expect("ReflectFromPtr to be registered");
let reflect = unsafe { reflect_from_ptr.as_reflect_mut(val.as_mut()) };
f(reflect)
}