Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,12 @@ name = "script_loading"
path = "examples/script_loading.rs"
required-features = ["lua54"]


[[example]]
name = "custom_conversions"
path = "examples/custom_conversions.rs"
required-features = ["lua54"]

[workspace.lints.clippy]
panic = "deny"
unwrap_used = "deny"
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_mod_scripting_asset/src/script_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ impl ScriptAsset {
pub fn new(s: impl Into<String>) -> Self {
s.into().into()
}

/// Returns a new script asset with the language provided
pub fn with_language(mut self, language: Language) -> Self {
self.language = language;
self
}
}
1 change: 1 addition & 0 deletions crates/bevy_mod_scripting_bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub use error::*;
pub use function::*;
pub use globals::*;
// pub use pretty_print::*;
pub use bevy_mod_scripting_world::*;
pub use conversions::*;
pub use path::*;
pub use query::*;
Expand Down
89 changes: 89 additions & 0 deletions crates/bevy_mod_scripting_core/src/derive_tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use bevy_ecs::world::World;
use bevy_mod_scripting_derive::{
ArgMeta, FromScript, GetTypeDependencies, IntoScript, TypedThrough, script_bindings,
};
use bevy_reflect::{Reflect, TypeRegistry, Typed};

#[derive(Clone, TypedThrough, GetTypeDependencies, ArgMeta, IntoScript, FromScript, Reflect)]
// this is not required in third party crates, unless they only depend on these directly
#[typed_through(bms_bindings_path = "bevy_mod_scripting_bindings")]
#[get_type_dependencies(bms_bindings_path = "bevy_mod_scripting_bindings")]
#[into_script(bms_bindings_path = "bevy_mod_scripting_bindings")]
#[from_script(bms_bindings_path = "bevy_mod_scripting_bindings")]
#[arg_meta(bms_bindings_path = "bevy_mod_scripting_bindings")]
pub struct MyThing(usize);

#[script_bindings(remote, bms_bindings_path = "bevy_mod_scripting_bindings")]
impl MyThing {
pub fn test(thing: MyThing) -> MyThing {
thing
}
}
mod tests {
use std::any::TypeId;

use bevy_ecs::world::World;
use bevy_mod_scripting_bindings::{
AppScriptFunctionRegistry, FunctionCallContext, GetTypeDependencies, ReflectReference,
ThroughTypeInfo, TypedThrough, WorldExtensions,
};
use bevy_mod_scripting_world::WorldAccessGuard;
use bevy_reflect::TypeRegistry;

use crate::derive_tests::{MyThing, register_functions};

#[test]
pub fn typed_through_represents_self() {
let info = MyThing::through_type_info();
assert!(
matches!(info, ThroughTypeInfo::TypeInfo(info) if info.type_id() == TypeId::of::<MyThing>())
);
}

#[test]
pub fn type_dependency_registers_only_self() {
let mut registry = TypeRegistry::new();
let prev_count = registry.iter().count();
<MyThing as GetTypeDependencies>::register_type_dependencies(&mut registry);
assert!(registry.contains(TypeId::of::<MyThing>()));
let next_count = registry.iter().count();
assert_eq!(next_count - prev_count, 1)
}

#[test]
pub fn bindings_function_works() {
let mut world = World::new();

world.init_resource::<AppScriptFunctionRegistry>();

let cache = WorldAccessGuard::setup_cache(&world, Default::default());
WorldAccessGuard::with_static_guard(&mut world, cache, |world| {
let world = &world;
world
.with_world_mut_access(|w| {
register_functions(w);
})
.unwrap();
let func = world
.lookup_function([TypeId::of::<MyThing>()], "test")
.unwrap();
let allocator = world.allocator();
let mut allocator = allocator.write();
let allocated = ReflectReference::new_allocated(MyThing(42), &mut allocator);
drop(allocator);
let out = func
.call(
vec![allocated.into()],
FunctionCallContext::new(bevy_mod_scripting_asset::Language::Lua),
)
.unwrap();
let out: MyThing = match out {
bevy_mod_scripting_bindings::ScriptValue::Reference(reflect_reference) => {
reflect_reference.downcast(world.clone()).unwrap()
}
_ => panic!("invalid return"),
};
assert_eq!(out.0, 42)
});
}
}
8 changes: 8 additions & 0 deletions crates/bevy_mod_scripting_core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ pub struct ScriptCallbackEvent {
pub args: Vec<ScriptValue>,
/// Whether the callback should emit a response event
pub trigger_response: bool,
/// How many times was this re-queued, when 0 it's not been processed
pub iteration: usize,
}

