Skip to content

feat(ladfile): improve globals in LAD format #372

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 6 commits into from
Mar 16, 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
5 changes: 3 additions & 2 deletions crates/bevy_mod_scripting_core/src/bindings/globals/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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 crate::{bindings::{function::from::{Union, Val}, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, WorldGuard}, docgen::into_through_type_info, error::InteropError};

use super::AppScriptGlobalsRegistry;

Expand Down Expand Up @@ -38,9 +38,10 @@ fn register_static_core_globals(world: &mut bevy::ecs::world::World) {

if let Some(global_name) = registration.type_info().type_path_table().ident() {
let documentation = "A reference to the type, allowing you to call static methods.";
let type_info = registration.type_info();
global_registry.register_static_documented_dynamic(
registration.type_id(),
None,
into_through_type_info(type_info),
global_name.into(),
documentation.into(),
);
Expand Down
22 changes: 11 additions & 11 deletions crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use super::{
script_value::ScriptValue,
WorldGuard,
};
use crate::{docgen::typed_through::ThroughTypeInfo, error::InteropError};
use bevy::{ecs::system::Resource, utils::hashbrown::HashMap};
use crate::{docgen::{into_through_type_info, typed_through::ThroughTypeInfo}, error::InteropError};
use bevy::{ecs::system::Resource, reflect::Typed, utils::hashbrown::HashMap};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, borrow::Cow, sync::Arc};

Expand Down Expand Up @@ -45,7 +45,7 @@ pub struct ScriptGlobal {
/// The type ID of the global variable.
pub type_id: TypeId,
/// Rich type information the global variable.
pub type_information: Option<ThroughTypeInfo>,
pub type_information: ThroughTypeInfo,
}

/// A registry of global variables that can be exposed to scripts.
Expand Down Expand Up @@ -96,7 +96,7 @@ impl ScriptGlobalsRegistry {

/// Inserts a global into the registry, returns the previous value if it existed
pub fn register<
T: ScriptReturn + 'static,
T: ScriptReturn + 'static + Typed,
F: Fn(WorldGuard) -> Result<T, InteropError> + 'static + Send + Sync,
>(
&mut self,
Expand All @@ -109,7 +109,7 @@ impl ScriptGlobalsRegistry {
maker: Some(Self::type_erase_maker(maker)),
documentation: None,
type_id: TypeId::of::<T>(),
type_information: None,
type_information: into_through_type_info(T::type_info()),
},
)
}
Expand All @@ -132,28 +132,28 @@ impl ScriptGlobalsRegistry {
maker: Some(Self::type_erase_maker(maker)),
documentation: Some(documentation.into()),
type_id: TypeId::of::<T>(),
type_information: Some(T::through_type_info()),
type_information: T::through_type_info(),
},
)
}

/// Registers a static global into the registry.
pub fn register_static<T: 'static>(&mut self, name: Cow<'static, str>) {
pub fn register_static<T: 'static + Typed>(&mut self, name: Cow<'static, str>) {
self.globals.insert(
name,
ScriptGlobal {
maker: None,
documentation: None,
type_id: TypeId::of::<T>(),
type_information: None,
type_information: into_through_type_info(T::type_info()),
},
);
}

