Skip to content

Commit 247aec6

Browse files
committed
make component retrieval work
1 parent 2165c43 commit 247aec6

File tree

4 files changed

+144
-40
lines changed

4 files changed

+144
-40
lines changed

assets/tests/register_new_component/new_component_can_be_retrieved.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
local NewComponent = world.register_new_component("NewComponent")
1+
local NewComponent = world.register_new_component("ScriptComponentA")
22
assert(NewComponent ~= nil, "Failed to register new component")
33
assert(NewComponent:short_name() == "ScriptComponent", "Unexpected component type")
44

crates/bevy_mod_scripting_core/src/bindings/query.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
//! Utilities for querying the world.
22
3-
use super::{with_global_access, ReflectReference, WorldAccessGuard};
3+
use super::{with_global_access, ReflectReference, ScriptComponent, WorldAccessGuard, WorldGuard};
44
use crate::error::InteropError;
55
use bevy::{
66
ecs::{
77
component::ComponentId,
88
entity::Entity,
99
query::{QueryData, QueryState},
10+
reflect::ReflectComponent,
1011
world::World,
1112
},
1213
prelude::{EntityRef, QueryBuilder},
14+
ptr::OwningPtr,
1315
reflect::{ParsedPath, Reflect, TypeRegistration},
1416
};
15-
use std::{any::TypeId, collections::VecDeque, sync::Arc};
17+
use std::{any::TypeId, collections::VecDeque, ptr::NonNull, sync::Arc};
1618

