use super::{
AppReflectAllocator, AppScriptComponentRegistry, ReflectBase, ReflectBaseType,
ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration,
ScriptTypeRegistration, Union,
access_map::{
AccessCount, AccessMapKey, AnyAccessMap, DynamicSystemMeta, ReflectAccessId,
ReflectAccessKind, SubsetAccessMap,
},
function::{
namespace::Namespace,
script_function::{AppScriptFunctionRegistry, DynamicScriptFunction, FunctionCallContext},
},
schedule::AppScheduleRegistry,
script_value::ScriptValue,
with_global_access,
};
use crate::{
error::InteropError,
function::{from::FromScript, from_ref::FromScriptRef},
reflection_extensions::PartialReflectExt,
with_access_read, with_access_write,
};
use ::{
bevy_app::AppExit,
bevy_asset::{AssetServer, Handle, LoadState},
bevy_ecs::{
component::{Component, ComponentId},
entity::Entity,
prelude::Resource,
reflect::{AppTypeRegistry, ReflectFromWorld, ReflectResource},
system::Commands,
world::{CommandQueue, Mut, World, unsafe_world_cell::UnsafeWorldCell},
},
bevy_reflect::{
DynamicEnum, DynamicStruct, DynamicTuple, DynamicTupleStruct, DynamicVariant,
PartialReflect, TypeRegistryArc, std_traits::ReflectDefault,
},
};
use bevy_asset::AssetPath;
use bevy_ecs::{
component::Mutable,
hierarchy::{ChildOf, Children},
system::Command,
world::WorldId,
};
use bevy_mod_scripting_asset::ScriptAsset;
use bevy_mod_scripting_display::GetTypeInfo;
use bevy_mod_scripting_script::ScriptAttachment;
use bevy_platform::collections::HashMap;
use bevy_reflect::{TypeInfo, VariantInfo};
use bevy_system_reflection::ReflectSchedule;
use std::{
any::{Any, TypeId},
borrow::Cow,
cell::RefCell,
fmt::Debug,
rc::Rc,
sync::{Arc, atomic::AtomicBool},
};
pub type WorldGuard<'w> = WorldAccessGuard<'w>;
pub type WorldGuardRef<'w> = &'w WorldAccessGuard<'w>;
#[derive(Clone, Debug)]
pub struct WorldAccessGuard<'w> {
pub(crate) inner: Rc<WorldAccessGuardInner<'w>>,
invalid: Rc<AtomicBool>,
}
impl WorldAccessGuard<'_> {
pub fn id(&self) -> WorldId {
self.inner.cell.id()
}
}
pub(crate) struct WorldAccessGuardInner<'w> {
cell: UnsafeWorldCell<'w>,
pub(crate) accesses: AnyAccessMap,
type_registry: TypeRegistryArc,
allocator: AppReflectAllocator,
function_registry: AppScriptFunctionRegistry,
schedule_registry: AppScheduleRegistry,
script_component_registry: AppScriptComponentRegistry,
}
impl std::fmt::Debug for WorldAccessGuardInner<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WorldAccessGuardInner").finish()
}
}
#[profiling::all_functions]
impl WorldAccessGuard<'static> {
pub(crate) fn shorten_lifetime<'w>(self) -> WorldGuard<'w> {
unsafe { std::mem::transmute(self) }
}
}
#[profiling::all_functions]
impl<'w> WorldAccessGuard<'w> {
fn scope(&self) -> Self {
let mut new_guard = self.clone();
new_guard.invalid = Rc::new(
new_guard
.invalid
.load(std::sync::atomic::Ordering::Relaxed)
.into(),
);
new_guard
}
fn is_valid(&self) -> bool {
!self.invalid.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn invalidate(&self) {
self.invalid
.store(true, std::sync::atomic::Ordering::Relaxed);
}
pub fn with_static_guard<O>(
world: &'w mut World,
f: impl FnOnce(WorldGuard<'static>) -> O,
) -> O {
let guard = WorldAccessGuard::new_exclusive(world);
let static_guard: WorldAccessGuard<'static> = unsafe { std::mem::transmute(guard) };
let o = f(static_guard.clone());
static_guard.invalidate();
o
}
pub fn with_existing_static_guard<O>(
guard: WorldAccessGuard<'w>,
f: impl FnOnce(WorldGuard<'static>) -> O,
) -> O {
let static_guard: WorldAccessGuard<'static> = unsafe { std::mem::transmute(guard.scope()) };
let o = f(static_guard.clone());
static_guard.invalidate();
o
}
pub unsafe fn new_non_exclusive(
world: UnsafeWorldCell<'w>,
subset: impl IntoIterator<Item = ReflectAccessId>,
type_registry: AppTypeRegistry,
allocator: AppReflectAllocator,
function_registry: AppScriptFunctionRegistry,
schedule_registry: AppScheduleRegistry,
script_component_registry: AppScriptComponentRegistry,
) -> Self {
Self {
inner: Rc::new(WorldAccessGuardInner {
cell: world,
accesses: AnyAccessMap::SubsetAccessMap(SubsetAccessMap::new(
subset,
|id| ReflectAccessId::from_index(id).kind == ReflectAccessKind::Allocation,
)),
type_registry: type_registry.0,
allocator,
function_registry,
schedule_registry,
script_component_registry,
}),
invalid: Rc::new(false.into()),
}
}
pub fn new_exclusive(world: &'w mut World) -> Self {
let type_registry = world.get_resource_or_init::<AppTypeRegistry>().0.clone();
let allocator = world.get_resource_or_init::<AppReflectAllocator>().clone();
let function_registry = world
.get_resource_or_init::<AppScriptFunctionRegistry>()
.clone();
let script_component_registry = world
.get_resource_or_init::<AppScriptComponentRegistry>()
.clone();
let schedule_registry = world.get_resource_or_init::<AppScheduleRegistry>().clone();
Self {
inner: Rc::new(WorldAccessGuardInner {
cell: world.as_unsafe_world_cell(),
accesses: AnyAccessMap::UnlimitedAccessMap(Default::default()),
allocator,
type_registry,
function_registry,
schedule_registry,
script_component_registry,
}),
invalid: Rc::new(false.into()),
}
}
pub(crate) fn queue(&self, command: impl Command) -> Result<(), InteropError> {
self.with_global_access(|w| {
w.commands().queue(command);
})
}
pub(crate) unsafe fn with_access_scope<O, F: FnOnce() -> O>(
&self,
f: F,
) -> Result<O, InteropError> {
Ok(self.inner.accesses.with_scope(f))
}
pub fn list_accesses(&self) -> Vec<(ReflectAccessId, AccessCount)> {
self.inner.accesses.list_accesses()
}
pub unsafe fn release_all_accesses(&self) {
self.inner.accesses.release_all_accesses();
}
pub fn access_len(&self) -> usize {
self.inner.accesses.count_accesses()
}
pub fn as_unsafe_world_cell(&self) -> Result<UnsafeWorldCell<'w>, InteropError> {
if !self.is_valid() {
return Err(InteropError::missing_world());
}
Ok(self.inner.cell)
}
pub fn as_unsafe_world_cell_readonly(&self) -> Result<UnsafeWorldCell<'w>, InteropError> {
if !self.is_valid() {
return Err(InteropError::missing_world());
}
Ok(self.inner.cell)
}
pub fn get_component_id(&self, id: TypeId) -> Result<Option<ComponentId>, InteropError> {
Ok(self
.as_unsafe_world_cell_readonly()?
.components()
.get_id(id))
}
pub fn get_resource_id(&self, id: TypeId) -> Result<Option<ComponentId>, InteropError> {
Ok(self
.as_unsafe_world_cell_readonly()?
.components()
.get_resource_id(id))
}
pub fn with_read_access<T: Into<ReflectAccessId>, O, F: FnOnce(&Self) -> O>(
&self,
id: T,
closure: F,
) -> Result<O, ()> {
let id = id.into();
if self.claim_read_access(id) {
let out = Ok(closure(self));
unsafe { self.release_access(id) };
out
} else {
Err(())
}
}
pub fn with_write_access<T: Into<ReflectAccessId>, O, F: FnOnce(&Self) -> O>(
&self,
id: T,
closure: F,
) -> Result<O, ()> {
let id = id.into();
if self.claim_write_access(id) {
let out = Ok(closure(self));
unsafe { self.release_access(id) };
out
} else {
Err(())
}
}
pub fn get_access_location(
&self,
raid: ReflectAccessId,
) -> Option<std::panic::Location<'static>> {
self.inner.accesses.access_location(raid)
}
#[track_caller]
pub fn claim_read_access(&self, raid: ReflectAccessId) -> bool {
self.inner.accesses.claim_read_access(raid)
}
#[track_caller]
pub fn claim_write_access(&self, raid: ReflectAccessId) -> bool {
self.inner.accesses.claim_write_access(raid)
}
pub unsafe fn release_access(&self, raid: ReflectAccessId) {
self.inner.accesses.release_access(raid)
}
pub fn claim_global_access(&self) -> bool {
self.inner.accesses.claim_global_access()
}
pub unsafe fn release_global_access(&self) {
self.inner.accesses.release_global_access()
}
pub fn type_registry(&self) -> TypeRegistryArc {
self.inner.type_registry.clone()
}
pub fn schedule_registry(&self) -> AppScheduleRegistry {
self.inner.schedule_registry.clone()
}
pub fn component_registry(&self) -> AppScriptComponentRegistry {
self.inner.script_component_registry.clone()
}
pub fn allocator(&self) -> AppReflectAllocator {
self.inner.allocator.clone()
}
pub fn script_function_registry(&self) -> AppScriptFunctionRegistry {
self.inner.function_registry.clone()
}
#[track_caller]
pub fn with_global_access<F: FnOnce(&mut World) -> O, O>(
&self,
f: F,
) -> Result<O, InteropError> {
with_global_access!(
&self.inner.accesses,
"Could not claim exclusive world access",
{
let world = unsafe { self.as_unsafe_world_cell()?.world_mut() };
Ok(f(world))
}
)?
}
pub fn with_resource<F, R, O>(&self, f: F) -> Result<O, InteropError>
where
R: Resource,
F: FnOnce(&R) -> O,
{
let cell = self.as_unsafe_world_cell()?;
let access_id = ReflectAccessId::for_resource::<R>(&cell)?;
with_access_read!(
&self.inner.accesses,
access_id,
format!("Could not access resource: {}", std::any::type_name::<R>()),
{
f(unsafe {
cell.get_resource::<R>().ok_or_else(|| {
InteropError::unregistered_component_or_resource_type(
std::any::type_name::<R>(),
)
})?
})
}
)
}
pub fn with_resource_mut<F, R, O>(&self, f: F) -> Result<O, InteropError>
where
R: Resource,
F: FnOnce(Mut<R>) -> O,
{
let cell = self.as_unsafe_world_cell()?;
let access_id = ReflectAccessId::for_resource::<R>(&cell)?;
with_access_write!(
&self.inner.accesses,
access_id,
format!("Could not access resource: {}", std::any::type_name::<R>()),
{
f(unsafe {
cell.get_resource_mut::<R>().ok_or_else(|| {
InteropError::unregistered_component_or_resource_type(
std::any::type_name::<R>(),
)
})?
})
}
)
}
pub fn with_component<F, T, O>(&self, entity: Entity, f: F) -> Result<O, InteropError>
where
T: Component,
F: FnOnce(Option<&T>) -> O,
{
let cell = self.as_unsafe_world_cell()?;
let access_id = ReflectAccessId::for_component::<T>(&cell)?;
with_access_read!(
&self.inner.accesses,
access_id,
format!("Could not access component: {}", std::any::type_name::<T>()),
{
f(unsafe { cell.get_entity(entity).map(|e| e.get::<T>()) }
.ok()
.unwrap_or(None))
}
)
}
pub fn with_component_mut<F, T, O>(&self, entity: Entity, f: F) -> Result<O, InteropError>
where
T: Component<Mutability = Mutable>,
F: FnOnce(Option<Mut<T>>) -> O,
{
let cell = self.as_unsafe_world_cell()?;
let access_id = ReflectAccessId::for_component::<T>(&cell)?;
with_access_write!(
&self.inner.accesses,
access_id,
format!("Could not access component: {}", std::any::type_name::<T>()),
{
f(unsafe { cell.get_entity(entity).map(|e| e.get_mut::<T>()) }
.ok()
.unwrap_or(None))
}
)
}
pub fn with_or_insert_component_mut<F, T, O>(
&self,
entity: Entity,
f: F,
) -> Result<O, InteropError>
where
T: Component<Mutability = Mutable> + Default,
F: FnOnce(&mut T) -> O,
{
self.with_global_access(|world| match world.get_mut::<T>(entity) {
Some(mut component) => f(&mut component),
None => {
let mut component = T::default();
let mut commands = world.commands();
let result = f(&mut component);
commands.entity(entity).insert(component);
result
}
})
}
pub fn lookup_function(
&self,
type_ids: impl IntoIterator<Item = TypeId>,
name: impl Into<Cow<'static, str>>,
) -> Result<DynamicScriptFunction, Cow<'static, str>> {
let registry = self.script_function_registry();
let registry = registry.read();
let mut name = name.into();
for type_id in type_ids {
name = match registry.get_function(Namespace::OnType(type_id), name) {
Ok(func) => return Ok(func.clone()),
Err(name) => name,
};
}
Err(name)
}
pub fn get_functions_on_type(
&self,
type_id: TypeId,
) -> Vec<(Cow<'static, str>, DynamicScriptFunction)> {
let registry = self.script_function_registry();
let registry = registry.read();
registry
.iter_namespace(Namespace::OnType(type_id))
.chain(
registry
.iter_namespace(Namespace::OnType(std::any::TypeId::of::<ReflectReference>())),
)
.map(|(key, func)| (key.name.clone(), func.clone()))
.collect()
}
pub fn is_valid_entity(&self, entity: Entity) -> Result<bool, InteropError> {
let cell = self.as_unsafe_world_cell()?;
Ok(cell.get_entity(entity).is_ok() && entity.index().index() != 0)
}
pub fn try_call_overloads(
&self,
type_id: TypeId,
name: impl Into<Cow<'static, str>>,
args: Vec<ScriptValue>,
context: FunctionCallContext,
) -> Result<ScriptValue, InteropError> {
let registry = self.script_function_registry();
let registry = registry.read();
let name = name.into();
let overload_iter = match registry.iter_overloads(Namespace::OnType(type_id), name) {
Ok(iter) => iter,
Err(name) => {
return Err(InteropError::missing_function(
name.to_string(),
Namespace::OnType(type_id),
Some(context.clone()),
));
}
};
let mut last_error = None;
for overload in overload_iter {
match overload.call(args.clone(), context.clone()) {
Ok(out) => return Ok(out),
Err(e) => last_error = Some(e),
}
}
Err(last_error.ok_or_else(|| InteropError::invariant("invariant, iterator should always return at least one item, and if the call fails it should return an error"))?)
}
}
#[profiling::all_functions]
impl WorldAccessGuard<'_> {
fn construct_from_script_value(
&self,
descriptor: impl Into<Cow<'static, str>>,
type_id: TypeId,
value: Option<ScriptValue>,
) -> Result<Box<dyn PartialReflect>, InteropError> {
let value = match value {
Some(value) => value,
None => {
let type_registry = self.type_registry();
let type_registry = type_registry.read();
let default_data = type_registry
.get_type_data::<ReflectDefault>(type_id)
.ok_or_else(|| {
InteropError::function_interop_error(
"construct",
Namespace::OnType(TypeId::of::<World>()),
InteropError::string(format!(
"field missing and no default provided: '{}'",
descriptor.into()
)),
None,
)
})?;
return Ok(default_data.default().into_partial_reflect());
}
};
<Box<dyn PartialReflect>>::from_script_ref(type_id, value, self.clone())
}
fn construct_dynamic_struct(
&self,
payload: &mut HashMap<String, ScriptValue>,
fields: Vec<(&'static str, TypeId)>,
) -> Result<DynamicStruct, InteropError> {
let mut dynamic = DynamicStruct::default();
for (field_name, field_type_id) in fields {
let constructed = self.construct_from_script_value(
field_name,
field_type_id,
payload.remove(field_name),
)?;
dynamic.insert_boxed(field_name, constructed);
}
Ok(dynamic)
}
fn construct_dynamic_tuple_struct(
&self,
payload: &mut HashMap<String, ScriptValue>,
fields: Vec<TypeId>,
one_indexed: bool,
) -> Result<DynamicTupleStruct, InteropError> {
let mut dynamic = DynamicTupleStruct::default();
for (field_idx, field_type_id) in fields.into_iter().enumerate() {
let script_idx = if one_indexed {
field_idx + 1
} else {
field_idx
};
let field_string = script_idx.to_string();
dynamic.insert_boxed(self.construct_from_script_value(
field_string.clone(),
field_type_id,
payload.remove(&field_string),
)?);
}
Ok(dynamic)
}
fn construct_dynamic_tuple(
&self,
payload: &mut HashMap<String, ScriptValue>,
fields: Vec<TypeId>,
one_indexed: bool,
) -> Result<DynamicTuple, InteropError> {
let mut dynamic = DynamicTuple::default();
for (field_idx, field_type_id) in fields.into_iter().enumerate() {
let script_idx = if one_indexed {
field_idx + 1
} else {
field_idx
};
let field_string = script_idx.to_string();
dynamic.insert_boxed(self.construct_from_script_value(
field_string.clone(),
field_type_id,
payload.remove(&field_string),
)?);
}
Ok(dynamic)
}
pub fn construct(
&self,
type_: ScriptTypeRegistration,
mut payload: HashMap<String, ScriptValue>,
one_indexed: bool,
) -> Result<Box<dyn PartialReflect>, InteropError> {
let type_info = type_.registration.type_info();
let dynamic: Box<dyn PartialReflect> = match type_info {
TypeInfo::Struct(struct_info) => {
let fields_iter = struct_info
.field_names()
.iter()
.map(|f| {
Ok((
*f,
struct_info
.field(f)
.ok_or_else(|| {
InteropError::invariant(
"field in field_names should have reflection information",
)
})?
.type_id(),
))
})
.collect::<Result<Vec<_>, InteropError>>()?;
let mut dynamic = self.construct_dynamic_struct(&mut payload, fields_iter)?;
dynamic.set_represented_type(Some(type_info));
Box::new(dynamic)
}
TypeInfo::TupleStruct(tuple_struct_info) => {
let fields_iter = (0..tuple_struct_info.field_len())
.map(|f| {
Ok(tuple_struct_info
.field_at(f)
.ok_or_else(|| {
InteropError::invariant(
"field in field_names should have reflection information",
)
})?
.type_id())
})
.collect::<Result<Vec<_>, InteropError>>()?;
let mut dynamic =
self.construct_dynamic_tuple_struct(&mut payload, fields_iter, one_indexed)?;
dynamic.set_represented_type(Some(type_info));
Box::new(dynamic)
}
TypeInfo::Tuple(tuple_info) => {
let fields_iter = (0..tuple_info.field_len())
.map(|f| {
Ok(tuple_info
.field_at(f)
.ok_or_else(|| {
InteropError::invariant(
"field in field_names should have reflection information",
)
})?
.type_id())
})
.collect::<Result<Vec<_>, InteropError>>()?;
let mut dynamic =
self.construct_dynamic_tuple(&mut payload, fields_iter, one_indexed)?;
dynamic.set_represented_type(Some(type_info));
Box::new(dynamic)
}
TypeInfo::Enum(enum_info) => {
let variant = payload.remove("variant").ok_or_else(|| {
InteropError::function_interop_error(
"construct",
Namespace::OnType(TypeId::of::<World>()),
InteropError::str("missing 'variant' field in enum constructor payload"),
None,
)
})?;
let variant_name = String::from_script(variant, self.clone())?;
let variant = enum_info.variant(&variant_name).ok_or_else(|| {
InteropError::function_interop_error(
"construct",
Namespace::OnType(TypeId::of::<World>()),
InteropError::string(format!(
"invalid variant name '{}' for enum '{}'",
variant_name,
enum_info.type_path()
)),
None,
)
})?;
let variant = match variant {
VariantInfo::Struct(struct_variant_info) => {
let fields_iter = struct_variant_info
.field_names()
.iter()
.map(|f| {
Ok((
*f,
struct_variant_info
.field(f)
.ok_or_else(|| {
InteropError::invariant(
"field in field_names should have reflection information",
)
})?
.type_id(),
))
})
.collect::<Result<Vec<_>, InteropError>>()?;
let dynamic = self.construct_dynamic_struct(&mut payload, fields_iter)?;
DynamicVariant::Struct(dynamic)
}
VariantInfo::Tuple(tuple_variant_info) => {
let fields_iter = (0..tuple_variant_info.field_len())
.map(|f| {
Ok(tuple_variant_info
.field_at(f)
.ok_or_else(|| {
InteropError::invariant(
"field in field_names should have reflection information",
)
})?
.type_id())
})
.collect::<Result<Vec<_>, InteropError>>()?;
let dynamic =
self.construct_dynamic_tuple(&mut payload, fields_iter, one_indexed)?;
DynamicVariant::Tuple(dynamic)
}
VariantInfo::Unit(_) => DynamicVariant::Unit,
};
let mut dynamic = DynamicEnum::new(variant_name, variant);
dynamic.set_represented_type(Some(type_info));
Box::new(dynamic)
}
_ => {
return Err(InteropError::unsupported_operation(
Some(type_info.type_id()),
Some(Box::new(payload)),
"Type constructor not supported",
));
}
};
<dyn PartialReflect>::from_reflect_or_clone(dynamic.as_ref(), self.clone())
}
pub fn load_script_asset<'a>(
&self,
asset_path: impl Into<AssetPath<'a>>,
) -> Result<Handle<ScriptAsset>, InteropError> {
self.with_resource(|r: &AssetServer| r.load(asset_path))
}
pub fn get_script_asset_load_state(
&self,
script: Handle<ScriptAsset>,
) -> Result<LoadState, InteropError> {
self.with_resource(|r: &AssetServer| r.load_state(script.id()))
}
pub fn spawn(&self) -> Result<Entity, InteropError> {
self.with_global_access(|world| {
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, world);
let id = commands.spawn_empty().id();
command_queue.apply(world);
id
})
}
pub fn get_type_by_name(&self, type_name: &str) -> Option<ScriptTypeRegistration> {
let type_registry = self.type_registry();
let type_registry = type_registry.read();
type_registry
.get_with_short_type_path(type_name)
.or_else(|| type_registry.get_with_type_path(type_name))
.map(|registration| ScriptTypeRegistration::new(Arc::new(registration.clone())))
}
pub(crate) fn get_type_registration(
&self,
registration: ScriptTypeRegistration,
) -> Result<
Union<
ScriptTypeRegistration,
Union<ScriptComponentRegistration, ScriptResourceRegistration>,
>,
InteropError,
> {
let registration = match self.get_resource_type(registration)? {
Ok(res) => {
return Ok(Union::new_right(Union::new_right(res)));
}
Err(registration) => registration,
};
let registration = match self.get_component_type(registration)? {
Ok(comp) => {
return Ok(Union::new_right(Union::new_left(comp)));
}
Err(registration) => registration,
};
Ok(Union::new_left(registration))
}
pub fn get_type_registration_by_name(
&self,
type_name: String,
) -> Result<
Option<
Union<
ScriptTypeRegistration,
Union<ScriptComponentRegistration, ScriptResourceRegistration>,
>,
>,
InteropError,
> {
let val = self.get_type_by_name(&type_name);
Ok(match val {
Some(registration) => Some(self.get_type_registration(registration)?),
None => {
let components = self.component_registry();
let components = components.read();
components
.get(&type_name)
.map(|c| Union::new_right(Union::new_left(c.registration.clone())))
}
})
}
pub fn get_schedule_by_name(&self, schedule_name: String) -> Option<ReflectSchedule> {
let schedule_registry = self.schedule_registry();
let schedule_registry = schedule_registry.read();
schedule_registry
.get_schedule_by_name(&schedule_name)
.cloned()
}
pub fn get_component_type(
&self,
registration: ScriptTypeRegistration,
) -> Result<Result<ScriptComponentRegistration, ScriptTypeRegistration>, InteropError> {
Ok(match self.get_component_id(registration.type_id())? {
Some(comp_id) => Ok(ScriptComponentRegistration::new(registration, comp_id)),
None => Err(registration),
})
}
pub fn get_resource_type(
&self,
registration: ScriptTypeRegistration,
) -> Result<Result<ScriptResourceRegistration, ScriptTypeRegistration>, InteropError> {
Ok(match self.get_resource_id(registration.type_id())? {
Some(resource_id) => Ok(ScriptResourceRegistration::new(registration, resource_id)),
None => Err(registration),
})
}
pub fn add_default_component(
&self,
entity: Entity,
registration: ScriptComponentRegistration,
) -> Result<(), InteropError> {
let instance = if let Some(default_td) = registration
.type_registration()
.type_registration()
.data::<ReflectDefault>()
{
default_td.default()
} else if let Some(from_world_td) = registration
.type_registration()
.type_registration()
.data::<ReflectFromWorld>()
{
self.with_global_access(|world| from_world_td.from_world(world))?
} else {
return Err(InteropError::missing_type_data(
registration.registration.type_id(),
"ReflectDefault or ReflectFromWorld".to_owned(),
));
};
registration.insert_into_entity(self.clone(), entity, instance)
}
pub fn insert_component(
&self,
entity: Entity,
registration: ScriptComponentRegistration,
value: ReflectReference,
) -> Result<(), InteropError> {
let instance = <Box<dyn PartialReflect>>::from_script_ref(
registration.type_registration().type_id(),
ScriptValue::Reference(value),
self.clone(),
)?;
let reflect = instance.try_into_reflect().map_err(|v| {
InteropError::failed_from_reflect(
Some(registration.type_registration().type_id()),
format!("instance produced by conversion to target type when inserting component is not a full reflect type: {v:?}"),
)
})?;
registration.insert_into_entity(self.clone(), entity, reflect)
}
pub fn get_component(
&self,
entity: Entity,
component_registration: ScriptComponentRegistration,
) -> Result<Option<ReflectReference>, InteropError> {
let cell = self.as_unsafe_world_cell()?;
let entity = cell
.get_entity(entity)
.map_err(|_| InteropError::missing_entity(entity))?;
if entity.contains_id(component_registration.component_id) {
Ok(Some(ReflectReference {
base: ReflectBaseType {
type_id: component_registration.type_registration().type_id(),
base_id: ReflectBase::Component(
entity.id(),
component_registration.component_id,
),
},
reflect_path: Default::default(),
}))
} else {
Ok(None)
}
}
pub fn has_component(
&self,
entity: Entity,
component_id: ComponentId,
) -> Result<bool, InteropError> {
let cell = self.as_unsafe_world_cell()?;
let entity = cell
.get_entity(entity)
.map_err(|_| InteropError::missing_entity(entity))?;
Ok(entity.contains_id(component_id))
}
pub fn remove_component(
&self,
entity: Entity,
registration: ScriptComponentRegistration,
) -> Result<(), InteropError> {
registration.remove_from_entity(self.clone(), entity)
}
pub fn get_resource(
&self,
resource_id: ComponentId,
) -> Result<Option<ReflectReference>, InteropError> {
let cell = self.as_unsafe_world_cell()?;
let component_info = match cell.components().get_info(resource_id) {
Some(info) => info,
None => return Ok(None),
};
Ok(Some(ReflectReference {
base: ReflectBaseType {
type_id: component_info
.type_id()
.ok_or_else(|| {
InteropError::unsupported_operation(
None,
None,
format!(
"Resource {} does not have a type id. Such resources are not supported by BMS.",
component_info.name()
),
)
})?,
base_id: ReflectBase::Resource(resource_id),
},
reflect_path: Default::default(),
}))
}
pub fn remove_resource(
&self,
registration: ScriptResourceRegistration,
) -> Result<(), InteropError> {
let component_data = registration
.type_registration()
.type_registration()
.data::<ReflectResource>()
.ok_or_else(|| {
InteropError::missing_type_data(
registration.registration.type_id(),
"ReflectResource".to_owned(),
)
})?;
self.with_global_access(|world| component_data.remove(world))
}
pub fn has_resource(&self, resource_id: ComponentId) -> Result<bool, InteropError> {
let cell = self.as_unsafe_world_cell()?;
let res_ptr = unsafe { cell.get_resource_by_id(resource_id) };
Ok(res_ptr.is_some())
}
pub fn has_entity(&self, entity: Entity) -> Result<bool, InteropError> {
self.is_valid_entity(entity)
}
pub fn get_children(&self, entity: Entity) -> Result<Vec<Entity>, InteropError> {
if !self.is_valid_entity(entity)? {
return Err(InteropError::missing_entity(entity));
}
self.with_component(entity, |c: Option<&Children>| {
c.map(|c| c.to_vec()).unwrap_or_default()
})
}
pub fn get_parent(&self, entity: Entity) -> Result<Option<Entity>, InteropError> {
if !self.is_valid_entity(entity)? {
return Err(InteropError::missing_entity(entity));
}
self.with_component(entity, |c: Option<&ChildOf>| c.map(|c| c.parent()))
}
pub fn push_children(&self, parent: Entity, children: &[Entity]) -> Result<(), InteropError> {
if !self.is_valid_entity(parent)? {
return Err(InteropError::missing_entity(parent));
}
for c in children {
if !self.is_valid_entity(*c)? {
return Err(InteropError::missing_entity(*c));
}
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(parent).add_children(children);
queue.apply(world);
})
}
pub fn remove_children(&self, parent: Entity, children: &[Entity]) -> Result<(), InteropError> {
if !self.is_valid_entity(parent)? {
return Err(InteropError::missing_entity(parent));
}
for c in children {
if !self.is_valid_entity(*c)? {
return Err(InteropError::missing_entity(*c));
}
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(parent).detach_children(children);
queue.apply(world);
})
}
pub fn insert_children(
&self,
parent: Entity,
index: usize,
children: &[Entity],
) -> Result<(), InteropError> {
if !self.is_valid_entity(parent)? {
return Err(InteropError::missing_entity(parent));
}
for c in children {
if !self.is_valid_entity(*c)? {
return Err(InteropError::missing_entity(*c));
}
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(parent).insert_children(index, children);
queue.apply(world);
})
}
pub fn despawn_recursive(&self, parent: Entity) -> Result<(), InteropError> {
if !self.is_valid_entity(parent)? {
return Err(InteropError::missing_entity(parent));
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(parent).despawn();
queue.apply(world);
})
}
pub fn despawn(&self, entity: Entity) -> Result<(), InteropError> {
if !self.is_valid_entity(entity)? {
return Err(InteropError::missing_entity(entity));
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(entity).remove::<Children>().despawn();
queue.apply(world);
})
}
pub fn despawn_descendants(&self, parent: Entity) -> Result<(), InteropError> {
if !self.is_valid_entity(parent)? {
return Err(InteropError::missing_entity(parent));
}
self.with_global_access(|world| {
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, world);
commands.entity(parent).despawn_related::<Children>();
queue.apply(world);
})
}
pub fn exit(&self) -> Result<(), InteropError> {
self.with_global_access(|world| {
world.write_message(AppExit::Success);
})
}
}
pub struct ThreadWorldContainer;
#[derive(Clone)]
pub struct ThreadScriptContext<'l> {
pub world: WorldGuard<'l>,
pub attachment: ScriptAttachment,
}
thread_local! {
static WORLD_CALLBACK_ACCESS: RefCell<Option<ThreadScriptContext<'static>>> = const { RefCell::new(None) };
}
#[profiling::all_functions]
impl ThreadWorldContainer {
pub fn set_context(&mut self, world: ThreadScriptContext<'static>) -> Result<(), InteropError> {
WORLD_CALLBACK_ACCESS.with(|w| {
w.replace(Some(world));
});
Ok(())
}
pub fn try_get_context<'l>(&self) -> Result<ThreadScriptContext<'l>, InteropError> {
WORLD_CALLBACK_ACCESS
.with(|w| w.borrow().clone().ok_or_else(InteropError::missing_world))
.map(|v| ThreadScriptContext {
world: v.world.shorten_lifetime(),
attachment: v.attachment,
})
}
}
impl GetTypeInfo for ThreadWorldContainer {
fn get_type_info(&self, type_id: TypeId) -> Option<&TypeInfo> {
let world = self.try_get_context().ok()?.world;
let registry = world.type_registry();
let registry = registry.read();
registry.get(type_id).map(|r| r.type_info())
}
fn query_type_registration(
&self,
type_id: TypeId,
type_data_id: TypeId,
) -> Option<Box<dyn bevy_reflect::TypeData>> {
let world = self.try_get_context().ok()?.world;
let registry = world.type_registry();
let registry = registry.read();
registry
.get(type_id)
.and_then(|r| r.data_by_id(type_data_id).map(|t| t.clone_type_data()))
}
fn get_component_info(
&self,
component_id: ComponentId,
) -> Option<&bevy_ecs::component::ComponentInfo> {
let world = self.try_get_context().ok()?.world;
let cell = world.as_unsafe_world_cell().ok()?;
cell.components().get_info(component_id)
}
unsafe fn as_any_static(&self) -> &dyn Any {
self
}
}
impl GetTypeInfo for WorldGuard<'_> {
fn get_type_info(&self, type_id: TypeId) -> Option<&TypeInfo> {
let registry = self.type_registry();
let registry = registry.read();
registry.get(type_id).map(|r| r.type_info())
}
fn query_type_registration(
&self,
type_id: TypeId,
type_data_id: TypeId,
) -> Option<Box<dyn bevy_reflect::TypeData>> {
let registry = self.type_registry();
let registry = registry.read();
registry
.get(type_id)
.and_then(|r| r.data_by_id(type_data_id).map(|t| t.clone_type_data()))
}
fn get_component_info(
&self,
component_id: ComponentId,
) -> Option<&bevy_ecs::component::ComponentInfo> {
let cell = self.as_unsafe_world_cell().ok()?;
cell.components().get_info(component_id)
}
unsafe fn as_any_static(&self) -> &dyn Any {
let static_self: &WorldGuard<'static> = unsafe { std::mem::transmute(self) };
static_self as &dyn Any
}
}
#[cfg(test)]
mod test {
use super::*;
use bevy_reflect::{GetTypeRegistration, ReflectFromReflect};
use test_utils::test_data::{SimpleEnum, SimpleStruct, SimpleTupleStruct, setup_world};
#[test]
fn test_construct_struct() {
let mut world = setup_world(|_, _| {});
let world = WorldAccessGuard::new_exclusive(&mut world);
let registry = world.type_registry();
let registry = registry.read();
let registration = registry.get(TypeId::of::<SimpleStruct>()).unwrap().clone();
let type_registration = ScriptTypeRegistration::new(Arc::new(registration));
let payload = HashMap::from_iter(vec![("foo".to_owned(), ScriptValue::Integer(1))]);
let result = world.construct(type_registration, payload, false);
let expected =
Ok::<_, InteropError>(Box::new(SimpleStruct { foo: 1 }) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
}
#[test]
fn test_construct_tuple_struct() {
let mut world = setup_world(|_, _| {});
let world = WorldAccessGuard::new_exclusive(&mut world);
let registry = world.type_registry();
let registry = registry.read();
let registration = registry
.get(TypeId::of::<SimpleTupleStruct>())
.unwrap()
.clone();
let type_registration = ScriptTypeRegistration::new(Arc::new(registration));
let payload = HashMap::from_iter(vec![("0".to_owned(), ScriptValue::Integer(1))]);
let result = world.construct(type_registration.clone(), payload, false);
let expected =
Ok::<_, InteropError>(Box::new(SimpleTupleStruct(1)) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
let payload = HashMap::from_iter(vec![("1".to_owned(), ScriptValue::Integer(1))]);
let result = world.construct(type_registration, payload, true);
let expected =
Ok::<_, InteropError>(Box::new(SimpleTupleStruct(1)) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
}
#[test]
fn test_construct_tuple() {
let mut world = setup_world(|_, registry| {
registry.register::<(usize, usize)>();
registry.register_type_data::<(usize, usize), ReflectFromReflect>();
});
<usize as GetTypeRegistration>::get_type_registration();
let world = WorldAccessGuard::new_exclusive(&mut world);
let registry = world.type_registry();
let registry = registry.read();
let registration = registry
.get(TypeId::of::<(usize, usize)>())
.unwrap()
.clone();
let type_registration = ScriptTypeRegistration::new(Arc::new(registration));
let payload = HashMap::from_iter(vec![
("0".to_owned(), ScriptValue::Integer(1)),
("1".to_owned(), ScriptValue::Integer(2)),
]);
let result = world.construct(type_registration.clone(), payload, false);
let expected = Ok::<_, InteropError>(Box::new((1, 2)) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
let payload = HashMap::from_iter(vec![
("1".to_owned(), ScriptValue::Integer(1)),
("2".to_owned(), ScriptValue::Integer(2)),
]);
let result = world.construct(type_registration.clone(), payload, true);
let expected = Ok::<_, InteropError>(Box::new((1, 2)) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
}
#[test]
fn test_construct_enum() {
let mut world = setup_world(|_, _| {});
let world = WorldAccessGuard::new_exclusive(&mut world);
let registry = world.type_registry();
let registry = registry.read();
let registration = registry.get(TypeId::of::<SimpleEnum>()).unwrap().clone();
let type_registration = ScriptTypeRegistration::new(Arc::new(registration));
let payload = HashMap::from_iter(vec![
("foo".to_owned(), ScriptValue::Integer(1)),
("variant".to_owned(), ScriptValue::String("Struct".into())),
]);
let result = world.construct(type_registration.clone(), payload, false);
let expected = Ok::<_, InteropError>(
Box::new(SimpleEnum::Struct { foo: 1 }) as Box<dyn PartialReflect>
);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
let payload = HashMap::from_iter(vec![
("0".to_owned(), ScriptValue::Integer(1)),
(
"variant".to_owned(),
ScriptValue::String("TupleStruct".into()),
),
]);
let result = world.construct(type_registration.clone(), payload, false);
let expected =
Ok::<_, InteropError>(Box::new(SimpleEnum::TupleStruct(1)) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
let payload = HashMap::from_iter(vec![(
"variant".to_owned(),
ScriptValue::String("Unit".into()),
)]);
let result = world.construct(type_registration, payload, false);
let expected = Ok::<_, InteropError>(Box::new(SimpleEnum::Unit) as Box<dyn PartialReflect>);
pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}"));
}
#[test]
fn test_scoped_handle_invalidate_doesnt_invalidate_parent() {
let mut world = setup_world(|_, _| {});
let world = WorldAccessGuard::new_exclusive(&mut world);
let scoped_world = world.scope();
scoped_world.spawn().unwrap();
world.spawn().unwrap();
pretty_assertions::assert_eq!(scoped_world.is_valid(), true);
pretty_assertions::assert_eq!(world.is_valid(), true);
scoped_world.invalidate();
pretty_assertions::assert_eq!(scoped_world.is_valid(), false);
pretty_assertions::assert_eq!(world.is_valid(), true);
world.spawn().unwrap();
}
#[test]
fn with_existing_static_guard_does_not_invalidate_original() {
let mut world = setup_world(|_, _| {});
let world = WorldAccessGuard::new_exclusive(&mut world);
let mut sneaky_clone = None;
WorldAccessGuard::with_existing_static_guard(world.clone(), |g| {
pretty_assertions::assert_eq!(g.is_valid(), true);
sneaky_clone = Some(g.clone());
});
pretty_assertions::assert_eq!(world.is_valid(), true, "original world was invalidated");
pretty_assertions::assert_eq!(
sneaky_clone.map(|c| c.is_valid()),
Some(false),
"scoped world was not invalidated"
);
}
#[test]
fn test_with_access_scope_success() {
let mut world = setup_world(|_, _| {});
let guard = WorldAccessGuard::new_exclusive(&mut world);
let result = unsafe { guard.with_access_scope(|| 100) };
assert_eq!(result.unwrap(), 100);
}
}