/// Registers a static global into the registry.
///
/// This is a version of [`Self::register_static`] which stores rich type information regarding the global.
pub fn register_static_documented<T: TypedScriptReturn + 'static>(
pub fn register_static_documented<T: TypedScriptReturn +'static>(
&mut self,
name: Cow<'static, str>,
documentation: Cow<'static, str>,
Expand All @@ -164,7 +164,7 @@ impl ScriptGlobalsRegistry {
maker: None,
documentation: Some(documentation),
type_id: TypeId::of::<T>(),
type_information: Some(T::through_type_info()),
type_information: T::through_type_info(),
},
);
}
Expand All @@ -175,7 +175,7 @@ impl ScriptGlobalsRegistry {
pub fn register_static_documented_dynamic(
&mut self,
type_id: TypeId,
type_information: Option<ThroughTypeInfo>,
type_information: ThroughTypeInfo,
name: Cow<'static, str>,
documentation: Cow<'static, str>,
) {
Expand Down
4 changes: 0 additions & 4 deletions crates/bevy_mod_scripting_core/src/docgen/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,9 @@ mod test {
match &info.arg_info[0].type_info {
Some(ThroughTypeInfo::UntypedWrapper {
through_type,
wrapper_type_id,
wrapper_kind,
}) => {
assert_eq!(through_type.type_id(), TypeId::of::<i32>());
assert_eq!(*wrapper_type_id, TypeId::of::<Ref<'static, i32>>());
assert_eq!(*wrapper_kind, UntypedWrapperKind::Ref);
}
_ => panic!("Expected UntypedWrapper"),
Expand All @@ -245,11 +243,9 @@ mod test {
match &info.arg_info[1].type_info {
Some(ThroughTypeInfo::UntypedWrapper {
through_type,
wrapper_type_id,
wrapper_kind,
}) => {
assert_eq!(through_type.type_id(), TypeId::of::<f32>());
assert_eq!(*wrapper_type_id, TypeId::of::<Mut<'static, f32>>());
assert_eq!(*wrapper_kind, UntypedWrapperKind::Mut);
}
_ => panic!("Expected UntypedWrapper"),
Expand Down
138 changes: 131 additions & 7 deletions crates/bevy_mod_scripting_core/src/docgen/typed_through.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Defines a set of traits which destruture [`bevy::reflect::TypeInfo`] and implement a light weight wrapper around it, to allow types
//! which normally can't implement [`bevy::reflect::Typed`] to be used in a reflection context.

use std::{any::TypeId, ffi::OsString, path::PathBuf};
use std::{ffi::OsString, path::PathBuf};

use bevy::reflect::{TypeInfo, Typed};

Expand All @@ -16,7 +16,7 @@ use crate::{
script_value::ScriptValue,
ReflectReference,
},
error::InteropError,
error::InteropError, reflection_extensions::TypeInfoExtensions,
};

/// All Through types follow one rule:
Expand All @@ -34,8 +34,6 @@ pub enum ThroughTypeInfo {
UntypedWrapper {
/// The type information of the inner type.
through_type: &'static TypeInfo,
/// The type id of the wrapper type.
wrapper_type_id: TypeId,
/// The name of the wrapper type.
wrapper_kind: UntypedWrapperKind,
},
Expand Down Expand Up @@ -75,6 +73,66 @@ pub enum TypedWrapperKind {
Tuple(Vec<ThroughTypeInfo>),
}

/// A dynamic version of [`TypedThrough`], which can be used to convert a [`TypeInfo`] into a [`ThroughTypeInfo`].
pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo {

let option = (||{
if let Ok(array) = type_info.as_array() {
let len = array.capacity();
let inner = array.item_info()?;
return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Array(
Box::new(into_through_type_info(inner)),
len,
)));
} else if let Ok(hash_map) = type_info.as_map() {
let key_type = hash_map.key_info()?;
let value_type = hash_map.value_info()?;

return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap(
Box::new(into_through_type_info(key_type)),
Box::new(into_through_type_info(value_type)),
)));
} else if let Ok(list) = type_info.as_list() {
let inner = list.item_info()?;
return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(
Box::new(into_through_type_info(inner)),
)));
} else if type_info.is_option(){
let enum_ = type_info.as_enum().ok()?;
let inner = enum_.variant("Some")?;
let inner = inner.as_tuple_variant().ok()?;
let inner = inner.field_at(0)?;
let inner = inner.type_info()?;
return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Option(
Box::new(into_through_type_info(inner)),
)));
} else if type_info.is_result() {
let enum_ = type_info.as_enum().ok()?;
// let error_variant = enum_.variant("Err")?;
// TODO verify error variant is InteropError

let inner = enum_.variant("Ok")?;
let inner = inner.as_tuple_variant().ok()?;
let inner = inner.field_at(0)?;
let inner = inner.type_info()?;
return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::InteropResult(
Box::new(into_through_type_info(inner)),
)));
} else if let Ok(tuple) = type_info.as_tuple() {
let mut tuple_types = Vec::new();
for i in 0..tuple.field_len() {
let field = tuple.field_at(i)?;
let field_type = field.type_info()?;
tuple_types.push(into_through_type_info(field_type));
}
return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(tuple_types)));
}
None
})();

option.unwrap_or(ThroughTypeInfo::UntypedWrapper { through_type: type_info, wrapper_kind: UntypedWrapperKind::Val })
}