1719
/// A reference to a type which is not a `Resource` or `Component`.
1820
///
@@ -27,9 +29,13 @@ pub struct ScriptTypeRegistration {
2729
/// A reference to a component type's reflection registration.
2830
///
2931
/// In general think of this as a handle to a type.
32+
///
33+
/// Not to be confused with script registered dynamic components, although this can point to a script registered component.
3034
pub struct ScriptComponentRegistration {
3135
pub(crate) registration: ScriptTypeRegistration,
3236
pub(crate) component_id: ComponentId,
37+
/// whether this is a component registered BY a script
38+
pub(crate) is_dynamic_script_component: bool,
3339
}
3440

3541
#[derive(Clone, Reflect, Debug)]
@@ -100,6 +106,8 @@ impl ScriptComponentRegistration {
100106
/// Creates a new [`ScriptComponentRegistration`] from a [`ScriptTypeRegistration`] and a [`ComponentId`].
101107
pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self {
102108
Self {
109+
is_dynamic_script_component: registration.type_id()
110+
== std::any::TypeId::of::<ScriptComponent>(),
103111
registration,
104112
component_id,
105113
}
@@ -120,6 +128,70 @@ impl ScriptComponentRegistration {
120128
pub fn into_type_registration(self) -> ScriptTypeRegistration {
121129
self.registration
122130
}
131+
132+
/// Inserts an instance of this component into the given entity
133+
///
134+
/// Requires whole world access
135+
pub fn insert_into_entity(
136+
&self,
137+
world: WorldGuard,
138+
entity: Entity,
139+
instance: Box<dyn Reflect>,
140+
) -> Result<(), InteropError> {
141+
if self.is_dynamic_script_component {
142+
// if dynamic we already know the type i.e. `ScriptComponent`
143+
// so we can just insert it
144+
145+
world.with_global_access(|world| {
146+
let mut entity = world
147+
.get_entity_mut(entity)
148+
.map_err(|_| InteropError::missing_entity(entity))?;
149+
let mut cast = instance.downcast::<ScriptComponent>().map_err(|v| {
150+
InteropError::type_mismatch(TypeId::of::<ScriptComponent>(), Some(v.type_id()))
151+
})?;
152+
let ptr = (cast.as_mut() as *mut ScriptComponent).cast();
153+
// Safety: cannot be null as we just created it from a valid reference
154+
let non_null_ptr = unsafe { NonNull::new_unchecked(ptr) };
155+
// Safety:
156+
// - we know the type is ScriptComponent, as we just created the pointer
157+
// - the box will stay valid for the life of this function, and we do not return the ptr
158+
// - pointer is alligned correctly
159+
let owning_ptr = unsafe { OwningPtr::new(non_null_ptr) };
160+
// Safety:
161+
// - Owning Ptr is valid as we just created it
162+
// - TODO: do we need to check if ComponentId is from this world? How?
163+
unsafe { entity.insert_by_id(self.component_id, owning_ptr) };
164+
Ok(())
165+
})?
166+
} else {
167+
let component_data = self
168+
.type_registration()
169+
.type_registration()
170+
.data::<ReflectComponent>()
171+
.ok_or_else(|| {
172+
InteropError::missing_type_data(
173+
self.registration.type_id(),
174+
"ReflectComponent".to_owned(),
175+
)
176+
})?;
177+
178+
bevy::log::debug!("found component data");
179+
180+
// TODO: this shouldn't need entire world access it feels
181+
let type_registry = world.type_registry();
182+
world.with_global_access(|world| {
183+
let mut entity = world
184+
.get_entity_mut(entity)
185+
.map_err(|_| InteropError::missing_entity(entity))?;
186+
{
187+
let registry = type_registry.read();
188+
bevy::log::debug!("inserting component instance using component data");
189+
component_data.insert(&mut entity, instance.as_partial_reflect(), &registry);
190+
}
191+
Ok(())
192+
})?
193+
}
194+
}
123195
}
124196

125197
impl std::fmt::Debug for ScriptTypeRegistration {

crates/bevy_mod_scripting_core/src/bindings/script_component.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use bevy::{
66
app::{App, Plugin},
77
ecs::{
88
component::{Component, ComponentDescriptor, StorageType},
9-
reflect::ReflectComponent,
109
system::Resource,
1110
},
1211
reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect},
@@ -17,7 +16,7 @@ use std::{alloc::Layout, mem::needs_drop, sync::Arc};
1716

1817
/// A dynamic script component, with script set
1918
#[derive(Reflect, Clone, Default)]
20-
#[reflect(Component, Default)]
19+
#[reflect(Default)]
2120
pub struct ScriptComponent {
2221
data: ScriptValue,
2322
}
@@ -74,6 +73,14 @@ impl WorldAccessGuard<'_> {
7473
&self,
7574
component_name: String,
7675
) -> Result<ScriptComponentRegistration, InteropError> {
76+
if !component_name.starts_with("Script") {
77+
return Err(InteropError::unsupported_operation(
78+
None,
79+
None,
80+
"script registered component name must start with 'Script'",
81+
));
82+
}
83+
7784
let component_id = self.with_global_access(|w| {
7885
let descriptor = unsafe {
7986
// Safety: same safety guarantees as ComponentDescriptor::new
@@ -127,3 +134,52 @@ impl Plugin for DynamicScriptComponentPlugin {
127134
.register_type::<ScriptComponent>();
128135
}
129136
}
137+
138+
// #[cfg(test)]
139+
// mod test {
140+
// use std::ptr::NonNull;
141+
142+
// use super::*;
143+
// use bevy::{ecs::world::World, ptr::OwningPtr};
144+
145+
// #[test]
146+
// fn test_script_component() {
147+
// let mut world = World::new();
148+
// let component_name = "MyScriptComponent";
149+
150+
// #[derive(Reflect, Component)]
151+
// struct UnderlyingComponent;
152+
153+
// // initialize component descriptor dynamically
154+
// let descriptor = unsafe {
155+
// // Safety: same safety guarantees as ComponentDescriptor::new
156+
// // we know the type in advance
157+
// // we only use this method to name the component
158+
// ComponentDescriptor::new_with_layout(
159+
// component_name,
160+
// UnderlyingComponent::STORAGE_TYPE,
161+
// Layout::new::<UnderlyingComponent>(),
162+
// needs_drop::<UnderlyingComponent>()
163+
// .then_some(|x| x.drop_as::<UnderlyingComponent>()),
164+
// )
165+
// };
166+
167+
// // register with the world
168+
// let component_id = world.register_component_with_descriptor(descriptor);
169+
170+
// // insert into the entity
171+
// let entity = world.spawn_empty().id();
172+
// let mut entity = world.entity_mut(entity);
173+
174+
// let value = Box::new(UnderlyingComponent);
175+
// let value_ref = Box::into_raw(value).cast::<u8>();
176+
// let ptr = unsafe { OwningPtr::new(NonNull::new(value_ref).unwrap()) };
177+
// unsafe { entity.insert_by_id(component_id, ptr) };
178+
179+
// // check it gets inserted
180+
// assert!(
181+
// entity.contains_id(component_id),
182+
// "entity does not contain freshly inserted component"
183+
// )
184+
// }
185+
// }

crates/bevy_mod_scripting_core/src/bindings/world.rs

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::{
2424
use crate::{
2525
bindings::{
2626
function::{from::FromScript, from_ref::FromScriptRef},
27-
with_access_read, with_access_write,
27+
with_access_read, with_access_write, ScriptComponent,
2828
},
2929
error::InteropError,
3030
reflection_extensions::PartialReflectExt,
@@ -928,20 +928,6 @@ impl WorldAccessGuard<'_> {
928928
entity: Entity,
929929
registration: ScriptComponentRegistration,
930930
) -> Result<(), InteropError> {
931-
// let cell = self.as_unsafe_world_cell()?;
932-
let component_data = registration
933-
.type_registration()
934-
.type_registration()
935-
.data::<ReflectComponent>()
936-
.ok_or_else(|| {
937-
InteropError::missing_type_data(
938-
registration.registration.type_id(),
939-
"ReflectComponent".to_owned(),
940-
)
941-
})?;
942-
943-
bevy::log::debug!("found component data");
944-
945931
// we look for ReflectDefault or ReflectFromWorld data then a ReflectComponent data
946932
let instance = if let Some(default_td) = registration
947933
.type_registration()
@@ -965,20 +951,7 @@ impl WorldAccessGuard<'_> {
965951
));
966952
};
967953

968-
// TODO: this shouldn't need entire world access it feels
969-
self.with_global_access(|world| {
970-
let type_registry = self.type_registry();
971-
972-
let mut entity = world
973-
.get_entity_mut(entity)
974-
.map_err(|_| InteropError::missing_entity(entity))?;
975-
{
976-
let registry = type_registry.read();
977-
bevy::log::debug!("inserting component instance using component data");
978-
component_data.insert(&mut entity, instance.as_partial_reflect(), &registry);
979-
}
980-
Ok(())
981-
})?
954+
registration.insert_into_entity(self.clone(), entity, instance)
982955
}
983956

984957
/// insert the component into the entity
@@ -1038,14 +1011,17 @@ impl WorldAccessGuard<'_> {
10381011
component_info
10391012
);
10401013

1041-
if entity.contains_id(component_id)
1042-
|| component_info
1043-
.type_id()
1044-
.is_some_and(|t| entity.contains_type_id(t))
1045-
{
1014+
if entity.contains_id(component_id) {
1015+
let type_id = component_info.type_id().or_else(|| {
1016+
// check its a script component
1017+
component_info
1018+
.name()
1019+
.starts_with("Script")
1020+
.then_some(TypeId::of::<ScriptComponent>())
1021+
});
10461022
Ok(Some(ReflectReference {
10471023
base: ReflectBaseType {
1048-
type_id: component_info.type_id().ok_or_else(|| {
1024+
type_id: type_id.ok_or_else(|| {
10491025
InteropError::unsupported_operation(
10501026
None,
10511027
None,

0 commit comments

Comments
 (0)