impl ScriptCallbackEvent {
Expand All @@ -225,6 +227,7 @@ impl ScriptCallbackEvent {
args,
recipients,
trigger_response: false,
iteration: 0,
}
}

Expand Down Expand Up @@ -268,6 +271,11 @@ impl ScriptCallbackEvent {
pub fn new_for_all_contexts<L: Into<CallbackLabel>>(label: L, args: Vec<ScriptValue>) -> Self {
Self::new(label, args, Recipients::AllContexts, None)
}

pub(crate) fn with_incremented_iteration(mut self) -> Self {
self.iteration += 1;
self
}
}

/// Event published when a script completes a callback and a response is requested.
Expand Down
12 changes: 9 additions & 3 deletions crates/bevy_mod_scripting_core/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,21 @@ pub(crate) fn event_handler_inner<P: IntoScriptPluginParams>(
event.recipients,
Recipients::ScriptEntity(_, _) | Recipients::StaticScript(_)
);

let might_not_have_reached_pipeline_if_new = event.iteration == 0;
for (attachment, ctxt) in recipients {
// we don't issue callbacks to scripts which are currently loading/unloading/reloading
let ctxt = if let Some(ctxt) = ctxt.as_loaded() {
ctxt
} else if highly_specific && ctxt.is_loading_or_reloading() {
// due to how the pipeline runs, we might see scripts which have just been
// loaded, and attached, but the pipeline hasn't run yet
// we want to give those one frame to trigger
} else if highly_specific && ctxt.is_loading_or_reloading()
|| might_not_have_reached_pipeline_if_new
{
println!("REQUEING");
// events with high specificity, have their callbacks re-queued in this case
// i.e. we don't want `on_update` to queue up, but a directed `on_collision_with_entity` callback will have
events_to_requeue.push(event.clone());
events_to_requeue.push(event.clone().with_incremented_iteration());
continue;
} else {
continue;
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_mod_scripting_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub mod callbacks;
pub mod commands;
pub mod config;
pub mod context;
#[cfg(test)]
mod derive_tests;

pub mod error;
pub mod event;
pub mod extractors;
Expand Down
50 changes: 50 additions & 0 deletions crates/bevy_mod_scripting_derive/src/derive/arg_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

use crate::derive::SharedArgs;
#[derive(Default)]
struct Args {
shared_args: SharedArgs,
}

impl Args {
fn parse(attrs: &[syn::Attribute]) -> syn::Result<Self> {
let mut shared_args = SharedArgs::default();

for attr in attrs {
if attr.path().is_ident("arg_meta") {
attr.parse_nested_meta(|meta| {
// delegate everything to SharedArgs
if shared_args.apply_nested_meta(&meta)? {
return Ok(());
}

Err(meta.error("Unknown argument to get_type_dependencies"))
})?;
}
}

Ok(Self { shared_args })
}
}
pub fn arg_meta(input: TokenStream) -> TokenStream {
let (args, ident, generics) = match syn::parse2::<DeriveInput>(input) {
Ok(derive_input) => {
let args = match Args::parse(&derive_input.attrs) {
Ok(args) => args,
Err(error) => return error.to_compile_error(),
};
(args, derive_input.ident, derive_input.generics)
}
Err(err) => return err.to_compile_error(),
};

let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

let bms_bindings_path = args.shared_args.bms_bindings_path;
quote! {
impl #impl_generics #bms_bindings_path::ArgMeta for #ident #type_generics #where_clause {
}
}
}
33 changes: 16 additions & 17 deletions crates/bevy_mod_scripting_derive/src/derive/debug_with_type_info.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
use proc_macro2::{Span, TokenStream};
use quote::format_ident;
use syn::{DeriveInput, Ident, parse_macro_input, parse_quote};
use syn::{DeriveInput, Ident, parse_macro_input};

use crate::derive::SharedArgs;

#[derive(Default)]
struct Args {
bms_display_path: syn::Path,
remote: bool,
shared_args: SharedArgs,
}

impl Args {
fn parse(input: &[syn::Attribute]) -> syn::Result<Self> {
let mut args = Args {
bms_display_path: parse_quote!(::bevy_mod_scripting::display),
shared_args: Default::default(),
remote: false,
};

for attr in input {
if attr.path().is_ident("debug_with_type_info") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("bms_display_path") {
let value = meta.value()?;
let string: syn::LitStr = value.parse()?;
args.bms_display_path = string.parse()?;
Ok(())
} else if meta.path.is_ident("remote") {
if meta.path.is_ident("remote") {
args.remote = true;
Ok(())
} else {
Err(syn::Error::new_spanned(
meta.path,
"unknown attribute, allowed: bms_display_path,remote",
))
return Ok(());
}

if args.shared_args.apply_nested_meta(&meta)? {
return Ok(());
}
})?

Err(meta.error("Unknown argument to debug_with_type_info"))
})?;
}
}