/// A trait for types that can be converted to a [`ThroughTypeInfo`].
pub trait TypedThrough {
/// Get the [`ThroughTypeInfo`] for the type.
Expand All @@ -94,7 +152,6 @@ impl<T: Typed> TypedThrough for Ref<'_, T> {
fn through_type_info() -> ThroughTypeInfo {
ThroughTypeInfo::UntypedWrapper {
through_type: T::type_info(),
wrapper_type_id: TypeId::of::<Ref<T>>(),
wrapper_kind: UntypedWrapperKind::Ref,
}
}
Expand All @@ -104,7 +161,6 @@ impl<T: Typed> TypedThrough for Mut<'_, T> {
fn through_type_info() -> ThroughTypeInfo {
ThroughTypeInfo::UntypedWrapper {
through_type: T::type_info(),
wrapper_type_id: TypeId::of::<Mut<T>>(),
wrapper_kind: UntypedWrapperKind::Mut,
}
}
Expand All @@ -114,7 +170,6 @@ impl<T: Typed> TypedThrough for Val<T> {
fn through_type_info() -> ThroughTypeInfo {
ThroughTypeInfo::UntypedWrapper {
through_type: T::type_info(),
wrapper_type_id: TypeId::of::<Val<T>>(),
wrapper_kind: UntypedWrapperKind::Val,
}
}
Expand Down Expand Up @@ -207,6 +262,7 @@ macro_rules! impl_through_typed_tuple {

bevy::utils::all_tuples!(impl_through_typed_tuple, 0, 13, T);


#[cfg(test)]
mod test {
use super::*;
Expand All @@ -224,29 +280,64 @@ mod test {
}
}

fn assert_dynamic_through_type_is_val_info<T: Typed + TypedThrough> () {
let type_info = T::type_info();
let through_type_info = into_through_type_info(type_info);

match through_type_info {
ThroughTypeInfo::UntypedWrapper{through_type, wrapper_kind} => {
assert_eq!(wrapper_kind, UntypedWrapperKind::Val);
assert_eq!(through_type.type_id(), type_info.type_id());
assert_eq!(through_type.type_path(), type_info.type_path());
}
_ => panic!("Expected ThroughTypeInfo::TypeInfo"),
}
}

#[test]
fn test_typed_through_primitives() {
assert_type_info_is_through::<bool>();
assert_dynamic_through_type_is_val_info::<bool>();
assert_type_info_is_through::<i8>();
assert_dynamic_through_type_is_val_info::<i8>();
assert_type_info_is_through::<i16>();
assert_dynamic_through_type_is_val_info::<i16>();
assert_type_info_is_through::<i32>();
assert_dynamic_through_type_is_val_info::<i32>();
assert_type_info_is_through::<i64>();
assert_dynamic_through_type_is_val_info::<i64>();
assert_type_info_is_through::<i128>();
assert_dynamic_through_type_is_val_info::<i128>();
assert_type_info_is_through::<u8>();
assert_dynamic_through_type_is_val_info::<u8>();
assert_type_info_is_through::<u16>();
assert_dynamic_through_type_is_val_info::<u16>();
assert_type_info_is_through::<u32>();
assert_dynamic_through_type_is_val_info::<u32>();
assert_type_info_is_through::<u64>();
assert_dynamic_through_type_is_val_info::<u64>();
assert_type_info_is_through::<u128>();
assert_dynamic_through_type_is_val_info::<u128>();
assert_type_info_is_through::<f32>();
assert_dynamic_through_type_is_val_info::<f32>();
assert_type_info_is_through::<f64>();
assert_dynamic_through_type_is_val_info::<f64>();
assert_type_info_is_through::<usize>();
assert_dynamic_through_type_is_val_info::<usize>();
assert_type_info_is_through::<isize>();
assert_dynamic_through_type_is_val_info::<isize>();
assert_type_info_is_through::<String>();
assert_dynamic_through_type_is_val_info::<String>();
assert_type_info_is_through::<PathBuf>();
assert_dynamic_through_type_is_val_info::<PathBuf>();
assert_type_info_is_through::<OsString>();
assert_dynamic_through_type_is_val_info::<OsString>();
assert_type_info_is_through::<char>();
assert_dynamic_through_type_is_val_info::<char>();
assert_type_info_is_through::<ReflectReference>();
assert_dynamic_through_type_is_val_info::<ReflectReference>();
assert_type_info_is_through::<&'static str>();
assert_dynamic_through_type_is_val_info::<&'static str>();
}

#[test]
Expand Down Expand Up @@ -281,4 +372,37 @@ mod test {
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(..))
));
}

#[test]
fn test_dynamic_typed_wrapper_outer_variant_matches() {
assert!(matches!(
into_through_type_info(Vec::<i32>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(..))
));

assert!(matches!(
into_through_type_info(std::collections::HashMap::<i32, f32>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap(..))
));

assert!(matches!(
into_through_type_info(Result::<i32, InteropError>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::InteropResult(..))
));

assert!(matches!(
into_through_type_info(<[i32; 3]>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Array(..))
));

assert!(matches!(
into_through_type_info(Option::<i32>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Option(..))
));

assert!(matches!(
into_through_type_info(<(i32, f32)>::type_info()),
ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(..))
));
}
}
6 changes: 6 additions & 0 deletions crates/bevy_mod_scripting_core/src/reflection_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ impl<T: PartialReflect + ?Sized> PartialReflectExt for T {

/// Extension trait for TypeInfos providing additional functionality for working with type information.
pub trait TypeInfoExtensions {
/// Returns true if the type is a result.
fn is_result(&self) -> bool;
/// Returns the inner type of the map if the type is a map, otherwise
fn map_inner_types(&self) -> Option<(TypeId, TypeId)>;
/// Returns the inner type of the list if the type is a list, otherwise None.
Expand All @@ -460,6 +462,10 @@ impl TypeInfoExtensions for TypeInfo {
self.is_type(Some("core"), "Option")
}

fn is_result(&self) -> bool {
self.is_type(Some("core"), "Result")
}

fn is_list(&self) -> bool {
matches!(self, TypeInfo::List(_))
}
Expand Down
Loading
Loading