use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldAccessGuard};
use crate::error::InteropError;
use ::{
bevy_app::{App, Plugin},
bevy_ecs::component::{
Component, ComponentCloneBehavior, ComponentDescriptor, Mutable, StorageType,
},
bevy_reflect::{GetTypeRegistration, Reflect, prelude::ReflectDefault},
};
use bevy_ecs::resource::Resource;
use bevy_platform::collections::HashMap;
use parking_lot::RwLock;
use std::{alloc::Layout, mem::needs_drop, sync::Arc};
#[derive(Reflect, Clone, Default)]
#[reflect(Default)]
pub struct DynamicComponent {
data: ScriptValue,
}
pub struct DynamicComponentInfo {
pub name: String,
pub registration: ScriptComponentRegistration,
}
impl Component for DynamicComponent {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
}
#[derive(Clone, Resource, Default)]
pub struct AppScriptComponentRegistry(pub Arc<RwLock<ScriptComponentRegistry>>);
#[profiling::all_functions]
impl AppScriptComponentRegistry {
pub fn read(&self) -> parking_lot::RwLockReadGuard<'_, ScriptComponentRegistry> {
self.0.read()
}
pub fn write(&self) -> parking_lot::RwLockWriteGuard<'_, ScriptComponentRegistry> {
self.0.write()
}
}
#[derive(Default)]
pub struct ScriptComponentRegistry {
components: HashMap<String, DynamicComponentInfo>,
}
#[profiling::all_functions]
impl ScriptComponentRegistry {
pub fn register(&mut self, info: DynamicComponentInfo) {
self.components.insert(info.name.clone(), info);
}
pub fn get(&self, name: &str) -> Option<&DynamicComponentInfo> {
self.components.get(name)
}
}
#[profiling::all_functions]
impl WorldAccessGuard<'_> {
pub fn register_script_component(
&self,
component_name: String,
) -> Result<ScriptComponentRegistration, InteropError> {
let component_registry = self.component_registry();
let component_registry_read = component_registry.read();
if component_registry_read.get(&component_name).is_some() {
return Err(InteropError::unsupported_operation(
None,
None,
"script registered component already exists",
));
}
let component_id = self.with_global_access(|w| {
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
component_name.clone(),
DynamicComponent::STORAGE_TYPE,
Layout::new::<DynamicComponent>(),
needs_drop::<DynamicComponent>().then_some(|x| x.drop_as::<DynamicComponent>()),
true,
ComponentCloneBehavior::Default,
None,
)
};
w.register_component_with_descriptor(descriptor)
})?;
drop(component_registry_read);
let mut component_registry = component_registry.write();
let registration = ScriptComponentRegistration::new(
ScriptTypeRegistration::new(Arc::new(
<DynamicComponent as GetTypeRegistration>::get_type_registration(),
)),
component_id,
);
let component_info = DynamicComponentInfo {
name: component_name.clone(),
registration: registration.clone(),
};
component_registry.register(component_info);
Ok(registration)
}
}
pub struct DynamicScriptComponentPlugin;
impl Plugin for DynamicScriptComponentPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AppScriptComponentRegistry>()
.register_type::<DynamicComponent>();
}
}
#[cfg(test)]
mod test {
use bevy_ecs::world::World;
use super::*;
#[test]
fn test_script_component() {
let mut world = World::new();
let registration = {
let guard = WorldAccessGuard::new_exclusive(&mut world);
guard
.register_script_component("ScriptTest".to_string())
.unwrap()
};
let registry = world.get_resource::<AppScriptComponentRegistry>().unwrap();
let registry = registry.read();
let info = registry.get("ScriptTest").unwrap();
assert_eq!(info.registration.component_id, registration.component_id);
assert_eq!(info.name, "ScriptTest");
let component = world
.components()
.get_info(info.registration.component_id)
.unwrap();
assert_eq!(component.name(), "ScriptTest".into());
}
}