Ruby support #1

Open
jaroslaw wants to merge 165 commits from ruby into main
8 changed files with 136 additions and 27 deletions
Showing only changes of commit fccc822f9a - Show all commits

View file

View file

View file

@ -0,0 +1,3 @@
def test_func
rust_func($entity.index)
end

View file

@ -0,0 +1,6 @@
$index = $entity.index
def test_func
print($index)
rust_func($index)
end

View file

@ -12,22 +12,23 @@ use std::{
use bevy::{ use bevy::{
asset::Asset, asset::Asset,
ecs::{component::Component, resource::Resource, schedule::ScheduleLabel}, ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel},
math::Vec3,
reflect::TypePath, reflect::TypePath,
tasks::futures_lite::io, tasks::futures_lite::io,
}; };
use magnus::prelude::*;
use magnus::{ use magnus::{
block::Proc, data_type_builder, function, value::Lazy, DataType, DataTypeFunctions, IntoValue, block::Proc, data_type_builder, function, value::Lazy, DataType, DataTypeFunctions, IntoValue,
RClass, Ruby, TryConvert, TypedData, RClass, Ruby, TryConvert, TypedData,
}; };
use magnus::{method, prelude::*};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
assets::GetExtensions, assets::GetExtensions,
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine}, callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
promise::Promise, promise::Promise,
FuncArgs, Runtime, ScriptingError, FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
}; };
#[derive(Resource)] #[derive(Resource)]
@ -142,6 +143,32 @@ fn then(r_self: magnus::Value) -> magnus::Value {
.into_value() .into_value()
} }
#[magnus::wrap(class = "BevyEntity")]
pub struct BevyEntity(pub Entity);
impl BevyEntity {
fn index(&self) -> u32 {
self.0.index()
}
}
#[magnus::wrap(class = "BevyVec3")]
pub struct BevyVec3(pub Vec3);
impl BevyVec3 {
fn x(&self) -> f32 {
self.0.x
}
fn y(&self) -> f32 {
self.0.y
}
fn z(&self) -> f32 {
self.0.z
}
}
impl Default for RubyRuntime { impl Default for RubyRuntime {
fn default() -> Self { fn default() -> Self {
let (lock, cvar) = &*Arc::clone(&RUBY_THREAD); let (lock, cvar) = &*Arc::clone(&RUBY_THREAD);
@ -159,29 +186,20 @@ impl Default for RubyRuntime {
promise promise
.define_method("and_then", magnus::method!(then, 0)) .define_method("and_then", magnus::method!(then, 0))
.unwrap(); .unwrap();
let entity = ruby
.define_class("BevyEntity", ruby.class_object())
.unwrap();
entity
.define_method("index", method!(BevyEntity::index, 0))
.unwrap();
let vec3 = ruby.define_class("BevyVec3", ruby.class_object()).unwrap();
vec3.define_method("x", method!(BevyVec3::x, 0)).unwrap();
vec3.define_method("y", method!(BevyVec3::y, 0)).unwrap();
vec3.define_method("z", method!(BevyVec3::z, 0)).unwrap();
})); }));
// TODO: add test for those types for every runtime // TODO: add test for those types for every runtime
// engine
// .register_userdata_type::<BevyEntity>(|typ| {
// typ.add_field_method_get("index", |_, entity| Ok(entity.0.index()));
// })
// .expect("Failed to register BevyEntity userdata type");
//
// engine
// .register_userdata_type::<Promise<(), LuaValue>>(|typ| {
// typ.add_method_mut("and_then", |engine, promise, callback: Function| {
// Ok(Promise::then(promise, LuaValue::new(engine, callback)))
// });
// })
// .expect("Failed to register Promise userdata type");
//
// engine
// .register_userdata_type::<BevyVec3>(|typ| {
// typ.add_field_method_get("x", |_engine, vec| Ok(vec.0.x));
// typ.add_field_method_get("y", |_engine, vec| Ok(vec.0.y));
// typ.add_field_method_get("z", |_engine, vec| Ok(vec.0.z));
// })
// .expect("Failed to register BevyVec3 userdata type");
// let vec3_constructor = engine // let vec3_constructor = engine
// .create_function(|_, (x, y, z)| Ok(BevyVec3(Vec3::new(x, y, z)))) // .create_function(|_, (x, y, z)| Ok(BevyVec3(Vec3::new(x, y, z))))
// .expect("Failed to create Vec3 constructor"); // .expect("Failed to create Vec3 constructor");
@ -262,14 +280,23 @@ impl Runtime for RubyRuntime {
fn eval( fn eval(
&self, &self,
script: &Self::ScriptAsset, script: &Self::ScriptAsset,
_entity: bevy::prelude::Entity, entity: bevy::prelude::Entity,
) -> Result<Self::ScriptData, crate::ScriptingError> { ) -> Result<Self::ScriptData, crate::ScriptingError> {
let script = script.0.clone(); let script = script.0.clone();
self.ruby_thread self.ruby_thread
.as_ref() .as_ref()
.unwrap() .unwrap()
.execute(Box::new(move |ruby| { .execute(Box::new(move |ruby| {
let var = ruby
.define_variable(ENTITY_VAR_NAME, BevyEntity(entity))
.unwrap();
let value = ruby.eval::<magnus::value::Value>(&script).unwrap(); let value = ruby.eval::<magnus::value::Value>(&script).unwrap();
// SAFETY: this is guaranteed to be executed on a single thread
// so should be safe according to Magnus documentation
unsafe { *var = ruby.qnil().as_value() };
RubyValue::new(value) RubyValue::new(value)
})); }));
Ok(RubyScriptData) Ok(RubyScriptData)
@ -336,7 +363,7 @@ impl Runtime for RubyRuntime {
&self, &self,
name: &str, name: &str,
_script_data: &mut Self::ScriptData, _script_data: &mut Self::ScriptData,
_entity: bevy::prelude::Entity, entity: bevy::prelude::Entity,
args: impl for<'a> crate::FuncArgs<'a, Self::Value, Self> + Send + 'static, args: impl for<'a> crate::FuncArgs<'a, Self::Value, Self> + Send + 'static,
) -> Result<Self::Value, crate::ScriptingError> { ) -> Result<Self::Value, crate::ScriptingError> {
let name = name.to_string(); let name = name.to_string();
@ -344,6 +371,10 @@ impl Runtime for RubyRuntime {
.as_ref() .as_ref()
.unwrap() .unwrap()
.execute(Box::new(move |ruby| { .execute(Box::new(move |ruby| {
let var = ruby
.define_variable(ENTITY_VAR_NAME, BevyEntity(entity))
.unwrap();
let args: Vec<_> = args let args: Vec<_> = args
.parse(&ruby) .parse(&ruby)
.into_iter() .into_iter()
@ -355,6 +386,11 @@ impl Runtime for RubyRuntime {
.map_err(|e| { .map_err(|e| {
ScriptingError::RuntimeError(Box::new(io::Error::other(e.to_string()))) ScriptingError::RuntimeError(Box::new(io::Error::other(e.to_string())))
})?; })?;
// SAFETY: this is guaranteed to be executed on a single thread
// so should be safe according to Magnus documentation
unsafe { *var = ruby.qnil().as_value() };
Ok(RubyValue::new(return_value)) Ok(RubyValue::new(return_value))
})) }))
} }
@ -391,7 +427,7 @@ impl Runtime for RubyRuntime {
} }
pub mod prelude { pub mod prelude {
pub use super::{RubyRuntime, RubyScript, RubyScriptData}; pub use super::{BevyEntity, BevyVec3, RubyRuntime, RubyScript, RubyScriptData};
} }
impl<T: TryConvert> FromRuntimeValueWithEngine<'_, RubyRuntime> for T { impl<T: TryConvert> FromRuntimeValueWithEngine<'_, RubyRuntime> for T {

View file

@ -378,6 +378,70 @@ macro_rules! scripting_tests {
1 1
); );
} }
#[test]
fn entity_variable_is_available_in_callback() {
let mut app = build_test_app();
#[derive(Default, Resource)]
struct State {
index: u32,
}
app.world_mut().init_resource::<State>();
app.add_scripting::<$runtime>(|runtime| {
runtime.add_function(
String::from("rust_func"),
|In((index,)): In<(u32,)>, mut res: ResMut<State>| {
res.index = index;
},
);
});
let entity = run_script::<$runtime, _, _>(
&mut app,
format!("tests/{}/entity_variable.{}", $script, $extension).to_string(),
call_script_on_update_from_rust::<$runtime>,
);
assert_eq!(
app.world().get_resource::<State>().unwrap().index,
entity.index()
);
}
#[test]
fn entity_variable_is_available_in_eval() {
let mut app = build_test_app();
#[derive(Default, Resource)]
struct State {
index: u32,
}
app.world_mut().init_resource::<State>();
app.add_scripting::<$runtime>(|runtime| {
runtime.add_function(
String::from("rust_func"),
|In((index,)): In<(u32,)>, mut res: ResMut<State>| {
res.index = index;
},
);
});
let entity = run_script::<$runtime, _, _>(
&mut app,
format!("tests/{}/entity_variable_eval.{}", $script, $extension).to_string(),
call_script_on_update_from_rust::<$runtime>,
);
assert_eq!(
app.world().get_resource::<State>().unwrap().index,
entity.index()
);
}
}; };
} }