Expand All @@ -48,7 +47,7 @@ pub fn debug_with_type_info(input: proc_macro::TokenStream) -> proc_macro::Token
Err(e) => return e.to_compile_error().into(),
};

let bms_display_path = &args.bms_display_path;
let bms_display_path = &args.shared_args.bms_display_path;

// use DebugStruct, DebugTuple, DebugMap, DebugList etc from bevy_mod_scripting_display
// from the trait DebugWithTypeInfoBuilder trait on the formatter in implementing Debug i.e.:
Expand Down
61 changes: 61 additions & 0 deletions crates/bevy_mod_scripting_derive/src/derive/from_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

use crate::derive::SharedArgs;

#[derive(Default)]
struct Args {
shared_args: SharedArgs,
}

impl Args {
fn parse(attrs: &[syn::Attribute]) -> syn::Result<Self> {
let mut shared_args = SharedArgs::default();

for attr in attrs {
if attr.path().is_ident("from_script") {
attr.parse_nested_meta(|meta| {
if shared_args.apply_nested_meta(&meta)? {
return Ok(());
}

Err(meta.error("Unknown argument to into_script"))
})?;
}
}

Ok(Self { shared_args })
}
}

pub fn from_script(input: TokenStream) -> TokenStream {
let (args, ident, generics) = match syn::parse2::<DeriveInput>(input) {
Ok(derive_input) => {
let args = match Args::parse(&derive_input.attrs) {
Ok(args) => args,
Err(error) => return error.to_compile_error(),
};
(args, derive_input.ident, derive_input.generics)
}
Err(err) => return err.to_compile_error(),
};

let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let bms_bindings_path = args.shared_args.bms_bindings_path;
quote! {

impl #impl_generics #bms_bindings_path::FromScript for #ident #type_generics #where_clause {
type This<'w> = #ident #type_generics;
fn from_script(
value: #bms_bindings_path::ScriptValue,
world: #bms_bindings_path::WorldGuard<'_>,
) -> Result<Self::This<'_>, #bms_bindings_path::InteropError>
where
Self: Sized,
{
#bms_bindings_path::V::<Self>::from_script(value, world).map(#bms_bindings_path::V::into_inner)
}
}
}
}
Loading
Loading