From ae442da145263d0a08c2f0a496be15d26c121d9b Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 16 Mar 2025 21:58:23 +0000 Subject: [PATCH 01/51] chore: Update 0.10.0.md --- release-notes/0.10.0.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/release-notes/0.10.0.md b/release-notes/0.10.0.md index b5da876113..644422d4b3 100644 --- a/release-notes/0.10.0.md +++ b/release-notes/0.10.0.md @@ -49,6 +49,10 @@ The LAD format now supports arbitrary `TypedThrough` types as global instances. The the mdbook pre-processor globals page got some love and now inlines links to each type as well as splits up the section to be more readable: ![423247897-1e9d4a95-5500-4001-8ef2-7372ae3dc56b](https://github.com/user-attachments/assets/b1ec947e-1f77-4abb-9ad4-f8d60cf96162) +### Other +- Fixed issue where unit enum variants would be converted to `nil` +- `ScriptComponent` now implements `Reflect` (Thanks @Peepo-Juice) + ## Changelog See a detailed changelog [here](https://github.com/makspll/bevy_mod_scripting/blob/main/CHANGELOG.md) From 19243ab61c854ed5653644b7c57a7123579dbf7a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Mar 2025 22:01:21 +0000 Subject: [PATCH 02/51] chore: Update coverage badge (#374) Updates coverage badge based on test results Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- badges/coverage.svg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/badges/coverage.svg b/badges/coverage.svg index 1d7a89c2e5..72da280085 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1,13 +1,13 @@ - COVERAGE: 73% + xmlns:xlink="http://www.w3.org/1999/xlink" width="142.5" height="28" role="img" aria-label="COVERAGE: 74%"> + COVERAGE: 74% COVERAGE - 73% + 74% \ No newline at end of file From 651bcd3a9ef897de5065044556ea9c15156abb2f Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 16 Mar 2025 22:26:04 +0000 Subject: [PATCH 03/51] chore: Trigger docs publish --- docs/src/SUMMARY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 601eacc155..9ca89fa9b6 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -35,6 +35,7 @@ - [Evaluating Feasibility](./Development/AddingLanguages/evaluating-feasibility.md) - [Necessary Features](./Development/AddingLanguages/necessary-features.md) -# WIP +# LAD docs (WIP) - [Generated Docs](./ladfiles/bindings.lad.json) + From 65042d1925ea64ebd43afbb73b3fd9e78b3e7fbb Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Wed, 19 Mar 2025 19:07:12 +0000 Subject: [PATCH 04/51] feat: overhaul mdbook preprocessor, prettify generated docs, support dummy globals (#377) # Summary - Completely overhauls the mdbook preprocessor. - Adds `dummy` globals to the globals registry for the purposes of documenting `entity`, `world`, etc. - Moves all of the manually written docs into the generated docs, and removes the `WIP` section - Type and instance lists are formatted better now - Adds markings in the LAD format for `importance` and `generated` types - Types in the generated docs are ordered according to their importance, core types are placed ahead of most types, and user marked `significant` types are placed even higher - Non reflected types are supported by the lad builder - Links in generic `TypedThrough` types are generated at each non generic type ![image](https://github.com/user-attachments/assets/a4bfa889-771d-4faf-825d-29143851aeb6) ![image](https://github.com/user-attachments/assets/0abadcd9-e0c4-4b4c-a7da-3f46029ad530) ![image](https://github.com/user-attachments/assets/b3fbca0b-1564-4523-bfb9-b88e624c32ae) ![image](https://github.com/user-attachments/assets/dab70e3c-e531-44ec-a1ff-ddba6e12f42c) --- crates/bevy_api_gen/templates/footer.tera | 3 +- .../src/bindings/globals/core.rs | 92 ++- .../src/bindings/globals/mod.rs | 46 +- .../src/bindings/mod.rs | 1 + .../src/bindings/query.rs | 32 +- .../src/bindings/reference.rs | 9 +- .../src/bindings/type_data.rs | 42 ++ .../src/docgen/typed_through.rs | 45 +- .../src/derive/script_bindings.rs | 56 ++ crates/bevy_mod_scripting_derive/src/lib.rs | 4 + .../src/bevy_bindings/bevy_core.rs | 3 +- .../src/bevy_bindings/bevy_ecs.rs | 36 +- .../src/bevy_bindings/bevy_hierarchy.rs | 9 +- .../src/bevy_bindings/bevy_input.rs | 120 ++-- .../src/bevy_bindings/bevy_math.rs | 165 +++-- .../src/bevy_bindings/bevy_reflect.rs | 162 +++-- .../src/bevy_bindings/bevy_time.rs | 18 +- .../src/bevy_bindings/bevy_transform.rs | 6 +- .../bevy_mod_scripting_functions/src/core.rs | 633 ++++++++++++++---- .../src/argument_visitor.rs | 47 +- .../mdbook_lad_preprocessor/src/lib.rs | 40 +- .../mdbook_lad_preprocessor/src/markdown.rs | 344 ++++++---- .../mdbook_lad_preprocessor/src/sections.rs | 481 ++++++++----- .../example_ladfile/expected/parent/lad.md | 3 +- .../expected/parent/lad/functions.md | 9 +- .../parent/lad/functions/hello_world.md | 11 +- .../expected/parent/lad/globals.md | 7 +- .../expected/parent/lad/types.md | 13 +- crates/ladfile/src/lib.rs | 38 +- crates/ladfile/test_assets/test.lad.json | 70 +- crates/ladfile_builder/src/lib.rs | 202 +++++- crates/ladfile_builder/src/plugin.rs | 52 +- docs/generated_docs.html | 237 +++++++ docs/src/SUMMARY.md | 14 +- .../ScriptingReference/reflect-reference.md | 239 ------- .../script-component-registration.md | 39 -- .../script-query-builder.md | 83 --- .../ScriptingReference/script-query-result.md | 41 -- .../script-resource-registration.md | 39 -- .../script-type-registration.md | 79 --- docs/src/ScriptingReference/world.md | 325 --------- 41 files changed, 2275 insertions(+), 1620 deletions(-) create mode 100644 crates/bevy_mod_scripting_core/src/bindings/type_data.rs create mode 100644 docs/generated_docs.html delete mode 100644 docs/src/ScriptingReference/reflect-reference.md delete mode 100644 docs/src/ScriptingReference/script-component-registration.md delete mode 100644 docs/src/ScriptingReference/script-query-builder.md delete mode 100644 docs/src/ScriptingReference/script-query-result.md delete mode 100644 docs/src/ScriptingReference/script-resource-registration.md delete mode 100644 docs/src/ScriptingReference/script-type-registration.md delete mode 100644 docs/src/ScriptingReference/world.md diff --git a/crates/bevy_api_gen/templates/footer.tera b/crates/bevy_api_gen/templates/footer.tera index 8f333ca65a..4c8ea6ada3 100644 --- a/crates/bevy_api_gen/templates/footer.tera +++ b/crates/bevy_api_gen/templates/footer.tera @@ -11,7 +11,8 @@ pub struct {{ "ScriptingPlugin" | prefix_cratename | convert_case(case="upper_ca #[script_bindings( remote, name = "{{ item.ident | convert_case(case="snake") }}_functions", - bms_core_path="bevy_mod_scripting_core" + bms_core_path="bevy_mod_scripting_core", + generated )] impl {{item.import_path}} { {% for function in item.functions %} diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index ec3e2655e6..5dee9ee92e 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -2,10 +2,21 @@ use std::{collections::HashMap, sync::Arc}; -use bevy::{app::Plugin, ecs::reflect::AppTypeRegistry}; +use bevy::{ + app::Plugin, + ecs::{entity::Entity, reflect::AppTypeRegistry, world::World}, +}; use bevy_mod_scripting_derive::script_globals; -use crate::{bindings::{function::from::{Union, Val}, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, WorldGuard}, docgen::into_through_type_info, error::InteropError}; +use crate::{ + bindings::{ + function::from::{Union, Val}, + ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, + WorldGuard, + }, + docgen::into_through_type_info, + error::InteropError, +}; use super::AppScriptGlobalsRegistry; @@ -22,54 +33,67 @@ impl Plugin for CoreScriptGlobalsPlugin { fn register_static_core_globals(world: &mut bevy::ecs::world::World) { let global_registry = world - .get_resource_or_init::() - .clone(); - let type_registry = world - .get_resource_or_init::() - .clone(); - let mut global_registry = global_registry.write(); - let type_registry = type_registry.read(); + .get_resource_or_init::() + .clone(); + let type_registry = world.get_resource_or_init::().clone(); + let mut global_registry = global_registry.write(); + let type_registry = type_registry.read(); - // find all reflectable types without generics - for registration in type_registry.iter() { - if !registration.type_info().generics().is_empty() { - continue; - } + // find all reflectable types without generics + for registration in type_registry.iter() { + if !registration.type_info().generics().is_empty() { + continue; + } - 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(), - into_through_type_info(type_info), - global_name.into(), - documentation.into(), - ); - } + 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(), + into_through_type_info(type_info), + global_name.into(), + documentation.into(), + ); } + } + + // register basic globals + global_registry.register_dummy::("world", "The current ECS world."); + global_registry + .register_dummy::("entity", "The entity this script is attached to if any."); + global_registry.register_dummy::("script_id", "the name/id of this script"); } -#[script_globals( - bms_core_path = "crate", - name = "core_globals", -)] +#[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, Union, Val>>>, InteropError> { + fn types( + guard: WorldGuard, + ) -> Result< + HashMap< + String, + Union< + Val, + Union, Val>, + >, + >, + InteropError, + > { let type_registry = guard.type_registry(); let type_registry = type_registry.read(); let mut type_cache = HashMap::::default(); - for registration in type_registry.iter(){ + 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)); + 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) } -} \ No newline at end of file +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs index 649620903c..a1f9d7d6ab 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs @@ -5,12 +5,15 @@ use super::{ script_value::ScriptValue, WorldGuard, }; -use crate::{docgen::{into_through_type_info, typed_through::ThroughTypeInfo}, error::InteropError}; +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}; -crate::private::export_all_in_modules!{ +crate::private::export_all_in_modules! { core } @@ -48,10 +51,22 @@ pub struct ScriptGlobal { pub type_information: ThroughTypeInfo, } +/// A dummy global variable that documents globals set via alternative ways. +pub struct ScriptGlobalDummy { + /// The type ID of the global variable. + pub type_id: TypeId, + /// Rich type information the global variable. + pub type_information: Option, + + /// The documentation for the global dummy variable. + pub documentation: Option>, +} + /// A registry of global variables that can be exposed to scripts. #[derive(Default)] pub struct ScriptGlobalsRegistry { globals: HashMap, ScriptGlobal>, + dummies: HashMap, ScriptGlobalDummy>, } impl ScriptGlobalsRegistry { @@ -85,6 +100,11 @@ impl ScriptGlobalsRegistry { self.globals.iter_mut() } + /// Iterates over the dummies in the registry + pub fn iter_dummies(&self) -> impl Iterator, &ScriptGlobalDummy)> { + self.dummies.iter() + } + fn type_erase_maker< T: ScriptReturn, F: Fn(WorldGuard) -> Result + Send + Sync + 'static, @@ -114,6 +134,26 @@ impl ScriptGlobalsRegistry { ) } + /// Registers a dummy global into the registry. + /// Dummies are not actually exposed to languages but exist purely for the purpose of documentation. + /// This can be useful for globals which you cannot expose normally. + /// + /// Dummy globals are stored as non-static instances, i.e. they're expected to be values not type references. + pub fn register_dummy( + &mut self, + name: impl Into>, + documentation: impl Into>, + ) { + self.dummies.insert( + name.into(), + ScriptGlobalDummy { + documentation: Some(documentation.into()), + type_id: TypeId::of::(), + type_information: None, + }, + ); + } + /// Inserts a global into the registry, returns the previous value if it existed. /// /// This is a version of [`Self::register`] which stores type information regarding the global. @@ -153,7 +193,7 @@ impl ScriptGlobalsRegistry { /// 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( + pub fn register_static_documented( &mut self, name: Cow<'static, str>, documentation: Cow<'static, str>, diff --git a/crates/bevy_mod_scripting_core/src/bindings/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/mod.rs index 90bdcb0c36..6cfb01fc35 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/mod.rs @@ -12,4 +12,5 @@ crate::private::export_all_in_modules! { script_system, script_value, world, + type_data } diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 06cd380776..648b71bd38 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -14,9 +14,9 @@ use bevy::{ }; use std::{any::TypeId, collections::VecDeque, sync::Arc}; -/// A wrapper around a `TypeRegistration` that provides additional information about the type. +/// A reference to a type which is not a `Resource` or `Component`. /// -/// This is used as a hook to a rust type from a scripting language. We should be able to easily convert between a type name and a [`ScriptTypeRegistration`]. +/// In general think of this as a handle to a type. #[derive(Clone, Reflect)] #[reflect(opaque)] pub struct ScriptTypeRegistration { @@ -24,14 +24,18 @@ pub struct ScriptTypeRegistration { } #[derive(Clone, Reflect, Debug)] -/// A registration for a component type. +/// A reference to a component type's reflection registration. +/// +/// In general think of this as a handle to a type. pub struct ScriptComponentRegistration { pub(crate) registration: ScriptTypeRegistration, pub(crate) component_id: ComponentId, } #[derive(Clone, Reflect, Debug)] -/// A registration for a resource type. +/// A reference to a resource type's reflection registration. +/// +/// In general think of this as a handle to a type. pub struct ScriptResourceRegistration { pub(crate) registration: ScriptTypeRegistration, pub(crate) resource_id: ComponentId, @@ -134,7 +138,25 @@ impl std::fmt::Display for ScriptTypeRegistration { #[derive(Clone, Default, Reflect)] #[reflect(opaque)] -/// A builder for a query. +/// The query builder is used to build ECS queries which retrieve spefific components filtered by specific conditions. +/// +/// For example: +/// ```rust,ignore +/// builder.component(componentA) +/// .component(componentB) +/// .with(componentC) +/// .without(componentD) +/// ``` +/// +/// Will retrieve entities which: +/// - Have componentA +/// - Have componentB +/// - Have componentC +/// - Do not have componentD +/// +/// As well as references to components: +/// - componentA +/// - componentB pub struct ScriptQueryBuilder { pub(crate) components: Vec, with: Vec, diff --git a/crates/bevy_mod_scripting_core/src/bindings/reference.rs b/crates/bevy_mod_scripting_core/src/bindings/reference.rs index a574a393f1..5319e08432 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/reference.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/reference.rs @@ -22,8 +22,13 @@ use bevy::{ }; use std::{any::TypeId, fmt::Debug}; -/// An accessor to a `dyn PartialReflect` struct, stores a base ID of the type and a reflection path -/// safe to build but to reflect on the value inside you need to ensure aliasing rules are upheld +/// A reference to an arbitrary reflected instance. +/// +/// The reference can point to either the ECS, or to the allocator. +/// +/// References are composed of two parts: +/// - The base kind, which specifies where the reference points to +/// - The path, which specifies how to access the value from the base #[derive(Debug, Clone, PartialEq, Eq, Reflect)] #[reflect(Default, opaque)] pub struct ReflectReference { diff --git a/crates/bevy_mod_scripting_core/src/bindings/type_data.rs b/crates/bevy_mod_scripting_core/src/bindings/type_data.rs new file mode 100644 index 0000000000..dd3b06e12d --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/type_data.rs @@ -0,0 +1,42 @@ +//! Contains various `Reflect` type data we use in BMS. + +use bevy::reflect::FromType; + +/// A marker type to indicate that a type is generated. +/// +/// To mark a type as generated use: +/// ```rust,ignore +/// registry.register_type_data::(); +/// ``` +#[derive(Clone, Copy)] +pub struct MarkAsGenerated; + +impl FromType for MarkAsGenerated { + fn from_type() -> Self { + Self + } +} + +/// A marker type to indicate that a type is significant. +/// +/// Significant types appear "before" core types in documentation +#[derive(Clone, Copy)] +pub struct MarkAsSignificant; + +impl FromType for MarkAsSignificant { + fn from_type() -> Self { + Self + } +} + +/// A marker type to indicate that a type is core to BMS. +/// +/// core types appear before insignificant types in documentation. +#[derive(Clone, Copy)] +pub struct MarkAsCore; + +impl FromType for MarkAsCore { + fn from_type() -> Self { + Self + } +} diff --git a/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs index d2f5d33e9b..7e467b6180 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs @@ -16,7 +16,8 @@ use crate::{ script_value::ScriptValue, ReflectReference, }, - error::InteropError, reflection_extensions::TypeInfoExtensions, + error::InteropError, + reflection_extensions::TypeInfoExtensions, }; /// All Through types follow one rule: @@ -75,8 +76,7 @@ pub enum TypedWrapperKind { /// 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 = (||{ + let option = (|| { if let Ok(array) = type_info.as_array() { let len = array.capacity(); let inner = array.item_info()?; @@ -84,10 +84,10 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { Box::new(into_through_type_info(inner)), len, ))); - } else if let Ok(hash_map) = type_info.as_map() { + } 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)), @@ -97,7 +97,7 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec( Box::new(into_through_type_info(inner)), ))); - } else if type_info.is_option(){ + } 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()?; @@ -110,14 +110,14 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { 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)), - ))); + 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() { @@ -125,12 +125,17 @@ pub fn into_through_type_info(type_info: &'static TypeInfo) -> ThroughTypeInfo { let field_type = field.type_info()?; tuple_types.push(into_through_type_info(field_type)); } - return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(tuple_types))); + return Some(ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple( + tuple_types, + ))); } None })(); - - option.unwrap_or(ThroughTypeInfo::UntypedWrapper { through_type: type_info, wrapper_kind: UntypedWrapperKind::Val }) + + option.unwrap_or(ThroughTypeInfo::UntypedWrapper { + through_type: type_info, + wrapper_kind: UntypedWrapperKind::Val, + }) } /// A trait for types that can be converted to a [`ThroughTypeInfo`]. @@ -262,7 +267,6 @@ macro_rules! impl_through_typed_tuple { bevy::utils::all_tuples!(impl_through_typed_tuple, 0, 13, T); - #[cfg(test)] mod test { use super::*; @@ -280,12 +284,15 @@ mod test { } } - fn assert_dynamic_through_type_is_val_info () { + fn assert_dynamic_through_type_is_val_info() { 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} => { + 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()); @@ -379,12 +386,12 @@ mod test { into_through_type_info(Vec::::type_info()), ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Vec(..)) )); - + assert!(matches!( into_through_type_info(std::collections::HashMap::::type_info()), ThroughTypeInfo::TypedWrapper(TypedWrapperKind::HashMap(..)) )); - + assert!(matches!( into_through_type_info(Result::::type_info()), ThroughTypeInfo::TypedWrapper(TypedWrapperKind::InteropResult(..)) @@ -403,6 +410,6 @@ mod test { assert!(matches!( into_through_type_info(<(i32, f32)>::type_info()), ThroughTypeInfo::TypedWrapper(TypedWrapperKind::Tuple(..)) - )); + )); } } diff --git a/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs b/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs index 8dd1569519..df4d9b957b 100644 --- a/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs +++ b/crates/bevy_mod_scripting_derive/src/derive/script_bindings.rs @@ -51,10 +51,45 @@ pub fn script_bindings( format_ident!("new") }; + let mark_as_generated = if args.generated { + quote_spanned! {impl_span=> + + let registry = world.get_resource_or_init::(); + let mut registry = registry.write(); + registry.register_type_data::<#type_ident_with_generics, #bms_core_path::bindings::MarkAsGenerated>(); + } + } else { + Default::default() + }; + + let mark_as_core = if bms_core_path.is_ident("crate") || args.core { + quote_spanned! {impl_span=> + let registry = world.get_resource_or_init::(); + let mut registry = registry.write(); + registry.register_type_data::<#type_ident_with_generics, #bms_core_path::bindings::MarkAsCore>(); + } + } else { + Default::default() + }; + + let mark_as_significant = if args.significant { + quote_spanned! {impl_span=> + let registry = world.get_resource_or_init::(); + let mut registry = registry.write(); + registry.register_type_data::<#type_ident_with_generics, #bms_core_path::bindings::MarkAsSignificant>(); + } + } else { + Default::default() + }; + let out = quote_spanned! {impl_span=> #visibility fn #function_name(world: &mut bevy::ecs::world::World) { #bms_core_path::bindings::function::namespace::NamespaceBuilder::<#type_ident_with_generics>::#builder_function_name(world) #(#function_registrations)*; + + #mark_as_generated + #mark_as_core + #mark_as_significant } #impl_block @@ -72,6 +107,12 @@ struct Args { pub bms_core_path: syn::Path, /// If true will use `new_unregistered` instead of `new` for the namespace builder pub unregistered: bool, + /// If true registers a marker type against the type registry to state that the type is generated (if unregistered is not set) + pub generated: bool, + /// If true registers a marker type against the type registry to state that the type is core to BMS (if unregistered is not set) + pub core: bool, + /// If true registers a marker type against the type registry to state that the type is significant (if unregistered is not set) + pub significant: bool, } impl syn::parse::Parse for Args { @@ -83,6 +124,9 @@ impl syn::parse::Parse for Args { let mut name = syn::Ident::new("functions", Span::call_site()); let mut remote = false; let mut unregistered = false; + let mut generated = false; + let mut core = false; + let mut significant = false; let mut bms_core_path = syn::Path::from(syn::Ident::new("bevy_mod_scripting", Span::call_site())); bms_core_path.segments.push(syn::PathSegment { @@ -99,6 +143,15 @@ impl syn::parse::Parse for Args { } else if path.is_ident("unregistered") { unregistered = true; continue; + } else if path.is_ident("generated") { + generated = true; + continue; + } else if path.is_ident("core") { + core = true; + continue; + } else if path.is_ident("significant") { + significant = true; + continue; } } syn::Meta::NameValue(name_value) => { @@ -136,6 +189,9 @@ impl syn::parse::Parse for Args { bms_core_path, name, unregistered, + generated, + core, + significant, }) } } diff --git a/crates/bevy_mod_scripting_derive/src/lib.rs b/crates/bevy_mod_scripting_derive/src/lib.rs index ed091dc6dc..b3a8620c6a 100644 --- a/crates/bevy_mod_scripting_derive/src/lib.rs +++ b/crates/bevy_mod_scripting_derive/src/lib.rs @@ -25,6 +25,10 @@ pub fn into_script(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// - `remote`: If true the original impl block will be ignored, and only the function registrations will be generated /// - `bms_core_path`: If set the path to override bms imports, normally only used internally /// - `unregistered`: If set, will use `new_unregistered` instead of `new` for the namespace builder +/// - `core`: If set, marks the type as `core` using the `MarkAsCore` type data +/// - `significant`: If set, marks the type as `significant` using the `MarkAsSignificant` type data +/// +/// It is encouraged to place `significant` markers on your own types, for the purposes of documentation generation. #[proc_macro_attribute] pub fn script_bindings( args: proc_macro::TokenStream, diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs index 5a74b8c8b8..2d2e6ee754 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs @@ -15,7 +15,8 @@ pub struct BevyCoreScriptingPlugin; #[script_bindings( remote, name = "name_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::core::prelude::Name { fn clone(_self: Ref) -> Val { diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_ecs.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_ecs.rs index 2e7bd6901b..aabe3ae722 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_ecs.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_ecs.rs @@ -15,7 +15,8 @@ pub struct BevyEcsScriptingPlugin; #[script_bindings( remote, name = "entity_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::entity::Entity { fn clone(_self: Ref) -> Val { @@ -90,31 +91,36 @@ impl bevy::ecs::entity::Entity { #[script_bindings( remote, name = "on_add_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::world::OnAdd {} #[script_bindings( remote, name = "on_insert_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::world::OnInsert {} #[script_bindings( remote, name = "on_remove_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::world::OnRemove {} #[script_bindings( remote, name = "on_replace_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::world::OnReplace {} #[script_bindings( remote, name = "component_id_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::component::ComponentId { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -163,7 +169,8 @@ impl bevy::ecs::component::ComponentId { #[script_bindings( remote, name = "tick_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::component::Tick { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -227,7 +234,8 @@ impl bevy::ecs::component::Tick { #[script_bindings( remote, name = "component_ticks_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::component::ComponentTicks { fn clone( @@ -305,7 +313,8 @@ impl bevy::ecs::component::ComponentTicks { #[script_bindings( remote, name = "identifier_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::identifier::Identifier { fn clone( @@ -362,7 +371,8 @@ impl bevy::ecs::identifier::Identifier { #[script_bindings( remote, name = "entity_hash_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::entity::EntityHash { fn clone( @@ -378,7 +388,8 @@ impl bevy::ecs::entity::EntityHash { #[script_bindings( remote, name = "removed_component_entity_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::removal_detection::RemovedComponentEntity { fn clone( @@ -394,7 +405,8 @@ impl bevy::ecs::removal_detection::RemovedComponentEntity { #[script_bindings( remote, name = "system_id_marker_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::ecs::system::SystemIdMarker {} impl ::bevy::app::Plugin for BevyEcsScriptingPlugin { diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs index 2c730db0b1..529c8c7f5d 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs @@ -15,7 +15,8 @@ pub struct BevyHierarchyScriptingPlugin; #[script_bindings( remote, name = "children_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::hierarchy::prelude::Children { /// Swaps the child at `a_index` with the child at `b_index`. @@ -36,7 +37,8 @@ impl bevy::hierarchy::prelude::Children { #[script_bindings( remote, name = "parent_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::hierarchy::prelude::Parent { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -70,7 +72,8 @@ impl bevy::hierarchy::prelude::Parent { #[script_bindings( remote, name = "hierarchy_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::hierarchy::HierarchyEvent { fn assert_receiver_is_total_eq(_self: Ref) -> () { diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs index ab06dabc55..712fa729dc 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs @@ -15,7 +15,8 @@ pub struct BevyInputScriptingPlugin; #[script_bindings( remote, name = "gamepad_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::Gamepad { /// Returns the directional pad as a [`Vec2`] @@ -101,7 +102,8 @@ impl bevy::input::gamepad::Gamepad { #[script_bindings( remote, name = "gamepad_axis_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadAxis { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -134,7 +136,8 @@ impl bevy::input::gamepad::GamepadAxis { #[script_bindings( remote, name = "gamepad_button_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadButton { fn assert_receiver_is_total_eq( @@ -169,7 +172,8 @@ impl bevy::input::gamepad::GamepadButton { #[script_bindings( remote, name = "gamepad_settings_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadSettings { fn clone( @@ -185,7 +189,8 @@ impl bevy::input::gamepad::GamepadSettings { #[script_bindings( remote, name = "key_code_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::KeyCode { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -218,7 +223,8 @@ impl bevy::input::keyboard::KeyCode { #[script_bindings( remote, name = "mouse_button_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::MouseButton { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -251,7 +257,8 @@ impl bevy::input::mouse::MouseButton { #[script_bindings( remote, name = "touch_input_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::touch::TouchInput { fn clone( @@ -277,7 +284,8 @@ impl bevy::input::touch::TouchInput { #[script_bindings( remote, name = "keyboard_focus_lost_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::KeyboardFocusLost { fn assert_receiver_is_total_eq( @@ -312,7 +320,8 @@ impl bevy::input::keyboard::KeyboardFocusLost { #[script_bindings( remote, name = "keyboard_input_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::KeyboardInput { fn assert_receiver_is_total_eq( @@ -347,7 +356,8 @@ impl bevy::input::keyboard::KeyboardInput { #[script_bindings( remote, name = "accumulated_mouse_motion_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::AccumulatedMouseMotion { fn clone( @@ -373,7 +383,8 @@ impl bevy::input::mouse::AccumulatedMouseMotion { #[script_bindings( remote, name = "accumulated_mouse_scroll_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::AccumulatedMouseScroll { fn clone( @@ -399,7 +410,8 @@ impl bevy::input::mouse::AccumulatedMouseScroll { #[script_bindings( remote, name = "mouse_button_input_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::MouseButtonInput { fn assert_receiver_is_total_eq( @@ -434,7 +446,8 @@ impl bevy::input::mouse::MouseButtonInput { #[script_bindings( remote, name = "mouse_motion_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::MouseMotion { fn clone( @@ -460,7 +473,8 @@ impl bevy::input::mouse::MouseMotion { #[script_bindings( remote, name = "mouse_wheel_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::MouseWheel { fn clone( @@ -486,7 +500,8 @@ impl bevy::input::mouse::MouseWheel { #[script_bindings( remote, name = "gamepad_axis_changed_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadAxisChangedEvent { fn clone( @@ -526,7 +541,8 @@ impl bevy::input::gamepad::GamepadAxisChangedEvent { #[script_bindings( remote, name = "gamepad_button_changed_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadButtonChangedEvent { fn clone( @@ -568,7 +584,8 @@ impl bevy::input::gamepad::GamepadButtonChangedEvent { #[script_bindings( remote, name = "gamepad_button_state_changed_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadButtonStateChangedEvent { fn assert_receiver_is_total_eq( @@ -617,7 +634,8 @@ impl bevy::input::gamepad::GamepadButtonStateChangedEvent { #[script_bindings( remote, name = "gamepad_connection_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadConnection { fn clone( @@ -643,7 +661,8 @@ impl bevy::input::gamepad::GamepadConnection { #[script_bindings( remote, name = "gamepad_connection_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadConnectionEvent { fn clone( @@ -697,7 +716,8 @@ impl bevy::input::gamepad::GamepadConnectionEvent { #[script_bindings( remote, name = "gamepad_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadEvent { fn clone( @@ -723,7 +743,8 @@ impl bevy::input::gamepad::GamepadEvent { #[script_bindings( remote, name = "gamepad_input_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadInput { fn assert_receiver_is_total_eq( @@ -758,7 +779,8 @@ impl bevy::input::gamepad::GamepadInput { #[script_bindings( remote, name = "gamepad_rumble_request_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadRumbleRequest { fn clone( @@ -784,7 +806,8 @@ impl bevy::input::gamepad::GamepadRumbleRequest { #[script_bindings( remote, name = "raw_gamepad_axis_changed_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::RawGamepadAxisChangedEvent { fn clone( @@ -824,7 +847,8 @@ impl bevy::input::gamepad::RawGamepadAxisChangedEvent { #[script_bindings( remote, name = "raw_gamepad_button_changed_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::RawGamepadButtonChangedEvent { fn clone( @@ -864,7 +888,8 @@ impl bevy::input::gamepad::RawGamepadButtonChangedEvent { #[script_bindings( remote, name = "raw_gamepad_event_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::RawGamepadEvent { fn clone( @@ -890,7 +915,8 @@ impl bevy::input::gamepad::RawGamepadEvent { #[script_bindings( remote, name = "pinch_gesture_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gestures::PinchGesture { fn clone( @@ -916,7 +942,8 @@ impl bevy::input::gestures::PinchGesture { #[script_bindings( remote, name = "rotation_gesture_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gestures::RotationGesture { fn clone( @@ -942,7 +969,8 @@ impl bevy::input::gestures::RotationGesture { #[script_bindings( remote, name = "double_tap_gesture_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gestures::DoubleTapGesture { fn clone( @@ -968,7 +996,8 @@ impl bevy::input::gestures::DoubleTapGesture { #[script_bindings( remote, name = "pan_gesture_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gestures::PanGesture { fn clone( @@ -994,7 +1023,8 @@ impl bevy::input::gestures::PanGesture { #[script_bindings( remote, name = "button_state_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::ButtonState { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -1030,7 +1060,8 @@ impl bevy::input::ButtonState { #[script_bindings( remote, name = "button_settings_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::ButtonSettings { fn clone( @@ -1119,7 +1150,8 @@ impl bevy::input::gamepad::ButtonSettings { #[script_bindings( remote, name = "axis_settings_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::AxisSettings { /// Clamps the `raw_value` according to the `AxisSettings`. @@ -1270,7 +1302,8 @@ impl bevy::input::gamepad::AxisSettings { #[script_bindings( remote, name = "button_axis_settings_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::ButtonAxisSettings { fn clone( @@ -1302,7 +1335,8 @@ impl bevy::input::gamepad::ButtonAxisSettings { #[script_bindings( remote, name = "gamepad_rumble_intensity_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::gamepad::GamepadRumbleIntensity { fn clone( @@ -1348,7 +1382,8 @@ impl bevy::input::gamepad::GamepadRumbleIntensity { #[script_bindings( remote, name = "key_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::Key { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -1379,7 +1414,8 @@ impl bevy::input::keyboard::Key { #[script_bindings( remote, name = "native_key_code_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::NativeKeyCode { fn assert_receiver_is_total_eq( @@ -1414,7 +1450,8 @@ impl bevy::input::keyboard::NativeKeyCode { #[script_bindings( remote, name = "native_key_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::keyboard::NativeKey { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -1447,7 +1484,8 @@ impl bevy::input::keyboard::NativeKey { #[script_bindings( remote, name = "mouse_scroll_unit_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::mouse::MouseScrollUnit { fn assert_receiver_is_total_eq( @@ -1482,7 +1520,8 @@ impl bevy::input::mouse::MouseScrollUnit { #[script_bindings( remote, name = "touch_phase_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::touch::TouchPhase { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -1515,7 +1554,8 @@ impl bevy::input::touch::TouchPhase { #[script_bindings( remote, name = "force_touch_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::input::touch::ForceTouch { fn clone( diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs index c9a4eb3108..6a65dc0527 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs @@ -15,7 +15,8 @@ pub struct BevyMathScriptingPlugin; #[script_bindings( remote, name = "aspect_ratio_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::AspectRatio { fn clone(_self: Ref) -> Val { @@ -67,7 +68,8 @@ impl bevy::math::AspectRatio { #[script_bindings( remote, name = "compass_octant_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::CompassOctant { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -98,7 +100,8 @@ impl bevy::math::CompassOctant { #[script_bindings( remote, name = "compass_quadrant_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::CompassQuadrant { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -131,7 +134,8 @@ impl bevy::math::CompassQuadrant { #[script_bindings( remote, name = "isometry_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Isometry2d { fn clone(_self: Ref) -> Val { @@ -268,7 +272,8 @@ impl bevy::math::Isometry2d { #[script_bindings( remote, name = "isometry_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Isometry3d { fn clone(_self: Ref) -> Val { @@ -372,7 +377,8 @@ impl bevy::math::Isometry3d { #[script_bindings( remote, name = "ray_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Ray2d { fn clone(_self: Ref) -> Val { @@ -431,7 +437,8 @@ impl bevy::math::Ray2d { #[script_bindings( remote, name = "ray_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Ray3d { fn clone(_self: Ref) -> Val { @@ -490,7 +497,8 @@ impl bevy::math::Ray3d { #[script_bindings( remote, name = "rot_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Rot2 { /// Returns the angle in radians needed to make `self` and `other` coincide. @@ -790,7 +798,8 @@ impl bevy::math::Rot2 { #[script_bindings( remote, name = "dir_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::Dir2 { /// Returns the inner [`Vec2`] @@ -960,7 +969,8 @@ impl bevy::math::prelude::Dir2 { #[script_bindings( remote, name = "dir_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::Dir3 { /// Returns the inner [`Vec3`] @@ -1101,7 +1111,8 @@ impl bevy::math::prelude::Dir3 { #[script_bindings( remote, name = "dir_3_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::Dir3A { /// Returns the inner [`Vec3A`] @@ -1213,7 +1224,8 @@ impl bevy::math::prelude::Dir3A { #[script_bindings( remote, name = "i_rect_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::IRect { /// Returns self as [`Rect`] (f32) @@ -1547,7 +1559,8 @@ impl bevy::math::prelude::IRect { #[script_bindings( remote, name = "rect_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::Rect { /// Returns self as [`IRect`] (i32) @@ -1890,7 +1903,8 @@ impl bevy::math::prelude::Rect { #[script_bindings( remote, name = "u_rect_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::prelude::URect { /// Returns self as [`IRect`] (i32) @@ -2224,13 +2238,15 @@ impl bevy::math::prelude::URect { #[script_bindings( remote, name = "affine_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Affine3 {} #[script_bindings( remote, name = "aabb_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::Aabb2d { /// Computes the smallest [`BoundingCircle`] containing this [`Aabb2d`]. @@ -2282,7 +2298,8 @@ impl bevy::math::bounding::Aabb2d { #[script_bindings( remote, name = "bounding_circle_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::BoundingCircle { /// Computes the smallest [`Aabb2d`] containing this [`BoundingCircle`]. @@ -2339,7 +2356,8 @@ impl bevy::math::bounding::BoundingCircle { #[script_bindings( remote, name = "circle_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Circle { fn clone( @@ -2392,7 +2410,8 @@ impl bevy::math::primitives::Circle { #[script_bindings( remote, name = "annulus_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Annulus { fn clone( @@ -2455,7 +2474,8 @@ impl bevy::math::primitives::Annulus { #[script_bindings( remote, name = "arc_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Arc2d { /// Get the angle of the arc @@ -2607,7 +2627,8 @@ impl bevy::math::primitives::Arc2d { #[script_bindings( remote, name = "capsule_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Capsule2d { fn clone( @@ -2652,7 +2673,8 @@ impl bevy::math::primitives::Capsule2d { #[script_bindings( remote, name = "circular_sector_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::CircularSector { /// Get the angle of the sector @@ -2785,7 +2807,8 @@ impl bevy::math::primitives::CircularSector { #[script_bindings( remote, name = "circular_segment_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::CircularSegment { /// Get the angle of the segment @@ -2918,7 +2941,8 @@ impl bevy::math::primitives::CircularSegment { #[script_bindings( remote, name = "ellipse_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Ellipse { fn clone( @@ -2988,7 +3012,8 @@ impl bevy::math::primitives::Ellipse { #[script_bindings( remote, name = "line_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Line2d { fn clone( @@ -3014,7 +3039,8 @@ impl bevy::math::primitives::Line2d { #[script_bindings( remote, name = "plane_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Plane2d { fn clone( @@ -3052,7 +3078,8 @@ impl bevy::math::primitives::Plane2d { #[script_bindings( remote, name = "rectangle_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Rectangle { fn clone( @@ -3142,7 +3169,8 @@ impl bevy::math::primitives::Rectangle { #[script_bindings( remote, name = "regular_polygon_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::RegularPolygon { /// Get the radius of the circumcircle on which all vertices @@ -3252,7 +3280,8 @@ impl bevy::math::primitives::RegularPolygon { #[script_bindings( remote, name = "rhombus_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Rhombus { /// Get the radius of the circumcircle on which all vertices @@ -3337,7 +3366,8 @@ impl bevy::math::primitives::Rhombus { #[script_bindings( remote, name = "segment_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Segment2d { fn clone( @@ -3395,7 +3425,8 @@ impl bevy::math::primitives::Segment2d { #[script_bindings( remote, name = "triangle_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Triangle2d { fn clone( @@ -3469,7 +3500,8 @@ impl bevy::math::primitives::Triangle2d { #[script_bindings( remote, name = "aabb_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::Aabb3d { /// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`]. @@ -3495,7 +3527,8 @@ impl bevy::math::bounding::Aabb3d { #[script_bindings( remote, name = "bounding_sphere_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::BoundingSphere { /// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`]. @@ -3526,7 +3559,8 @@ impl bevy::math::bounding::BoundingSphere { #[script_bindings( remote, name = "sphere_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Sphere { fn clone( @@ -3579,7 +3613,8 @@ impl bevy::math::primitives::Sphere { #[script_bindings( remote, name = "cuboid_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Cuboid { fn clone( @@ -3674,7 +3709,8 @@ impl bevy::math::primitives::Cuboid { #[script_bindings( remote, name = "cylinder_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Cylinder { /// Get the base of the cylinder as a [`Circle`] @@ -3730,7 +3766,8 @@ impl bevy::math::primitives::Cylinder { #[script_bindings( remote, name = "capsule_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Capsule3d { fn clone( @@ -3776,7 +3813,8 @@ impl bevy::math::primitives::Capsule3d { #[script_bindings( remote, name = "cone_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Cone { /// Get the base of the cone as a [`Circle`] @@ -3838,7 +3876,8 @@ impl bevy::math::primitives::Cone { #[script_bindings( remote, name = "conical_frustum_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::ConicalFrustum { fn clone( @@ -3864,7 +3903,8 @@ impl bevy::math::primitives::ConicalFrustum { #[script_bindings( remote, name = "infinite_plane_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::InfinitePlane3d { fn clone( @@ -3948,7 +3988,8 @@ impl bevy::math::primitives::InfinitePlane3d { #[script_bindings( remote, name = "line_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Line3d { fn clone( @@ -3974,7 +4015,8 @@ impl bevy::math::primitives::Line3d { #[script_bindings( remote, name = "segment_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Segment3d { fn clone( @@ -4032,7 +4074,8 @@ impl bevy::math::primitives::Segment3d { #[script_bindings( remote, name = "torus_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Torus { fn clone( @@ -4083,7 +4126,8 @@ impl bevy::math::primitives::Torus { #[script_bindings( remote, name = "triangle_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Triangle3d { /// Get the centroid of the triangle. @@ -4178,7 +4222,8 @@ impl bevy::math::primitives::Triangle3d { #[script_bindings( remote, name = "ray_cast_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::RayCast2d { /// Get the distance of an intersection with an [`Aabb2d`], if any. @@ -4254,7 +4299,8 @@ impl bevy::math::bounding::RayCast2d { #[script_bindings( remote, name = "aabb_cast_2_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::AabbCast2d { /// Get the distance at which the [`Aabb2d`]s collide, if at all. @@ -4312,7 +4358,8 @@ impl bevy::math::bounding::AabbCast2d { #[script_bindings( remote, name = "bounding_circle_cast_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::BoundingCircleCast { /// Get the distance at which the [`BoundingCircle`]s collide, if at all. @@ -4370,7 +4417,8 @@ impl bevy::math::bounding::BoundingCircleCast { #[script_bindings( remote, name = "ray_cast_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::RayCast3d { /// Get the distance of an intersection with an [`Aabb3d`], if any. @@ -4432,7 +4480,8 @@ impl bevy::math::bounding::RayCast3d { #[script_bindings( remote, name = "aabb_cast_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::AabbCast3d { /// Get the distance at which the [`Aabb3d`]s collide, if at all. @@ -4474,7 +4523,8 @@ impl bevy::math::bounding::AabbCast3d { #[script_bindings( remote, name = "bounding_sphere_cast_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::bounding::BoundingSphereCast { fn clone( @@ -4516,7 +4566,8 @@ impl bevy::math::bounding::BoundingSphereCast { #[script_bindings( remote, name = "interval_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::curve::interval::Interval { /// Clamp the given `value` to lie within this interval. @@ -4620,7 +4671,8 @@ impl bevy::math::curve::interval::Interval { #[script_bindings( remote, name = "float_ord_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::FloatOrd { fn clone(_self: Ref) -> Val { @@ -4676,7 +4728,8 @@ impl bevy::math::FloatOrd { #[script_bindings( remote, name = "plane_3_d_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Plane3d { fn clone( @@ -4716,7 +4769,8 @@ impl bevy::math::primitives::Plane3d { #[script_bindings( remote, name = "tetrahedron_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::primitives::Tetrahedron { /// Get the centroid of the tetrahedron. @@ -4779,7 +4833,8 @@ impl bevy::math::primitives::Tetrahedron { #[script_bindings( remote, name = "ease_function_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::curve::easing::EaseFunction { fn clone( diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs index 83ad7639a1..303da037a7 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs @@ -15,7 +15,8 @@ pub struct BevyReflectScriptingPlugin; #[script_bindings( remote, name = "atomic_bool_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicBool { /// Consumes the atomic and returns the contained value. @@ -50,7 +51,8 @@ impl std::sync::atomic::AtomicBool { #[script_bindings( remote, name = "atomic_i_16_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicI16 { /// Consumes the atomic and returns the contained value. @@ -84,7 +86,8 @@ impl std::sync::atomic::AtomicI16 { #[script_bindings( remote, name = "atomic_i_32_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicI32 { /// Consumes the atomic and returns the contained value. @@ -118,7 +121,8 @@ impl std::sync::atomic::AtomicI32 { #[script_bindings( remote, name = "atomic_i_64_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicI64 { /// Consumes the atomic and returns the contained value. @@ -152,7 +156,8 @@ impl std::sync::atomic::AtomicI64 { #[script_bindings( remote, name = "atomic_i_8_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicI8 { /// Consumes the atomic and returns the contained value. @@ -186,7 +191,8 @@ impl std::sync::atomic::AtomicI8 { #[script_bindings( remote, name = "atomic_isize_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicIsize { /// Consumes the atomic and returns the contained value. @@ -222,7 +228,8 @@ impl std::sync::atomic::AtomicIsize { #[script_bindings( remote, name = "atomic_u_16_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicU16 { /// Consumes the atomic and returns the contained value. @@ -256,7 +263,8 @@ impl std::sync::atomic::AtomicU16 { #[script_bindings( remote, name = "atomic_u_32_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicU32 { /// Consumes the atomic and returns the contained value. @@ -290,7 +298,8 @@ impl std::sync::atomic::AtomicU32 { #[script_bindings( remote, name = "atomic_u_64_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicU64 { /// Consumes the atomic and returns the contained value. @@ -324,7 +333,8 @@ impl std::sync::atomic::AtomicU64 { #[script_bindings( remote, name = "atomic_u_8_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicU8 { /// Consumes the atomic and returns the contained value. @@ -358,7 +368,8 @@ impl std::sync::atomic::AtomicU8 { #[script_bindings( remote, name = "atomic_usize_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::sync::atomic::AtomicUsize { /// Consumes the atomic and returns the contained value. @@ -394,7 +405,8 @@ impl std::sync::atomic::AtomicUsize { #[script_bindings( remote, name = "duration_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::utils::Duration { /// Computes the absolute difference between `self` and `other`. @@ -932,7 +944,8 @@ impl bevy::utils::Duration { #[script_bindings( remote, name = "instant_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::utils::Instant { /// # Panics @@ -1081,7 +1094,8 @@ impl bevy::utils::Instant { #[script_bindings( remote, name = "range_full_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl std::ops::RangeFull { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -1109,7 +1123,8 @@ impl std::ops::RangeFull { #[script_bindings( remote, name = "quat_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Quat { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -1650,7 +1665,8 @@ impl bevy::math::Quat { #[script_bindings( remote, name = "vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Vec3 { /// Returns a vector containing the absolute value of each element of `self`. @@ -2610,7 +2626,8 @@ impl bevy::math::Vec3 { #[script_bindings( remote, name = "i_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::IVec2 { /// Returns a vector containing the absolute value of each element of `self`. @@ -3302,7 +3319,8 @@ impl bevy::math::IVec2 { #[script_bindings( remote, name = "i_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::IVec3 { /// Returns a vector containing the absolute value of each element of `self`. @@ -3999,7 +4017,8 @@ impl bevy::math::IVec3 { #[script_bindings( remote, name = "i_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::IVec4 { /// Returns a vector containing the absolute value of each element of `self`. @@ -4679,7 +4698,8 @@ impl bevy::math::IVec4 { #[script_bindings( remote, name = "i_64_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::I64Vec2 { /// Returns a vector containing the absolute value of each element of `self`. @@ -5378,7 +5398,8 @@ impl bevy::math::I64Vec2 { #[script_bindings( remote, name = "i_64_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::I64Vec3 { /// Returns a vector containing the absolute value of each element of `self`. @@ -6081,7 +6102,8 @@ impl bevy::math::I64Vec3 { #[script_bindings( remote, name = "i_64_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::I64Vec4 { /// Returns a vector containing the absolute value of each element of `self`. @@ -6767,7 +6789,8 @@ impl bevy::math::I64Vec4 { #[script_bindings( remote, name = "u_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::UVec2 { fn add( @@ -7331,7 +7354,8 @@ impl bevy::math::UVec2 { #[script_bindings( remote, name = "u_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::UVec3 { fn add( @@ -7930,7 +7954,8 @@ impl bevy::math::UVec3 { #[script_bindings( remote, name = "u_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::UVec4 { fn add( @@ -8512,7 +8537,8 @@ impl bevy::math::UVec4 { #[script_bindings( remote, name = "u_64_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::U64Vec2 { fn add( @@ -9079,7 +9105,8 @@ impl bevy::math::U64Vec2 { #[script_bindings( remote, name = "u_64_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::U64Vec3 { fn add( @@ -9682,7 +9709,8 @@ impl bevy::math::U64Vec3 { #[script_bindings( remote, name = "u_64_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::U64Vec4 { fn add( @@ -10268,7 +10296,8 @@ impl bevy::math::U64Vec4 { #[script_bindings( remote, name = "vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Vec2 { /// Returns a vector containing the absolute value of each element of `self`. @@ -11245,7 +11274,8 @@ impl bevy::math::Vec2 { #[script_bindings( remote, name = "vec_3_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Vec3A { /// Returns a vector containing the absolute value of each element of `self`. @@ -12217,7 +12247,8 @@ impl bevy::math::Vec3A { #[script_bindings( remote, name = "vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Vec4 { /// Returns a vector containing the absolute value of each element of `self`. @@ -13134,7 +13165,8 @@ impl bevy::math::Vec4 { #[script_bindings( remote, name = "b_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::BVec2 { /// Returns true if all the elements are true, false otherwise. @@ -13206,7 +13238,8 @@ impl bevy::math::BVec2 { #[script_bindings( remote, name = "b_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::BVec3 { /// Returns true if all the elements are true, false otherwise. @@ -13278,7 +13311,8 @@ impl bevy::math::BVec3 { #[script_bindings( remote, name = "b_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::BVec4 { /// Returns true if all the elements are true, false otherwise. @@ -13350,7 +13384,8 @@ impl bevy::math::BVec4 { #[script_bindings( remote, name = "d_vec_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DVec2 { /// Returns a vector containing the absolute value of each element of `self`. @@ -14332,7 +14367,8 @@ impl bevy::math::DVec2 { #[script_bindings( remote, name = "d_vec_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DVec3 { /// Returns a vector containing the absolute value of each element of `self`. @@ -15302,7 +15338,8 @@ impl bevy::math::DVec3 { #[script_bindings( remote, name = "d_vec_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DVec4 { /// Returns a vector containing the absolute value of each element of `self`. @@ -16223,7 +16260,8 @@ impl bevy::math::DVec4 { #[script_bindings( remote, name = "mat_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Mat2 { /// Takes the absolute value of each element in `self` @@ -16524,7 +16562,8 @@ impl bevy::math::Mat2 { #[script_bindings( remote, name = "mat_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Mat3 { /// Takes the absolute value of each element in `self` @@ -16981,7 +17020,8 @@ impl bevy::math::Mat3 { #[script_bindings( remote, name = "mat_3_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Mat3A { /// Takes the absolute value of each element in `self` @@ -17440,7 +17480,8 @@ impl bevy::math::Mat3A { #[script_bindings( remote, name = "mat_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Mat4 { /// Takes the absolute value of each element in `self` @@ -18215,7 +18256,8 @@ impl bevy::math::Mat4 { #[script_bindings( remote, name = "d_mat_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DMat2 { /// Takes the absolute value of each element in `self` @@ -18495,7 +18537,8 @@ impl bevy::math::DMat2 { #[script_bindings( remote, name = "d_mat_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DMat3 { /// Takes the absolute value of each element in `self` @@ -18932,7 +18975,8 @@ impl bevy::math::DMat3 { #[script_bindings( remote, name = "d_mat_4_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DMat4 { /// Takes the absolute value of each element in `self` @@ -19659,7 +19703,8 @@ impl bevy::math::DMat4 { #[script_bindings( remote, name = "affine_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Affine2 { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -19894,7 +19939,8 @@ impl bevy::math::Affine2 { #[script_bindings( remote, name = "affine_3_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::Affine3A { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -20252,7 +20298,8 @@ impl bevy::math::Affine3A { #[script_bindings( remote, name = "d_affine_2_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DAffine2 { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -20472,7 +20519,8 @@ impl bevy::math::DAffine2 { #[script_bindings( remote, name = "d_affine_3_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DAffine3 { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -20804,7 +20852,8 @@ impl bevy::math::DAffine3 { #[script_bindings( remote, name = "d_quat_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::DQuat { /// Returns true if the absolute difference of all elements between `self` and `rhs` @@ -21317,7 +21366,8 @@ impl bevy::math::DQuat { #[script_bindings( remote, name = "euler_rot_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::EulerRot { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -21345,7 +21395,8 @@ impl bevy::math::EulerRot { #[script_bindings( remote, name = "b_vec_3_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::BVec3A { /// Returns true if all the elements are true, false otherwise. @@ -21410,7 +21461,8 @@ impl bevy::math::BVec3A { #[script_bindings( remote, name = "b_vec_4_a_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::math::BVec4A { /// Returns true if all the elements are true, false otherwise. @@ -21475,7 +21527,8 @@ impl bevy::math::BVec4A { #[script_bindings( remote, name = "smol_str_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl smol_str::SmolStr { fn clone(_self: Ref) -> Val { @@ -21512,7 +21565,8 @@ impl smol_str::SmolStr { #[script_bindings( remote, name = "uuid_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl uuid::Uuid { /// Returns a 128bit value containing the value. diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs index 2693af0abb..4802139580 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs @@ -15,7 +15,8 @@ pub struct BevyTimeScriptingPlugin; #[script_bindings( remote, name = "fixed_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::prelude::Fixed { fn clone(_self: Ref) -> Val { @@ -29,7 +30,8 @@ impl bevy::time::prelude::Fixed { #[script_bindings( remote, name = "real_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::prelude::Real { fn clone(_self: Ref) -> Val { @@ -43,7 +45,8 @@ impl bevy::time::prelude::Real { #[script_bindings( remote, name = "timer_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::prelude::Timer { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -408,7 +411,8 @@ impl bevy::time::prelude::Timer { #[script_bindings( remote, name = "timer_mode_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::prelude::TimerMode { fn assert_receiver_is_total_eq(_self: Ref) -> () { @@ -441,7 +445,8 @@ impl bevy::time::prelude::TimerMode { #[script_bindings( remote, name = "virtual_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::prelude::Virtual { fn clone( @@ -457,7 +462,8 @@ impl bevy::time::prelude::Virtual { #[script_bindings( remote, name = "stopwatch_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::time::Stopwatch { fn assert_receiver_is_total_eq(_self: Ref) -> () { diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_transform.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_transform.rs index a6bb246da4..33f27c9332 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_transform.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_transform.rs @@ -15,7 +15,8 @@ pub struct BevyTransformScriptingPlugin; #[script_bindings( remote, name = "global_transform_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::transform::components::GlobalTransform { /// Returns the 3d affine transformation matrix as an [`Affine3A`]. @@ -379,7 +380,8 @@ impl bevy::transform::components::GlobalTransform { #[script_bindings( remote, name = "transform_functions", - bms_core_path = "bevy_mod_scripting_core" + bms_core_path = "bevy_mod_scripting_core", + generated )] impl bevy::transform::components::Transform { /// Equivalent to [`local_z()`][Transform::local_z] diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 13d7c4d280..bcac621f01 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -43,6 +43,14 @@ pub fn register_bevy_bindings(app: &mut App) { unregistered )] impl World { + /// Returns either a `ScriptComponentRegistration` or `ScriptResourceRegistration` depending on the type of the type requested. + /// If the type is neither returns a `ScriptTypeRegistration`. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `type_name`: The name of the type to retrieve. + /// Returns: + /// * `type`: The registration of the type, if it exists. fn get_type_by_name( ctxt: FunctionCallContext, type_name: String, @@ -63,6 +71,19 @@ impl World { } /// Retrieves the schedule with the given name, Also ensures the schedule is initialized before returning it. + /// + /// Schedules in bevy are "containers" for systems, each schedule runs separately and contains different systems. + /// + /// By default among others bevy contains the following schedules: + /// - `Update`: Runs every frame. + /// - `PostUpdate`: Runs after the `Update` schedule. + /// - `FixedUpdate`: Runs at a fixed rate. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `name`: The name of the schedule to retrieve. + /// Returns: + /// * `schedule`: The schedule with the given name, if it exists fn get_schedule_by_name( ctxt: FunctionCallContext, name: String, @@ -80,6 +101,14 @@ impl World { Ok(Some(schedule.into())) } + /// Tries to retrieve the given component type on an entity. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to retrieve the component from. + /// * `registration`: The component to retrieve. + /// Returns: + /// * `component`: The component on the entity, if it exists. fn get_component( ctxt: FunctionCallContext, entity: Val, @@ -91,6 +120,14 @@ impl World { Ok(val) } + /// Checks if the given entity has the given component. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to check. + /// * `registration`: The component to check for. + /// Returns: + /// * `has_component`: Whether the entity has the component. fn has_component( ctxt: FunctionCallContext, entity: Val, @@ -101,6 +138,13 @@ impl World { world.has_component(*entity, registration.component_id()) } + /// Removes the given component from the entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to remove the component from. + /// * `registration`: The component to remove. + /// Returns: + /// * `result`: Nothing if the component was removed successfully or didn't exist in the first place. fn remove_component( ctxt: FunctionCallContext, entity: Val, @@ -111,6 +155,12 @@ impl World { world.remove_component(*entity, registration.clone()) } + /// Retrieves the resource with the given registration. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `registration`: The registration of the resource to retrieve. + /// Returns: + /// * `resource`: The resource, if it exists. fn get_resource( ctxt: FunctionCallContext, registration: Val, @@ -121,6 +171,13 @@ impl World { Ok(val) } + /// Checks if the world has the given resource. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `registration`: The registration of the resource to check for. + /// Returns: + /// * `has_resource`: Whether the world has the resource. fn has_resource( ctxt: FunctionCallContext, registration: Val, @@ -130,6 +187,12 @@ impl World { world.has_resource(registration.resource_id()) } + /// Removes the given resource from the world. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `registration`: The resource to remove. + /// Returns: + /// * `result`: Nothing if the resource was removed successfully or didn't exist in the first place. fn remove_resource( ctxt: FunctionCallContext, registration: Val, @@ -139,6 +202,12 @@ impl World { world.remove_resource(registration.into_inner()) } + /// Adds the given resource to the world. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `registration`: The resource to add. + /// Returns: + /// * `result`: Nothing if the resource was added successfully. fn add_default_component( ctxt: FunctionCallContext, entity: Val, @@ -149,12 +218,27 @@ impl World { world.add_default_component(*entity, registration.clone()) } + /// Spawns a new entity and returns it + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// Returns: + /// * `entity`: The newly spawned entity fn spawn(ctxt: FunctionCallContext) -> Result, InteropError> { profiling::function_scope!("spawn"); let world = ctxt.world()?; Ok(Val(world.spawn()?)) } + /// Inserts the given component value into the provided entity + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to insert the component into. + /// * `registration`: The component registration of the component to insert. + /// * `value`: The value of the component to insert. Can be constructed using `construct` + /// Returns: + /// * `result`: Nothing if the component was inserted successfully. fn insert_component( ctxt: FunctionCallContext, entity: Val, @@ -166,6 +250,15 @@ impl World { world.insert_component(*entity, registration.into_inner(), value) } + /// Inserts the given children entities into the provided parent entity. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The parent entity to receive children + /// * `index`: The index to insert the children at + /// * `children`: The children entities to insert + /// Returns: + /// * `result`: Nothing if the children were inserted successfully. fn insert_children( ctxt: FunctionCallContext, entity: Val, @@ -186,6 +279,13 @@ impl World { ) } + /// Pushes the given children entities into the provided parent entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The parent entity to receive children + /// * `children`: The children entities to push + /// Returns: + /// * `result`: Nothing if the children were pushed successfully. fn push_children( ctxt: FunctionCallContext, entity: Val, @@ -199,6 +299,12 @@ impl World { ) } + /// Retrieves the children of the given entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to retrieve the children of. + /// Returns: + /// * `children`: The children of the entity. fn get_children( ctxt: FunctionCallContext, entity: Val, @@ -209,40 +315,79 @@ impl World { Ok(children.into_iter().map(Val).collect::>()) } + /// Retrieves the parent of the given entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to retrieve the parent of. + /// Returns: + /// * `parent`: The parent of the entity fn get_parent( ctxt: FunctionCallContext, - e: Val, + entity: Val, ) -> Result>, InteropError> { profiling::function_scope!("get_parent"); let world = ctxt.world()?; - let parent = world.get_parent(*e)?; + let parent = world.get_parent(*entity)?; Ok(parent.map(Val)) } - fn despawn(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + /// Despawns the given entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to despawn. + fn despawn(ctxt: FunctionCallContext, entity: Val) -> Result<(), InteropError> { profiling::function_scope!("despawn"); let world = ctxt.world()?; - world.despawn(*e) + world.despawn(*entity) } - fn despawn_descendants(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + /// Despawn the descendants of the given entity. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to despawn the descendants of. + /// Returns: + /// * `result`: Nothing if the descendants were despawned successfully. + fn despawn_descendants( + ctxt: FunctionCallContext, + entity: Val, + ) -> Result<(), InteropError> { profiling::function_scope!("despawn_descendants"); let world = ctxt.world()?; - world.despawn_descendants(*e) + world.despawn_descendants(*entity) } - fn despawn_recursive(ctxt: FunctionCallContext, e: Val) -> Result<(), InteropError> { + /// Despawns the entity and all its descendants. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to despawn recursively. + /// Returns: + /// * `result`: Nothing if the entity and its descendants were despawned successfully. + fn despawn_recursive( + ctxt: FunctionCallContext, + entity: Val, + ) -> Result<(), InteropError> { profiling::function_scope!("despawn_recursive"); let world = ctxt.world()?; - world.despawn_recursive(*e) + world.despawn_recursive(*entity) } + /// Checks if the given entity exists. + /// Arguments: + /// * `ctxt`: The function call context. + /// * `entity`: The entity to check. + /// Returns: + /// * `has_entity`: Whether the entity exists. fn has_entity(ctxt: FunctionCallContext, e: Val) -> Result { profiling::function_scope!("has_entity"); let world = ctxt.world()?; world.has_entity(*e) } + /// Creates a new `ScriptQueryBuilder` which can be used to query the ECS. + /// + /// Returns: + /// * `query`: The new query builder. fn query() -> Result, InteropError> { profiling::function_scope!("query"); let query_builder = ScriptQueryBuilder::default(); @@ -290,6 +435,11 @@ impl World { Ok(Val(system)) } + /// Quits the program. + /// Arguments: + /// * `ctxt`: The function call context. + /// Returns: + /// * `result`: Nothing if the program was exited successfully. fn exit(ctxt: FunctionCallContext) -> Result<(), InteropError> { profiling::function_scope!("exit"); let world = ctxt.world()?; @@ -300,48 +450,81 @@ impl World { #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "reflect_reference_functions" + name = "reflect_reference_functions", + core )] impl ReflectReference { /// If this type is an enum, will return the name of the variant it represents on the type. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to get the variant name of. + /// Returns: + /// * `variant_name`: The name of the variant, if the reference is an enum. fn variant_name( ctxt: FunctionCallContext, - s: ReflectReference, + reference: ReflectReference, ) -> Result, InteropError> { profiling::function_scope!("variant_name"); let world = ctxt.world()?; - s.variant_name(world) + reference.variant_name(world) } /// Displays this reference without printing the exact contents. - fn display_ref(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + /// + /// This is useful for debugging and logging. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to display. + /// Returns: + /// * `display`: The display string. + fn display_ref( + ctxt: FunctionCallContext, + reference: ReflectReference, + ) -> Result { profiling::function_scope!("display_ref"); let world = ctxt.world()?; - Ok(s.display_with_world(world)) + Ok(reference.display_with_world(world)) } /// Displays the "value" of this reference + /// + /// This is useful for debugging and logging. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to display. + /// Returns: + /// * `display`: The display string. fn display_value( ctxt: FunctionCallContext, - s: ReflectReference, + reference: ReflectReference, ) -> Result { profiling::function_scope!("display_value"); let world = ctxt.world()?; - Ok(s.display_value_with_world(world)) + Ok(reference.display_value_with_world(world)) } /// Gets and clones the value under the specified key if the underlying type is a map type. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to index into. + /// * `key`: The key to index with. + /// Returns: + /// * `value`: The value at the key, if the reference is a map. fn map_get( ctxt: FunctionCallContext, - self_: ReflectReference, + reference: ReflectReference, key: ScriptValue, ) -> Result, InteropError> { profiling::function_scope!("map_get"); let world = ctxt.world()?; let key = >::from_script_ref( - self_.key_type_id(world.clone())?.ok_or_else(|| { + reference.key_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( - self_.tail_type_id(world.clone()).unwrap_or_default(), + reference.tail_type_id(world.clone()).unwrap_or_default(), Some(Box::new(key.clone())), "Could not get key type id. Are you trying to index into a type that's not a map?".to_owned(), ) @@ -349,7 +532,7 @@ impl ReflectReference { key, world.clone(), )?; - self_.with_reflect_mut(world.clone(), |s| match s.try_map_get(key.as_ref())? { + reference.with_reflect_mut(world.clone(), |s| match s.try_map_get(key.as_ref())? { Some(value) => { let reference = { let allocator = world.allocator(); @@ -367,9 +550,16 @@ impl ReflectReference { /// returns the concrete value. /// /// Does not support map types at the moment, for maps see `map_get` + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to index into. + /// * `key`: The key to index with. + /// Returns: + /// * `value`: The value at the key, if the reference is indexable. fn get( ctxt: FunctionCallContext, - mut self_: ReflectReference, + mut reference: ReflectReference, key: ScriptValue, ) -> Result { profiling::function_scope!("get"); @@ -377,71 +567,93 @@ impl ReflectReference { if ctxt.convert_to_0_indexed() { path.convert_to_0_indexed(); } - self_.index_path(path); + reference.index_path(path); let world = ctxt.world()?; - ReflectReference::into_script_ref(self_, world) + ReflectReference::into_script_ref(reference, world) } /// Sets the value under the specified path on the underlying value. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to set the value on. + /// * `key`: The key to set the value at. + /// * `value`: The value to set. + /// Returns: + /// * `result`: Nothing if the value was set successfully. fn set( ctxt: FunctionCallContext, - self_: ScriptValue, + reference: ScriptValue, key: ScriptValue, value: ScriptValue, - ) -> Result { + ) -> Result<(), InteropError> { profiling::function_scope!("set"); - if let ScriptValue::Reference(mut self_) = self_ { + if let ScriptValue::Reference(mut self_) = reference { let world = ctxt.world()?; let mut path: ParsedPath = key.try_into()?; if ctxt.convert_to_0_indexed() { path.convert_to_0_indexed(); } self_.index_path(path); - let r: ScriptValue = self_ - .with_reflect_mut(world.clone(), |r| { - let target_type_id = r - .get_represented_type_info() - .map(|i| i.type_id()) - .or_fake_id(); - let other = >::from_script_ref( - target_type_id, - value, - world.clone(), - )?; - r.try_apply(other.as_partial_reflect()) - .map_err(|e| InteropError::external_error(Box::new(e)))?; - Ok::<_, InteropError>(()) - }) - .into(); - return Ok(r); + self_.with_reflect_mut(world.clone(), |r| { + let target_type_id = r + .get_represented_type_info() + .map(|i| i.type_id()) + .or_fake_id(); + let other = >::from_script_ref( + target_type_id, + value, + world.clone(), + )?; + r.try_apply(other.as_partial_reflect()) + .map_err(|e| InteropError::external_error(Box::new(e)))?; + Ok::<_, InteropError>(()) + })??; } - Ok(ScriptValue::Unit) + Ok(()) } /// Pushes the value into the reference, if the reference is an appropriate container type. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to push the value into. + /// * `value`: The value to push. + /// Returns: + /// * `result`: Nothing if the value was pushed successfully. fn push( ctxt: FunctionCallContext, - s: ReflectReference, - v: ScriptValue, + reference: ReflectReference, + value: ScriptValue, ) -> Result<(), InteropError> { profiling::function_scope!("push"); let world = ctxt.world()?; - let target_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { + let target_type_id = reference.element_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(v.clone())), + reference.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(value.clone())), "Could not get element type id. Are you trying to insert elements into a type that's not a list?".to_owned(), ) })?; - let other = >::from_script_ref(target_type_id, v, world.clone())?; - s.with_reflect_mut(world, |s| s.try_push_boxed(other))? + let other = + >::from_script_ref(target_type_id, value, world.clone())?; + reference.with_reflect_mut(world, |s| s.try_push_boxed(other))? } /// Pops the value from the reference, if the reference is an appropriate container type. - fn pop(ctxt: FunctionCallContext, s: ReflectReference) -> Result { + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to pop the value from. + /// Returns: + /// * `value`: The value that was popped, if the reference supports popping. + fn pop( + ctxt: FunctionCallContext, + reference: ReflectReference, + ) -> Result { profiling::function_scope!("pop"); let world = ctxt.world()?; - let o = s.with_reflect_mut(world.clone(), |s| s.try_pop_boxed())??; + let o = reference.with_reflect_mut(world.clone(), |s| s.try_pop_boxed())??; let reference = { let allocator = world.allocator(); let mut allocator = allocator.write(); @@ -452,78 +664,108 @@ impl ReflectReference { } /// Inserts the value into the reference at the specified index, if the reference is an appropriate container type. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to insert the value into. + /// * `key`: The index to insert the value at. + /// * `value`: The value to insert. + /// Returns: + /// * `result`: Nothing if the value was inserted successfully. fn insert( ctxt: FunctionCallContext, - s: ReflectReference, - k: ScriptValue, - v: ScriptValue, + reference: ReflectReference, + key: ScriptValue, + value: ScriptValue, ) -> Result<(), InteropError> { profiling::function_scope!("insert"); let world = ctxt.world()?; - let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { + let key_type_id = reference.key_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(k.clone())), + reference.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(key.clone())), "Could not get key type id. Are you trying to insert elements into a type that's not a map?".to_owned(), ) })?; - let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + let mut key = >::from_script_ref(key_type_id, key, world.clone())?; if ctxt.convert_to_0_indexed() { key.convert_to_0_indexed_key(); } - let value_type_id = s.element_type_id(world.clone())?.ok_or_else(|| { + let value_type_id = reference.element_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(v.clone())), + reference.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(value.clone())), "Could not get element type id. Are you trying to insert elements into a type that's not a map?".to_owned(), ) })?; - let value = >::from_script_ref(value_type_id, v, world.clone())?; + let value = + >::from_script_ref(value_type_id, value, world.clone())?; - s.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? + reference.with_reflect_mut(world, |s| s.try_insert_boxed(key, value))? } /// Clears the container, if the reference is an appropriate container type. - fn clear(ctxt: FunctionCallContext, s: ReflectReference) -> Result<(), InteropError> { + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to clear. + /// Returns: + /// * `result`: Nothing if the reference was cleared + fn clear(ctxt: FunctionCallContext, reference: ReflectReference) -> Result<(), InteropError> { profiling::function_scope!("clear"); let world = ctxt.world()?; - s.with_reflect_mut(world, |s| s.try_clear())? + reference.with_reflect_mut(world, |s| s.try_clear())? } /// Retrieves the length of the reference, if the reference is an appropriate container type. - fn len(ctxt: FunctionCallContext, s: ReflectReference) -> Result, InteropError> { + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to get the length of. + /// Returns: + /// * `len`: The length of the reference, if the reference is a container. + fn len( + ctxt: FunctionCallContext, + reference: ReflectReference, + ) -> Result, InteropError> { profiling::function_scope!("len"); let world = ctxt.world()?; - s.len(world) + reference.len(world) } /// Removes the value at the specified key from the reference, if the reference is an appropriate container type. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to remove the value from. + /// * `key`: The key to remove the value at. + /// Returns: + /// * `result`: The removed value if any fn remove( ctxt: FunctionCallContext, - s: ReflectReference, - k: ScriptValue, + reference: ReflectReference, + key: ScriptValue, ) -> Result { profiling::function_scope!("remove"); let world = ctxt.world()?; - let key_type_id = s.key_type_id(world.clone())?.ok_or_else(|| { + let key_type_id = reference.key_type_id(world.clone())?.ok_or_else(|| { InteropError::unsupported_operation( - s.tail_type_id(world.clone()).unwrap_or_default(), - Some(Box::new(k.clone())), + reference.tail_type_id(world.clone()).unwrap_or_default(), + Some(Box::new(key.clone())), "Could not get key type id. Are you trying to remove elements from a type that's not a map?".to_owned(), ) })?; - let mut key = >::from_script_ref(key_type_id, k, world.clone())?; + let mut key = >::from_script_ref(key_type_id, key, world.clone())?; if ctxt.convert_to_0_indexed() { key.convert_to_0_indexed_key(); } - let removed = s.with_reflect_mut(world.clone(), |s| s.try_remove_boxed(key))??; + let removed = reference.with_reflect_mut(world.clone(), |s| s.try_remove_boxed(key))??; match removed { Some(removed) => { let reference = { @@ -540,14 +782,22 @@ impl ReflectReference { /// Iterates over the reference, if the reference is an appropriate container type. /// /// Returns an "next" iterator function. + /// + /// The iterator function should be called until it returns `nil` to signal the end of the iteration. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to iterate over. + /// Returns: + /// * `iter`: The iterator function. fn iter( ctxt: FunctionCallContext, - s: ReflectReference, + reference: ReflectReference, ) -> Result { profiling::function_scope!("iter"); let world = ctxt.world()?; - let mut len = s.len(world.clone())?.unwrap_or_default(); - let mut infinite_iter = s.into_iter_infinite(); + let mut len = reference.len(world.clone())?.unwrap_or_default(); + let mut infinite_iter = reference.into_iter_infinite(); let iter_function = move || { // world is not thread safe, we can't capture it in the closure // or it will also be non-thread safe @@ -569,13 +819,19 @@ impl ReflectReference { } /// Lists the functions available on the reference. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to list the functions of. + /// Returns: + /// * `functions`: The functions available on the reference. fn functions( ctxt: FunctionCallContext, - s: ReflectReference, + reference: ReflectReference, ) -> Result>, InteropError> { profiling::function_scope!("functions"); let world = ctxt.world()?; - let type_id = s.tail_type_id(world.clone())?.or_fake_id(); + let type_id = reference.tail_type_id(world.clone())?.or_fake_id(); let functions = world .get_functions_on_type(type_id) .into_iter() @@ -589,97 +845,165 @@ impl ReflectReference { #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_type_registration_functions" + name = "script_type_registration_functions", + core )] impl ScriptTypeRegistration { - fn type_name(s: Ref) -> String { + /// Retrieves the name of the type. + /// + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `type_name`: The name of the type. + fn type_name(registration: Ref) -> String { profiling::function_scope!("type_name"); - s.type_name().to_string() + registration.type_name().to_string() } - fn short_name(s: Ref) -> String { + /// Retrieves the short name of the type. + /// The short name is a more human-readable version of the type name. + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `short_name`: The short name of the + fn short_name(registration: Ref) -> String { profiling::function_scope!("short_name"); - s.short_name().to_string() + registration.short_name().to_string() } } #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_component_registration_functions" + name = "script_component_registration_functions", + core )] impl ScriptComponentRegistration { - fn type_name(s: Ref) -> &'static str { + /// Retrieves the name of the type. + /// + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `type_name`: The name of the type. + fn type_name(registration: Ref) -> &'static str { profiling::function_scope!("type_name"); - s.type_registration().type_name() + registration.type_registration().type_name() } - fn short_name(s: Ref) -> &'static str { + /// Retrieves the short name of the type. + /// The short name is a more human-readable version of the type name. + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `short_name`: The short name of the + fn short_name(registration: Ref) -> &'static str { profiling::function_scope!("short_name"); - s.type_registration().short_name() + registration.type_registration().short_name() } } #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_resource_registration_functions" + name = "script_resource_registration_functions", + core )] impl ScriptResourceRegistration { - fn type_name(s: Ref) -> &'static str { + /// Retrieves the name of the type. + /// + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `type_name`: The name of the type. + fn type_name(registration: Ref) -> &'static str { profiling::function_scope!("type_name"); - s.type_registration().type_name() + registration.type_registration().type_name() } - fn short_name(s: Ref) -> &'static str { + /// Retrieves the short name of the type. + /// The short name is a more human-readable version of the type name. + /// Arguments: + /// * `registration`: The type registration. + /// Returns: + /// * `short_name`: The short name of the + fn short_name(registration: Ref) -> &'static str { profiling::function_scope!("short_name"); - s.type_registration().short_name() + registration.type_registration().short_name() } } #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_query_builder_functions" + name = "script_query_builder_functions", + core )] impl ScriptQueryBuilder { + /// Adds a component to be retrieved by the query + /// + /// Arguments: + /// * `query`: The query to add the component to + /// * `component`: The component to add + /// Returns: + /// * `query`: The query with the component added fn component( - s: Val, + query: Val, components: Val, ) -> Val { profiling::function_scope!("component"); - let mut builder = s.into_inner(); + let mut builder = query.into_inner(); builder.component(components.into_inner()); Val(builder) } + /// Adds a component to filter the query by. This component will NOT be retrieved. + /// + /// Arguments: + /// * `query`: The query to add the component to + /// * `component`: The component to filter by + /// Returns: + /// * `query`: The query with the component added fn with( - s: Val, + query: Val, with: Val, ) -> Val { profiling::function_scope!("with"); - let mut builder = s.into_inner(); + let mut builder = query.into_inner(); builder.with_component(with.into_inner()); Val(builder) } + /// Adds a component to filter the query by. This component will NOT be retrieved. + /// + /// Arguments: + /// * `query`: The query to add the component to + /// * `component`: The component to filter by + /// Returns: + /// * `query`: The query with the component added fn without( - s: Val, + query: Val, without: Val, ) -> Val { profiling::function_scope!("without"); - let mut builder = s.into_inner(); + let mut builder = query.into_inner(); builder.without_component(without.into_inner()); Val(builder) } + /// Builds the query and retrieves the entities and component references. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `query`: The query to build. + /// Returns: + /// * `result`: The entities and component references that match the query. fn build( ctxt: FunctionCallContext, - s: Val, + query: Val, ) -> Result>, InteropError> { profiling::function_scope!("build"); let world = ctxt.world()?; - let builder = s.into_inner(); + let builder = query.into_inner(); let result = world.query(builder)?; let result = result.into_iter().map(Val).collect::>(); Ok(result) @@ -689,57 +1013,73 @@ impl ScriptQueryBuilder { #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_query_result_functions" + name = "script_query_result_functions", + core )] impl ScriptQueryResult { - fn entity(s: Ref) -> Val { + /// Retrieves the entity from the query result. + /// + /// Arguments: + /// * `query`: The query result to retrieve the entity from. + /// Returns: + /// * `entity`: The entity from the query result. + fn entity(query: Ref) -> Val { profiling::function_scope!("entity"); - Val::new(s.entity) + Val::new(query.entity) } - fn components(s: Ref) -> Vec { + /// Retrieves the components from the query result. + /// + /// These are ordered by the order they were added to the query. + /// + /// Arguments: + /// * `query`: The query result to retrieve the components from. + /// Returns: + /// * `components`: The components from the query result. + fn components(query: Ref) -> Vec { profiling::function_scope!("components"); - s.components.to_vec() + query.components.to_vec() } } #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "reflect_schedule_functions" + name = "reflect_schedule_functions", + core )] impl ReflectSchedule { /// Retrieves all the systems in the schedule. /// /// Arguments: - /// * `self_`: The schedule to retrieve the systems from. + /// * `schedule`: The schedule to retrieve the systems from. /// Returns: /// * `systems`: The systems in the schedule. fn systems( ctxt: FunctionCallContext, - self_: Ref, + schedule: Ref, ) -> Result>, InteropError> { profiling::function_scope!("systems"); let world = ctxt.world()?; - let systems = world.systems(&self_); + let systems = world.systems(&schedule); Ok(systems?.into_iter().map(Into::into).collect()) } /// Retrieves the system with the given name in the schedule /// /// Arguments: - /// * `self_`: The schedule to retrieve the system from. + /// * `schedule`: The schedule to retrieve the system from. /// * `name`: The identifier or full path of the system to retrieve. /// Returns: /// * `system`: The system with the given name, if it exists. fn get_system_by_name( ctxt: FunctionCallContext, - self_: Ref, + schedule: Ref, name: String, ) -> Result>, InteropError> { profiling::function_scope!("system_by_name"); let world = ctxt.world()?; - let system = world.systems(&self_)?; + let system = world.systems(&schedule)?; Ok(system .into_iter() .find_map(|s| (s.identifier() == name || s.path() == name).then_some(s.into()))) @@ -751,19 +1091,19 @@ impl ReflectSchedule { /// /// Arguments: /// * `ctxt`: The function call context - /// * `self_`: The schedule to render. + /// * `schedule`: The schedule to render. /// Returns: /// * `dot`: The dot graph string. fn render_dot( ctxt: FunctionCallContext, - self_: Ref, + schedule: Ref, ) -> Result { profiling::function_scope!("render_dot"); let world = ctxt.world()?; world.with_resource(|schedules: &Schedules| { let schedule = schedules - .get(*self_.label()) - .ok_or_else(|| InteropError::missing_schedule(self_.identifier()))?; + .get(*schedule.label()) + .ok_or_else(|| InteropError::missing_schedule(schedule.identifier()))?; let mut graph = bevy_system_reflection::schedule_to_reflect_graph(schedule); graph.absorb_type_system_sets(); graph.sort(); @@ -776,42 +1116,51 @@ impl ReflectSchedule { #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "reflect_system_functions" + name = "reflect_system_functions", + core )] impl ReflectSystem { /// Retrieves the identifier of the system /// Arguments: - /// * `self_`: The system to retrieve the identifier from. + /// * `system`: The system to retrieve the identifier from. /// Returns: /// * `identifier`: The identifier of the system, e.g. `my_system` - fn identifier(self_: Ref) -> String { + fn identifier(system: Ref) -> String { profiling::function_scope!("identifier"); - self_.identifier().to_string() + system.identifier().to_string() } /// Retrieves the full path of the system /// Arguments: - /// * `self_`: The system to retrieve the path from. + /// * `system`: The system to retrieve the path from. /// Returns: /// * `path`: The full path of the system, e.g. `my_crate::systems::my_system` - fn path(self_: Ref) -> String { + fn path(system: Ref) -> String { profiling::function_scope!("path"); - self_.path().to_string() + system.path().to_string() } } #[script_bindings( remote, bms_core_path = "bevy_mod_scripting_core", - name = "script_system_builder_functions" + name = "script_system_builder_functions", + core )] impl ScriptSystemBuilder { + /// Adds a query to the system builder. + /// + /// Arguments: + /// * `builder`: The system builder to add the query to. + /// * `query`: The query to add. + /// Returns: + /// * `builder`: The system builder with the query added. fn query( - self_: Val, + builder: Val, query: Val, ) -> Result, InteropError> { profiling::function_scope!("query"); - let mut builder = self_.into_inner(); + let mut builder = builder.into_inner(); builder.query(query.into_inner()); Ok(builder.into()) } @@ -819,28 +1168,28 @@ impl ScriptSystemBuilder { /// Requests the system have access to the given resource. The resource will be added to the /// list of arguments of the callback in the order they're provided. /// Arguments: - /// * `self_`: The system builder to add the resource to. + /// * `builder`: The system builder to add the resource to. /// * `resource`: The resource to add. /// Returns: /// * `builder`: The system builder with the resource added. fn resource( - self_: Val, + builder: Val, resource: Val, ) -> Val { profiling::function_scope!("resource"); - let mut builder = self_.into_inner(); + let mut builder = builder.into_inner(); builder.resource(resource.into_inner()); builder.into() } /// Specifies the system is to run exclusively, meaning it can access anything, but will not run in parallel with other systems. /// Arguments: - /// * `self_`: The system builder to make exclusive. + /// * `builder`: The system builder to make exclusive. /// Returns: /// * `builder`: The system builder that is now exclusive. - fn exclusive(self_: Val) -> Val { + fn exclusive(builder: Val) -> Val { profiling::function_scope!("exclusive"); - let mut builder = self_.into_inner(); + let mut builder = builder.into_inner(); builder.exclusive(true); builder.into() } @@ -850,16 +1199,16 @@ impl ScriptSystemBuilder { /// Note: this is an experimental feature, and the ordering might not work correctly for script initialized systems /// /// Arguments: - /// * `self_`: The system builder to add the dependency to. + /// * `builder`: The system builder to add the dependency to. /// * `system`: The system to run after. /// Returns: /// * `builder`: The system builder with the dependency added. fn after( - self_: Val, + builder: Val, system: Val, ) -> Val { profiling::function_scope!("after"); - let mut builder = self_.into_inner(); + let mut builder = builder.into_inner(); builder.after_system(system.into_inner()); Val(builder) } @@ -869,16 +1218,16 @@ impl ScriptSystemBuilder { /// Note: this is an experimental feature, and the ordering might not work correctly for script initialized systems /// /// Arguments: - /// * `self_`: The system builder to add the dependency to. + /// * `builder`: The system builder to add the dependency to. /// * `system`: The system to run before. /// Returns: /// * `builder`: The system builder with the dependency added. fn before( - self_: Val, + builder: Val, system: Val, ) -> Val { profiling::function_scope!("before"); - let mut builder = self_.into_inner(); + let mut builder = builder.into_inner(); builder.before_system(system.into_inner()); Val(builder) } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs index 9186a48d99..c170687517 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/argument_visitor.rs @@ -1,5 +1,7 @@ //! Defines a visitor for function arguments of the `LAD` format. +use std::path::PathBuf; + use ladfile::{ArgumentVisitor, LadTypeId}; use crate::markdown::MarkdownBuilder; @@ -7,7 +9,8 @@ use crate::markdown::MarkdownBuilder; pub(crate) struct MarkdownArgumentVisitor<'a> { ladfile: &'a ladfile::LadFile, buffer: MarkdownBuilder, - linkifier: Box Option + 'static>, + linkifier: Box Option + 'static>, + pub raw_type_id_replacement: Option<&'static str>, } impl<'a> MarkdownArgumentVisitor<'a> { /// Create a new instance of the visitor @@ -18,12 +21,13 @@ impl<'a> MarkdownArgumentVisitor<'a> { ladfile, buffer: builder, linkifier: Box::new(|_, _| None), + raw_type_id_replacement: None, } } /// Create a new instance of the visitor with a custom linkifier function pub fn new_with_linkifier< - F: Fn(LadTypeId, &'a ladfile::LadFile) -> Option + 'static, + F: Fn(LadTypeId, &'a ladfile::LadFile) -> Option + 'static, >( ladfile: &'a ladfile::LadFile, linkifier: F, @@ -33,6 +37,12 @@ impl<'a> MarkdownArgumentVisitor<'a> { without } + /// Set the raw type id replacement + pub fn with_raw_type_id_replacement(mut self, replacement: &'static str) -> Self { + self.raw_type_id_replacement = Some(replacement); + self + } + pub fn build(mut self) -> String { self.buffer.build() } @@ -43,15 +53,17 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { // Write identifier let generics = self.ladfile.get_type_generics(type_id); - let type_identifier = self.ladfile.get_type_identifier(type_id); + let type_identifier = self + .ladfile + .get_type_identifier(type_id, self.raw_type_id_replacement); if let Some(generics) = generics { self.buffer.text(type_identifier); self.buffer.text('<'); for (i, generic) in generics.iter().enumerate() { - self.visit_lad_type_id(&generic.type_id); if i > 0 { self.buffer.text(", "); } + self.visit_lad_type_id(&generic.type_id); } self.buffer.text('>'); } else { @@ -59,6 +71,9 @@ impl ArgumentVisitor for MarkdownArgumentVisitor<'_> { let link_value = (self.linkifier)(type_id.clone(), self.ladfile); let link_display = type_identifier; if let Some(link_value) = link_value { + // canonicalize to linux paths + let link_value = link_value.to_string_lossy().to_string().replace("\\", "/"); + self.buffer.link(link_display, link_value); } else { self.buffer.text(link_display); @@ -139,12 +154,18 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new_with_linkifier(&ladfile, |type_id, ladfile| { - Some(format!("root/{}", ladfile.get_type_identifier(&type_id))) + Some( + PathBuf::from("root\\asd") + .join(ladfile.get_type_identifier(&type_id, None).to_string()), + ) }); let first_type_id = ladfile.types.first().unwrap().0; visitor.visit_lad_type_id(first_type_id); - assert_eq!(visitor.buffer.build(), "[EnumType](root/EnumType)"); + assert_eq!( + visitor.buffer.build(), + "StructType<[usize](root/asd/usize)>" + ); } #[test] @@ -155,13 +176,13 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); visitor.visit_lad_type_id(first_type_id); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "StructType"); visitor.buffer.clear(); let second_type_id = ladfile.types.iter().nth(1).unwrap().0; visitor.visit_lad_type_id(second_type_id); - assert_eq!(visitor.buffer.build(), "StructType"); + assert_eq!(visitor.buffer.build(), "EnumType"); } #[test] @@ -172,7 +193,7 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); visitor.visit(&LadTypeKind::Ref(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "StructType"); } #[test] @@ -183,7 +204,7 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); visitor.visit(&LadTypeKind::Mut(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "StructType"); } #[test] @@ -194,7 +215,7 @@ mod test { let mut visitor = MarkdownArgumentVisitor::new(&ladfile); visitor.visit(&LadTypeKind::Val(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "StructType"); } #[test] @@ -254,7 +275,7 @@ mod test { )); assert_eq!( visitor.buffer.build(), - "HashMap" + "HashMap | StructType | StructType>" ); } @@ -293,6 +314,6 @@ mod test { let first_type_id = ladfile.types.first().unwrap().0; visitor.visit(&LadTypeKind::Unknown(first_type_id.clone())); - assert_eq!(visitor.buffer.build(), "EnumType"); + assert_eq!(visitor.buffer.build(), "StructType"); } } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs index 9912e05376..4304945d20 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs @@ -1,8 +1,11 @@ //! The library crate for the mdbook LAD preprocessor. #![allow(missing_docs)] -use mdbook::{errors::Error, preprocess::Preprocessor}; -use sections::Section; +use mdbook::{ + errors::Error, + preprocess::{Preprocessor, PreprocessorContext}, +}; +use sections::{Section, SectionData}; mod argument_visitor; mod markdown; mod sections; @@ -27,6 +30,7 @@ impl LADPreprocessor { /// `parent` is the optional parent chapter reference, /// and `chapter_index` is the index of the chapter among its siblings. fn process_lad_chapter( + _context: &PreprocessorContext, chapter: &mdbook::book::Chapter, parent: Option<&mdbook::book::Chapter>, chapter_index: usize, @@ -38,10 +42,19 @@ impl LADPreprocessor { "Parsed LAD file: {}", serde_json::to_string_pretty(&ladfile).unwrap_or_default() ); - let new_chapter = Section::Summary { - ladfile: &ladfile, - title: Some(chapter_title), - } + + let parent_path = parent + .and_then(|p| p.path.clone()) + .unwrap_or_default() + .with_extension(""); + + let new_chapter = Section::new( + parent_path, + &ladfile, + SectionData::Summary { + title: Some(chapter_title), + }, + ) .into_chapter(parent, chapter_index); log::debug!( "New chapter: {}", @@ -58,7 +71,7 @@ impl Preprocessor for LADPreprocessor { fn run( &self, - _ctx: &mdbook::preprocess::PreprocessorContext, + context: &mdbook::preprocess::PreprocessorContext, mut book: mdbook::book::Book, ) -> mdbook::errors::Result { let mut errors = Vec::new(); @@ -75,6 +88,7 @@ impl Preprocessor for LADPreprocessor { if let mdbook::BookItem::Chapter(chapter) = item { if LADPreprocessor::is_lad_file(chapter) { match LADPreprocessor::process_lad_chapter( + context, chapter, Some(parent), idx, @@ -106,8 +120,16 @@ impl Preprocessor for LADPreprocessor { if !LADPreprocessor::is_lad_file(chapter) { return; } - - let new_chapter = match LADPreprocessor::process_lad_chapter(chapter, None, 0) { + let new_chapter = match LADPreprocessor::process_lad_chapter( + context, + chapter, + None, + chapter + .number + .clone() + .and_then(|n| n.0.last().map(|v| (*v) as usize)) + .unwrap_or_default(), + ) { Ok(new_chapter) => new_chapter, Err(e) => { errors.push(e); diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs index 68dba62c70..4b69ee1afc 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/markdown.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; /// Takes the first n characters from the markdown, without splitting any formatting. -pub(crate) fn markdown_substring(markdown: &str, length: usize) -> &str { +pub(crate) fn markdown_substring(markdown: &str, length: usize) -> String { if markdown.len() <= length { - return markdown; + return markdown.to_string(); } let mut end = length; for &(open, close) in &[("`", "`"), ("**", "**"), ("*", "*"), ("_", "_"), ("[", "]")] { @@ -27,11 +27,14 @@ pub(crate) fn markdown_substring(markdown: &str, length: usize) -> &str { } } } else { - return markdown; + return markdown.to_string(); } } } - &markdown[..end] + + let trimmed = markdown[..end].to_string(); + // append ... + format!("{}...", trimmed) } /// Escapes Markdown reserved characters in the given text. @@ -53,17 +56,17 @@ fn escape_markdown(text: &str, escape: bool) -> String { } /// Trait for converting elements into markdown strings. -pub trait IntoMarkdown { +pub trait IntoMarkdown: std::fmt::Debug { fn to_markdown(&self, builder: &mut MarkdownBuilder); } /// Comprehensive enum representing various Markdown constructs. -#[derive(Debug, Clone)] #[allow(dead_code)] +#[derive(Debug)] pub enum Markdown { Heading { level: u8, - text: String, + content: Box, }, Paragraph { text: String, @@ -77,7 +80,7 @@ pub enum Markdown { }, List { ordered: bool, - items: Vec, + items: Vec>, }, Quote(String), Image { @@ -85,14 +88,14 @@ pub enum Markdown { src: String, }, Link { - text: String, + text: Box, url: String, anchor: bool, }, HorizontalRule, Table { headers: Vec, - rows: Vec>, + rows: Vec>>, }, Raw(String), } @@ -108,6 +111,15 @@ impl Markdown { } } + pub fn space() -> Self { + Markdown::Paragraph { + text: " ".to_owned(), + bold: false, + italic: false, + code: false, + } + } + pub fn bold(self) -> Self { match self { Markdown::Paragraph { text, .. } => Markdown::Paragraph { @@ -148,12 +160,15 @@ impl Markdown { impl IntoMarkdown for Markdown { fn to_markdown(&self, builder: &mut MarkdownBuilder) { match self { - Markdown::Heading { level, text } => { + Markdown::Heading { level, content } => { // Clamp the header level to Markdown's 1-6. let clamped_level = level.clamp(&1, &6); let hashes = "#".repeat(*clamped_level as usize); - // Escape the text for Markdown - builder.append(&format!("{hashes} {text}")); + builder.append(&hashes); + builder.append(" "); + builder.with_tight_inline(|builder| { + content.to_markdown(builder); + }); } Markdown::Paragraph { text, @@ -196,19 +211,24 @@ impl IntoMarkdown for Markdown { builder.append(&format!("```{}\n{}\n```", lang, code)); } Markdown::List { ordered, items } => { - let list_output = items - .iter() - .enumerate() - .map(|(i, item)| { - if *ordered { - format!("{}. {}", i + 1, item) - } else { - format!("- {}", item) - } - }) - .collect::>() - .join("\n"); - builder.append(&list_output); + items.iter().enumerate().for_each(|(i, item)| { + if *ordered { + builder.append(&(i + 1).to_string()); + builder.append(". "); + builder.with_tight_inline(|builder| { + item.to_markdown(builder); + }); + } else { + builder.append("- "); + builder.with_tight_inline(|builder| { + item.to_markdown(builder); + }); + } + + if i < items.len() - 1 { + builder.append("\n"); + } + }); } Markdown::Quote(text) => { let quote_output = text @@ -228,6 +248,10 @@ impl IntoMarkdown for Markdown { } Markdown::Link { text, url, anchor } => { // anchors must be lowercase, only contain letters or dashes + builder.append("["); + builder.with_tight_inline(|builder| text.to_markdown(builder)); + builder.append("]("); + let url = if *anchor { // prefix with # format!( @@ -237,14 +261,11 @@ impl IntoMarkdown for Markdown { .replace(|c: char| !c.is_alphabetic(), "") ) } else { - url.clone() + url.clone().replace("\\", "/") }; - // Escape link text while leaving url untouched. - builder.append(&format!( - "[{}]({})", - escape_markdown(text, builder.escape), - url - )); + + builder.append(&url); + builder.append(")"); } Markdown::HorizontalRule => { builder.append("---"); @@ -254,51 +275,63 @@ impl IntoMarkdown for Markdown { return; } - // Generate a Markdown table: - // Header row: let header_line = format!("| {} |", headers.join(" | ")); + builder.append(&header_line); + builder.append("\n"); + // Separator row: let separator_line = format!( - "|{}|", + "|{}|\n", headers .iter() .map(|_| " --- ") .collect::>() .join("|") ); - // Rows: - let rows_lines = rows - .iter() - .map(|row| format!("| {} |", row.join(" | "))) - .collect::>() - .join("\n"); - builder.append(&format!( - "{}\n{}\n{}", - header_line, separator_line, rows_lines - )); + builder.append(&separator_line); + + for (row_idx, row) in rows.iter().enumerate() { + builder.append("| "); + for (i, cell) in row.iter().enumerate() { + builder.with_tight_inline(|builder| { + cell.to_markdown(builder); + }); + if i < row.len() - 1 { + builder.append(" | "); + } + } + builder.append(" |"); + if row_idx < rows.len() - 1 { + builder.append("\n"); + } + } } Markdown::Raw(text) => { builder.append(text); } } + builder.separate(); } } impl IntoMarkdown for &str { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - builder.append(&escape_markdown(self, builder.escape)) + builder.append(&escape_markdown(self, builder.escape)); + builder.separate(); } } impl IntoMarkdown for String { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - builder.append(&escape_markdown(self.as_ref(), builder.escape)) + builder.append(&escape_markdown(self.as_ref(), builder.escape)); + builder.separate(); } } impl IntoMarkdown for Cow<'_, str> { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - builder.append(&escape_markdown(self.as_ref(), builder.escape)) + builder.append(&escape_markdown(self.as_ref(), builder.escape)); + builder.separate(); } } @@ -321,27 +354,19 @@ macro_rules! markdown_vec { impl IntoMarkdown for Vec { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - for (i, item) in self.iter().enumerate() { + for item in self.iter() { item.to_markdown(builder); - if i < self.len() - 1 { - if builder.inline { - builder.append(builder.inline_separator); - } else { - builder.append("\n\n"); - } - } } } } /// Builder pattern for generating comprehensive Markdown documentation. /// Now also doubles as the accumulator for the generated markdown. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MarkdownBuilder { - elements: Vec, output: String, pub inline: bool, - pub inline_separator: &'static str, + pub tight_inline: bool, pub escape: bool, } @@ -349,21 +374,46 @@ pub struct MarkdownBuilder { impl MarkdownBuilder { /// Clears the builder's buffer pub fn clear(&mut self) { - self.elements.clear(); self.output.clear(); } + pub fn with_tight_inline(&mut self, f: F) { + let prev_inline = self.inline; + let prev_tight_inline = self.tight_inline; + self.tight_inline(); + f(self); + self.inline = prev_inline; + self.tight_inline = prev_tight_inline; + } + /// Creates a new MarkdownBuilder. pub fn new() -> Self { MarkdownBuilder { - elements: Vec::new(), output: String::new(), inline: false, - inline_separator: " ", + tight_inline: false, escape: true, } } + // inserts the correct separator + // this should be used after each element is added + pub fn separate(&mut self) { + self.output.push_str(self.separator()); + } + + fn separator(&self) -> &'static str { + if self.inline { + if self.tight_inline { + "" + } else { + " " + } + } else { + "\n\n" + } + } + /// Disables or enables the automatic escaping of Markdown reserved characters. /// by default it is enabled. /// @@ -376,6 +426,14 @@ impl MarkdownBuilder { /// Enables inline mode, which prevents newlines from being inserted for elements that support it pub fn inline(&mut self) -> &mut Self { self.inline = true; + self.tight_inline = false; + self + } + + /// Disables inline mode. + pub fn non_inline(&mut self) -> &mut Self { + self.inline = false; + self.tight_inline = false; self } @@ -383,7 +441,7 @@ impl MarkdownBuilder { /// Each element will simply be concatenated without any separator. pub fn tight_inline(&mut self) -> &mut Self { self.inline = true; - self.inline_separator = ""; + self.tight_inline = true; self } @@ -399,49 +457,54 @@ impl MarkdownBuilder { } /// Adds a heading element (Levels from 1-6). - pub fn heading(&mut self, level: u8, text: impl IntoMarkdown) -> &mut Self { - let mut builder = MarkdownBuilder::new(); - builder.inline(); - text.to_markdown(&mut builder); - let text = builder.build(); - - self.elements.push(Markdown::Heading { + pub fn heading(&mut self, level: u8, text: impl IntoMarkdown + 'static) -> &mut Self { + Markdown::Heading { level: level.min(6), - text, - }); + content: Box::new(text), + } + .to_markdown(self); + self + } + + /// Adds a raw markdown element + pub fn raw(&mut self, text: impl Into) -> &mut Self { + self.append(&text.into()); self } /// Adds a paragraph element. pub fn text(&mut self, text: impl Into) -> &mut Self { - self.elements.push(Markdown::Paragraph { + Markdown::Paragraph { text: text.into(), bold: false, italic: false, code: false, - }); + } + .to_markdown(self); self } /// Adds a bold element. pub fn bold(&mut self, text: impl Into) -> &mut Self { - self.elements.push(Markdown::Paragraph { + Markdown::Paragraph { text: text.into(), bold: true, italic: false, code: false, - }); + } + .to_markdown(self); self } /// Adds an italic element. pub fn italic(&mut self, text: impl Into) -> &mut Self { - self.elements.push(Markdown::Paragraph { + Markdown::Paragraph { text: text.into(), bold: false, italic: true, code: false, - }); + } + .to_markdown(self); self } @@ -451,82 +514,86 @@ impl MarkdownBuilder { language: Option>, code: impl Into, ) -> &mut Self { - self.elements.push(Markdown::CodeBlock { + Markdown::CodeBlock { language: language.map(|l| l.into()), code: code.into(), - }); + } + .to_markdown(self); self } /// Adds an inline code element. pub fn inline_code(&mut self, code: impl Into) -> &mut Self { - self.elements.push(Markdown::Paragraph { + Markdown::Paragraph { text: code.into(), bold: false, italic: false, code: true, - }); + } + .to_markdown(self); self } /// Adds a list element. - pub fn list(&mut self, ordered: bool, items: Vec) -> &mut Self { - let converted_items: Vec = items - .into_iter() - .map(|s| { - let mut builder = MarkdownBuilder::new(); - builder.inline(); - s.to_markdown(&mut builder); - builder.build() - }) - .collect(); - - self.elements.push(Markdown::List { + pub fn list(&mut self, ordered: bool, items: Vec) -> &mut Self { + Markdown::List { ordered, - items: converted_items, - }); + items: items + .into_iter() + .map(|i| Box::new(i) as Box) + .collect(), + } + .to_markdown(self); self } /// Adds a quote element. pub fn quote(&mut self, text: impl IntoMarkdown) -> &mut Self { let mut builder = MarkdownBuilder::new(); + builder.tight_inline(); text.to_markdown(&mut builder); - self.elements.push(Markdown::Quote(builder.build())); + Markdown::Quote(builder.build()).to_markdown(self); self } /// Adds an image element. pub fn image(&mut self, alt: impl Into, src: impl Into) -> &mut Self { - self.elements.push(Markdown::Image { + Markdown::Image { alt: alt.into(), src: src.into(), - }); + } + .to_markdown(self); self } /// Adds a link element. - pub fn link(&mut self, text: impl Into, url: impl Into) -> &mut Self { - self.elements.push(Markdown::Link { - text: text.into(), + pub fn link(&mut self, text: impl IntoMarkdown + 'static, url: impl Into) -> &mut Self { + Markdown::Link { + text: Box::new(text), url: url.into(), anchor: false, - }); + } + .to_markdown(self); self } - pub fn section_link(&mut self, text: impl Into, url: impl Into) -> &mut Self { - self.elements.push(Markdown::Link { - text: text.into(), + pub fn section_link( + &mut self, + text: impl IntoMarkdown + 'static, + url: impl Into, + ) -> &mut Self { + Markdown::Link { + text: Box::new(text), url: url.into(), anchor: true, - }); + } + .to_markdown(self); self } /// Adds a horizontal rule element. pub fn horizontal_rule(&mut self) -> &mut Self { - self.elements.push(Markdown::HorizontalRule); + Markdown::HorizontalRule.to_markdown(self); self } @@ -534,38 +601,31 @@ impl MarkdownBuilder { pub fn table(&mut self, f: impl FnOnce(&mut TableBuilder)) -> &mut Self { let mut builder = TableBuilder::new(); f(&mut builder); - self.elements.push(builder.build()); + log::info!("Table Builder: {builder:#?}"); + builder.build().to_markdown(self); self } /// Builds the markdown document as a single String by delegating the conversion /// of each element to its `into_markdown` implementation. pub fn build(&mut self) -> String { - let len = self.elements.len(); - for (i, element) in self.elements.clone().into_iter().enumerate() { - element.to_markdown(self); - if i < len - 1 { - if self.inline { - self.append(self.inline_separator); - } else { - self.append("\n\n"); - } - } - } + // replace inline placeholders with the characters they represent, + // at the same time remove multiple consecutive placeholders self.output.clone() } } impl IntoMarkdown for MarkdownBuilder { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - *builder = self.clone() + builder.append(&self.output); } } /// Mini builder for constructing Markdown tables. +#[derive(Debug)] pub struct TableBuilder { headers: Vec, - rows: Vec>, + rows: Vec>>, } impl TableBuilder { @@ -584,17 +644,12 @@ impl TableBuilder { } /// Adds a row to the table. - pub fn row(&mut self, row: Vec) -> &mut Self { - let converted = row - .into_iter() - .map(|r| { - let mut builder = MarkdownBuilder::new(); - builder.inline(); - r.to_markdown(&mut builder); - builder.build() - }) - .collect(); - self.rows.push(converted); + pub fn row(&mut self, row: Vec) -> &mut Self { + self.rows.push( + row.into_iter() + .map(|r| Box::new(r) as Box) + .collect(), + ); self } @@ -632,7 +687,9 @@ mod tests { true, Vec::from_iter(vec![markdown_vec![ Markdown::new_paragraph("italic").italic(), + Markdown::space(), Markdown::new_paragraph("bold").bold(), + Markdown::space(), Markdown::new_paragraph("code").code(), ]]), ) @@ -649,6 +706,14 @@ mod tests { .row(markdown_vec![ "Row 2 Col 1", Markdown::new_paragraph("HashMap").code() + ]) + .row(markdown_vec![ + "Hello", + Markdown::Link { + text: Box::new("iam a link"), + url: "to a thing".to_owned(), + anchor: false + } ]); }) .build(); @@ -679,6 +744,7 @@ mod tests { | --- | --- | | Row 1 Col 1 | Row 1 Col 2 | | Row 2 Col 1 | `HashMap` | + | Hello | [iam a link](to a thing) | "#; let trimmed_indentation_expected = expected @@ -703,15 +769,15 @@ mod tests { // Test markdown_substring with simple 5–7 character inputs. let cases = vec![ // Inline code: "a`bcd`" → with len 3, substring "a`b" is extended to the full inline segment. - ("a`bcd`", 3, "a`bcd`"), + ("a`bcd`", 3, "a`bcd`..."), // Bold: "a**b**" → with len 3, substring "a**" is extended to "a**b**". - ("a**b**", 3, "a**b**"), + ("a**b**", 3, "a**b**..."), // Italic: "a*b*" → with len 1, substring "["a*", extended to "a*b*". - ("a*b*", 1, "a*b*"), + ("a*b*", 1, "a*b*..."), // Underscore: "a_b_" → with len 1, extended to "a_b_". - ("a_b_", 1, "a_b_"), + ("a_b_", 1, "a_b_..."), // Link-like: "[x](y)" → with len 1, extended to the next closing bracket. - ("[x](y)", 1, "[x](y)"), + ("[x](y)", 1, "[x](y)..."), ]; for (input, len, expected) in cases { assert_eq!( diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs index 7d83ae8947..aefa59e2db 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs @@ -1,14 +1,14 @@ use crate::{ argument_visitor::MarkdownArgumentVisitor, - markdown::{markdown_substring, IntoMarkdown, Markdown, MarkdownBuilder}, + markdown::{markdown_substring, IntoMarkdown, Markdown, MarkdownBuilder, TableBuilder}, markdown_vec, }; use ladfile::{ - ArgumentVisitor, LadArgument, LadFile, LadFunction, LadInstance, LadType, LadTypeId, - LadTypeLayout, + ArgumentVisitor, LadArgument, LadBMSPrimitiveKind, LadFile, LadFunction, LadInstance, LadType, + LadTypeId, LadTypeKind, LadTypeLayout, }; -use mdbook::book::Chapter; -use std::{borrow::Cow, collections::HashSet}; +use mdbook::book::{Chapter, SectionNumber}; +use std::{borrow::Cow, collections::HashSet, path::PathBuf}; fn print_type(ladfile: &LadFile, type_: &LadTypeId) -> String { let mut visitor = MarkdownArgumentVisitor::new(ladfile); @@ -16,6 +16,17 @@ fn print_type(ladfile: &LadFile, type_: &LadTypeId) -> String { visitor.build() } +fn print_type_with_replacement( + ladfile: &LadFile, + type_: &LadTypeId, + raw_type_id_replacement: &'static str, +) -> String { + let mut visitor = + MarkdownArgumentVisitor::new(ladfile).with_raw_type_id_replacement(raw_type_id_replacement); + visitor.visit_lad_type_id(type_); + visitor.build() +} + fn build_escaped_visitor(arg_visitor: MarkdownArgumentVisitor<'_>) -> String { arg_visitor .build() @@ -24,29 +35,35 @@ fn build_escaped_visitor(arg_visitor: MarkdownArgumentVisitor<'_>) -> String { .replace("|", "\\|") } -/// Sections which convert to single markdown files -pub(crate) enum Section<'a> { +#[derive(Debug)] +pub(crate) enum SectionData<'a> { Summary { - ladfile: &'a ladfile::LadFile, title: Option, }, /// A link directory to all the types within the ladfile - TypeSummary { ladfile: &'a ladfile::LadFile }, + TypeSummary, /// A link directory to all global functions within the ladfile - FunctionSummary { ladfile: &'a ladfile::LadFile }, + FunctionSummary, /// A link directory to all global instances within the ladfile - InstancesSummary { ladfile: &'a ladfile::LadFile }, + InstancesSummary, TypeDetail { lad_type_id: &'a LadTypeId, lad_type: &'a LadType, - ladfile: &'a ladfile::LadFile, }, FunctionDetail { function: &'a LadFunction, - ladfile: &'a ladfile::LadFile, }, } +/// Sections which convert to single markdown files +#[derive(Debug)] +pub(crate) struct Section<'a> { + /// The path to the parent we can use for absolute links + pub parent_path: PathBuf, + pub ladfile: &'a LadFile, + pub data: SectionData<'a>, +} + /// Makes a filename safe to put in links pub fn linkify_filename(name: impl Into) -> String { name.into() @@ -57,6 +74,14 @@ pub fn linkify_filename(name: impl Into) -> String { } impl<'a> Section<'a> { + pub(crate) fn new(parent_path: PathBuf, ladfile: &'a LadFile, data: SectionData<'a>) -> Self { + Self { + ladfile, + data, + parent_path, + } + } + /// convert into a chapter, including children pub(crate) fn into_chapter(self, parent: Option<&Chapter>, index: usize) -> Chapter { let mut builder = MarkdownBuilder::new(); @@ -68,15 +93,20 @@ impl<'a> Section<'a> { None => &default_chapter, }; - let parent_path = parent.path.clone().unwrap_or_default().with_extension(""); + let parent_path = self.parent_path.clone(); + let parent_source_path = parent .source_path .clone() .unwrap_or_default() .with_extension(""); - let mut current_number = parent.number.clone().unwrap_or_default(); - current_number.push(index as u32); + let current_number = if let Some(mut parent_number) = parent.number.clone() { + parent_number.push(index as u32); + parent_number + } else { + SectionNumber(vec![index as u32]) + }; let mut chapter = Chapter { name: self.title(), @@ -100,174 +130,237 @@ impl<'a> Section<'a> { } pub(crate) fn title(&self) -> String { - match self { - Section::Summary { title, .. } => { + match &self.data { + SectionData::Summary { title, .. } => { title.as_deref().unwrap_or("Bindings Summary").to_owned() } - Section::TypeSummary { .. } => "Types".to_owned(), - Section::FunctionSummary { .. } => "Functions".to_owned(), - Section::InstancesSummary { .. } => "Globals".to_owned(), - Section::TypeDetail { - ladfile, - lad_type_id, - .. - } => print_type(ladfile, lad_type_id), - Section::FunctionDetail { function, .. } => function.identifier.to_string(), + SectionData::TypeSummary { .. } => "Types".to_owned(), + SectionData::FunctionSummary { .. } => "Functions".to_owned(), + SectionData::InstancesSummary { .. } => "Globals".to_owned(), + SectionData::TypeDetail { lad_type_id, .. } => print_type(self.ladfile, lad_type_id), + SectionData::FunctionDetail { function, .. } => function.identifier.to_string(), } } + pub(crate) fn is_code_heading(&self) -> bool { + matches!( + self.data, + SectionData::TypeDetail { .. } | SectionData::FunctionDetail { .. } + ) + } + pub(crate) fn file_name(&self) -> String { linkify_filename(self.title()) + ".md" } pub(crate) fn children(&self) -> Vec> { - match self { - Section::Summary { ladfile, .. } => { + let child_parent_path = self + .parent_path + .join(linkify_filename(self.title())) + .with_extension(""); + + match self.data { + SectionData::Summary { .. } => { vec![ - Section::InstancesSummary { ladfile }, - Section::FunctionSummary { ladfile }, - Section::TypeSummary { ladfile }, + Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::InstancesSummary, + ), + Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::FunctionSummary, + ), + Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::TypeSummary, + ), ] } - Section::TypeSummary { ladfile } => ladfile + SectionData::TypeSummary => self + .ladfile .types .iter() - .map(|(lad_type_id, lad_type)| Section::TypeDetail { - lad_type, - ladfile, - lad_type_id, + .map(|(lad_type_id, lad_type)| { + Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::TypeDetail { + lad_type, + lad_type_id, + }, + ) }) .collect(), - Section::FunctionSummary { ladfile } => { - let associated_functions = ladfile + SectionData::FunctionSummary => { + let associated_functions = self + .ladfile .types .iter() .flat_map(|t| &t.1.associated_functions) .collect::>(); - let non_associated_functions = ladfile + let non_associated_functions = self + .ladfile .functions .iter() .filter_map(|f| (!associated_functions.contains(f.0)).then_some(f.1)); non_associated_functions - .map(|function| Section::FunctionDetail { function, ladfile }) + .map(|function| { + Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::FunctionDetail { function }, + ) + }) .collect() } - Section::InstancesSummary { .. } => { + SectionData::InstancesSummary { .. } => { vec![] } - Section::TypeDetail { - lad_type, ladfile, .. - } => lad_type + SectionData::TypeDetail { lad_type, .. } => lad_type .associated_functions .iter() .filter_map(|f| { - let function = ladfile.functions.get(f)?; - Some(Section::FunctionDetail { function, ladfile }) + let function = self.ladfile.functions.get(f)?; + Some(Section::new( + child_parent_path.clone(), + self.ladfile, + SectionData::FunctionDetail { function }, + )) }) .collect(), - Section::FunctionDetail { .. } => vec![], + SectionData::FunctionDetail { .. } => vec![], } } pub(crate) fn section_items(&self) -> Vec { - match self { - Section::Summary { .. } => { - let mut builder = MarkdownBuilder::new(); - builder.heading(1, self.title()); - builder.heading(2, "Contents"); - builder.text("This is an automatically generated file, you'll find links to the contents below"); - builder.table(|builder| { - builder.headers(vec!["Section", "Contents"]); - builder.row(markdown_vec![ - Markdown::new_paragraph("Types").code(), - Markdown::Link { - text: "Describes all available binding types".into(), - url: format!("./{}/types.md", linkify_filename(self.title())), - anchor: false - } - ]); - builder.row(markdown_vec![ - Markdown::new_paragraph("Global Functions").code(), - Markdown::Link { - text: "Documents all the global functions present in the bindings" - .into(), - url: format!("./{}/functions.md", linkify_filename(self.title())), - anchor: false - } - ]); - builder.row(markdown_vec![ - Markdown::new_paragraph("Globals").code(), - Markdown::Link { - text: "Documents all global variables present in the bindings".into(), - url: format!("./{}/globals.md", linkify_filename(self.title())), - anchor: false - } - ]); - }); + match self.data { + SectionData::Summary { .. } => { + let title = self.title().clone(); + vec![SectionItem::Markdown { - markdown: Box::new(builder), + markdown: Box::new(move |builder| { + builder.heading(2, "Contents"); + builder.text("This is an automatically generated file, you'll find links to the contents below"); + builder.table(|builder| { + builder.headers(vec!["Section", "Contents"]); + builder.row(markdown_vec![ + Markdown::new_paragraph("Types").code(), + Markdown::Link { + text: Box::new("Describes all available binding types"), + url: format!("./{}/types.md", linkify_filename(title.clone())), + anchor: false + } + ]); + builder.row(markdown_vec![ + Markdown::new_paragraph("Global Functions").code(), + Markdown::Link { + text: + Box::new("Documents all the global functions present in the bindings"), + url: format!( + "./{}/functions.md", + linkify_filename(title.clone()) + ), + anchor: false + } + ]); + builder.row(markdown_vec![ + Markdown::new_paragraph("Globals").code(), + Markdown::Link { + text: Box::new("Documents all global variables present in the bindings"), + url: format!( + "./{}/globals.md", + linkify_filename(title.clone()) + ), + anchor: false + } + ]); + }); + }), }] } - Section::InstancesSummary { ladfile } => { - let instances = ladfile.globals.iter().collect::>(); - let types_directory = linkify_filename(Section::TypeSummary { ladfile }.title()); + SectionData::InstancesSummary => { + let instances = self.ladfile.globals.iter().collect::>(); + let types_directory = PathBuf::from("/").join(self.parent_path.join("types")); vec![SectionItem::InstancesSummary { instances, - ladfile, + ladfile: self.ladfile, types_directory, }] } - Section::TypeSummary { ladfile } => { - let types = ladfile.types.keys().collect::>(); + SectionData::TypeSummary => { + let types = self.ladfile.types.keys().collect::>(); vec![SectionItem::TypesSummary { types, - types_directory: linkify_filename(self.title()), - ladfile, + types_directory: self + .parent_path + .join(linkify_filename(self.title())) + .to_string_lossy() + .to_string(), + ladfile: self.ladfile, }] } - Section::FunctionSummary { ladfile } => { - let associated_functions = ladfile + SectionData::FunctionSummary => { + let associated_functions = self + .ladfile .types .iter() .flat_map(|t| &t.1.associated_functions) .collect::>(); - let non_associated_functions = ladfile + let non_associated_functions = self + .ladfile .functions .iter() .filter_map(|f| (!associated_functions.contains(f.0)).then_some(f.1)) .collect(); - - vec![SectionItem::FunctionsSummary { - functions: non_associated_functions, - functions_directory: "functions".to_owned(), - }] + vec![ + SectionItem::Markdown { + markdown: Box::new(|builder| { + builder.heading(2, "Non-Associated Functions"); + builder.text("Global functions that are not associated with any type and callable from anywhere in the script."); + }), + }, + SectionItem::FunctionsSummary { + functions: non_associated_functions, + functions_directory: "functions".to_owned(), + }, + ] } - Section::TypeDetail { - lad_type, ladfile, .. - } => { + SectionData::TypeDetail { lad_type, .. } => { let functions = lad_type .associated_functions .iter() - .filter_map(|i| ladfile.functions.get(i)) + .filter_map(|i| self.ladfile.functions.get(i)) .collect::>(); - vec![ SectionItem::Layout { layout: &lad_type.layout, }, SectionItem::Description { lad_type }, + SectionItem::Markdown { + markdown: Box::new(|builder| { + builder.heading(2, "Associated Functions"); + }), + }, SectionItem::FunctionsSummary { functions, functions_directory: linkify_filename(self.title()), }, ] } - Section::FunctionDetail { function, ladfile } => { - vec![SectionItem::FunctionDetails { function, ladfile }] + SectionData::FunctionDetail { function } => { + let types_directory = self.parent_path.join("../types"); + vec![SectionItem::FunctionDetails { + function, + ladfile: self.ladfile, + types_directory, + }] } } } @@ -275,7 +368,11 @@ impl<'a> Section<'a> { impl IntoMarkdown for Section<'_> { fn to_markdown(&self, builder: &mut MarkdownBuilder) { - builder.heading(1, self.title()); + if self.is_code_heading() { + builder.heading(1, Markdown::new_paragraph(self.title()).code()); + } else { + builder.heading(1, self.title()); + } for item in self.section_items() { item.to_markdown(builder); @@ -288,7 +385,7 @@ const NO_DOCS_STRING: &str = "No Documentation 🚧"; /// Items which combine markdown elements to build a section pub enum SectionItem<'a> { Markdown { - markdown: Box, + markdown: Box, }, Layout { layout: &'a LadTypeLayout, @@ -303,6 +400,7 @@ pub enum SectionItem<'a> { FunctionDetails { function: &'a LadFunction, ladfile: &'a ladfile::LadFile, + types_directory: PathBuf, }, TypesSummary { types: Vec<&'a LadTypeId>, @@ -312,14 +410,28 @@ pub enum SectionItem<'a> { InstancesSummary { ladfile: &'a ladfile::LadFile, instances: Vec<(&'a Cow<'static, str>, &'a LadInstance)>, - types_directory: String, + types_directory: PathBuf, }, } +impl std::fmt::Debug for SectionItem<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + SectionItem::Markdown { .. } => "Markdown", + SectionItem::Layout { .. } => "Layout", + SectionItem::Description { .. } => "Description", + SectionItem::FunctionsSummary { .. } => "FunctionsSummary", + SectionItem::FunctionDetails { .. } => "FunctionDetails", + SectionItem::TypesSummary { .. } => "TypesSummary", + SectionItem::InstancesSummary { .. } => "InstancesSummary", + }) + } +} + impl IntoMarkdown for SectionItem<'_> { fn to_markdown(&self, builder: &mut MarkdownBuilder) { match self { - SectionItem::Markdown { markdown } => markdown.to_markdown(builder), + SectionItem::Markdown { markdown } => (markdown)(builder), SectionItem::Layout { layout } => { // process the variants here let opaque = layout.for_each_variant( @@ -374,41 +486,28 @@ impl IntoMarkdown for SectionItem<'_> { functions, functions_directory: functions_path, } => { - builder.heading(2, "Functions"); + builder.text("For function details and documentation, click on the function link."); // make a table of functions as a quick reference, make them link to function details sub-sections builder.table(|builder| { builder.headers(vec!["Function", "Summary"]); for function in functions.iter() { - let mut first_col = function.identifier.to_string(); - first_col.push('('); - for (idx, arg) in function.arguments.iter().enumerate() { - first_col.push_str( - &arg.name - .as_ref() - .cloned() - .unwrap_or_else(|| Cow::Owned(format!("arg{}", idx))), - ); - if idx != function.arguments.len() - 1 { - first_col.push_str(", "); - } - } - first_col.push(')'); + let first_col = function.identifier.to_string(); // first line with content from documentation trimmed to 100 chars let second_col = function .documentation .as_deref() .map(|doc| markdown_substring(doc, 100)) - .unwrap_or_else(|| NO_DOCS_STRING); + .unwrap_or_else(|| NO_DOCS_STRING.to_string()); builder.row(markdown_vec![ - Markdown::new_paragraph(first_col).code(), Markdown::Link { - text: second_col.to_owned().replace("\n", " "), + text: Box::new(first_col), url: format!("./{}/{}.md", functions_path, function.identifier), anchor: false - } + }, + Markdown::new_paragraph(second_col.to_string().replace("\n", " ")), ]); } }); @@ -418,31 +517,37 @@ impl IntoMarkdown for SectionItem<'_> { types_directory, ladfile, } => { - builder.heading(2, "Types"); + builder.heading(2, "Available Types"); + builder.text("All registered reflect-able types which can be constructed and directly manipulated by scripts."); // make a table of types as a quick reference, make them link to type details sub-sections builder.table(|builder| { builder.headers(vec!["Type", "Summary"]); for type_ in types.iter() { - let printed_type = print_type(ladfile, type_); + let printed_type_for_url = print_type(ladfile, type_); + let printed_type_pretty = + print_type_with_replacement(ladfile, type_, "Unknown"); let documentation = ladfile.get_type_documentation(type_); // first line with content from documentation trimmed to 100 chars let second_col = documentation .map(|doc| markdown_substring(doc, 100)) - .unwrap_or_else(|| NO_DOCS_STRING); + .unwrap_or_else(|| NO_DOCS_STRING.to_string()); + + let mut link_builder = MarkdownBuilder::new(); + link_builder.tight_inline(); + link_builder.link( + Markdown::new_paragraph(printed_type_pretty).code(), + format!( + "/{types_directory}/{}.md", + linkify_filename(printed_type_for_url) + ), + ); builder.row(markdown_vec![ - Markdown::new_paragraph(printed_type.clone()).code(), - Markdown::Link { - text: second_col.to_owned().replace("\n", " "), - url: format!( - "./{types_directory}/{}.md", - linkify_filename(printed_type) - ), - anchor: false - } + link_builder, + Markdown::new_paragraph(second_col.replace("\n", " ")), ]); } }); @@ -467,7 +572,7 @@ impl IntoMarkdown for SectionItem<'_> { move |lad_type_id, ladfile| { let printed_type = linkify_filename(print_type(ladfile, &lad_type_id)); - Some(format!("./{types_directory}/{printed_type}.md")) + Some(types_directory.join(printed_type).with_extension("md")) }, ); arg_visitor.visit(&v.type_kind); @@ -500,7 +605,29 @@ impl IntoMarkdown for SectionItem<'_> { } }); } - SectionItem::FunctionDetails { function, ladfile } => { + SectionItem::FunctionDetails { + function, + ladfile, + types_directory, + } => { + // if the function takes in a FunctionCallContext argument, we notify that it's an impure function + // which potentially tries to access anything in the world + if function.arguments.iter().any(|a| { + matches!( + a.kind, + LadTypeKind::Primitive(LadBMSPrimitiveKind::FunctionCallContext) + ) + }) { + builder.raw( + r#" +
+ This function is impure, it might potentially try to access anything in the world. + If you are using it in the context of a script system, it might cause access errors. +
+ "#.trim(), + ); + } + // we don't escape this, this is already markdown builder.quote(Markdown::Raw( function @@ -511,50 +638,74 @@ impl IntoMarkdown for SectionItem<'_> { )); builder.heading(4, "Arguments"); - builder.list( - false, - function - .arguments - .iter() - .enumerate() - .map(|(idx, arg)| lad_argument_to_list_elem(idx, arg, ladfile)) - .collect(), - ); + let headers = vec!["Name", "Type", "Documentation"]; + builder.table(|builder| { + builder.headers(headers.clone()); + for (idx, arg) in function.arguments.iter().enumerate() { + build_lad_function_argument_row( + idx, + arg, + ladfile, + types_directory.clone(), + builder, + ); + } + }); builder.heading(4, "Returns"); - builder.list( - false, - vec![lad_argument_to_list_elem(0, &function.return_type, ladfile)], - ); + builder.table(|builder| { + builder.headers(headers.clone()); + build_lad_function_argument_row( + 0, + &function.return_type, + ladfile, + types_directory.clone(), + builder, + ) + }); } } } } -fn lad_argument_to_list_elem( +fn build_lad_function_argument_row( idx: usize, arg: &LadArgument, ladfile: &LadFile, -) -> impl IntoMarkdown { - let mut arg_visitor = MarkdownArgumentVisitor::new(ladfile); + types_directory: PathBuf, + builder: &mut TableBuilder, +) { + // we exclude function call context as it's not something scripts pass down + if matches!( + arg.kind, + LadTypeKind::Primitive(LadBMSPrimitiveKind::FunctionCallContext) + ) { + return; + } + + let types_directory = types_directory.to_owned(); + let mut arg_visitor = + MarkdownArgumentVisitor::new_with_linkifier(ladfile, move |lad_type_id, ladfile| { + let printed_type = linkify_filename(print_type(ladfile, &lad_type_id)); + Some(types_directory.join(printed_type).with_extension("md")) + }); arg_visitor.visit(&arg.kind); - let markdown = arg_visitor.build(); + let markdown = build_escaped_visitor(arg_visitor); let arg_name = arg .name .as_ref() .cloned() .unwrap_or_else(|| Cow::Owned(format!("arg{}", idx))); - markdown_vec![ + + builder.row(markdown_vec![ Markdown::new_paragraph(arg_name).bold(), - Markdown::new_paragraph(":"), - Markdown::new_paragraph(markdown).code(), - Markdown::new_paragraph("-"), + Markdown::Raw(markdown), Markdown::Raw( arg.documentation .as_deref() .unwrap_or(NO_DOCS_STRING) .to_owned() ) - ] + ]); } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad.md index b6b45e41d8..8cad7280a5 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad.md @@ -8,4 +8,5 @@ This is an automatically generated file, you'll find links to the contents below | --- | --- | | `Types` | [Describes all available binding types](./lad/types.md) | | `Global Functions` | [Documents all the global functions present in the bindings](./lad/functions.md) | -| `Globals` | [Documents all global variables present in the bindings](./lad/globals.md) | \ No newline at end of file +| `Globals` | [Documents all global variables present in the bindings](./lad/globals.md) | + diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions.md index 547b97b243..da21640c96 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions.md @@ -1,7 +1,12 @@ # Functions -## Functions +## Non\-Associated Functions + +Global functions that are not associated with any type and callable from anywhere in the script\. + +For function details and documentation, click on the function link\. | Function | Summary | | --- | --- | -| `hello_world(arg1)` | [No Documentation 🚧](./functions/hello_world.md) | \ No newline at end of file +| [hello\_world](./functions/hello_world.md) | No Documentation 🚧 | + diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md index 0d692dfbd3..47ce0b7268 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md @@ -1,11 +1,16 @@ -# hello\_world +# `hello_world` > No Documentation 🚧 #### Arguments -- **arg1** : `usize` \- No Documentation 🚧 +| Name | Type | Documentation | +| --- | --- | --- | +| **arg1** | [usize](parent/lad/functions/../types/usize.md) | No Documentation 🚧 | #### Returns -- **arg0** : `usize` \- No Documentation 🚧 \ No newline at end of file +| Name | Type | Documentation | +| --- | --- | --- | +| **arg0** | [usize](parent/lad/functions/../types/usize.md) | No Documentation 🚧 | + diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md index d27fb52df9..55e506f43d 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md @@ -10,8 +10,8 @@ Instances containing actual accessible values\. | Instance | Type | | --- | --- | -| `my_non_static_instance` | Vec\<[UnitType](./types/unittype.md)\> | -| `map` | HashMap\<[String](./types/string.md), [String](./types/string.md) \| [String](./types/string.md)\> | +| `my_non_static_instance` | Vec\<[UnitType](/parent/lad/types/unittype.md)\> | +| `map` | HashMap\<[String](/parent/lad/types/string.md), [String](/parent/lad/types/string.md) \| [String](/parent/lad/types/string.md)\> | ### Static Instances @@ -19,4 +19,5 @@ Static type references, existing for the purpose of typed static function calls\ | Instance | Type | | --- | --- | -| `my_static_instance` | StructType\<[usize](./types/usize.md)\> | \ No newline at end of file +| `my_static_instance` | StructType\<[usize](/parent/lad/types/usize.md)\> | + diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md index a591a784da..67240fe834 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/types.md @@ -1,10 +1,13 @@ # Types -## Types +## Available Types + +All registered reflect\-able types which can be constructed and directly manipulated by scripts\. | Type | Summary | | --- | --- | -| `EnumType` | [No Documentation 🚧](./types/enumtype.md) | -| `StructType` | [ I am a struct](./types/structtypeusize.md) | -| `TupleStructType` | [ I am a tuple test type](./types/tuplestructtype.md) | -| `UnitType` | [ I am a unit test type](./types/unittype.md) | \ No newline at end of file +| [`StructType`](/parent/lad/types/structtypeusize.md) | I am a struct | +| [`EnumType`](/parent/lad/types/enumtype.md) | No Documentation 🚧 | +| [`TupleStructType`](/parent/lad/types/tuplestructtype.md) | I am a tuple test type | +| [`UnitType`](/parent/lad/types/unittype.md) | I am a unit test type | + diff --git a/crates/ladfile/src/lib.rs b/crates/ladfile/src/lib.rs index 55ec97de25..93ad7518b6 100644 --- a/crates/ladfile/src/lib.rs +++ b/crates/ladfile/src/lib.rs @@ -52,7 +52,11 @@ impl LadFile { } /// Retrieves the best type identifier suitable for a type id. - pub fn get_type_identifier(&self, type_id: &LadTypeId) -> Cow<'static, str> { + pub fn get_type_identifier( + &self, + type_id: &LadTypeId, + raw_type_id_replacement: Option<&'static str>, + ) -> Cow<'static, str> { if let Some(primitive) = self.primitives.get(type_id) { return primitive.kind.lad_type_id().to_string().into(); } @@ -60,7 +64,13 @@ impl LadFile { self.types .get(type_id) .map(|t| t.identifier.clone().into()) - .unwrap_or_else(|| type_id.0.clone()) + .unwrap_or_else(|| { + if let Some(replacement) = raw_type_id_replacement { + replacement.into() + } else { + type_id.0.clone() + } + }) } /// Retrieves the generics of a type id if it is a generic type. @@ -441,8 +451,32 @@ pub struct LadType { /// The layout or kind of the type. pub layout: LadTypeLayout, + + /// If a type is marked as auto generated. Auto generated types might be treated differently by + /// backends which generate documentation or other files. For example they might be hidden or put in a separate section. + #[serde(default)] + pub generated: bool, + + /// An "importance" value. By default all types get a value of 1000. + /// A lower insignificance means the type is more important. + /// + /// Backends can use this value to determine the order in which types are displayed. + #[serde(default = "default_importance")] + pub insignificance: usize, } +/// The default importance value for a type. +pub fn default_importance() -> usize { + 1000 +} + +// /// A type importance value. +// pub struct Importance(pub usize) + +// impl Default for Importance { + +// } + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] /// Description of a type layout in a LAD file. diff --git a/crates/ladfile/test_assets/test.lad.json b/crates/ladfile/test_assets/test.lad.json index 2b8686d392..10765db1e4 100644 --- a/crates/ladfile/test_assets/test.lad.json +++ b/crates/ladfile/test_assets/test.lad.json @@ -37,6 +37,37 @@ } }, "types": { + "ladfile_builder::test::StructType": { + "identifier": "StructType", + "crate": "ladfile_builder", + "path": "ladfile_builder::test::StructType", + "generics": [ + { + "type_id": "usize", + "name": "T" + } + ], + "documentation": " I am a struct", + "associated_functions": [ + "ladfile_builder::test::StructType::hello_world" + ], + "layout": { + "kind": "Struct", + "name": "StructType", + "fields": [ + { + "name": "field", + "type": "usize" + }, + { + "name": "field2", + "type": "usize" + } + ] + }, + "generated": false, + "insignificance": 1000 + }, "ladfile_builder::test::EnumType": { "identifier": "EnumType", "crate": "ladfile_builder", @@ -68,36 +99,9 @@ } ] } - ] - }, - "ladfile_builder::test::StructType": { - "identifier": "StructType", - "crate": "ladfile_builder", - "path": "ladfile_builder::test::StructType", - "generics": [ - { - "type_id": "usize", - "name": "T" - } ], - "documentation": " I am a struct", - "associated_functions": [ - "ladfile_builder::test::StructType::hello_world" - ], - "layout": { - "kind": "Struct", - "name": "StructType", - "fields": [ - { - "name": "field", - "type": "usize" - }, - { - "name": "field2", - "type": "usize" - } - ] - } + "generated": false, + "insignificance": 1000 }, "ladfile_builder::test::TupleStructType": { "identifier": "TupleStructType", @@ -115,7 +119,9 @@ "type": "String" } ] - } + }, + "generated": false, + "insignificance": 1000 }, "ladfile_builder::test::UnitType": { "identifier": "UnitType", @@ -125,7 +131,9 @@ "layout": { "kind": "Struct", "name": "UnitType" - } + }, + "generated": false, + "insignificance": 1000 } }, "functions": { diff --git a/crates/ladfile_builder/src/lib.rs b/crates/ladfile_builder/src/lib.rs index 9f0693fb3d..7c32b4d8b6 100644 --- a/crates/ladfile_builder/src/lib.rs +++ b/crates/ladfile_builder/src/lib.rs @@ -1,6 +1,7 @@ //! Parsing definitions for the LAD (Language Agnostic Decleration) file format. pub mod plugin; +use bevy::{ecs::world::World, utils::HashSet}; use bevy_mod_scripting_core::{ bindings::{ function::{ @@ -70,17 +71,24 @@ pub struct LadFileBuilder<'t> { type_id_mapping: HashMap, type_registry: &'t TypeRegistry, sorted: bool, + exclude_types_involving_unregistered_types: bool, } impl<'t> LadFileBuilder<'t> { - /// Create a new LAD file builder loaded with primitives. - pub fn new(type_registry: &'t TypeRegistry) -> Self { - let mut builder = Self { + /// Create a new LAD file builder without default primitives. + pub fn new_empty(type_registry: &'t TypeRegistry) -> Self { + Self { file: LadFile::new(), type_id_mapping: HashMap::new(), type_registry, sorted: false, - }; + exclude_types_involving_unregistered_types: false, + } + } + + /// Create a new LAD file builder loaded with primitives. + pub fn new(type_registry: &'t TypeRegistry) -> Self { + let mut builder = Self::new_empty(type_registry); builder .add_bms_primitive::("A boolean value") @@ -111,6 +119,13 @@ impl<'t> LadFileBuilder<'t> { builder } + /// Set whether types involving unregistered types should be excluded. + /// I.e. `HashMap` with T or V not being registered will be excluded. + pub fn set_exclude_including_unregistered(&mut self, exclude: bool) -> &mut Self { + self.exclude_types_involving_unregistered_types = exclude; + self + } + /// Set whether the LAD file should be sorted at build time. pub fn set_sorted(&mut self, sorted: bool) -> &mut Self { self.sorted = sorted; @@ -138,6 +153,24 @@ impl<'t> LadFileBuilder<'t> { self } + /// Mark a type as generated. + pub fn mark_generated(&mut self, type_id: TypeId) -> &mut Self { + let type_id = self.lad_id_from_type_id(type_id); + if let Some(t) = self.file.types.get_mut(&type_id) { + t.generated = true; + } + self + } + + /// Set the insignificance value of a type. + pub fn set_insignificance(&mut self, type_id: TypeId, importance: usize) -> &mut Self { + let type_id = self.lad_id_from_type_id(type_id); + if let Some(t) = self.file.types.get_mut(&type_id) { + t.insignificance = importance; + } + self + } + /// Add a global instance to the LAD file. /// /// Requires the type to be registered via [`Self::add_type`] or [`Self::add_type_info`] first to provide rich type information. @@ -181,6 +214,54 @@ impl<'t> LadFileBuilder<'t> { self } + /// Adds a global instance to the LAD file with a custom lad type kind. + pub fn add_instance_manually( + &mut self, + key: impl Into>, + is_static: bool, + type_kind: LadTypeKind, + ) -> &mut Self { + self.file.globals.insert( + key.into(), + LadInstance { + type_kind, + is_static, + }, + ); + self + } + + /// Adds a type which does not implement reflect to the list of types. + pub fn add_nonreflect_type( + &mut self, + crate_: Option<&str>, + documentation: &str, + ) -> &mut Self { + let path = std::any::type_name::().to_string(); + let identifier = path + .split("::") + .last() + .map(|o| o.to_owned()) + .unwrap_or_else(|| path.clone()); + + let lad_type_id = self.lad_id_from_type_id(std::any::TypeId::of::()); + self.file.types.insert( + lad_type_id, + LadType { + identifier, + crate_: crate_.map(|s| s.to_owned()), + path, + generics: vec![], + documentation: Some(documentation.trim().to_owned()), + associated_functions: vec![], + layout: LadTypeLayout::Opaque, + generated: false, + insignificance: default_importance(), + }, + ); + self + } + /// Add a type definition to the LAD file. /// /// Equivalent to calling [`Self::add_type_info`] with `T::type_info()`. @@ -215,6 +296,8 @@ impl<'t> LadFileBuilder<'t> { .map(|s| s.to_owned()), path: type_info.type_path_table().path().to_owned(), layout: self.lad_layout_from_type_info(type_info), + generated: false, + insignificance: default_importance(), }; self.file.types.insert(type_id, lad_type); self @@ -293,13 +376,53 @@ impl<'t> LadFileBuilder<'t> { self } + fn has_unknowns(&self, type_id: TypeId) -> bool { + if primitive_from_type_id(type_id).is_some() { + return false; + } + + let type_info = match self.type_registry.get_type_info(type_id) { + Some(info) => info, + None => return true, + }; + let inner_type_ids: Vec<_> = match type_info { + TypeInfo::Struct(struct_info) => { + struct_info.generics().iter().map(|g| g.type_id()).collect() + } + TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info + .generics() + .iter() + .map(|g| g.type_id()) + .collect(), + TypeInfo::Tuple(tuple_info) => { + tuple_info.generics().iter().map(|g| g.type_id()).collect() + } + TypeInfo::List(list_info) => vec![list_info.item_ty().id()], + TypeInfo::Array(array_info) => vec![array_info.item_ty().id()], + TypeInfo::Map(map_info) => vec![map_info.key_ty().id(), map_info.value_ty().id()], + TypeInfo::Set(set_info) => vec![set_info.value_ty().id()], + TypeInfo::Enum(enum_info) => enum_info.generics().iter().map(|g| g.type_id()).collect(), + TypeInfo::Opaque(_) => vec![], + }; + + inner_type_ids.iter().any(|id| self.has_unknowns(*id)) + } + /// Build the finalized and optimized LAD file. pub fn build(&mut self) -> LadFile { let mut file = std::mem::replace(&mut self.file, LadFile::new()); - if self.sorted { - file.types.sort_keys(); - file.functions.sort_keys(); - file.primitives.sort_keys(); + + if self.exclude_types_involving_unregistered_types { + let mut to_remove = HashSet::new(); + for reg in self.type_registry.iter() { + let type_id = reg.type_id(); + if self.has_unknowns(type_id) { + to_remove.insert(self.lad_id_from_type_id(type_id)); + } + } + + // remove those type ids + file.types.retain(|id, _| !to_remove.contains(id)); } // associate functions on type namespaces with their types @@ -314,6 +437,39 @@ impl<'t> LadFileBuilder<'t> { } } + if self.sorted { + file.types.sort_by(|ak, av, bk, bv| { + let complexity_a: usize = av + .path + .char_indices() + .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1)) + .sum(); + let complexity_b = bv + .path + .char_indices() + .filter_map(|(_, c)| (c == '<' || c == ',').then_some(1)) + .sum(); + + let has_functions_a = !av.associated_functions.is_empty(); + let has_functions_b = !bv.associated_functions.is_empty(); + + let ordered_by_name = ak.cmp(bk); + let ordered_by_generics_complexity = complexity_a.cmp(&complexity_b); + let ordered_by_generated = av.generated.cmp(&bv.generated); + let ordered_by_having_functions = has_functions_b.cmp(&has_functions_a); + let ordered_by_significance = av.insignificance.cmp(&bv.insignificance); + + ordered_by_significance + .then(ordered_by_having_functions) + .then(ordered_by_generics_complexity) + .then(ordered_by_name) + .then(ordered_by_generated) + }); + + file.functions.sort_keys(); + file.primitives.sort_keys(); + } + file } @@ -526,6 +682,11 @@ impl<'t> LadFileBuilder<'t> { } fn lad_id_from_type_id(&mut self, type_id: TypeId) -> LadTypeId { + // a special exception + if type_id == std::any::TypeId::of::() { + return LadTypeId::new_string_id("World".into()); + } + if let Some(lad_id) = self.type_id_mapping.get(&type_id) { return lad_id.clone(); } @@ -961,4 +1122,29 @@ mod test { let deserialized = parse_lad_file(&asset).unwrap(); assert_eq!(deserialized.version, "{{version}}"); } + + #[test] + fn test_nested_unregistered_generic_is_removed() { + let mut type_registry = TypeRegistry::default(); + + #[derive(Reflect)] + #[reflect(no_field_bounds, from_reflect = false)] + struct StructType { + #[reflect(ignore)] + phantom: std::marker::PhantomData, + } + + #[derive(Reflect)] + struct Blah; + + type_registry.register::>(); + + let lad_file = LadFileBuilder::new_empty(&type_registry) + .set_sorted(true) + .set_exclude_including_unregistered(true) + .add_type::>() + .build(); + + assert_eq!(lad_file.types.len(), 0); + } } diff --git a/crates/ladfile_builder/src/plugin.rs b/crates/ladfile_builder/src/plugin.rs index 1c6f5851d9..7f758a8dc9 100644 --- a/crates/ladfile_builder/src/plugin.rs +++ b/crates/ladfile_builder/src/plugin.rs @@ -7,12 +7,15 @@ use bevy::{ ecs::{ reflect::AppTypeRegistry, system::{Res, Resource}, + world::World, }, }; use bevy_mod_scripting_core::bindings::{ function::{namespace::Namespace, script_function::AppScriptFunctionRegistry}, globals::AppScriptGlobalsRegistry, + IntoNamespace, MarkAsCore, MarkAsGenerated, MarkAsSignificant, }; +use ladfile::{default_importance, LadTypeKind}; use crate::LadFileBuilder; @@ -33,6 +36,11 @@ pub struct LadFileSettings { /// The description to use for the LAD file, by default it's empty pub description: &'static str, + /// Whether to exclude types which are not registered. + /// + /// i.e. `HashMap` where `T` or `V` are not registered types + pub exclude_types_containing_unregistered: bool, + /// Whether to pretty print the output JSON. By default this is true (slay) pub pretty: bool, } @@ -43,17 +51,24 @@ impl Default for LadFileSettings { path: PathBuf::from("bindings.lad.json"), description: "", pretty: true, + exclude_types_containing_unregistered: true, } } } impl ScriptingDocgenPlugin { /// Create a new instance of the plugin with the given path - pub fn new(path: PathBuf, description: &'static str, pretty: bool) -> Self { + pub fn new( + path: PathBuf, + description: &'static str, + exclude_types_containing_unregistered: bool, + pretty: bool, + ) -> Self { Self(LadFileSettings { path, description, pretty, + exclude_types_containing_unregistered, }) } } @@ -71,8 +86,24 @@ pub fn generate_lad_file( let mut builder = LadFileBuilder::new(&type_registry); builder .set_description(settings.description) + .set_exclude_including_unregistered(settings.exclude_types_containing_unregistered) .set_sorted(true); + // process world as a special value + builder.add_nonreflect_type::( + Some("bevy_ecs"), + r#"The ECS world containing all Components, Resources and Systems. Main point of interaction with a Bevy App."#.trim(), + ); + + for (_, function) in function_registry.iter_namespace(World::into_namespace()) { + builder.add_function_info(function.info.clone()); + } + + builder.set_insignificance( + std::any::TypeId::of::(), + (default_importance() / 2) - 1, + ); + // first of all, iterate over all the types and register them for registration in type_registry.iter() { let type_info = registration.type_info(); @@ -84,6 +115,18 @@ pub fn generate_lad_file( builder.add_type_info(type_info); + if registration.contains::() { + builder.mark_generated(registration.type_id()); + } + + if registration.contains::() { + builder.set_insignificance(registration.type_id(), default_importance() / 2); + } + + if registration.contains::() { + builder.set_insignificance(registration.type_id(), default_importance() / 4); + } + // find functions on the namespace for (_, function) in function_registry.iter_namespace(Namespace::OnType(type_info.type_id())) @@ -98,12 +141,17 @@ pub fn generate_lad_file( } // find global instances - for (key, global) in global_registry.iter() { let type_info = global.type_information.clone(); builder.add_instance_dynamic(key.to_string(), global.maker.is_none(), type_info); } + // find global dummies + for (key, global) in global_registry.iter_dummies() { + let lad_type_id = builder.lad_id_from_type_id(global.type_id); + builder.add_instance_manually(key.to_string(), false, LadTypeKind::Val(lad_type_id)); + } + let file = builder.build(); let mut path = PathBuf::from("assets"); diff --git a/docs/generated_docs.html b/docs/generated_docs.html new file mode 100644 index 0000000000..e03efd0dd1 --- /dev/null +++ b/docs/generated_docs.html @@ -0,0 +1,237 @@ + + + + + + Generated Docs - Bevy Scripting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Generated Docs

+

Contents

+

This is an automatically generated file, you'll find links to the contents below

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 9ca89fa9b6..cb80e83102 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -17,14 +17,7 @@ - [Introduction](./ScriptingReference/introduction.md) - [Constructing Arbitrary Types](./ScriptingReference/constructing-arbitrary-types.md) -- [Core Bindings](./ScriptingReference/core-api.md) - - [World](./ScriptingReference/world.md) - - [ReflectReference](./ScriptingReference/reflect-reference.md) - - [ScriptTypeRegistration](./ScriptingReference/script-type-registration.md) - - [ScriptComponentRegistration](./ScriptingReference/script-component-registration.md) - - [ScriptResourceRegistration](./ScriptingReference/script-resource-registration.md) - - [ScriptQueryBuilder](./ScriptingReference/script-query-builder.md) - - [ScriptQueryResult](./ScriptingReference/script-query-result.md) +- [Core Bindings](./ladfiles/bindings.lad.json) - [Core Callbacks](./ScriptingReference/core-callbacks.md) # Developing BMS @@ -34,8 +27,3 @@ - [New Languages](./Development/AddingLanguages/introduction.md) - [Evaluating Feasibility](./Development/AddingLanguages/evaluating-feasibility.md) - [Necessary Features](./Development/AddingLanguages/necessary-features.md) - -# LAD docs (WIP) - -- [Generated Docs](./ladfiles/bindings.lad.json) - diff --git a/docs/src/ScriptingReference/reflect-reference.md b/docs/src/ScriptingReference/reflect-reference.md deleted file mode 100644 index dca9880d0e..0000000000 --- a/docs/src/ScriptingReference/reflect-reference.md +++ /dev/null @@ -1,239 +0,0 @@ -# ReflectReference - -ReflectReferences are simply references to data living either in: -- A component -- A resource -- The allocator - -Reflect references contain a standard interface which operates over the reflection layer exposed by `Bevy` and also provides a way to call various dynamic functions registered on the underlying pointed to data. - -## display_ref - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to display | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The reference in string format | - -```lua -print(ref:display_ref()) -print(ref) -``` - -## display_value - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to display | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The value in string format | - -```lua -print(ref:display_value()) -``` - -## get -The index function, allows you to index into the reflect reference. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `key` | `ScriptValue` | The key to get the value for | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptValue` | The value | - -```lua -local value = ref:get(key) --- same as -local value = ref.key -local value = ref[key] -local value = ref["key"] --- for tuple structs -local valye = ref._1 -``` - -## set - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `key` | `ScriptValue` | The key to set the value for | -| `value` | `ScriptValue` | The value to set | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptValue` | The result | - -```lua -ref:set(key, value) --- same as -ref.key = value -ref[key] = value -ref["key"] = value --- for tuple structs -ref._1 = value -``` - -## push -Generic push method, if the underlying type supports it, will push the value into the end of the reference. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `value` | `ScriptValue` | The value to push | - -```lua -ref:push(value) -``` - -## pop -Generic pop method, if the underlying type supports it, will pop the value from the end of the reference. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to pop from | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptValue` | The popped value | - -```lua -local value = ref:pop() -``` - -## insert -Generic insert method, if the underlying type supports it, will insert the value at the key. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `key` | `ScriptValue` | The key to insert the value for | -| `value` | `ScriptValue` | The value to insert | - -```lua -ref:insert(key, value) -``` - -## clear -Generic clear method, if the underlying type supports it, will clear the referenced container type. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to clear | - - -```lua -ref:clear() -``` - -## len -Generic length method, if the underlying type supports it, will return the length of the referenced container or length relevant to the type itself (number of fields etc.). - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to get the length of | - -Returns: - -| Return | Description | -| --- | --- | -| `usize` | The length | - -```lua -length = ref:len() -``` - -## remove -Generic remove method, if the underlying type supports it, will remove the value at the key. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `key` | `ScriptValue` | The key to remove the value for | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptValue` | The removed value | - -```lua -local value = ref:remove(key) -``` - -## iter -The iterator function, returns a function which can be called to iterate over the reference. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ReflectReference` | The reference to iterate over | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptFunctionMut` | The iterator function | - -```lua -local iter = ref:iter() -local val = iter() -while val do - print(val) - next = iter() -end - --- same as -for val in pairs(ref) do - print(val) -end -``` - -## functions -Returns a list of functions that can be called on the reference. - -Returns: - -| Return | Description | -| --- | --- | -| `Vec` | The list of functions | - -```lua -local functions = ref:functions() -for _, func in ipairs(functions) do - print(func.name) - -end -``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/script-component-registration.md b/docs/src/ScriptingReference/script-component-registration.md deleted file mode 100644 index c83fe416c9..0000000000 --- a/docs/src/ScriptingReference/script-component-registration.md +++ /dev/null @@ -1,39 +0,0 @@ -# ScriptComponentRegistration - -A reference to a component type's registration, in general think of this as a handle to a type. - -## type_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptComponentRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The type name | - -```lua -local name = MyType:type_name() -``` - -## short_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptComponentRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The short name | - -```lua -local name = MyType:short_name() -``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/script-query-builder.md b/docs/src/ScriptingReference/script-query-builder.md deleted file mode 100644 index 7d4fcbd93e..0000000000 --- a/docs/src/ScriptingReference/script-query-builder.md +++ /dev/null @@ -1,83 +0,0 @@ -# ScriptQueryBuilder - -The query builder is used to build queries for entities with specific components. Can be used to interact with arbitrary entities in the world. - -## component - -Adds a component to the query, this will be accessible in the query results under the index corresponding to the index of this component in the query. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryBuilder` | The query builder | -| `component` | `ScriptComponentRegistration` | The component to query for | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptQueryBuilder` | The updated query builder | - -```lua -query:component(MyType):component(MyOtherType) -``` - -## with - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryBuilder` | The query builder | -| `with` | `ScriptComponentRegistration` | The component to include in the query | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptQueryBuilder` | The updated query builder | - -```lua -query:with(MyType):with(MyOtherType) -``` - -## without - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryBuilder` | The query builder | -| `without` | `ScriptComponentRegistration` | The component to exclude from the query | - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptQueryBuilder` | The updated query builder | - -```lua -query:without(MyType):without(MyOtherType) -``` - -## build - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryBuilder` | The query builder | - -Returns: - -| Return | Description | -| --- | --- | -| `Vec` | The query results | - -```lua -local results = query:build() -for _, result in pairs(results) do - print(result) -end -``` diff --git a/docs/src/ScriptingReference/script-query-result.md b/docs/src/ScriptingReference/script-query-result.md deleted file mode 100644 index f25e5d1c8d..0000000000 --- a/docs/src/ScriptingReference/script-query-result.md +++ /dev/null @@ -1,41 +0,0 @@ -# ScriptQueryResult - -The result of a query, built by the query builder. - -## entity - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryResult` | The query result | - -Returns: - -| Return | Description | -| --- | --- | -| `Entity` | The entity | - -```lua -local entity = result:entity() -``` - -## components - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptQueryResult` | The query result | - -Returns: - -| Return | Description | -| --- | --- | -| `Vec` | The components | - -```lua -for _, component in pairs(result:components()) do - print(component) -end -``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/script-resource-registration.md b/docs/src/ScriptingReference/script-resource-registration.md deleted file mode 100644 index 13e500f88b..0000000000 --- a/docs/src/ScriptingReference/script-resource-registration.md +++ /dev/null @@ -1,39 +0,0 @@ -# ScriptResourceRegistration - -A reference to a resource type's registration, in general think of this as a handle to a type. - -## type_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptResourceRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The type name | - -```lua -local name = MyType:type_name() -``` - -## short_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptResourceRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The short name | - -```lua -local name = MyType:short_name() -``` \ No newline at end of file diff --git a/docs/src/ScriptingReference/script-type-registration.md b/docs/src/ScriptingReference/script-type-registration.md deleted file mode 100644 index 1eb45d62d7..0000000000 --- a/docs/src/ScriptingReference/script-type-registration.md +++ /dev/null @@ -1,79 +0,0 @@ -# ScriptTypeRegistration - -A reference to a type registration, in general think of this as a handle to a type. - -## type_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The type name | - -```lua -local name = MyType:type_name() -``` - -## short_name - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `String` | The short name | - -```lua -local name = MyType:short_name() -``` - -## is_resource - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `bool` | `true` if the type is a resource, otherwise `false` | - -```lua -if MyType:is_resource() then - print("MyType is a resource") -end -``` - -## is_component - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `s` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` | - -Returns: - -| Return | Description | -| --- | --- | -| `bool` | `true` if the type is a component, otherwise `false` | - -```lua -if MyType:is_component() then - print("MyType is a component") -end -``` diff --git a/docs/src/ScriptingReference/world.md b/docs/src/ScriptingReference/world.md deleted file mode 100644 index 967ebe36b1..0000000000 --- a/docs/src/ScriptingReference/world.md +++ /dev/null @@ -1,325 +0,0 @@ -## World - -The `World` is the entry point for interacting with `Bevy`. It is provided to scripts under either the `world` or `World` static variable. - -### get_type_by_name - -Returns either a `ScriptComponentRegistration` or `ScriptResourceRegistration` depending on the type of the type requested. If the type is neither returns a `ScriptTypeRegistration`. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `type_name` | `String` | The name of the type to get, this can be either the short type name, i.e. `my_type` or the long name i.e. `my_crate::my_module::my_type` | - -Returns: - -| Return | Description | -| --- | --- | -| `Option` | The registration for the type if it exists, otherwise `None` | - -```lua -MyType = world.get_type_by_name("MyType") -if MyType == nil then - print("MyType not found") -end - --- OR -MyType = types.MyType -``` - -### get_component - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to get the component from | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the component | - -Returns: - -| Return | Description | -| --- | --- | -| `Option` | The reference to the component if it exists, otherwise `None` | - -```lua -local component = world.get_component(entity, MyType) -if component ~= nil then - print("found component:" .. component) -end -``` - -### has_component - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to check the component for | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the component | - -Returns: - -| Return | Description | -| --- | --- | -| `bool` | `true` if the entity has the component, otherwise `false` | - -```lua -if world.has_component(entity, MyType) then - print("Entity has MyType") -end -``` - -### remove_component - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to remove the component from | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the component | - -```lua -world.remove_component(entity, MyType) -``` - -### get_resource - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the resource | - -Returns: - -| Return | Description | -| --- | --- | -| `Option` | The resource if it exists, otherwise `None` | - -```lua -local resource = world.get_resource(MyType) -if resource ~= nil then - print("found resource:" .. resource) -end -``` - -### has_resource - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the resource | - -Returns: - -| Return | Description | -| --- | --- | -| `bool` | `true` if the resource exists, otherwise `false` | - -```lua -local hasResource = world.has_resource(MyType) -``` - -### remove_resource - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the resource | - -```lua -world.remove_resource(MyType) -``` - -### add_default_component - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to add the component to | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the component | - -```lua -world.add_default_component(entity, MyType) -``` - -### insert_component - -Inserts or applies the given value to the component of the entity. If the component does not exist it will be added. - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to add the component to | -| `registration` | `ScriptTypeRegistration` | The type registration as returned by `get_type_by_name` of the component | -| `component` | `ReflectReference` | A reference to an existing component value to be inserted | - -```lua -local existingComponent = world.get_component(otherEntity, MyType) -world.insert_component(entity, MyType, existingComponent) -``` - - -### spawn - -Returns: - -| Return | Description | -| --- | --- | -| `Entity` | The spawned entity | - -```lua -local entity = world.spawn() -``` - -### insert_children - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The parent entity | -| `index` | `usize` | The index to insert the children at | -| `children` | `Vec` | The children entities to insert | - -```lua -world.insert_children(parent, 1, {child1, child2}) -``` - -### push_children - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The parent entity | -| `children` | `Vec` | The children entities to push | - - -```lua -world.push_children(parent, {child1, child2}) -``` - -### get_children - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The parent entity | - -Returns: - -| Return | Description | -| --- | --- | -| `Vec` | The children entities | - -```lua -local children = world.get_children(parent) -for _, child in pairs(children) do - print("child: " .. child) -end -``` - -### get_parent - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The child entity | - -Returns: - -| Return | Description | -| --- | --- | -| `Option` | The parent entity if it exists, otherwise `None` | - -```lua -local parent = world.get_parent(child) -if parent ~= nil then - print("parent: " .. parent) -end -``` - -### despawn - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to despawn | - -```lua -world.despawn(entity) -``` - -### despawn_descendants - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to despawn descendants of | - -```lua -world.despawn_descendants(entity) -``` - -### despawn_recursive - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to despawn recursively | - -```lua -world.despawn_recursive(entity) -``` - -### has_entity - -Arguments: - -| Argument | Type | Description | -| --- | --- | --- | -| `entity` | `Entity` | The entity to check | - -Returns: - -| Return | Description | -| --- | --- | -| `bool` | `true` if the entity exists, otherwise `false` | - -```lua -local exists = world.has_entity(entity) -if exists then - print("entity exists") -end -``` - -### query - -Returns: - -| Return | Description | -| --- | --- | -| `ScriptQueryBuilder` | The query builder | - -```lua -local queryBuilder = world.query() -``` - -### exit -Send the exit signal to the application, will gracefully shutdown the application. - -```lua -world.exit() -``` From e1d17f6fe12b016d871577f3dd7cf3001b5f8386 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Fri, 21 Mar 2025 08:40:10 +0000 Subject: [PATCH 05/51] chore: Update readme.md --- readme.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 2b9c78e69f..abf303eb3a 100644 --- a/readme.md +++ b/readme.md @@ -20,10 +20,13 @@ Although Bevy doesn't directly support scripting, efforts are underway to incorp - Script management via commands - Hot loading - Support for multiple scripting languages -- All script bindings managed in one place (`ScriptDynamicFunctionRegistry`) +- Extremely flexible bindings + - Attach bindings to ANY reflect implementing types including foreign types. + - Globals and bindings are set in one place and translated to all supported languages + - Broad set of core Bevy bindings generated for you +- Dynamic systems & components registerable from scripts - Customizable event driven communication between bevy and scripts (`on_update`, `on_init` etc..) -- Automatically generated bevy bindings -- ~Documentation generation~ temporarilly on hold[^1] +- Documentation generation ## Support From a6a989c8b11177a8ebbb85f2c2b52264da643ba7 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Fri, 21 Mar 2025 08:50:19 +0000 Subject: [PATCH 06/51] feat: :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data (#379) # Summary In order to support dynamic script components we need: - to relax some requirements on having a `ReflectComponent` registration to allow component operations - add a script component registry, which stores metadata about each dynamically registered component - Add `ScriptValue` to `into_script_ref` and `from_script_ref` implementations to allow any value to be inserted into the components payload The nice thing about this implementation is it *just* works with the reference system. The current problems are that the global type cache will not reflect this script component, to fix that we'd need a mechanism for "re-computing" certain globals for scripts. This also does not allow setting a schema on these, which is handy but also footgunny As a side effect I've also made `remove_component` no longer require the `ReflectComponent` registration --- .github/workflows/mdbook.yml | 2 + .../tests/has_component/dynamic_component.lua | 6 + .../has_component/dynamic_component.rhai | 6 + .../new_component_can_be_retrieved.lua | 12 ++ .../new_component_can_be_set.lua | 17 ++ .../can_remove_dynamic_component.lua | 11 ++ .../can_remove_dynamic_component.rhai | 11 ++ .../remove_component/no_component_data.lua | 5 + ...ata_errors.rhai => no_component_data.rhai} | 5 +- .../no_component_data_errors.lua | 7 - .../src/bindings/function/from_ref.rs | 11 +- .../src/bindings/function/into_ref.rs | 1 + .../src/bindings/globals/core.rs | 2 + .../src/bindings/mod.rs | 1 + .../src/bindings/query.rs | 91 ++++++++- .../src/bindings/script_component.rs | 164 ++++++++++++++++ .../src/bindings/script_system.rs | 10 +- .../src/bindings/script_value.rs | 3 +- .../src/bindings/world.rs | 185 ++++++++---------- crates/bevy_mod_scripting_core/src/error.rs | 4 +- crates/bevy_mod_scripting_core/src/lib.rs | 5 +- .../bevy_mod_scripting_functions/src/core.rs | 26 ++- .../bevy_mod_scripting_lua/tests/lua_tests.rs | 8 +- 23 files changed, 464 insertions(+), 129 deletions(-) create mode 100644 assets/tests/has_component/dynamic_component.lua create mode 100644 assets/tests/has_component/dynamic_component.rhai create mode 100644 assets/tests/register_new_component/new_component_can_be_retrieved.lua create mode 100644 assets/tests/register_new_component/new_component_can_be_set.lua create mode 100644 assets/tests/remove_component/can_remove_dynamic_component.lua create mode 100644 assets/tests/remove_component/can_remove_dynamic_component.rhai create mode 100644 assets/tests/remove_component/no_component_data.lua rename assets/tests/remove_component/{no_component_data_errors.rhai => no_component_data.rhai} (50%) delete mode 100644 assets/tests/remove_component/no_component_data_errors.lua create mode 100644 crates/bevy_mod_scripting_core/src/bindings/script_component.rs diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index ab68716504..2d6f1489c5 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -9,6 +9,7 @@ on: - 'docs/**' - 'crates/xtask/**' - '.github/workflows/mdbook.yml' + - 'crates/bevy_mod_scripting_functions/**' pull_request: branches: - "**" @@ -16,6 +17,7 @@ on: - 'docs/**' - 'crates/xtask/**' - '.github/workflows/mdbook.yml' + - 'crates/bevy_mod_scripting_functions/**' jobs: diff --git a/assets/tests/has_component/dynamic_component.lua b/assets/tests/has_component/dynamic_component.lua new file mode 100644 index 0000000000..fc197bb2db --- /dev/null +++ b/assets/tests/has_component/dynamic_component.lua @@ -0,0 +1,6 @@ +local NewComponent = world.register_new_component("ScriptComponentA") +local entity = world.spawn() + +assert(world.has_component(entity, NewComponent) == false, "Entity should not have component") +world.add_default_component(entity, NewComponent) +assert(world.has_component(entity, NewComponent) == true, "Entity should have component") \ No newline at end of file diff --git a/assets/tests/has_component/dynamic_component.rhai b/assets/tests/has_component/dynamic_component.rhai new file mode 100644 index 0000000000..0c628ccaa4 --- /dev/null +++ b/assets/tests/has_component/dynamic_component.rhai @@ -0,0 +1,6 @@ +let NewComponent = world.register_new_component.call("ScriptComponentA"); +let entity = world.spawn_.call(); + +assert(world.has_component.call(entity, NewComponent) == false, "Entity should not have component"); +world.add_default_component.call(entity, NewComponent); +assert(world.has_component.call(entity, NewComponent) == true, "Entity should have component"); \ No newline at end of file diff --git a/assets/tests/register_new_component/new_component_can_be_retrieved.lua b/assets/tests/register_new_component/new_component_can_be_retrieved.lua new file mode 100644 index 0000000000..30e66b30ce --- /dev/null +++ b/assets/tests/register_new_component/new_component_can_be_retrieved.lua @@ -0,0 +1,12 @@ +local NewComponent = world.register_new_component("ScriptComponentA") +assert(NewComponent ~= nil, "Failed to register new component") +assert(NewComponent:short_name() == "DynamicComponent", "Unexpected component type") + + +local new_entity = world.spawn() + +world.add_default_component(new_entity, NewComponent) + +local component_intance = world.get_component(new_entity, NewComponent) + +assert(component_intance ~= nil, "Failed to get component instance") \ No newline at end of file diff --git a/assets/tests/register_new_component/new_component_can_be_set.lua b/assets/tests/register_new_component/new_component_can_be_set.lua new file mode 100644 index 0000000000..fe4c4458ba --- /dev/null +++ b/assets/tests/register_new_component/new_component_can_be_set.lua @@ -0,0 +1,17 @@ +function on_test() + local NewComponent = world.register_new_component("ScriptComponentA") + + local new_entity = world.spawn() + world.insert_component(new_entity, NewComponent, construct(types.DynamicComponent, { + data = "Hello World" + })) + + local component_instance = world.get_component(new_entity, NewComponent) + assert(component_instance.data == "Hello World", "unexpected value: " .. component_instance.data) + + component_instance.data = { + foo = "bar" + } + + assert(component_instance.data.foo == "bar", "unexpected value: " .. component_instance.data.foo) +end \ No newline at end of file diff --git a/assets/tests/remove_component/can_remove_dynamic_component.lua b/assets/tests/remove_component/can_remove_dynamic_component.lua new file mode 100644 index 0000000000..d7eb6204af --- /dev/null +++ b/assets/tests/remove_component/can_remove_dynamic_component.lua @@ -0,0 +1,11 @@ +local NewComponent = world.register_new_component("ScriptComponentA") +local new_entity = world.spawn() +world.add_default_component(new_entity, NewComponent) + +local component_instance = world.get_component(new_entity, NewComponent) +assert(component_instance ~= nil, "unexpected value: " .. tostring(component_instance.data)) + +world.remove_component(new_entity, NewComponent) +local component_instance = world.get_component(new_entity, NewComponent) + +assert(component_instance == nil, "unexpected value: " .. tostring(component_instance)) diff --git a/assets/tests/remove_component/can_remove_dynamic_component.rhai b/assets/tests/remove_component/can_remove_dynamic_component.rhai new file mode 100644 index 0000000000..a7ad664d65 --- /dev/null +++ b/assets/tests/remove_component/can_remove_dynamic_component.rhai @@ -0,0 +1,11 @@ +let NewComponent = world.register_new_component.call("ScriptComponentA"); +let new_entity = world.spawn_.call(); +world.add_default_component.call(new_entity, NewComponent); + +let component_instance = world.get_component.call(new_entity, NewComponent); +assert(type_of(component_instance) != "()", "unexpected value: " + component_instance.data); + +world.remove_component.call(new_entity, NewComponent); +let component_instance_after = world.get_component.call(new_entity, NewComponent); + +assert(type_of(component_instance_after) == "()", "unexpected value: " + component_instance_after); \ No newline at end of file diff --git a/assets/tests/remove_component/no_component_data.lua b/assets/tests/remove_component/no_component_data.lua new file mode 100644 index 0000000000..c473ca055c --- /dev/null +++ b/assets/tests/remove_component/no_component_data.lua @@ -0,0 +1,5 @@ + +local entity = world._get_entity_with_test_component("CompWithDefault") +local component = world.get_type_by_name("CompWithDefault") +world.remove_component(entity, component) +assert(world.has_component(entity, component) == false, "Component was not removed") diff --git a/assets/tests/remove_component/no_component_data_errors.rhai b/assets/tests/remove_component/no_component_data.rhai similarity index 50% rename from assets/tests/remove_component/no_component_data_errors.rhai rename to assets/tests/remove_component/no_component_data.rhai index 07f8f714a8..bf70543716 100644 --- a/assets/tests/remove_component/no_component_data_errors.rhai +++ b/assets/tests/remove_component/no_component_data.rhai @@ -2,6 +2,5 @@ let entity = world._get_entity_with_test_component.call("CompWithDefault"); let component = world.get_type_by_name.call("CompWithDefault"); -assert_throws(||{ - world.remove_component.call(entity, component); -}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") +world.remove_component.call(entity, component); +assert(world.has_component.call(entity, component) == false, "Component was not removed"); \ No newline at end of file diff --git a/assets/tests/remove_component/no_component_data_errors.lua b/assets/tests/remove_component/no_component_data_errors.lua deleted file mode 100644 index 0dc5c2d780..0000000000 --- a/assets/tests/remove_component/no_component_data_errors.lua +++ /dev/null @@ -1,7 +0,0 @@ - -local entity = world._get_entity_with_test_component("CompWithDefault") -local component = world.get_type_by_name("CompWithDefault") - -assert_throws(function () - world.remove_component(entity, component) -end, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs index bf43ccf5a6..3308392750 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from_ref.rs @@ -1,15 +1,15 @@ //! Contains the [`FromScriptRef`] trait and its implementations. -use std::{any::TypeId, ffi::OsString, path::PathBuf}; -use bevy::reflect::{ - DynamicEnum, DynamicList, DynamicMap, DynamicTuple, DynamicVariant, Map, PartialReflect, -}; use crate::{ - bindings::{match_by_type, WorldGuard, FromScript}, + bindings::{match_by_type, FromScript, WorldGuard}, error::InteropError, reflection_extensions::TypeInfoExtensions, ScriptValue, }; +use bevy::reflect::{ + DynamicEnum, DynamicList, DynamicMap, DynamicTuple, DynamicVariant, Map, PartialReflect, +}; +use std::{any::TypeId, ffi::OsString, path::PathBuf}; /// Converts from a [`ScriptValue`] to a value equivalent to the given [`TypeId`]. /// @@ -56,6 +56,7 @@ impl FromScriptRef for Box { tq : String => return ::from_script(value, world).map(|a| Box::new(a) as _), tr : PathBuf => return ::from_script(value, world).map(|a| Box::new(a) as _), ts : OsString=> return ::from_script(value, world).map(|a| Box::new(a) as _), + tsv: ScriptValue => return ::from_script(value, world).map(|a| Box::new(a) as _), tn : () => return <()>::from_script(value, world).map(|a| Box::new(a) as _) } ); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs index 3679c784a2..bdce9f9630 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs @@ -102,6 +102,7 @@ fn into_script_ref( }, tr : PathBuf => return downcast_into_value!(r, PathBuf).clone().into_script(world), ts : OsString=> return downcast_into_value!(r, OsString).clone().into_script(world), + tsv: ScriptValue=> return Ok(downcast_into_value!(r, ScriptValue).clone()), tn : () => return Ok(ScriptValue::Unit) } ); diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index 5dee9ee92e..202ae6ea70 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -69,6 +69,8 @@ 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. + /// + /// Note that this cache will NOT contain types manually registered by scripts via `register_new_component`. fn types( guard: WorldGuard, ) -> Result< diff --git a/crates/bevy_mod_scripting_core/src/bindings/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/mod.rs index 6cfb01fc35..d7402ec681 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/mod.rs @@ -12,5 +12,6 @@ crate::private::export_all_in_modules! { script_system, script_value, world, + script_component, type_data } diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 648b71bd38..33e4ce3c02 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -1,18 +1,20 @@ //! Utilities for querying the world. -use super::{with_global_access, ReflectReference, WorldAccessGuard}; +use super::{with_global_access, DynamicComponent, ReflectReference, WorldAccessGuard, WorldGuard}; use crate::error::InteropError; use bevy::{ ecs::{ component::ComponentId, entity::Entity, query::{QueryData, QueryState}, + reflect::ReflectComponent, world::World, }, prelude::{EntityRef, QueryBuilder}, + ptr::OwningPtr, reflect::{ParsedPath, Reflect, TypeRegistration}, }; -use std::{any::TypeId, collections::VecDeque, sync::Arc}; +use std::{any::TypeId, collections::VecDeque, ptr::NonNull, sync::Arc}; /// A reference to a type which is not a `Resource` or `Component`. /// @@ -27,9 +29,13 @@ pub struct ScriptTypeRegistration { /// A reference to a component type's reflection registration. /// /// In general think of this as a handle to a type. +/// +/// Not to be confused with script registered dynamic components, although this can point to a script registered component. pub struct ScriptComponentRegistration { pub(crate) registration: ScriptTypeRegistration, pub(crate) component_id: ComponentId, + /// whether this is a component registered BY a script + pub(crate) is_dynamic_script_component: bool, } #[derive(Clone, Reflect, Debug)] @@ -100,6 +106,8 @@ impl ScriptComponentRegistration { /// Creates a new [`ScriptComponentRegistration`] from a [`ScriptTypeRegistration`] and a [`ComponentId`]. pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self { Self { + is_dynamic_script_component: registration.type_id() + == std::any::TypeId::of::(), registration, component_id, } @@ -120,6 +128,85 @@ impl ScriptComponentRegistration { pub fn into_type_registration(self) -> ScriptTypeRegistration { self.registration } + + /// Removes an instance of this component from the given entity + pub fn remove_from_entity( + &self, + world: WorldGuard, + entity: Entity, + ) -> Result<(), InteropError> { + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + entity.remove_by_id(self.component_id); + Ok(()) + })? + } + + /// Inserts an instance of this component into the given entity + /// + /// Requires whole world access + pub fn insert_into_entity( + &self, + world: WorldGuard, + entity: Entity, + instance: Box, + ) -> Result<(), InteropError> { + if self.is_dynamic_script_component { + // if dynamic we already know the type i.e. `ScriptComponent` + // so we can just insert it + + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + let cast = instance.downcast::().map_err(|v| { + InteropError::type_mismatch(TypeId::of::(), Some(v.type_id())) + })?; + // the reason we leak the box, is because we don't want to double drop the owning ptr + + let ptr = (Box::leak(cast) as *mut DynamicComponent).cast(); + // Safety: cannot be null as we just created it from a valid reference + let non_null_ptr = unsafe { NonNull::new_unchecked(ptr) }; + // Safety: + // - we know the type is ScriptComponent, as we just created the pointer + // - the box will stay valid for the life of this function, and we do not return the ptr + // - pointer is alligned correctly + // - nothing else will call drop on this + let owning_ptr = unsafe { OwningPtr::new(non_null_ptr) }; + // Safety: + // - Owning Ptr is valid as we just created it + // - TODO: do we need to check if ComponentId is from this world? How? + unsafe { entity.insert_by_id(self.component_id, owning_ptr) }; + Ok(()) + })? + } else { + let component_data = self + .type_registration() + .type_registration() + .data::() + .ok_or_else(|| { + InteropError::missing_type_data( + self.registration.type_id(), + "ReflectComponent".to_owned(), + ) + })?; + + // TODO: this shouldn't need entire world access it feels + let type_registry = world.type_registry(); + world.with_global_access(|world| { + let mut entity = world + .get_entity_mut(entity) + .map_err(|_| InteropError::missing_entity(entity))?; + { + let registry = type_registry.read(); + component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); + } + Ok(()) + })? + } + } } impl std::fmt::Debug for ScriptTypeRegistration { diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs new file mode 100644 index 0000000000..b1e12f3dc3 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -0,0 +1,164 @@ +//! Everything necessary to support scripts registering their own components + +use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldAccessGuard}; +use crate::error::InteropError; +use bevy::{ + app::{App, Plugin}, + ecs::{ + component::{Component, ComponentDescriptor, StorageType}, + system::Resource, + }, + reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect}, + utils::HashMap, +}; +use parking_lot::RwLock; +use std::{alloc::Layout, mem::needs_drop, sync::Arc}; + +/// A dynamic script component +#[derive(Reflect, Clone, Default)] +#[reflect(Default)] +pub struct DynamicComponent { + data: ScriptValue, +} + +/// Some metadata about dynamic script components +pub struct DynamicComponentInfo { + /// The name of the component + pub name: String, + /// The type registration for the component + pub registration: ScriptComponentRegistration, +} + +impl Component for DynamicComponent { + const STORAGE_TYPE: StorageType = StorageType::Table; +} + +/// A registry of dynamically registered script components +#[derive(Clone, Resource, Default)] +pub struct AppScriptComponentRegistry(pub Arc>); + +impl AppScriptComponentRegistry { + /// Reads the underlying registry + pub fn read(&self) -> parking_lot::RwLockReadGuard { + self.0.read() + } + + /// Writes to the underlying registry + pub fn write(&self) -> parking_lot::RwLockWriteGuard { + self.0.write() + } +} + +#[derive(Default)] +/// A registry of dynamically registered script components +pub struct ScriptComponentRegistry { + components: HashMap, +} + +impl ScriptComponentRegistry { + /// Registers a dynamic script component, possibly overwriting an existing one + pub fn register(&mut self, info: DynamicComponentInfo) { + self.components.insert(info.name.clone(), info); + } + + /// Gets a dynamic script component by name + pub fn get(&self, name: &str) -> Option<&DynamicComponentInfo> { + self.components.get(name) + } +} + +impl WorldAccessGuard<'_> { + /// Registers a dynamic script component, and returns a reference to its registration + pub fn register_script_component( + &self, + component_name: String, + ) -> Result { + 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 { + // Safety: same safety guarantees as ComponentDescriptor::new + // we know the type in advance + // we only use this method to name the component + ComponentDescriptor::new_with_layout( + component_name.clone(), + DynamicComponent::STORAGE_TYPE, + Layout::new::(), + needs_drop::().then_some(|x| x.drop_as::()), + ) + }; + 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( + ::get_type_registration(), + )), + component_id, + ); + + let component_info = DynamicComponentInfo { + name: component_name.clone(), + registration: registration.clone(), + }; + + component_registry.register(component_info); + + // TODO: we should probably retrieve this from the registry, but I don't see what people would want to register on this type + // in addition to the existing registrations. + Ok(registration) + } +} + +/// A plugin to support dynamic script components +pub(crate) struct DynamicScriptComponentPlugin; + +impl Plugin for DynamicScriptComponentPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .register_type::(); + } +} + +#[cfg(test)] +mod test { + use super::*; + use bevy::ecs::world::World; + + #[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::().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"); + + // can get the component through the world + let component = world + .components() + .get_info(info.registration.component_id) + .unwrap(); + + assert_eq!(component.name(), "ScriptTest"); + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs index feaf42be54..f9be183de8 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs @@ -5,8 +5,9 @@ use super::{ function::{from::Val, into::IntoScript, script_function::AppScriptFunctionRegistry}, schedule::AppScheduleRegistry, script_value::ScriptValue, - AppReflectAllocator, ReflectBaseType, ReflectReference, ScriptQueryBuilder, ScriptQueryResult, - ScriptResourceRegistration, WorldAccessGuard, WorldGuard, + AppReflectAllocator, AppScriptComponentRegistry, ReflectBaseType, ReflectReference, + ScriptQueryBuilder, ScriptQueryResult, ScriptResourceRegistration, WorldAccessGuard, + WorldGuard, }; use crate::{ bindings::pretty_print::DisplayWithWorld, @@ -288,6 +289,7 @@ struct ScriptSystemState { type_registry: AppTypeRegistry, function_registry: AppScriptFunctionRegistry, schedule_registry: AppScheduleRegistry, + component_registry: AppScriptComponentRegistry, allocator: AppReflectAllocator, subset: HashSet, callback_label: CallbackLabel, @@ -424,6 +426,7 @@ impl System for DynamicScriptSystem

{ state.allocator.clone(), state.function_registry.clone(), state.schedule_registry.clone(), + state.component_registry.clone(), ) }; @@ -577,6 +580,9 @@ impl System for DynamicScriptSystem

{ .clone(), schedule_registry: world.get_resource_or_init::().clone(), allocator: world.get_resource_or_init::().clone(), + component_registry: world + .get_resource_or_init::() + .clone(), subset, callback_label: self.name.to_string().into(), system_params, diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs index 306d318a32..c8cbb21e48 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs @@ -13,10 +13,11 @@ use super::{ /// An abstraction of values that can be passed to and from scripts. /// This allows us to re-use logic between scripting languages. -#[derive(Debug, Clone, PartialEq, Reflect)] +#[derive(Debug, Clone, PartialEq, Reflect, Default)] #[reflect(opaque)] pub enum ScriptValue { /// Represents the absence of a value. + #[default] Unit, /// Represents a boolean value. Bool(bool), diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index b6ef742261..79d082a6c1 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -9,13 +9,23 @@ use super::{ access_map::{ AccessCount, AccessMapKey, AnyAccessMap, DynamicSystemMeta, ReflectAccessId, ReflectAccessKind, SubsetAccessMap, - }, function::{ + }, + 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, Union + }, + pretty_print::DisplayWithWorld, + schedule::AppScheduleRegistry, + script_value::ScriptValue, + with_global_access, AppReflectAllocator, AppScriptComponentRegistry, ReflectBase, + ReflectBaseType, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, + ScriptTypeRegistration, Union, }; use crate::{ - bindings::{function::{from::FromScript, from_ref::FromScriptRef}, with_access_read, with_access_write}, + bindings::{ + function::{from::FromScript, from_ref::FromScriptRef}, + with_access_read, with_access_write, + }, error::InteropError, reflection_extensions::PartialReflectExt, }; @@ -24,7 +34,7 @@ use bevy::{ ecs::{ component::{Component, ComponentId}, entity::Entity, - reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld, ReflectResource}, + reflect::{AppTypeRegistry, ReflectFromWorld, ReflectResource}, system::{Commands, Resource}, world::{unsafe_world_cell::UnsafeWorldCell, CommandQueue, Mut, World}, }, @@ -73,6 +83,8 @@ pub(crate) struct WorldAccessGuardInner<'w> { function_registry: AppScriptFunctionRegistry, /// The schedule registry for the world schedule_registry: AppScheduleRegistry, + /// The registry of script registered components + script_component_registry: AppScriptComponentRegistry, } impl std::fmt::Debug for WorldAccessGuardInner<'_> { @@ -159,6 +171,7 @@ impl<'w> WorldAccessGuard<'w> { allocator: AppReflectAllocator, function_registry: AppScriptFunctionRegistry, schedule_registry: AppScheduleRegistry, + script_component_registry: AppScriptComponentRegistry, ) -> Self { Self { inner: Rc::new(WorldAccessGuardInner { @@ -172,6 +185,7 @@ impl<'w> WorldAccessGuard<'w> { allocator, function_registry, schedule_registry, + script_component_registry, }), invalid: Rc::new(false.into()), } @@ -194,6 +208,10 @@ impl<'w> WorldAccessGuard<'w> { .get_resource_or_init::() .clone(); + let script_component_registry = world + .get_resource_or_init::() + .clone(); + let schedule_registry = world.get_resource_or_init::().clone(); Self { inner: Rc::new(WorldAccessGuardInner { @@ -203,6 +221,7 @@ impl<'w> WorldAccessGuard<'w> { type_registry, function_registry, schedule_registry, + script_component_registry, }), invalid: Rc::new(false.into()), } @@ -318,6 +337,11 @@ impl<'w> WorldAccessGuard<'w> { self.inner.schedule_registry.clone() } + /// Returns the component registry for the world + pub fn component_registry(&self) -> AppScriptComponentRegistry { + self.inner.script_component_registry.clone() + } + /// Returns the script allocator for the world pub fn allocator(&self) -> AppReflectAllocator { self.inner.allocator.clone() @@ -542,7 +566,6 @@ 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>, @@ -802,18 +825,26 @@ impl WorldAccessGuard<'_> { } /// 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 { + pub fn get_type_by_name(&self, type_name: &str) -> Option { 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)) + .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()))) } /// 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>, InteropError> { - + pub(crate) fn get_type_registration( + &self, + registration: ScriptTypeRegistration, + ) -> Result< + Union< + ScriptTypeRegistration, + Union, + >, + InteropError, + > { let registration = match self.get_resource_type(registration)? { Ok(res) => { return Ok(Union::new_right(Union::new_right(res))); @@ -831,15 +862,31 @@ impl WorldAccessGuard<'_> { Ok(Union::new_left(registration)) } - /// Similar to [`Self::get_type_by_name`] but returns a type erased [`ScriptTypeRegistration`], [`ScriptComponentRegistration`] or [`ScriptResourceRegistration`] + /// 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>>, InteropError> { - let val = self.get_type_by_name(type_name); + pub fn get_type_registration_by_name( + &self, + type_name: String, + ) -> Result< + Option< + Union< + ScriptTypeRegistration, + Union, + >, + >, + InteropError, + > { + let val = self.get_type_by_name(&type_name); Ok(match val { - Some(registration) => { - Some(self.get_type_registration(registration)?) + Some(registration) => Some(self.get_type_registration(registration)?), + None => { + // try the component registry + let components = self.component_registry(); + let components = components.read(); + components + .get(&type_name) + .map(|c| Union::new_right(Union::new_left(c.registration.clone()))) } - None => None, }) } @@ -881,18 +928,6 @@ impl WorldAccessGuard<'_> { entity: Entity, registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { - // let cell = self.as_unsafe_world_cell()?; - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - // we look for ReflectDefault or ReflectFromWorld data then a ReflectComponent data let instance = if let Some(default_td) = registration .type_registration() @@ -913,19 +948,7 @@ impl WorldAccessGuard<'_> { )); }; - // TODO: this shouldn't need entire world access it feels - self.with_global_access(|world| { - let type_registry = self.type_registry(); - - let mut entity = world - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; - { - let registry = type_registry.read(); - component_data.insert(&mut entity, instance.as_partial_reflect(), ®istry); - } - Ok(()) - })? + registration.insert_into_entity(self.clone(), entity, instance) } /// insert the component into the entity @@ -935,63 +958,41 @@ impl WorldAccessGuard<'_> { registration: ScriptComponentRegistration, value: ReflectReference, ) -> Result<(), InteropError> { - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - - with_global_access!(&self.inner.accesses, "Could not insert element", { - let cell = self.as_unsafe_world_cell()?; - let type_registry = self.type_registry(); - let type_registry = type_registry.read(); - let world_mut = unsafe { cell.world_mut() }; - let mut entity = world_mut - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; + let instance = >::from_script_ref( + registration.type_registration().type_id(), + ScriptValue::Reference(value), + self.clone(), + )?; - let ref_ = unsafe { value.reflect_unsafe(self.clone())? }; - component_data.apply_or_insert(&mut entity, ref_, &type_registry); + 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:?}"), + ) + })?; - Ok(()) - })? + registration.insert_into_entity(self.clone(), entity, reflect) } /// get the component from the entity pub fn get_component( &self, entity: Entity, - component_id: ComponentId, + component_registration: ScriptComponentRegistration, ) -> Result, InteropError> { let cell = self.as_unsafe_world_cell()?; let entity = cell .get_entity(entity) .ok_or_else(|| InteropError::missing_entity(entity))?; - let component_info = cell - .components() - .get_info(component_id) - .ok_or_else(|| InteropError::invalid_component(component_id))?; - - if entity.contains_id(component_id) { + if entity.contains_id(component_registration.component_id) { Ok(Some(ReflectReference { base: ReflectBaseType { - type_id: component_info.type_id().ok_or_else(|| { - InteropError::unsupported_operation( - None, - None, - format!( - "Component {} does not have a type id. Such components are not supported by BMS.", - component_id.display_without_world() - ), - ) - })?, - base_id: ReflectBase::Component(entity.id(), component_id), + type_id: component_registration.type_registration().type_id(), + base_id: ReflectBase::Component( + entity.id(), + component_registration.component_id, + ), }, reflect_path: ParsedPath(vec![]), })) @@ -1020,25 +1021,7 @@ impl WorldAccessGuard<'_> { entity: Entity, registration: ScriptComponentRegistration, ) -> Result<(), InteropError> { - let component_data = registration - .type_registration() - .type_registration() - .data::() - .ok_or_else(|| { - InteropError::missing_type_data( - registration.registration.type_id(), - "ReflectComponent".to_owned(), - ) - })?; - - // TODO: this shouldn't need entire world access it feels - self.with_global_access(|world| { - let mut entity = world - .get_entity_mut(entity) - .map_err(|_| InteropError::missing_entity(entity))?; - component_data.remove(&mut entity); - Ok(()) - })? + registration.remove_from_entity(self.clone(), entity) } /// get the given resource diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index 1c63c64bd0..3e2a104c57 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -401,10 +401,10 @@ impl InteropError { /// Thrown if a type cannot be converted from reflect, this can happen if the type was unable to /// re-construct itself from a dynamic value. - pub fn failed_from_reflect(type_id: Option, reason: String) -> Self { + pub fn failed_from_reflect(type_id: Option, reason: impl Into) -> Self { Self(Arc::new(InteropErrorInner::FailedFromReflect { type_id, - reason, + reason: reason.into(), })) } diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index de21ffed62..a244130b61 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -14,7 +14,8 @@ use bindings::{ globals::{core::CoreScriptGlobalsPlugin, AppScriptGlobalsRegistry}, schedule::AppScheduleRegistry, script_value::ScriptValue, - AppReflectAllocator, ReflectAllocator, ReflectReference, ScriptTypeRegistration, + AppReflectAllocator, DynamicScriptComponentPlugin, ReflectAllocator, ReflectReference, + ScriptTypeRegistration, }; use commands::{AddStaticScript, RemoveStaticScript}; use context::{ @@ -312,7 +313,7 @@ fn once_per_app_init(app: &mut App) { ((garbage_collector).in_set(ScriptingSystemSet::GarbageCollection),), ); - app.add_plugins(CoreScriptGlobalsPlugin); + app.add_plugins((CoreScriptGlobalsPlugin, DynamicScriptComponentPlugin)); configure_asset_systems(app); } diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index bcac621f01..a3689932ca 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -116,7 +116,7 @@ impl World { ) -> Result, InteropError> { profiling::function_scope!("get_component"); let world = ctxt.world()?; - let val = world.get_component(*entity, registration.component_id())?; + let val = world.get_component(*entity, registration.into_inner())?; Ok(val) } @@ -445,6 +445,28 @@ impl World { let world = ctxt.world()?; world.exit() } + + /// Registers a new component type with the world. + /// + /// The component will behave like any other native component for all intents and purposes. + /// The type that will be instantiated to back this component will be `DynamicComponent` which contains just one field: + /// - `data` + /// + /// This field can be set to any value and modified freely. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `name`: The name of the component type + /// Returns: + /// * `registration`: The registration of the new component type if successful. + fn register_new_component( + ctxt: FunctionCallContext, + name: String, + ) -> Result, InteropError> { + profiling::function_scope!("register_new_component"); + let world = ctxt.world()?; + world.register_script_component(name).map(Val) + } } #[script_bindings( @@ -1272,7 +1294,7 @@ impl GlobalNamespace { let reflect_val = val.try_into_reflect().map_err(|_| { InteropError::failed_from_reflect( Some(registration.type_id()), - "Could not construct the type".into(), + "Could not construct the type", ) })?; diff --git a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs index 3fa8b4b75d..cacfd11bb6 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs +++ b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs @@ -14,12 +14,15 @@ use std::{ path::{Path, PathBuf}, }; +#[derive(Debug)] struct Test { path: PathBuf, } impl Test { fn execute(self) -> Result<(), Failed> { + println!("Running test: {:?}", self.path); + execute_integration_test::( |world, type_registry| { let _ = world; @@ -126,10 +129,11 @@ fn main() { let args = Arguments::from_args(); // Create a list of tests and/or benchmarks (in this case: two dummy tests). - let tests = discover_all_tests() + let all_tests = discover_all_tests(); + println!("discovered {} tests. {:?}", all_tests.len(), all_tests); + let tests = all_tests .into_iter() .map(|t| Trial::test(t.name(), move || t.execute())); - // Run all tests and exit the application appropriatly. libtest_mimic::run(&args, tests.collect()).exit(); } From 7a39963683c07d9cc601c8e3297b6367d5832758 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Fri, 21 Mar 2025 08:59:35 +0000 Subject: [PATCH 07/51] feat: add ScriptValue override for printing opaque values (#380) # Summary - Means printing reflect types containing script values will print them nicer --- .../src/bindings/pretty_print.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs index 2e515afa9c..65d83c426d 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs @@ -174,6 +174,7 @@ impl ReflectReferencePrinter { } id if id == TypeId::of::() => downcast_case!(v, output, char), id if id == TypeId::of::() => downcast_case!(v, output, bool), + id if id == TypeId::of::() => downcast_case!(v, output, ScriptValue), _ => { output.push_str( v.get_represented_type_info() @@ -654,7 +655,7 @@ impl DisplayWithWo #[cfg(test)] mod test { - use bevy::prelude::AppTypeRegistry; + use bevy::{prelude::AppTypeRegistry, reflect::Reflect}; use crate::bindings::{ function::script_function::AppScriptFunctionRegistry, AppReflectAllocator, @@ -778,4 +779,29 @@ mod test { assert_eq!(map.display_value_with_world(world.clone()), "{hello: true}"); } + + #[test] + fn test_script_value_in_reference() { + let mut world = setup_world(); + let world = WorldGuard::new_exclusive(&mut world); + + #[derive(Reflect)] + struct Test { + val: ScriptValue, + } + + let test = Test { + val: ScriptValue::Bool(true), + }; + + let allocator = world.allocator(); + let mut allocator_write = allocator.write(); + + let reflect_reference = ReflectReference::new_allocated(test, &mut allocator_write); + drop(allocator_write); + assert_eq!( + reflect_reference.display_value_with_world(world.clone()), + "{val: Reflect(ScriptValue(Bool(true)))}" + ); + } } From 06c153718d3581184aebde55df48e315ce48b22a Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 15:27:37 +0000 Subject: [PATCH 08/51] feat: Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource (#381) # Summary - Moves tests to run centrally in the workspace tests directory - Refactors test utilities to share a lot of the test discovery and running code between languages and benchmarks - Adds criterion benchmarks - Adds CI for snapshoting performance from main to `bencher.dev` --- .github/workflows/snapshot_benchmark_main.yml | 32 ++ Cargo.toml | 18 +- assets/benchmarks/component/access.lua | 5 + assets/benchmarks/component/access.rhai | 5 + assets/benchmarks/component/get.lua | 5 + assets/benchmarks/component/get.rhai | 5 + benches/benchmarks.rs | 95 ++++++ crates/bevy_mod_scripting_core/src/script.rs | 38 +++ .../bevy_mod_scripting_lua/Cargo.toml | 9 - .../data/construct/construct_unit_struct.lua | 4 - .../bevy_mod_scripting_lua/tests/lua_tests.rs | 139 --------- .../bevy_mod_scripting_rhai/Cargo.toml | 9 - .../tests/data/construct/unit_struct.rhai | 2 - .../tests/rhai_tests.rs | 151 ---------- .../Cargo.toml | 10 +- .../src/lib.rs | 280 +++++++++++++++++- crates/testing_crates/test_utils/src/lib.rs | 73 +++++ crates/xtask/src/main.rs | 42 +-- tests/script_tests.rs | 55 ++++ 19 files changed, 629 insertions(+), 348 deletions(-) create mode 100644 .github/workflows/snapshot_benchmark_main.yml create mode 100644 assets/benchmarks/component/access.lua create mode 100644 assets/benchmarks/component/access.rhai create mode 100644 assets/benchmarks/component/get.lua create mode 100644 assets/benchmarks/component/get.rhai create mode 100644 benches/benchmarks.rs delete mode 100644 crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_unit_struct.lua delete mode 100644 crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs delete mode 100644 crates/languages/bevy_mod_scripting_rhai/tests/data/construct/unit_struct.rhai delete mode 100644 crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs create mode 100644 tests/script_tests.rs diff --git a/.github/workflows/snapshot_benchmark_main.yml b/.github/workflows/snapshot_benchmark_main.yml new file mode 100644 index 0000000000..283ffbdb2c --- /dev/null +++ b/.github/workflows/snapshot_benchmark_main.yml @@ -0,0 +1,32 @@ +on: + push: + branches: main + +jobs: + benchmark_base_branch: + name: Continuous Benchmarking with Bencher + permissions: + checks: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run Xtask initializer + run: | + cargo xtask init + - uses: bencherdev/bencher@main + - name: Track base branch benchmarks with Bencher + run: | + bencher run \ + --project bms \ + --token '${{ secrets.BENCHER_API_TOKEN }}' \ + --branch main \ + --testbed ubuntu-latest \ + --threshold-measure latency \ + --threshold-test t_test \ + --threshold-max-sample-size 64 \ + --threshold-upper-boundary 0.99 \ + --thresholds-reset \ + --err \ + --adapter json \ + --github-actions '${{ secrets.GITHUB_TOKEN }}' \ + bencher run --adapter rust_criterion "cargo bench --features lua54" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7c14693ebe..3044400cce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,10 @@ features = ["lua54", "rhai"] [features] default = ["core_functions", "bevy_bindings"] -## lua -lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"] +lua = [ + "bevy_mod_scripting_lua", + "bevy_mod_scripting_functions/lua_bindings", +] ## lua # one of these must be selected lua51 = ["bevy_mod_scripting_lua/lua51", "lua"] lua52 = ["bevy_mod_scripting_lua/lua52", "lua"] @@ -76,8 +78,12 @@ clap = { version = "4.1", features = ["derive"] } rand = "0.8.5" bevy_console = "0.13" # rhai-rand = "0.1" +criterion = { version = "0.5" } ansi-parser = "0.9" ladfile_builder = { path = "crates/ladfile_builder", version = "0.2.6" } +script_integration_test_harness = { workspace = true } +test_utils = { workspace = true } +libtest-mimic = "0.8" [workspace] members = [ @@ -149,3 +155,11 @@ todo = "deny" [workspace.lints.rust] missing_docs = "deny" + +[[bench]] +name = "benchmarks" +harness = false + +[[test]] +name = "script_tests" +harness = false diff --git a/assets/benchmarks/component/access.lua b/assets/benchmarks/component/access.lua new file mode 100644 index 0000000000..639a25ed0f --- /dev/null +++ b/assets/benchmarks/component/access.lua @@ -0,0 +1,5 @@ +local entity_with_component = world._get_entity_with_test_component("TestComponent") + +function bench() + local strings = world.get_component(entity_with_component, types.TestComponent).strings +end \ No newline at end of file diff --git a/assets/benchmarks/component/access.rhai b/assets/benchmarks/component/access.rhai new file mode 100644 index 0000000000..1e5b7db5e6 --- /dev/null +++ b/assets/benchmarks/component/access.rhai @@ -0,0 +1,5 @@ +let entity_with_component = world._get_entity_with_test_component.call("TestComponent"); + +fn bench(){ + let strings = world.get_component.call(entity_with_component, types.TestComponent).strings; +} \ No newline at end of file diff --git a/assets/benchmarks/component/get.lua b/assets/benchmarks/component/get.lua new file mode 100644 index 0000000000..6a733e717e --- /dev/null +++ b/assets/benchmarks/component/get.lua @@ -0,0 +1,5 @@ +local entity_with_component = world._get_entity_with_test_component("TestComponent") + +function bench() + world.get_component(entity_with_component, types.TestComponent) +end \ No newline at end of file diff --git a/assets/benchmarks/component/get.rhai b/assets/benchmarks/component/get.rhai new file mode 100644 index 0000000000..75cf5e7cc2 --- /dev/null +++ b/assets/benchmarks/component/get.rhai @@ -0,0 +1,5 @@ +let entity_with_component = world._get_entity_with_test_component.call("TestComponent"); + +fn bench(){ + world.get_component.call(entity_with_component, types.TestComponent); +} \ No newline at end of file diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs new file mode 100644 index 0000000000..76766cfe47 --- /dev/null +++ b/benches/benchmarks.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use bevy::utils::HashMap; +use criterion::{ + criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion, +}; +use script_integration_test_harness::{run_lua_benchmark, run_rhai_benchmark}; +use test_utils::{discover_all_tests, Test}; + +extern crate bevy_mod_scripting; +extern crate script_integration_test_harness; +extern crate test_utils; + +pub trait BenchmarkExecutor { + fn benchmark_group(&self) -> String; + fn benchmark_name(&self) -> String; + fn execute(&self, criterion: &mut BenchmarkGroup); +} + +impl BenchmarkExecutor for Test { + fn benchmark_group(&self) -> String { + // we want to use OS agnostic paths + // use the file path from `benchmarks` onwards using folders as groupings + // replace file separators with `/` + // replace _ with spaces + let path = self.path.to_string_lossy(); + let path = path.split("benchmarks").collect::>()[1] + .replace(std::path::MAIN_SEPARATOR, "/"); + let first_folder = path.split("/").collect::>()[1]; + first_folder.replace("_", " ") + } + + fn benchmark_name(&self) -> String { + // use just the file stem + let name = self + .path + .file_stem() + .unwrap() + .to_string_lossy() + .to_string() + .replace("_", " "); + + let language = self.kind.to_string(); + + format!("{name} {language}") + } + + fn execute(&self, criterion: &mut BenchmarkGroup) { + match self.kind { + test_utils::TestKind::Lua => run_lua_benchmark( + &self.path.to_string_lossy(), + &self.benchmark_name(), + criterion, + ) + .expect("Benchmark failed"), + test_utils::TestKind::Rhai => run_rhai_benchmark( + &self.path.to_string_lossy(), + &self.benchmark_name(), + criterion, + ) + .expect("benchmark failed"), + } + } +} + +fn script_benchmarks(criterion: &mut Criterion) { + // find manifest dir + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let tests = discover_all_tests(manifest_dir, |p| p.starts_with("benchmarks")); + + // group by benchmark group + let mut grouped: HashMap> = + tests.into_iter().fold(HashMap::default(), |mut acc, t| { + acc.entry(t.benchmark_group()).or_default().push(t); + acc + }); + + // sort within groups by benchmark name + for (_, tests) in grouped.iter_mut() { + tests.sort_by_key(|a| a.benchmark_name()); + } + + for (group, tests) in grouped { + let mut benchmark_group = criterion.benchmark_group(group); + + for t in tests { + t.execute(&mut benchmark_group); + } + + benchmark_group.finish(); + } +} + +criterion_group!(benches, script_benchmarks); +criterion_main!(benches); diff --git a/crates/bevy_mod_scripting_core/src/script.rs b/crates/bevy_mod_scripting_core/src/script.rs index 64a93be8bd..c4fd82a5ea 100644 --- a/crates/bevy_mod_scripting_core/src/script.rs +++ b/crates/bevy_mod_scripting_core/src/script.rs @@ -39,6 +39,44 @@ pub struct Scripts { pub(crate) scripts: HashMap>, } +impl Scripts

{ + /// Inserts a script into the collection + pub fn insert(&mut self, script: Script

) { + self.scripts.insert(script.id.clone(), script); + } + + /// Removes a script from the collection, returning `true` if the script was in the collection, `false` otherwise + pub fn remove>(&mut self, script: S) -> bool { + self.scripts.remove(&script.into()).is_some() + } + + /// Checks if a script is in the collection + /// Returns `true` if the script is in the collection, `false` otherwise + pub fn contains>(&self, script: S) -> bool { + self.scripts.contains_key(&script.into()) + } + + /// Returns a reference to the script with the given id + pub fn get>(&self, script: S) -> Option<&Script

> { + self.scripts.get(&script.into()) + } + + /// Returns a mutable reference to the script with the given id + pub fn get_mut>(&mut self, script: S) -> Option<&mut Script

> { + self.scripts.get_mut(&script.into()) + } + + /// Returns an iterator over the scripts + pub fn iter(&self) -> impl Iterator> { + self.scripts.values() + } + + /// Returns a mutable iterator over the scripts + pub fn iter_mut(&mut self) -> impl Iterator> { + self.scripts.values_mut() + } +} + impl Default for Scripts

{ fn default() -> Self { Self { diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 7193a502c7..3d8c55919d 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -46,14 +46,5 @@ smol_str = "0.2.2" smallvec = "1.13" profiling = { workspace = true } -[dev-dependencies] -script_integration_test_harness = { workspace = true } -libtest-mimic = "0.8" -regex = "1.11" - -[[test]] -name = "lua_tests" -harness = false - [lints] workspace = true diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_unit_struct.lua b/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_unit_struct.lua deleted file mode 100644 index 4f574e75aa..0000000000 --- a/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_unit_struct.lua +++ /dev/null @@ -1,4 +0,0 @@ -local type = world.get_type_by_name("UnitStruct") -local constructed = construct(type, {}) - -assert(constructed ~= nil, "Value was not constructed") diff --git a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs deleted file mode 100644 index cacfd11bb6..0000000000 --- a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs +++ /dev/null @@ -1,139 +0,0 @@ -#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, missing_docs)] -use bevy_mod_scripting_core::{ - bindings::{pretty_print::DisplayWithWorld, ThreadWorldContainer, WorldContainer}, - error::ScriptError, - ConfigureScriptPlugin, -}; -use bevy_mod_scripting_lua::LuaScriptingPlugin; -use libtest_mimic::{Arguments, Failed, Trial}; -use mlua::{Function, Lua, MultiValue}; -use script_integration_test_harness::execute_integration_test; -use std::{ - fs::{self, DirEntry}, - io, panic, - path::{Path, PathBuf}, -}; - -#[derive(Debug)] -struct Test { - path: PathBuf, -} - -impl Test { - fn execute(self) -> Result<(), Failed> { - println!("Running test: {:?}", self.path); - - execute_integration_test::( - |world, type_registry| { - let _ = world; - let _ = type_registry; - }, - |app| { - app.add_plugins(LuaScriptingPlugin::default().add_context_initializer(|_,ctxt: &mut Lua| { - let globals = ctxt.globals(); - globals.set( - "assert_throws", - ctxt.create_function(|_lua, (f, reg): (Function, String)| { - let world = ThreadWorldContainer.try_get_world()?; - let result = f.call::<()>(MultiValue::new()); - let err = match result { - Ok(_) => { - return Err(mlua::Error::external( - "Expected function to throw error, but it did not.", - )) - } - Err(e) => - ScriptError::from_mlua_error(e).display_with_world(world) - , - }; - - let regex = regex::Regex::new(®).unwrap(); - if regex.is_match(&err) { - Ok(()) - } else { - Err(mlua::Error::external( - format!( - "Expected error message to match the regex: \n{}\n\nBut got:\n{}", - regex.as_str(), - err - ), - )) - } - })?, - )?; - Ok(()) - })); - }, - self.path.as_os_str().to_str().unwrap(), - ) - .map_err(Failed::from) - } - - fn name(&self) -> String { - format!( - "script_test - lua - {}", - self.path - .to_string_lossy() - .split(&format!("tests{}data", std::path::MAIN_SEPARATOR)) - .last() - .unwrap() - ) - } -} - -fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { - if dir.is_dir() { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - visit_dirs(&path, cb)?; - } else { - cb(&entry); - } - } - } else { - panic!("Not a directory: {:?}", dir); - } - Ok(()) -} - -fn discover_all_tests() -> Vec { - let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let assets_root = workspace_root - .join("..") - .join("..") - .join("..") - .join("assets"); - let test_root = assets_root.join("tests"); - let mut test_files = Vec::new(); - visit_dirs(&test_root, &mut |entry| { - let path = entry.path(); - if path.extension().unwrap() == "lua" { - // only take the path from the assets bit - let relative = path.strip_prefix(&assets_root).unwrap(); - test_files.push(Test { - path: relative.to_path_buf(), - }); - } - }) - .unwrap(); - - test_files -} - -// run this with `cargo test --features lua54 --package bevy_mod_scripting_lua --test lua_tests` -// or filter using the prefix "lua test -" -fn main() { - // Parse command line arguments - let args = Arguments::from_args(); - - // Create a list of tests and/or benchmarks (in this case: two dummy tests). - let all_tests = discover_all_tests(); - println!("discovered {} tests. {:?}", all_tests.len(), all_tests); - let tests = all_tests - .into_iter() - .map(|t| Trial::test(t.name(), move || t.execute())); - // Run all tests and exit the application appropriatly. - libtest_mimic::run(&args, tests.collect()).exit(); -} diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index fd9f10783a..a017fa2e4e 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -22,14 +22,5 @@ bevy_mod_scripting_core = { workspace = true, features = ["rhai_impls"] } strum = { version = "0.26", features = ["derive"] } parking_lot = "0.12.1" -[dev-dependencies] -script_integration_test_harness = { workspace = true } -libtest-mimic = "0.8" -regex = "1.11" - -[[test]] -name = "rhai_tests" -harness = false - [lints] workspace = true diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/unit_struct.rhai b/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/unit_struct.rhai deleted file mode 100644 index 2b5840df44..0000000000 --- a/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/unit_struct.rhai +++ /dev/null @@ -1,2 +0,0 @@ -let type = world.get_type_by_name.call("UnitStruct"); -let constructed = construct.call(type, #{}); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs b/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs deleted file mode 100644 index 479f030a4e..0000000000 --- a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs +++ /dev/null @@ -1,151 +0,0 @@ -#![allow( - clippy::unwrap_used, - clippy::todo, - clippy::expect_used, - clippy::panic, - missing_docs -)] -use bevy_mod_scripting_core::{ - bindings::{pretty_print::DisplayWithWorld, ThreadWorldContainer, WorldContainer}, - error::ScriptError, - AddRuntimeInitializer, -}; -use bevy_mod_scripting_rhai::RhaiScriptingPlugin; -use libtest_mimic::{Arguments, Failed, Trial}; -use rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}; -use script_integration_test_harness::execute_integration_test; -use std::{ - fs::{self, DirEntry}, - io, panic, - path::{Path, PathBuf}, -}; - -struct Test { - path: PathBuf, -} - -impl Test { - fn execute(self) -> Result<(), Failed> { - execute_integration_test::( - |world, type_registry| { - let _ = world; - let _ = type_registry; - }, - |app| { - app.add_plugins(RhaiScriptingPlugin::default()); - app.add_runtime_initializer::(|runtime| { - let mut runtime = runtime.write(); - - runtime.register_fn("assert", |a: Dynamic, b: &str| { - if !a.is::() { - panic!("Expected a boolean value, but got {:?}", a); - } - if !a.as_bool().unwrap() { - panic!("Assertion failed. {}", b); - } - }); - - runtime.register_fn("assert", |a: Dynamic| { - if !a.is::() { - panic!("Expected a boolean value, but got {:?}", a); - } - if !a.as_bool().unwrap() { - panic!("Assertion failed"); - } - }); - runtime.register_fn("assert_throws", |ctxt: NativeCallContext, fn_: FnPtr, regex: String| { - let world = ThreadWorldContainer.try_get_world()?; - let args: [Dynamic;0] = []; - let result = fn_.call_within_context::<()>(&ctxt, args); - match result { - Ok(_) => panic!("Expected function to throw error, but it did not."), - Err(e) => { - let e = ScriptError::from_rhai_error(*e); - let err = e.display_with_world(world); - let regex = regex::Regex::new(®ex).unwrap(); - if regex.is_match(&err) { - Ok::<(), Box>(()) - } else { - panic!( - "Expected error message to match the regex: \n{}\n\nBut got:\n{}", - regex.as_str(), - err - ) - } - }, - } - }); - Ok(()) - }); - }, - self.path.as_os_str().to_str().unwrap(), - ) - .map_err(Failed::from) - } - - fn name(&self) -> String { - format!( - "script_test - lua - {}", - self.path - .to_string_lossy() - .split(&format!("tests{}data", std::path::MAIN_SEPARATOR)) - .last() - .unwrap() - ) - } -} - -fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { - if dir.is_dir() { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - visit_dirs(&path, cb)?; - } else { - cb(&entry); - } - } - } else { - panic!("Not a directory: {:?}", dir); - } - Ok(()) -} - -fn discover_all_tests() -> Vec { - let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let assets_root = workspace_root - .join("..") - .join("..") - .join("..") - .join("assets"); - let test_root = assets_root.join("tests"); - let mut test_files = Vec::new(); - visit_dirs(&test_root, &mut |entry| { - let path = entry.path(); - if path.extension().unwrap() == "rhai" { - let relative = path.strip_prefix(&assets_root).unwrap(); - test_files.push(Test { - path: relative.to_path_buf(), - }); - } - }) - .unwrap(); - - test_files -} - -// run this with `cargo test --features lua54 --package bevy_mod_scripting_lua --test lua_tests` -// or filter using the prefix "lua test -" -fn main() { - // Parse command line arguments - let args = Arguments::from_args(); - - // Create a list of tests and/or benchmarks (in this case: two dummy tests). - let tests = discover_all_tests() - .into_iter() - .map(|t| Trial::test(t.name(), move || t.execute())); - - // Run all tests and exit the application appropriatly. - libtest_mimic::run(&args, tests.collect()).exit(); -} diff --git a/crates/testing_crates/script_integration_test_harness/Cargo.toml b/crates/testing_crates/script_integration_test_harness/Cargo.toml index 850c776797..3e7111d35b 100644 --- a/crates/testing_crates/script_integration_test_harness/Cargo.toml +++ b/crates/testing_crates/script_integration_test_harness/Cargo.toml @@ -4,6 +4,11 @@ version = "0.1.0" edition = "2021" publish = false +[features] +default = ["lua", "rhai"] +lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"] +rhai = ["bevy_mod_scripting_rhai", "bevy_mod_scripting_functions/rhai_bindings"] + [dependencies] bevy = { workspace = true } test_utils = { workspace = true } @@ -11,8 +16,9 @@ bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_functions = { workspace = true, features = [ "bevy_bindings", "core_functions", - "rhai_bindings", - "lua_bindings", ] } regex = { version = "1.11" } pretty_assertions = "1.*" +bevy_mod_scripting_lua = { path = "../../languages/bevy_mod_scripting_lua", optional = true } +bevy_mod_scripting_rhai = { path = "../../languages/bevy_mod_scripting_rhai", optional = true } +criterion = "0.5" diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index d17836bbc4..2078cf2ef4 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -7,13 +7,13 @@ use std::{ }; use bevy::{ - app::{App, Last, PostUpdate, Startup, Update}, + app::{Last, Plugin, PostUpdate, Startup, Update}, asset::{AssetServer, Handle}, ecs::{ event::{Event, Events}, schedule::{IntoSystemConfigs, SystemConfigs}, system::{IntoSystem, Local, Res, SystemState}, - world::Mut, + world::{FromWorld, Mut}, }, prelude::{Entity, World}, reflect::TypeRegistry, @@ -27,7 +27,7 @@ use bevy_mod_scripting_core::{ extractors::{HandlerContext, WithWorldGuard}, handler::handle_script_errors, script::ScriptId, - IntoScriptPluginParams, + IntoScriptPluginParams, ScriptingPlugin, }; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; use test_functions::register_test_functions; @@ -38,6 +38,33 @@ fn dummy_startup_system() {} fn dummy_before_post_update_system() {} fn dummy_post_update_system() {} +pub trait Benchmarker: 'static + Send + Sync { + fn bench( + &self, + label: &str, + f: &dyn Fn() -> Result, + ) -> Result; + + fn clone_box(&self) -> Box; +} + +#[derive(Clone)] +pub struct NoOpBenchmarker; + +impl Benchmarker for NoOpBenchmarker { + fn bench( + &self, + _label: &str, + f: &dyn Fn() -> Result, + ) -> Result { + f() + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + #[derive(Event)] struct TestEventFinished; @@ -62,13 +89,124 @@ impl TestCallbackBuilder } } +#[cfg(feature = "lua")] +pub fn make_test_lua_plugin() -> bevy_mod_scripting_lua::LuaScriptingPlugin { + use bevy_mod_scripting_core::{bindings::WorldContainer, ConfigureScriptPlugin}; + use bevy_mod_scripting_lua::{mlua, LuaScriptingPlugin}; + + LuaScriptingPlugin::default().add_context_initializer( + |_, ctxt: &mut bevy_mod_scripting_lua::mlua::Lua| { + let globals = ctxt.globals(); + globals.set( + "assert_throws", + ctxt.create_function(|_lua, (f, reg): (mlua::Function, String)| { + let world = + bevy_mod_scripting_core::bindings::ThreadWorldContainer.try_get_world()?; + let result = f.call::<()>(mlua::MultiValue::new()); + let err = match result { + Ok(_) => { + return Err(mlua::Error::external( + "Expected function to throw error, but it did not.", + )) + } + Err(e) => ScriptError::from_mlua_error(e).display_with_world(world), + }; + + let regex = regex::Regex::new(®).unwrap(); + if regex.is_match(&err) { + Ok(()) + } else { + Err(mlua::Error::external(format!( + "Expected error message to match the regex: \n{}\n\nBut got:\n{}", + regex.as_str(), + err + ))) + } + })?, + )?; + Ok(()) + }, + ) +} + +#[cfg(feature = "rhai")] +pub fn make_test_rhai_plugin() -> bevy_mod_scripting_rhai::RhaiScriptingPlugin { + use bevy_mod_scripting_core::{ + bindings::{ThreadWorldContainer, WorldContainer}, + ConfigureScriptPlugin, + }; + use bevy_mod_scripting_rhai::{ + rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}, + RhaiScriptingPlugin, + }; + + RhaiScriptingPlugin::default().add_runtime_initializer(|runtime| { + let mut runtime = runtime.write(); + + runtime.register_fn("assert", |a: Dynamic, b: &str| { + if !a.is::() { + panic!("Expected a boolean value, but got {:?}", a); + } + if !a.as_bool().unwrap() { + panic!("Assertion failed. {}", b); + } + }); + + runtime.register_fn("assert", |a: Dynamic| { + if !a.is::() { + panic!("Expected a boolean value, but got {:?}", a); + } + if !a.as_bool().unwrap() { + panic!("Assertion failed"); + } + }); + runtime.register_fn( + "assert_throws", + |ctxt: NativeCallContext, fn_: FnPtr, regex: String| { + let world = ThreadWorldContainer.try_get_world()?; + let args: [Dynamic; 0] = []; + let result = fn_.call_within_context::<()>(&ctxt, args); + match result { + Ok(_) => panic!("Expected function to throw error, but it did not."), + Err(e) => { + let e = ScriptError::from_rhai_error(*e); + let err = e.display_with_world(world); + let regex = regex::Regex::new(®ex).unwrap(); + if regex.is_match(&err) { + Ok::<(), Box>(()) + } else { + panic!( + "Expected error message to match the regex: \n{}\n\nBut got:\n{}", + regex.as_str(), + err + ) + } + } + } + }, + ); + Ok(()) + }) +} + +#[cfg(feature = "lua")] +pub fn execute_lua_integration_test(script_id: &str) -> Result<(), String> { + let plugin = make_test_lua_plugin(); + execute_integration_test(plugin, |_, _| {}, script_id) +} + +#[cfg(feature = "rhai")] +pub fn execute_rhai_integration_test(script_id: &str) -> Result<(), String> { + let plugin = make_test_rhai_plugin(); + execute_integration_test(plugin, |_, _| {}, script_id) +} + pub fn execute_integration_test< - P: IntoScriptPluginParams, + P: IntoScriptPluginParams + Plugin + AsMut>, F: FnOnce(&mut World, &mut TypeRegistry), - G: FnOnce(&mut App), >( + plugin: P, init: F, - init_app: G, script_id: &str, ) -> Result<(), String> { // set "BEVY_ASSET_ROOT" to the global assets folder, i.e. CARGO_MANIFEST_DIR/../../../assets @@ -86,12 +224,10 @@ pub fn execute_integration_test< let mut app = setup_integration_test(init); - app.add_plugins(ScriptFunctionsPlugin); + app.add_plugins((ScriptFunctionsPlugin, plugin)); register_test_functions(&mut app); - init_app(&mut app); - app.add_event::(); callback_labels!( @@ -180,6 +316,7 @@ fn run_test_callback( vec![], guard.clone(), ); + let e = match res { Ok(ScriptValue::Error(e)) => e.into(), Err(e) => e, @@ -196,7 +333,132 @@ fn run_test_callback( } } }; + handle_script_errors(guard, vec![e.clone()].into_iter()); Err(e) } + +#[cfg(feature = "lua")] +pub fn run_lua_benchmark( + script_id: &str, + label: &str, + criterion: &mut criterion::BenchmarkGroup, +) -> Result<(), String> { + use bevy_mod_scripting_lua::mlua::Function; + + let plugin = make_test_lua_plugin(); + run_plugin_benchmark( + plugin, + script_id, + label, + criterion, + |ctxt, _runtime, label, criterion| { + let bencher: Function = ctxt.globals().get("bench").map_err(|e| e.to_string())?; + criterion.bench_function(label, |c| { + c.iter(|| { + bencher.call::<()>(()).unwrap(); + }) + }); + Ok(()) + }, + ) +} + +#[cfg(feature = "rhai")] +pub fn run_rhai_benchmark( + script_id: &str, + label: &str, + criterion: &mut criterion::BenchmarkGroup, +) -> Result<(), String> { + use bevy_mod_scripting_rhai::rhai::Dynamic; + + let plugin = make_test_rhai_plugin(); + run_plugin_benchmark( + plugin, + script_id, + label, + criterion, + |ctxt, runtime, label, criterion| { + let runtime = runtime.read(); + const ARGS: [usize; 0] = []; + criterion.bench_function(label, |c| { + c.iter(|| { + let _ = runtime + .call_fn::(&mut ctxt.scope, &ctxt.ast, "bench", ARGS) + .unwrap(); + }) + }); + Ok(()) + }, + ) +} + +pub fn run_plugin_benchmark( + plugin: P, + script_id: &str, + label: &str, + criterion: &mut criterion::BenchmarkGroup, + bench_fn: F, +) -> Result<(), String> +where + P: IntoScriptPluginParams + Plugin, + F: Fn(&mut P::C, &P::R, &str, &mut criterion::BenchmarkGroup) -> Result<(), String>, +{ + use bevy_mod_scripting_core::bindings::{ + ThreadWorldContainer, WorldAccessGuard, WorldContainer, + }; + + let mut app = setup_integration_test(|_, _| {}); + + app.add_plugins((ScriptFunctionsPlugin, plugin)); + register_test_functions(&mut app); + + let script_id = script_id.to_owned(); + let script_id_clone = script_id.clone(); + app.add_systems( + Startup, + move |server: Res, mut handle: Local>| { + *handle = server.load(script_id_clone.to_owned()); + }, + ); + + // finalize + app.cleanup(); + app.finish(); + + let timer = Instant::now(); + + let mut state = SystemState::>>::from_world(app.world_mut()); + + loop { + app.update(); + + let mut handler_ctxt = state.get_mut(app.world_mut()); + let (guard, context) = handler_ctxt.get_mut(); + + if context.is_script_fully_loaded(script_id.clone().into()) { + let script = context + .scripts() + .get_mut(script_id.to_owned()) + .ok_or_else(|| String::from("Could not find scripts resource"))?; + let ctxt_arc = script.context.clone(); + let mut ctxt_locked = ctxt_arc.lock(); + + let runtime = &context.runtime_container().runtime; + + return WorldAccessGuard::with_existing_static_guard(guard, |guard| { + // Ensure the world is available via ThreadWorldContainer + ThreadWorldContainer + .set_world(guard.clone()) + .map_err(|e| e.display_with_world(guard))?; + // Pass the locked context to the closure for benchmarking its Lua (or generic) part + bench_fn(&mut ctxt_locked, runtime, label, criterion) + }); + } + state.apply(app.world_mut()); + if timer.elapsed() > Duration::from_secs(30) { + return Err("Timeout after 30 seconds".into()); + } + } +} diff --git a/crates/testing_crates/test_utils/src/lib.rs b/crates/testing_crates/test_utils/src/lib.rs index 1b883431b8..ed79df1316 100644 --- a/crates/testing_crates/test_utils/src/lib.rs +++ b/crates/testing_crates/test_utils/src/lib.rs @@ -1,2 +1,75 @@ +use std::{ + fs::{self, DirEntry}, + io, + path::{Path, PathBuf}, +}; + pub mod test_data; pub mod test_plugin; + +#[derive(Debug, Clone, Copy)] +pub enum TestKind { + Lua, + Rhai, +} + +impl std::fmt::Display for TestKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestKind::Lua => write!(f, "Lua"), + TestKind::Rhai => write!(f, "Rhai"), + } + } +} + +#[derive(Debug, Clone)] +pub struct Test { + pub path: PathBuf, + pub kind: TestKind, +} + +fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + visit_dirs(&path, cb)?; + } else { + cb(&entry); + } + } + } else { + panic!("Not a directory: {:?}", dir); + } + Ok(()) +} + +pub fn discover_all_tests(manifest_dir: PathBuf, filter: impl Fn(&Path) -> bool) -> Vec { + let assets_root = manifest_dir.join("assets"); + let mut test_files = Vec::new(); + visit_dirs(&assets_root, &mut |entry| { + let path = entry.path(); + if let Some(kind) = path + .extension() + .and_then(|e| match e.to_string_lossy().as_ref() { + "lua" => Some(TestKind::Lua), + "rhai" => Some(TestKind::Rhai), + _ => None, + }) + { + // only take the path from the assets bit + let relative = path.strip_prefix(&assets_root).unwrap(); + if !filter(relative) { + return; + } + test_files.push(Test { + path: relative.to_path_buf(), + kind, + }); + } + }) + .unwrap(); + + test_files +} diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 35a5fb72e9..77713de1c9 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -812,29 +812,29 @@ impl Xtasks { args.push(command.to_owned()); - if command != "fmt" && command != "bevy-api-gen" && command != "run" && command != "install" - { + if command != "fmt" && command != "bevy-api-gen" && command != "install" { // fmt doesn't care about features, workspaces or profiles + if command != "run" { + args.push("--workspace".to_owned()); + + if let Some(profile) = app_settings.profile.as_ref() { + let use_profile = if profile == "ephemeral-build" && app_settings.coverage { + // use special profile for coverage as it needs debug information + // but also don't want it too slow + "ephemeral-coverage" + } else { + profile + }; + + if !app_settings.coverage { + args.push("--profile".to_owned()); + args.push(use_profile.to_owned()); + } - args.push("--workspace".to_owned()); - - if let Some(profile) = app_settings.profile.as_ref() { - let use_profile = if profile == "ephemeral-build" && app_settings.coverage { - // use special profile for coverage as it needs debug information - // but also don't want it too slow - "ephemeral-coverage" - } else { - profile - }; - - if !app_settings.coverage { - args.push("--profile".to_owned()); - args.push(use_profile.to_owned()); - } - - if let Some(jobs) = app_settings.jobs { - args.push("--jobs".to_owned()); - args.push(jobs.to_string()); + if let Some(jobs) = app_settings.jobs { + args.push("--jobs".to_owned()); + args.push(jobs.to_string()); + } } } diff --git a/tests/script_tests.rs b/tests/script_tests.rs new file mode 100644 index 0000000000..73c7c3995c --- /dev/null +++ b/tests/script_tests.rs @@ -0,0 +1,55 @@ +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, missing_docs)] + +use std::path::PathBuf; + +use libtest_mimic::{Arguments, Failed, Trial}; +use script_integration_test_harness::{ + execute_lua_integration_test, execute_rhai_integration_test, +}; + +use test_utils::{discover_all_tests, Test, TestKind}; + +trait TestExecutor { + fn execute(self) -> Result<(), Failed>; + fn name(&self) -> String; +} + +impl TestExecutor for Test { + fn execute(self) -> Result<(), Failed> { + println!("Running test: {:?}", self.path); + + match self.kind { + TestKind::Lua => execute_lua_integration_test(&self.path.to_string_lossy())?, + TestKind::Rhai => execute_rhai_integration_test(&self.path.to_string_lossy())?, + } + + Ok(()) + } + + fn name(&self) -> String { + format!( + "script_test - {} - {}", + self.kind, + self.path + .to_string_lossy() + .split(&format!("tests{}data", std::path::MAIN_SEPARATOR)) + .last() + .unwrap() + ) + } +} + +// run this with `cargo test --features lua54 --package bevy_mod_scripting_lua --test lua_tests` +// or filter using the prefix "lua test -" +fn main() { + // Parse command line arguments + let args = Arguments::from_args(); + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let tests = discover_all_tests(manifest_dir, |p| p.starts_with("tests")) + .into_iter() + .map(|t| Trial::test(t.name(), move || t.execute())) + .collect::>(); + + libtest_mimic::run(&args, tests).exit(); +} From 789fe2eff0ac7cae1e2962977da8d1e45ffb242c Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 17:42:55 +0000 Subject: [PATCH 09/51] chore: fix bencher (#382) # Summary - Fix issues in bencher CI now that I can do that off of a PR --- .github/workflows/bevy_mod_scripting.yml | 3 + .github/workflows/snapshot_benchmark_main.yml | 32 ------- crates/xtask/src/main.rs | 84 +++++++++++++++++++ 3 files changed, 87 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/snapshot_benchmark_main.yml diff --git a/.github/workflows/bevy_mod_scripting.yml b/.github/workflows/bevy_mod_scripting.yml index 4b4e3490c0..e5afebca0c 100644 --- a/.github/workflows/bevy_mod_scripting.yml +++ b/.github/workflows/bevy_mod_scripting.yml @@ -72,6 +72,9 @@ jobs: echo "Convert to single line JSON" jq -c . matrix.json > matrix-one-line.json echo "matrix=$(cat matrix-one-line.json)" >> $GITHUB_OUTPUT +env: + BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} check: needs: [check-needs-run, generate-job-matrix] diff --git a/.github/workflows/snapshot_benchmark_main.yml b/.github/workflows/snapshot_benchmark_main.yml deleted file mode 100644 index 283ffbdb2c..0000000000 --- a/.github/workflows/snapshot_benchmark_main.yml +++ /dev/null @@ -1,32 +0,0 @@ -on: - push: - branches: main - -jobs: - benchmark_base_branch: - name: Continuous Benchmarking with Bencher - permissions: - checks: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Run Xtask initializer - run: | - cargo xtask init - - uses: bencherdev/bencher@main - - name: Track base branch benchmarks with Bencher - run: | - bencher run \ - --project bms \ - --token '${{ secrets.BENCHER_API_TOKEN }}' \ - --branch main \ - --testbed ubuntu-latest \ - --threshold-measure latency \ - --threshold-test t_test \ - --threshold-max-sample-size 64 \ - --threshold-upper-boundary 0.99 \ - --thresholds-reset \ - --err \ - --adapter json \ - --github-actions '${{ secrets.GITHUB_TOKEN }}' \ - bencher run --adapter rust_criterion "cargo bench --features lua54" \ No newline at end of file diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 77713de1c9..72bfcba346 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -349,6 +349,9 @@ impl App { Xtasks::Install { binary } => { cmd.arg("install").arg(binary.as_ref()); } + Xtasks::Bench {} => { + cmd.arg("bench"); + } } cmd @@ -634,6 +637,8 @@ enum Xtasks { /// ] /// CiMatrix, + /// Runs bencher in dry mode by default if not on the main branch + Bench {}, } #[derive(Serialize, Clone)] @@ -709,6 +714,7 @@ impl Xtasks { bevy_features, } => Self::codegen(app_settings, output_dir, bevy_features), Xtasks::Install { binary } => Self::install(app_settings, binary), + Xtasks::Bench {} => Self::bench(app_settings), }?; Ok("".into()) @@ -1208,6 +1214,77 @@ impl Xtasks { Ok(()) } + fn bench(app_settings: GlobalArgs) -> Result<()> { + // first of all figure out which branch we're on + // run // git rev-parse --abbrev-ref HEAD + + let command = Command::new("git") + .args(["rev-parse", "--abbrev-ref", "HEAD"]) + .current_dir(Self::workspace_dir(&app_settings).unwrap()) + .output() + .with_context(|| "Trying to figure out which branch we're on in benchmarking")?; + let branch = String::from_utf8(command.stdout)?; + + let is_main = branch.trim() == "main"; + + // figure out if we're running in github actions + let github_token = std::env::var("GITHUB_TOKEN").ok(); + + // get testbed + // we want this to be a combination of + // is_github_ci? + // OS + // machine id + + let os = std::env::consts::OS; + + let testbed = format!( + "{os}{}", + github_token.is_some().then_some("-gha").unwrap_or_default() + ); + + // also figure out if we're on a fork + + let token = std::env::var("BENCHER_API_TOKEN").ok(); + + let mut bencher_cmd = Command::new("bencher"); + bencher_cmd + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .current_dir(Self::workspace_dir(&app_settings).unwrap()) + .arg("run") + .args(["--project", "bms"]) + .args(["--branch", &format!("\"{branch}\"")]) + .args(["--token", &token.unwrap_or_default()]) + .args(["--testbed", &testbed]) + .args(["--build-time"]) + .args(["--threshold-measure", "latency"]) + .args(["--threshold-test", "t_test"]) + .args(["--threshold-max-sample-size", "64"]) + .args(["--threshold-upper-boundary", "0.99"]) + .args(["--thresholds-reset"]) + .args(["--err"]); + + if let Some(token) = github_token { + bencher_cmd.args(["--github-actions", &token]); + } + + if !is_main { + bencher_cmd.args(["--dry-run"]); + } + + bencher_cmd + .args(["--adapter", "rust_criterion"]) + .arg("cargo bench --features=lua54"); + + let out = bencher_cmd.output()?; + if !out.status.success() { + bail!("Failed to run bencher: {:?}", out); + } + + Ok(()) + } + fn set_cargo_coverage_settings() { // This makes local dev hell // std::env::set_var("CARGO_INCREMENTAL", "0"); @@ -1369,6 +1446,13 @@ impl Xtasks { }, }); + // also run a benchmark + // on non-main branches this will just dry run + output.push(App { + global_args: default_args.clone(), + subcmd: Xtasks::Bench {}, + }); + // and finally run tests with coverage output.push(App { global_args: default_args From 28bfc4e6b211d210f6d1db80598da62e36b6ac1d Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 17:44:20 +0000 Subject: [PATCH 10/51] chore: Update bevy_mod_scripting.yml --- .github/workflows/bevy_mod_scripting.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bevy_mod_scripting.yml b/.github/workflows/bevy_mod_scripting.yml index e5afebca0c..a7e32bd452 100644 --- a/.github/workflows/bevy_mod_scripting.yml +++ b/.github/workflows/bevy_mod_scripting.yml @@ -16,7 +16,9 @@ env: IMAGE_NAME: bevy-mod-scripting CODEGEN_BRANCH_NAME: __update-bevy-bindings-${{ github.head_ref || github.ref_name }} GH_TOKEN: ${{ github.token }} - + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }} + concurrency: # Use github.run_id on main branch # Use github.event.pull_request.number on pull requests, so it's unique per pull request @@ -72,9 +74,6 @@ jobs: echo "Convert to single line JSON" jq -c . matrix.json > matrix-one-line.json echo "matrix=$(cat matrix-one-line.json)" >> $GITHUB_OUTPUT -env: - BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} check: needs: [check-needs-run, generate-job-matrix] From 2e1f2a8f43dfaff449ea90d306e8cfedf86c8fa7 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 18:48:54 +0000 Subject: [PATCH 11/51] chore: add more error messaging for bencher (#383) --- crates/xtask/src/main.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 72bfcba346..efba243e01 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1217,10 +1217,10 @@ impl Xtasks { fn bench(app_settings: GlobalArgs) -> Result<()> { // first of all figure out which branch we're on // run // git rev-parse --abbrev-ref HEAD - + let workspace_dir = Self::workspace_dir(&app_settings).unwrap(); let command = Command::new("git") .args(["rev-parse", "--abbrev-ref", "HEAD"]) - .current_dir(Self::workspace_dir(&app_settings).unwrap()) + .current_dir(workspace_dir.clone()) .output() .with_context(|| "Trying to figure out which branch we're on in benchmarking")?; let branch = String::from_utf8(command.stdout)?; @@ -1251,7 +1251,6 @@ impl Xtasks { bencher_cmd .stdout(std::process::Stdio::inherit()) .stderr(std::process::Stdio::inherit()) - .current_dir(Self::workspace_dir(&app_settings).unwrap()) .arg("run") .args(["--project", "bms"]) .args(["--branch", &format!("\"{branch}\"")]) @@ -1277,7 +1276,11 @@ impl Xtasks { .args(["--adapter", "rust_criterion"]) .arg("cargo bench --features=lua54"); - let out = bencher_cmd.output()?; + log::info!("Running bencher command: {:?}", bencher_cmd); + + let out = bencher_cmd + .output() + .with_context(|| "Could not trigger bencher command")?; if !out.status.success() { bail!("Failed to run bencher: {:?}", out); } @@ -1495,6 +1498,25 @@ impl Xtasks { )?; } + // install bencher + // linux curl --proto '=https' --tlsv1.2 -sSfL https://bencher.dev/download/install-cli.sh | sh + // windows irm https://bencher.dev/download/install-cli.ps1 | iex + Self::run_system_command( + &app_settings, + "cargo", + "Failed to install bencher", + vec![ + "install", + "--git", + "https://github.com/bencherdev/bencher", + "--branch", + "main", + "--locked", + "--force", + "bencher_cli", + ], + None, + )?; // install cargo mdbook Self::run_system_command( &app_settings, From 1f87c660c96d997d5868c0159de3fc24f5b3fe2c Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 22 Mar 2025 19:24:01 +0000 Subject: [PATCH 12/51] chore: fix branch name in bench --- crates/xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index efba243e01..6ef69e3507 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1223,7 +1223,7 @@ impl Xtasks { .current_dir(workspace_dir.clone()) .output() .with_context(|| "Trying to figure out which branch we're on in benchmarking")?; - let branch = String::from_utf8(command.stdout)?; + let branch = String::from_utf8(command.stdout)?.trim().replace("\n", ""); let is_main = branch.trim() == "main"; From d6eafdd710ef7fdfec5e5e0a1e6b9e826525a35e Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 22 Mar 2025 19:48:38 +0000 Subject: [PATCH 13/51] chore: dont enable --build time in bencher --- crates/xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 6ef69e3507..5048d12371 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1256,7 +1256,7 @@ impl Xtasks { .args(["--branch", &format!("\"{branch}\"")]) .args(["--token", &token.unwrap_or_default()]) .args(["--testbed", &testbed]) - .args(["--build-time"]) + // .args(["--build-time"]) .args(["--threshold-measure", "latency"]) .args(["--threshold-test", "t_test"]) .args(["--threshold-max-sample-size", "64"]) From 220e618a0d4a65719d44ab62e2084b27de72f1b2 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 19:51:39 +0000 Subject: [PATCH 14/51] chore: Update readme.md --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index abf303eb3a..797f4a2b94 100644 --- a/readme.md +++ b/readme.md @@ -28,6 +28,11 @@ Although Bevy doesn't directly support scripting, efforts are underway to incorp - Customizable event driven communication between bevy and scripts (`on_update`, `on_init` etc..) - Documentation generation +## Benchmarks +BMS applies continuous benchmarking and the latest benchmark results can be found over at [bencher.dev](https://bencher.dev/perf/bms). + +The tested scripts themselves are placed in the `assets/benchmarks` directory. + ## Support The languages currently supported are as follows: From 5914b490104a8eee3c960b7622d4b373f3a00faa Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 22 Mar 2025 19:55:09 +0000 Subject: [PATCH 15/51] chore: fix branch format --- crates/xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 5048d12371..e24e372ffb 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1253,7 +1253,7 @@ impl Xtasks { .stderr(std::process::Stdio::inherit()) .arg("run") .args(["--project", "bms"]) - .args(["--branch", &format!("\"{branch}\"")]) + .args(["--branch", &branch]) .args(["--token", &token.unwrap_or_default()]) .args(["--testbed", &testbed]) // .args(["--build-time"]) From d0d277fba28c7c61be49ee6a6a6a8b0a0b065293 Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 22 Mar 2025 20:25:03 +0000 Subject: [PATCH 16/51] allow check creation for bencher --- .github/workflows/bevy_mod_scripting.yml | 1 + crates/bevy_mod_scripting_core/src/asset.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bevy_mod_scripting.yml b/.github/workflows/bevy_mod_scripting.yml index a7e32bd452..b8d0fcc06c 100644 --- a/.github/workflows/bevy_mod_scripting.yml +++ b/.github/workflows/bevy_mod_scripting.yml @@ -81,6 +81,7 @@ jobs: pull-requests: write contents: write issues: write + checks: write name: Check - ${{ matrix.run_args.name }} runs-on: ${{ matrix.run_args.os }} strategy: diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index a91d395623..8891a9264b 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -255,7 +255,7 @@ pub(crate) fn dispatch_script_asset_events( } } -/// Listens to [`ScriptAssetEvent::Removed`] events and removes the corresponding script metadata +/// Listens to [`ScriptAssetEvent::Removed`] events and removes the corresponding script metadata. pub(crate) fn remove_script_metadata( mut events: EventReader, mut asset_path_map: ResMut, From 570df08c5fdc2eb752d105e8929a48f1e9b28e4c Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 22 Mar 2025 22:31:49 +0000 Subject: [PATCH 17/51] chore: add resource benchmark (#385) # Summary - adds a resource benchmark --- assets/benchmarks/resource/access.lua | 3 +++ assets/benchmarks/resource/access.rhai | 3 +++ assets/benchmarks/resource/get.lua | 3 +++ assets/benchmarks/resource/get.rhai | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 assets/benchmarks/resource/access.lua create mode 100644 assets/benchmarks/resource/access.rhai create mode 100644 assets/benchmarks/resource/get.lua create mode 100644 assets/benchmarks/resource/get.rhai diff --git a/assets/benchmarks/resource/access.lua b/assets/benchmarks/resource/access.lua new file mode 100644 index 0000000000..6105935357 --- /dev/null +++ b/assets/benchmarks/resource/access.lua @@ -0,0 +1,3 @@ +function bench() + local bytes = world.get_resource(types.TestResource).bytes; +end \ No newline at end of file diff --git a/assets/benchmarks/resource/access.rhai b/assets/benchmarks/resource/access.rhai new file mode 100644 index 0000000000..b655f4137a --- /dev/null +++ b/assets/benchmarks/resource/access.rhai @@ -0,0 +1,3 @@ +fn bench(){ + let bytes = world.get_resource.call(types.TestResource).bytes; +} \ No newline at end of file diff --git a/assets/benchmarks/resource/get.lua b/assets/benchmarks/resource/get.lua new file mode 100644 index 0000000000..adad569b18 --- /dev/null +++ b/assets/benchmarks/resource/get.lua @@ -0,0 +1,3 @@ +function bench() + world.get_resource(types.TestResource); +end \ No newline at end of file diff --git a/assets/benchmarks/resource/get.rhai b/assets/benchmarks/resource/get.rhai new file mode 100644 index 0000000000..989b3e382d --- /dev/null +++ b/assets/benchmarks/resource/get.rhai @@ -0,0 +1,3 @@ +fn bench(){ + world.get_resource.call(types.TestResource); +} \ No newline at end of file From bac836fb169e30be43dbf47ef1ee79046f55ec3c Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 23 Mar 2025 18:13:54 +0000 Subject: [PATCH 18/51] chore: add query and reflection benchmarks (#386) # Summary - Adds more interesting benchmarks around querying and reflection, revealing slower parts of BMS and/or Bevy - Re-create all plots based on benchmarks present every push to main --- assets/benchmarks/function/call.lua | 3 + assets/benchmarks/function/call.rhai | 3 + assets/benchmarks/function/call_4_args.lua | 3 + assets/benchmarks/function/call_4_args.rhai | 3 + assets/benchmarks/math/vec_mat_ops.lua | 20 +++ assets/benchmarks/math/vec_mat_ops.rhai | 19 +++ assets/benchmarks/query/1000_entities.lua | 35 ++++ assets/benchmarks/query/1000_entities.rhai | 34 ++++ assets/benchmarks/query/100_entities.lua | 34 ++++ assets/benchmarks/query/100_entities.rhai | 33 ++++ assets/benchmarks/query/10_entities.lua | 34 ++++ assets/benchmarks/query/10_entities.rhai | 33 ++++ assets/benchmarks/reflection/10.lua | 52 ++++++ assets/benchmarks/reflection/10.rhai | 52 ++++++ assets/benchmarks/reflection/100.lua | 53 ++++++ assets/benchmarks/reflection/100.rhai | 52 ++++++ .../Cargo.toml | 2 + .../src/lib.rs | 13 +- .../src/test_functions.rs | 37 +++- crates/xtask/src/main.rs | 158 +++++++++++++++++- 20 files changed, 664 insertions(+), 9 deletions(-) create mode 100644 assets/benchmarks/function/call.lua create mode 100644 assets/benchmarks/function/call.rhai create mode 100644 assets/benchmarks/function/call_4_args.lua create mode 100644 assets/benchmarks/function/call_4_args.rhai create mode 100644 assets/benchmarks/math/vec_mat_ops.lua create mode 100644 assets/benchmarks/math/vec_mat_ops.rhai create mode 100644 assets/benchmarks/query/1000_entities.lua create mode 100644 assets/benchmarks/query/1000_entities.rhai create mode 100644 assets/benchmarks/query/100_entities.lua create mode 100644 assets/benchmarks/query/100_entities.rhai create mode 100644 assets/benchmarks/query/10_entities.lua create mode 100644 assets/benchmarks/query/10_entities.rhai create mode 100644 assets/benchmarks/reflection/10.lua create mode 100644 assets/benchmarks/reflection/10.rhai create mode 100644 assets/benchmarks/reflection/100.lua create mode 100644 assets/benchmarks/reflection/100.rhai diff --git a/assets/benchmarks/function/call.lua b/assets/benchmarks/function/call.lua new file mode 100644 index 0000000000..21b351ac09 --- /dev/null +++ b/assets/benchmarks/function/call.lua @@ -0,0 +1,3 @@ +function bench() + noop() +end \ No newline at end of file diff --git a/assets/benchmarks/function/call.rhai b/assets/benchmarks/function/call.rhai new file mode 100644 index 0000000000..5adf764732 --- /dev/null +++ b/assets/benchmarks/function/call.rhai @@ -0,0 +1,3 @@ +fn bench(){ + noop.call(); +} \ No newline at end of file diff --git a/assets/benchmarks/function/call_4_args.lua b/assets/benchmarks/function/call_4_args.lua new file mode 100644 index 0000000000..e595a6d465 --- /dev/null +++ b/assets/benchmarks/function/call_4_args.lua @@ -0,0 +1,3 @@ +function bench() + noop_4_args(1,"asd",{1,2,3}, {asd = 1}) +end \ No newline at end of file diff --git a/assets/benchmarks/function/call_4_args.rhai b/assets/benchmarks/function/call_4_args.rhai new file mode 100644 index 0000000000..299b35871f --- /dev/null +++ b/assets/benchmarks/function/call_4_args.rhai @@ -0,0 +1,3 @@ +fn bench(){ + noop_4_args.call(1,"asd",[1,2,3],#{ asd: 1}); +} \ No newline at end of file diff --git a/assets/benchmarks/math/vec_mat_ops.lua b/assets/benchmarks/math/vec_mat_ops.lua new file mode 100644 index 0000000000..218a9ef060 --- /dev/null +++ b/assets/benchmarks/math/vec_mat_ops.lua @@ -0,0 +1,20 @@ + +reseed() + +local matrix = nil +local vector = nil +function pre_bench() + -- generate random 3x3 matrix and 3x1 vec + vector = Vec3.new(random(1,999), random(1,999), random(1,999)) + matrix = Mat3.from_cols( + Vec3.new(random(1,999), random(1,999), random(1,999)), + Vec3.new(random(1,999), random(1,999), random(1,999)), + Vec3.new(random(1,999), random(1,999), random(1,999)) + ) +end + +function bench() + local mul = matrix * vector + local add = matrix + vector + local div = vector / matrix +end \ No newline at end of file diff --git a/assets/benchmarks/math/vec_mat_ops.rhai b/assets/benchmarks/math/vec_mat_ops.rhai new file mode 100644 index 0000000000..b204d1c5f2 --- /dev/null +++ b/assets/benchmarks/math/vec_mat_ops.rhai @@ -0,0 +1,19 @@ + +reseed.call(); + +let matrix = (); +let vector = (); +fn pre_bench(){ + vector = Vec3.new_.call(random.call(1,999), random.call(1,999), random.call(1,999)); + matrix = Mat3.from_cols.call( + Vec3.new_.call(random.call(1,999), random.call(1,999), random.call(1,999)), + Vec3.new_.call(random.call(1,999), random.call(1,999), random.call(1,999)), + Vec3.new_.call(random.call(1,999), random.call(1,999), random.call(1,999)) + ); +} + +fn bench() { + let mul = matrix * vector; + let add = matrix + vector; + let div = vector / matrix; +} \ No newline at end of file diff --git a/assets/benchmarks/query/1000_entities.lua b/assets/benchmarks/query/1000_entities.lua new file mode 100644 index 0000000000..9eb806dfcf --- /dev/null +++ b/assets/benchmarks/query/1000_entities.lua @@ -0,0 +1,35 @@ +local entity_a = world.spawn() +local entity_b = world.spawn() +local entity_c = world.spawn() + +local components = { + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +} + +reseed() + +for i = 1, 1000 do + local entity = world.spawn() + -- spawn 1000 entities with random components + local left_to_pick = {1,2,3,4} + for j = 1, 3 do + local index = random_int(1, #left_to_pick) + local component = components[left_to_pick[index]] + table.remove(left_to_pick, index) + world.add_default_component(entity, component) + end +end + +function bench() + for i,result in pairs(world.query() + :component(types.CompWithFromWorldAndComponentData) + :component(types.SimpleTupleStruct) + :with(types.SimpleEnum) + :without(types.CompWithDefaultAndComponentData) + :build()) do + + end +end \ No newline at end of file diff --git a/assets/benchmarks/query/1000_entities.rhai b/assets/benchmarks/query/1000_entities.rhai new file mode 100644 index 0000000000..80d3a0f88c --- /dev/null +++ b/assets/benchmarks/query/1000_entities.rhai @@ -0,0 +1,34 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); + +let components = [ + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +]; + +reseed.call(); + +for i in 1..=1000 { + let entity = world.spawn_.call(); + // spawn 1000 entities with random components + let left_to_pick = [0, 1, 2, 3]; + for j in 1..=3 { + let index = random_int.call(1, left_to_pick.len()) - 1; + let component = components[left_to_pick[index]]; + left_to_pick.remove(index); + world.add_default_component.call(entity, component); + }; +}; + +fn bench() { + for (result, i) in world.query.call() + .component.call(types.CompWithFromWorldAndComponentData) + .component.call(types.SimpleTupleStruct) + .with_.call(types.SimpleEnum) + .without.call(types.CompWithDefaultAndComponentData) + .build.call() { + }; +} \ No newline at end of file diff --git a/assets/benchmarks/query/100_entities.lua b/assets/benchmarks/query/100_entities.lua new file mode 100644 index 0000000000..6cad058a8e --- /dev/null +++ b/assets/benchmarks/query/100_entities.lua @@ -0,0 +1,34 @@ +local entity_a = world.spawn() +local entity_b = world.spawn() +local entity_c = world.spawn() + +local components = { + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +} + +reseed() + +for i = 1, 100 do + local entity = world.spawn() + local left_to_pick = {1,2,3,4} + for j = 1, 3 do + local index = random_int(1, #left_to_pick) + local component = components[left_to_pick[index]] + table.remove(left_to_pick, index) + world.add_default_component(entity, component) + end +end + +function bench() + for i,result in pairs(world.query() + :component(types.CompWithFromWorldAndComponentData) + :component(types.SimpleTupleStruct) + :with(types.SimpleEnum) + :without(types.CompWithDefaultAndComponentData) + :build()) do + + end +end \ No newline at end of file diff --git a/assets/benchmarks/query/100_entities.rhai b/assets/benchmarks/query/100_entities.rhai new file mode 100644 index 0000000000..2b6b5021a2 --- /dev/null +++ b/assets/benchmarks/query/100_entities.rhai @@ -0,0 +1,33 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); + +let components = [ + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +]; + +reseed.call(); + +for i in 1..=100 { + let entity = world.spawn_.call(); + let left_to_pick = [0, 1, 2, 3]; + for j in 1..=3 { + let index = random_int.call(1, left_to_pick.len()) - 1; + let component = components[left_to_pick[index]]; + left_to_pick.remove(index); + world.add_default_component.call(entity, component); + }; +}; + +fn bench() { + for (result, i) in world.query.call() + .component.call(types.CompWithFromWorldAndComponentData) + .component.call(types.SimpleTupleStruct) + .with_.call(types.SimpleEnum) + .without.call(types.CompWithDefaultAndComponentData) + .build.call() { + }; +} \ No newline at end of file diff --git a/assets/benchmarks/query/10_entities.lua b/assets/benchmarks/query/10_entities.lua new file mode 100644 index 0000000000..77fc24b76c --- /dev/null +++ b/assets/benchmarks/query/10_entities.lua @@ -0,0 +1,34 @@ +local entity_a = world.spawn() +local entity_b = world.spawn() +local entity_c = world.spawn() + +local components = { + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +} + +reseed() + +for i = 1, 10 do + local entity = world.spawn() + local left_to_pick = {1,2,3,4} + for j = 1, 3 do + local index = random_int(1, #left_to_pick) + local component = components[left_to_pick[index]] + table.remove(left_to_pick, index) + world.add_default_component(entity, component) + end +end + +function bench() + for i,result in pairs(world.query() + :component(types.CompWithFromWorldAndComponentData) + :component(types.SimpleTupleStruct) + :with(types.SimpleEnum) + :without(types.CompWithDefaultAndComponentData) + :build()) do + + end +end \ No newline at end of file diff --git a/assets/benchmarks/query/10_entities.rhai b/assets/benchmarks/query/10_entities.rhai new file mode 100644 index 0000000000..14343b82f9 --- /dev/null +++ b/assets/benchmarks/query/10_entities.rhai @@ -0,0 +1,33 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); + +let components = [ + types.CompWithDefaultAndComponentData, + types.CompWithFromWorldAndComponentData, + types.SimpleTupleStruct, + types.SimpleEnum, +]; + +reseed.call(); + +for i in 1..=10 { + let entity = world.spawn_.call(); + let left_to_pick = [0, 1, 2, 3]; + for j in 1..=3 { + let index = random_int.call(1, left_to_pick.len()) - 1; + let component = components[left_to_pick[index]]; + left_to_pick.remove(index); + world.add_default_component.call(entity, component); + }; +}; + +fn bench() { + for (result, i) in world.query.call() + .component.call(types.CompWithFromWorldAndComponentData) + .component.call(types.SimpleTupleStruct) + .with_.call(types.SimpleEnum) + .without.call(types.CompWithDefaultAndComponentData) + .build.call() { + }; +} \ No newline at end of file diff --git a/assets/benchmarks/reflection/10.lua b/assets/benchmarks/reflection/10.lua new file mode 100644 index 0000000000..45b57f265c --- /dev/null +++ b/assets/benchmarks/reflection/10.lua @@ -0,0 +1,52 @@ +-- create a dynamic component with n depth of fields +local Component = world.register_new_component("DeepComponent"); +local new_entity = world.spawn(); + +reseed() + +local keys = { + "_0", + "my_key", + "longish_key" +} + +-- Recursively build a single nested table using the pre-selected step_keys. +local function make_path(step_keys, index) + if index > #step_keys then + return {} + end + local key = step_keys[index] + return { [key] = make_path(step_keys, index + 1) } +end + + +local current_reference = nil +local steps = 10 +local step_keys = {} +function pre_bench() + step_keys = {} + current_reference = nil + -- Choose keys for every step. + for i = 1, steps do + local key = keys[random_int(1, #keys)] + step_keys[i] = key + end + + -- Build the nested table. + local instance = make_path(step_keys, 1) + world.remove_component(new_entity, Component) + world.insert_component(new_entity, Component, construct(types.DynamicComponent, { + data = instance + })) + current_reference = world.get_component(new_entity, Component) +end + +function bench() + -- reference into random key into current_reference steps times + local reference = current_reference.data + local current_step = 1 + while current_step <= steps do + reference = reference[step_keys[current_step]] + current_step = current_step + 1 + end +end \ No newline at end of file diff --git a/assets/benchmarks/reflection/10.rhai b/assets/benchmarks/reflection/10.rhai new file mode 100644 index 0000000000..0f483a096d --- /dev/null +++ b/assets/benchmarks/reflection/10.rhai @@ -0,0 +1,52 @@ +let Component = world.register_new_component.call("DeepComponent"); +let new_entity = world.spawn_.call(); + +reseed.call(); + +let keys = [ + "_0", + "my_key", + "longish_key" +]; + +fn make_path(step_keys, index) { + if index >= step_keys.len() { + return #{}; + }; + let key = step_keys[index]; + let nested = make_path(step_keys, index + 1); + let obj = #{}; + obj.set(key,nested); + return obj; +}; + +let current_reference = (); +let steps = 10; +let step_keys = []; + +fn pre_bench() { + step_keys = []; + current_reference = (); + + // Choose keys for every step. + for i in 0 .. steps { + let key = keys[random_int.call(0, keys.len() - 1)]; + step_keys.push(key); + }; + + // Build the nested table. + let instance = make_path(step_keys, 0); + world.remove_component.call(new_entity, Component); + world.insert_component.call(new_entity, Component, construct.call(types.DynamicComponent, #{ "data": instance })); + current_reference = world.get_component.call(new_entity, Component); +}; + +fn bench() { + // reference into random key into current_reference steps times + let reference = current_reference.data; + let current_step = 0; + while current_step < steps { + reference = reference[step_keys[current_step]]; + current_step += 1; + }; +}; \ No newline at end of file diff --git a/assets/benchmarks/reflection/100.lua b/assets/benchmarks/reflection/100.lua new file mode 100644 index 0000000000..a61578ed0d --- /dev/null +++ b/assets/benchmarks/reflection/100.lua @@ -0,0 +1,53 @@ +-- create a dynamic component with n depth of fields +local Component = world.register_new_component("DeepComponent"); +local new_entity = world.spawn(); + +reseed() + +local keys = { + "_0", + "my_key", + "longish_key" +} + +-- Recursively build a single nested table using the pre-selected step_keys. +local function make_path(step_keys, index) + if index > #step_keys then + return {} + end + local key = step_keys[index] + return { [key] = make_path(step_keys, index + 1) } +end + + +local current_reference = nil +local steps = 100 +local step_keys = {} +function pre_bench() + step_keys = {} + current_reference = nil + + -- Choose keys for every step. + for i = 1, steps do + local key = keys[random_int(1, #keys)] + step_keys[i] = key + end + + -- Build the nested table. + local instance = make_path(step_keys, 1) + world.remove_component(new_entity, Component) + world.insert_component(new_entity, Component, construct(types.DynamicComponent, { + data = instance + })) + current_reference = world.get_component(new_entity, Component) +end + +function bench() + -- reference into random key into current_reference steps times + local reference = current_reference.data + local current_step = 1 + while current_step <= steps do + reference = reference[step_keys[current_step]] + current_step = current_step + 1 + end +end \ No newline at end of file diff --git a/assets/benchmarks/reflection/100.rhai b/assets/benchmarks/reflection/100.rhai new file mode 100644 index 0000000000..32bef1925e --- /dev/null +++ b/assets/benchmarks/reflection/100.rhai @@ -0,0 +1,52 @@ +let Component = world.register_new_component.call("DeepComponent"); +let new_entity = world.spawn_.call(); + +reseed.call(); + +let keys = [ + "_0", + "my_key", + "longish_key" +]; + +fn make_path(step_keys, index) { + if index >= step_keys.len() { + return #{}; + }; + let key = step_keys[index]; + let nested = make_path(step_keys, index + 1); + let obj = #{}; + obj.set(key,nested); + return obj; +}; + +let current_reference = (); +let steps = 100; +let step_keys = []; + +fn pre_bench() { + step_keys = []; + current_reference = (); + + // Choose keys for every step. + for i in 0 .. steps { + let key = keys[random_int.call(0, keys.len() - 1)]; + step_keys.push(key); + }; + + // Build the nested table. + let instance = make_path(step_keys, 0); + world.remove_component.call(new_entity, Component); + world.insert_component.call(new_entity, Component, construct.call(types.DynamicComponent, #{ "data": instance })); + current_reference = world.get_component.call(new_entity, Component); +}; + +fn bench() { + // reference into random key into current_reference steps times + let reference = current_reference.data; + let current_step = 0; + while current_step < steps { + reference = reference[step_keys[current_step]]; + current_step += 1; + }; +}; \ No newline at end of file diff --git a/crates/testing_crates/script_integration_test_harness/Cargo.toml b/crates/testing_crates/script_integration_test_harness/Cargo.toml index 3e7111d35b..45c85b81bc 100644 --- a/crates/testing_crates/script_integration_test_harness/Cargo.toml +++ b/crates/testing_crates/script_integration_test_harness/Cargo.toml @@ -22,3 +22,5 @@ pretty_assertions = "1.*" bevy_mod_scripting_lua = { path = "../../languages/bevy_mod_scripting_lua", optional = true } bevy_mod_scripting_rhai = { path = "../../languages/bevy_mod_scripting_rhai", optional = true } criterion = "0.5" +rand = "0.9" +rand_chacha = "0.9" diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index 2078cf2ef4..0518ec05d8 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -142,7 +142,7 @@ pub fn make_test_rhai_plugin() -> bevy_mod_scripting_rhai::RhaiScriptingPlugin { RhaiScriptingPlugin::default().add_runtime_initializer(|runtime| { let mut runtime = runtime.write(); - + runtime.set_max_call_levels(1000); runtime.register_fn("assert", |a: Dynamic, b: &str| { if !a.is::() { panic!("Expected a boolean value, but got {:?}", a); @@ -355,7 +355,11 @@ pub fn run_lua_benchmark( criterion, |ctxt, _runtime, label, criterion| { let bencher: Function = ctxt.globals().get("bench").map_err(|e| e.to_string())?; + let pre_bencher: Option = ctxt.globals().get("pre_bench").ok(); criterion.bench_function(label, |c| { + if let Some(pre_bencher) = &pre_bencher { + pre_bencher.call::<()>(()).unwrap(); + } c.iter(|| { bencher.call::<()>(()).unwrap(); }) @@ -382,7 +386,14 @@ pub fn run_rhai_benchmark( |ctxt, runtime, label, criterion| { let runtime = runtime.read(); const ARGS: [usize; 0] = []; + let has_pre_bench = ctxt.ast.iter_functions().any(|f| f.name == "pre_bench"); criterion.bench_function(label, |c| { + // call "pre_bench" if any + if has_pre_bench { + let _ = runtime + .call_fn::(&mut ctxt.scope, &ctxt.ast, "pre_bench", ARGS) + .unwrap(); + } c.iter(|| { let _ = runtime .call_fn::(&mut ctxt.scope, &ctxt.ast, "bench", ARGS) diff --git a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs index 71a5268816..910ee6c412 100644 --- a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use bevy::{ app::App, @@ -15,12 +18,20 @@ use bevy_mod_scripting_core::{ }, pretty_print::DisplayWithWorld, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, - ScriptTypeRegistration, + ScriptTypeRegistration, ScriptValue, }, error::InteropError, }; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha12Rng; use test_utils::test_data::EnumerateTestComponents; +// lazy lock rng state +static RNG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { + let seed = [42u8; 32]; + Mutex::new(ChaCha12Rng::from_seed(seed)) +}); + pub fn register_test_functions(world: &mut App) { let world = world.world_mut(); NamespaceBuilder::::new_unregistered(world) @@ -108,7 +119,29 @@ pub fn register_test_functions(world: &mut App) { NamespaceBuilder::::new_unregistered(world) .register("global_hello_world", || Ok("hi!")) + .register("random", |start: Option, end: Option| { + let start = start.unwrap_or(0); + let end = end.unwrap_or(1); + let mut rng = RNG.lock().unwrap(); + rng.random_range::(start..=end) + }) + .register("random_int", |start: Option, end: Option| { + let start = start.unwrap_or(0); + let end = end.unwrap_or(1); + let mut rng = RNG.lock().unwrap(); + rng.random_range::(start..=end) + }) + .register("reseed", || { + let seed = [42u8; 32]; + let mut rng = RNG.lock().unwrap(); + *rng = ChaCha12Rng::from_seed(seed); + }) .register("make_hashmap", |map: HashMap| map) + .register("noop", || {}) + .register( + "noop_4_args", + |_a: ScriptValue, _b: ScriptValue, _c: ScriptValue, _d: ScriptValue| {}, + ) .register( "assert_str_eq", |s1: String, s2: String, reason: Option| { diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index e24e372ffb..3f43e9b86a 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -349,8 +349,12 @@ impl App { Xtasks::Install { binary } => { cmd.arg("install").arg(binary.as_ref()); } - Xtasks::Bench {} => { + Xtasks::Bench { publish } => { cmd.arg("bench"); + + if publish { + cmd.arg("--publish"); + } } } @@ -638,7 +642,12 @@ enum Xtasks { /// CiMatrix, /// Runs bencher in dry mode by default if not on the main branch - Bench {}, + /// To publish main branch defaults set publish mode to true + Bench { + /// Publish the benchmarks when on main + #[clap(long, default_value = "false", help = "Publish the benchmarks")] + publish: bool, + }, } #[derive(Serialize, Clone)] @@ -714,7 +723,7 @@ impl Xtasks { bevy_features, } => Self::codegen(app_settings, output_dir, bevy_features), Xtasks::Install { binary } => Self::install(app_settings, binary), - Xtasks::Bench {} => Self::bench(app_settings), + Xtasks::Bench { publish: execute } => Self::bench(app_settings, execute), }?; Ok("".into()) @@ -1214,7 +1223,7 @@ impl Xtasks { Ok(()) } - fn bench(app_settings: GlobalArgs) -> Result<()> { + fn bench(app_settings: GlobalArgs, execute: bool) -> Result<()> { // first of all figure out which branch we're on // run // git rev-parse --abbrev-ref HEAD let workspace_dir = Self::workspace_dir(&app_settings).unwrap(); @@ -1268,7 +1277,7 @@ impl Xtasks { bencher_cmd.args(["--github-actions", &token]); } - if !is_main { + if !is_main || !execute { bencher_cmd.args(["--dry-run"]); } @@ -1285,6 +1294,143 @@ impl Xtasks { bail!("Failed to run bencher: {:?}", out); } + // if we're on linux and publishing and on main synch graphs + if os == "linux" && is_main && execute { + Self::synch_bencher_graphs()?; + } + + Ok(()) + } + + fn synch_bencher_graphs() -> Result<()> { + // first run `bencher benchmark list bms + // this produces list of objects each containing a `uuid` and `name` + + let parse_list_of_dicts = |bytes: Vec| { + serde_json::from_slice::>>(&bytes) + .with_context(|| "Could not parse bencher output") + }; + + let token = std::env::var("BENCHER_API_TOKEN").ok(); + let mut bencher_cmd = Command::new("bencher"); + let benchmarks = bencher_cmd + .arg("benchmark") + .args(["list", "bms"]) + .args(["--token", &token.clone().unwrap_or_default()]) + .output() + .with_context(|| "Could not list benchmarks")?; + if !benchmarks.status.success() { + bail!("Failed to list benchmarks: {:?}", benchmarks); + } + + // parse teh name and uuid pairs + let benchmarks = parse_list_of_dicts(benchmarks.stdout)? + .into_iter() + .map(|p| { + let name = p.get("name").expect("no name in project"); + let uuid = p.get("uuid").expect("no uuid in project"); + (name.clone(), uuid.clone()) + }) + .collect::>(); + + // delete all plots using bencher plot list bms to get "uuid's" + // then bencher plot delete bms + + let bencher_cmd = Command::new("bencher") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .args(["plot", "list", "bms"]) + .args(["--token", &token.clone().unwrap_or_default()]) + .output() + .with_context(|| "Could not list plots")?; + + if !bencher_cmd.status.success() { + bail!("Failed to list plots: {:?}", bencher_cmd); + } + + let plots = parse_list_of_dicts(bencher_cmd.stdout)? + .into_iter() + .map(|p| { + let uuid = p.get("uuid").expect("no uuid in plot"); + uuid.clone() + }) + .collect::>(); + + for uuid in plots { + let bencher_cmd = Command::new("bencher") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .args(["plot", "delete", "bms", &uuid]) + .args(["--token", &token.clone().unwrap_or_default()]) + .output() + .with_context(|| "Could not delete plot")?; + + if !bencher_cmd.status.success() { + bail!("Failed to delete plot: {:?}", bencher_cmd); + } + } + let testbeds = Command::new("bencher") + .arg("testbed") + .args(["list", "bms"]) + .args(["--token", &token.clone().unwrap_or_default()]) + .output() + .with_context(|| "Could not list testbeds")?; + + if !testbeds.status.success() { + bail!("Failed to list testbeds: {:?}", testbeds); + } + + let testbeds = parse_list_of_dicts(testbeds.stdout)? + .into_iter() + .map(|p| { + let name = p.get("name").expect("no name in testbed"); + let uuid = p.get("uuid").expect("no uuid in testbed"); + (name.clone(), uuid.clone()) + }) + .filter(|(name, _)| name.contains("gha")) + .collect::>(); + + let group_to_benchmark_map: HashMap<_, Vec<_>> = + benchmarks + .iter() + .fold(HashMap::new(), |mut acc, (name, uuid)| { + let group = name.split('/').next().unwrap_or_default(); + acc.entry(group.to_owned()).or_default().push(uuid.clone()); + acc + }); + + // create plot using + // bencher plot create --x-axis date_time --branches main --testbeds --benchmarks --measures latency + + for (group, uuids) in group_to_benchmark_map { + for (testbed_name, testbed_uuid) in testbeds.iter() { + let without_gha = testbed_name.replace("-gha", ""); + let plot_name = format!("{without_gha} {group}"); + + let window_months = 12; + let window_seconds = window_months * 30 * 24 * 60 * 60; + let bencher_cmd = Command::new("bencher") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .args(["plot", "create", "bms"]) + .args(["--title", &plot_name]) + .arg("--upper-boundary") + .args(["--x-axis", "date_time"]) + .args(["--window", &window_seconds.to_string()]) + .args(["--branches", "main"]) + .args(["--testbeds", testbed_uuid]) + .args(["--benchmarks", &uuids.join(",")]) + .args(["--measures", "latency"]) + .args(["--token", &token.clone().unwrap_or_default()]) + .output() + .with_context(|| "Could not create plot")?; + + if !bencher_cmd.status.success() { + bail!("Failed to create plot: {:?}", bencher_cmd); + } + } + } + Ok(()) } @@ -1453,7 +1599,7 @@ impl Xtasks { // on non-main branches this will just dry run output.push(App { global_args: default_args.clone(), - subcmd: Xtasks::Bench {}, + subcmd: Xtasks::Bench { publish: true }, }); // and finally run tests with coverage From 7f44ebd3542b9d0a4ccffaff967f536b37bf82bd Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 23 Mar 2025 18:35:43 +0000 Subject: [PATCH 19/51] chore: correct graph synching code --- crates/xtask/src/main.rs | 57 +++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 3f43e9b86a..a29d8f3836 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1224,8 +1224,8 @@ impl Xtasks { } fn bench(app_settings: GlobalArgs, execute: bool) -> Result<()> { - // first of all figure out which branch we're on - // run // git rev-parse --abbrev-ref HEAD + // // first of all figure out which branch we're on + // // run // git rev-parse --abbrev-ref HEAD let workspace_dir = Self::workspace_dir(&app_settings).unwrap(); let command = Command::new("git") .args(["rev-parse", "--abbrev-ref", "HEAD"]) @@ -1273,8 +1273,8 @@ impl Xtasks { .args(["--thresholds-reset"]) .args(["--err"]); - if let Some(token) = github_token { - bencher_cmd.args(["--github-actions", &token]); + if let Some(token) = &github_token { + bencher_cmd.args(["--github-actions", token]); } if !is_main || !execute { @@ -1295,7 +1295,7 @@ impl Xtasks { } // if we're on linux and publishing and on main synch graphs - if os == "linux" && is_main && execute { + if os == "linux" && is_main && execute && github_token.is_some() { Self::synch_bencher_graphs()?; } @@ -1307,13 +1307,26 @@ impl Xtasks { // this produces list of objects each containing a `uuid` and `name` let parse_list_of_dicts = |bytes: Vec| { - serde_json::from_slice::>>(&bytes) + if bytes.is_empty() { + bail!("Empty input"); + } + serde_json::from_slice::>>(&bytes) + .map(|map| { + map.into_iter() + .map(|map| { + map.into_iter() + .map(|(k, v)| (k, v.as_str().unwrap_or_default().to_string())) + .collect::>() + }) + .collect::>() + }) .with_context(|| "Could not parse bencher output") }; let token = std::env::var("BENCHER_API_TOKEN").ok(); let mut bencher_cmd = Command::new("bencher"); let benchmarks = bencher_cmd + .stdout(std::process::Stdio::piped()) .arg("benchmark") .args(["list", "bms"]) .args(["--token", &token.clone().unwrap_or_default()]) @@ -1324,7 +1337,8 @@ impl Xtasks { } // parse teh name and uuid pairs - let benchmarks = parse_list_of_dicts(benchmarks.stdout)? + let benchmarks = parse_list_of_dicts(benchmarks.stdout) + .with_context(|| "Reading benchmarks")? .into_iter() .map(|p| { let name = p.get("name").expect("no name in project"); @@ -1337,7 +1351,7 @@ impl Xtasks { // then bencher plot delete bms let bencher_cmd = Command::new("bencher") - .stdout(std::process::Stdio::inherit()) + .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::inherit()) .args(["plot", "list", "bms"]) .args(["--token", &token.clone().unwrap_or_default()]) @@ -1348,9 +1362,11 @@ impl Xtasks { bail!("Failed to list plots: {:?}", bencher_cmd); } - let plots = parse_list_of_dicts(bencher_cmd.stdout)? + let plots = parse_list_of_dicts(bencher_cmd.stdout) + .with_context(|| "reading plots")? .into_iter() .map(|p| { + log::info!("Plot to delete: {:?}", p); let uuid = p.get("uuid").expect("no uuid in plot"); uuid.clone() }) @@ -1376,11 +1392,15 @@ impl Xtasks { .output() .with_context(|| "Could not list testbeds")?; + const MAIN_BRANCH_UUID: &str = "1d70a4e3-d416-43fc-91bd-4b1c8f9e9580"; + const LATENCY_MEASURE_UUID: &str = "6820b034-5163-4cdd-95f5-5640dd0ff298"; + if !testbeds.status.success() { bail!("Failed to list testbeds: {:?}", testbeds); } - let testbeds = parse_list_of_dicts(testbeds.stdout)? + let testbeds = parse_list_of_dicts(testbeds.stdout) + .with_context(|| "reading testbeds")? .into_iter() .map(|p| { let name = p.get("name").expect("no name in testbed"); @@ -1409,19 +1429,24 @@ impl Xtasks { let window_months = 12; let window_seconds = window_months * 30 * 24 * 60 * 60; - let bencher_cmd = Command::new("bencher") + let mut bencher_cmd = Command::new("bencher"); + bencher_cmd .stdout(std::process::Stdio::inherit()) .stderr(std::process::Stdio::inherit()) .args(["plot", "create", "bms"]) .args(["--title", &plot_name]) - .arg("--upper-boundary") .args(["--x-axis", "date_time"]) .args(["--window", &window_seconds.to_string()]) - .args(["--branches", "main"]) + .args(["--branches", MAIN_BRANCH_UUID]) .args(["--testbeds", testbed_uuid]) - .args(["--benchmarks", &uuids.join(",")]) - .args(["--measures", "latency"]) - .args(["--token", &token.clone().unwrap_or_default()]) + .args(["--measures", LATENCY_MEASURE_UUID]) + .args(["--token", &token.clone().unwrap_or_default()]); + + for benchmark_uuid in &uuids { + bencher_cmd.arg("--benchmarks").arg(benchmark_uuid); + } + + let bencher_cmd = bencher_cmd .output() .with_context(|| "Could not create plot")?; From 4ceac917d2a509279ce27c8cfe95285def997b3d Mon Sep 17 00:00:00 2001 From: makspll Date: Sun, 23 Mar 2025 18:38:57 +0000 Subject: [PATCH 20/51] chore: sort graphs --- crates/xtask/src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index a29d8f3836..f942866615 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1399,7 +1399,7 @@ impl Xtasks { bail!("Failed to list testbeds: {:?}", testbeds); } - let testbeds = parse_list_of_dicts(testbeds.stdout) + let mut testbeds = parse_list_of_dicts(testbeds.stdout) .with_context(|| "reading testbeds")? .into_iter() .map(|p| { @@ -1409,6 +1409,7 @@ impl Xtasks { }) .filter(|(name, _)| name.contains("gha")) .collect::>(); + testbeds.sort(); let group_to_benchmark_map: HashMap<_, Vec<_>> = benchmarks @@ -1422,7 +1423,7 @@ impl Xtasks { // create plot using // bencher plot create --x-axis date_time --branches main --testbeds --benchmarks --measures latency - for (group, uuids) in group_to_benchmark_map { + for (group, uuids) in group_to_benchmark_map.iter().sorted() { for (testbed_name, testbed_uuid) in testbeds.iter() { let without_gha = testbed_name.replace("-gha", ""); let plot_name = format!("{without_gha} {group}"); @@ -1442,7 +1443,7 @@ impl Xtasks { .args(["--measures", LATENCY_MEASURE_UUID]) .args(["--token", &token.clone().unwrap_or_default()]); - for benchmark_uuid in &uuids { + for benchmark_uuid in uuids { bencher_cmd.arg("--benchmarks").arg(benchmark_uuid); } From e806daaf39e6fa0f4cf405ea588a620d47cac3a3 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 23 Mar 2025 19:50:15 +0000 Subject: [PATCH 21/51] test: add dynamic component query tests (#387) # Summary - Adds tests for queries on dynamic components --- .../tests/query/dynamic_component_query.lua | 20 +++++++++++++++++++ .../tests/query/dynamic_component_query.rhai | 17 ++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 assets/tests/query/dynamic_component_query.lua create mode 100644 assets/tests/query/dynamic_component_query.rhai diff --git a/assets/tests/query/dynamic_component_query.lua b/assets/tests/query/dynamic_component_query.lua new file mode 100644 index 0000000000..65375c79ec --- /dev/null +++ b/assets/tests/query/dynamic_component_query.lua @@ -0,0 +1,20 @@ +local entity_a = world.spawn() +local NewComponent = world.register_new_component("NewComponent") + +world.add_default_component(entity_a, NewComponent) + +local found_entities = {} +for i,result in pairs(world.query():component(NewComponent):build()) do + table.insert(found_entities, result:entity()) +end + +assert(#found_entities == 1, "Expected 1 entities, got " .. #found_entities) + +expected_entities = { + entity_a +} + +for i, entity in ipairs(found_entities) do + assert(entity:index() == expected_entities[i]:index(), "Expected entity " .. expected_entities[i]:index() .. " but got " .. entity:index()) +end + diff --git a/assets/tests/query/dynamic_component_query.rhai b/assets/tests/query/dynamic_component_query.rhai new file mode 100644 index 0000000000..b89c9fafe1 --- /dev/null +++ b/assets/tests/query/dynamic_component_query.rhai @@ -0,0 +1,17 @@ +let entity_a = world.spawn_.call(); +let NewComponent = world.register_new_component.call("NewComponent"); + +world.add_default_component.call(entity_a, NewComponent); + +let found_entities = []; +for (result, i) in world.query.call().component.call(NewComponent).build.call() { + found_entities.push(result.entity.call()); +} + +assert(found_entities.len == 1, "Expected 1 entities, got " + found_entities.len); + +let expected_entities = [entity_a]; + +for (entity, i) in found_entities { + assert(entity.index.call() == expected_entities[i].index.call(), "Expected entity " + expected_entities[i].index.call() + " but got " + entity.index.call()); +} \ No newline at end of file From a147891b9a657ed4c22655d2ebb15f237329413e Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sun, 23 Mar 2025 20:59:06 +0000 Subject: [PATCH 22/51] fix: fix global type cache not containing generic types (#388) # Summary `types["GenericType"]` should work now, this was using `ident` instead of `short_path` by mistake --- assets/tests/globals/type_cache_available.lua | 4 +++ .../tests/globals/type_cache_available.rhai | 4 +++ .../src/bindings/globals/core.rs | 13 +++++---- .../src/lib.rs | 27 ------------------- .../test_utils/src/test_data.rs | 21 ++++++++++++--- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/assets/tests/globals/type_cache_available.lua b/assets/tests/globals/type_cache_available.lua index f9cc8435dc..3d99c77a24 100644 --- a/assets/tests/globals/type_cache_available.lua +++ b/assets/tests/globals/type_cache_available.lua @@ -3,4 +3,8 @@ 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()); + + local my_generic_type = types["GenericComponent"]; + assert(my_generic_type ~= nil, "Type GenericComponent is not available in type cache"); + assert(my_generic_type:short_name() == "GenericComponent", "Type t.GenericComponent:short_name() is not correct: " .. my_generic_type:short_name()); end \ No newline at end of file diff --git a/assets/tests/globals/type_cache_available.rhai b/assets/tests/globals/type_cache_available.rhai index ff736a7afb..fd01920c74 100644 --- a/assets/tests/globals/type_cache_available.rhai +++ b/assets/tests/globals/type_cache_available.rhai @@ -2,4 +2,8 @@ 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()); + + let my_type = types["GenericComponent"]; + assert(type_of(my_type) != "()", "Type GenericComponent is not available in type cache"); + assert(my_type.short_name.call() == "GenericComponent", "Type t.GenericComponent:short_name() is not correct: " + my_type.short_name.call()); } \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index 202ae6ea70..394fe4d6b3 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -87,13 +87,12 @@ impl CoreGlobals { let type_registry = type_registry.read(); let mut type_cache = HashMap::::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); - } + let type_path = registration.type_info().type_path_table().short_path(); + 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(type_path.to_owned(), registration); } Ok(type_cache) diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index 0518ec05d8..81209056eb 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -38,33 +38,6 @@ fn dummy_startup_system() {} fn dummy_before_post_update_system() {} fn dummy_post_update_system() {} -pub trait Benchmarker: 'static + Send + Sync { - fn bench( - &self, - label: &str, - f: &dyn Fn() -> Result, - ) -> Result; - - fn clone_box(&self) -> Box; -} - -#[derive(Clone)] -pub struct NoOpBenchmarker; - -impl Benchmarker for NoOpBenchmarker { - fn bench( - &self, - _label: &str, - f: &dyn Fn() -> Result, - ) -> Result { - f() - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - #[derive(Event)] struct TestEventFinished; diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index 59bb5bf0f0..fea49718d7 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -23,6 +23,20 @@ impl TestComponent { } } +#[derive(Component, Reflect, PartialEq, Eq, Debug, Default)] +#[reflect(Component)] +pub struct GenericComponent { + pub value: T, +} + +impl GenericComponent { + pub fn init() -> Self { + Self { + value: "Initial Value".to_string(), + } + } +} + /// Test Resource with Reflect and ReflectResource registered #[derive(Resource, Reflect, Default, PartialEq, Eq, Debug)] #[reflect(Resource)] @@ -278,11 +292,12 @@ impl_test_component_ids!( SimpleStruct => 6, SimpleTupleStruct => 7, SimpleEnum => 8, + GenericComponent => 9, ], [ - TestResource => 9, - ResourceWithDefault => 10, - TestResourceWithVariousFields => 11, + TestResource => 10, + ResourceWithDefault => 11, + TestResourceWithVariousFields => 12, ] ); From 3a679911ca3e0bea217e87a0baaf0507f32e853e Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Mon, 24 Mar 2025 13:27:16 +0000 Subject: [PATCH 23/51] chore: Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 797f4a2b94..fc8d609fb3 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,7 @@ Although Bevy doesn't directly support scripting, efforts are underway to incorp - Globals and bindings are set in one place and translated to all supported languages - Broad set of core Bevy bindings generated for you - Dynamic systems & components registerable from scripts + - Scripts can run in parallel to existing rust systems or other scripts - Customizable event driven communication between bevy and scripts (`on_update`, `on_init` etc..) - Documentation generation From 4a670bf38f22573d46a7c07009a57640decf2cf4 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Mon, 24 Mar 2025 22:58:45 +0000 Subject: [PATCH 24/51] chore: run bencher on PR or fork (#390) # Summary - Runs bencher on PR's as per instructions at https://bencher.dev/docs/how-to/github-actions/ --- .github/workflows/bencher_on_pr_or_fork.yml | 34 ++++++++++++ .../bencher_on_pr_or_fork_closed.yml | 17 ++++++ .../bencher_on_pr_or_fork_upload.yml | 52 ++++++++++++++++++ Cargo.toml | 1 + benches/benchmarks.rs | 13 +++-- crates/xtask/src/main.rs | 55 ++++++++++++++----- 6 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/bencher_on_pr_or_fork.yml create mode 100644 .github/workflows/bencher_on_pr_or_fork_closed.yml create mode 100644 .github/workflows/bencher_on_pr_or_fork_upload.yml diff --git a/.github/workflows/bencher_on_pr_or_fork.yml b/.github/workflows/bencher_on_pr_or_fork.yml new file mode 100644 index 0000000000..48699a1295 --- /dev/null +++ b/.github/workflows/bencher_on_pr_or_fork.yml @@ -0,0 +1,34 @@ +name: Run benchmarks on PR + +on: + pull_request: + types: [opened, reopened, edited, synchronize] + +jobs: + benchmark_fork_pr_branch: + name: Run Fork PR Benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Rust Cache + uses: Swatinem/rust-cache@v2.7.7 + with: + # reasoning: we want to cache xtask, most of the jobs in the matrix will be sped up a good bit thanks to that + save-if: ${{ github.ref == 'refs/heads/main' }} + cache-all-crates: true + - name: Run `cargo xtask init` + run: | + cargo xtask init + - name: Run `cargo xtask bench` and save results + run: | + cargo xtask bench > benchmark_results.txt + - name: Upload Benchmark Results + uses: actions/upload-artifact@v4 + with: + name: benchmark_results.txt + path: ./benchmark_results.txt + - name: Upload GitHub Pull Request Event + uses: actions/upload-artifact@v4 + with: + name: event.json + path: ${{ github.event_path }} \ No newline at end of file diff --git a/.github/workflows/bencher_on_pr_or_fork_closed.yml b/.github/workflows/bencher_on_pr_or_fork_closed.yml new file mode 100644 index 0000000000..7107cdead4 --- /dev/null +++ b/.github/workflows/bencher_on_pr_or_fork_closed.yml @@ -0,0 +1,17 @@ +on: + pull_request_target: + types: [closed] + +jobs: + archive_fork_pr_branch: + name: Archive closed fork PR branch with Bencher + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bencherdev/bencher@main + - name: Archive closed fork PR branch with Bencher + run: | + bencher archive \ + --project bms \ + --token '${{ secrets.BENCHER_API_TOKEN }}' \ + --branch "$GITHUB_HEAD_REF" \ No newline at end of file diff --git a/.github/workflows/bencher_on_pr_or_fork_upload.yml b/.github/workflows/bencher_on_pr_or_fork_upload.yml new file mode 100644 index 0000000000..38442c96ec --- /dev/null +++ b/.github/workflows/bencher_on_pr_or_fork_upload.yml @@ -0,0 +1,52 @@ +name: Upload benchmarks to bencher + +on: + workflow_run: + workflows: [Run benchmarks on PR] + types: [completed] + +jobs: + track_fork_pr_branch: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + env: + BENCHMARK_RESULTS: benchmark_results.txt + PR_EVENT: event.json + steps: + - name: Download Benchmark Results + uses: dawidd6/action-download-artifact@v6 + with: + name: ${{ env.BENCHMARK_RESULTS }} + run_id: ${{ github.event.workflow_run.id }} + - name: Download PR Event + uses: dawidd6/action-download-artifact@v6 + with: + name: ${{ env.PR_EVENT }} + run_id: ${{ github.event.workflow_run.id }} + - name: Export PR Event Data + uses: actions/github-script@v6 + with: + script: | + let fs = require('fs'); + let prEvent = JSON.parse(fs.readFileSync(process.env.PR_EVENT, {encoding: 'utf8'})); + core.exportVariable("PR_HEAD", prEvent.pull_request.head.ref); + core.exportVariable("PR_BASE", prEvent.pull_request.base.ref); + core.exportVariable("PR_BASE_SHA", prEvent.pull_request.base.sha); + core.exportVariable("PR_NUMBER", prEvent.number); + - uses: bencherdev/bencher@main + - name: Track Benchmarks with Bencher + run: | + bencher run \ + --project bms \ + --token '${{ secrets.BENCHER_API_TOKEN }}' \ + --branch "$PR_HEAD" \ + --start-point "$PR_BASE" \ + --start-point-hash "$PR_BASE_SHA" \ + --start-point-clone-thresholds \ + --start-point-reset \ + --testbed linux-gha \ + --err \ + --adapter rust_criterion \ + --github-actions '${{ secrets.GITHUB_TOKEN }}' \ + --ci-number "$PR_NUMBER" \ + --file "$BENCHMARK_RESULTS" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 3044400cce..57de792ae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ include = ["readme.md", "/src", "/examples", "/assets", "LICENSE", "/badges"] [lib] name = "bevy_mod_scripting" path = "src/lib.rs" +bench = false [package.metadata."docs.rs"] features = ["lua54", "rhai"] diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 76766cfe47..e2f394569a 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,9 +1,7 @@ -use std::path::PathBuf; +use std::{path::PathBuf, time::Duration}; use bevy::utils::HashMap; -use criterion::{ - criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion, -}; +use criterion::{criterion_main, measurement::Measurement, BenchmarkGroup, Criterion}; use script_integration_test_harness::{run_lua_benchmark, run_rhai_benchmark}; use test_utils::{discover_all_tests, Test}; @@ -91,5 +89,10 @@ fn script_benchmarks(criterion: &mut Criterion) { } } -criterion_group!(benches, script_benchmarks); +pub fn benches() { + let mut criterion: criterion::Criterion<_> = (criterion::Criterion::default()) + .configure_from_args() + .measurement_time(Duration::from_secs(10)); + script_benchmarks(&mut criterion); +} criterion_main!(benches); diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index f942866615..6e0dac98e2 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -9,7 +9,7 @@ use std::{ ffi::{OsStr, OsString}, io::Write, path::{Path, PathBuf}, - process::Command, + process::{Command, Output}, str::FromStr, }; use strum::{IntoEnumIterator, VariantNames}; @@ -349,13 +349,16 @@ impl App { Xtasks::Install { binary } => { cmd.arg("install").arg(binary.as_ref()); } - Xtasks::Bench { publish } => { - cmd.arg("bench"); + Xtasks::Bencher { publish } => { + cmd.arg("bencher"); if publish { cmd.arg("--publish"); } } + Xtasks::Bench {} => { + cmd.arg("bench"); + } } cmd @@ -643,11 +646,13 @@ enum Xtasks { CiMatrix, /// Runs bencher in dry mode by default if not on the main branch /// To publish main branch defaults set publish mode to true - Bench { + Bencher { /// Publish the benchmarks when on main #[clap(long, default_value = "false", help = "Publish the benchmarks")] publish: bool, }, + /// Runs criterion benchmarks generates json required to be published by bencher and generates html performance report + Bench {}, } #[derive(Serialize, Clone)] @@ -691,8 +696,10 @@ impl Xtasks { let mut rows = Vec::default(); for os in ::VARIANTS { for row in output.iter() { - let step_should_run_on_main_os = - matches!(row.subcmd, Xtasks::Build | Xtasks::Docs { .. }); + let step_should_run_on_main_os = matches!( + row.subcmd, + Xtasks::Build | Xtasks::Docs { .. } | Xtasks::Bencher { .. } + ); let is_coverage_step = row.global_args.coverage; if !os.is_main_os() && step_should_run_on_main_os { @@ -723,7 +730,8 @@ impl Xtasks { bevy_features, } => Self::codegen(app_settings, output_dir, bevy_features), Xtasks::Install { binary } => Self::install(app_settings, binary), - Xtasks::Bench { publish: execute } => Self::bench(app_settings, execute), + Xtasks::Bencher { publish } => Self::bencher(app_settings, publish), + Xtasks::Bench {} => Self::bench(app_settings), }?; Ok("".into()) @@ -811,7 +819,7 @@ impl Xtasks { context: &str, add_args: I, dir: Option<&Path>, - ) -> Result<()> { + ) -> Result { let coverage_mode = app_settings .coverage .then_some("with coverage") @@ -878,7 +886,7 @@ impl Xtasks { let output = cmd.output().with_context(|| context.to_owned())?; match output.status.code() { - Some(0) => Ok(()), + Some(0) => Ok(output), _ => bail!( "{} failed with exit code: {}. Features: {}", context, @@ -1223,7 +1231,26 @@ impl Xtasks { Ok(()) } - fn bench(app_settings: GlobalArgs, execute: bool) -> Result<()> { + fn bench(app_settings: GlobalArgs) -> Result<()> { + Self::run_workspace_command( + // run with just lua54 + &app_settings.with_features(Features::new(vec![ + Feature::Lua54, + Feature::Rhai, + Feature::CoreFunctions, + Feature::BevyBindings, + ])), + "bench", + "Failed to run benchmarks", + Vec::::default(), + None, + ) + .with_context(|| "when executing criterion benchmarks")?; + + Ok(()) + } + + fn bencher(app_settings: GlobalArgs, publish: bool) -> Result<()> { // // first of all figure out which branch we're on // // run // git rev-parse --abbrev-ref HEAD let workspace_dir = Self::workspace_dir(&app_settings).unwrap(); @@ -1277,13 +1304,13 @@ impl Xtasks { bencher_cmd.args(["--github-actions", token]); } - if !is_main || !execute { + if !is_main || !publish { bencher_cmd.args(["--dry-run"]); } bencher_cmd .args(["--adapter", "rust_criterion"]) - .arg("cargo bench --features=lua54"); + .arg("cargo xtask bench"); log::info!("Running bencher command: {:?}", bencher_cmd); @@ -1295,7 +1322,7 @@ impl Xtasks { } // if we're on linux and publishing and on main synch graphs - if os == "linux" && is_main && execute && github_token.is_some() { + if os == "linux" && is_main && publish && github_token.is_some() { Self::synch_bencher_graphs()?; } @@ -1625,7 +1652,7 @@ impl Xtasks { // on non-main branches this will just dry run output.push(App { global_args: default_args.clone(), - subcmd: Xtasks::Bench { publish: true }, + subcmd: Xtasks::Bencher { publish: true }, }); // and finally run tests with coverage From 09f1f155939100c0a5cd167120d7a75112d7240d Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Wed, 26 Mar 2025 11:57:18 +0000 Subject: [PATCH 25/51] fix: mdbook preprocessor links not taking into account root url (#391) # Summary - These were broken on the github.io url after I made the links absolute --- .../bencher_on_pr_or_fork_upload.yml | 2 ++ .../mdbook_lad_preprocessor/src/lib.rs | 28 ++++++++++++++++++- .../tests/books/example_ladfile/book.toml | 2 +- docs/book.toml | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bencher_on_pr_or_fork_upload.yml b/.github/workflows/bencher_on_pr_or_fork_upload.yml index 38442c96ec..3c3204d701 100644 --- a/.github/workflows/bencher_on_pr_or_fork_upload.yml +++ b/.github/workflows/bencher_on_pr_or_fork_upload.yml @@ -5,6 +5,8 @@ on: workflows: [Run benchmarks on PR] types: [completed] +permissions: write-all + jobs: track_fork_pr_branch: if: github.event.workflow_run.conclusion == 'success' diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs index 4304945d20..bacb7a2bdb 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs @@ -10,6 +10,24 @@ mod argument_visitor; mod markdown; mod sections; +#[derive(Debug)] +struct Options { + pub root: String, +} + +impl From<&PreprocessorContext> for Options { + fn from(context: &PreprocessorContext) -> Self { + let root = context + .config + .get_preprocessor("lad-preprocessor") + .and_then(|t| t.get("root")) + .and_then(|v| v.as_str()) + .map(|s| s.to_owned()) + .unwrap_or_default(); + Options { root } + } +} + const LAD_EXTENSION: &str = "lad.json"; pub struct LADPreprocessor; @@ -31,6 +49,7 @@ impl LADPreprocessor { /// and `chapter_index` is the index of the chapter among its siblings. fn process_lad_chapter( _context: &PreprocessorContext, + options: &Options, chapter: &mdbook::book::Chapter, parent: Option<&mdbook::book::Chapter>, chapter_index: usize, @@ -45,9 +64,11 @@ impl LADPreprocessor { let parent_path = parent .and_then(|p| p.path.clone()) - .unwrap_or_default() + .unwrap_or_else(|| options.root.clone().into()) .with_extension(""); + log::debug!("Parent path: {:?}", parent_path); + let new_chapter = Section::new( parent_path, &ladfile, @@ -75,6 +96,9 @@ impl Preprocessor for LADPreprocessor { mut book: mdbook::book::Book, ) -> mdbook::errors::Result { let mut errors = Vec::new(); + let options = Options::from(context); + + log::debug!("Options: {:?}", options); // first replace children in parents book.for_each_mut(|item| { @@ -89,6 +113,7 @@ impl Preprocessor for LADPreprocessor { if LADPreprocessor::is_lad_file(chapter) { match LADPreprocessor::process_lad_chapter( context, + &options, chapter, Some(parent), idx, @@ -122,6 +147,7 @@ impl Preprocessor for LADPreprocessor { } let new_chapter = match LADPreprocessor::process_lad_chapter( context, + &options, chapter, None, chapter diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml index 0620453489..8c84c8e7f9 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml @@ -8,6 +8,6 @@ description = "Documentation for the Bevy Scripting library" [preprocessor.lad-preprocessor] - +root = "root" [output.markdown] diff --git a/docs/book.toml b/docs/book.toml index b347b4786a..6728e0f0ee 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -16,3 +16,4 @@ enable = true level = 1 [preprocessor.lad-preprocessor] +root = "bevy_mod_scripting" From a643b70f7627bf5988491708721c47848e3b2ef9 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Wed, 26 Mar 2025 13:17:29 +0000 Subject: [PATCH 26/51] fix: make all links in the mdbook preprocessor relative (#392) # Summary - absolute links don't work that well, just switch back to relative ones --- .../lad_backends/mdbook_lad_preprocessor/src/lib.rs | 11 +++++++---- .../mdbook_lad_preprocessor/src/sections.rs | 4 ++-- .../expected/parent/lad/functions/hello_world.md | 4 ++-- .../example_ladfile/expected/parent/lad/globals.md | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs index bacb7a2bdb..81634677ad 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs @@ -6,6 +6,7 @@ use mdbook::{ preprocess::{Preprocessor, PreprocessorContext}, }; use sections::{Section, SectionData}; +use std::sync::OnceLock; mod argument_visitor; mod markdown; mod sections; @@ -29,6 +30,8 @@ impl From<&PreprocessorContext> for Options { } const LAD_EXTENSION: &str = "lad.json"; +// global for options +static OPTIONS: OnceLock = OnceLock::new(); pub struct LADPreprocessor; @@ -49,7 +52,6 @@ impl LADPreprocessor { /// and `chapter_index` is the index of the chapter among its siblings. fn process_lad_chapter( _context: &PreprocessorContext, - options: &Options, chapter: &mdbook::book::Chapter, parent: Option<&mdbook::book::Chapter>, chapter_index: usize, @@ -64,7 +66,7 @@ impl LADPreprocessor { let parent_path = parent .and_then(|p| p.path.clone()) - .unwrap_or_else(|| options.root.clone().into()) + .unwrap_or_default() .with_extension(""); log::debug!("Parent path: {:?}", parent_path); @@ -99,6 +101,9 @@ impl Preprocessor for LADPreprocessor { let options = Options::from(context); log::debug!("Options: {:?}", options); + OPTIONS + .set(options) + .map_err(|_| mdbook::errors::Error::msg("could not initialize options"))?; // first replace children in parents book.for_each_mut(|item| { @@ -113,7 +118,6 @@ impl Preprocessor for LADPreprocessor { if LADPreprocessor::is_lad_file(chapter) { match LADPreprocessor::process_lad_chapter( context, - &options, chapter, Some(parent), idx, @@ -147,7 +151,6 @@ impl Preprocessor for LADPreprocessor { } let new_chapter = match LADPreprocessor::process_lad_chapter( context, - &options, chapter, None, chapter diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs index aefa59e2db..87ac40d1de 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/sections.rs @@ -286,7 +286,7 @@ impl<'a> Section<'a> { } SectionData::InstancesSummary => { let instances = self.ladfile.globals.iter().collect::>(); - let types_directory = PathBuf::from("/").join(self.parent_path.join("types")); + let types_directory = PathBuf::from("./types"); vec![SectionItem::InstancesSummary { instances, ladfile: self.ladfile, @@ -355,7 +355,7 @@ impl<'a> Section<'a> { ] } SectionData::FunctionDetail { function } => { - let types_directory = self.parent_path.join("../types"); + let types_directory = PathBuf::from("../types"); vec![SectionItem::FunctionDetails { function, ladfile: self.ladfile, diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md index 47ce0b7268..b0fa92768f 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/functions/hello_world.md @@ -6,11 +6,11 @@ | Name | Type | Documentation | | --- | --- | --- | -| **arg1** | [usize](parent/lad/functions/../types/usize.md) | No Documentation 🚧 | +| **arg1** | [usize](../types/usize.md) | No Documentation 🚧 | #### Returns | Name | Type | Documentation | | --- | --- | --- | -| **arg0** | [usize](parent/lad/functions/../types/usize.md) | No Documentation 🚧 | +| **arg0** | [usize](../types/usize.md) | No Documentation 🚧 | diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md index 55e506f43d..0fbafcca88 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/expected/parent/lad/globals.md @@ -10,8 +10,8 @@ Instances containing actual accessible values\. | Instance | Type | | --- | --- | -| `my_non_static_instance` | Vec\<[UnitType](/parent/lad/types/unittype.md)\> | -| `map` | HashMap\<[String](/parent/lad/types/string.md), [String](/parent/lad/types/string.md) \| [String](/parent/lad/types/string.md)\> | +| `my_non_static_instance` | Vec\<[UnitType](./types/unittype.md)\> | +| `map` | HashMap\<[String](./types/string.md), [String](./types/string.md) \| [String](./types/string.md)\> | ### Static Instances @@ -19,5 +19,5 @@ Static type references, existing for the purpose of typed static function calls\ | Instance | Type | | --- | --- | -| `my_static_instance` | StructType\<[usize](/parent/lad/types/usize.md)\> | +| `my_static_instance` | StructType\<[usize](./types/usize.md)\> | From 07a819d810e1e9ffe6b69549378ca8cde0b06826 Mon Sep 17 00:00:00 2001 From: makspll Date: Wed, 26 Mar 2025 14:07:06 +0000 Subject: [PATCH 27/51] chore: fix ci --- .../mdbook_lad_preprocessor/src/lib.rs | 15 +++------------ .../tests/books/example_ladfile/book.toml | 1 - docs/book.toml | 1 - 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs index 81634677ad..8edc0640e3 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs +++ b/crates/lad_backends/mdbook_lad_preprocessor/src/lib.rs @@ -12,20 +12,11 @@ mod markdown; mod sections; #[derive(Debug)] -struct Options { - pub root: String, -} +struct Options {} impl From<&PreprocessorContext> for Options { - fn from(context: &PreprocessorContext) -> Self { - let root = context - .config - .get_preprocessor("lad-preprocessor") - .and_then(|t| t.get("root")) - .and_then(|v| v.as_str()) - .map(|s| s.to_owned()) - .unwrap_or_default(); - Options { root } + fn from(_context: &PreprocessorContext) -> Self { + Options {} } } diff --git a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml index 8c84c8e7f9..6b5a6db954 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml +++ b/crates/lad_backends/mdbook_lad_preprocessor/tests/books/example_ladfile/book.toml @@ -8,6 +8,5 @@ description = "Documentation for the Bevy Scripting library" [preprocessor.lad-preprocessor] -root = "root" [output.markdown] diff --git a/docs/book.toml b/docs/book.toml index 6728e0f0ee..b347b4786a 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -16,4 +16,3 @@ enable = true level = 1 [preprocessor.lad-preprocessor] -root = "bevy_mod_scripting" From 1c91ba8029eebb9f6b6a488fa7dcfdfdb5bd1f63 Mon Sep 17 00:00:00 2001 From: makspll Date: Wed, 26 Mar 2025 14:10:16 +0000 Subject: [PATCH 28/51] chore: get all plots --- crates/xtask/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 6e0dac98e2..9dc073f8ad 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1356,6 +1356,7 @@ impl Xtasks { .stdout(std::process::Stdio::piped()) .arg("benchmark") .args(["list", "bms"]) + .args(["--per-page", "255"]) .args(["--token", &token.clone().unwrap_or_default()]) .output() .with_context(|| "Could not list benchmarks")?; From fed65006397002c3dc64c75dfc10f957cc7cebbe Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Wed, 26 Mar 2025 22:49:37 +0000 Subject: [PATCH 29/51] feat: add `profile_with_tracy` feature which plays nicely with bevy's `bevy/trace_tracy` feature (#393) # Summary Adds `xtask` utilities for profiling a specific benchmark, as well as a feature which plays nicely with bevy's. I tried using an independent `profiling` setup but I think this one is the least confusing and most in line with what bevy consumers will already be familiar with --- Cargo.toml | 4 ++ benches/benchmarks.rs | 41 +++++++++-- .../src/bindings/function/script_function.rs | 4 +- .../src/lib.rs | 5 ++ .../test_utils/src/test_data.rs | 2 +- crates/xtask/src/main.rs | 71 ++++++++++++++----- 6 files changed, 99 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57de792ae5..70738d6ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,9 @@ rhai = ["bevy_mod_scripting_rhai", "bevy_mod_scripting_functions/rhai_bindings"] ## rune # rune = ["bevy_mod_scripting_rune"] +### Profiling +profile_with_tracy = ["bevy/trace_tracy"] + [dependencies] bevy = { workspace = true } bevy_mod_scripting_core = { workspace = true } @@ -85,6 +88,7 @@ ladfile_builder = { path = "crates/ladfile_builder", version = "0.2.6" } script_integration_test_harness = { workspace = true } test_utils = { workspace = true } libtest-mimic = "0.8" +tracing-tracy = "0.11" [workspace] members = [ diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index e2f394569a..cd7f4f6e24 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,14 +1,18 @@ -use std::{path::PathBuf, time::Duration}; - -use bevy::utils::HashMap; +use bevy::log::tracing_subscriber; +use bevy::log::tracing_subscriber::layer::SubscriberExt; +use bevy::utils::{tracing, HashMap}; use criterion::{criterion_main, measurement::Measurement, BenchmarkGroup, Criterion}; use script_integration_test_harness::{run_lua_benchmark, run_rhai_benchmark}; +use std::{path::PathBuf, sync::LazyLock, time::Duration}; use test_utils::{discover_all_tests, Test}; extern crate bevy_mod_scripting; extern crate script_integration_test_harness; extern crate test_utils; +static ENABLE_PROFILING: LazyLock = + LazyLock::new(|| std::env::var("ENABLE_PROFILING").is_ok()); + pub trait BenchmarkExecutor { fn benchmark_group(&self) -> String; fn benchmark_name(&self) -> String; @@ -89,10 +93,33 @@ fn script_benchmarks(criterion: &mut Criterion) { } } +fn maybe_with_profiler(f: impl Fn(bool)) { + if *ENABLE_PROFILING { + println!("profiling enabled, make sure to run tracy. If using it across windows/WSL you can use something like `tracy-capture.exe -o output.tracy -a localhost` on windows"); + // set global tracing subscriber so bevy doesn't set it itself first + let subscriber = tracing_subscriber::Registry::default(); + let tracy_layer = tracing_tracy::TracyLayer::default(); + + let subscriber = subscriber.with(tracy_layer); + + tracing::subscriber::set_global_default(subscriber).unwrap(); + + let _ = tracing_tracy::client::span!("test2"); + tracing::info_span!("test"); + + f(true); + } else { + f(false); + } +} + pub fn benches() { - let mut criterion: criterion::Criterion<_> = (criterion::Criterion::default()) - .configure_from_args() - .measurement_time(Duration::from_secs(10)); - script_benchmarks(&mut criterion); + maybe_with_profiler(|_profiler| { + let mut criterion: criterion::Criterion<_> = (criterion::Criterion::default()) + .configure_from_args() + .measurement_time(Duration::from_secs(10)); + + script_benchmarks(&mut criterion); + }); } criterion_main!(benches); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index c7d6061f2e..88200962e8 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -103,7 +103,7 @@ impl DynamicScriptFunction { args: I, context: FunctionCallContext, ) -> Result { - profiling::scope!("Dynamic Call ", self.name().to_string()); + profiling::scope!("Dynamic Call ", self.name().deref()); let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? let return_val = (self.func)(context, args); @@ -159,7 +159,7 @@ impl DynamicScriptFunctionMut { args: I, context: FunctionCallContext, ) -> Result { - profiling::scope!("Dynamic Call Mut", self.name().to_string()); + profiling::scope!("Dynamic Call Mut", self.name().deref()); let args = args.into_iter().collect::>(); // should we be inlining call errors into the return value? let mut write = self.func.write(); diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index 81209056eb..d9f79fae25 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -318,6 +318,7 @@ pub fn run_lua_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { + use bevy::utils::tracing; use bevy_mod_scripting_lua::mlua::Function; let plugin = make_test_lua_plugin(); @@ -333,6 +334,7 @@ pub fn run_lua_benchmark( if let Some(pre_bencher) = &pre_bencher { pre_bencher.call::<()>(()).unwrap(); } + tracing::info_span!("profiling_iter", label); c.iter(|| { bencher.call::<()>(()).unwrap(); }) @@ -348,6 +350,7 @@ pub fn run_rhai_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { + use bevy::utils::tracing; use bevy_mod_scripting_rhai::rhai::Dynamic; let plugin = make_test_rhai_plugin(); @@ -367,6 +370,8 @@ pub fn run_rhai_benchmark( .call_fn::(&mut ctxt.scope, &ctxt.ast, "pre_bench", ARGS) .unwrap(); } + tracing::info_span!("profiling_iter", label); + c.iter(|| { let _ = runtime .call_fn::(&mut ctxt.scope, &ctxt.ast, "bench", ARGS) diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index fea49718d7..b2c8e5d8c5 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -346,7 +346,7 @@ pub fn setup_integration_test(init: F) HierarchyPlugin, DiagnosticsPlugin, LogPlugin { - filter: "bevy_mod_scripting_core=debug".to_string(), + filter: "bevy_mod_scripting_core=trace".to_string(), ..Default::default() }, )); diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 9dc073f8ad..c3ad9fd391 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -51,8 +51,7 @@ enum Feature { // Rune, // Profiling - #[strum(serialize = "bevy/trace_tracy")] - Tracy, + ProfileWithTracy, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, strum::EnumIter)] @@ -101,10 +100,10 @@ impl IntoFeatureGroup for Feature { Feature::MluaAsync | Feature::MluaMacros | Feature::MluaSerialize - | Feature::UnsafeLuaModules - | Feature::Tracy => FeatureGroup::ForExternalCrate, - Feature::BevyBindings | Feature::CoreFunctions => FeatureGroup::BMSFeature, - // don't use wildcard here, we want to be explicit + | Feature::UnsafeLuaModules => FeatureGroup::ForExternalCrate, + Feature::BevyBindings | Feature::CoreFunctions | Feature::ProfileWithTracy => { + FeatureGroup::BMSFeature + } // don't use wildcard here, we want to be explicit } } } @@ -119,7 +118,6 @@ impl Default for Features { Feature::Lua54, Feature::CoreFunctions, Feature::BevyBindings, - Feature::Tracy, ]) } } @@ -356,8 +354,19 @@ impl App { cmd.arg("--publish"); } } - Xtasks::Bench {} => { + Xtasks::Bench { + name, + enable_profiling: profile, + } => { cmd.arg("bench"); + + if let Some(name) = name { + cmd.arg("--name").arg(name); + } + + if profile { + cmd.arg("--profile"); + } } } @@ -652,7 +661,14 @@ enum Xtasks { publish: bool, }, /// Runs criterion benchmarks generates json required to be published by bencher and generates html performance report - Bench {}, + Bench { + /// Whether or not to enable tracy profiling + #[clap(long, default_value = "false", help = "Enable tracy profiling")] + enable_profiling: bool, + /// The name argument passed to `cargo bench`, can be used in combination with profile to selectively profile benchmarks + #[clap(long, help = "The name argument passed to `cargo bench`")] + name: Option, + }, } #[derive(Serialize, Clone)] @@ -731,7 +747,10 @@ impl Xtasks { } => Self::codegen(app_settings, output_dir, bevy_features), Xtasks::Install { binary } => Self::install(app_settings, binary), Xtasks::Bencher { publish } => Self::bencher(app_settings, publish), - Xtasks::Bench {} => Self::bench(app_settings), + Xtasks::Bench { + name, + enable_profiling, + } => Self::bench(app_settings, enable_profiling, name), }?; Ok("".into()) @@ -1231,18 +1250,34 @@ impl Xtasks { Ok(()) } - fn bench(app_settings: GlobalArgs) -> Result<()> { + fn bench(app_settings: GlobalArgs, profile: bool, name: Option) -> Result<()> { + log::info!("Profiling enabled: {profile}"); + + let mut features = vec![ + Feature::Lua54, + Feature::Rhai, + Feature::CoreFunctions, + Feature::BevyBindings, + ]; + + if profile { + std::env::set_var("ENABLE_PROFILING", "1"); + // features.push(Feature::BevyTracy); + features.push(Feature::ProfileWithTracy); + } + + let args = if let Some(name) = name { + vec!["--".to_owned(), name] + } else { + vec![] + }; + Self::run_workspace_command( // run with just lua54 - &app_settings.with_features(Features::new(vec![ - Feature::Lua54, - Feature::Rhai, - Feature::CoreFunctions, - Feature::BevyBindings, - ])), + &app_settings.with_features(Features::new(features)), "bench", "Failed to run benchmarks", - Vec::::default(), + args, None, ) .with_context(|| "when executing criterion benchmarks")?; From b4a5c904cbaca2707c347f325d5b12b8953a2e69 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Thu, 27 Mar 2025 09:02:32 +0000 Subject: [PATCH 30/51] chore: Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index fc8d609fb3..b1d0a2aac5 100644 --- a/readme.md +++ b/readme.md @@ -30,7 +30,7 @@ Although Bevy doesn't directly support scripting, efforts are underway to incorp - Documentation generation ## Benchmarks -BMS applies continuous benchmarking and the latest benchmark results can be found over at [bencher.dev](https://bencher.dev/perf/bms). +BMS applies continuous benchmarking and the latest benchmark results can be found over at [bencher.dev](https://bencher.dev/console/projects/bms/plots). The tested scripts themselves are placed in the `assets/benchmarks` directory. From bda2b29749a961b14741aafd96b4df42b65c2a4b Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Thu, 27 Mar 2025 17:40:37 +0000 Subject: [PATCH 31/51] feat: improve tracing spans, add `profile_with_tracy` feature flag (#394) # Summary Overhauls tracing spans allowing neat visualisations like this one: ![image](https://github.com/user-attachments/assets/fcc62e04-7b6e-46e0-9a32-eb14dc4a1459) At the same time, expands benchmarks to cover ground on "marshalling" teritory, which seems to be a large bottleneck in performance. Also adds utilities to xtask to allow seamless tracing of BMS benchmarks with `tracy` --- .../bencher_on_pr_or_fork_closed.yml | 3 +- Cargo.toml | 1 + benches/benchmarks.rs | 163 +++++++++++++++++- crates/bevy_mod_scripting_core/Cargo.toml | 2 +- crates/bevy_mod_scripting_core/src/asset.rs | 8 + .../src/bindings/function/from.rs | 23 ++- .../src/bindings/function/into.rs | 34 ++-- .../src/bindings/function/into_ref.rs | 2 + .../src/bindings/function/namespace.rs | 2 + .../src/bindings/function/script_function.rs | 44 +++-- .../src/bindings/globals/core.rs | 3 + .../src/bindings/globals/mod.rs | 2 + .../src/bindings/query.rs | 5 + .../src/bindings/schedule.rs | 1 + .../src/bindings/script_component.rs | 3 + .../src/bindings/script_system.rs | 6 + .../src/bindings/script_value.rs | 13 ++ .../src/bindings/world.rs | 6 + .../bevy_mod_scripting_core/src/commands.rs | 3 + .../src/docgen/info.rs | 4 + crates/bevy_mod_scripting_core/src/lib.rs | 1 + crates/bevy_mod_scripting_core/src/runtime.rs | 1 + crates/bevy_mod_scripting_core/src/script.rs | 2 + .../src/lib.rs | 60 ++++++- .../src/test_functions.rs | 4 +- crates/testing_crates/test_utils/src/lib.rs | 14 +- crates/xtask/src/main.rs | 32 ++-- tests/script_tests.rs | 2 +- 28 files changed, 369 insertions(+), 75 deletions(-) diff --git a/.github/workflows/bencher_on_pr_or_fork_closed.yml b/.github/workflows/bencher_on_pr_or_fork_closed.yml index 7107cdead4..74d57cf940 100644 --- a/.github/workflows/bencher_on_pr_or_fork_closed.yml +++ b/.github/workflows/bencher_on_pr_or_fork_closed.yml @@ -14,4 +14,5 @@ jobs: bencher archive \ --project bms \ --token '${{ secrets.BENCHER_API_TOKEN }}' \ - --branch "$GITHUB_HEAD_REF" \ No newline at end of file + --branch "$GITHUB_HEAD_REF" + continue-on-error: true \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 70738d6ee6..14e902b0eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ script_integration_test_harness = { workspace = true } test_utils = { workspace = true } libtest-mimic = "0.8" tracing-tracy = "0.11" +regex = "1.11" [workspace] members = [ diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index cd7f4f6e24..d4b96f391f 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,8 +1,19 @@ -use bevy::log::tracing_subscriber; use bevy::log::tracing_subscriber::layer::SubscriberExt; -use bevy::utils::{tracing, HashMap}; +use bevy::log::{tracing_subscriber, Level}; +use bevy::reflect::Reflect; +use bevy::utils::tracing; +use bevy::utils::tracing::span; +use bevy_mod_scripting_core::bindings::{ + FromScript, IntoScript, Mut, Ref, ReflectReference, ScriptValue, Val, +}; use criterion::{criterion_main, measurement::Measurement, BenchmarkGroup, Criterion}; -use script_integration_test_harness::{run_lua_benchmark, run_rhai_benchmark}; +use criterion::{BatchSize, BenchmarkFilter}; +use regex::Regex; +use script_integration_test_harness::test_functions::rand::Rng; +use script_integration_test_harness::{ + perform_benchmark_with_generator, run_lua_benchmark, run_rhai_benchmark, +}; +use std::collections::HashMap; use std::{path::PathBuf, sync::LazyLock, time::Duration}; use test_utils::{discover_all_tests, Test}; @@ -65,10 +76,24 @@ impl BenchmarkExecutor for Test { } } -fn script_benchmarks(criterion: &mut Criterion) { +fn script_benchmarks(criterion: &mut Criterion, filter: Option) { // find manifest dir let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let tests = discover_all_tests(manifest_dir, |p| p.starts_with("benchmarks")); + let tests = discover_all_tests(manifest_dir, |p| { + p.path.starts_with("benchmarks") + && if let Some(filter) = &filter { + let matching = filter.is_match(&p.benchmark_name()); + if !matching { + println!( + "Skipping benchmark: '{}'. due to filter: '{filter}'", + p.benchmark_name() + ); + }; + matching + } else { + true + } + }); // group by benchmark group let mut grouped: HashMap> = @@ -83,9 +108,16 @@ fn script_benchmarks(criterion: &mut Criterion) { } for (group, tests) in grouped { + println!("Running benchmarks for group: {}", group); let mut benchmark_group = criterion.benchmark_group(group); for t in tests { + println!("Running benchmark: {}", t.benchmark_name()); + span!( + Level::INFO, + "Benchmark harness for test", + test_name = &t.benchmark_name() + ); t.execute(&mut benchmark_group); } @@ -104,22 +136,135 @@ fn maybe_with_profiler(f: impl Fn(bool)) { tracing::subscriber::set_global_default(subscriber).unwrap(); - let _ = tracing_tracy::client::span!("test2"); - tracing::info_span!("test"); - f(true); } else { f(false); } } +/// benchmarks measuring conversion time for script values and other things +fn conversion_benchmarks(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("conversions"); + + #[derive(Reflect)] + struct ReflectyVal(pub u32); + + perform_benchmark_with_generator( + "ScriptValue::List", + &|rng, _| { + let mut array = Vec::new(); + for _ in 0..10 { + array.push(ScriptValue::Integer(rng.random())); + } + ScriptValue::List(array) + }, + &|w, i| { + let i = i.into_script(w.clone()).unwrap(); + let _ = Vec::::from_script(i, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); + + perform_benchmark_with_generator( + "ScriptValue::Map", + &|rng, _| { + let mut map = HashMap::default(); + for _ in 0..10 { + map.insert( + rng.random::().to_string(), + ScriptValue::Integer(rng.random()), + ); + } + ScriptValue::Map(map) + }, + &|w, i| { + let i = i.into_script(w.clone()).unwrap(); + let _ = HashMap::::from_script(i, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); + + perform_benchmark_with_generator( + "ScriptValue::Reference::from_into", + &|rng, world| { + let allocator = world.allocator(); + let mut allocator = allocator.write(); + ReflectReference::new_allocated(ReflectyVal(rng.random()), &mut allocator) + }, + &|w, i| { + let i = i.into_script(w.clone()).unwrap(); + let _ = ReflectReference::from_script(i, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); + + perform_benchmark_with_generator( + "Val::from_into", + &|rng, _| Val::new(ReflectyVal(rng.random::())), + &|w, i| { + let v = i.into_script(w.clone()).unwrap(); + Val::::from_script(v, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); + + perform_benchmark_with_generator( + "Ref::from", + &|rng, w| { + Val::new(ReflectyVal(rng.random::())) + .into_script(w) + .unwrap() + }, + &|w, i| { + Ref::::from_script(i, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); + + perform_benchmark_with_generator( + "Mut::from", + &|rng, w| { + Val::new(ReflectyVal(rng.random::())) + .into_script(w) + .unwrap() + }, + &|w, i| { + Mut::::from_script(i, w).unwrap(); + }, + &mut group, + BatchSize::SmallInput, + ); +} + pub fn benches() { maybe_with_profiler(|_profiler| { let mut criterion: criterion::Criterion<_> = (criterion::Criterion::default()) .configure_from_args() .measurement_time(Duration::from_secs(10)); + let arguments = std::env::args() + .skip(1) // the executable name + .filter(|a| !a.starts_with("-")) + .collect::>(); + + // take first argument as .*.* regex for benchmarks + // criterion will already have that as a filter, but we want to make sure we're on the same page + let filter = if let Some(n) = arguments.first() { + println!("using filter: '{n}'"); + let regex = Regex::new(n).unwrap(); + let filter = BenchmarkFilter::Regex(regex.clone()); + criterion = criterion.with_benchmark_filter(filter); + Some(regex) + } else { + None + }; - script_benchmarks(&mut criterion); + script_benchmarks(&mut criterion, filter); + conversion_benchmarks(&mut criterion); }); } criterion_main!(benches); diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index ed0690e6b8..c2b0340d18 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -33,7 +33,7 @@ bevy = { workspace = true, default-features = false, features = ["bevy_asset"] } thiserror = "1.0.31" parking_lot = "0.12.1" dashmap = "6" -smallvec = "1.11" +smallvec = { version = "1.11", features = ["union"] } itertools = "0.13" derivative = "2.2" profiling = { workspace = true } diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index 8891a9264b..5b122ab7f8 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -73,6 +73,7 @@ pub struct ScriptAssetLoader { pub preprocessor: Option Result<(), ScriptError> + Send + Sync>>, } +#[profiling::all_functions] impl AssetLoader for ScriptAssetLoader { type Asset = ScriptAsset; @@ -121,6 +122,7 @@ pub struct ScriptAssetSettings { pub supported_extensions: &'static [&'static str], } +#[profiling::all_functions] impl ScriptAssetSettings { /// Selects the language for a given asset path pub fn select_script_language(&self, path: &AssetPath) -> Language { @@ -178,6 +180,7 @@ pub struct ScriptMetadata { pub language: Language, } +#[profiling::all_functions] impl ScriptMetadataStore { /// Inserts a new metadata entry pub fn insert(&mut self, id: AssetId, meta: ScriptMetadata) { @@ -202,6 +205,7 @@ impl ScriptMetadataStore { } /// Converts incoming asset events, into internal script asset events, also loads and inserts metadata for newly added scripts +#[profiling::function] pub(crate) fn dispatch_script_asset_events( mut events: EventReader>, mut script_asset_events: EventWriter, @@ -256,6 +260,7 @@ pub(crate) fn dispatch_script_asset_events( } /// Listens to [`ScriptAssetEvent::Removed`] events and removes the corresponding script metadata. +#[profiling::function] pub(crate) fn remove_script_metadata( mut events: EventReader, mut asset_path_map: ResMut, @@ -273,6 +278,7 @@ pub(crate) fn remove_script_metadata( /// Listens to [`ScriptAssetEvent`] events and dispatches [`CreateOrUpdateScript`] and [`DeleteScript`] commands accordingly. /// /// Allows for hot-reloading of scripts. +#[profiling::function] pub(crate) fn sync_script_data( mut events: EventReader, script_assets: Res>, @@ -321,6 +327,7 @@ pub(crate) fn sync_script_data( } /// Setup all the asset systems for the scripting plugin and the dependencies +#[profiling::function] pub(crate) fn configure_asset_systems(app: &mut App) -> &mut App { // these should be in the same set as bevy's asset systems // currently this is in the PreUpdate set @@ -348,6 +355,7 @@ pub(crate) fn configure_asset_systems(app: &mut App) -> &mut App { } /// Setup all the asset systems for the scripting plugin and the dependencies +#[profiling::function] pub(crate) fn configure_asset_systems_for_plugin( app: &mut App, ) -> &mut App { diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs index 98c9422020..0852840708 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs @@ -32,6 +32,7 @@ pub trait FromScript { Self: Sized; } +#[profiling::all_functions] impl FromScript for ScriptValue { type This<'w> = Self; fn from_script(value: ScriptValue, _world: WorldGuard) -> Result { @@ -39,6 +40,7 @@ impl FromScript for ScriptValue { } } +#[profiling::all_functions] impl FromScript for () { type This<'w> = Self; fn from_script(_value: ScriptValue, _world: WorldGuard) -> Result { @@ -46,6 +48,7 @@ impl FromScript for () { } } +#[profiling::all_functions] impl FromScript for bool { type This<'w> = Self; #[profiling::function] @@ -70,6 +73,7 @@ impl FromScript for bool { macro_rules! impl_from_with_downcast { ($($ty:ty),*) => { $( + #[profiling::all_functions] impl FromScript for $ty { type This<'w> = Self; #[profiling::function] @@ -92,6 +96,7 @@ impl_from_with_downcast!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, macro_rules! impl_from_stringlike { ($($ty:ty),*) => { $( + #[profiling::all_functions] impl FromScript for $ty { type This<'w> = Self; #[profiling::function] @@ -109,6 +114,7 @@ macro_rules! impl_from_stringlike { impl_from_stringlike!(String, PathBuf, OsString); +#[profiling::all_functions] impl FromScript for char { type This<'w> = Self; #[profiling::function] @@ -133,6 +139,7 @@ impl FromScript for char { } } +#[profiling::all_functions] impl FromScript for ReflectReference { type This<'w> = Self; #[profiling::function] @@ -154,6 +161,7 @@ impl FromScript for ReflectReference { #[derive(Reflect)] pub struct Val(pub T); +#[profiling::all_functions] impl Val { /// Create a new `Val` with the given value. pub fn new(value: T) -> Self { @@ -186,6 +194,7 @@ impl From for Val { } } +#[profiling::all_functions] impl FromScript for Val { type This<'w> = Self; #[profiling::function] @@ -230,6 +239,7 @@ impl Deref for Ref<'_, T> { } } +#[profiling::all_functions] impl FromScript for Ref<'_, T> { type This<'a> = Ref<'a, T>; #[profiling::function] @@ -302,6 +312,7 @@ impl<'a, T> From<&'a mut T> for Mut<'a, T> { } } +#[profiling::all_functions] impl FromScript for Mut<'_, T> { type This<'w> = Mut<'w, T>; #[profiling::function] @@ -337,6 +348,7 @@ impl FromScript for Mut<'_, T> { } } +#[profiling::all_functions] impl FromScript for Option where for<'w> T::This<'w>: Into, @@ -351,6 +363,7 @@ where } } +#[profiling::all_functions] impl FromScript for Vec where for<'w> T::This<'w>: Into, @@ -374,6 +387,7 @@ where } } +#[profiling::all_functions] impl FromScript for [T; N] where for<'w> T::This<'w>: Into, @@ -399,6 +413,7 @@ where } } +#[profiling::all_functions] impl FromScript for DynamicScriptFunctionMut { type This<'w> = Self; #[profiling::function] @@ -416,6 +431,7 @@ impl FromScript for DynamicScriptFunctionMut { } } +#[profiling::all_functions] impl FromScript for DynamicScriptFunction { type This<'w> = Self; #[profiling::function] @@ -433,6 +449,7 @@ impl FromScript for DynamicScriptFunction { } } +#[profiling::all_functions] impl FromScript for std::collections::HashMap where V: FromScript + 'static, @@ -468,6 +485,7 @@ where /// A union of two or more (by nesting unions) types. pub struct Union(Result); +#[profiling::all_functions] impl Union { /// Create a new union with the left value. pub fn new_left(value: T1) -> Self { @@ -479,7 +497,6 @@ impl Union { Union(Err(value)) } - /// Try interpret the union as the left type pub fn into_left(self) -> Result { match self.0 { @@ -495,7 +512,7 @@ impl Union { Ok(l) => Err(l), } } - + /// Map the union to another type pub fn map_both U1, G: Fn(T2) -> U2>(self, f: F, g: G) -> Union { match self.0 { @@ -505,6 +522,7 @@ impl Union { } } +#[profiling::all_functions] impl FromScript for Union where for<'a> T1::This<'a>: Into, @@ -530,6 +548,7 @@ where macro_rules! impl_from_script_tuple { ($($ty:ident),*) => { #[allow(non_snake_case)] + #[profiling::all_functions] impl<$($ty: FromScript),*> FromScript for ($($ty,)*) where Self: 'static, diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs index a841ad714c..fd1fb88e9b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs @@ -1,10 +1,13 @@ //! Implementations of the [`IntoScript`] trait for various types. -use std::{borrow::Cow, collections::HashMap, ffi::OsString, path::PathBuf}; use bevy::reflect::Reflect; +use std::{borrow::Cow, collections::HashMap, ffi::OsString, path::PathBuf}; -use crate::{bindings::{ReflectReference, ScriptValue, WorldGuard}, error::InteropError}; use super::{DynamicScriptFunction, DynamicScriptFunctionMut, Union, Val}; +use crate::{ + bindings::{ReflectReference, ScriptValue, WorldGuard}, + error::InteropError, +}; /// Converts a value into a [`ScriptValue`]. pub trait IntoScript { @@ -26,27 +29,28 @@ impl IntoScript for ScriptValue { } } - +#[profiling::all_functions] impl IntoScript for () { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Unit) } } - +#[profiling::all_functions] impl IntoScript for DynamicScriptFunctionMut { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::FunctionMut(self)) } } +#[profiling::all_functions] impl IntoScript for DynamicScriptFunction { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Function(self)) } } - +#[profiling::all_functions] impl IntoScript for bool { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Bool(self)) @@ -56,6 +60,7 @@ impl IntoScript for bool { macro_rules! impl_into_with_downcast { ($variant:tt as $cast:ty [$($ty:ty),*]) => { $( + #[profiling::all_functions] impl IntoScript for $ty { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::$variant(self as $cast)) @@ -69,10 +74,10 @@ macro_rules! impl_into_with_downcast { impl_into_with_downcast!(Integer as i64 [i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize]); impl_into_with_downcast!(Float as f64 [f32, f64]); - macro_rules! impl_into_stringlike { ($id:ident,[ $(($ty:ty => $conversion:expr)),*]) => { $( + #[profiling::all_functions] impl IntoScript for $ty { fn into_script(self, _world: WorldGuard) -> Result { let $id = self; @@ -94,21 +99,21 @@ impl_into_stringlike!( ] ); - +#[profiling::all_functions] impl IntoScript for &'static str { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::String(Cow::Borrowed(self))) } } - - +#[profiling::all_functions] impl IntoScript for ReflectReference { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Reference(self)) } } +#[profiling::all_functions] impl IntoScript for Val { fn into_script(self, world: WorldGuard) -> Result { let boxed = Box::new(self.0); @@ -121,6 +126,7 @@ impl IntoScript for Val { } } +#[profiling::all_functions] impl IntoScript for Option { fn into_script(self, world: WorldGuard) -> Result { match self { @@ -130,6 +136,7 @@ impl IntoScript for Option { } } +#[profiling::all_functions] impl IntoScript for Vec { fn into_script(self, world: WorldGuard) -> Result { let mut values = Vec::with_capacity(self.len()); @@ -140,6 +147,7 @@ impl IntoScript for Vec { } } +#[profiling::all_functions] impl IntoScript for [T; N] { fn into_script(self, world: WorldGuard) -> Result { let mut values = Vec::with_capacity(N); @@ -150,15 +158,17 @@ impl IntoScript for [T; N] { } } -impl IntoScript for Union { +#[profiling::all_functions] +impl IntoScript for Union { fn into_script(self, world: WorldGuard) -> Result { match self.into_left() { Ok(left) => left.into_script(world), Err(right) => right.into_script(world), } } -} +} +#[profiling::all_functions] impl IntoScript for HashMap { fn into_script(self, world: WorldGuard) -> Result { let mut map = HashMap::new(); @@ -169,6 +179,7 @@ impl IntoScript for HashMap { } } +#[profiling::all_functions] impl IntoScript for InteropError { fn into_script(self, _world: WorldGuard) -> Result { Ok(ScriptValue::Error(self)) @@ -178,6 +189,7 @@ impl IntoScript for InteropError { macro_rules! impl_into_script_tuple { ($( $ty:ident ),* ) => { #[allow(non_snake_case)] + #[profiling::all_functions] impl<$($ty: IntoScript),*> IntoScript for ($($ty,)*) { fn into_script(self, world: WorldGuard) -> Result { let ($($ty,)*) = self; diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs index bdce9f9630..ca29e7c8f5 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into_ref.rs @@ -54,6 +54,7 @@ macro_rules! downcast_into_value { }; } +#[profiling::all_functions] impl IntoScriptRef for ReflectReference { #[profiling::function] fn into_script_ref( @@ -63,6 +64,7 @@ impl IntoScriptRef for ReflectReference { self_.with_reflect(world.clone(), |r| into_script_ref(self_.clone(), r, world))? } } + #[profiling::function] fn into_script_ref( mut self_: ReflectReference, diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs b/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs index e9f4d5b30f..390393abfb 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/namespace.rs @@ -44,6 +44,7 @@ impl IntoNamespace for T { } /// A type which implements [`IntoNamespace`] by always converting to the global namespace +#[profiling::all_functions] impl Namespace { /// Returns the prefix for this namespace pub fn prefix(self) -> Cow<'static, str> { @@ -70,6 +71,7 @@ pub struct NamespaceBuilder<'a, N> { pub world: &'a mut World, } +#[profiling::all_functions] impl<'a, S: IntoNamespace> NamespaceBuilder<'a, S> { /// Creates a new `NamespaceBuilder` that will register functions in the namespace corresponding to the given type /// It will also register the type itself in the type registry. diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index 88200962e8..d0627f67a7 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -44,6 +44,7 @@ pub trait ScriptFunctionMut<'env, Marker> { pub struct FunctionCallContext { language: Language, } + impl FunctionCallContext { /// Create a new FunctionCallContext with the given 1-indexing conversion preference pub const fn new(language: Language) -> Self { @@ -51,15 +52,18 @@ impl FunctionCallContext { } /// Tries to access the world, returning an error if the world is not available + #[profiling::function] pub fn world<'l>(&self) -> Result, InteropError> { ThreadWorldContainer.try_get_world() } /// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed. + #[profiling::function] pub fn convert_to_0_indexed(&self) -> bool { matches!(&self.language, Language::Lua) } /// Gets the scripting language of the caller + #[profiling::function] pub fn language(&self) -> Language { self.language.clone() } @@ -94,6 +98,7 @@ pub struct DynamicScriptFunctionMut { >, } +#[profiling::all_functions] impl DynamicScriptFunction { /// Call the function with the given arguments and caller context. /// @@ -150,6 +155,7 @@ impl DynamicScriptFunction { } } +#[profiling::all_functions] impl DynamicScriptFunctionMut { /// Call the function with the given arguments and caller context. /// @@ -283,6 +289,7 @@ impl DerefMut for AppScriptFunctionRegistry { /// A thread-safe reference counted wrapper around a [`ScriptFunctionRegistry`] pub struct ScriptFunctionRegistryArc(pub Arc>); +#[profiling::all_functions] impl ScriptFunctionRegistryArc { /// claim a read lock on the registry pub fn read(&self) -> RwLockReadGuard { @@ -593,6 +600,7 @@ macro_rules! impl_script_function { let func = (move |caller_context: FunctionCallContext, mut args: VecDeque | { let res: Result = (|| { + profiling::scope!("script function call mechanism"); let received_args_len = args.len(); let expected_arg_count = count!($($param )*); @@ -603,29 +611,37 @@ macro_rules! impl_script_function { world.with_access_scope(||{ let mut current_arg = 0; - $( - current_arg += 1; - let $param = args.pop_front(); - let $param = match $param { - Some($param) => $param, - None => { - if let Some(default) = <$param>::default_value() { - default - } else { - return Err(InteropError::argument_count_mismatch(expected_arg_count,received_args_len)); + $(let $param = { + profiling::scope!("argument conversion", &format!("argument #{}", current_arg)); + current_arg += 1; + let $param = args.pop_front(); + let $param = match $param { + Some($param) => $param, + None => { + if let Some(default) = <$param>::default_value() { + default + } else { + return Err(InteropError::argument_count_mismatch(expected_arg_count,received_args_len)); + } } - } + }; + let $param = <$param>::from_script($param, world.clone()) + .map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?; + $param }; - let $param = <$param>::from_script($param, world.clone()) - .map_err(|e| InteropError::function_arg_conversion_error(current_arg.to_string(), e))?; )* let ret = { - let out = self( $( $context,)? $( $param.into(), )* ); + let out = { + profiling::scope!("function call"); + self( $( $context,)? $( $param.into(), )* ) + }; + $( let $out = out?; let out = $out; )? + profiling::scope!("return type conversion"); out.into_script(world.clone()).map_err(|e| InteropError::function_arg_conversion_error("return value".to_owned(), e)) }; ret diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index 394fe4d6b3..9960205917 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -26,11 +26,13 @@ pub struct CoreScriptGlobalsPlugin; impl Plugin for CoreScriptGlobalsPlugin { fn build(&self, _app: &mut bevy::app::App) {} fn finish(&self, app: &mut bevy::app::App) { + profiling::function_scope!("app finish"); register_static_core_globals(app.world_mut()); register_core_globals(app.world_mut()); } } +#[profiling::function] fn register_static_core_globals(world: &mut bevy::ecs::world::World) { let global_registry = world .get_resource_or_init::() @@ -83,6 +85,7 @@ impl CoreGlobals { >, InteropError, > { + profiling::function_scope!("registering core globals"); let type_registry = guard.type_registry(); let type_registry = type_registry.read(); let mut type_cache = HashMap::::default(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs index a1f9d7d6ab..42f4d2de80 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs @@ -21,6 +21,7 @@ crate::private::export_all_in_modules! { #[derive(Default, Resource, Clone)] pub struct AppScriptGlobalsRegistry(Arc>); +#[profiling::all_functions] impl AppScriptGlobalsRegistry { /// Returns a reference to the inner [`ScriptGlobalsRegistry`]. pub fn read(&self) -> RwLockReadGuard { @@ -69,6 +70,7 @@ pub struct ScriptGlobalsRegistry { dummies: HashMap, ScriptGlobalDummy>, } +#[profiling::all_functions] impl ScriptGlobalsRegistry { /// Gets the global with the given name pub fn get(&self, name: &str) -> Option<&ScriptGlobal> { diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 33e4ce3c02..b13fe9f63a 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -47,6 +47,7 @@ pub struct ScriptResourceRegistration { pub(crate) resource_id: ComponentId, } +#[profiling::all_functions] impl ScriptTypeRegistration { /// Creates a new [`ScriptTypeRegistration`] from a [`TypeRegistration`]. pub fn new(registration: Arc) -> Self { @@ -76,6 +77,8 @@ impl ScriptTypeRegistration { &self.registration } } + +#[profiling::all_functions] impl ScriptResourceRegistration { /// Creates a new [`ScriptResourceRegistration`] from a [`ScriptTypeRegistration`] and a [`ComponentId`]. pub fn new(registration: ScriptTypeRegistration, resource_id: ComponentId) -> Self { @@ -102,6 +105,7 @@ impl ScriptResourceRegistration { } } +#[profiling::all_functions] impl ScriptComponentRegistration { /// Creates a new [`ScriptComponentRegistration`] from a [`ScriptTypeRegistration`] and a [`ComponentId`]. pub fn new(registration: ScriptTypeRegistration, component_id: ComponentId) -> Self { @@ -250,6 +254,7 @@ pub struct ScriptQueryBuilder { without: Vec, } +#[profiling::all_functions] impl ScriptQueryBuilder { /// Adds components to the query. pub fn components(&mut self, components: Vec) -> &mut Self { diff --git a/crates/bevy_mod_scripting_core/src/bindings/schedule.rs b/crates/bevy_mod_scripting_core/src/bindings/schedule.rs index 62bc2b7633..4e04dbdc78 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/schedule.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/schedule.rs @@ -44,6 +44,7 @@ pub struct ScheduleRegistry { schedules: HashMap, } +#[profiling::all_functions] impl ScheduleRegistry { /// Creates a new schedule registry containing all default bevy schedules. pub fn new() -> Self { diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index b1e12f3dc3..74243b29a3 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -37,6 +37,7 @@ impl Component for DynamicComponent { #[derive(Clone, Resource, Default)] pub struct AppScriptComponentRegistry(pub Arc>); +#[profiling::all_functions] impl AppScriptComponentRegistry { /// Reads the underlying registry pub fn read(&self) -> parking_lot::RwLockReadGuard { @@ -55,6 +56,7 @@ pub struct ScriptComponentRegistry { components: HashMap, } +#[profiling::all_functions] impl ScriptComponentRegistry { /// Registers a dynamic script component, possibly overwriting an existing one pub fn register(&mut self, info: DynamicComponentInfo) { @@ -67,6 +69,7 @@ impl ScriptComponentRegistry { } } +#[profiling::all_functions] impl WorldAccessGuard<'_> { /// Registers a dynamic script component, and returns a reference to its registration pub fn register_script_component( diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs index f9be183de8..76d68370c8 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs @@ -49,6 +49,7 @@ impl std::fmt::Debug for ScriptSystemSet { } } +#[profiling::all_functions] impl ScriptSystemSet { /// Creates a new script system set pub fn new(id: impl Into>) -> Self { @@ -56,6 +57,7 @@ impl ScriptSystemSet { } } +#[profiling::all_functions] impl SystemSet for ScriptSystemSet { fn dyn_clone(&self) -> bevy::ecs::label::Box { Box::new(self.clone()) @@ -88,6 +90,7 @@ pub struct ScriptSystemBuilder { is_exclusive: bool, } +#[profiling::all_functions] impl ScriptSystemBuilder { /// Creates a new script system builder pub fn new(name: CallbackLabel, script_id: ScriptId) -> Self { @@ -197,6 +200,7 @@ struct DynamicHandlerContext<'w, P: IntoScriptPluginParams> { runtime_container: &'w RuntimeContainer

, } +#[profiling::all_functions] impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { #[allow( clippy::expect_used, @@ -345,6 +349,7 @@ pub struct DynamicScriptSystem { /// A marker type distinguishing between vanilla and script system types pub struct IsDynamicScriptSystem

(PhantomData P>); +#[profiling::all_functions] impl IntoSystem<(), (), IsDynamicScriptSystem

> for ScriptSystemBuilder { @@ -366,6 +371,7 @@ impl IntoSystem<(), (), IsDynamicScriptSystem

> } } +#[profiling::all_functions] impl System for DynamicScriptSystem

{ type In = (); diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs index c8cbb21e48..7c15721940 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_value.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_value.rs @@ -41,6 +41,7 @@ pub enum ScriptValue { Error(InteropError), } +#[profiling::all_functions] impl ScriptValue { /// Returns the contained string if this is a string variant otherwise returns the original value. pub fn as_string(self) -> Result, Self> { @@ -68,66 +69,77 @@ impl ScriptValue { } } +#[profiling::all_functions] impl From<()> for ScriptValue { fn from(_: ()) -> Self { ScriptValue::Unit } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: bool) -> Self { ScriptValue::Bool(value) } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: i64) -> Self { ScriptValue::Integer(value) } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: f64) -> Self { ScriptValue::Float(value) } } +#[profiling::all_functions] impl From<&'static str> for ScriptValue { fn from(value: &'static str) -> Self { ScriptValue::String(value.into()) } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: String) -> Self { ScriptValue::String(value.into()) } } +#[profiling::all_functions] impl From> for ScriptValue { fn from(value: Cow<'static, str>) -> Self { ScriptValue::String(value) } } +#[profiling::all_functions] impl From> for ScriptValue { fn from(value: Vec) -> Self { ScriptValue::List(value) } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: ReflectReference) -> Self { ScriptValue::Reference(value) } } +#[profiling::all_functions] impl From for ScriptValue { fn from(value: InteropError) -> Self { ScriptValue::Error(value) } } +#[profiling::all_functions] impl> From> for ScriptValue { fn from(value: Option) -> Self { match value { @@ -137,6 +149,7 @@ impl> From> for ScriptValue { } } +#[profiling::all_functions] impl, E: Into> From> for ScriptValue { fn from(value: Result) -> Self { match value { diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 79d082a6c1..2b2c749d51 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -93,6 +93,7 @@ impl std::fmt::Debug for WorldAccessGuardInner<'_> { } } +#[profiling::all_functions] impl WorldAccessGuard<'static> { /// Shortens the lifetime of the guard to the given lifetime. pub(crate) fn shorten_lifetime<'w>(self) -> WorldGuard<'w> { @@ -243,6 +244,11 @@ impl<'w> WorldAccessGuard<'w> { self.inner.accesses.list_accesses() } + /// Should only really be used for testing purposes + pub unsafe fn release_all_accesses(&self) { + self.inner.accesses.release_all_accesses(); + } + /// Returns the number of accesses currently held. pub fn access_len(&self) -> usize { self.inner.accesses.count_accesses() diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 667661f8a8..02c49bb3a8 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -94,6 +94,7 @@ pub struct CreateOrUpdateScript { _ph: std::marker::PhantomData, } +#[profiling::all_functions] impl CreateOrUpdateScript

{ /// Creates a new CreateOrUpdateScript command with the given ID, content and asset pub fn new(id: ScriptId, content: Box<[u8]>, asset: Option>) -> Self { @@ -169,6 +170,7 @@ impl CreateOrUpdateScript

{ } } +#[profiling::all_functions] impl Command for CreateOrUpdateScript

{ fn apply(self, world: &mut bevy::prelude::World) { with_handler_system_state(world, |guard, handler_ctxt: &mut HandlerContext

| { @@ -293,6 +295,7 @@ impl RemoveStaticScript { } } +#[profiling::all_functions] impl Command for RemoveStaticScript { fn apply(self, world: &mut bevy::prelude::World) { let mut static_scripts = world.get_resource_or_init::(); diff --git a/crates/bevy_mod_scripting_core/src/docgen/info.rs b/crates/bevy_mod_scripting_core/src/docgen/info.rs index 46dcc7e1a9..1bb4c25fa1 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/info.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/info.rs @@ -34,6 +34,7 @@ impl Default for FunctionInfo { } } +#[profiling::all_functions] impl FunctionInfo { /// Create a new function info with default values. pub fn new() -> Self { @@ -108,6 +109,7 @@ pub struct FunctionArgInfo { pub type_info: Option, } +#[profiling::all_functions] impl FunctionArgInfo { /// Create a new function argument info with a name. pub fn with_name(mut self, name: Cow<'static, str>) -> Self { @@ -145,6 +147,7 @@ impl Default for FunctionReturnInfo { } } +#[profiling::all_functions] impl FunctionReturnInfo { /// Create a new function return info for a specific type. pub fn new_for() -> Self { @@ -157,6 +160,7 @@ impl FunctionReturnInfo { macro_rules! impl_documentable { ($( $param:ident ),*) => { + #[profiling::all_functions] impl<$($param,)* F, O> GetFunctionInfo O> for F where F: Fn($($param),*) -> O, diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index a244130b61..dd16b844f1 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -117,6 +117,7 @@ impl Default for ScriptingPlugin

{ } } +#[profiling::all_functions] impl Plugin for ScriptingPlugin

{ fn build(&self, app: &mut bevy::prelude::App) { app.insert_resource(self.runtime_settings.clone()) diff --git a/crates/bevy_mod_scripting_core/src/runtime.rs b/crates/bevy_mod_scripting_core/src/runtime.rs index 6ddb7050b1..359a7a0c97 100644 --- a/crates/bevy_mod_scripting_core/src/runtime.rs +++ b/crates/bevy_mod_scripting_core/src/runtime.rs @@ -52,6 +52,7 @@ impl Default for RuntimeContainer

{ } } +#[profiling::function] pub(crate) fn initialize_runtime( runtime: ResMut>, settings: Res>, diff --git a/crates/bevy_mod_scripting_core/src/script.rs b/crates/bevy_mod_scripting_core/src/script.rs index c4fd82a5ea..d5b0fe46ae 100644 --- a/crates/bevy_mod_scripting_core/src/script.rs +++ b/crates/bevy_mod_scripting_core/src/script.rs @@ -39,6 +39,7 @@ pub struct Scripts { pub(crate) scripts: HashMap>, } +#[profiling::all_functions] impl Scripts

{ /// Inserts a script into the collection pub fn insert(&mut self, script: Script

) { @@ -113,6 +114,7 @@ pub struct StaticScripts { pub(crate) scripts: HashSet, } +#[profiling::all_functions] impl StaticScripts { /// Inserts a static script into the collection pub fn insert>(&mut self, script: S) { diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index d9f79fae25..eb3247cf91 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -20,7 +20,9 @@ use bevy::{ }; use bevy_mod_scripting_core::{ asset::ScriptAsset, - bindings::{pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldGuard}, + bindings::{ + pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldAccessGuard, WorldGuard, + }, callback_labels, error::{InteropError, ScriptError}, event::{IntoCallbackLabel, ScriptErrorEvent}, @@ -30,7 +32,9 @@ use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, }; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; -use test_functions::register_test_functions; +use criterion::{measurement::Measurement, BatchSize}; +use rand::SeedableRng; +use test_functions::{register_test_functions, RNG}; use test_utils::test_data::setup_integration_test; fn dummy_update_system() {} @@ -318,7 +322,7 @@ pub fn run_lua_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { - use bevy::utils::tracing; + use bevy::{log::Level, utils::tracing}; use bevy_mod_scripting_lua::mlua::Function; let plugin = make_test_lua_plugin(); @@ -334,8 +338,8 @@ pub fn run_lua_benchmark( if let Some(pre_bencher) = &pre_bencher { pre_bencher.call::<()>(()).unwrap(); } - tracing::info_span!("profiling_iter", label); c.iter(|| { + tracing::event!(Level::TRACE, "profiling_iter {}", label); bencher.call::<()>(()).unwrap(); }) }); @@ -350,7 +354,7 @@ pub fn run_rhai_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { - use bevy::utils::tracing; + use bevy::{log::Level, utils::tracing}; use bevy_mod_scripting_rhai::rhai::Dynamic; let plugin = make_test_rhai_plugin(); @@ -370,9 +374,9 @@ pub fn run_rhai_benchmark( .call_fn::(&mut ctxt.scope, &ctxt.ast, "pre_bench", ARGS) .unwrap(); } - tracing::info_span!("profiling_iter", label); c.iter(|| { + tracing::event!(Level::TRACE, "profiling_iter {}", label); let _ = runtime .call_fn::(&mut ctxt.scope, &ctxt.ast, "bench", ARGS) .unwrap(); @@ -447,7 +451,49 @@ where } state.apply(app.world_mut()); if timer.elapsed() > Duration::from_secs(30) { - return Err("Timeout after 30 seconds".into()); + return Err("Timeout after 30 seconds, could not load script".into()); } } } + +pub fn perform_benchmark_with_generator< + M: Measurement, + I, + G: Fn(&mut rand_chacha::ChaCha12Rng, WorldAccessGuard) -> I, + B: Fn(WorldAccessGuard, I), +>( + label: &str, + generator: &G, + bench_fn: &B, + group: &mut criterion::BenchmarkGroup, + batch_size: BatchSize, +) { + let mut world = std::mem::take(setup_integration_test(|_, _| {}).world_mut()); + + let world_guard = WorldAccessGuard::new_exclusive(&mut world); + let mut rng_guard = RNG.lock().unwrap(); + *rng_guard = rand_chacha::ChaCha12Rng::from_seed([42u8; 32]); + drop(rng_guard); + group.bench_function(label, |c| { + c.iter_batched( + || { + let mut rng_guard = RNG.lock().unwrap(); + unsafe { world_guard.release_all_accesses() }; + { + let allocator = world_guard.allocator(); + let mut allocator = allocator.write(); + allocator.clean_garbage_allocations(); + } + ( + generator(&mut rng_guard, world_guard.clone()), + world_guard.clone(), + ) + }, + |(i, w)| { + bevy::utils::tracing::event!(bevy::log::Level::TRACE, "profiling_iter {}", label); + bench_fn(w, i) + }, + batch_size, + ); + }); +} diff --git a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs index 910ee6c412..ad658c0a62 100644 --- a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs @@ -27,11 +27,13 @@ use rand_chacha::ChaCha12Rng; use test_utils::test_data::EnumerateTestComponents; // lazy lock rng state -static RNG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { +pub static RNG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { let seed = [42u8; 32]; Mutex::new(ChaCha12Rng::from_seed(seed)) }); +pub use rand; + pub fn register_test_functions(world: &mut App) { let world = world.world_mut(); NamespaceBuilder::::new_unregistered(world) diff --git a/crates/testing_crates/test_utils/src/lib.rs b/crates/testing_crates/test_utils/src/lib.rs index ed79df1316..b371f235bb 100644 --- a/crates/testing_crates/test_utils/src/lib.rs +++ b/crates/testing_crates/test_utils/src/lib.rs @@ -45,7 +45,7 @@ fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { Ok(()) } -pub fn discover_all_tests(manifest_dir: PathBuf, filter: impl Fn(&Path) -> bool) -> Vec { +pub fn discover_all_tests(manifest_dir: PathBuf, filter: impl Fn(&Test) -> bool) -> Vec { let assets_root = manifest_dir.join("assets"); let mut test_files = Vec::new(); visit_dirs(&assets_root, &mut |entry| { @@ -60,13 +60,15 @@ pub fn discover_all_tests(manifest_dir: PathBuf, filter: impl Fn(&Path) -> bool) { // only take the path from the assets bit let relative = path.strip_prefix(&assets_root).unwrap(); - if !filter(relative) { - return; - } - test_files.push(Test { + let test = Test { path: relative.to_path_buf(), kind, - }); + }; + + if !filter(&test) { + return; + } + test_files.push(test); } }) .unwrap(); diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index c3ad9fd391..38c38d27d0 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1417,6 +1417,7 @@ impl Xtasks { .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::inherit()) .args(["plot", "list", "bms"]) + .args(["--per-page", "255"]) .args(["--token", &token.clone().unwrap_or_default()]) .output() .with_context(|| "Could not list plots")?; @@ -1448,31 +1449,18 @@ impl Xtasks { bail!("Failed to delete plot: {:?}", bencher_cmd); } } - let testbeds = Command::new("bencher") - .arg("testbed") - .args(["list", "bms"]) - .args(["--token", &token.clone().unwrap_or_default()]) - .output() - .with_context(|| "Could not list testbeds")?; const MAIN_BRANCH_UUID: &str = "1d70a4e3-d416-43fc-91bd-4b1c8f9e9580"; const LATENCY_MEASURE_UUID: &str = "6820b034-5163-4cdd-95f5-5640dd0ff298"; - - if !testbeds.status.success() { - bail!("Failed to list testbeds: {:?}", testbeds); - } - - let mut testbeds = parse_list_of_dicts(testbeds.stdout) - .with_context(|| "reading testbeds")? - .into_iter() - .map(|p| { - let name = p.get("name").expect("no name in testbed"); - let uuid = p.get("uuid").expect("no uuid in testbed"); - (name.clone(), uuid.clone()) - }) - .filter(|(name, _)| name.contains("gha")) - .collect::>(); - testbeds.sort(); + const LINUX_GHA_TESTBED: &str = "467e8580-a67a-435e-a602-b167541f332c"; + const MACOS_GHA_TESTBAD: &str = "f8aab940-27d2-4b52-93df-4518fe68abfb"; + const WINDOWS_GHA_TESTBED: &str = "be8ff546-31d3-40c4-aacc-763e5e8a09c4"; + + let testbeds = [ + ("linux-gha", LINUX_GHA_TESTBED), + ("macos-gha", MACOS_GHA_TESTBAD), + ("windows-gha", WINDOWS_GHA_TESTBED), + ]; let group_to_benchmark_map: HashMap<_, Vec<_>> = benchmarks diff --git a/tests/script_tests.rs b/tests/script_tests.rs index 73c7c3995c..cbb451abd6 100644 --- a/tests/script_tests.rs +++ b/tests/script_tests.rs @@ -46,7 +46,7 @@ fn main() { let args = Arguments::from_args(); let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let tests = discover_all_tests(manifest_dir, |p| p.starts_with("tests")) + let tests = discover_all_tests(manifest_dir, |p| p.path.starts_with("tests")) .into_iter() .map(|t| Trial::test(t.name(), move || t.execute())) .collect::>(); From d21f2f243100fe6e8f6c3cd751a8e8e5847500d3 Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 27 Mar 2025 18:21:52 +0000 Subject: [PATCH 32/51] chore: make benchmark publish happen on all os's again --- crates/xtask/src/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 38c38d27d0..0a5c7ffb8f 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -712,10 +712,8 @@ impl Xtasks { let mut rows = Vec::default(); for os in ::VARIANTS { for row in output.iter() { - let step_should_run_on_main_os = matches!( - row.subcmd, - Xtasks::Build | Xtasks::Docs { .. } | Xtasks::Bencher { .. } - ); + let step_should_run_on_main_os = + matches!(row.subcmd, Xtasks::Build | Xtasks::Docs { .. }); let is_coverage_step = row.global_args.coverage; if !os.is_main_os() && step_should_run_on_main_os { From 517a08ca2ec476f9b5e37cbeb021076f6db49f1e Mon Sep 17 00:00:00 2001 From: makspll Date: Thu, 27 Mar 2025 19:10:26 +0000 Subject: [PATCH 33/51] chore: disable profile with tracy for most checks --- crates/xtask/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 0a5c7ffb8f..28400b0357 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -142,6 +142,10 @@ impl Features { ) } + fn without(self, feature: Feature) -> Self { + Self(self.0.into_iter().filter(|f| *f != feature).collect()) + } + fn to_cargo_args(&self) -> Vec { if self.0.is_empty() { vec![] @@ -1625,7 +1629,7 @@ impl Xtasks { let default_args = app_settings .clone() - .with_features(Features::all_features()) + .with_features(Features::all_features().without(Feature::ProfileWithTracy)) .with_profile( app_settings .profile From bf310d5327bb1dcd545b71b5ea29b677db6653e9 Mon Sep 17 00:00:00 2001 From: makspll Date: Fri, 28 Mar 2025 09:14:35 +0000 Subject: [PATCH 34/51] chore: avoid problems with locking xtask executable on windows --- crates/xtask/src/main.rs | 47 ++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 28400b0357..d4f8fa2b51 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -752,7 +752,10 @@ impl Xtasks { Xtasks::Bench { name, enable_profiling, - } => Self::bench(app_settings, enable_profiling, name), + } => { + let _ = Self::bench(app_settings, enable_profiling, name, false)?; + Ok(()) + } }?; Ok("".into()) @@ -840,6 +843,7 @@ impl Xtasks { context: &str, add_args: I, dir: Option<&Path>, + capture_streams_in_output: bool, ) -> Result { let coverage_mode = app_settings .coverage @@ -898,10 +902,12 @@ impl Xtasks { }; let mut cmd = Command::new("cargo"); - cmd.args(args) - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()) - .current_dir(working_dir); + cmd.args(args).current_dir(working_dir); + + if !capture_streams_in_output { + cmd.stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + } info!("Using command: {:?}", cmd); @@ -925,6 +931,7 @@ impl Xtasks { "Failed to build workspace", vec!["--all-targets"], None, + false, )?; Ok(()) } @@ -943,6 +950,7 @@ impl Xtasks { "Failed to run clippy", clippy_args, None, + false, )?; if ide_mode { @@ -956,6 +964,7 @@ impl Xtasks { "Failed to run cargo fmt", vec!["--all", "--", "--check"], None, + false, )?; Ok(()) @@ -983,6 +992,7 @@ impl Xtasks { "Failed to run clippy on codegen crate", clippy_args, None, + false, )?; // TODO: for now do nothing, it's difficult to get rust analyzer to accept the nightly version @@ -1124,6 +1134,7 @@ impl Xtasks { "-v", ], Some(&bevy_dir), + false, )?; // collect @@ -1142,6 +1153,7 @@ impl Xtasks { "-v", ], Some(&bevy_dir), + false, )?; Ok(()) @@ -1234,6 +1246,7 @@ impl Xtasks { "Failed to build crates.io docs", args, None, + false, )?; } @@ -1252,7 +1265,12 @@ impl Xtasks { Ok(()) } - fn bench(app_settings: GlobalArgs, profile: bool, name: Option) -> Result<()> { + fn bench( + app_settings: GlobalArgs, + profile: bool, + name: Option, + capture_streams_in_output: bool, + ) -> Result { log::info!("Profiling enabled: {profile}"); let mut features = vec![ @@ -1274,17 +1292,18 @@ impl Xtasks { vec![] }; - Self::run_workspace_command( + let output = Self::run_workspace_command( // run with just lua54 &app_settings.with_features(Features::new(features)), "bench", "Failed to run benchmarks", args, None, + capture_streams_in_output, ) .with_context(|| "when executing criterion benchmarks")?; - Ok(()) + Ok(output) } fn bencher(app_settings: GlobalArgs, publish: bool) -> Result<()> { @@ -1320,6 +1339,13 @@ impl Xtasks { let token = std::env::var("BENCHER_API_TOKEN").ok(); + // first of all run bench, and save output to a file + + let result = Self::bench(app_settings, false, None, true)?; + let bench_file_path = PathBuf::from("./bencher_output.txt"); + let mut file = std::fs::File::create(&bench_file_path)?; + file.write_all(&result.stdout)?; + let mut bencher_cmd = Command::new("bencher"); bencher_cmd .stdout(std::process::Stdio::inherit()) @@ -1347,7 +1373,8 @@ impl Xtasks { bencher_cmd .args(["--adapter", "rust_criterion"]) - .arg("cargo xtask bench"); + .arg("--file") + .arg(bench_file_path); log::info!("Running bencher command: {:?}", bencher_cmd); @@ -1547,6 +1574,7 @@ impl Xtasks { "Failed to run tests", test_args, None, + false, )?; // generate coverage report and lcov file @@ -1880,6 +1908,7 @@ impl Xtasks { "Failed to run example", vec!["--example", example.as_str()], None, + false, )?; Ok(()) From c507fab9c4c3710df13ab4d1733be765060ff024 Mon Sep 17 00:00:00 2001 From: makspll Date: Fri, 28 Mar 2025 09:47:24 +0000 Subject: [PATCH 35/51] chore: set log level when benching to something less likely to impact benchmarks --- crates/testing_crates/test_utils/src/test_data.rs | 5 ++++- crates/xtask/src/main.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index b2c8e5d8c5..030f7dc020 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -340,13 +340,16 @@ pub fn setup_integration_test(init: F) // first setup all normal test components and resources let mut app = setup_app(init); + let log_level = + std::env::var("RUST_LOG").unwrap_or_else(|_| "bevy_mod_scripting_core=debug".to_string()); + app.add_plugins(( MinimalPlugins, AssetPlugin::default(), HierarchyPlugin, DiagnosticsPlugin, LogPlugin { - filter: "bevy_mod_scripting_core=trace".to_string(), + filter: log_level, ..Default::default() }, )); diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index d4f8fa2b51..e30858c972 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1284,6 +1284,8 @@ impl Xtasks { std::env::set_var("ENABLE_PROFILING", "1"); // features.push(Feature::BevyTracy); features.push(Feature::ProfileWithTracy); + } else { + std::env::set_var("RUST_LOG", "bevy_mod_scripting=error"); } let args = if let Some(name) = name { From d38ed374e23e7839d451b4629f4315cd81cc979f Mon Sep 17 00:00:00 2001 From: makspll Date: Fri, 28 Mar 2025 12:47:32 +0000 Subject: [PATCH 36/51] chore: revert change which slipped into wrong PR --- crates/bevy_mod_scripting_core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index c2b0340d18..ed0690e6b8 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -33,7 +33,7 @@ bevy = { workspace = true, default-features = false, features = ["bevy_asset"] } thiserror = "1.0.31" parking_lot = "0.12.1" dashmap = "6" -smallvec = { version = "1.11", features = ["union"] } +smallvec = "1.11" itertools = "0.13" derivative = "2.2" profiling = { workspace = true } From 96a088c13261821a3f1ce9f387f879eb443b9647 Mon Sep 17 00:00:00 2001 From: makspll Date: Fri, 28 Mar 2025 13:04:20 +0000 Subject: [PATCH 37/51] chore: randomize access count in benchmarks --- .../src/lib.rs | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index eb3247cf91..fc265369fe 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -10,18 +10,20 @@ use bevy::{ app::{Last, Plugin, PostUpdate, Startup, Update}, asset::{AssetServer, Handle}, ecs::{ + component::Component, event::{Event, Events}, schedule::{IntoSystemConfigs, SystemConfigs}, - system::{IntoSystem, Local, Res, SystemState}, + system::{IntoSystem, Local, Res, Resource, SystemState}, world::{FromWorld, Mut}, }, prelude::{Entity, World}, - reflect::TypeRegistry, + reflect::{Reflect, TypeRegistry}, }; use bevy_mod_scripting_core::{ asset::ScriptAsset, bindings::{ - pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldAccessGuard, WorldGuard, + pretty_print::DisplayWithWorld, script_value::ScriptValue, ReflectAccessId, + WorldAccessGuard, WorldGuard, }, callback_labels, error::{InteropError, ScriptError}, @@ -33,7 +35,7 @@ use bevy_mod_scripting_core::{ }; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; use criterion::{measurement::Measurement, BatchSize}; -use rand::SeedableRng; +use rand::{Rng, SeedableRng}; use test_functions::{register_test_functions, RNG}; use test_utils::test_data::setup_integration_test; @@ -468,7 +470,23 @@ pub fn perform_benchmark_with_generator< group: &mut criterion::BenchmarkGroup, batch_size: BatchSize, ) { + #[derive(Reflect, Component, Resource)] + struct Fake1; + #[derive(Reflect, Component, Resource)] + struct Fake2; + #[derive(Reflect, Resource)] + struct Fake3; + #[derive(Reflect, Resource)] + struct Fake4; + #[derive(Reflect, Resource)] + struct Fake5; + let mut world = std::mem::take(setup_integration_test(|_, _| {}).world_mut()); + let f1 = world.register_component::(); + let f2 = world.register_component::(); + let f3 = world.register_resource::(); + let f4 = world.register_resource::(); + let f5 = world.register_resource::(); let world_guard = WorldAccessGuard::new_exclusive(&mut world); let mut rng_guard = RNG.lock().unwrap(); @@ -484,6 +502,20 @@ pub fn perform_benchmark_with_generator< let mut allocator = allocator.write(); allocator.clean_garbage_allocations(); } + + // lock a random amount of fake components/resources, to make benchmarks more natural + for _ in 0..rng_guard.random_range(0..=5) { + // pick random component + match rng_guard.random_range(0..=4) { + 0 => world_guard.claim_write_access(ReflectAccessId::for_component_id(f1)), + 1 => world_guard.claim_write_access(ReflectAccessId::for_component_id(f2)), + 2 => world_guard.claim_write_access(ReflectAccessId::for_component_id(f3)), + 3 => world_guard.claim_write_access(ReflectAccessId::for_component_id(f4)), + 4 => world_guard.claim_write_access(ReflectAccessId::for_component_id(f5)), + _ => false, + }; + } + ( generator(&mut rng_guard, world_guard.clone()), world_guard.clone(), From 158cde22a944319497bfe6c4af67870784943d8d Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Fri, 28 Mar 2025 19:23:13 +0000 Subject: [PATCH 38/51] feat: allow the conversion of lua functions into `ScriptValue` via `DynamicScriptFunction` (#396) # Summary Because `mlua::Function`'s store a pointer to their lua context, it's possible to call these functions without direct access to a lua context. This allows us to convert a lua function into a `DynamicScriptFunction` which can be marshalled as a `ScriptValue` and arbitrarilly stored on the rust side. However this will cause issues if your script contexts get unloaded, in the form of panics most likely --- ...can_convert_lua_fn_to_dynamic_function.lua | 10 ++ .../src/bindings/script_value.rs | 17 +++- .../src/test_functions.rs | 5 +- other-release-plz.toml | 96 ------------------- 4 files changed, 29 insertions(+), 99 deletions(-) create mode 100644 assets/tests/callback_to_dynamic_function/can_convert_lua_fn_to_dynamic_function.lua delete mode 100644 other-release-plz.toml diff --git a/assets/tests/callback_to_dynamic_function/can_convert_lua_fn_to_dynamic_function.lua b/assets/tests/callback_to_dynamic_function/can_convert_lua_fn_to_dynamic_function.lua new file mode 100644 index 0000000000..0135c9c778 --- /dev/null +++ b/assets/tests/callback_to_dynamic_function/can_convert_lua_fn_to_dynamic_function.lua @@ -0,0 +1,10 @@ +local my_fn = into_script_function( + function(string, list) + print(string, list) + assert(string == "test", "string is not test got: " .. string) + assert(list[1] == "test", "list[1] is not test, got: ".. list[1]) + end +) + +my_fn("test", {"test"}) + diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 888f4e821a..bd296ef8a7 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -2,10 +2,11 @@ use super::reference::LuaReflectReference; use bevy_mod_scripting_core::{ asset::Language, bindings::{function::script_function::FunctionCallContext, script_value::ScriptValue}, + error::InteropError, }; use mlua::{FromLua, IntoLua, Value, Variadic}; use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, ops::{Deref, DerefMut}, }; @@ -51,6 +52,20 @@ impl FromLua for LuaScriptValue { Value::Integer(i) => ScriptValue::Integer(i as i64), Value::Number(n) => ScriptValue::Float(n), Value::String(s) => ScriptValue::String(s.to_str()?.to_owned().into()), + Value::Function(f) => ScriptValue::Function( + (move |_context: FunctionCallContext, args: VecDeque| { + println!("Lua function called with args: {:?}", args); + match f.call::( + args.into_iter() + .map(LuaScriptValue) + .collect::>(), + ) { + Ok(v) => v.0, + Err(e) => ScriptValue::Error(InteropError::external_error(Box::new(e))), + } + }) + .into(), + ), Value::Table(table) => { // check the key types, if strings then it's a map let mut iter = table.pairs::(); diff --git a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs index ad658c0a62..138a46373f 100644 --- a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs @@ -17,8 +17,8 @@ use bevy_mod_scripting_core::{ script_function::{DynamicScriptFunctionMut, FunctionCallContext}, }, pretty_print::DisplayWithWorld, - ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, - ScriptTypeRegistration, ScriptValue, + DynamicScriptFunction, ReflectReference, ScriptComponentRegistration, + ScriptResourceRegistration, ScriptTypeRegistration, ScriptValue, }, error::InteropError, }; @@ -144,6 +144,7 @@ pub fn register_test_functions(world: &mut App) { "noop_4_args", |_a: ScriptValue, _b: ScriptValue, _c: ScriptValue, _d: ScriptValue| {}, ) + .register("into_script_function", |f: DynamicScriptFunction| f) .register( "assert_str_eq", |s1: String, s2: String, reason: Option| { diff --git a/other-release-plz.toml b/other-release-plz.toml deleted file mode 100644 index 86f0b8d00a..0000000000 --- a/other-release-plz.toml +++ /dev/null @@ -1,96 +0,0 @@ -[workspace] -dependencies_update = false -publish_timeout = "30m" -git_release_enable = false -git_tag_enable = false -git_release_body = """ -{{ changelog }} -{% if remote.contributors %} -### Contributors -{% for contributor in remote.contributors | unique(attribute="username") %} -* @{{ contributor.username }} -{% endfor %} -{% endif %} -""" - -[changelog] -commit_parsers = [ - # dont include chore changes in changelog - { message = "^chore.*", skip = true }, - { message = "^test.*", skip = true }, - { message = "^docs.*", skip = true }, - { message = "^feat", group = "added" }, - { message = "^changed", group = "changed" }, - { message = "^deprecated", group = "deprecated" }, - { message = "^fix", group = "fixed" }, - { message = "^security", group = "security" }, - { message = "^.*", group = "other" }, -] - -[[package]] -name = "bevy_mod_scripting" -publish_features = ["lua54"] -version_group = "main" -git_release_latest = true -git_release_enable = true -git_tag_enable = true -git_tag_name = "v{{ version }}" -git_release_name = "v{{ version }}" - -changelog_include = [ - "bevy_mod_scripting_lua", - "bevy_mod_scripting_core", - "bevy_mod_scripting_rhai", - # "bevy_mod_scripting_rune", - "bevy_mod_scripting_functions", -] - -[[package]] -name = "bevy_mod_scripting_lua" -publish_features = ["lua54"] -version_group = "main" - -[[package]] -name = "bevy_mod_scripting_core" -version_group = "main" - -[[package]] -name = "bevy_mod_scripting_derive" -version_group = "main" - -[[package]] -name = "bevy_mod_scripting_rhai" -version_group = "main" - -# [[package]] -# name = "bevy_mod_scripting_rune" -# version_group = "main" - -[[package]] -name = "bevy_mod_scripting_functions" -version_group = "main" - -[[package]] -name = "ladfile" -git_release_enable = true -git_release_latest = false -git_tag_enable = true -git_tag_name = "v{{ version }}-ladfile" -git_release_name = "v{{ version }}-ladfile" - -[[package]] -name = "ladfile_builder" -git_release_enable = true -git_release_latest = false -git_tag_enable = true -git_tag_name = "v{{ version }}-ladfile_builder" -git_release_name = "v{{ version }}-ladfile_builder" - -[[package]] -changelog_update = true -name = "mdbook_lad_preprocessor" -git_release_enable = true -git_release_latest = false -git_tag_enable = true -git_tag_name = "v{{ version }}-mdbook_lad_preprocessor" -git_release_name = "v{{ version }}-mdbook_lad_preprocessor" From 56d62b805f4d063c88b79a36d3bb778c73d64e83 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 12:12:47 +0000 Subject: [PATCH 39/51] feat: optimize `get` and `set` functions, add `MagicFunctions` sub-registry (#397) # Summary Attempts to speed up the get and set operations by: - No longer looking for `get` and `set` overrides on `ReflectReference`'s - Extracting those functions from the function registry and keeping them as `MagicFunctions` which do not require script value conversions --- crates/bevy_mod_scripting_core/Cargo.toml | 4 +- .../src/bindings/function/magic_functions.rs | 119 ++++++++++++++++++ .../src/bindings/function/mod.rs | 18 +-- .../src/bindings/function/script_function.rs | 4 +- .../src/bindings/reference.rs | 4 +- .../bevy_mod_scripting_functions/src/core.rs | 74 +---------- .../src/bindings/reference.rs | 31 ++--- .../src/bindings/reference.rs | 44 +++---- .../AddingLanguages/necessary-features.md | 4 + .../Summary/controlling-script-bindings.md | 6 +- 10 files changed, 178 insertions(+), 130 deletions(-) create mode 100644 crates/bevy_mod_scripting_core/src/bindings/function/magic_functions.rs diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index ed0690e6b8..450fa51a85 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -25,7 +25,9 @@ mlua_impls = ["mlua"] rhai_impls = ["rhai"] [dependencies] -mlua = { version = "0.10", default-features = false, optional = true } +mlua = { version = "0.10", default-features = false, optional = true, features = [ + "lua54", +] } rhai = { version = "1.21", default-features = false, features = [ "sync", ], optional = true } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/magic_functions.rs b/crates/bevy_mod_scripting_core/src/bindings/function/magic_functions.rs new file mode 100644 index 0000000000..fd54390373 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/bindings/function/magic_functions.rs @@ -0,0 +1,119 @@ +//! All the switchable special functions used by language implementors +use super::{FromScriptRef, FunctionCallContext, IntoScriptRef}; +use crate::{ + bindings::{ReflectReference, ReflectionPathExt, ScriptValue}, + error::InteropError, + reflection_extensions::TypeIdExtensions, +}; +use bevy::reflect::{ParsedPath, PartialReflect}; + +/// A list of magic methods, these only have one replacable implementation, and apply to all `ReflectReferences`. +/// It's up to the language implementer to call these in the right order (after any type specific overrides). +/// +/// These live in a separate mini registry since they are so commonly needed. This helps us avoid needless hashing and lookups as well as script value conversions +#[derive(Debug)] +pub struct MagicFunctions { + /// Indexer function + pub get: + fn(FunctionCallContext, ReflectReference, ScriptValue) -> Result, + /// Indexer setter function + pub set: fn( + FunctionCallContext, + ReflectReference, + ScriptValue, + ScriptValue, + ) -> Result<(), InteropError>, +} + +impl MagicFunctions { + /// Calls the currently set `get` function with the given arguments. + pub fn get( + &self, + ctxt: FunctionCallContext, + reference: ReflectReference, + key: ScriptValue, + ) -> Result { + (self.get)(ctxt, reference, key) + } + + /// Calls the currently set `set` function with the given arguments. + pub fn set( + &self, + ctxt: FunctionCallContext, + reference: ReflectReference, + key: ScriptValue, + value: ScriptValue, + ) -> Result<(), InteropError> { + (self.set)(ctxt, reference, key, value) + } + + /// Indexes into the given reference and if the nested type is a reference type, returns a deeper reference, otherwise + /// returns the concrete value. + /// + /// Does not support map types at the moment, for maps see `map_get` + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to index into. + /// * `key`: The key to index with. + /// + /// Returns: + /// * `value`: The value at the key, if the reference is indexable. + pub fn default_get( + ctxt: FunctionCallContext, + mut reference: ReflectReference, + key: ScriptValue, + ) -> Result { + let mut path: ParsedPath = key.try_into()?; + if ctxt.convert_to_0_indexed() { + path.convert_to_0_indexed(); + } + reference.index_path(path); + let world = ctxt.world()?; + ReflectReference::into_script_ref(reference, world) + } + + /// Sets the value under the specified path on the underlying value. + /// + /// Arguments: + /// * `ctxt`: The function call context. + /// * `reference`: The reference to set the value on. + /// * `key`: The key to set the value at. + /// * `value`: The value to set. + /// + /// Returns: + /// * `result`: Nothing if the value was set successfully. + pub fn default_set( + ctxt: FunctionCallContext, + mut reference: ReflectReference, + key: ScriptValue, + value: ScriptValue, + ) -> Result<(), InteropError> { + let world = ctxt.world()?; + let mut path: ParsedPath = key.try_into()?; + if ctxt.convert_to_0_indexed() { + path.convert_to_0_indexed(); + } + reference.index_path(path); + reference.with_reflect_mut(world.clone(), |r| { + let target_type_id = r + .get_represented_type_info() + .map(|i| i.type_id()) + .or_fake_id(); + let other = + >::from_script_ref(target_type_id, value, world.clone())?; + r.try_apply(other.as_partial_reflect()) + .map_err(|e| InteropError::external_error(Box::new(e)))?; + Ok::<_, InteropError>(()) + })? + } +} + +impl Default for MagicFunctions { + fn default() -> Self { + Self { + get: MagicFunctions::default_get, + set: MagicFunctions::default_set, + } + } +} diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs index e32f3145c4..c8a33cee8e 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/mod.rs @@ -1,6 +1,6 @@ //! Abstractions to do with dynamic script functions -crate::private::export_all_in_modules!{ +crate::private::export_all_in_modules! { arg_meta, from, from_ref, @@ -8,7 +8,8 @@ crate::private::export_all_in_modules!{ into_ref, namespace, script_function, - type_dependencies + type_dependencies, + magic_functions } #[cfg(test)] @@ -18,12 +19,13 @@ mod test { use bevy_mod_scripting_derive::script_bindings; use crate::bindings::{ - function::{ - from::{Ref, Union, Val}, - namespace::IntoNamespace, - script_function::AppScriptFunctionRegistry, - }, script_value::ScriptValue - }; + function::{ + from::{Ref, Union, Val}, + namespace::IntoNamespace, + script_function::AppScriptFunctionRegistry, + }, + script_value::ScriptValue, + }; use super::arg_meta::{ScriptArgument, ScriptReturn, TypedScriptArgument, TypedScriptReturn}; diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index d0627f67a7..78075e241b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -1,5 +1,6 @@ //! Implementations of the [`ScriptFunction`] and [`ScriptFunctionMut`] traits for functions with up to 13 arguments. +use super::MagicFunctions; use super::{from::FromScript, into::IntoScript, namespace::Namespace}; use crate::asset::Language; use crate::bindings::function::arg_meta::ArgMeta; @@ -16,7 +17,6 @@ use std::collections::{HashMap, VecDeque}; use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::sync::Arc; - #[diagnostic::on_unimplemented( message = "This function does not fulfil the requirements to be a script callable function. All arguments must implement the ScriptArgument trait and all return values must implement the ScriptReturn trait", note = "If you're trying to return a non-primitive type, you might need to use Val Ref or Mut wrappers" @@ -315,6 +315,8 @@ pub struct FunctionKey { /// A registry of dynamic script functions pub struct ScriptFunctionRegistry { functions: HashMap, + /// A registry of magic functions + pub magic_functions: MagicFunctions, } #[profiling::all_functions] diff --git a/crates/bevy_mod_scripting_core/src/bindings/reference.rs b/crates/bevy_mod_scripting_core/src/bindings/reference.rs index 5319e08432..ea5899da27 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/reference.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/reference.rs @@ -28,7 +28,9 @@ use std::{any::TypeId, fmt::Debug}; /// /// References are composed of two parts: /// - The base kind, which specifies where the reference points to -/// - The path, which specifies how to access the value from the base +/// - The path, which specifies how to access the value from the base. +/// +/// Bindings defined on this type, apply to ALL references. #[derive(Debug, Clone, PartialEq, Eq, Reflect)] #[reflect(Default, opaque)] pub struct ReflectReference { diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index a3689932ca..d1188bf2d5 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use bevy::{prelude::*, reflect::ParsedPath}; +use bevy::prelude::*; use bevy_mod_scripting_core::{ bindings::{ function::{ @@ -24,9 +24,8 @@ use bindings::{ }, pretty_print::DisplayWithWorld, script_value::ScriptValue, - ReflectReference, ReflectionPathExt, ScriptComponentRegistration, ScriptQueryBuilder, - ScriptQueryResult, ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, - WorldContainer, + ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult, + ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, WorldContainer, }; use error::InteropError; use reflection_extensions::{PartialReflectExt, TypeIdExtensions}; @@ -568,73 +567,6 @@ impl ReflectReference { })? } - /// Indexes into the given reference and if the nested type is a reference type, returns a deeper reference, otherwise - /// returns the concrete value. - /// - /// Does not support map types at the moment, for maps see `map_get` - /// - /// Arguments: - /// * `ctxt`: The function call context. - /// * `reference`: The reference to index into. - /// * `key`: The key to index with. - /// Returns: - /// * `value`: The value at the key, if the reference is indexable. - fn get( - ctxt: FunctionCallContext, - mut reference: ReflectReference, - key: ScriptValue, - ) -> Result { - profiling::function_scope!("get"); - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed() { - path.convert_to_0_indexed(); - } - reference.index_path(path); - let world = ctxt.world()?; - ReflectReference::into_script_ref(reference, world) - } - - /// Sets the value under the specified path on the underlying value. - /// - /// Arguments: - /// * `ctxt`: The function call context. - /// * `reference`: The reference to set the value on. - /// * `key`: The key to set the value at. - /// * `value`: The value to set. - /// Returns: - /// * `result`: Nothing if the value was set successfully. - fn set( - ctxt: FunctionCallContext, - reference: ScriptValue, - key: ScriptValue, - value: ScriptValue, - ) -> Result<(), InteropError> { - profiling::function_scope!("set"); - if let ScriptValue::Reference(mut self_) = reference { - let world = ctxt.world()?; - let mut path: ParsedPath = key.try_into()?; - if ctxt.convert_to_0_indexed() { - path.convert_to_0_indexed(); - } - self_.index_path(path); - self_.with_reflect_mut(world.clone(), |r| { - let target_type_id = r - .get_represented_type_info() - .map(|i| i.type_id()) - .or_fake_id(); - let other = >::from_script_ref( - target_type_id, - value, - world.clone(), - )?; - r.try_apply(other.as_partial_reflect()) - .map_err(|e| InteropError::external_error(Box::new(e)))?; - Ok::<_, InteropError>(()) - })??; - } - Ok(()) - } - /// Pushes the value into the reference, if the reference is an appropriate container type. /// /// Arguments: diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs index 2711619cd2..3308647d50 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs @@ -57,15 +57,13 @@ impl UserData for LuaReflectReference { Err(key) => key, }; - let func = world - .lookup_function([type_id, TypeId::of::()], "get") - .map_err(|f| { - InteropError::missing_function(TypeId::of::(), f) - })?; + // call the default magic getter + let registry = world.script_function_registry(); + let registry = registry.read(); - // call the function with the key - let out = - func.call(vec![ScriptValue::Reference(self_), key], LUA_CALLER_CONTEXT)?; + let out = registry + .magic_functions + .get(LUA_CALLER_CONTEXT, self_, key)?; Ok(LuaScriptValue(out)) }, ); @@ -78,20 +76,15 @@ impl UserData for LuaReflectReference { let self_: ReflectReference = self_.into(); let key: ScriptValue = key.into(); let value: ScriptValue = value.into(); - let type_id = self_.tail_type_id(world.clone())?.or_fake_id(); - let func = world - .lookup_function([type_id, TypeId::of::()], "set") - .map_err(|f| { - InteropError::missing_function(TypeId::of::(), f) - })?; + let registry = world.script_function_registry(); + let registry = registry.read(); - let out = func.call( - vec![ScriptValue::Reference(self_), key, value], - LUA_CALLER_CONTEXT, - )?; + registry + .magic_functions + .set(LUA_CALLER_CONTEXT, self_, key, value)?; - Ok(LuaScriptValue(out)) + Ok(()) }, ); diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs index 65c2ee6fd6..4bb4b2524c 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs @@ -291,12 +291,12 @@ impl CustomType for RhaiReflectReference { fn build(mut builder: rhai::TypeBuilder) { builder .with_name(std::any::type_name::()) - .with_indexer_get(|self_: &mut Self, _index: Dynamic| { + .with_indexer_get(|self_: &mut Self, index: Dynamic| { let world = ThreadWorldContainer.try_get_world()?; let self_ = &self_.0; let type_id = self_.tail_type_id(world.clone())?.or_fake_id(); - let key: ScriptValue = ScriptValue::from_dynamic(_index)?; + let key: ScriptValue = ScriptValue::from_dynamic(index)?; let key = match key.as_string() { Ok(string) => { match world @@ -312,39 +312,29 @@ impl CustomType for RhaiReflectReference { Err(key) => key, }; - let func = world - .lookup_function([type_id, TypeId::of::()], "get") - .map_err(|_| InteropError::missing_function(type_id, "get".to_owned()))?; + // call the default magic getter + let registry = world.script_function_registry(); + let registry = registry.read(); - let out = func.call( - vec![ScriptValue::Reference(self_.clone()), key], - RHAI_CALLER_CONTEXT, - )?; + let out = registry + .magic_functions + .get(RHAI_CALLER_CONTEXT, self_.clone(), key)?; out.into_dynamic() }) - .with_indexer_set(|self_: &mut Self, _index: Dynamic, _value: Dynamic| { + .with_indexer_set(|self_: &mut Self, index: Dynamic, value: Dynamic| { let world = ThreadWorldContainer.try_get_world()?; let self_ = self_.0.clone(); - let key = ScriptValue::from_dynamic(_index)?; - let value = ScriptValue::from_dynamic(_value)?; - let type_id = self_.tail_type_id(world.clone())?.or_fake_id(); + let key = ScriptValue::from_dynamic(index)?; + let value = ScriptValue::from_dynamic(value)?; - let func = world - .lookup_function([type_id, TypeId::of::()], "set") - .map_err(|f| { - InteropError::missing_function(TypeId::of::(), f) - })?; + let registry = world.script_function_registry(); + let registry = registry.read(); - let out = func.call( - vec![ScriptValue::Reference(self_), key, value], - RHAI_CALLER_CONTEXT, - )?; - - match out { - ScriptValue::Error(interop_error) => Err(interop_error)?, - _ => Ok(()), - } + registry + .magic_functions + .set(RHAI_CALLER_CONTEXT, self_, key, value)?; + Ok(()) }) .with_fn( RhaiOperator::Sub.function_name(), diff --git a/docs/src/Development/AddingLanguages/necessary-features.md b/docs/src/Development/AddingLanguages/necessary-features.md index d1b0251bae..9bbfefb37f 100644 --- a/docs/src/Development/AddingLanguages/necessary-features.md +++ b/docs/src/Development/AddingLanguages/necessary-features.md @@ -1,3 +1,7 @@ +

+ This section needs work and is not fully accurate as is. +
+ # Necessary Features In order for a language to be called "implemented" in BMS, it needs to support the following features: diff --git a/docs/src/Summary/controlling-script-bindings.md b/docs/src/Summary/controlling-script-bindings.md index 1b706a35d9..d3642bd875 100644 --- a/docs/src/Summary/controlling-script-bindings.md +++ b/docs/src/Summary/controlling-script-bindings.md @@ -141,8 +141,8 @@ There are a few reserved functions that you can override by registering them on | Function Name | Description | Overridable? | Has Default Implementation? | |---------------|-------------| ------------ | --------------------------- | -| get | a getter function, used for indexing into a type | ✅ | ✅ | -| set | a setter function, used for setting a value on a type | ✅ | ✅ | +| get | a getter function, used for indexing into a type | ❌ | ✅ | +| set | a setter function, used for setting a value on a type | ❌ | ✅ | | sub | a subtraction function, used for subtracting two values | ✅ | ❌ | | add | an addition function, used for adding two values | ✅ | ❌ | | mul | a multiplication function, used for multiplying two values | ✅ | ❌ | @@ -157,3 +157,5 @@ There are a few reserved functions that you can override by registering them on | display_value | a display function, used for displaying a mutable reference to a value | ❌ | ✅ | In this context `overridable` indicates whether language implementations will look for a specific function on your type before looking at the generic `ReflectReference` namespace. You can still remove the existing registration for these functions on the `ReflectReference` namespace if you want to replace them with your own implementation. + +Note the `ReflectReference` namespace is special, in that functions defined on it, act like a fallback and hence apply to ALL references. From de824aa87b46d1df22a540ace4fa8d3b59b28453 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 14:04:59 +0000 Subject: [PATCH 40/51] feat: optimize access map (#395) # Summary Removes some unnecessary code in access maps to improve performance slightly. Also refactors area slightly to make it easier to modify in the future --- .../src/bindings/access_map.rs | 137 +++++++----------- 1 file changed, 49 insertions(+), 88 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs index e65d993f5d..97d7f2b5dd 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs @@ -1,5 +1,4 @@ //! A map of access claims used to safely and dynamically access the world. -use std::thread::ThreadId; use bevy::{ ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, @@ -16,7 +15,6 @@ use super::{ReflectAllocationId, ReflectBase}; #[derive(Debug, Clone, PartialEq, Eq)] /// An owner of an access claim and the code location of the claim. pub struct ClaimOwner { - id: ThreadId, location: std::panic::Location<'static>, } @@ -35,6 +33,7 @@ impl Default for AccessCount { } } +#[profiling::all_functions] impl AccessCount { fn new() -> Self { Self { @@ -71,6 +70,7 @@ pub trait AccessMapKey { fn from_index(value: u64) -> Self; } +#[profiling::all_functions] impl AccessMapKey for u64 { fn as_index(&self) -> u64 { *self @@ -100,6 +100,7 @@ pub struct ReflectAccessId { pub(crate) id: u64, } +#[profiling::all_functions] impl AccessMapKey for ReflectAccessId { fn as_index(&self) -> u64 { // project two linear non-negative ranges [0,inf] to a single linear non-negative range, offset by 1 to avoid 0 @@ -134,6 +135,7 @@ impl AccessMapKey for ReflectAccessId { } } +#[profiling::all_functions] impl ReflectAccessId { /// Creates a new access id for the global world pub fn for_global() -> Self { @@ -192,6 +194,7 @@ impl ReflectAccessId { } } +#[profiling::all_functions] impl From for ReflectAccessId { fn from(value: ComponentId) -> Self { Self { @@ -201,6 +204,7 @@ impl From for ReflectAccessId { } } +#[profiling::all_functions] impl From for ReflectAccessId { fn from(value: ReflectAllocationId) -> Self { Self { @@ -210,12 +214,14 @@ impl From for ReflectAccessId { } } +#[profiling::all_functions] impl From for ComponentId { fn from(val: ReflectAccessId) -> Self { ComponentId::new(val.id as usize) } } +#[profiling::all_functions] impl From for ReflectAllocationId { fn from(val: ReflectAccessId) -> Self { ReflectAllocationId::new(val.id) @@ -315,6 +321,29 @@ struct AccessMapInner { global_lock: AccessCount, } +#[profiling::all_functions] +impl AccessMapInner { + #[inline] + fn entry(&self, key: u64) -> Option<&AccessCount> { + self.individual_accesses.get(&key) + } + + #[inline] + fn entry_mut(&mut self, key: u64) -> Option<&mut AccessCount> { + self.individual_accesses.get_mut(&key) + } + + #[inline] + fn entry_or_default(&mut self, key: u64) -> &mut AccessCount { + self.individual_accesses.entry(key).or_default() + } + + #[inline] + fn remove(&mut self, key: u64) { + self.individual_accesses.remove(&key); + } +} + const GLOBAL_KEY: u64 = 0; #[profiling::all_functions] @@ -362,10 +391,10 @@ impl DynamicSystemMeta for AccessMap { return false; } - let entry = inner.individual_accesses.entry(key).or_default(); + let entry = inner.entry_or_default(key); + if entry.can_read() { entry.read_by.push(ClaimOwner { - id: std::thread::current().id(), location: *std::panic::Location::caller(), }); true @@ -388,10 +417,10 @@ impl DynamicSystemMeta for AccessMap { return false; } - let entry = inner.individual_accesses.entry(key).or_default(); + let entry = inner.entry_or_default(key); + if entry.can_write() { entry.read_by.push(ClaimOwner { - id: std::thread::current().id(), location: *std::panic::Location::caller(), }); entry.written = true; @@ -409,7 +438,6 @@ impl DynamicSystemMeta for AccessMap { return false; } inner.global_lock.read_by.push(ClaimOwner { - id: std::thread::current().id(), location: *std::panic::Location::caller(), }); inner.global_lock.written = true; @@ -420,31 +448,19 @@ impl DynamicSystemMeta for AccessMap { let mut inner = self.0.lock(); let key = key.as_index(); - if let Some(entry) = inner.individual_accesses.get_mut(&key) { + if let Some(entry) = inner.entry_mut(key) { entry.written = false; - if let Some(claim) = entry.read_by.pop() { - assert!( - claim.id == std::thread::current().id(), - "Access released from wrong thread, claimed at {}", - claim.location.display_location() - ); - } + entry.read_by.pop(); if entry.readers() == 0 { - inner.individual_accesses.remove(&key); + inner.remove(key); } } } fn release_global_access(&self) { let mut inner = self.0.lock(); + inner.global_lock.read_by.pop(); inner.global_lock.written = false; - if let Some(claim) = inner.global_lock.read_by.pop() { - assert!( - claim.id == std::thread::current().id(), - "Global access released from wrong thread, claimed at {}", - claim.location.display_location() - ); - } } fn list_accesses(&self) -> Vec<(K, AccessCount)> { @@ -452,7 +468,7 @@ impl DynamicSystemMeta for AccessMap { inner .individual_accesses .iter() - .map(|(&key, count)| (K::from_index(key), count.clone())) + .map(|(key, a)| (K::from_index(*key), a.clone())) .collect() } @@ -486,8 +502,7 @@ impl DynamicSystemMeta for AccessMap { }) } else { inner - .individual_accesses - .get(&key.as_index()) + .entry(key.as_index()) .and_then(|access| access.as_location()) } } @@ -496,8 +511,9 @@ impl DynamicSystemMeta for AccessMap { let inner = self.0.lock(); inner .individual_accesses - .values() - .find_map(|access| access.as_location()) + .iter() + .next() + .and_then(|(_, access)| access.as_location()) } } @@ -507,6 +523,7 @@ pub struct SubsetAccessMap { subset: Box bool + Send + Sync + 'static>, } +#[profiling::all_functions] impl SubsetAccessMap { /// Creates a new subset access map with the provided subset of ID's as well as a exception function. pub fn new( @@ -528,6 +545,7 @@ impl SubsetAccessMap { } } +#[profiling::all_functions] impl DynamicSystemMeta for SubsetAccessMap { fn with_scope O>(&self, f: F) -> O { self.inner.with_scope(f) @@ -601,6 +619,7 @@ pub enum AnyAccessMap { SubsetAccessMap(SubsetAccessMap), } +#[profiling::all_functions] impl DynamicSystemMeta for AnyAccessMap { fn with_scope O>(&self, f: F) -> O { match self { @@ -700,12 +719,14 @@ pub trait DisplayCodeLocation { fn display_location(self) -> String; } +#[profiling::all_functions] impl DisplayCodeLocation for std::panic::Location<'_> { fn display_location(self) -> String { format!("\"{}:{}\"", self.file(), self.line()) } } +#[profiling::all_functions] impl DisplayCodeLocation for Option> { fn display_location(self) -> String { self.map(|l| l.display_location()) @@ -926,66 +947,6 @@ mod test { assert!(!subset_access_map.claim_global_access()); } - #[test] - #[should_panic] - fn access_map_releasing_read_access_from_wrong_thread_panics() { - let access_map = AccessMap::default(); - - access_map.claim_read_access(1); - std::thread::spawn(move || { - access_map.release_access(1); - }) - .join() - .unwrap(); - } - - #[test] - #[should_panic] - fn subset_map_releasing_read_access_from_wrong_thread_panics() { - let access_map = AccessMap::default(); - let subset_access_map = SubsetAccessMap { - inner: access_map, - subset: Box::new(|id| id == 1), - }; - - subset_access_map.claim_read_access(1); - std::thread::spawn(move || { - subset_access_map.release_access(1); - }) - .join() - .unwrap(); - } - - #[test] - #[should_panic] - fn access_map_releasing_write_access_from_wrong_thread_panics() { - let access_map = AccessMap::default(); - - access_map.claim_write_access(1); - std::thread::spawn(move || { - access_map.release_access(1); - }) - .join() - .unwrap(); - } - - #[test] - #[should_panic] - fn subset_map_releasing_write_access_from_wrong_thread_panics() { - let access_map = AccessMap::default(); - let subset_access_map = SubsetAccessMap { - inner: access_map, - subset: Box::new(|id| id == 1), - }; - - subset_access_map.claim_write_access(1); - std::thread::spawn(move || { - subset_access_map.release_access(1); - }) - .join() - .unwrap(); - } - #[test] fn as_and_from_index_for_access_id_non_overlapping() { let global = ReflectAccessId::for_global(); From 15bc67483ede1788f86e8b478a31a99a63d7ccab Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 14:48:49 +0000 Subject: [PATCH 41/51] chore: add commit perf scope --- .github/workflows/pr-titles.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-titles.yml b/.github/workflows/pr-titles.yml index 4b3242d11e..d5e92a5a6c 100644 --- a/.github/workflows/pr-titles.yml +++ b/.github/workflows/pr-titles.yml @@ -27,8 +27,9 @@ jobs: test docs refactor + perf scopes: | ladfile ladfile_builder - bms \ No newline at end of file + bms From 5a6e4b3481a59eb2505da7aeb1d99bd80937e00f Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 15:30:34 +0000 Subject: [PATCH 42/51] perf: try play with hashing for access maps (#398) # Summary Tries a simpler hash function for access maps specifically. The motivation is this: - access maps are small, most often probably less than 10 items - we don't care about collisions as much because of this - we deal with simple u64 keys, which are unique by definition So then: - using these u64's as hashes directly should make lookups faster, and collisions won't be a big deal --- .../src/bindings/access_map.rs | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs index 97d7f2b5dd..cf91e5bced 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs @@ -1,5 +1,7 @@ //! A map of access claims used to safely and dynamically access the world. +use std::hash::{BuildHasherDefault, Hasher}; + use bevy::{ ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, prelude::Resource, @@ -315,9 +317,34 @@ pub trait DynamicSystemMeta { fn access_first_location(&self) -> Option>; } -#[derive(Debug, Default, Clone)] +#[derive(Default)] +/// A hash function which doesn't do much. for maps which expect very small hashes. +/// Assumes only needs to hash u64 values, unsafe otherwise +struct SmallIdentityHash(u64); +impl Hasher for SmallIdentityHash { + fn finish(&self) -> u64 { + self.0 + } + + fn write(&mut self, bytes: &[u8]) { + // concat all bytes via && + // this is a bit of a hack, but it works for our use case + // and is faster than using a hash function + #[allow(clippy::expect_used, reason = "cannot handle this panic otherwise")] + let arr: &[u8; 8] = bytes.try_into().expect("this hasher only supports u64"); + // depending on endianess + + #[cfg(target_endian = "big")] + let word = u64::from_be_bytes(*arr); + #[cfg(target_endian = "little")] + let word = u64::from_le_bytes(*arr); + self.0 = word + } +} + +#[derive(Default, Debug, Clone)] struct AccessMapInner { - individual_accesses: HashMap, + individual_accesses: HashMap>, global_lock: AccessCount, } @@ -795,6 +822,8 @@ pub(crate) use with_global_access; #[cfg(test)] mod test { + use std::hash::Hash; + use super::*; #[test] @@ -1200,4 +1229,12 @@ mod test { assert!(!subset_access_map.claim_read_access(2)); assert!(!subset_access_map.claim_write_access(2)); } + + #[test] + fn test_hasher_on_u64() { + let mut hasher = SmallIdentityHash::default(); + let value = 42u64; + value.hash(&mut hasher); + assert_eq!(hasher.finish(), 42); + } } From 5ebabbf05845167f8356511e39f3bdc0348e7f3b Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 29 Mar 2025 16:01:14 +0000 Subject: [PATCH 43/51] chore: change bencher plots to use version x axis --- crates/xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index e30858c972..ea8dc4ee96 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1518,7 +1518,7 @@ impl Xtasks { .stderr(std::process::Stdio::inherit()) .args(["plot", "create", "bms"]) .args(["--title", &plot_name]) - .args(["--x-axis", "date_time"]) + .args(["--x-axis", "version"]) .args(["--window", &window_seconds.to_string()]) .args(["--branches", MAIN_BRANCH_UUID]) .args(["--testbeds", testbed_uuid]) From 54742c60467bd99cf1104a82a0c3348209409a7f Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 17:01:44 +0000 Subject: [PATCH 44/51] perf: switch to hashbrown hashmap in the function registry (#399) # Summary The function registry as well as the allocator used the standard `std` hashmap, which by default uses a secure but slow hash. We don't need to worry about security here, so switching to hashbrown should give us a fair bit of speed up for free. --- crates/bevy_mod_scripting_core/src/bindings/allocator.rs | 2 +- .../src/bindings/function/script_function.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_mod_scripting_core/src/bindings/allocator.rs b/crates/bevy_mod_scripting_core/src/bindings/allocator.rs index 93830d0bb2..c37acdb943 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/allocator.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/allocator.rs @@ -6,12 +6,12 @@ use bevy::{ ecs::system::{Res, Resource}, prelude::ResMut, reflect::PartialReflect, + utils::hashbrown::HashMap, }; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ cell::UnsafeCell, cmp::Ordering, - collections::HashMap, fmt::{Display, Formatter}, hash::Hasher, sync::{atomic::AtomicU64, Arc}, diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index 78075e241b..543d5fe701 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -11,9 +11,10 @@ use crate::{ ScriptValue, }; use bevy::prelude::{Reflect, Resource}; +use bevy::utils::hashbrown::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::borrow::Cow; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::sync::Arc; From 73a0d1e3aa98e3b60d5fe6b1d1bf3517f6d00f53 Mon Sep 17 00:00:00 2001 From: makspll Date: Sat, 29 Mar 2025 19:02:50 +0000 Subject: [PATCH 45/51] feat!: bump bersion --- .../languages/bevy_mod_scripting_rhai/src/bindings/reference.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs index 4bb4b2524c..8e8d288ef8 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs @@ -232,7 +232,7 @@ impl RhaiOperator { } } -/// An iterator over a [`ReflectReference`] that implements [`IntoIterator`] for Rhai +/// An iterator over a [`ReflectReference`] that implements [`IntoIterator`] for Rhai. pub struct RhaiReflectRefIter { next_func: DynamicScriptFunctionMut, } From 859b2ec184715071ff3b5b1fe9d2a9156f189bf0 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 19:03:52 +0000 Subject: [PATCH 46/51] chore: Create 0.11.0 release notes --- release-notes/0.11.0 | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 release-notes/0.11.0 diff --git a/release-notes/0.11.0 b/release-notes/0.11.0 new file mode 100644 index 0000000000..009d15d6ac --- /dev/null +++ b/release-notes/0.11.0 @@ -0,0 +1,11 @@ +# [`bevy_mod_scripting`](https://github.com/makspll/bevy_mod_scripting/) 0.11.0 is out! + +![image](https://github.com/user-attachments/assets/6ae0f927-ea1b-4d90-a809-4cc513e49b18) + +## Summary + +## Changelog +See a detailed changelog [here](https://github.com/makspll/bevy_mod_scripting/blob/main/CHANGELOG.md) + +## Migration Guide +The migration guide for this release can be found [here](https://github.com/makspll/bevy_mod_scripting/blob/main/release-notes/0.11.0-migration.md) From 99e8682ec1e0e4dba962f83e0153483528afcf9e Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 19:04:12 +0000 Subject: [PATCH 47/51] chore: Rename 0.11.0 to 0.11.0.md --- release-notes/{0.11.0 => 0.11.0.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename release-notes/{0.11.0 => 0.11.0.md} (100%) diff --git a/release-notes/0.11.0 b/release-notes/0.11.0.md similarity index 100% rename from release-notes/0.11.0 rename to release-notes/0.11.0.md From 9bdfd8499be12b51698269c9219ce6b2765f4134 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 19:15:56 +0000 Subject: [PATCH 48/51] chore: Update 0.11.0.md --- release-notes/0.11.0.md | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/release-notes/0.11.0.md b/release-notes/0.11.0.md index 009d15d6ac..e51f7f3690 100644 --- a/release-notes/0.11.0.md +++ b/release-notes/0.11.0.md @@ -4,6 +4,52 @@ ## Summary +### :sparkles: Dynamic Components +To compliment dynamic systems introduced in the previous updates, scripts can now register their own, fully legit bevy components! + +```lua +local NewComponent = world.register_new_component("ScriptComponentA") + +local new_entity = world.spawn() +world.insert_component(new_entity, NewComponent, construct(types.DynamicComponent, { + data = "Hello World" +})) + +local component_instance = world.get_component(new_entity, NewComponent) +assert(component_instance.data == "Hello World", "unexpected value: " .. component_instance.data) + +component_instance.data = { + foo = "bar" +} + +assert(component_instance.data.foo == "bar", "unexpected value: " .. component_instance.data.foo) +``` + +These are backed by the `DynamicComponent` type which looks like this: +```rust +pub struct DynamicComponent { + data: ScriptValue, +} +``` +scripts can freely set this data payload to anything that is supported by `ScriptValue`'s !. + +These can also be queried as normal! + +### Mdbook Preprocessor Prettified +The documentation you can generate via exported `ladfile`s now looks much better, types have nested links to other types, and things generally look better! +![424231364-a4bfa889-771d-4faf-825d-29143851aeb6](https://github.com/user-attachments/assets/f963bcae-7f4b-4088-b38e-6095b7fde3a9) + + +### Continous Benchmarking +BMS now runs and publishes the results of a variety of benchmarks over at [bencher](https://bencher.dev/console/projects/bms/plots) + +### Performance & Profiling Improvements +You can now easilly profile BMS using the new `profile_with_tracy` feature which will also enable bevy's equivalent. Tracing spans have generally been improved, giving you lots of great detail into where most time is spent! + +The `get` and `set` indexer functions have been extracted into a `MagicFunctions` sub-registry to improve the performance of reflection. + +Various internal hashmaps have been tuned to get free performance wins. + ## Changelog See a detailed changelog [here](https://github.com/makspll/bevy_mod_scripting/blob/main/CHANGELOG.md) From e6e53ee8a5cec9fed5f4f68f4086155143a2510c Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 19:21:02 +0000 Subject: [PATCH 49/51] chore: Create 0.11.0-migration.md --- release-notes/0.11.0-migration.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 release-notes/0.11.0-migration.md diff --git a/release-notes/0.11.0-migration.md b/release-notes/0.11.0-migration.md new file mode 100644 index 0000000000..72786b260c --- /dev/null +++ b/release-notes/0.11.0-migration.md @@ -0,0 +1,24 @@ +# Migrating to BMS 0.11.0 + +### Removal of custom `get` and `set` overrides +If you were previously registering custom `get` or `set` functions on specific types, +these will no longer be searched for by the `lua` and `rhai` implementations. + +If this is a wanted feature, let us know and we might bring it back! + +If you are a language implementor, you will need to transition to calling the mini-registry for these. + +### Calling `get` and `set` directly in scripts +Since these have been removed from bindings and moved into an easier to access sub-registry, which is not accessible to scripts directly +if you used these directly in scripts, i.e. with `my_type:get("field")`, you will have to migrate to the native syntax: +i.e.: +```lua +my_type.field +my_type["field"] +my_type["1"] +my_type._1 +``` +However `map_get` and other `ReflectReference` functions remain where they used to. + + +Happy migrating! From 4572009473ad3338189563ad8d2980266984a499 Mon Sep 17 00:00:00 2001 From: Maksymilian Mozolewski Date: Sat, 29 Mar 2025 19:22:58 +0000 Subject: [PATCH 50/51] chore: Update 0.11.0.md --- release-notes/0.11.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-notes/0.11.0.md b/release-notes/0.11.0.md index e51f7f3690..a919e7ceb4 100644 --- a/release-notes/0.11.0.md +++ b/release-notes/0.11.0.md @@ -39,6 +39,8 @@ These can also be queried as normal! The documentation you can generate via exported `ladfile`s now looks much better, types have nested links to other types, and things generally look better! ![424231364-a4bfa889-771d-4faf-825d-29143851aeb6](https://github.com/user-attachments/assets/f963bcae-7f4b-4088-b38e-6095b7fde3a9) +### Storing lua closures +The conversion from `mlua::Function` to `ScriptValue` is now supported, and as such you can store arbitrary lua callbacks through reflection in your `components/resources` (being careful not to unload scripts while these are being used, as it will likely cause panics in mlua) ### Continous Benchmarking BMS now runs and publishes the results of a variety of benchmarks over at [bencher](https://bencher.dev/console/projects/bms/plots) From f20698e902ecc052aecbf392053f04c6191cd29b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 29 Mar 2025 19:27:20 +0000 Subject: [PATCH 51/51] chore: release (#384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 🤖 New release * `bevy_mod_scripting_derive`: 0.10.0 -> 0.11.0 * `bevy_mod_scripting_core`: 0.10.0 -> 0.11.0 (✓ API compatible changes) * `bevy_mod_scripting_lua`: 0.10.0 -> 0.11.0 (✓ API compatible changes) * `bevy_mod_scripting_rhai`: 0.10.0 -> 0.11.0 (✓ API compatible changes) * `bevy_mod_scripting_functions`: 0.10.0 -> 0.11.0 (✓ API compatible changes) * `ladfile`: 0.4.0 -> 0.5.0 (⚠ API breaking changes) * `mdbook_lad_preprocessor`: 0.1.4 -> 0.1.5 (✓ API compatible changes) * `ladfile_builder`: 0.2.6 -> 0.3.0 (⚠ API breaking changes) * `bevy_mod_scripting`: 0.10.0 -> 0.11.0 (✓ API compatible changes) ### ⚠ `ladfile` breaking changes ```text --- failure constructible_struct_adds_field: externally-constructible struct adds field --- Description: A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field. ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.40.0/src/lints/constructible_struct_adds_field.ron Failed in: field LadType.generated in /tmp/.tmpI1fySQ/bevy_mod_scripting/crates/ladfile/src/lib.rs:458 field LadType.insignificance in /tmp/.tmpI1fySQ/bevy_mod_scripting/crates/ladfile/src/lib.rs:465 --- failure method_parameter_count_changed: pub method parameter count changed --- Description: A publicly-visible method now takes a different number of parameters. ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.40.0/src/lints/method_parameter_count_changed.ron Failed in: ladfile::LadFile::get_type_identifier now takes 3 parameters instead of 2, in /tmp/.tmpI1fySQ/bevy_mod_scripting/crates/ladfile/src/lib.rs:55 ``` ### ⚠ `ladfile_builder` breaking changes ```text --- failure constructible_struct_adds_field: externally-constructible struct adds field --- Description: A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field. ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.40.0/src/lints/constructible_struct_adds_field.ron Failed in: field LadFileSettings.exclude_types_containing_unregistered in /tmp/.tmpI1fySQ/bevy_mod_scripting/crates/ladfile_builder/src/plugin.rs:42 --- failure method_parameter_count_changed: pub method parameter count changed --- Description: A publicly-visible method now takes a different number of parameters. ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.40.0/src/lints/method_parameter_count_changed.ron Failed in: ladfile_builder::plugin::ScriptingDocgenPlugin::new now takes 4 parameters instead of 3, in /tmp/.tmpI1fySQ/bevy_mod_scripting/crates/ladfile_builder/src/plugin.rs:61 ```
Changelog

## `bevy_mod_scripting_derive`

## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_derive-v0.10.0...bevy_mod_scripting_derive-v0.11.0) - 2025-03-29 ### Added - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377))
## `bevy_mod_scripting_core`
## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_core-v0.10.0...bevy_mod_scripting_core-v0.11.0) - 2025-03-29 ### Added - optimize access map ([#395](https://github.com/makspll/bevy_mod_scripting/pull/395)) - optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) - improve tracing spans, add `profile_with_tracy` feature flag ([#394](https://github.com/makspll/bevy_mod_scripting/pull/394)) - add `profile_with_tracy` feature which plays nicely with bevy's `bevy/trace_tracy` feature ([#393](https://github.com/makspll/bevy_mod_scripting/pull/393)) - Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) - add ScriptValue override for printing opaque values ([#380](https://github.com/makspll/bevy_mod_scripting/pull/380)) - :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) ### Fixed - fix global type cache not containing generic types ([#388](https://github.com/makspll/bevy_mod_scripting/pull/388)) ### Other - switch to hashbrown hashmap in the function registry ([#399](https://github.com/makspll/bevy_mod_scripting/pull/399)) - try play with hashing for access maps ([#398](https://github.com/makspll/bevy_mod_scripting/pull/398)) - allow check creation for bencher
## `bevy_mod_scripting_lua`
## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_lua-v0.10.0...bevy_mod_scripting_lua-v0.11.0) - 2025-03-29 ### Added - optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) - allow the conversion of lua functions into `ScriptValue` via `DynamicScriptFunction` ([#396](https://github.com/makspll/bevy_mod_scripting/pull/396)) - Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) - :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379))
## `bevy_mod_scripting_rhai`
## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_rhai-v0.10.0...bevy_mod_scripting_rhai-v0.11.0) - 2025-03-29 ### Added - [**breaking**] bump bersion - optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) - Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381))
## `bevy_mod_scripting_functions`
## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_functions-v0.10.0...bevy_mod_scripting_functions-v0.11.0) - 2025-03-29 ### Added - optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) - :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377))
## `ladfile`
## [0.5.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.4.0-ladfile...v0.5.0-ladfile) - 2025-03-29 ### Added - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377))
## `mdbook_lad_preprocessor`
## [0.1.5](https://github.com/makspll/bevy_mod_scripting/compare/v0.1.4-mdbook_lad_preprocessor...v0.1.5-mdbook_lad_preprocessor) - 2025-03-29 ### Added - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) ### Fixed - make all links in the mdbook preprocessor relative ([#392](https://github.com/makspll/bevy_mod_scripting/pull/392)) - mdbook preprocessor links not taking into account root url ([#391](https://github.com/makspll/bevy_mod_scripting/pull/391))
## `ladfile_builder`
## [0.3.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.2.6-ladfile_builder...v0.3.0-ladfile_builder) - 2025-03-29 ### Added - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377))
## `bevy_mod_scripting`
## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.10.0...v0.11.0) - 2025-03-29 ### Added - allow the conversion of lua functions into `ScriptValue` via `DynamicScriptFunction` ([#396](https://github.com/makspll/bevy_mod_scripting/pull/396)) - improve tracing spans, add `profile_with_tracy` feature flag ([#394](https://github.com/makspll/bevy_mod_scripting/pull/394)) - add `profile_with_tracy` feature which plays nicely with bevy's `bevy/trace_tracy` feature ([#393](https://github.com/makspll/bevy_mod_scripting/pull/393)) - Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) - :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) - optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) - optimize access map ([#395](https://github.com/makspll/bevy_mod_scripting/pull/395)) - add ScriptValue override for printing opaque values ([#380](https://github.com/makspll/bevy_mod_scripting/pull/380)) - overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) - [**breaking**] bump bersion ### Fixed - fix global type cache not containing generic types ([#388](https://github.com/makspll/bevy_mod_scripting/pull/388)) ### Other - switch to hashbrown hashmap in the function registry ([#399](https://github.com/makspll/bevy_mod_scripting/pull/399)) - try play with hashing for access maps ([#398](https://github.com/makspll/bevy_mod_scripting/pull/398)) - allow check creation for bencher

--- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Maksymilian Mozolewski --- CHANGELOG.md | 24 +++++++++++++++++++ Cargo.toml | 14 +++++------ crates/bevy_mod_scripting_core/CHANGELOG.md | 23 ++++++++++++++++++ crates/bevy_mod_scripting_core/Cargo.toml | 2 +- crates/bevy_mod_scripting_derive/CHANGELOG.md | 6 +++++ crates/bevy_mod_scripting_derive/Cargo.toml | 2 +- .../bevy_mod_scripting_functions/CHANGELOG.md | 8 +++++++ .../bevy_mod_scripting_functions/Cargo.toml | 6 ++--- .../mdbook_lad_preprocessor/CHANGELOG.md | 11 +++++++++ .../mdbook_lad_preprocessor/Cargo.toml | 4 ++-- crates/ladfile/CHANGELOG.md | 6 +++++ crates/ladfile/Cargo.toml | 2 +- crates/ladfile_builder/CHANGELOG.md | 6 +++++ crates/ladfile_builder/Cargo.toml | 4 ++-- .../bevy_mod_scripting_lua/CHANGELOG.md | 9 +++++++ .../bevy_mod_scripting_lua/Cargo.toml | 2 +- .../bevy_mod_scripting_rhai/CHANGELOG.md | 8 +++++++ .../bevy_mod_scripting_rhai/Cargo.toml | 2 +- 18 files changed, 120 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7314ea351..2fbad8e6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.10.0...v0.11.0) - 2025-03-29 + +### Added + +- allow the conversion of lua functions into `ScriptValue` via `DynamicScriptFunction` ([#396](https://github.com/makspll/bevy_mod_scripting/pull/396)) +- improve tracing spans, add more benchmarks ([#394](https://github.com/makspll/bevy_mod_scripting/pull/394)) +- add `profile_with_tracy` feature which plays nicely with bevy's `bevy/trace_tracy` feature ([#393](https://github.com/makspll/bevy_mod_scripting/pull/393)) +- Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) +- :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) +- [**breaking**] optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) +- optimize access map ([#395](https://github.com/makspll/bevy_mod_scripting/pull/395)) +- add ScriptValue override for printing opaque values ([#380](https://github.com/makspll/bevy_mod_scripting/pull/380)) +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + +### Fixed + +- fix global type cache not containing generic types ([#388](https://github.com/makspll/bevy_mod_scripting/pull/388)) + +### Other + +- switch to hashbrown hashmap in the function registry ([#399](https://github.com/makspll/bevy_mod_scripting/pull/399)) +- try play with hashing for access maps ([#398](https://github.com/makspll/bevy_mod_scripting/pull/398)) +- allow check creation for bencher + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.9.11...v0.10.0) - 2025-03-16 ### Added diff --git a/Cargo.toml b/Cargo.toml index 14e902b0eb..767c2ee7cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting" -version = "0.10.0" +version = "0.11.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" @@ -59,8 +59,8 @@ profile_with_tracy = ["bevy/trace_tracy"] [dependencies] bevy = { workspace = true } bevy_mod_scripting_core = { workspace = true } -bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.10.0", optional = true } -bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.10.0", optional = true } +bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.11.0", optional = true } +bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.11.0", optional = true } # bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true } bevy_mod_scripting_functions = { workspace = true } bevy_mod_scripting_derive = { workspace = true } @@ -68,9 +68,9 @@ bevy_mod_scripting_derive = { workspace = true } [workspace.dependencies] profiling = { version = "1.0" } bevy = { version = "0.15.2", default-features = false } -bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.10.0" } -bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.10.0", default-features = false } -bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.10.0" } +bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.11.0" } +bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.11.0", default-features = false } +bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.11.0" } # test utilities script_integration_test_harness = { path = "crates/testing_crates/script_integration_test_harness" } @@ -84,7 +84,7 @@ bevy_console = "0.13" # rhai-rand = "0.1" criterion = { version = "0.5" } ansi-parser = "0.9" -ladfile_builder = { path = "crates/ladfile_builder", version = "0.2.6" } +ladfile_builder = { path = "crates/ladfile_builder", version = "0.3.0" } script_integration_test_harness = { workspace = true } test_utils = { workspace = true } libtest-mimic = "0.8" diff --git a/crates/bevy_mod_scripting_core/CHANGELOG.md b/crates/bevy_mod_scripting_core/CHANGELOG.md index 79566b29ab..b898f89cfe 100644 --- a/crates/bevy_mod_scripting_core/CHANGELOG.md +++ b/crates/bevy_mod_scripting_core/CHANGELOG.md @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_core-v0.10.0...bevy_mod_scripting_core-v0.11.0) - 2025-03-29 + +### Added + +- optimize access map ([#395](https://github.com/makspll/bevy_mod_scripting/pull/395)) +- optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) +- improve tracing spans, add `profile_with_tracy` feature flag ([#394](https://github.com/makspll/bevy_mod_scripting/pull/394)) +- add `profile_with_tracy` feature which plays nicely with bevy's `bevy/trace_tracy` feature ([#393](https://github.com/makspll/bevy_mod_scripting/pull/393)) +- Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) +- add ScriptValue override for printing opaque values ([#380](https://github.com/makspll/bevy_mod_scripting/pull/380)) +- :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + +### Fixed + +- fix global type cache not containing generic types ([#388](https://github.com/makspll/bevy_mod_scripting/pull/388)) + +### Other + +- switch to hashbrown hashmap in the function registry ([#399](https://github.com/makspll/bevy_mod_scripting/pull/399)) +- try play with hashing for access maps ([#398](https://github.com/makspll/bevy_mod_scripting/pull/398)) +- allow check creation for bencher + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_core-v0.9.11...bevy_mod_scripting_core-v0.10.0) - 2025-03-16 ### Added diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 450fa51a85..5dc0e80a3e 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_core" -version = "0.10.0" +version = "0.11.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/crates/bevy_mod_scripting_derive/CHANGELOG.md b/crates/bevy_mod_scripting_derive/CHANGELOG.md index 0fd83f45e8..ad5ea65e1c 100644 --- a/crates/bevy_mod_scripting_derive/CHANGELOG.md +++ b/crates/bevy_mod_scripting_derive/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_derive-v0.10.0...bevy_mod_scripting_derive-v0.11.0) - 2025-03-29 + +### Added + +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_derive-v0.9.11...bevy_mod_scripting_derive-v0.10.0) - 2025-03-16 ### Added diff --git a/crates/bevy_mod_scripting_derive/Cargo.toml b/crates/bevy_mod_scripting_derive/Cargo.toml index 3575291562..7010ee7a07 100644 --- a/crates/bevy_mod_scripting_derive/Cargo.toml +++ b/crates/bevy_mod_scripting_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_derive" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" diff --git a/crates/bevy_mod_scripting_functions/CHANGELOG.md b/crates/bevy_mod_scripting_functions/CHANGELOG.md index 7b85e94d12..43caa3b183 100644 --- a/crates/bevy_mod_scripting_functions/CHANGELOG.md +++ b/crates/bevy_mod_scripting_functions/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_functions-v0.10.0...bevy_mod_scripting_functions-v0.11.0) - 2025-03-29 + +### Added + +- optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) +- :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_functions-v0.9.11...bevy_mod_scripting_functions-v0.10.0) - 2025-03-16 ### Added diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml index f0439ce8c7..4bf3d198ef 100644 --- a/crates/bevy_mod_scripting_functions/Cargo.toml +++ b/crates/bevy_mod_scripting_functions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_functions" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" @@ -35,8 +35,8 @@ uuid = "1.11" smol_str = "0.2.2" bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_derive = { workspace = true } -bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.10.0" } -bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.10.0" } +bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.11.0" } +bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.11.0" } bevy_system_reflection = { path = "../bevy_system_reflection", version = "0.1.0" } [lints] diff --git a/crates/lad_backends/mdbook_lad_preprocessor/CHANGELOG.md b/crates/lad_backends/mdbook_lad_preprocessor/CHANGELOG.md index f409a8254e..024ee8d316 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/CHANGELOG.md +++ b/crates/lad_backends/mdbook_lad_preprocessor/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.5](https://github.com/makspll/bevy_mod_scripting/compare/v0.1.4-mdbook_lad_preprocessor...v0.1.5-mdbook_lad_preprocessor) - 2025-03-29 + +### Added + +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + +### Fixed + +- make all links in the mdbook preprocessor relative ([#392](https://github.com/makspll/bevy_mod_scripting/pull/392)) +- mdbook preprocessor links not taking into account root url ([#391](https://github.com/makspll/bevy_mod_scripting/pull/391)) + ## [0.1.4](https://github.com/makspll/bevy_mod_scripting/compare/v0.1.3-mdbook_lad_preprocessor...v0.1.4-mdbook_lad_preprocessor) - 2025-03-16 ### Added diff --git a/crates/lad_backends/mdbook_lad_preprocessor/Cargo.toml b/crates/lad_backends/mdbook_lad_preprocessor/Cargo.toml index beb363f62d..348030cc23 100644 --- a/crates/lad_backends/mdbook_lad_preprocessor/Cargo.toml +++ b/crates/lad_backends/mdbook_lad_preprocessor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mdbook_lad_preprocessor" -version = "0.1.4" +version = "0.1.5" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" @@ -15,7 +15,7 @@ readme = "readme.md" [dependencies] clap = "4" mdbook = "0.4" -ladfile = { path = "../../ladfile", version = "0.4.0" } +ladfile = { path = "../../ladfile", version = "0.5.0" } env_logger = "0.11" log = "0.4" serde_json = "1.0" diff --git a/crates/ladfile/CHANGELOG.md b/crates/ladfile/CHANGELOG.md index d59492157f..f645cefb77 100644 --- a/crates/ladfile/CHANGELOG.md +++ b/crates/ladfile/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.4.0-ladfile...v0.5.0-ladfile) - 2025-03-29 + +### Added + +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + ## [0.4.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.3.1-ladfile...v0.4.0-ladfile) - 2025-03-16 ### Added diff --git a/crates/ladfile/Cargo.toml b/crates/ladfile/Cargo.toml index fa292c4fb9..1a6a488df0 100644 --- a/crates/ladfile/Cargo.toml +++ b/crates/ladfile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ladfile" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" diff --git a/crates/ladfile_builder/CHANGELOG.md b/crates/ladfile_builder/CHANGELOG.md index 44c88bee03..9cf0925e18 100644 --- a/crates/ladfile_builder/CHANGELOG.md +++ b/crates/ladfile_builder/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0](https://github.com/makspll/bevy_mod_scripting/compare/v0.2.6-ladfile_builder...v0.3.0-ladfile_builder) - 2025-03-29 + +### Added + +- overhaul mdbook preprocessor, prettify generated docs, support dummy globals ([#377](https://github.com/makspll/bevy_mod_scripting/pull/377)) + ## [0.2.6](https://github.com/makspll/bevy_mod_scripting/compare/v0.2.5-ladfile_builder...v0.2.6-ladfile_builder) - 2025-03-16 ### Added diff --git a/crates/ladfile_builder/Cargo.toml b/crates/ladfile_builder/Cargo.toml index 4e5f46de6f..2bc36b0be8 100644 --- a/crates/ladfile_builder/Cargo.toml +++ b/crates/ladfile_builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ladfile_builder" -version = "0.2.6" +version = "0.3.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" @@ -17,7 +17,7 @@ bevy_mod_scripting_core = { workspace = true } # I don't think bevy has a top level feature for this :C bevy = { workspace = true } bevy_reflect = { version = "0.15.2", features = ["documentation"] } -ladfile = { version = "0.4.0", path = "../ladfile" } +ladfile = { version = "0.5.0", path = "../ladfile" } regex = "1.11" [dev-dependencies] diff --git a/crates/languages/bevy_mod_scripting_lua/CHANGELOG.md b/crates/languages/bevy_mod_scripting_lua/CHANGELOG.md index dc9d4dd408..05c4e1947d 100644 --- a/crates/languages/bevy_mod_scripting_lua/CHANGELOG.md +++ b/crates/languages/bevy_mod_scripting_lua/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_lua-v0.10.0...bevy_mod_scripting_lua-v0.11.0) - 2025-03-29 + +### Added + +- optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) +- allow the conversion of lua functions into `ScriptValue` via `DynamicScriptFunction` ([#396](https://github.com/makspll/bevy_mod_scripting/pull/396)) +- Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) +- :sparkles: Dynamic Script Components, `register_new_component` binding, `remove_component` no longer requires `ReflectComponent` data ([#379](https://github.com/makspll/bevy_mod_scripting/pull/379)) + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_lua-v0.9.11...bevy_mod_scripting_lua-v0.10.0) - 2025-03-16 ### Added diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 3d8c55919d..ee9cc9c25e 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_lua" -version = "0.10.0" +version = "0.11.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/crates/languages/bevy_mod_scripting_rhai/CHANGELOG.md b/crates/languages/bevy_mod_scripting_rhai/CHANGELOG.md index c97594ee4c..187c3df3f4 100644 --- a/crates/languages/bevy_mod_scripting_rhai/CHANGELOG.md +++ b/crates/languages/bevy_mod_scripting_rhai/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_rhai-v0.10.0...bevy_mod_scripting_rhai-v0.11.0) - 2025-03-29 + +### Added + +- [**breaking**] bump bersion +- optimize `get` and `set` functions, add `MagicFunctions` sub-registry ([#397](https://github.com/makspll/bevy_mod_scripting/pull/397)) +- Add initial benchmarks, integrate them into CI & add getters/settters for `Scripts` resource ([#381](https://github.com/makspll/bevy_mod_scripting/pull/381)) + ## [0.10.0](https://github.com/makspll/bevy_mod_scripting/compare/bevy_mod_scripting_rhai-v0.9.11...bevy_mod_scripting_rhai-v0.10.0) - 2025-03-16 ### Added diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index a017fa2e4e..85430f3fd2 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_rhai" -version = "0.10.0" +version = "0.11.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0"