Skip to content

feat: add global types cache making get_type_by_name redundant #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions assets/tests/globals/type_cache_available.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

function on_test()
local my_type = types.TestResource;
assert(my_type ~= nil, "Type TestResource is not available in type cache");
assert(my_type:short_name() == "TestResource", "Type t.TestResource:short_name() is not correct: " .. my_type:short_name());
end
5 changes: 5 additions & 0 deletions assets/tests/globals/type_cache_available.rhai
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn on_test() {
let my_type = types.TestResource;
assert(type_of(my_type) != "()", "Type TestResource is not available in type cache");
assert(my_type.short_name.call() == "TestResource", "Type t.TestResource:short_name() is not correct: " + my_type.short_name.call());
}
45 changes: 39 additions & 6 deletions crates/bevy_mod_scripting_core/src/bindings/globals/core.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
//! Core globals exposed by the BMS framework

use std::{collections::HashMap, sync::Arc};

use bevy::{app::Plugin, ecs::reflect::AppTypeRegistry};
use bevy_mod_scripting_derive::script_globals;

use crate::{bindings::{function::from::{Union, Val}, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, WorldGuard}, error::InteropError};

use super::AppScriptGlobalsRegistry;

Expand All @@ -10,12 +15,16 @@ pub struct CoreScriptGlobalsPlugin;
impl Plugin for CoreScriptGlobalsPlugin {
fn build(&self, _app: &mut bevy::app::App) {}
fn finish(&self, app: &mut bevy::app::App) {
let global_registry = app
.world_mut()
register_static_core_globals(app.world_mut());
register_core_globals(app.world_mut());
}
}

fn register_static_core_globals(world: &mut bevy::ecs::world::World) {
let global_registry = world
.get_resource_or_init::<AppScriptGlobalsRegistry>()
.clone();
let type_registry = app
.world_mut()
let type_registry = world
.get_resource_or_init::<AppTypeRegistry>()
.clone();
let mut global_registry = global_registry.write();
Expand All @@ -34,8 +43,32 @@ impl Plugin for CoreScriptGlobalsPlugin {
None,
global_name.into(),
documentation.into(),
)
);
}
}
}
}

#[script_globals(
bms_core_path = "crate",
name = "core_globals",
)]
impl CoreGlobals {
/// A cache of types normally available through the `world.get_type_by_name` function.
///
/// You can use this to avoid having to store type references.
fn types(guard: WorldGuard) -> Result<HashMap<String, Union<Val<ScriptTypeRegistration>, Union<Val<ScriptComponentRegistration>, Val<ScriptResourceRegistration>>>>, InteropError> {
let type_registry = guard.type_registry();
let type_registry = type_registry.read();
let mut type_cache = HashMap::<String, _>::default();
for registration in type_registry.iter(){
if let Some(ident) = registration.type_info().type_path_table().ident() {
let registration = ScriptTypeRegistration::new(Arc::new(registration.clone()));
let registration = guard.clone().get_type_registration(registration)?;
let registration = registration.map_both(Val::from, |u| u.map_both(Val::from, Val::from));
type_cache.insert(ident.to_string(), registration);
}
}

Ok(type_cache)
}
}
12 changes: 6 additions & 6 deletions crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ impl ScriptGlobalsRegistry {
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
>(
&mut self,
name: Cow<'static, str>,
name: impl Into<Cow<'static, str>>,
maker: F,
) -> Option<ScriptGlobal> {
self.globals.insert(
name,
name.into(),
ScriptGlobal {
maker: Some(Self::type_erase_maker(maker)),
documentation: None,
Expand All @@ -122,15 +122,15 @@ impl ScriptGlobalsRegistry {
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
>(
&mut self,
name: Cow<'static, str>,
name: impl Into<Cow<'static, str>>,
maker: F,
documentation: Cow<'static, str>,
documentation: impl Into<Cow<'static, str>>,
) -> Option<ScriptGlobal> {
self.globals.insert(
name,
name.into(),
ScriptGlobal {
maker: Some(Self::type_erase_maker(maker)),
documentation: Some(documentation),
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: Some(T::through_type_info()),
},
Expand Down
37 changes: 35 additions & 2 deletions crates/bevy_mod_scripting_core/src/bindings/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::{
}, function::{
namespace::Namespace,
script_function::{AppScriptFunctionRegistry, DynamicScriptFunction, FunctionCallContext},
}, pretty_print::DisplayWithWorld, schedule::AppScheduleRegistry, script_value::ScriptValue, with_global_access, AppReflectAllocator, ReflectBase, ReflectBaseType, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration
}, pretty_print::DisplayWithWorld, schedule::AppScheduleRegistry, script_value::ScriptValue, with_global_access, AppReflectAllocator, ReflectBase, ReflectBaseType, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, Union
};
use crate::{
bindings::{function::{from::FromScript, from_ref::FromScriptRef}, with_access_read, with_access_write},
Expand Down Expand Up @@ -542,6 +542,7 @@ impl<'w> WorldAccessGuard<'w> {
/// Impl block for higher level world methods
#[profiling::all_functions]
impl WorldAccessGuard<'_> {

fn construct_from_script_value(
&self,
descriptor: impl Into<Cow<'static, str>>,
Expand Down Expand Up @@ -800,7 +801,7 @@ impl WorldAccessGuard<'_> {
})
}

/// get a type registration for the type
/// get a type registration for the type, without checking if it's a component or resource
pub fn get_type_by_name(&self, type_name: String) -> Option<ScriptTypeRegistration> {
let type_registry = self.type_registry();
let type_registry = type_registry.read();
Expand All @@ -810,6 +811,38 @@ impl WorldAccessGuard<'_> {
.map(|registration| ScriptTypeRegistration::new(Arc::new(registration.clone())))
}

/// get a type erased type registration for the type including information about whether it's a component or resource
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))
}

/// Similar to [`Self::get_type_by_name`] but returns a type erased [`ScriptTypeRegistration`], [`ScriptComponentRegistration`] or [`ScriptResourceRegistration`]
/// depending on the underlying type and state of the world.
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 => None,
})
}

/// get a schedule by name
pub fn get_schedule_by_name(&self, schedule_name: String) -> Option<ReflectSchedule> {
let schedule_registry = self.schedule_registry();
Expand Down
44 changes: 13 additions & 31 deletions crates/bevy_mod_scripting_functions/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use bindings::{
};
use error::InteropError;
use reflection_extensions::{PartialReflectExt, TypeIdExtensions};

pub fn register_bevy_bindings(app: &mut App) {
#[cfg(feature = "bevy_bindings")]
app.add_plugins(crate::bevy_bindings::LuaBevyScriptingPlugin);
Expand All @@ -45,39 +46,20 @@ impl World {
fn get_type_by_name(
ctxt: FunctionCallContext,
type_name: String,
) -> Result<Option<ReflectReference>, InteropError> {
) -> Result<
Option<
Union<
Val<ScriptTypeRegistration>,
Union<Val<ScriptComponentRegistration>, Val<ScriptResourceRegistration>>,
>,
>,
InteropError,
> {
profiling::function_scope!("get_type_by_name");
let world = ctxt.world()?;
let val = world.get_type_by_name(type_name);

Ok(match val {
Some(registration) => {
let allocator = world.allocator();

let registration = match world.get_resource_type(registration)? {
Ok(res) => {
let mut allocator = allocator.write();
return Ok(Some(ReflectReference::new_allocated(res, &mut allocator)));
}
Err(registration) => registration,
};

let registration = match world.get_component_type(registration)? {
Ok(comp) => {
let mut allocator = allocator.write();
return Ok(Some(ReflectReference::new_allocated(comp, &mut allocator)));
}
Err(registration) => registration,
};

let mut allocator = allocator.write();
Some(ReflectReference::new_allocated(
registration,
&mut allocator,
))
}
None => None,
})
world
.get_type_registration_by_name(type_name)
.map(|v| v.map(|v| v.map_both(Val::from, |u| u.map_both(Val::from, Val::from))))
}

/// Retrieves the schedule with the given name, Also ensures the schedule is initialized before returning it.
Expand Down
Loading